erv erv - 1 month ago 13
Javascript Question

tabs.executeScript() not injecting script in content page of extension

I am just starting to learn how to create my own addon in firefox. I'm using webExtensions, although I'm finding it hard to get comprehensive documentation (I think because it's still a new implementation?). The code all seems to work fine, and it completes an 'onExecuted' function after the executeScript call. But my injected script (output.js) doesn't seem to fire. After running the code I get the following error in the javascript console: "No matching message handler". I wasn't sure whether the injected script is supposed to be a self contained js file, or if it is just a collection of js commands. I tried the latter too (just a simple message box), and that didn't work either.

My code:
manifest.json:

{
"manifest_version": 2,
"name": "ExportTabs",
"version": "0.9",

"description": "Export/Import Tabs",
"icons": {
"48": "icons/export48.png"
},

"permissions": [
"tabs",
"activeTab",
"<all_urls>"
],

"applications": {
"gecko": {
"id": "938745934875@example.com"
}
},

"browser_action": {
"default_icon": "icons/export.png",
"default_popup": "popup/popup.html"
}
}


My popup.html when the addon button is pressed:

<!DOCTYPE html>

<html>
<head>
<meta charset="utf-8">
</head>

<body>
<div>
<button onclick="save()">Save Tabs</button>
<button onclick="view()">View Tabs</button>
</div>

<script src="popup.js"></script>
</body>

</html>


The popup.js file:

function save() {
chrome.tabs.create({"url": chrome.extension.getURL("popup/output.html")}, onCreated);
}

function onCreated(newTab) {
chrome.tabs.executeScript(newTab.id, {
file: "popup/output.js",
allFrames: true
}, onExecuted);
}

function onExecuted(result) {
alert("inside onExecuted");
}


ouput.html for the newly created tab:

<!DOCTYPE html>

<html>
<head>
<meta charset="utf-8">
</head>

<body>
<div>testing stuff</div>
<div id="output"></div>

</body>

</html>


output.js for the above page:

//output.js
(function() {
alert("inside output.js");
})();


I should add that I've got noscript addon, so have tried this with "allow scripts globally" and it still gives the same error.
Cheers for any help.

edit: I tried replacing the
file: "popup/output.js",
in the executeScript method with
code: 'alert("hello");',
and it still didn't work.

Information moved from Answer posted by OP:

Bump. I tried this code out on another box with ff50.something (beta channel), and the injection doesn't even make 'onExecuted'. But it doesn't throw any errors. Basically the popup works, and then nothing happens after that.

Answer

Individual problem analysis

Problem "No matching message handler"

That the message "No matching message handler" is shown is a known bug in Firefox versions prior to 49. The bug is documented as Bug 1254003.

Problem "executeScript does not inject script"

In versions 49+, it still doesn't work because of this documented restriction:

You can only inject code into pages whose URL can be expressed using a match pattern: meaning, its scheme must be one of "http", "https", "file", "ftp". This means that you can't inject code into any of the browser's built-in pages, such as about:debugging, about:addons, or the page that opens when you open a new empty tab.

So, you cannot use tabs.executeScript() to inject scripts into content loaded from your own extensions. The example works when I replace loading a page from the extension with loading a page from the web:

function save() 
{
    chrome.tabs.create({"url": "https://en.wikipedia.org/wiki/Bacon"}, onCreated);
}

When you want to execute code in the page loaded from your extension, there are two possibilities:

  1. Execute the JavaScript directly by loading a script file in your content page
  2. If you need to pass parameters to the script inside the loaded content page, you can send messages between the script that opens the tab and the script inside the opened tab. See chapter "Communicating with background scripts" on Content Scripts documentation.

Sample code:

Function Save in popup.js.

function onCreated(newTab) 
{
    // Use setTimeOut to give the loaded page some time to register the message listener
    setTimeout(onCreatedInner, 500);
    function onCreatedInner()
    {   
        console.log("oncreated " +   " " +  newTab.id);
        chrome.runtime.sendMessage(42);
    }
}

output.js:

chrome.runtime.onMessage.addListener(notify);

function notify(message) 
{
    window.alert(message);

}

Problem "JavaScript code in popup is not executed"

On your friend's setup you run into a different problem: By default, you are not allowed to use inline scripts in content pages in web extensions in Firefox. You should see that in the browser console you get the error

Content Security Policy: The page's settings blocked the loading of a resource at self

So, instead of using an inline "onclick" with the save button, assign the onclick event dynamically from your JavaScript script. How to change the popup.html:

<button onclick="save" id="buttonSave">Save Tabs</button>
<button >View Tabs</button>

Code to add at the beginning of popup.js:

var elem = document.getElementById("buttonSave");
elem.onclick  = save;

Full working example

Here is the full minimal sample code that works as expected:

File manifest.json

{
    "manifest_version": 2,
    "name": "ExportTabs",
    "version": "0.9",

    "description": "Export/Import Tabs",
    "icons": {
        "48": "icons/export48.png"
    },


    "permissions": [
        "tabs",
        "activeTab",
        "<all_urls>"
    ],

    "applications": {
        "gecko": {
            "id": "938745934875@example.com"
        }
    },

    "browser_action": {
        "default_icon": "icons/export.png",
        "default_popup": "popup/popup.html" 
    }
}

File popup.html

<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
  </head>

  <body>
    <div>
        <button onclick="save" id="buttonSave">Save Tabs</button>
        <button >View Tabs</button>
    </div>

    <script src="popup.js"></script>
  </body>

</html>

File popup.js

// Register onclick-Event for Save Button
var elem = document.getElementById("buttonSave");
elem.onclick  = save;

function save() 
{
     chrome.tabs.create({"url": chrome.extension.getURL("popup/output.html")}, onCreated);
}

function onCreated(newTab) 
{
    // Use setTimeOut to give the loaded page some time to register the message listener
    setTimeout(onCreatedInner, 500);
    function onCreatedInner()
    {   
        chrome.runtime.sendMessage(42);
    }
}

File output.html

<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
  </head>

  <body>
    <div>testing stuff</div>
    <div id="output"></div>

  </body>
  <script src="output.js"></script>

</html>

File output.js

// register message handler
chrome.runtime.onMessage.addListener(notify);

function notify(message) 
{
    // Write message into page 
    var elem = document.getElementById("output");
    elem.innerText  = message;

    // Show alert
    window.alert(message);
}