Content script unable to send messages via send.port.emit

I have encountered undocumented behaviour in an SDK extension I am currently working on and would appreciate help from the community to find either a fix or a way around it.

The problem only occurs when an HTML page embedded in the extension is opened in a tab, like so:

tabs.open(data.url("html/test.html"));

Note that test.html above would reside in data/html from the extension’s root directory.

I’m injecting a content script and listening for messages emitted as given below:

tabs.once("open", function (tab) {
  var w = tab.attach({ contentScriptFile: [ data.url("js/embed.js") ]});

  w.port.once("init", function () {
    console.log("content script initialised");
  });
});

The content script, data/js/embed.js, contains the following:

console.log("attaching embedded script");
self.port.emit("init");

The problem is that if the content script is attached to a data HTML page (a page embedded in the extension), messages sent by the content script are suppressed.

It is absolutely crucial that my extension opens an embedded HTML page and is able to both receive from and send messages to the extension. How can I overcome this limitation?


I’ve created a tiny extension that reproduces the problem clearly. You can find it here.

1 Like

Found a way of circumventing the limitation but it’s rather hacky. It consists of grabbing the content window object of the tab and then relying on the Window’s postMessage API. Like so:

var tabs = require("sdk/tabs"),
    self = require("sdk/self"),
    data = self.data,
    utils = require("sdk/tabs/utils"),
    {viewFor} = require("sdk/view/core");

tabs.once("ready", function (tab) {
  var window = utils.getTabContentWindow(viewFor(tab));
  window.addEventListener("message", function (ev) {
    console.log("message received: ", ev.data);
  });
});

tabs.open(data.url("html/test.html"));

Then in a script loaded by the HTML page, one would do something similar to:

window.postMessage({ method: "init", state: true }, origin);

I would still like to know why one can’t use the native self.port.emit API though? Or why it quietly suppresses attempts at emitting messages.

I think all you need to do is take your original script and change “open” to “ready”.

Hmm, that’s strange.

When the opened tab refers to a web page (non extension), it is possible to emit messages to the content script in the tab’s open event. But not so when the opened tab refers to an extension page – then, as MartinJ correctly says, the content script only receives the message if it is emitted in the ready event.

Why though?

Thank you, Martin, for the excellent observation!

I think it’s supposed to fail in both cases.
Think about it, when the tab opens there is nothing loaded yet, so what will your worker attach to.
I think you might have tested the script against a cached page or something.
In which case, it’s loaded instantly??

Just a guess.

Yes, there is nothing loaded yet when the open event is triggered but that should be of no consequence since I’m specifically attaching a content script to the page with which the the addon attempts to communicate with. I argue that attaching the content script in the open or ready events shouldn’t matter, especially since they run in their own execution contexts.