Index: gc_lang/fr/webext/background.js
==================================================================
--- gc_lang/fr/webext/background.js
+++ gc_lang/fr/webext/background.js
@@ -331,11 +331,12 @@
 /*
     Context Menu
 */
 // Analyze
 browser.contextMenus.create({ id: "grammar_checker_editable",   title: "Analyser cette zone de texte",              contexts: ["editable"] });
-browser.contextMenus.create({ id: "grammar_checker_selection",   title: "Analyser la sélection",                     contexts: ["selection"] });
+browser.contextMenus.create({ id: "grammar_checker_selection",  title: "Analyser la sélection",                     contexts: ["selection"] });
+browser.contextMenus.create({ id: "grammar_checker_iframe",     title: "Analyser le contenu de ce cadre",           contexts: ["frame"] });
 browser.contextMenus.create({ id: "grammar_checker_page",       title: "Analyser la page",                          contexts: ["all"] });
 browser.contextMenus.create({ id: "separator_tools",            type: "separator",                                  contexts: ["all"] });
 // Tools
 browser.contextMenus.create({ id: "conjugueur_tab",             title: "Conjugueur [onglet]",                       contexts: ["all"] });
 browser.contextMenus.create({ id: "conjugueur_window",          title: "Conjugueur [fenêtre]",                      contexts: ["all"] });
@@ -354,10 +355,13 @@
         // analyze
         case "grammar_checker_editable":
         case "grammar_checker_page":
             sendCommandToTab(xTab.id, xInfo.menuItemId);
             break;
+        case "grammar_checker_iframe":
+            sendCommandToTab(xTab.id, xInfo.menuItemId, xInfo.frameId);
+            break;
         case "grammar_checker_selection":
             sendCommandToTab(xTab.id, xInfo.menuItemId, xInfo.selectionText);
             oWorkerHandler.xGCEWorker.postMessage({
                 sCommand: "parseAndSpellcheck",
                 dParam: {sText: xInfo.selectionText, sCountry: "FR", bDebug: false, bContext: false},

ADDED   gc_lang/fr/webext/content_scripts/api.js
Index: gc_lang/fr/webext/content_scripts/api.js
==================================================================
--- /dev/null
+++ gc_lang/fr/webext/content_scripts/api.js
@@ -0,0 +1,56 @@
+// JavaScript
+
+"use strict";
+
+
+const oGrammalecteAPI = {
+    // functions callable from within pages
+
+    sVersion: "1.0",
+
+    parse: function (arg1) {
+        let xNode = null;
+        if (typeof(arg1) === 'string') {
+            if (document.getElementById(arg1)) {
+                xNode = document.getElementById(arg1);
+            } else {
+                this.parseText(arg1);
+            }
+        }
+        else if (arg1 instanceof HTMLElement) {
+            xNode = arg1;
+        }
+        if (xNode) {
+            console.log("xnode");
+            if (xNode.tagName == "INPUT"  ||  xNode.tagName == "TEXTAREA"  ||  xNode.isContentEditable) {
+                this.parseNode(xNode);
+            }
+            else if (xNode.tagName == "IFRAME") {
+                this.parseText(xNode.contentWindow.document.body.innerText);
+            }
+            else {
+                this.parseText(xNode.innerText);
+            }
+        }
+    },
+
+    parseNode: function (xNode) {
+        console.log("parseNode");
+        if (xNode instanceof HTMLElement) {
+            let xEvent = new CustomEvent("GrammalecteCall", { detail: {sCommand: "parseNode", xNode: xNode} });
+            document.dispatchEvent(xEvent);
+        } else {
+            console.log("[Grammalecte API] Error: parameter is not a HTML node.");
+        }
+    },
+
+    parseText: function (sText) {
+        console.log("parseText");
+        if (typeof(sText) === "string") {
+            let xEvent = new CustomEvent("GrammalecteCall", { detail: {sCommand: "parseText", sText: sText} });
+            document.dispatchEvent(xEvent);
+        } else {
+            console.log("[Grammalecte API] Error: parameter is not a text.");
+        }
+    }
+}

Index: gc_lang/fr/webext/content_scripts/init.js
==================================================================
--- gc_lang/fr/webext/content_scripts/init.js
+++ gc_lang/fr/webext/content_scripts/init.js
@@ -13,10 +13,11 @@
 
 "use strict";
 
 
 function showError (e) {
+    // console can’t display error objects from content scripts
     console.error(e.fileName + "\n" + e.name + "\nline: " + e.lineNumber + "\n" + e.message);
 }
 
 // Chrome don’t follow the W3C specification:
 // https://browserext.github.io/browserext/
@@ -86,34 +87,20 @@
     _prepareButtons: function (oOptions) {
         if (oOptions.hasOwnProperty("ui_options")) {
             this.oOptions = oOptions.ui_options;
             // textarea
             for (let xNode of document.getElementsByTagName("textarea")) {
-                if (xNode.dataset.grammalecte_callbutton && document.getElementById(xNode.dataset.grammalecte_callbutton)) {
-                    let xButton = document.getElementById(xNode.dataset.grammalecte_callbutton)
-                    xButton.onclick = () => {
-                        oGrammalecte.startGCPanel(xNode);
-                    };
-                    this.lButton.push(xButton);
-                    this.nButton += 1;
-                }
-                else if (this.oOptions.textarea  &&  xNode.style.display !== "none" && xNode.style.visibility !== "hidden" && xNode.getAttribute("spellcheck") !== "false") {
+                if (this.oOptions.textarea  &&  xNode.style.display !== "none" && xNode.style.visibility !== "hidden" && xNode.getAttribute("spellcheck") !== "false"
+                    && !(xNode.dataset.grammalecte_button  &&  xNode.dataset.grammalecte_button == "false")) {
                     this.lButton.push(new GrammalecteButton(this.nButton, xNode));
                     this.nButton += 1;
                 }
             }
             // editable nodes
             for (let xNode of document.querySelectorAll("[contenteditable]")) {
-                if (xNode.dataset.grammalecte_callbutton && document.getElementById(xNode.dataset.grammalecte_callbutton)) {
-                    let xButton = document.getElementById(xNode.dataset.grammalecte_callbutton)
-                    xButton.onclick = () => {
-                        oGrammalecte.startGCPanel(xNode);
-                    };
-                    this.lButton.push(xButton);
-                    this.nButton += 1;
-                }
-                else if (this.oOptions.editablenode  &&  xNode.style.display !== "none" && xNode.style.visibility !== "hidden") {
+                if (this.oOptions.editablenode  &&  xNode.style.display !== "none" && xNode.style.visibility !== "hidden"
+                    && !(xNode.dataset.grammalecte_button  &&  xNode.dataset.grammalecte_button == "false")) {
                     this.lButton.push(new GrammalecteButton(this.nButton, xNode));
                     this.nButton += 1;
                 }
             }
         }
@@ -385,19 +372,63 @@
             oGrammalecte.startGCPanel(oGrammalecte.getPageText());
             break;
         case "grammar_checker_selection":
             oGrammalecte.startGCPanel(result, false); // result is the selected text
             // selected text is sent to the GC worker in the background script.
+            break;
+        case "grammar_checker_iframe":
+            console.log("[Grammalecte] selected iframe: ", result);
+            if (document.activeElement.tagName == "IFRAME") {
+                //console.log(document.activeElement.id); frameId given by result is different than frame.id
+                oGrammalecte.startGCPanel(document.activeElement.contentWindow.document.body.innerText);
+            } else {
+                oGrammalecte.showMessage("Erreur. Le cadre sur lequel vous avez cliqué n’a pas pu être identifié. Sélectionnez le texte à corriger et relancez le correcteur via le menu contextuel.");
+            }
             break;
         // rescan page command
         case "rescanPage":
             oGrammalecte.rescanPage();
             break;
         default:
             console.log("[Content script] Unknown command: " + sActionDone);
     }
 });
+
+
+
+/*
+    Callable API for the webpage.
+    The API script must be injected this way to be callable by the page
+*/
+let xScriptGrammalecteAPI = document.createElement("script");
+xScriptGrammalecteAPI.src = browser.extension.getURL("content_scripts/api.js");
+document.documentElement.appendChild(xScriptGrammalecteAPI);
+
+document.addEventListener("GrammalecteCall", function (xEvent) {
+    // GrammalecteCall events are dispatched by functions in the API
+    try {
+        let oCommand = xEvent.detail;
+        switch (oCommand.sCommand) {
+            case "parseNode":
+                if (oCommand.xNode) {
+                    oGrammalecte.startGCPanel(oCommand.xNode);
+                }
+                break;
+            case "parseText":
+                if (oCommand.sText) {
+                    oGrammalecte.startGCPanel(oCommand.sText);
+                }
+                break;
+            default:
+                console.log("[Grammalecte] Event: Unknown command", oCommand.sCommand);
+        }
+    }
+    catch (e) {
+        showError(e);
+    }
+});
+
 
 
 /*
     Other messages from background
 */

Index: gc_lang/fr/webext/manifest.json
==================================================================
--- gc_lang/fr/webext/manifest.json
+++ gc_lang/fr/webext/manifest.json
@@ -93,10 +93,11 @@
       "description": "Ouvre l’éditeur lexical"
     }
   },
 
   "web_accessible_resources": [
+    "content_scripts/api.js",
     "content_scripts/panel.css",
     "content_scripts/panel_tf.css",
     "content_scripts/panel_gc.css",
     "content_scripts/panel_lxg.css",
     "content_scripts/panel_conj.css",