You can take part in this work. Join the working group's discussion list.
Web designers! We have a FAQ, a forum, and a help mailing list for you!
© Copyright 2004-2008 Apple Computer, Inc., Mozilla Foundation, and Opera Software ASA.
You are granted a license to use, reproduce and create derivative works of this document.
This specification defines an API that allows Web application authors to spawn background workers running scripts in parallel to their main page. This allows for thread-like operation with message-passing as the coordination mechanism.
This is a work in progress! This document is changing on a daily if not hourly basis in response to comments and as a general part of its development process. Comments are very welcome, please send them to whatwg@whatwg.org. Thank you.
The current focus is in developing a first draft proposal.
Implementors should be aware that this specification is not stable. Implementors who are not taking part in the discussions are likely to find the specification changing out from under them in incompatible ways. Vendors interested in implementing this specification before it eventually reaches the call for implementations should join the WHATWG mailing list and take part in the discussions.
This specification is also being produced by the W3C Web Apps WG. The two specifications are identical from the table of contents onwards.
This section is non-normative.
This specification defines an API for running scripts in the background independently of any user interface scripts.
This allows for long-running scripts that are not interrupted by scripts that respond to clicks or other user interactions, and allows long tasks to be executed without yielding to keep the page responsive.
Workers (as these background scripts are called herein) are relatively heavy-weight, and are not intended to be used in large numbers. For example, it would be inappropriate to launch one worker for each pixel of a four megapixel image. The examples below show some appropriate uses of workers.
Generally, workers are expected to be long-lived, have a high start-up performance cost, and a high per-instance memory cost.
This section is non-normative.
There are a variety of uses that workers can be put to. The following subsections show various examples of this use.
This section is non-normative.
The simplest use of workers is for performing a computationally expensive task without interrupting the user interface.
In this example, the main document spawns a worker to (naïvely) compute prime numbers, and progressively displays the most recently found prime number.
The main page is as follows:
<!DOCTYPE HTML>
<html>
<head>
<title>Worker example: One-core computation</title>
</head>
<body>
<p>The highest prime number discovered so far is: <output id="result"></output></p>
<script>
var worker = new Worker('worker.js');
worker.onmessage = function (event) {
document.getElementById('result').textContent = event.data;
};
</script>
</body>
</html>
The Worker()
constructor call creates a worker and returns a Worker object representing that worker, which
is used to communicate with the worker. That object's onmessage
event handler attribute allows the code to receive messages from the
worker.
The worker itself is as follows:
var n = 1;
search: while (true) {
n += 1;
for (var i = 2; i <= Math.sqrt(n); i += 1)
if (n % i == 0)
continue search;
// found a prime!
postMessage(n);
}
The bulk of this code is simply an unoptimised search for a prime
number. To send a message back to the page, the postMessage() method is used to post a
message when a prime is found.
This section is non-normative.
In this example, the main document spawns a worker whose only task is to listen for notifications from the server, and, when appropriate, either add or remove data from the client-side database.
Since no communication occurs between the worker and the main page, the main page can start the worker by just doing:
<script>
new Worker('worker.js');
</script>
The worker itself is as follows:
var server = new WebSocket('ws://whatwg.org/database');
var database = openDatabase('demobase', '1.0', 'Demo Database', 10240);
server.onmessage = function (event) {
// data is in the format "command key value"
var data = event.data.split(' ');
switch (data[0]) {
case '+':
database.transaction(function(tx) {
tx.executeSql('INSERT INTO pairs (key, value) VALUES (?, ?)', data[1], data[2]);
});
case '-':
database.transaction(function(tx) {
tx.executeSql('DELETE FROM pairs WHERE key=? AND value=?', data[1], data[2]);
});
}
};
This connects to the server using the WebSocket mechanism
and opens the local database (which, we presume, has been created
earlier). The worker then just listens for messages from the server and
acts on them as appropriate, forever (or until the main page is closed).
View this example online. (This example will not actually function, since the server does not actually exist and the database is not created by this sample code.)
This section is non-normative.
In this example, the main document uses two workers, one for fetching stock updates for at regular intervals, and one for fetching performing search queries that the user requests.
The main page is as follows:
<!DOCTYPE HTML>
<html>
<head>
<title>Worker example: Stock ticker</title>
<script>
// TICKER
var symbol = 'GOOG'; // default symbol to watch
var ticker = new Worker('ticker.js');
// SEARCHER
var searcher = new Worker('searcher.js');
function search(query) {
searcher.postMessage(query);
}
// SYMBOL SELECTION UI
function select(newSymbol) {
symbol = newSymbol;
ticker.postMessage(symbol);
}
</script>
</head>
<body>
<p><output id="symbol"></output> <output id="value"></output></p>
<script>
ticker.onmessage = function (event) {
var data = event.data.split(' ');
document.getElementById('symbol').textContent = data[0];
document.getElementById('value').textContent = data[1];
};
ticker.postMessage(symbol);
</script>
<p><label>Search: <input type="text" oninput="search(this.value)"></label></p>
<ul id="results"></ul>
<script>
searcher.onmessage = function (event) {
var data = event.data.split(' ');
var results = document.getElementById('results');
while (results.hasChildNodes()) // clear previous results
results.removeChild(results.firstChild);
for (var i = 0; i < data.length; i += 1) {
// add a list item with a button for each result
var li = document.createElement('li');
var button = document.createElement('button');
button.value = data[i];
button.type = 'button';
button.onclick = function () { select(this.value); };
button.textContent = data[i];
li.appendChild(button);
results.appendChild(li);
}
};
</script>
<p>(The data in this example is not real. Try searching for "Google" or "Apple".)</p>
</body>
</html>
The two workers use a common library for performing the actual network calls. This library is as follows:
function get(url) {
try {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.send();
return xhr.responseText;
} catch (e) {
return ''; // turn all errors into empty results
}
}
The stock updater worker is as follows:
importScripts('io.js');
var timer;
var symbol;
function update() {
postMessage(symbol + ' ' + get('stock.cgi?' + symbol));
timer = setTimeout(update, 10000);
}
onmessage = function (event) {
if (timer)
clearTimeout(timer);
symbol = event.data;
update();
};
The search query worker is as follows:
importScripts('io.js');
onmessage = function (event) {
postMessage(get('search.cgi?' + event.data));
};
This section is non-normative.
In this example, multiple windows (viewers) can be opened that are all viewing the same map. All the windows share the same map information, with a single worker coordinating all the viewers. Each viewer can move around independently, but if they set any data on the map, all the viewers are updated.
The main page isn't interesting, it merely provides a way to open the viewers:
<!DOCTYPE HTML>
<html>
<head>
<title>Workers example: Multiviewer</title>
<script>
function openViewer() {
window.open('viewer.html');
}
</script>
</head>
<body>
<p><button type=button onclick="openViewer()">Open a new
viewer</button></p>
<p>Each viewer opens in a new window. You can have as many viewers
as you like, they all view the same data.</p>
</body>
</html>
The viewer is more involved:
<!DOCTYPE HTML>
<html>
<head>
<title>Workers example: Multiviewer viewer</title>
<script>
var worker = new SharedWorker('worker.js', 'core');
// CONFIGURATION
function configure(event) {
if (event.data.substr(0, 4) != 'cfg ') return;
var name = event.data.substr(4).split(' ', 1);
// update display to mention our name is name
document.getElementsByTagName('h1')[0].textContent += ' ' + name;
// no longer need this listener
worker.port.removeEventListener('message', configure, false);
}
worker.port.addEventListener('message', configure, false);
// MAP
function paintMap(event) {
if (event.data.substr(0, 4) != 'map ') return;
var data = event.data.substr(4).split(',');
// display tiles data[0] .. data[8]
var canvas = document.getElementById('map');
var context = canvas.getContext('2d');
for (var y = 0; y < 3; y += 1) {
for (var x = 0; x < 3; x += 1) {
var tile = data[y * 3 + x];
if (tile == '0')
context.fillStyle = 'green';
else
context.fillStyle = 'maroon';
fillRect(x * 50, y * 50, 50, 50);
}
}
}
worker.port.addEventListener('message', paintMap, false);
// PUBLIC CHAT
function updatePublicChat(event) {
if (event.data.substr(0, 4) != 'txt ') return;
var name = event.data.substr(4).split(' ', 1);
var message = event.data.substr(4 + length(name) + 1);
// display "<name> message" in public chat
var dialog = document.getElementById('public');
var dt = document.createElement('dt');
dt.textContent = name;
dialog.appendChild(dt);
var dd = document.createElement('dd');
dd.textContent = message;
dialog.appendChild(dd);
}
worker.port.addEventListener('message', updatePublicChat, false);
// PRIVATE CHAT
function startPrivateChat(event) {
if (event.data.substr(0, 4) != 'msg ') return;
var name = event.data.substr(4).split(' ', 1);
var port = event.port;
// display a private chat UI
var ul = document.getElementById('private');
var li = document.createElement('li');
var h3 = document.createElement('h3');
h3.textContent = 'Private chat with ' + name;
li.appendChild(h3);
var dialog = document.createElement('dialog');
var addMessage = function(name, message) {
var dt = document.createElement('dt');
dt.textContent = name;
dialog.appendChild(dt);
var dd = document.createElement('dd');
dd.textContent = message;
dialog.appendChild(dd);
};
port.onmessage = function (event) {
addMessage(name, event.data);
};
li.appendChild(dialog);
var form = document.createElement('form');
var p = document.createElement('p');
var input = document.createElement('input');
input.size = 50;
p.appendChild(input);
p.appendChild(document.createTextNode(' '));
var button = document.createElement('button');
button.textContent = 'Post';
p.appendChild(button);
form.onsubmit = function () {
port.postMessage(input.value);
addMessage('me', input.value);
input.value = '';
return false;
};
form.appendChild(p);
li.appendChild(form);
}
worker.port.addEventListener('message', startPrivateChat, false);
</script>
</head>
<body>
<h1>Viewer</h1>
<h2>Map</h2>
<p><canvas id="map" height=150 width=150></canvas></p>
<p>
<button type=button onclick="worker.port.postMessage('mov left')">Left</button>
<button type=button onclick="worker.port.postMessage('mov up')">Up</button>
<button type=button onclick="worker.port.postMessage('mov down')">Down</button>
<button type=button onclick="worker.port.postMessage('mov right')">Right</button>
<button type=button onclick="worker.port.postMessage('set 0')">Set 0</button>
<button type=button onclick="worker.port.postMessage('set 1')">Set 1</button>
</p>
<h2>Public Chat</h2>
<dialog id="public"></dialog>
<form onsubmit="worker.port.postMessage('txt ' + message.value); message.value = ''; return false;">
<p>
<input type="text" name="message" size="50">
<button>Post</button>
</p>
</form>
<h2>Private Chat</h2>
<ul id="private"></ul>
</body>
</html>
There are several key things worth noting about the way the viewer is written.
Multiple listeners. Instead of a single message processing function, the code here attaches multiple event listeners, each one performing a quick check to see if it is relevant for the message. In this example it doesn't make much difference, but if multiple authors wanted to collaborate using a single port to communicate with a worker, it would allow for independent code instead of changes having to all be made to a single event handling function.
Registering event listeners in this way also allows you to unregister
specific listeners when you are done with them, as is done with the configure() method in this example.
Finally, the worker:
var nextName = 0;
function getNextName() {
// this could use more friendly names
// but for now just return a number
return nextName++;
}
var map = [
[0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 1, 0, 1, 1],
[0, 1, 0, 1, 0, 0, 0],
[0, 1, 0, 1, 0, 1, 1],
[0, 0, 0, 1, 0, 0, 0],
[1, 0, 0, 1, 1, 1, 1],
[1, 1, 0, 1, 1, 0, 1],
];
function wrapX(x) {
if (x < 0) return wrapX(x + map[0].length);
if (x >= map[0].length) return wrapX(x - map[0].length);
return x;
}
function wrapY(y) {
if (y < 0) return wrapY(y + map.length);
if (y >= map[0].length) return wrapY(y - map.length);
return y;
}
function sendMapData(callback) {
var data = '';
for (var y = viewer.y-1; y <= viewer.y+1; y += 1) {
for (var x = viewer.x-1; x <= viewer.x+1; x += 1) {
if (data != '')
data += ',';
data += map[y][x];
}
}
callback('map ' + data);
}
var viewers = {};
onconnect = function (event) {
event.port._name = getNextName();
event.port._data = { port: event.port, x: 0, y: 0, };
viewers[event.port._name] = event.port._data;
event.port.postMessage('cfg ' + name);
event.port.onmessage = getMessage;
sendMapData(event.port.postMessage);
};
function getMessage(event) {
switch (event.data.substr(0, 4)) {
case 'mov ':
var direction = event.data.substr(4);
var dx = 0;
var dy = 0;
switch (direction) {
case 'up': dy = -1; break;
case 'down': dy = 1; break;
case 'left': dx = -1; break;
case 'right': dx = 1; break;
}
event.target._data.x = wrapX(event.target._data.x + dx);
event.target._data.y = wrapY(event.target._data.y + dy);
sendMapData(event.target.postMessage);
break;
case 'set ':
var value = event.data.substr(4);
map[event.target._data.y][event.target._data.x] = value;
for (var viewer in viewers)
sendMapData(viewers[viewer].port.postMessage);
break;
case 'txt ':
var name = event.target._name;
var message = event.data.substr(4);
for (var viewer in viewers)
viewers[viewer].port.postMessage('txt ' + name + ' ' + message);
break;
case 'msg ':
var party1 = event._data;
var party2 = viewers[event.data.substr(4).split(' ', 1)];
if (party2) {
var channel = new MessageChannel();
party1.port.postMessage('msg ' + party2.name, channel.port1);
party2.port.postMessage('msg ' + party1.name, channel.port2);
}
break;
}
}
Connecting to multiple pages. The script uses the onconnect event listener to listen for
multiple connections.
Direct channels. When the worker receives a "msg" message from one viewer naming another viewer, it sets up a direct connection between the two, so that the two viewers can communicate directly without the worker having to proxy all the messages.
This section is non-normative.
With multicore CPUs becoming prevalent, one way to obtain better performance is to split computationally expensive tasks amongst multiple workers. In this example, a computationally expensive task that is to be performed for every number from 1 to 10,000,000 is farmed out to ten subworkers.
The main page is as follows, it just reports the result:
<!DOCTYPE HTML>
<html>
<head>
<title>Worker example: One-core computation</title>
</head>
<body>
<p>The highest prime number discovered so far is: <output id="result"></output></p>
<script>
var worker = new Worker('worker.js');
worker.onmessage = function (event) {
document.getElementById('result').textContent = event.data;
};
</script>
</body>
</html>
The worker itself is as follows:
// settings
var num_workers = 10;
var items_per_worker = 1000000;
// start the workers
var result = 0;
var pending_workers = num_workers;
for (var i = 0; i < num_workers; i += 1) {
var worker = new Worker('core.js');
worker.postMessage(i * items_per_worker);
worker.postMessage((i+1) * items_per_worker);
worker.onmessage = storeResult;
}
// handle the results
function storeResult(event) {
result += 1*event.data;
pending_workers -= 1;
if (pending_workers <= 0)
postMessage(result); // finished!
}
It consists of a loop to start the subworkers, and then a handler that waits for all the subworkers to respond.
The subworkers are implemented as follows:
var start;
onmessage = getStart;
function getStart(event) {
start = 1*event.data;
onmessage = getEnd;
}
var end;
function getEnd(event) {
end = 1*event.data;
onmessage = null;
work();
}
function work() {
var result = 0;
for (var i = start; i < end; i += 1) {
// perform some complex calculation here
result += 1;
}
postMessage(result);
close();
}
They receive two numbers in two events, perform the computation for the range of numbers thus specified, and then report the result back to the parent.
This section is non-normative.
Suppose that a cryptography library is made available that provides three tasks:
The library itself is as follows:
function handleMessage(e) {
if (e.data == "genkeys")
genkeys(e.port);
else if (e.data == "encrypt")
encrypt(e.port);
else if (e.data == "decrypt")
decrypt(e.port);
}
function genkeys(p) {
var keys = _generateKeyPair();
p.postMessage(keys[0]);
p.postMessage(keys[1]);
}
function encrypt(p) {
var key, state = 0;
p.onmessage = function (e) {
if (state == 0) {
key = e.data;
state = 1;
} else {
p.postMessage(_encrypt(key, e.data));
}
};
}
function decrypt(p) {
var key, state = 0;
p.onmessage = function (e) {
if (state == 0) {
key = e.data;
state = 1;
} else {
p.postMessage(_decrypt(key, e.data));
}
};
}
// support being used as a shared worker as well as a dedicated worker
if (this.onmessage) // dedicated worker
onmessage = handleMessage;
else // shared worker
onconnect = function (e) { e.port.onmessage = handleMessage; }
// the "crypto" functions:
function _generateKeyPair() {
return [Math.random(), Math.random()];
}
function _encrypt(k, s) {
return 'encrypted-' + k + ' ' + s;
}
function _decrypt(k, s) {
return s.substr(s.indexOf(' ')+1);
}
Note that the crypto functions here are just stubs and don't do real cryptography.
This library could be used as follows:
<!DOCTYPE HTML>
<html>
<head>
<title>Worker example: Crypto library</title>
<script>
var crytoLib = new Worker('libcrypto-v1.js'); // or could use 'libcrypto-v2.js'
function getKeys() {
var state = 0;
cryptoLib.startConversation("genkeys").onmessage = function (e) {
if (state == 0)
document.getElementById('public').value = e.data;
else if (state == 1)
document.getElementById('private').value = e.data;
state += 1;
};
}
function enc() {
var port = cryptoLib.startConversation("encrypt");
port.postMessage(document.getElementById('public').value);
port.postMessage(document.getElementById('input').value);
port.onmessage = function (e) {
document.getElementById('input').value = e.data;
port.close();
};
}
function dec() {
var port = cryptoLib.startConversation("decrypt");
port.postMessage(document.getElementById('private').value);
port.postMessage(document.getElementById('input').value);
port.onmessage = function (e) {
document.getElementById('input').value = e.data;
port.close();
};
}
</script>
<style>
textarea { display: block; }
</style>
</head>
<body onload="getKeys()">
<fieldset>
<legend>Keys</legend>
<p><label>Public Key: <textarea id="public"></textarea></label></p>
<p><label>Private Key: <textarea id="private"></textarea></label></p>
</fieldset>
<p><label>Input: <textarea id="input"></textarea></label></p>
<p><button onclick="enc()">Encrypt</button> <button onclick="dec()">Decrypt</button></p>
</body>
</html>
A later version of the API, though, might want to offload all the crypto work onto subworkers. This could be done as follows:
function handleMessage(e) {
if (e.data == "genkeys")
genkeys(e.port);
else if (e.data == "encrypt")
encrypt(e.port);
else if (e.data == "decrypt")
decrypt(e.port);
}
function genkeys(p) {
var generator = new Worker('libcrypto-v2-generator.js');
generator.postMessage('', p);
}
function encrypt(p) {
p.onmessage = function (e) {
var key = e.data;
var encryptor = new Worker('libcrypto-v2-encryptor.js');
encryptor.postMessage(key, p);
};
}
function encrypt(p) {
p.onmessage = function (e) {
var key = e.data;
var decryptor = new Worker('libcrypto-v2-decryptor.js');
decryptor.postMessage(key, p);
};
}
// support being used as a shared worker as well as a dedicated worker
if (this.onmessage) // dedicated worker
onmessage = handleMessage;
else // shared worker
onconnect = function (e) { e.port.onmessage = handleMessage; }
The little subworkers would then be as follows.
For generating key pairs:
onmessage = function (e) {
var k = _generateKeyPair();
e.port.postMessage(k[0]);
e.port.postMessage(k[1]);
close();
}
function _generateKeyPair() {
return [Math.random(), Math.random()];
}
For encrypting:
onmessage = function (e) {
var key = e.data;
e.port.onmessage = function (e) {
var s = e.data;
postMessage(_encrypt(key, s));
}
e.port.onclose = function (e) {
close();
}
}
function _encrypt(k, s) {
return 'encrypted-' + k + ' ' + s;
}
For decrypting:
onmessage = function (e) {
var key = e.data;
e.port.onmessage = function (e) {
var s = e.data;
postMessage(_decrypt(key, s));
}
e.port.onclose = function (e) {
close();
}
}
function _decrypt(k, s) {
return s.substr(s.indexOf(' ')+1);
}
Notice how the users of the API don't have to even know that this is happening — the API hasn't changed; the library can delegate to subworkers without changing its API, even though it is accepting data using message channels.
All diagrams, examples, and notes in this specification are non-normative, as are all sections explicitly marked non-normative. Everything else in this specification is normative.
The key words "MUST", "MUST NOT", "REQUIRED", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in the normative parts of this document are to be interpreted as described in RFC2119. For readability, these words do not appear in all uppercase letters in this specification. [RFC2119]
Requirements phrased in the imperative as part of algorithms (such as "strip any leading space characters" or "return false and abort these steps") are to be interpreted with the meaning of the key word ("must", "should", "may", etc) used in introducing the algorithm.
Some conformance requirements are phrased as requirements on attributes, methods or objects. Such requirements are to be interpreted as requirements on user agents.
Conformance requirements phrased as algorithms or specific steps may be implemented in any manner, so long as the end result is equivalent. (In particular, the algorithms defined in this specification are intended to be easy to follow, and not intended to be performant.)
The only conformance class defined by this specification is user agents.
User agents may impose implementation-specific limits on otherwise unconstrained inputs, e.g. to prevent denial of service attacks, to guard against running out of memory, or to work around platform-specific limitations.
This specification relies on several other underlying specifications.
Many fundamental concepts from HTML5 are used by this specification. [HTML5]
This specification is intended to be used with JavaScript as the scripting language. [JS]
The IDL blocks in this specification use the semantics of the WebIDL specification. [WebIDL]
For simplicity, terms such as shown, displayed, and visible might sometimes be used when referring to the way a document is rendered to the user. These terms are not meant to imply a visual medium; they must be considered to apply to other media in equivalent ways.
The construction "a Foo object", where Foo is actually an interface, is sometimes used instead of
the more accurate "an object implementing the interface Foo".
The term DOM is used to refer to the API set made available to scripts
in Web applications, and does not necessarily imply the existence of an
actual Document object or of any other Node
objects as defined in the DOM Core specifications. [DOM3CORE]
A DOM attribute is said to be getting when its value is being retrieved (e.g. by author script), and is said to be setting when a new value is assigned to it.
If a DOM object is said to be live, then that means that any attributes returning that object must always return the same object (not a new object each time), and the attributes and methods on that object must operate on the actual underlying data, not a snapshot of the data.
There are two kinds of workers; dedicated workers, and shared workers. Dedicated workers, once created, and are linked to their creator; but message ports can be used to communicate from a dedicated worker to multiple other browsing contexts or workers. Shared workers, on the other hand, are named, and once created any script running in the same origin can obtain a reference to that worker and communicate with it.
The global scope is the "inside" of a worker.
WorkerGlobalScope abstract interface[NoInterfaceObject] interface WorkerGlobalScope {
readonly attribute WorkerGlobalScope self;
readonly attribute WorkerLocation location;
// also implements everything on WorkerUtils
void close();
attribute EventListener onclose;
};
Objects implementing the WorkerGlobalScope interface must also
implement the EventTarget interface.
The self attribute must
return the WorkerGlobalScope
object itself.
The location attribute
must return the WorkerLocation
object created for the WorkerGlobalScope object when the
worker was created. It represents the absolute URL of the
script that was used to initialize the worker.
When a script invokes the close() method on a
WorkerGlobalScope object,
the user agent must run the following steps:
Create an Event object with the event name close, which does not bubble and is not
cancelable, and add it to the WorkerGlobalScope object's queue of events, targetted at the WorkerGlobalScope object itself.
Set the worker's WorkerGlobalScope object's closing flag to
true.
For each MessagePort object that is entangled with
another port and that has one (but only one) port whose owner is the
WorkerGlobalScope object
on which the method was invoked (this would include, for instance, the
implicit port in used for dedicated workers), run the following
substeps:
Unentangle the two ports.
At the next available opportunity, after any scripts have finished
executing, fire a simple event called
close at the other port (the one whose
owner is not the WorkerGlobalScope object on which
the close() method was called).
The following are the event handler DOM attributes that
must be supported by objects implementing the WorkerGlobalScope interface:
onclose
Must be invoked whenever a close event
is targeted at or bubbles through the WorkerGlobalScope object.
DedicatedWorkerGlobalScope
interface[NoInterfaceObject] interface DedicatedWorkerGlobalScope : WorkerGlobalScope {
void postMessage(in DOMString message);
void postMessage(in DOMString message, in MessagePort messagePort);
attribute EventListener onmessage;
};
DedicatedWorkerGlobalScope
objects act as if they had an implicit MessagePort associated
with them. This port is part of a channel that is set up when the worker
is created, but it is not exposed. This object must never be garbage
collected before the DedicatedWorkerGlobalScope
object.
All messages received by that port must immediately be retargetted at
the DedicatedWorkerGlobalScope
object.
The postMessage()
method on DedicatedWorkerGlobalScope
objects must act as if, when invoked, it
immediately invoked the method of the same name on the port, with the same
arguments, and returned the same return value.
The following are the event handler DOM attributes that
must be supported by objects implementing the DedicatedWorkerGlobalScope
interface:
onmessage
Must be invoked whenever a message event is
targeted at or bubbles through the DedicatedWorkerGlobalScope
object.
SharedWorkerGlobalScope
inteface[NoInterfaceObject] interface SharedWorkerGlobalScope : WorkerGlobalScope {
readonly attribute DOMString name;
attribute EventListener onconnect;
};
Shared workers receive message ports through connect events on their
global object for each connection.
The name attribute
must return the value it was assigned when the SharedWorkerGlobalScope object
was created by the "run a worker" algorithm. Its
value represents the name that can be used to obtain a reference to the
worker using the SharedWorker
constructor.
The following are the event handler DOM attributes that
must be supported by objects implementing the SharedWorkerGlobalScope
interface:
onconnect
Must be invoked whenever a connect event is
targeted at or bubbles through the SharedWorkerGlobalScope
object.
The base URL of a URL passed to an API in a
worker is the absolute URL given that the worker's location attribute represents.
Both the origin and effective script origin of
scripts running in workers are the origin of the
absolute URL given that the worker's location attribute represents.
Each WorkerGlobalScope
object is asssociated with a queue of events, which is
initially empty.
An event in the queue can be a DOM event or a timeout callback.
All asynchronous callbacks and events that would be called or dispatched in the worker must be added to the worker's queue, with the "run a worker" processing model below taking care of actually calling the callbacks or dispatching the events.
Each WorkerGlobalScope
object also has a closing flag, which must
initially be false, but which can get set to true by the algorithms in the
processing model section below.
Once the WorkerGlobalScope's closing flag is
set to true, the queue must discard anything else that would be added to
it (existing already on the queue is unaffected unless otherwise
specified). Effectively, once the closing flag is true, timers stop
firing, notifications for all pending asynchronous operations are dropped,
etc.
Workers communicate with other workers and with browsing contexts through message channels and their MessagePort
objects.
Each WorkerGlobalScope
worker global scope has a list of the worker's ports, which consists of all the
MessagePort objects that are entangled with another port and
that have one (but only one) port owned by worker global
scope. This list includes
the implicit MessagePort in the case of dedicated workers.
A worker is said to be a permissible worker if either:
MessagePort owned by the
worker was entangled with a MessagePort p whose owner is a Window object whose
active document is the Document that was that
browsing context's active document when p was created, and that Document is
fully active, or
MessagePort owned by the
worker was entangled with a MessagePort owned by another
worker that is currently a permissible worker.
A worker is said to be a protected worker if either:
MessagePort that was entangled
with a MessagePort owned by this worker.
A worker is said to be an active needed worker if either:
MessagePort p whose
owner is a Window object whose active document
is the Document that was that browsing
context's active document when that
MessagePort p was created, and that
Document is fully active, or
MessagePort owned by a WorkerGlobalScope object that is
itself an active needed worker.
A worker is said to be a suspendable worker if it is not an active needed worker but either:
MessagePort owned by a Window
object, or
MessagePort owned by a WorkerGlobalScope object that is
itself a needed worker.
When a user agent is to run a worker for a script
with URL url, a browsing context owner browsing context, a Document owner document, and with global scope worker
global scope, it must run the following steps:
Create a completely separate and parallel execution environment (i.e. a separate thread or process or equivalent construct), and run the rest of these steps asychronously in that context.
Attempt to fetch the resource identified by url.
If the attempt fails, then, at the next available opportunity, after
any scripts have finished executing, fire a
simple event called error at all
Worker or SharedWorker objects associated with
worker global scope. Abort these steps.
If the attempt succeeds, then let script be the resource that was obtained.
As with script elements, the MIME type of the
script is ignored. Unlike with script elements, there is no
way to override the type. It's always assumed to be JavaScript.
Create a new WorkerLocation
object for the location attribute of worker
global scope, representing url.
Let script's script execution context (and thus also global object) be worker global scope.
Let script's script browsing context be owner browsing context.
Let script's script document context be owner document.
Closing orphan workers: Start monitoring the worker
such that as soon as it stops being either an active
needed worker or a suspendable worker,
the worker global scope's closing flag is set to true and
an event named close, which uses the
Event object, which does not bubble, and which is not
cancelable, is added it to worker global scope's
WorkerGlobalScope object's
queue of events, targetted at the worker global scope itself.
Suspending workers: Start monitoring the worker, such that whenever the worker global scope's closing flag is false and the worker is a suspendable worker, the user agent suspends execution of script in that worker until such time as either the closing flag switches to true or the worker stops being a suspendable worker.
Run script until it either returns, fails to catch an exception, or gets prematurely aborted by the "kill a worker" or "terminate a worker" algorithms defined below.
If the script gets aborted by the "kill a
worker" algorithm, then that same algorithm will cause there to only
be a single event in the queue of events at the
next step, namely the close event. The
"terminate a worker" algorithm removes all the
events.
Event loop: Wait until either there is an event in the queue of events associated with worker global scope or the worker global scope's closing flag is set to true.
Dispatch the oldest event or callback in the queue of events, if any. The handling of this event or the execution of this callback might get prematurely aborted by the "kill a worker" or "terminate a worker" algorithms defined below.
If there are any more events in the queue of events or if the worker global scope's closing flag is set to false, then jump back to the step above labeled event loop.
timers, intervals, XMLHttpRequests, database transactions, etc, must be killed; ports must be unentangled
At the next available opportunity, after any scripts have finished
executing, fire a simple event called
close at all Worker or SharedWorker objects associated with
this worker.
When a user agent is to kill a worker it must run the following steps in parallel with the worker's main loop (the "run a worker" processing model defined above):
Create an Event object with the event name close, which does not bubble and is not
cancelable, and add it to the worker's WorkerGlobalScope object's queue of events, targetted at the WorkerGlobalScope object itself.
Set the worker's WorkerGlobalScope object's closing flag to
true.
Wait a user-agent-defined amount of time. If the "run
a worker" processing model defined above immediately starts running
event listeners registered for the close
event, this time should not be zero — the idea is that the close event can be used to clean up when
shutting down unexpectedly.
If there are any events in the queue of events
other than the close event that this
algorithm just added, discard them without dispatching them.
If the close event that this algorithm
just added hasn't yet been dispatched, then abort the script currently
running in the worker.
Wait a user-agent-defined amount of time.
Abort the script currently running in the worker (if any script is
running, then it will be a handler for the close event).
User agents may invoke the "kill a worker" processing model on a worker at any time, e.g. in response to user requests, in response to CPU quota management, or when a worker stops being an active needed worker if the worker continues executing even after its closing flag was set to true.
When a user agent is to terminate a worker it must run the following steps in parallel with the worker's main loop (the "run a worker" processing model defined above):
Set the worker's WorkerGlobalScope object's closing flag to
true.
If there are any events in the queue of events
other than the close event that this
algorithm just added, discard them without dispatching them.
Abort the script currently running in the worker.
AbstractWorker abstract interface[NoInterfaceObject] interface AbstractWorker {
attribute EventListener onerror;
attribute EventListener onclose;
};
Objects implementing the AbstractWorker interface must also
implement the EventTarget interface.
The following are the event handler DOM attributes that
must be supported by objects implementing the AbstractWorker interface:
onerror
Must be invoked whenever an error event
is targeted at or bubbles through the AbstractWorker object.
onclose
Must be invoked whenever an close event
is targeted at or bubbles through the AbstractWorker object.
Worker interface[NoInterfaceObject,
Constructor(in DOMString scriptURL)]
interface Worker : AbstractWorker {
void terminate();
void postMessage(in DOMString message);
void postMessage(in DOMString message, in MessagePort messagePort);
attribute EventListener onmessage;
};
The terminate() method, when
invoked, must cause the "terminate a worker"
algorithm to be run on the worker with with the object is associated.
Worker objects act as if they had an
implicit MessagePort associated with them. This port is part
of a channel that is set up when the worker is created, but it is not
exposed. This object must never be garbage collected before the Worker object.
All messages received by that port must immediately be retargetted at
the Worker object.
The postMessage()
method on Worker objects must act as if, when invoked,
it immediately invoked the method of the
same name on the port, with the same arguments, and returned the same
return value.
The following are the event handler DOM attributes that
must be supported by objects implementing the Worker interface:
onmessage
Must be invoked whenever a message event is targeted at or
bubbles through the Worker object.
When the Worker(scriptURL) constructor is invoked, the user
agent must run the following steps:
Resolve the scriptURL argument.
If this fails, throw a SYNTAX_ERR exception.
If the origin of the resulting absolute URL is not the same as the origin of the script that invoked the constructor, then throw a security exception.
Create a new DedicatedWorkerGlobalScope
object. Let this be the worker global scope.
Create a new Worker object,
associated with worker global scope. Let this
Worker object be worker.
Create a MessagePort object owned by the
script execution context of the script that invoked the
method. Let this be the outside port.
Associate the outside port with worker.
Create a MessagePort object owned by the
worker global scope. Let this be the inside port.
Associate the inside port with worker global scope.
Entangle outside port and inside port.
Return worker, and run the following steps asynchronously.
Open inside port's port message queue.
Open outside port's port message queue.
Run a worker, with the script browsing context of the script that invoked the method as the owner browsing context, with the script document context of the script that invoked the method as the owner document, and with worker global scope as the global scope.
SharedWorker interface[NoInterfaceObject,
Constructor(in DOMString scriptURL, in DOMString name)]
interface SharedWorker : AbstractWorker {
readonly attribute MessagePort port;
};
The port
attribute must return the value it was assigned by the object's
constructor. It represents the MessagePort for communicating
with the shared worker.
When the SharedWorker(scriptURL,
name) constructor is invoked, the user
agent must run the following steps:
Resolve the scriptURL argument.
If this fails, throw a SYNTAX_ERR exception.
If the origin of the resulting absolute URL is not the same as the origin of the script that invoked the constructor, then throw a security exception.
Execute the following substeps atomically:
Create a new SharedWorker
object, which will shortly be associated with a SharedWorkerGlobalScope
object. Let this SharedWorker
object be worker.
Create a MessagePort object owned by the
script execution context of the script that invoked the
method. Let this be the outside port.
Assign outside port to the port attribute
of worker.
If there exists a SharedWorkerGlobalScope
object whose closing flag is false, whose
name attribute is
exactly equal to the name argument, and whose
location attribute represents an
absolute URL that has the same origin as the
resulting absolute URL, then run these substeps:
Let worker global scope be that SharedWorkerGlobalScope
object.
If worker global scope's location attribute represents an
absolute URL that is not exactly equal to the resulting
absolute URL, then throw a
URL_MISMATCH_ERR exception and abort all these steps.
code 21
Associate worker with worker global scope.
Create a MessagePort object owned by the
worker global scope. Let this be the inside port.
Entangle outside port and inside port.
Return worker and perform the next step asynchronously.
Create an event that uses the MessageEvent interface,
with the name connect, which does
not bubble, is cancelable, has no default action, has a data attribute whose value is the
empty string and has a messagePort attribute
whose value is the newly created port, and add it to worker global scope's queue of
events, targetted at the worker global scope
itself.
Abort all these steps.
Create a new SharedWorkerGlobalScope
object. Let this be the worker global
scope.
Associate worker with worker global scope.
Set the name attribute of worker global
scope be name.
Create a MessagePort object owned by the
worker global scope. Let this be the inside port.
Entangle outside port and inside port.
Return worker and perform the next step asynchronously.
Create an event that uses the MessageEvent interface,
with the name connect, which does not
bubble, is cancelable, has no default action, has a data attribute whose value is the
empty string and has a messagePort attribute whose
value is the newly created port, and add it to worker
global scope's queue of events, targetted at
the worker global scope itself.
Run a worker, with the script browsing context of the script that invoked the method as the owner browsing context, with the script document context of the script that invoked the method as the owner document, and with worker global scope as the global scope.
[NoInterfaceObject] interface WorkerUtils {
void importScripts([Variadic] in DOMString urls);
readonly attribute Storage localStorage;
readonly attribute Navigator navigator;
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);
};
Objects that implement the WorkerGlobalScope interface must also
implement the WorkerUtils
interface.
Objects that implement the WorkerUtils interface must also implement
the WindowTimers interface. (This interface provides the
setTimeout() method and its friends.)
Need to define a sync database API.
The DOM APIs (Node objects, Document objects,
etc) are not available to workers in this version of this specification.
When a script invokes the importScripts(urls) method on a WorkerGlobalScope object, the user
agent must run the following steps:
If there are no arguments, return without doing anything. Abort these steps.
Resolve each argument.
If any fail, throw a SYNTAX_ERR exception.
Attempt to fetch each resource identified by the resulting absolute URL.
For each argument in turn, in the order given, starting with the first one, run these substeps:
Wait for the fetching attempt for the corresponding resource to complete.
If the fetching attempt failed, throw a NETWORK_ERR
exception and abort all these steps. [XHR]
If the fetching attempt succeeded, then let script be the resource that was obtained.
As with the worker's script, the script here is always assumed to be JavaScript, regardless of the MIME type.
Let script's script execution context, script browsing context, and script document context be the same as for the script that was executed by the run a worker processing model for this worker.
Parse and execute script until it either returns, fails to parse, fails to catch an exception, or gets prematurely aborted by the "kill a worker" or "terminate a worker" algorithms defined above.
If it failed to parse, then throw a
SyntaxError exception and abort all
these steps.
If an exception was raised or if the script was prematurely aborted,
then abort all these steps, letting the exception or aborting continue
to be processed by the script that called the importScripts() method.
If the "kill a worker" or "terminate a worker" algorithms abort the script then abort all these steps.
Navigator objectThe navigator
attribute of the WorkerUtils
interface must return an instance of the Navigator interface, which represents the
identity and state of the user agent (the client):
[NoInterfaceObject] interface Navigator {
// objects implementing this interface also implement the interfaces listed below
};
Objects implementing the Navigator interface must also implement the
NavigatorID and NavigatorOnLine interfaces
specified in the HTML5 specification. [HTML5]
The Navigator
interface defined in this specification is different than the one defined
in the HTML5 specification.
The localStorage, openDatabase()
must act as defined for the APIs with the same names on the
Window object in the HTML5 specification, with the exception
that where the API would use the origin of the active
document of the browsing context of the
Window object on which the method was supposedly invoked, it
must instead use the origin of the script that invoked the
method. [HTML5]
The showNotification() methods
must act as defined for the APIs with the same names on the
Window object in the HTML5 specification. [HTML5]
There must be no interface objects and constructors available in the
global scope of scripts whose script execution context is a
WorkerGlobalScope object
except for the following:
XMLHttpRequest and all interface objects and constructors
defined by the XMLHttpRequest specifications, except that the
document response entity body must always be null. [XHR]
The WebSocket interface object and constructor.
The MessageChannel interface object and constructor.
The Worker() and
SharedWorker(url) constructors.
[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;
};
A WorkerLocation object
represents an absolute URL set at its creation.
The href
attribute must return the absolute URL that the object
represents.
The WorkerLocation interface
also has the complement of URL decomposition attributes, protocol,
host, port, hostname,
pathname, search, and
hash. These
must follow the rules given for URL decomposition attributes, with the
input being the absolute
URL that the object represents (same as the href attribute),
and the common setter action being a
no-op, since the attributes are defined to be readonly. [HTML5]
This section will be written in a future draft.
Thanks to Aaron Boodman, Dmitry Titov, Jonas Sicking, Justin James, Kevin Hakanson, Maciej Stachowiak, Michael Nordman, Mike Smith, and Philip Taylor for their useful and substantial comments.
Huge thanks to the whole Gears team, who pioneered this technology and whose experience has been a huge influence on this specification.