CLEAN SOLUTION FOUND
I found a very clean solution which really renders this whole question pointless, and I am certain it existed when I asked this question... I was much to ignorant too look for it.
Using attachTo: 'top'
in the PageMod
constructor only attaches the script to the top-level documents and not to any iframes
.
So, if you find that PageMod
is attaching multiple times for your add-on, it is probably due to it being attached to iframes
, along with the top level tab document. Add attachTo: 'top'
as a property for the object passed to the PageMod
constructor, and you wouldn't need to worry about iframes
.
For the question below, the solution would be
var _workers = [];
var pageMod = require("sdk/page-mod").PageMod({
include: /https?://www.websitename.net.*/,
contentScript: "self.port.on('hello', function() { " +
"console.log('['+document.location.href+']: " +
"My worker said hello to me!');",
contentScriptWhen: "end",
attachTo: 'top', //<-- add this property to only attach to top level document
onAttach: function(worker) {
_workers.push(worker);
worker.on("detach", function() {
var ind = _workers.indexOf(this);
if(ind !== -1) {
_workers.splice(ind, 1);
}
});
worker.port.emit("hello");
}
});
Of course, without @Noitidart's help, I never would have pinpointed the reason to be iframes
.
OLD QUESTION
I'm trying to write an add-on for Firefox which modifies the pages of a particular website. I thought PageMod
was the perfect option for that, but I've run into some problems. The workers seem to be attaching to the same URL multiple times (4 to 2), and I haven't got a clue why this would be happening.
I tried the following code in the add-on's main.js:
var _workers = [];
var pageMod = require("sdk/page-mod").PageMod({
include: /https?://www.websitename.net.*/,
contentScript: "self.port.on('hello', function() { " +
"console.log('['+document.location.href+']: " +
"My worker said hello to me!');",
contentScriptWhen: "end",
onAttach: function(worker) {
_workers.push(worker);
worker.on("detach", function() {
var ind = _workers.indexOf(this);
if(ind !== -1) {
_workers.splice(ind, 1);
}
});
worker.port.emit("hello");
}
});
On running this code and opening https://www.websitename.net
in a single, solitary tab, the console had 2 to 4 instances of
[https://www.websitename.net/]: My worker said hello to me!
Which means that the same page has multiple workers attached to it. I don't know why this is happening, I certainly don't want it to because I intend to later use the _workers
array to communicate with the attached scripts.
EDIT:
Thanks to @Noitidart, I've now confirmed that of the multiple workers, only one is the actual page and the others are iframes, which is why the DOM content available to the content scripts for each worker was different.
The question still stands, though. How to ensure that the workers (and thus, the content scripts) are not attached to the iframes?
I could ensure that the worker that isn't attached to an iframe
is the one pushed to _workers
, but is there a way to attach a content script to a worker once I've ensured it is the correct one? I don't want the rather large content script to be attached multiple times.
EDIT 2:
Again, thanks to @Noitidart, I think I have somewhat of a solution: destroy if worker is detected to be attached to an iframe
.
A better solution would be to only attach the main content script to the correct worker, but presently I have been unable to find a way to attach a content script to the correct worker (worker.contentScriptFile = data.url("file.js")
doesn't seem to work).
So, the current hack:
verify.js:
if(window.top !== window) {
self.port.emit('iframe');
}
else {
self.port.emit('hi');
}
main.js:
var _workers = [];
var pageMod = require("sdk/page-mod").PageMod({
include: /https?://www.websitename.net.*/,
contentScriptFile: [data.url("verify.js"), data.url("my_main_script.js")],
contentScriptWhen: "end",
onAttach: function(worker) {
worker.on("detach", function() {
var ind = _workers.indexOf(this);
if(ind !== -1) {
_workers.splice(ind, 1);
}
});
worker.port.on("hi", function() {
//Not iframe, keep this one.
//If there is a way, attach 'my_main_script.js'
//to this worker now, and not before.
_workers.push(worker);
//Emit 'ack' to tell my_main_script.js
//that we're good to go.
worker.port.emit('ack');
});
worker.port.on("iframe", function() {
//iframe, get rid of it.
worker.destroy();
});
}
});
I should note that the order in which the scrips appear in contentScriptFile
doesn't matter, both scripts are loaded for each attachment. So no point hoping that verify.js destroys the worker before my_main_script.js is loaded.
And again, this solution is not complete. If I haven't emphasized enough on this point, I really hope there's a way to attach my_main_script.js to the correct worker, instead of having it load for all workers. Please comment/answer if there is such a way.
See Question&Answers more detail:
os