4

I'm trying to write a restartless add-on for Firefox Mobile that will insert content onto specific web pages. It all seems to work OK until I try disabling then re-enabling the add-on, at which point I get multiple responses to the page load event, and I can't figure out a way to sort them out.

Since Fennec uses the Electrolysis multi-process platform, I know that I need to split my code into chrome and content scripts. My bootstrap.js looks something like this (trimmed for clarity):

function startup(data, reason) {
  mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIChromeFrameMessageManager);
  mm.loadFrameScript(getResourceURISpec('content.js'), true);
}

function shutdown(data, reason) {
  let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIChromeFrameMessageManager);
  mm.sendAsyncMessage("GeoMapEnh:Disable", {reason: reason});
}

function install(data, reason) {}
function uninstall(data, reason) {}

Basically, the bootstrap.js just launches a content script, and sends a message to tell it to clean up on shutdown. The content.js sets up an eventListener to watch for page loads, that looks a bit like this:

addMessageListener("GeoMapEnh:Disable", disableScript);
addEventListener("DOMContentLoaded", loadHandler, false );

function loadHandler(e) {
  LOG("Page loaded");
  // Do something cool with the web page.
}

function disableScript(aMessage) {
  if (aMessage.name != "GeoMapEnh:Disable") {
    return;
  }
  LOG("Disabling content script: " + aMessage.json.reason);
  try {
    removeEventListener("DOMContentLoaded", loadHandler, false );
    removeMessageListener("GeoMapEnh:Disable", disableScript);
  } catch(e) {
    LOG("Remove failed: " + e);
  }
}

function LOG(msg) {
  dump(msg + "\n");
  var consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
  consoleService.logStringMessage(msg);
}

When I first run the extension, everything works fine. An instance of content.js is executed for each browser tab (and any new tabs I open) and my eventListener detects the page loads it is supposed to via the DOMContentLoaded event. When I disable the extension, everything still seems OK: page loads stop being detected.

When I re-enable the extension, it all goes wrong. I still get an instance of content.js executing for each open tab, but now, if I open new tabs, DOMContentLoaded triggers mutiple eventListeners and I can't distinguish which one should handle the event. Worse yet, some of the eventListeners are active, but do not give debug info via my LOG function, and do not all of them get removed if I disable the extension a second time.

I do want to watch all browser tabs, including any new ones, but I only want my extension to insert content on the page that triggers it, and only once per page load. I've tried the following without success:

  • Calling e.stopPropagation() to stop the event being passed to other listeners. No effect.
  • Calling e.preventDefault() and testing e.defaultPrevented to see if the event has already been handled. It never has.
  • Testing if (this === content.document) to see if the eventListener has been triggered by its own page content. Doesn't work, as I get multiple "true" responses.
  • Making the eventListener capturing. No effect.
  • Using the load event rather than DOMContentLoaded.

I can't set a shared variable to say the event has been handled as under Electrolysis, the different eventListeners will be executing in different contexts. Also, I wouldn't be able to distinguish between multiple tabs loading the same page and one page load being detected multiple times. I could do this via IPC message passing back to the chrome bootstrap script, but I then I wouldn't know how to address the response back to the correct browser tab.

Any ideas? Is this a Firefox bug, or am I doing something silly in my code? I am using Fennec Desktop v4 for development, and will target Fennec Android v6 for production.

JRI
  • 1,766
  • 13
  • 25
  • After more fiddling, it looks like it could be something to do with `messageManager.loadFrameScript()`, and specifically [Bug 681206](https://bugzilla.mozilla.org/show_bug.cgi?id=681206). Guess I'll have to wait for the fix, put up with multiple scripts piling up, and add extra checking when I'm about to insert content. – JRI Sep 11 '11 at 21:58
  • Isn't that why you send `GeoMapEnh:Disable` to your "old" content script - to make sure it will drop dead even though you cannot unload it? – Wladimir Palant Sep 12 '11 at 06:03
  • That's what I thought too! Sending the `GeoMapEnh:Disable` message does kill the content scripts, but as soon as you open another tab, `loadFrameScript()` starts new content scripts, even when the extension is supposed to be disabled. – JRI Oct 13 '11 at 06:51
  • Ah, right - the existing content scripts drop dead but you cannot prevent new ones from being created. Looks like the bug will be fixed in Fennec 9 (to be released on December 20th) so you will be able to call `mm.removeDelayedFrameScript(getResourceURISpec('content.js'))` on shutdown (sending `GeoMapEnh:Disable` should still be necessary). – Wladimir Palant Oct 13 '11 at 07:00
  • My temporary workaround is to have the content script send a synchronous message to ask whether the extension is enabled, before it launches its message listeners. This mainly solves the problem, but means that I have to leave the matching `MessageListener` live in `bootscript.js` when the extension is disabled. – JRI Oct 14 '11 at 07:24
  • What happens if you don't leave that message listener around? I think that `sendSyncMessage()` will return an empty array then and you will know that the extension is disabled. – Wladimir Palant Oct 14 '11 at 07:49
  • Thanks - that worked great for disabling cleanly! But it gets worse: if I re-enable the extension, I still have the old `loadFrameScript()` starting new content scripts, but a new instance of `bootstrap.js` starts another `loadFrameScript()`, and I get more duplicates. I can't see how to avoid this as I can't tell whether `bootstrap.js` is running for the first time. Instances can't communicate and I get ADDON_ENABLE both when the extension is /re/-enabled and when it runs the first time after Fennec starts with the add-on disabled. I may just have to wait for Fennec 9 :( – JRI Oct 17 '11 at 20:48

0 Answers0