I'm working on a small extension with the Firefox addon-sdk that has to alter the content of DOM elements in pages. I'm using PageMod
to add the content script and register some events, some of which I want to pass along a callback function to, like this :
main.js
pageMod.PageMod({
include: "*",
contentScriptWhen: 'ready',
contentScriptFile: [self.data.url("my_content_script.js")],
onAttach: function(worker) {
worker.port.on("processElement", function(elementSrc, callback) {
doSomeProcessingViaJsctypes();
callback("http://someUrl/image.png");
});
}
});
my_content_script.js
var elements = document.getElementsByTagName("img");
var elementsLength = elements.length;
for (var i = 0; i < elementsLength; i++)
{
(function(obj) {
obj.setAttribute("data-processed", "true");
self.port.emit("processElement", obj.src, function(newSrc) {
console.log("replaced " + obj.src);
obj.src = newSrc;
});
})(elements[i]);
}
The error
TypeError: callback is not a function
Stack trace:
.onAttach/<@resource://gre/modules/XPIProvider.jsm -> file:///c:/users/sebast~1/appdata/local/temp/tmpjprtpo.mozrunner/extensions/jid1-gWyqTW27PXeXmA@jetpack/bootstrap.js -> resource://gre/modules/commonjs/toolkit/loader.js -> resource://jid1-gwyqtw27pxexma-at-jetpack/testextension/lib/main.js:53
I can't seem to find anything on the matter on the web. I need this approach since the processing takes a bit of time and depends on a .dll file so I can't call it from the content script.
If I were to process the element and after that call a worker.port.emit()
I would have to iterate through the entire tree again to identify the element and change it's src
attribute. This will take a long time and would add extra loops for each img
in the document.
I've thought about generating a unique class name and appending it to the element's classes and then calling getElementsByClassName()
. I have not tested this but it seems to me that it would take the same amount of time as the process I described above.
Any suggestions would be appreciated.
EDIT : I have found this answer on a different question. Wladimir Palant suggests using window-utils
to get the activeBrowserWindow
and then iterate thorough it's content
.
He also mentions that
these low-level APIs aren't guaranteed to be stable and the window-utils module isn't even fully documented
Has anything changed since then? I was wondering if you can get the same content
attribute using the tabs and if you can identify the tab from which a worker sent a self.port.emit()
.
When using messaging between content-scripts and your modules (main.js
), you can only pass data around that is JSON-serializable.
Passing <img>.src
should be OK, as this a string, and therefore JSON-serializable.
Your code breaks because of the callback function you're trying to pass, since function
is not JSON-serializable (same as whole DOM nodes are not JSON-serializable).
Also, .emit
and .on
use only the first argument as the message payload.
Instead of a callback, you'll have to actually emit
another message back to the content script after you did your processing. And since you cannot pass DOM elements, you'll need to keep track of what DOM element belongs to what message.
Alright, here is for example how I'd do it.
First main.js
:
const self = require("sdk/self");
const {PageMod} = require("sdk/page-mod");
function processImage(src) {
return src + " dummy";
}
PageMod({
include: "*",
contentScriptWhen: 'ready',
contentScriptFile: [self.data.url("content.js")],
onAttach: function(worker) {
worker.port.on("processImage", function(data) {
worker.port.emit("processedImage", {
job: data.job,
newSrc: processImage(data.src)
});
});
}
});
In my design, each processImage
message has a job
associated with it (see the content script), which main.js
considers opaque and just posts back verbatim with the response.
Now, data/content.js
, aka. my content script:
var jobs = new Map();
var jobCounter = 0;
self.port.on("processedImage", function(data) {
var img = jobs.get(data.job);
jobs.delete(data.job);
var newSrc = data.newSrc;
console.log("supposed replace", img.src, "with", newSrc);
});
for (var i of document.querySelectorAll("img")) {
var job = jobCounter++; // new job number
jobs.set(job, i);
self.port.emit("processImage", {
job: job,
src: i.src
});
}
So essentially for each image, we will create a job number (could be an uuid or whatever instead, but incrementing a counter is good enough for our use case), and put the DOM image associated with that job number into a map to keep track of it.
After that is, just post the message to main.js
.
The processedImage
handler, will the receive back the job number and new source, use the job number and jobs
map get back the DOM element, remove it from the map again (we don't wanna leak it stuff) and do whatever processing is required; in this example just log stuff.