Web Workers allow you to run code in the background in browsers such as Firefox. This is how to build one into a Firefox Extension, which is slightly different than from just creating one as normal on a page. The documentation for doing this is basically non-existent, so hopefully you’ll find this useful.
Please make sure you have a development environment set up similar to the one described here in my previous post.
How do workers work?
- Workers in /data/ are not directly connected to scripts in /lib/
- However, they can communicate by sending messages to each other
- These messages are text only, so could contain serialized JSON, but nothing else
- You’ll notice below that we are basically just slinging messages between two scripts
The code for the worker
Navigate to the /data/ directory and create a file called hello_world.js
> pwd /Users/mruttley/Documents/test > ls data lib package.json test > cd data/ > vim hello_world.js
Now paste the following in there (new users of vim, press
ito start typing and
Escfollowed by
:wqto save):
//Code for the worker self.onmessage = function(messageFromClient) { self.postMessage("Hello " + messageFromClient.data); };
This says that whenever the worker receives a Message from the client, then send a message back with the word “Hello” prepended.
One note here: In workers, you can’t use the useful function
console.log("message"), instead use
dump("message")
Let’s call the worker from the main code
Let’s navigate back to the
/lib/folder and edit the
main.jsfile, which is the first thing that runs in the extension.
> cd ../lib/ > vim main.js
Paste in the following code:
var worker = new Worker("hello_world.js"); worker.onmessage = function(e) { console.log(e.data); }; worker.postMessage("Matthew");
And run
cfx run. You’ll notice a messy error:
> cfx run Using binary at '/Applications/Firefox.app/Contents/MacOS/firefox-bin'. Using profile at '/var/folders/p1/zzdzcrrx6pq96hgsmy5xjqmh0000gp/T/tmpgFixDP.mozrunner'. console.error: test: Message: ReferenceError: Worker is not defined Stack: @resource://jid1-zmowxggdley0aa-at-jetpack/test/lib/main.js:1:9 CuddlefishLoader/options<.load@resource://gre/modules/commonjs/sdk/loader/cuddlefish.js:129:18 run@resource://gre/modules/commonjs/sdk/addon/runner.js:138:19 startup/</<@resource://gre/modules/commonjs/sdk/addon/runner.js:81:7 Handler.prototype.process@resource://gre/modules/Promise-backend.js:865:23 this.PromiseWalker.walkerLoop@resource://gre/modules/Promise-backend.js:744:7 ************************* A coding exception was thrown in a Promise resolution callback. See https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise Full message: ReferenceError: Worker is not defined Full stack: @resource://jid1-zmowxggdley0aa-at-jetpack/test/lib/main.js:1:9 CuddlefishLoader/options<.load@resource://gre/modules/commonjs/sdk/loader/cuddlefish.js:129:18 run@resource://gre/modules/commonjs/sdk/addon/runner.js:138:19 startup/</<@resource://gre/modules/commonjs/sdk/addon/runner.js:81:7 Handler.prototype.process@resource://gre/modules/Promise-backend.js:865:23 this.PromiseWalker.walkerLoop@resource://gre/modules/Promise-backend.js:744:7 ************************* console.error: test: Message: ReferenceError: Worker is not defined Stack: @resource://jid1-zmowxggdley0aa-at-jetpack/test/lib/main.js:1:9 CuddlefishLoader/options<.load@resource://gre/modules/commonjs/sdk/loader/cuddlefish.js:129:18 run@resource://gre/modules/commonjs/sdk/addon/runner.js:138:19 startup/</<@resource://gre/modules/commonjs/sdk/addon/runner.js:81:7 Handler.prototype.process@resource://gre/modules/Promise-backend.js:865:23 this.PromiseWalker.walkerLoop@resource://gre/modules/Promise-backend.js:744:7
Aha! The key line here is: ReferenceError: Worker is not defined
. This is because Firefox Extensions use something called a ChromeWorker instead. We need to import this in main.js by pasting this at the top:
var {ChromeWorker} = require("chrome")
and changing the line that references the hello_world.js file to call a ChromeWorker instead:
//var worker = new Worker("hello_world.js"); //remove this var worker = new ChromeWorker("hello_world.js"); //add this instead
Ok let’s try running it again! Try
cfx run. Wtf another error?!
> cfx run Using binary at '/Applications/Firefox.app/Contents/MacOS/firefox-bin'. Using profile at '/var/folders/p1/zzdzcrrx6pq96hgsmy5xjqmh0000gp/T/tmpJJXeC4.mozrunner'. console.error: test: Message: Error: Malformed script URI: hello_world.js Stack: @resource://jid1-zmowxggdley0aa-at-jetpack/test/lib/main.js:3:14 CuddlefishLoader/options<.load@resource://gre/modules/commonjs/sdk/loader/cuddlefish.js:129:18 run@resource://gre/modules/commonjs/sdk/addon/runner.js:138:19 startup/</<@resource://gre/modules/commonjs/sdk/addon/runner.js:81:7 Handler.prototype.process@resource://gre/modules/Promise-backend.js:865:23 this.PromiseWalker.walkerLoop@resource://gre/modules/Promise-backend.js:744:7 console.error: test: Message: Error: Malformed script URI: hello_world.js Stack: @resource://jid1-zmowxggdley0aa-at-jetpack/test/lib/main.js:3:14 CuddlefishLoader/options<.load@resource://gre/modules/commonjs/sdk/loader/cuddlefish.js:129:18 run@resource://gre/modules/commonjs/sdk/addon/runner.js:138:19 startup/</<@resource://gre/modules/commonjs/sdk/addon/runner.js:81:7 Handler.prototype.process@resource://gre/modules/Promise-backend.js:865:23 this.PromiseWalker.walkerLoop@resource://gre/modules/Promise-backend.js:744:7
The key line here is: Malformed script URI: hello_world.js
. This cryptic error is because firefox can’t yet access anything in the
/data/folder. We have to use another part of the SDK to enable access to it.
Open
main.jsand put this at the top:
var self = require("sdk/self");
Now we can use the function
self.data.url(). When you put a filename as the first argument, it will return a string like
resource://jid1-zmowxggdley0aa-at-jetpack/test/data/whatever_file.jswhich properly refers to it in the case of extensions. Modify the worker import line as follows:
//let worker = new Worker("hello_world.js"); //remove this let worker = new ChromeWorker(self.data.url("hello_world.js")); //add this
Now let’s run the extension again using
cfx run:
> cfx run Using binary at '/Applications/Firefox.app/Contents/MacOS/firefox-bin'. Using profile at '/var/folders/p1/zzdzcrrx6pq96hgsmy5xjqmh0000gp/T/tmppvMjZp.mozrunner'. console.log: test: Hello Matthew
Yay it works! The Worker returned the message “Hello Matthew“.
FAQ
- What does this
{notation}
mean?
It is shorthand for:
var chrome = require("chrome") var Worker = chrome['ChromeWorker']
Basically this means that
require("chrome")returns an Object, and we just need the value that is referenced by the key “ChromeWorker”. This is a very succinct way of extracting things from JavaScript Objects that will come in handy in the future.
- Why is Worker now called ChromeWorker? Are we doing something with Google Chrome?
This is a naming coincidence and nothing to do with Chrome as in the browser. Chrome in this case refers to Firefox Addon internals.
This post first appeared on Ikigomu | A Data Science, NLP And Personal Blog By, please read the originial post: here