This article shows you how to use worker threads in extensions to perform tasks in the background without blocking the user interface.
If you haven't already created an extension, or would like to refresh your memory, take a look at the previous articles in this series:
You may download the complete example:
This version of the stock ticker extension moves the XMLHttpRequest
call that fetches updated stock information into a worker thread, which then passes that information back to the main body of the extension's code to update the display in the status bar. This demonstrates not only how to use workers in an extension, but also how to perform XMLHttpRequest
in a worker, and how workers and main thread code can pass data back and forth.
The worker thread's job in this example is to issue the XMLHttpRequest
calls that fetch the updated stock information. That's all it does. Every 10 minutes, it calls XMLHttpRequest
, and, when the results are received, sends an event back to the main thread with the received data.
So we need to move the refreshInformation()
method from the stockwatcher2.js
file into a separate file that will host the worker thread. That file, ticker_worker.js
, is shown here:
var symbol = ""; function refreshInformation() { if (!symbol) { throw "No symbol set!"; } var fullUrl = "http://quote.yahoo.com/d/quotes.csv?f=sl1d1t1c1ohgv&e=.csv&s=" + symbol; function infoReceived() { var output = httpRequest.responseText; if (output) { postMessage(output.trim()); } httpRequest = null; } var httpRequest = new XMLHttpRequest(); httpRequest.open("GET", fullUrl, true); httpRequest.onload = infoReceived; httpRequest.send(null); } setInterval(function() { refreshInformation(); }, 10*60*1000); onmessage = function(event) { if (event.data) { symbol = event.data.toUpperCase(); } refreshInformation(); }
When the worker thread is started, the main body of this code (in lines 26-35) is executed. It starts by setting up an interval handler (in lines 26-28) that calls refreshInformation()
every 10 minutes.
Then it sets the worker's onmessage
event handler to a function which looks at the event passed into it, and does one of two things:
refreshInformation()
method is called to fetch the latest information about the stock and pass it back to the main thread. This provides a way for the main thread to specifically request that the worker update the stock information at once.The refreshInformation()
method is fairly similar to the previous versions, with two notable exceptions:
XMLHttpRequest
, instead of immediately updating the displayed information in the status bar, a message is sent to the main thread using the worker's postMessage()
method. This is because only the main thread is allowed to access the user interface.The changes here are also relatively minor, but crucial.
This method is called when the extension is first loaded, and needs to be updated to start up and configure the worker. Let's take a look:
startup: function() { // Register to receive notifications of preference changes this.prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService) .getBranch("stockwatcher2."); this.prefs.QueryInterface(Components.interfaces.nsIPrefBranch2); this.prefs.addObserver("", this, false); this.tickerSymbol = this.prefs.getCharPref("symbol").toUpperCase(); this.worker = new Worker("chrome://stockwatcher2/content/ticker_worker.js"); // Small little dance to get 'this' to refer to StockWatcher, not the // worker, when a message is received. var self = this; this.worker.onmessage = function(event) { self.onworkermessage.call(self, event); }; this.worker.postMessage(this.tickerSymbol); },
The worker is set up and configured here in lines 13-22:
ticker_worker.js
file.onmessage
handler so that when the worker calls back to the main thread, the main thread's value of this is correctly the main thread's object instead of the worker's.This method's job is to update which stock is being monitored when the user changes the preferences. It needs to be updated to post a message to the worker to tell it which stock symbol to track.
observe: function(subject, topic, data) { if (topic != "nsPref:changed") { return; } switch(data) { case "symbol": this.tickerSymbol = this.prefs.getCharPref("symbol").toUpperCase(); this.worker.postMessage(this.tickerSymbol); break; } },
The key here is line 10, which sends the new ticker symbol to monitor to the ticker thread by calling its postMessage()
method. This results in the ticker's onmessage
handler being called.
These two methods are very simple. watchStock()
is updated to pass the symbol to the ticker thread, and refreshInformation()
, whose main functionality is now in the worker, is updated to simply pass an empty message to the worker, which tells the worker to refresh the stock information immediately.
watchStock: function(newSymbol) { this.tickerSymbol = newSymbol.toUpperCase(); this.prefs.setCharPref("symbol", newSymbol); this.worker.postMessage(this.tickerSymbol); }, refreshInformation: function() { // Empty message just means 'refresh'. this.worker.postMessage(""); },
This method is called when the worker posts a message back to the main thread. Its job is to update the ticker information that's currently displayed in the status bar, as well as to update the tooltip that appears while the mouse cursor is hovering over the ticker. This code is essentially identical to what's done in the previous version, except that it's done in response to an event instead of within the refreshInformation()
method.
Gecko 2.0 added the new ChromeWorker
object, which provides a special chrome-only worker that can be used by applications and extensions. This worker object has access to js-ctypes, which standard workers do not have.