9

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.

Rikonator
  • 1,830
  • 16
  • 27
  • are there iframes in your document? they may be getting attached to that as well. – Noitidart Mar 13 '14 at 16:27
  • @Noitidart Actually, yes. And the number of iframes in the document seem to correspond to the number of times the workers are attached. Nice catch! Is there a way to stop the scripts being attached to iframes? – Rikonator Mar 13 '14 at 16:39
  • +1 for sharing solution thanks man – Noitidart Mar 14 '14 at 05:21
  • I cant tell you how much I appreciate that you shared the solution. I was nearly going insane with that error. THX a lot, man! – user1252280 Jun 30 '14 at 22:20

2 Answers2

4

In the contentScript or whatever is running in the web site (i dont know much about sdk), check if (window.frameElement == true) if its true than its a frame then you want to cancel the attach.

edit: maybe window.frameElement wont work from within the frame. if it doesnt work do this if (window.top != window) then its a frame

Noitidart
  • 35,443
  • 37
  • 154
  • 323
  • I hadn't considered something so simple as calling `worker.destroy()` when I detected an `iframe` (using `window!==window.top`). Thanks for your help! I really appreciate it. – Rikonator Mar 13 '14 at 17:06
  • Can you share which way worked in the worker or whatever, so I know for future when I help addon sdk users. Thanks – Noitidart Mar 13 '14 at 17:06
  • Also pleas share your final simplified code so others can benefit too please :) These topics is where I learn about sdk. lol – Noitidart Mar 13 '14 at 17:07
  • I'm quickly checking a few things and trying to see which would be the most optimal fix. Right now, it's destroying workers attached to iframes. But I'd rather find a way so that the main content script isn't attached to these workers at all. I'll edit my question in a few minutes with suitable code that works best. – Rikonator Mar 13 '14 at 17:12
2

Here's a link to the Mozilla documentation you can use the attachTo setting. The default setting of 'all' includes iframes, so you should do something like the following

var pageMod = require("sdk/page-mod");
  pageMod.PageMod({
  include: "*",
  contentScript: "",
  attachTo: ["existing", "top"],
  onAttach: function(worker) {
   console.log(worker.tab.url);
 }
});
ejectamenta
  • 1,047
  • 17
  • 20
  • This solved my issue, the page was loading iframes inside the content and my code was being called each time an iframe finished loading. – Brett Dec 16 '15 at 01:07