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
1 2 3 4 5 6 |
> 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 i to start typing and Esc followed by :wq to save):
1 2 3 4 5 |
//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.js file, which is the first thing that runs in the extension.
1 2 |
> cd ../lib/ > vim main.js |
Paste in the following code:
1 2 3 4 5 6 7 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
> 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:
1 |
var {ChromeWorker} = require("chrome") |
and changing the line that references the hello_world.js file to call a ChromeWorker instead:
1 2 |
//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?!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
> 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.js and put this at the top:
1 |
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.js which properly refers to it in the case of extensions. Modify the worker import line as follows:
1 2 |
//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 :
1 2 3 4 |
> 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:
1 2 |
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.