Untitled diff

Created Diff never expires
26 removals
688 lines
62 additions
724 lines
1 Introduction
10.1 Introduction


1.1 Scope
10.1.1 Scope


This section is non-normative.
This section is non-normative.


This specification defines an API for running scripts in the background independently of any user interface scripts.
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.
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.
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.
Generally, workers are expected to be long-lived, have a high start-up performance cost, and a high per-instance memory cost.


1.2 Examples
10.1.2 Examples


This section is non-normative.
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.
There are a variety of uses that workers can be put to. The following subsections show various examples of this use.


1.2.1 A background number-crunching worker
10.1.2.1 A background number-crunching worker


This section is non-normative.
This section is non-normative.


The simplest use of workers is for performing a computationally expensive task without interrupting the user interface.
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.
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:
The main page is as follows:


<!DOCTYPE HTML>
<!DOCTYPE HTML>
<html>
<html>
<head>
<head>
<title>Worker example: One-core computation</title>
<title>Worker example: One-core computation</title>
</head>
</head>
<body>
<body>
<p>The highest prime number discovered so far is: <output id="result"></output></p>
<p>The highest prime number discovered so far is: <output id="result"></output></p>
<script>
<script>
var worker = new Worker('worker.js');
var worker = new Worker('worker.js');
worker.onmessage = function (event) {
worker.onmessage = function (event) {
document.getElementById('result').textContent = event.data;
document.getElementById('result').textContent = event.data;
};
};
</script>
</script>
</body>
</body>
</html>
</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 allows the code to receive messages from the worker.
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 allows the code to receive messages from the worker.


The worker itself is as follows:
The worker itself is as follows:


var n = 1;
var n = 1;
search: while (true) {
search: while (true) {
n += 1;
n += 1;
for (var i = 2; i <= Math.sqrt(n); i += 1)
for (var i = 2; i <= Math.sqrt(n); i += 1)
if (n % i == 0)
if (n % i == 0)
continue search;
continue search;
// found a prime!
// found a prime!
postMessage(n);
postMessage(n);
}
}
The bulk of this code is simply an unoptimized search for a prime number. The postMessage() method is used to send a message back to the page when a prime is found.
The bulk of this code is simply an unoptimised search for a prime number. The postMessage() method is used to send a message back to the page when a prime is found.


View this example online.
View this example online.


1.2.2 Worker used for background I/O
10.1.2.2 Worker used for background I/O


This section is non-normative.
This section is non-normative.


In this example, the main document uses two workers, one for fetching stock updates at regular intervals, and one for performing search queries that the user requests.
In this example, the main document uses two workers, one for fetching stock updates at regular intervals, and one for performing search queries that the user requests.


The main page is as follows:
The main page is as follows:


<!DOCTYPE HTML>
<!DOCTYPE HTML>
<html>
<html>
<head>
<head>
<title>Worker example: Stock ticker</title>
<title>Worker example: Stock ticker</title>
<script>
<script>
// TICKER
// TICKER
var symbol = 'GOOG'; // default symbol to watch
var symbol = 'GOOG'; // default symbol to watch
var ticker = new Worker('ticker.js');
var ticker = new Worker('ticker.js');


// SEARCHER
// SEARCHER
var searcher = new Worker('searcher.js');
var searcher = new Worker('searcher.js');
function search(query) {
function search(query) {
searcher.postMessage(query);
searcher.postMessage(query);
}
}


// SYMBOL SELECTION UI
// SYMBOL SELECTION UI
function select(newSymbol) {
function select(newSymbol) {
symbol = newSymbol;
symbol = newSymbol;
ticker.postMessage(symbol);
ticker.postMessage(symbol);
}
}
</script>
</script>
<meta http-equiv="Refresh" content="120; URL=../">
<meta http-equiv="Refresh" content="120; URL=../">
</head>
</head>
<body onload="search('')">
<body onload="search('')">
<p><output id="symbol"></output> <output id="value"></output></p>
<p><output id="symbol"></output> <output id="value"></output></p>
<script>
<script>
ticker.onmessage = function (event) {
ticker.onmessage = function (event) {
var data = event.data.split(' ');
var data = event.data.split(' ');
document.getElementById('symbol').textContent = data[0];
document.getElementById('symbol').textContent = data[0];
document.getElementById('value').textContent = data[1];
document.getElementById('value').textContent = data[1];
};
};
ticker.postMessage(symbol);
ticker.postMessage(symbol);
</script>
</script>
<p><label>Search: <input type="text" autofocus oninput="search(this.value)"></label></p>
<p><label>Search: <input type="text" autofocus oninput="search(this.value)"></label></p>
<ul id="results"></ul>
<ul id="results"></ul>
<script>
<script>
searcher.onmessage = function (event) {
searcher.onmessage = function (event) {
var data = event.data.split(' ');
var data = event.data.split(' ');
var results = document.getElementById('results');
var results = document.getElementById('results');
while (results.hasChildNodes()) // clear previous results
while (results.hasChildNodes()) // clear previous results
results.removeChild(results.firstChild);
results.removeChild(results.firstChild);
for (var i = 0; i < data.length; i += 1) {
for (var i = 0; i < data.length; i += 1) {
// add a list item with a button for each result
// add a list item with a button for each result
var li = document.createElement('li');
var li = document.createElement('li');
var button = document.createElement('button');
var button = document.createElement('button');
button.value = data[i];
button.value = data[i];
button.type = 'button';
button.type = 'button';
button.onclick = function () { select(this.value); };
button.onclick = function () { select(this.value); };
button.textContent = data[i];
button.textContent = data[i];
li.appendChild(button);
li.appendChild(button);
results.appendChild(li);
results.appendChild(li);
}
}
};
};
</script>
</script>
<p>(The data in this example is not real. Try searching for "Google" or "Apple".)</p>
<p>(The data in this example is not real. Try searching for "Google" or "Apple".)</p>
</body>
</body>
</html>
</html>
The two workers use a common library for performing the actual network calls. This library is as follows:
The two workers use a common library for performing the actual network calls. This library is as follows:


function get(url) {
function get(url) {
try {
try {
var xhr = new XMLHttpRequest();
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.open('GET', url, false);
xhr.send();
xhr.send();
return xhr.responseText;
return xhr.responseText;
} catch (e) {
} catch (e) {
return ''; // turn all errors into empty results
return ''; // turn all errors into empty results
}
}
}
}
The stock updater worker is as follows:
The stock updater worker is as follows:


importScripts('io.js');
importScripts('io.js');
var timer;
var timer;
var symbol;
var symbol;
function update() {
function update() {
postMessage(symbol + ' ' + get('stock.cgi?' + symbol));
postMessage(symbol + ' ' + get('stock.cgi?' + symbol));
timer = setTimeout(update, 10000);
timer = setTimeout(update, 10000);
}
}
onmessage = function (event) {
onmessage = function (event) {
if (timer)
if (timer)
clearTimeout(timer);
clearTimeout(timer);
symbol = event.data;
symbol = event.data;
update();
update();
};
};
The search query worker is as follows:
The search query worker is as follows:


importScripts('io.js');
importScripts('io.js');
onmessage = function (event) {
onmessage = function (event) {
postMessage(get('search.cgi?' + event.data));
postMessage(get('search.cgi?' + event.data));
};
};
View this example online.
View this example online.


1.2.3 Shared workers introduction
10.1.2.3 Shared workers introduction


Support:
Chrome 4+
Chrome for Android None
IE None
UC Browser for Android 9.9+
Firefox 29+
iOS Safari None
Opera Mini None
Android Browser None
Safari None
Opera 10.6+
IE Mobile None
Edge None
Source: caniuse.com
This section is non-normative.
This section is non-normative.


This section introduces shared workers using a Hello World example. Shared workers use slightly different APIs, since each worker can have multiple connections.
This section introduces shared workers using a Hello World example. Shared workers use slightly different APIs, since each worker can have multiple connections.


This first example shows how you connect to a worker and how a worker can send a message back to the page when it connects to it. Received messages are displayed in a log.
This first example shows how you connect to a worker and how a worker can send a message back to the page when it connects to it. Received messages are displayed in a log.


Here is the HTML page:
Here is the HTML page:


<!DOCTYPE HTML>
<!DOCTYPE HTML>
<title>Shared workers: demo 1</title>
<title>Shared workers: demo 1</title>
<pre id="log">Log:</pre>
<pre id="log">Log:</pre>
<script>
<script>
var worker = new SharedWorker('test.js');
var worker = new SharedWorker('test.js');
var log = document.getElementById('log');
var log = document.getElementById('log');
worker.port.onmessage = function(e) { // note: not worker.onmessage!
worker.port.onmessage = function(e) { // note: not worker.onmessage!
log.textContent += '\n' + e.data;
log.textContent += '\n' + e.data;
}
}
</script>
</script>
Here is the JavaScript worker:
Here is the JavaScript worker:


onconnect = function(e) {
onconnect = function(e) {
var port = e.ports[0];
var port = e.ports[0];
port.postMessage('Hello World!');
port.postMessage('Hello World!');
}
}
View this example online.
View this example online.


This second example extends the first one by changing two things: first, messages are received using addEventListener() instead of an event handler IDL attribute, and second, a message is sent to the worker, causing the worker to send another message in return. Received messages are again displayed in a log.
This second example extends the first one by changing two things: first, messages are received using addEventListener() instead of an event handler IDL attribute, and second, a message is sent to the worker, causing the worker to send another message in return. Received messages are again displayed in a log.


Here is the HTML page:
Here is the HTML page:


<!DOCTYPE HTML>
<!DOCTYPE HTML>
<title>Shared workers: demo 2</title>
<title>Shared workers: demo 2</title>
<pre id="log">Log:</pre>
<pre id="log">Log:</pre>
<script>
<script>
var worker = new SharedWorker('test.js');
var worker = new SharedWorker('test.js');
var log = document.getElementById('log');
var log = document.getElementById('log');
worker.port.addEventListener('message', function(e) {
worker.port.addEventListener('message', function(e) {
log.textContent += '\n' + e.data;
log.textContent += '\n' + e.data;
}, false);
}, false);
worker.port.start(); // note: need this when using addEventListener
worker.port.start(); // note: need this when using addEventListener
worker.port.postMessage('ping');
worker.port.postMessage('ping');
</script>
</script>
Here is the JavaScript worker:
Here is the JavaScript worker:


onconnect = function(e) {
onconnect = function(e) {
var port = e.ports[0];
var port = e.ports[0];
port.postMessage('Hello World!');
port.postMessage('Hello World!');
port.onmessage = function(e) {
port.onmessage = function(e) {
port.postMessage('pong'); // not e.ports[0].postMessage!
port.postMessage('pong'); // not e.ports[0].postMessage!
// e.target.postMessage('pong'); would work also
// e.target.postMessage('pong'); would work also
}
}
}
}
View this example online.
View this example online.


Finally, the example is extended to show how two pages can connect to the same worker; in this case, the second page is merely in an iframe on the first page, but the same principle would apply to an entirely separate page in a separate top-level browsing context.
Finally, the example is extended to show how two pages can connect to the same worker; in this case, the second page is merely in an iframe on the first page, but the same principle would apply to an entirely separate page in a separate top-level browsing context.


Here is the outer HTML page:
Here is the outer HTML page:


<!DOCTYPE HTML>
<!DOCTYPE HTML>
<title>Shared workers: demo 3</title>
<title>Shared workers: demo 3</title>
<pre id="log">Log:</pre>
<pre id="log">Log:</pre>
<script>
<script>
var worker = new SharedWorker('test.js');
var worker = new SharedWorker('test.js');
var log = document.getElementById('log');
var log = document.getElementById('log');
worker.port.addEventListener('message', function(e) {
worker.port.addEventListener('message', function(e) {
log.textContent += '\n' + e.data;
log.textContent += '\n' + e.data;
}, false);
}, false);
worker.port.start();
worker.port.start();
worker.port.postMessage('ping');
worker.port.postMessage('ping');
</script>
</script>
<iframe src="inner.html"></iframe>
<iframe src="inner.html"></iframe>
Here is the inner HTML page:
Here is the inner HTML page:


<!DOCTYPE HTML>
<!DOCTYPE HTML>
<title>Shared workers: demo 3 inner frame</title>
<title>Shared workers: demo 3 inner frame</title>
<pre id=log>Inner log:</pre>
<pre id=log>Inner log:</pre>
<script>
<script>
var worker = new SharedWorker('test.js');
var worker = new SharedWorker('test.js');
var log = document.getElementById('log');
var log = document.getElementById('log');
worker.port.onmessage = function(e) {
worker.port.onmessage = function(e) {
log.textContent += '\n' + e.data;
log.textContent += '\n' + e.data;
}
}
</script>
</script>
Here is the JavaScript worker:
Here is the JavaScript worker:


var count = 0;
var count = 0;
onconnect = function(e) {
onconnect = function(e) {
count += 1;
count += 1;
var port = e.ports[0];
var port = e.ports[0];
port.postMessage('Hello World! You are connection #' + count);
port.postMessage('Hello World! You are connection #' + count);
port.onmessage = function(e) {
port.onmessage = function(e) {
port.postMessage('pong');
port.postMessage('pong');
}
}
}
}
View this example online.
View this example online.


1.2.4 Shared state using a shared worker
10.1.2.4 Shared state using a shared worker


This section is non-normative.
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.
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:
The main page isn't interesting, it merely provides a way to open the viewers:


<!DOCTYPE HTML>
<!DOCTYPE HTML>
<html>
<html>
<head>
<head>
<title>Workers example: Multiviewer</title>
<title>Workers example: Multiviewer</title>
<script>
<script>
function openViewer() {
function openViewer() {
window.open('viewer.html');
window.open('viewer.html');
}
}
</script>
</script>
</head>
</head>
<body>
<body>
<p><button type=button onclick="openViewer()">Open a new
<p><button type=button onclick="openViewer()">Open a new
viewer</button></p>
viewer</button></p>
<p>Each viewer opens in a new window. You can have as many viewers
<p>Each viewer opens in a new window. You can have as many viewers
as you like, they all view the same data.</p>
as you like, they all view the same data.</p>
</body>
</body>
</html>
</html>
The viewer is more involved:
The viewer is more involved:


<!DOCTYPE HTML>
<!DOCTYPE HTML>
<html>
<html>
<head>
<head>
<title>Workers example: Multiviewer viewer</title>
<title>Workers example: Multiviewer viewer</title>
<script>
<script>
var worker = new SharedWorker('worker.js', 'core');
var worker = new SharedWorker('worker.js', 'core');


// CONFIGURATION
// CONFIGURATION
function configure(event) {
function configure(event) {
if (event.data.substr(0, 4) != 'cfg ') return;
if (event.data.substr(0, 4) != 'cfg ') return;
var name = event.data.substr(4).split(' ', 1)[0];
var name = event.data.substr(4).split(' ', 1)[0];
// update display to mention our name is name
// update display to mention our name is name
document.getElementsByTagName('h1')[0].textContent += ' ' + name;
document.getElementsByTagName('h1')[0].textContent += ' ' + name;
// no longer need this listener
// no longer need this listener
worker.port.removeEventListener('message', configure, false);
worker.port.removeEventListener('message', configure, false);
}
}
worker.port.addEventListener('message', configure, false);
worker.port.addEventListener('message', configure, false);


// MAP
// MAP
function paintMap(event) {
function paintMap(event) {
if (event.data.substr(0, 4) != 'map ') return;
if (event.data.substr(0, 4) != 'map ') return;
var data = event.data.substr(4).split(',');
var data = event.data.substr(4).split(',');
// display tiles data[0] .. data[8]
// display tiles data[0] .. data[8]
var canvas = document.getElementById('map');
var canvas = document.getElementById('map');
var context = canvas.getContext('2d');
var context = canvas.getContext('2d');
for (var y = 0; y < 3; y += 1) {
for (var y = 0; y < 3; y += 1) {
for (var x = 0; x < 3; x += 1) {
for (var x = 0; x < 3; x += 1) {
var tile = data[y * 3 + x];
var tile = data[y * 3 + x];
if (tile == '0')
if (tile == '0')
context.fillStyle = 'green';
context.fillStyle = 'green';
else
else
context.fillStyle = 'maroon';
context.fillStyle = 'maroon';
context.fillRect(x * 50, y * 50, 50, 50);
context.fillRect(x * 50, y * 50, 50, 50);
}
}
}
}
}
}
worker.port.addEventListener('message', paintMap, false);
worker.port.addEventListener('message', paintMap, false);


// PUBLIC CHAT
// PUBLIC CHAT
function updatePublicChat(event) {
function updatePublicChat(event) {
if (event.data.substr(0, 4) != 'txt ') return;
if (event.data.substr(0, 4) != 'txt ') return;
var name = event.data.substr(4).split(' ', 1)[0];
var name = event.data.substr(4).split(' ', 1)[0];
var message = event.data.substr(4 + name.length + 1);
var message = event.data.substr(4 + name.length + 1);
// display "<name> message" in public chat
// display "<name> message" in public chat
var public = document.getElementById('public');
var public = document.getElementById('public');
var p = document.createElement('p');
var p = document.createElement('p');
var n = document.createElement('button');
var n = document.createElement('button');
n.textContent = '<' + name + '>';
n.textContent = '<' + name + '> ';
n.onclick = function () { worker.port.postMessage('msg ' + name); };
n.onclick = function () { worker.port.postMessage('msg ' + name); };
p.appendChild(n);
p.appendChild(n);
var m = document.createElement('span');
var m = document.createElement('span');
m.textContent = message;
m.textContent = message;
p.appendChild(m);
p.appendChild(m);
public.appendChild(p);
public.appendChild(p);
}
}
worker.port.addEventListener('message', updatePublicChat, false);
worker.port.addEventListener('message', updatePublicChat, false);


// PRIVATE CHAT
// PRIVATE CHAT
function startPrivateChat(event) {
function startPrivateChat(event) {
if (event.data.substr(0, 4) != 'msg ') return;
if (event.data.substr(0, 4) != 'msg ') return;
var name = event.data.substr(4).split(' ', 1)[0];
var name = event.data.substr(4).split(' ', 1)[0];
var port = event.ports[0];
var port = event.ports[0];
// display a private chat UI
// display a private chat UI
var ul = document.getElementById('private');
var ul = document.getElementById('private');
var li = document.createElement('li');
var li = document.createElement('li');
var h3 = document.createElement('h3');
var h3 = document.createElement('h3');
h3.textContent = 'Private chat with ' + name;
h3.textContent = 'Private chat with ' + name;
li.appendChild(h3);
li.appendChild(h3);
var div = document.createElement('div');
var div = document.createElement('div');
var addMessage = function(name, message) {
var addMessage = function(name, message) {
var p = document.createElement('p');
var p = document.createElement('p');
var n = document.createElement('strong');
var n = document.createElement('strong');
n.textContent = '<' + name + '>';
n.textContent = '<' + name + '> ';
p.appendChild(n);
p.appendChild(n);
var t = document.createElement('span');
var t = document.createElement('span');
t.textContent = message;
t.textContent = message;
p.appendChild(t);
p.appendChild(t);
div.appendChild(p);
div.appendChild(p);
};
};
port.onmessage = function (event) {
port.onmessage = function (event) {
addMessage(name, event.data);
addMessage(name, event.data);
};
};
li.appendChild(div);
li.appendChild(div);
var form = document.createElement('form');
var form = document.createElement('form');
var p = document.createElement('p');
var p = document.createElement('p');
var input = document.createElement('input');
var input = document.createElement('input');
input.size = 50;
input.size = 50;
p.appendChild(input);
p.appendChild(input);
p.appendChild(document.createTextNode(' '));
p.appendChild(document.createTextNode(' '));
var button = document.createElement('button');
var button = document.createElement('button');
button.textContent = 'Post';
button.textContent = 'Post';
p.appendChild(button);
p.appendChild(button);
form.onsubmit = function () {
form.onsubmit = function () {
port.postMessage(input.value);
port.postMessage(input.value);
addMessage('me', input.value);
addMessage('me', input.value);
input.value = '';
input.value = '';
return false;
return false;
};
};
form.appendChild(p);
form.appendChild(p);
li.appendChild(form);
li.appendChild(form);
ul.appendChild(li);
ul.appendChild(li);
}
}
worker.port.addEventListener('message', startPrivateChat, false);
worker.port.addEventListener('message', startPrivateChat, false);


worker.port.start();
worker.port.start();
</script>
</script>
</head>
</head>
<body>
<body>
<h1>Viewer</h1>
<h1>Viewer</h1>
<h2>Map</h2>
<h2>Map</h2>
<p><canvas id="map" height=150 width=150></canvas></p>
<p><canvas id="map" height=150 width=150></canvas></p>
<p>
<p>
<button type=button onclick="worker.port.postMessage('mov left')">Left</button>
<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 up')">Up</button>
<button type=button onclick="worker.port.postMessage('mov down')">Down</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('mov right')">Right</button>
<button type=button onclick="worker.port.postMessage('set 0')">Set 0</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>
<button type=button onclick="worker.port.postMessage('set 1')">Set 1</button>
</p>
</p>
<h2>Public Chat</h2>
<h2>Public Chat</h2>
<div id="public"></div>
<div id="public"></div>
<form onsubmit="worker.port.postMessage('txt ' + message.value); message.value = ''; return false;">
<form onsubmit="worker.port.postMessage('txt ' + message.value); message.value = ''; return false;">
<p>
<p>
<input type="text" name="message" size="50">
<input type="text" name="message" size="50">
<button>Post</button>
<button>Post</button>
</p>
</p>
</form>
</form>
<h2>Private Chat</h2>
<h2>Private Chat</h2>
<ul id="private"></ul>
<ul id="private"></ul>
</body>
</body>
</html>
</html>
There are several key things worth noting about the way the viewer is written.
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.
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.
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:
Finally, the worker:


var nextName = 0;
var nextName = 0;
function getNextName() {
function getNextName() {
// this could use more friendly names
// this could use more friendly names
// but for now just return a number
// but for now just return a number
return nextName++;
return nextName++;
}
}


var map = [
var map = [
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 1, 0, 1, 1],
[1, 1, 0, 1, 0, 1, 1],
[0, 1, 0, 1, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 0],
[0, 1, 0, 1, 0, 1, 1],
[0, 1, 0, 1, 0, 1, 1],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[1, 0, 0, 1, 1, 1, 1],
[1, 0, 0, 1, 1, 1, 1],
[1, 1, 0, 1, 1, 0, 1],
[1, 1, 0, 1, 1, 0, 1],
];
];


function wrapX(x) {
function wrapX(x) {
if (x < 0) return wrapX(x + map[0].length);
if (x < 0) return wrapX(x + map[0].length);
if (x >= map[0].length) return wrapX(x - map[0].length);
if (x >= map[0].length) return wrapX(x - map[0].length);
return x;
return x;
}
}


function wrapY(y) {
function wrapY(y) {
if (y < 0) return wrapY(y + map.length);
if (y < 0) return wrapY(y + map.length);
if (y >= map[0].length) return wrapY(y - map.length);
if (y >= map[0].length) return wrapY(y - map.length);
return y;
return y;
}
}


function wrap(val, min, max) {
function wrap(val, min, max) {
if (val < min)
if (val < min)
return val + (max-min)+1;
return val + (max-min)+1;
if (val > max)
if (val > max)
return val - (max-min)-1;
return val - (max-min)-1;
return val;
return val;
}
}


function sendMapData(viewer) {
function sendMapData(viewer) {
var data = '';
var data = '';
for (var y = viewer.y-1; y <= viewer.y+1; y += 1) {
for (var y = viewer.y-1; y <= viewer.y+1; y += 1) {
for (var x = viewer.x-1; x <= viewer.x+1; x += 1) {
for (var x = viewer.x-1; x <= viewer.x+1; x += 1) {
if (data != '')
if (data != '')
data += ',';
data += ',';
data += map[wrap(y, 0, map[0].length-1)][wrap(x, 0, map.length-1)];
data += map[wrap(y, 0, map[0].length-1)][wrap(x, 0, map.length-1)];
}
}
}
}
viewer.port.postMessage('map ' + data);
viewer.port.postMessage('map ' + data);
}
}


var viewers = {};
var viewers = {};
onconnect = function (event) {
onconnect = function (event) {
var name = getNextName();
var name = getNextName();
event.ports[0]._data = { port: event.ports[0], name: name, x: 0, y: 0, };
event.ports[0]._data = { port: event.ports[0], name: name, x: 0, y: 0, };
viewers[name] = event.ports[0]._data;
viewers[name] = event.ports[0]._data;
event.ports[0].postMessage('cfg ' + name);
event.ports[0].postMessage('cfg ' + name);
event.ports[0].onmessage = getMessage;
event.ports[0].onmessage = getMessage;
sendMapData(event.ports[0]._data);
sendMapData(event.ports[0]._data);
};
};


function getMessage(event) {
function getMessage(event) {
switch (event.data.substr(0, 4)) {
switch (event.data.substr(0, 4)) {
case 'mov ':
case 'mov ':
var direction = event.data.substr(4);
var direction = event.data.substr(4);
var dx = 0;
var dx = 0;
var dy = 0;
var dy = 0;
switch (direction) {
switch (direction) {
case 'up': dy = -1; break;
case 'up': dy = -1; break;
case 'down': dy = 1; break;
case 'down': dy = 1; break;
case 'left': dx = -1; break;
case 'left': dx = -1; break;
case 'right': dx = 1; break;
case 'right': dx = 1; break;
}
}
event.target._data.x = wrapX(event.target._data.x + dx);
event.target._data.x = wrapX(event.target._data.x + dx);
event.target._data.y = wrapY(event.target._data.y + dy);
event.target._data.y = wrapY(event.target._data.y + dy);
sendMapData(event.target._data);
sendMapData(event.target._data);
break;
break;
case 'set ':
case 'set ':
var value = event.data.substr(4);
var value = event.data.substr(4);
map[event.target._data.y][event.target._data.x] = value;
map[event.target._data.y][event.target._data.x] = value;
for (var viewer in viewers)
for (var viewer in viewers)
sendMapData(viewers[viewer]);
sendMapData(viewers[viewer]);
break;
break;
case 'txt ':
case 'txt ':
var name = event.target._data.name;
var name = event.target._data.name;
var message = event.data.substr(4);
var message = event.data.substr(4);
for (var viewer in viewers)
for (var viewer in viewers)
viewers[viewer].port.postMessage('txt ' + name + ' ' + message);
viewers[viewer].port.postMessage('txt ' + name + ' ' + message);
break;
break;
case 'msg ':
case 'msg ':
var party1 = event.target._data;
var party1 = event.target._data;
var party2 = viewers[event.data.substr(4).split(' ', 1)[0]];
var party2 = viewers[event.data.substr(4).split(' ', 1)[0]];
if (party2) {
if (party2) {
var channel = new MessageChannel();
var channel = new MessageChannel();
party1.port.postMessage('msg ' + party2.name, [channel.port1]);
party1.port.postMessage('msg ' + party2.name, [channel.port1]);
party2.port.postMessage('msg ' + party1.name, [channel.port2]);
party2.port.postMessage('msg ' + party1.name, [channel.port2]);
}
}
break;
break;
}
}
}
}
Connecting to multiple pages. The script uses the onconnect event listener to listen for multiple connections.
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.
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.


View this example online.
View this example online.


1.2.5 Delegation
10.1.2.5 Delegation


This section is non-normative.
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.
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:
The main page is as follows, it just reports the result:


<!DOCTYPE HTML>
<!DOCTYPE HTML>
<html>
<html>
<head>
<head>
<title>Worker example: Multicore computation</title>
<title>Worker example: Multicore computation</title>
</head>
</head>
<body>
<body>
<p>Result: <output id="result"></output></p>
<p>Result: <output id="result"></output></p>
<script>
<script>
var worker = new Worker('worker.js');
var worker = new Worker('worker.js');
worker.onmessage = function (event) {
worker.onmessage = function (event) {
document.getElementById('result').textContent = event.data;
document.getElementById('result').textContent = event.data;
};
};
</script>
</script>
</body>
</body>
</html>
</html>
The worker itself is as follows:
The worker itself is as follows:


// settings
// settings
var num_workers = 10;
var num_workers = 10;
var items_per_worker = 1000000;
var items_per_worker = 1000000;


// start the workers
// start the workers
var result = 0;
var result = 0;
var pending_workers = num_workers;
var pending_workers = num_workers;
for (var i = 0; i < num_workers; i += 1) {
for (var i = 0; i < num_workers; i += 1) {
var worker = new Worker('core.js');
var worker = new Worker('core.js');
worker.postMessage(i * items_per_worker);
worker.postMessage(i * items_per_worker);
worker.postMessage((i+1) * items_per_worker);
worker.postMessage((i+1) * items_per_worker);
worker.onmessage = storeResult;
worker.onmessage = storeResult;
}
}


// handle the results
// handle the results
function storeResult(event) {
function storeResult(event) {
result += 1*event.data;
result += 1*event.data;
pending_workers -= 1;
pending_workers -= 1;
if (pending_workers <= 0)
if (pending_workers <= 0)
postMessage(result); // finished!
postMessage(result); // finished!
}
}
It consists of a loop to start the subworkers, and then a handler that waits for all the subworkers to respond.
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:
The subworkers are implemented as follows:


var start;
var start;
onmessage = getStart;
onmessage = getStart;
function getStart(event) {
function getStart(event) {
start = 1*event.data;
start = 1*event.data;
onmessage = getEnd;
onmessage = getEnd;
}
}


var end;
var end;
function getEnd(event) {
function getEnd(event) {
end = 1*event.data;
end = 1*event.data;
onmessage = null;
onmessage = null;
work();
work();
}
}


function work() {
function work() {
var result = 0;
var result = 0;
for (var i = start; i < end; i += 1) {
for (var i = start; i < end; i += 1) {
// perform some complex calculation here
// perform some complex calculation here
result += 1;
result += 1;
}
}
postMessage(result);
postMessage(result);
close();
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.
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.


View this example online.
View this example online.


1.3 Tutorials
10.1.3 Tutorials


1.3.1 Creating a dedicated worker
10.1.3.1 Creating a dedicated worker


This section is non-normative.
This section is non-normative.


Creating a worker requires a URL to a JavaScript file. The Worker() constructor is invoked with the URL to that file as its only argument; a worker is then created and returned:
Creating a worker requires a URL to a JavaScript file. The Worker() constructor is invoked with the URL to that file as its only argument; a worker is then created and returned:


var worker = new Worker('helper.js');
var worker = new Worker('helper.js');
1.3.2 Communicating with a dedicated worker
10.1.3.2 Communicating with a dedicated worker


This section is non-normative.
This section is non-normative.


Dedicated workers use MessagePort objects behind the scenes, and thus support all the same features, such as sending structured data, transferring binary data, and transferring other ports.
Dedicated workers use MessagePort objects behind the scenes, and thus support all the same features, such as sending structured data, transferring binary data, and transferring other ports.


To receive messages from a dedicated worker, use the onmessage event handler IDL attribute on the Worker object:
To receive messages from a dedicated worker, use the onmessage event handler IDL attribute on the Worker object:


worker.onmessage = function (event) { ... };
worker.onmessage = function (event) { ... };
You can also use the addEventListener() method.
You can also use the addEventListener() method.


The implicit MessagePort used by dedicated workers has its port message queue implicitly enabled when it is created, so there is no equivalent to the MessagePort interface's start() method on the Worker interface.
The implicit MessagePort used by dedicated workers has its port message queue implicitly enabled when it is created, so there is no equivalent to the MessagePort interface's start() method on the Worker interface.


To send data to a worker, use the postMessage() method. Structured data can be sent over this communication channel. To send ArrayBuffer objects efficiently (by transferring them rather than cloning them), list them in an array in the second argument.
To send data to a worker, use the postMessage() method. Structured data can be sent over this communication channel. To send ArrayBuffer objects efficiently (by transferring them rather than cloning them), list them in an array in the second argument.


worker.postMessage({
worker.postMessage({
operation: 'find-edges',
operation: 'find-edges',
input: buffer, // an ArrayBuffer object
input: buffer, // an ArrayBuffer object
threshold: 0.6,
threshold: 0.6,
}, [buffer]);
}, [buffer]);
To receive a message inside the worker, the onmessage event handler IDL attribute is used.
To receive a message inside the worker, the onmessage event handler IDL attribute is used.


onmessage = function (event) { ... };
onmessage = function (event) { ... };
You can again also use the addEventListener() method.
You can again also use the addEventListener() method.


In either case, the data is provided in the event object's data attribute.
In either case, the data is provided in the event object's data attribute.


To send messages back, you again use postMessage(). It supports the structured data in the same manner.
To send messages back, you again use postMessage(). It supports the structured data in the same manner.


postMessage(event.data.input, [event.data.input]); // transfer the buffer back
postMessage(event.data.input, [event.data.input]); // transfer the buffer back
1.3.3 Shared workers
10.1.3.3 Shared workers


This section is non-normative.
This section is non-normative.


Shared workers are identified by the URL of the script used to create it, optionally with an explicit name. The name allows multiple instances of a particular shared worker to be started.
Shared workers are identified by the URL of the script used to create it, optionally with an explicit name. The name allows multiple instances of a particular shared worker to be started.


Shared workers are scoped by origin. Two different sites using the same names will not collide. However, if a page tries to use the same shared worker name as another page on the same site, but with a different script URL, it will fail.
Shared workers are scoped by origin. Two different sites using the same names will not collide. However, if a page tries to use the same shared worker name as another page on the same site, but with a different script URL, it will fail.


Creating shared workers is done using the SharedWorker() constructor. This constructor takes the URL to the script to use for its first argument, and the name of the worker, if any, as the second argument.
Creating shared workers is done using the SharedWorker() constructor. This constructor takes the URL to the script to use for its first argument, and the name of the worker, if any, as the second argument.


var worker = new SharedWorker('service.js');
var worker = new SharedWorker('service.js');
Communicating with shared workers is done with explicit MessagePort objects. The object returned by the SharedWorker() constructor holds a reference to the port on its port attribute.
Communicating with shared workers is done with explicit MessagePort objects. The object returned by the SharedWorker() constructor holds a reference to the port on its port attribute.


worker.port.onmessage = function (event) { ... };
worker.port.onmessage = function (event) { ... };
worker.port.postMessage('some message');
worker.port.postMessage('some message');
worker.port.postMessage({ foo: 'structured', bar: ['data', 'also', 'possible']});
worker.port.postMessage({ foo: 'structured', bar: ['data', 'also', 'possible']});
Inside the shared worker, new clients of the worker are announced using the connect event. The port for the new client is given by the event object's source attribute.
Inside the shared worker, new clients of the worker are announced using the connect event. The port for the new client is given by the event object's source attribute.


onconnect = function (event) {
onconnect = function (event) {
var newPort = event.source;
var newPort = event.source;
// set up a listener
// set up a listener
newPort.onmessage = function (event) { ... };
newPort.onmessage = function (event) { ... };
// send a message back to the port
// send a message back to the port
newPort.postMessage('ready!'); // can also send structured data, of course
newPort.postMessage('ready!'); // can also send structured data, of course
};
};
2 Conformance requirements
10.2 Infrastructure


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.
There are two kinds of workers; dedicated workers, and shared workers. Dedicated workers, once created, 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 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]
10.2.1 The global scope


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.
The global scope is the "inside" of a worker.


Some conformance requirements are phrased as requirements on attributes, methods or objects. Such requirements are to be interpreted as requirements on user agents.
10.2.1.1 The WorkerGlobalScope common interface


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.)
Spec bugs: 26319
[Exposed=Worker]
interface WorkerGlobalScope : EventTarget {
readonly attribute WorkerGlobalScope self;
readonly attribute WorkerLocation location;
readonly attribute WorkerNavigator navigator;
void importScripts(DOMString... urls);


The only conformance class defined by this specification is user agents.
void close();
attribute OnErrorEventHandler onerror;
attribute EventHandler onlanguagechange;
attribute EventHandler onoffline;
attribute EventHandler ononline;
};
A WorkerGlobalScope object has an associated url (null or a URL). It is initially null.


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.
A WorkerGlobalScope object has an associated HTTPS state ("modern", "deprecated", or "none"). It is initially "none".


When support for a feature is disabled (e.g. as an emergency measure to mitigate a security problem, or to aid in development, or for performance reasons), user agents must act as if they had no support for the feature whatsoever, and as if the feature was not mentioned in this specification. For example, if a particular feature is accessed via an attribute in a Web IDL interface, the attribute itself would be omitted from the objects that im
A WorkerGlobalScope object has an associated CSP list. It is initially an empty list.

The self attribute must return the WorkerGlobalScope object itself.

The location attribute must return the WorkerLocation object whose associated WorkerGlobalScope object is the WorkerGlobalScope object.

While the WorkerLocation object is created after the WorkerGlobalScope object, this is not problematic as it cannot be observed from script.

When a script invokes the close() method on a WorkerGlobalScope obj