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.