Using workers in extensions

  • Revision slug: Using_workers_in_extensions
  • Revision title: Using workers in extensions
  • Revision id: 101590
  • Created:
  • Creator: Sheppy
  • Is current revision? No
  • Comment 4 words added, 4 words removed

Revision Content

{{ Previous("Updating an extension to support multiple Mozilla applications") }}

{{ fx_minversion_header("3") }}

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:

Download the sample

You may download the complete example:

How this differs from previous versions

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

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:

  • If there is a data field on the event, the stock symbol being tracked is set to the upper case version of that value.  This is used to initialize the worker, and to change which stock is being monitored.
  • If there is no data field, the 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:

  • If the ticker symbol has not been set yet, an exception is thrown (lines 4-6).
  • When the result is received from 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 main thread

The changes here are also relatively minor, but crucial.

The startup() method

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:

  • Line 13 instantiates a new worker, specifying the URI of the ticker_worker.js file.
  • Lines 17-20 change the definition of the worker's 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.
  • Line 22 sends a message to the ticker thread to tell it what symbol to monitor.

The observe() method

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.

The watchStock() and refreshInformation() methods

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("");
  },

The onworkermessage() method

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.

{{ h1_gecko_minversion("A note about ChromeWorkers", "1.9.3") }}

Gecko 1.9.3 {{ geckoRelease("1.9.3") }} 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.

See also

Revision Source

<p>{{ Previous("Updating an extension to support multiple Mozilla applications") }}</p>
<p>{{ fx_minversion_header("3") }}</p>
<p>This article shows you how to use worker threads in extensions to perform tasks in the background without blocking the user interface.</p>
<p>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:</p>
<ul> <li><a href="/en/Creating_a_status_bar_extension" title="en/Creating_a_status_bar_extension">Creating a status bar extension</a></li> <li><a href="/en/Creating_a_dynamic_status_bar_extension" title="en/Creating_a_dynamic_status_bar_extension">Creating a dynamic status bar extension</a></li> <li><a href="/en/Adding_preferences_to_an_extension" title="en/Adding_preferences_to_an_extension">Adding preferences to an extension</a></li> <li><a href="/en/Localizing_an_extension" title="en/Localizing_an_extension">Localizing an extension</a></li> <li><a class="internal" href="/en/Updating_an_extension_to_support_multiple_Mozilla_applications" title="En/Updating an extension to support multiple Mozilla applications">Updating an extension to support multiple Mozilla applications</a></li>
</ul>
<h2>Download the sample</h2>
<p>You may download the complete example:</p>
<ul> <li>  <a class="internal" href="/@api/deki/files/3145/=stockwatcher2%2540example.com.zip" title="/@api/deki/files/3145/=stockwatcher2@example.com.zip">Download the example.</a></li>
</ul>
<h2>How this differs from previous versions</h2>
<p>This version of the stock ticker extension moves the <a class="internal" href="/en/XMLHttpRequest" title="En/XMLHttpRequest"><code>XMLHttpRequest</code></a> 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 <code>XMLHttpRequest</code> in a worker, and how workers and main thread code can pass data back and forth.</p>
<h2>The worker</h2>
<p>The worker thread's job in this example is to issue the <code>XMLHttpRequest</code> calls that fetch the updated stock information.  That's all it does.  Every 10 minutes, it calls <code>XMLHttpRequest</code>, and, when the results are received, sends an event back to the main thread with the received data.</p>
<p>So we need to move the <code>refreshInformation()</code> method from the <code>stockwatcher2.js</code> file into a separate file that will host the worker thread.  That file, <code>ticker_worker.js</code>, is shown here:</p>
<pre class="brush: js">var symbol = "";

function refreshInformation() {
  if (!symbol) {
    throw "No symbol set!";
  }

  var fullUrl =
    "http://quote.yahoo.com/d/quotes.csv?f=sl1d1t1c1ohgv&amp;e=.csv&amp;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();
}</pre>
<p>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 <code>refreshInformation()</code> every 10 minutes.</p>
<p>Then it sets the worker's <code>onmessage</code> event handler to a function which looks at the event passed into it, and does one of two things:</p>
<ul> <li>If there is a data field on the event, the stock symbol being tracked is set to the upper case version of that value.  This is used to initialize the worker, and to change which stock is being monitored.</li> <li>If there is no data field, the <code>refreshInformation()</code> 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.</li>
</ul>
<p>The <code>refreshInformation()</code> method is fairly similar to the previous versions, with two notable exceptions:</p>
<ul> <li>If the ticker symbol has not been set yet, an exception is thrown (lines 4-6).</li> <li>When the result is received from <code>XMLHttpRequest</code>, instead of immediately updating the displayed information in the status bar, a message is sent to the main thread using the worker's <code>postMessage()</code> method.  This is because only the main thread is allowed to access the user interface.</li>
</ul>
<h2>The main thread</h2>
<p>The changes here are also relatively minor, but crucial.</p>
<h3>The startup() method</h3>
<p>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:</p>
<pre class="brush: js">  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);
  },
</pre>
<p>The worker is set up and configured here in lines 13-22:</p>
<ul> <li>Line 13 instantiates a new worker, specifying the URI of the <code>ticker_worker.js</code> file.</li> <li>Lines 17-20 change the definition of the worker's <code>onmessage</code> 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.</li> <li>Line 22 sends a message to the ticker thread to tell it what symbol to monitor.</li>
</ul>
<h2>The observe() method</h2>
<p>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.</p>
<pre class="brush: js">  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;
    }
  },
</pre>
<p>The key here is line 10, which sends the new ticker symbol to monitor to the ticker thread by calling its <code>postMessage()</code> method.  This results in the ticker's <code>onmessage</code> handler being called.</p>
<h2>The watchStock() and refreshInformation() methods</h2>
<p>These two methods are very simple.  <code>watchStock()</code> is updated to pass the symbol to the ticker thread, and <code>refreshInformation()</code>, 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.</p>
<pre class="brush: js">  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("");
  },
</pre>
<h2>The onworkermessage() method</h2>
<p>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 <code>refreshInformation()</code> method.</p>
<p>{{ h1_gecko_minversion("A note about ChromeWorkers", "1.9.3") }}</p>
<p>Gecko 1.9.3 {{ geckoRelease("1.9.3") }} 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 <a href="/en/js-ctypes" title="en/js-ctypes">js-ctypes</a>, which standard workers do not have.</p><h2>See also</h2>
<ul> <li><a class="internal" href="/En/Using_web_workers" title="En/Using DOM workers">Using web workers</a></li> <li><a class="internal" href="/En/DOM/Worker" title="En/DOM/Worker">Worker</a></li> <li><a href="/En/DOM/ChromeWorker" title="En/DOM/ChromeWorker">ChromeWorker</a></li>
</ul>
Revert to this revision