DOMWorkerThreads: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
No edit summary
No edit summary
 
(14 intermediate revisions by 3 users not shown)
Line 1: Line 1:
== DOM Worker Threads API Proposal ==
== Web Worker API Proposal ==


== Abstract ==
== API Proposal ==
 
This is an API '''proposal''' for DOM Worker Threads.
 
At this time this document is not complete and is not yet ready for general consideration or debate. It's just getting started!
 
Look for a post to mozilla.dev.platform when this proposal is ready to be discussed.
 
== Introduction ==
 
=== Use Cases ===
 
=== Terminology ===
 
A '''worker''' is an execution context for JavaScript that runs off of the main UI thread. The global scope for a worker is almost empty - familiar objects such as <code>window</code> and <code>document</code> are not available, nor is the Mozilla-specific <code>Components</code> object.
 
A '''worker pool''' is a collection of related workers. Workers within a pool can communicate with each other freely, but workers cannot communicate with workers from another pool.
 
== Conformance ==
 
== Dependencies ==
 
== Specification Proposal ==


<blockquote>
<blockquote>
<code>
<code>
<pre>
<pre>
#include "nsISupports.idl"


interface nsIDOMWorkerThread;
[NoInterfaceObject] interface WorkerFactory {
interface nsIScriptError;
  Worker createWorker(in DOMString scriptURL);
};


[scriptable, function, uuid(e50ca05d-1381-4abb-a021-02eb720cfc38)]
interface Worker {
interface nsIDOMWorkerMessageListener : nsISupports
   void postMessage(in DOMString aMessage);
{
  /**
  * An nsIDOMWorkerThread receives the onMessage callback when another
  * worker posts a message to it.
  *
  * @param aMessage (in DOMString)
  *        The message sent from another worker.
  * @param aSource (in nsIDOMWorkerThread)
  *        The worker that sent the message. Useful for a quick response.
  */
   void onMessage(in DOMString aMessage,
                in nsIDOMWorkerThread aSource);
};


[scriptable, function, uuid(9df8422e-25dd-43f4-b9b9-709f9e074647)]
   // event handler attributes
interface nsIDOMWorkerErrorListener : nsISupports
          attribute EventListener onmessage;
{
          attribute EventListener onerror;
   /**
          attribute EventListener onload;
  * An nsIDOMWorkerPool receives the onError callback when one of its child
          attribute EventListener onunload;
  * workers has a parse error or an unhandled exception.
  *
  * @param aMessage (in nsIScriptError)
  *        Details about the error that occurred. See nsIScriptError.
  * @param aSource (in nsIDOMWorkerThread)
  *        The worker that sent the message. Depending on the specific error in
  *        question it may not be possible to use this object (in the case of a
  *        parse error, for instance, aSource will be unusable).
  */
  void onError(in nsIScriptError aError,
              in nsIDOMWorkerThread aSource);
};
};


[scriptable, uuid(6f19f3ff-2aaa-4504-9b71-dca3c191efed)]
interface nsIDOMWorkerThread : nsISupports
{
  /**
  * Sends a message to the worker.
  *
  * @param aMessage (in DOMString)
  *        The message to send. This may be a string or an object that can be
  *        serialized via JSON (see http://developer.mozilla.org/en/docs/JSON).
  */
  void postMessage(in DOMString aMessage);
};


[scriptable, uuid(45312e93-8a3e-4493-9bd9-272a6c23a16c)]
[NoInterfaceObject] interface WorkerGlobalScope
interface nsIDOMWorkerPool : nsIDOMWorkerThread
{
{
   /**
   readonly attribute WorkerGlobalScope self;
  * The nsIDOMWorkerErrorListener which handles errors in child threads.
  readonly attribute WorkerLocation location;
  *
  readonly attribute boolean closing;
  * Developers should set this attribute to a proper object before calling
   void close();
  * createWorker in order to catch parse errors as well as runtime exceptions.
  */
   attribute nsIDOMWorkerErrorListener errorListener;


   /**
   // event handler attributes
  * Create a new worker object by evaluating the given script.
          attribute EventListener onunload;
  *
  * @param aSourceScript (in DOMString)
  *        The script to compile. See below for details on the scope in which
  *        the script will run.
  */
  nsIDOMWorkerThread createWorker(in DOMString aScriptText);


   /**
   // WorkerUtils
  * Create a new worker object by evaluating the given script file.
  void importScripts([Variadic] in DOMString urls);
  *
  readonly attribute Storage localStorage;
  * @param aSourceScript (in nsIURI)
  Database openDatabase(in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize);
  *        The script file to compile. See below for details on the scope in
  void showNotification(in DOMString title, in DOMString subtitle, in DOMString description);
  *        which the script will run. See nsIURI.
   void showNotification(in DOMString title, in DOMString subtitle, in DOMString description, in VoidCallback onclick);
  */
   nsIDOMWorkerThread createWorkerFromURI(in DOMString aScriptURI);
};
};


[scriptable, function, uuid(fc8c2d9c-7b0e-467e-91f8-4715497d8ac6)]
[NoInterfaceObject] interface WorkerLocation {
interface nsIDOMWorkerHttpRequestCallback : nsISupports
  readonly attribute DOMString href;
{
  readonly attribute DOMString protocol;
   void onChange();
  readonly attribute DOMString host;
  readonly attribute DOMString hostname;
  readonly attribute DOMString port;
  readonly attribute DOMString pathname;
  readonly attribute DOMString search;
   readonly attribute DOMString hash;
};
};


[scriptable, uuid(b66e5bf1-3269-4e33-ab32-4786fec42377)]
</pre>
interface nsIDOMWorkerHttpRequest : nsISupports
</code>
{
</blockquote>
  /**
  * See nsIXMLHttpRequest.idl
  */
  readonly attribute AString responseText;


  /**
=== Sample usage ===
  * See nsIXMLHttpRequest.idl
  */
  readonly attribute unsigned long status;


  /**
This is a '''very''' suboptimal way of calculating a number in the Fibonacci sequence.
  * See nsIXMLHttpRequest.idl
  */
  readonly attribute AUTF8String statusText;


  /**
Main page:
  * See nsIXMLHttpRequest.idl
<blockquote>
  */
<code>
   void abort();
<pre>
worker = createWorker("f.js");
worker.onmessage = function(e) {
   alert("The 100th Fibonacci number is " + e.data);
}
worker.postMessage(100);
</pre>
</code>
</blockquote>


  /**
f.js:
  * See nsIXMLHttpRequest.idl
<blockquote>
  */
<code>
  string getAllResponseHeaders();
<pre>
parent.onmessage = function(e) {
  if (e.data <= 1)
    postMessage(e.data);


   /**
   w1 = createWorker("f.js");
  * See nsIXMLHttpRequest.idl
  w1.onmessage = handler;
  */
  w1.postMessage(e.data - 1);
   ACString getResponseHeader(in AUTF8String header);
 
   w2 = createWorker("f.js");
  w2.onmessage = handler;
  w2.postMessage(e.data - 2);
}


  /**
c = 0;
  * See nsIXMLHttpRequest.idl
sum = 0;
  */
  void open(in AUTF8String method, in AUTF8String url);


   /**
function handler(e) {
  * See nsIXMLHttpRequest.idl
   sum += parseInt(e.data);
  */
  if (++c == 2) {
  void send(in nsIVariant body);
    postMessage(sum);
  }
}
</pre>
</code>
</blockquote>


  /**
== References ==
  * See nsIXMLHttpRequest.idl
  */
  void sendAsBinary(in DOMString body);


  /**
The only thing I've seen so far is the [http://code.google.com/apis/gears/api_workerpool.html Google Gears WorkerPool API]. We would certainly like to provide a API that would make migrating Gears code trivial.
  * See nsIXMLHttpRequest.idl
  */
  void setRequestHeader(in AUTF8String header, in AUTF8String value);


  /**
== API Proposal For Shared Workers ==
  * See nsIXMLHttpRequest.idl
  */
  readonly attribute long readyState;


  /**
If we want to support shared workers in the initial release of this spec, here are two proposals that will work with the above initial API.
  * See nsIXMLHttpRequest.idl
  */
  void overrideMimeType(in AUTF8String mimetype);


  /**
=== Proposal 1 ===
  * See nsIXMLHttpRequest.idl
  */
  attribute nsIDOMWorkerHttpRequestCallback onload;


  /**
<blockquote>
  * See nsIXMLHttpRequest.idl
<code>
  */
<pre>
  attribute nsIDOMWorkerHttpRequestCallback onerror;


   /**
[NoInterfaceObject] interface WorkerFactory {
  * See nsIXMLHttpRequest.idl
   ...
  */
   Worker createSharedWorker(in DOMString name, in DOMString scriptURL);
  attribute nsIDOMWorkerHttpRequestCallback onprogress;
 
  /**
  * See nsIXMLHttpRequest.idl
  */
  attribute nsIDOMWorkerHttpRequestCallback onuploadprogress;
 
  /**
  * See nsIXMLHttpRequest.idl
  */
   attribute nsIDOMWorkerHttpRequestCallback onreadystatechange;
};
};


interface nsIDOMWorkerGlobalScope
[NoInterfaceObject] interface WorkerParent {
{
   void postMessage(in DOMString aMessage);
  /**
   attribute EventListener onmessage;
  * The worker object that created this scope.
  */
  readonly attribute nsIDOMWorkerThread thisThread;
 
  /**
  * Sends a message to the pool that created the worker.
  *
  * @param aMessage (in DOMString)
  *        The message to send. This may be a string or an object that can be
  *        serialized via JSON (see http://developer.mozilla.org/en/docs/JSON).
  */
   void postMessageToPool(in DOMString aMessage);
 
  /**
  * The nsIDOMWorkerMessageListener which handles messages for this worker.
  *
  * Developers should set this attribute to a proper object before another
  * worker begins sending messages to ensure that all messages are received.
  */
   attribute nsIDOMWorkerMessageListener messageListener;
 
  /**
  * Loads the script indicated from the given URI.
  *
  * This function will block until the script file has loaded and has been
  * executed.
  *
  * @param aURIString
  *        The URI to load.
  *
  * @throws Will throw an error if the URI cannot be loaded or executed.
  */
  void loadScript(in DOMString aURIString);
 
  /**
  * Creates a new HttpRequest object.
  *
  * Use: 'var req = new HttpRequest();'
  */
  /* nsIDOMWorkerHttpRequest HttpRequest(); */
 
  /**
  * See nsIDOMJSWindow.idl
  */
  void dump(in DOMString str);
 
  /**
  * See nsIDOMJSWindow.idl
  */
  long setTimeout(/* in JSObject aFunctionOrString, */
                  /* in JSObject aMilisecondDelay, */
                  /* [optional] in aArgsToFunctionOrString */);
 
  /**
  * See nsIDOMJSWindow.idl
  */
  long setInterval(/* in JSObject aFunctionOrString, */
                  /* in JSObject aMilisecondDelay, */
                  /* [optional] in aArgsToFunctionOrString */);
 
  /**
  * See nsIDOMJSWindow.idl
  */
  void clearTimeout(/* in JSObject aTimeoutId */);
 
  /**
  * See nsIDOMJSWindow.idl
  */
  void clearInterval(/* in JSObject aIntervalId */);
};
};


Line 283: Line 130:
</blockquote>
</blockquote>


=== The worker pool's scope ===
<code>createSharedWorker</code> creates a new <code>Worker</code> object that interacts with the same <code>WorkerGlobalScope</code> as any previously existing <code>Worker</code>s.


The scope for the worker pool is the standard DOM scope. All DOM objects such as <code>window</code> and <code>document</code> are available, as well as functions such as <code>alert()</code> and <code>dump()</code>.
When a shared <code>Worker</code> receives a message it can send data back to the sender using the <code>Event.source</code> property which is a <code>WorkerParent</code>.


=== The worker's scope ===
Using <code>WorkerGlobalScope.postMessage</code> and <code>WorkerGlobalScope.onmessage</code> results in a message being sent to the first context that opened the shared worker (or nothing if that context is dead).


The worker's scope is far more limited. See <code>nsIDOMWorkerGlobalScope</code> above for details.
Downsides with this proposal:
* There is no way to communicate back unless first communicated to.
* The first one to instantiate a shared worker get special treatment.


=== Sample usage ===
=== Proposal 2 ===


<blockquote>
<blockquote>
<code>
<code>
<pre>
<pre>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
  <title>Test threads</title>
  <body>
  <script language="javascript">
    var scriptToRun = 'function messageListener(message, source) { ' +
                      '  dump("Worker: " + message + "\\n"); ' +
                      '} ' +
                      'thisThread.messageListener = messageListener; ' +
                      'for (var i = 0; i < 10000000; i++) { ' +
                      ' /* do something */ ' +
                      '} '+
                      'sendMessageToPool("Done!"); ';


    var wp = navigator.newWorkerPool();
[NoInterfaceObject] interface WorkerFactory {
    wp.messageListener = function(message, source) {
  ...
      dump("Pool: " + message + "\n");
  MessagePort connectToSharedWorker(in DOMString name, in DOMString scriptURL);
    };
};
    wp.errorListener = function(error, source) {
      dump("Pool: error = '" + error + "'\n");
    }


    var threads = []
[NoInterfaceObject] interface WorkerGlobalScope {
    for (var i = 0; i < 10; i++) {
  ...
      var thread = wp.createWorker(scriptToRun);
          attribute EventListener onconnect;
      thread.postMessage("hello");
};
      threads.push(thread);
    }
 
  </script>
  </body>
</html>


</pre>
</pre>
Line 333: Line 160:
</blockquote>
</blockquote>


== Not In This Specification ==
<code>connectToSharedWorker</code> creates a two new <code>MessagePort</code> which are entangled with each other. One of the two ports is returned, and the other is sent to the <code>Worker</code> with the given name. If such a <code>Worker</code> does not yet exist, one is created.
 
== References ==


The only thing I've seen so far is the [http://code.google.com/apis/gears/api_workerpool.html Google Gears WorkerPool API]. We would certainly like to provide a compatibility API that would make migrating Gears code trivial.
The <code>Worker</code> receives the other <code>MessagePort</code> through an <code>onconnect</code> <code>Event</code> fired on the <code>WorkerGlobalScope</code> object.


== Acknowledgements ==
* There is no way to get a reference to the shared <code>Worker</code> object itself.
* Communication for shared workers is different from communication for non-shared ones.

Latest revision as of 16:58, 20 August 2008

Web Worker API Proposal

API Proposal


[NoInterfaceObject] interface WorkerFactory {
  Worker createWorker(in DOMString scriptURL);
};

interface Worker {
  void postMessage(in DOMString aMessage);

  // event handler attributes
           attribute EventListener onmessage;
           attribute EventListener onerror;
           attribute EventListener onload;
           attribute EventListener onunload;
};


[NoInterfaceObject] interface WorkerGlobalScope
{
  readonly attribute WorkerGlobalScope self;
  readonly attribute WorkerLocation location;
  readonly attribute boolean closing;
  void close();

  // event handler attributes
           attribute EventListener onunload;

  // WorkerUtils
  void importScripts([Variadic] in DOMString urls);
  readonly attribute Storage localStorage;
  Database openDatabase(in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize);
  void showNotification(in DOMString title, in DOMString subtitle, in DOMString description);
  void showNotification(in DOMString title, in DOMString subtitle, in DOMString description, in VoidCallback onclick);
};

[NoInterfaceObject] interface WorkerLocation {
  readonly attribute DOMString href;
  readonly attribute DOMString protocol;
  readonly attribute DOMString host;
  readonly attribute DOMString hostname;
  readonly attribute DOMString port;
  readonly attribute DOMString pathname;
  readonly attribute DOMString search;
  readonly attribute DOMString hash;
};

Sample usage

This is a very suboptimal way of calculating a number in the Fibonacci sequence.

Main page:

worker = createWorker("f.js");
worker.onmessage = function(e) {
  alert("The 100th Fibonacci number is " + e.data);
}
worker.postMessage(100);

f.js:

parent.onmessage = function(e) {
  if (e.data <= 1)
    postMessage(e.data);

  w1 = createWorker("f.js");
  w1.onmessage = handler;
  w1.postMessage(e.data - 1);
  
  w2 = createWorker("f.js");
  w2.onmessage = handler;
  w2.postMessage(e.data - 2);
}

c = 0;
sum = 0;

function handler(e) {
  sum += parseInt(e.data);
  if (++c == 2) {
    postMessage(sum);
  }
}

References

The only thing I've seen so far is the Google Gears WorkerPool API. We would certainly like to provide a API that would make migrating Gears code trivial.

API Proposal For Shared Workers

If we want to support shared workers in the initial release of this spec, here are two proposals that will work with the above initial API.

Proposal 1


[NoInterfaceObject] interface WorkerFactory {
  ...
  Worker createSharedWorker(in DOMString name, in DOMString scriptURL);
};

[NoInterfaceObject] interface WorkerParent {
  void postMessage(in DOMString aMessage);
  attribute EventListener onmessage;
};

createSharedWorker creates a new Worker object that interacts with the same WorkerGlobalScope as any previously existing Workers.

When a shared Worker receives a message it can send data back to the sender using the Event.source property which is a WorkerParent.

Using WorkerGlobalScope.postMessage and WorkerGlobalScope.onmessage results in a message being sent to the first context that opened the shared worker (or nothing if that context is dead).

Downsides with this proposal:

  • There is no way to communicate back unless first communicated to.
  • The first one to instantiate a shared worker get special treatment.

Proposal 2


[NoInterfaceObject] interface WorkerFactory {
  ...
  MessagePort connectToSharedWorker(in DOMString name, in DOMString scriptURL);
};

[NoInterfaceObject] interface WorkerGlobalScope {
  ...
           attribute EventListener onconnect;  
};

connectToSharedWorker creates a two new MessagePort which are entangled with each other. One of the two ports is returned, and the other is sent to the Worker with the given name. If such a Worker does not yet exist, one is created.

The Worker receives the other MessagePort through an onconnect Event fired on the WorkerGlobalScope object.

  • There is no way to get a reference to the shared Worker object itself.
  • Communication for shared workers is different from communication for non-shared ones.