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> <meta charset="utf-8"> <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 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 unoptimized search for a prime number. The postMessage()
method is used to send a
message back to the page when a prime is found.
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.
The main page is as follows:
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <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> <meta http-equiv="Refresh" content="120; URL=../"> </head> <body onload="search('')"> <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" autofocus 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.
All of our examples so far show workers that run classic
scripts. Workers can instead be instantiated using module
scripts, which have the usual benefits: the ability to use the JavaScript
import
statement to import other modules; strict mode by default; and
top-level declarations not polluting the worker's global scope.
Note that such module-based workers follow different restrictions regarding cross-origin
content, compared to classic workers. Unlike classic workers, module workers can be instantiated
using a cross-origin script, as long as that script is exposed using the CORS
protocol. Additionally, the importScripts()
method will automatically fail
inside module workers; the JavaScript import
statement is generally a
better choice.
In this example, the main document uses a worker to do off-main-thread image manipulation. It imports the filters used from another module.
The main page is as follows:
<!DOCTYPE html> <meta charset="utf-8"> <title>Worker example: image decoding</title> <label> Type an image URL to decode <input type="url" id="image-url" list="image-list"> <datalist id="image-list"> <option value="https://html.spec.whatwg.org/images/drawImage.png"> <option value="https://html.spec.whatwg.org/images/robots.jpeg"> <option value="https://html.spec.whatwg.org/images/arcTo2.png"> </datalist> </label> <label> Choose a filter to apply <select id="filter"> <option value="none">none</option> <option value="grayscale">grayscale</option> <option value="brighten">brighten by 20%</option> </select> </label> <script type="module"> const worker = new Worker("worker.js", { type: "module" }); worker.onmessage = receiveFromWorker; const url = document.querySelector("#image-url"); const filter = document.querySelector("#filter"); const output = document.querySelector("#output"); url.oninput = updateImage; filter.oninput = sendToWorker; let imageData, context; function updateImage() { const img = new Image(); img.src = url.value; img.onload = () => { output.innerHTML = ""; const canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; context = canvas.getContext("2d"); context.drawImage(img, 0, 0); imageData = context.getImageData(0, 0, canvas.width, canvas.height); sendToWorker(); output.appendChild(canvas); }; } function sendToWorker() { worker.postMessage({ imageData, filter: filter.value }); } function receiveFromWorker(e) { context.putImageData(e.data, 0, 0); } </script>
The worker file is then:
import * as filters from "./filters.js"; self.onmessage = e => { const { imageData, filter } = e.data; filters[filter](imageData); self.postMessage(imageData, [imageData.data.buffer]); };
Which imports the file filters.js
:
export function none() {} export function grayscale({ data: d }) { for (let i = 0; i < d.length; i += 4) { const [r, g, b] = [d[i], d[i + 1], d[i + 2]]; // CIE luminance for the RGB // The human eye is bad at seeing red and blue, so we de-emphasize them. d[i] = d[i + 1] = d[i + 2] = 0.2126 * r + 0.7152 * g + 0.0722 * b; } }; export function brighten({ data: d }) { for (let i = 0; i < d.length; ++i) { d[i] *= 1.2; } };
Support: sharedworkersChrome for Android NoneChrome 4+UC Browser for Android 11+iOS Safari NoneFirefox 29+IE NoneSamsung Internet 4+Opera Mini NoneAndroid Browser NoneSafari NoneEdge NoneOpera 10.6+
Source: caniuse.com
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 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:
<!DOCTYPE HTML> <meta charset="utf-8"> <title>Shared workers: demo 1</title> <pre id="log">Log:</pre> <script> var worker = new SharedWorker('test.js'); var log = document.getElementById('log'); worker.port.onmessage = function(e) { // note: not worker.onmessage! log.textContent += '\n' + e.data; } </script>
Here is the JavaScript worker:
onconnect = function(e) { var port = e.ports[0]; port.postMessage('Hello World!'); }
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:
<!DOCTYPE HTML> <meta charset="utf-8"> <title>Shared workers: demo 2</title> <pre id="log">Log:</pre> <script> var worker = new SharedWorker('test.js'); var log = document.getElementById('log'); worker.port.addEventListener('message', function(e) { log.textContent += '\n' + e.data; }, false); worker.port.start(); // note: need this when using addEventListener worker.port.postMessage('ping'); </script>
Here is the JavaScript worker:
onconnect = function(e) { var port = e.ports[0]; port.postMessage('Hello World!'); port.onmessage = function(e) { port.postMessage('pong'); // not e.ports[0].postMessage! // e.target.postMessage('pong'); would work also } }
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:
<!DOCTYPE HTML> <meta charset="utf-8"> <title>Shared workers: demo 3</title> <pre id="log">Log:</pre> <script> var worker = new SharedWorker('test.js'); var log = document.getElementById('log'); worker.port.addEventListener('message', function(e) { log.textContent += '\n' + e.data; }, false); worker.port.start(); worker.port.postMessage('ping'); </script> <iframe src="inner.html"></iframe>
Here is the inner HTML page:
<!DOCTYPE HTML> <meta charset="utf-8"> <title>Shared workers: demo 3 inner frame</title> <pre id=log>Inner log:</pre> <script> var worker = new SharedWorker('test.js'); var log = document.getElementById('log'); worker.port.onmessage = function(e) { log.textContent += '\n' + e.data; } </script>
Here is the JavaScript worker:
var count = 0; onconnect = function(e) { count += 1; var port = e.ports[0]; port.postMessage('Hello World! You are connection #' + count); port.onmessage = function(e) { port.postMessage('pong'); } }
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> <meta charset="utf-8"> <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> <meta charset="utf-8"> <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)[0]; // 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'; context.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)[0]; var message = event.data.substr(4 + name.length + 1); // display "<name> message" in public chat var public = document.getElementById('public'); var p = document.createElement('p'); var n = document.createElement('button'); n.textContent = '<' + name + '> '; n.onclick = function () { worker.port.postMessage('msg ' + name); }; p.appendChild(n); var m = document.createElement('span'); m.textContent = message; p.appendChild(m); public.appendChild(p); } 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)[0]; var port = event.ports[0]; // 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 div = document.createElement('div'); var addMessage = function(name, message) { var p = document.createElement('p'); var n = document.createElement('strong'); n.textContent = '<' + name + '> '; p.appendChild(n); var t = document.createElement('span'); t.textContent = message; p.appendChild(t); div.appendChild(p); }; port.onmessage = function (event) { addMessage(name, event.data); }; li.appendChild(div); 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); ul.appendChild(li); } worker.port.addEventListener('message', startPrivateChat, false); worker.port.start(); </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> <div id="public"></div> <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 wrap(val, min, max) { if (val < min) return val + (max-min)+1; if (val > max) return val - (max-min)-1; return val; } function sendMapData(viewer) { 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[wrap(y, 0, map[0].length-1)][wrap(x, 0, map.length-1)]; } } viewer.port.postMessage('map ' + data); } var viewers = {}; onconnect = function (event) { var name = getNextName(); event.ports[0]._data = { port: event.ports[0], name: name, x: 0, y: 0, }; viewers[name] = event.ports[0]._data; event.ports[0].postMessage('cfg ' + name); event.ports[0].onmessage = getMessage; sendMapData(event.ports[0]._data); }; 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._data); 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]); break; case 'txt ': var name = event.target._data.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.target._data; var party2 = viewers[event.data.substr(4).split(' ', 1)[0]]; 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> <meta charset="utf-8"> <title>Worker example: Multicore computation</title> </head> <body> <p>Result: <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.
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');
If you want your worker script to be interpreted as a module script instead of the default classic script, you need to use a slightly different signature:
var worker = new Worker('helper.js', { type: "module" });
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.
To receive messages from a dedicated worker, use the onmessage
event
handler IDL attribute on the Worker
object:
worker.onmessage = function (event) { ... };
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.
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({ operation: 'find-edges', input: buffer, // an ArrayBuffer object threshold: 0.6, }, [buffer]);
To receive a message inside the worker, the onmessage
event handler IDL attribute is used.
onmessage = function (event) { ... };
You can again also use the addEventListener()
method.
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.
postMessage(event.data.input, [event.data.input]); // transfer the buffer back
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 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.
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.
worker.port.onmessage = function (event) { ... }; worker.port.postMessage('some message'); 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.
onconnect = function (event) { var newPort = event.source; // set up a listener newPort.onmessage = function (event) { ... }; // send a message back to the port newPort.postMessage('ready!'); // can also send structured data, of course };
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 global scope is the "inside" of a worker.
WorkerGlobalScope
common interface[Exposed=Worker] interface WorkerGlobalScope : EventTarget { readonly attribute WorkerGlobalScope self; readonly attribute WorkerLocation location; readonly attribute WorkerNavigator navigator; void importScripts(USVString... urls); attribute OnErrorEventHandler onerror; attribute EventHandler onlanguagechange; attribute EventHandler onoffline; attribute EventHandler ononline; attribute EventHandler onrejectionhandled; attribute EventHandler onunhandledrejection; };
A WorkerGlobalScope
object has an associated type ("classic
" or "module
"). It is set during creation.
A WorkerGlobalScope
object has an associated url (null or a
URL). It is initially null.
A WorkerGlobalScope
object has an associated HTTPS state
(an HTTPS state value). It is initially "none
".
A WorkerGlobalScope
object has an associated referrer
policy (a referrer policy). It is initially the empty string.
A WorkerGlobalScope
object has an associated CSP list. It is
initially an empty list.
A WorkerGlobalScope
object has an associated module map.
It is a module map, initially empty.
self
location
WorkerLocation
object.navigator
WorkerNavigator
object.importScripts
(urls...)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.
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event handler IDL attributes,
by objects implementing the WorkerGlobalScope
interface:
Event handler | Event handler event type |
---|---|
onerror | error
|
onlanguagechange | languagechange
|
onoffline | offline
|
ononline | online
|
onrejectionhandled | rejectionhandled
|
onunhandledrejection | unhandledrejection
|
DedicatedWorkerGlobalScope
interface[Global=(Worker,DedicatedWorker),Exposed=DedicatedWorker] interface DedicatedWorkerGlobalScope : WorkerGlobalScope { void postMessage(any message, optional sequence<object> transfer = []); void close(); attribute EventHandler 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 retargeted at the
DedicatedWorkerGlobalScope
object.
postMessage
(message [,
transfer ])Worker
object associated with
dedicatedWorkerGlobal. transfer can be passed as a list of objects that are
to be transferred rather than cloned.close
()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.
To close a worker, given a workerGlobal, run these steps:
Discard any tasks that have been added to workerGlobal's event loop's task queues.
Set workerGlobal's closing flag to true. (This prevents any further tasks from being queued.)
The close()
method, when
invoked, must close a worker with this DedicatedWorkerGlobalScope
object.
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event
handler IDL attributes, by objects implementing the DedicatedWorkerGlobalScope
interface:
Event handler | Event handler event type |
---|---|
onmessage | message
|
For the purposes of the application cache networking model, a dedicated worker is an extension of the cache host from which it was created.
SharedWorkerGlobalScope
interface[Global=(Worker,SharedWorker),Exposed=SharedWorker] interface SharedWorkerGlobalScope : WorkerGlobalScope { readonly attribute DOMString name; void close(); attribute EventHandler onconnect; };
A SharedWorkerGlobalScope
object has an associated constructor origin, constructor url, and name. They are initialized when the
SharedWorkerGlobalScope
object is created, in the run a worker
algorithm.
Shared workers receive message ports through connect
events on their SharedWorkerGlobalScope
object for each
connection.
name
close
()The name
attribute must
return the SharedWorkerGlobalScope
object's name. Its value represents the name that can
be used to obtain a reference to the worker using the SharedWorker
constructor.
The close()
method, when
invoked, must close a worker with this SharedWorkerGlobalScope
object.
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event
handler IDL attributes, by objects implementing the SharedWorkerGlobalScope
interface:
Event handler | Event handler event type |
---|---|
onconnect | connect
|
Each WorkerGlobalScope
object has a distinct event loop, separate
from those used by units of related
similar-origin browsing contexts. This event loop has no associated
browsing context, and its task queues only have
events, callbacks, and networking activity as tasks. These event loops are created by the run a worker algorithm.
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 event
loop's task queues must discard any further tasks that would be added to them (tasks already on the queue are
unaffected except where otherwise specified). Effectively, once the closing flag is true, timers stop firing,
notifications for all pending background 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.
Each WorkerGlobalScope
also has a list of the worker's
workers. Initially this list is empty; it is populated when the worker creates or obtains
further workers.
Finally, each WorkerGlobalScope
also has a list of the
worker's Document
s. Initially this list is empty; it is populated when the
worker is created.
Whenever a Document
d is added to the worker's Document
s, the user agent must, for
each worker q in the list of the worker's workers whose list of
the worker's Document
s does not contain d, add d to q's WorkerGlobalScope
owner's list of the worker's
Document
s.
Whenever a Document
object is discarded,
it must be removed from the list of the worker's Document
s of each
worker whose list contains that Document
.
Given an environment settings object o when creating or obtaining a
worker, the list of relevant Document
objects to add depends on the type
of global object specified by o.
If o specifies a global object
that is a WorkerGlobalScope
object (i.e. if we are creating a nested worker), then
the relevant Document
s are the the worker's Document
s of
the global object specified by o.
Otherwise, o specifies a global
object that is a Window
object, and the relevant Document
is just
the responsible document specified by o.
A worker is said to be a permissible worker if its list of the worker's
Document
s is not empty, or if its list has been empty for no more than a short
user-agent-defined timeout value, its WorkerGlobalScope
is actually a
SharedWorkerGlobalScope
object (i.e. the worker is a shared worker), and the user
agent has a browsing context whose Document
is not completely
loaded.
The second part of this definition allows a shared worker to survive for a short time while a page is loading, in case that page is going to contact the shared worker again. This can be used by user agents as a way to avoid the cost of restarting a shared worker used by a site when the user is navigating from page to page within that site.
A worker is said to be an active needed worker if any of the Document
objects in the worker's Document
s are fully active.
A worker is said to be a protected worker if it is an active needed
worker and either it has outstanding timers, database transactions, or network connections,
or its list of the worker's ports is not empty, or its WorkerGlobalScope
is actually a SharedWorkerGlobalScope
object (i.e. the worker is a shared
worker).
A worker is said to be a suspendable worker if it is not an active needed worker but it is a permissible worker.
When a user agent is to run a worker for a script with
Worker
or SharedWorker
object worker, URL
url, environment settings object outside settings,
MessagePort
outside port, a WorkerOptions
dictionary
options, and an optional string name, it must run the following steps.
(name is always provided when worker is a SharedWorker
.)
Create a separate parallel execution environment (i.e. a separate thread or process or equivalent construct), and run the rest of these steps in that context.
For the purposes of timing APIs, this is the official moment of creation of the worker.
Let is shared be true if worker is a SharedWorker
object, and false otherwise.
Let docs be the list of relevant Document
objects to
add given outside settings.
Let parent worker global scope be null.
If outside settings's global
object is a WorkerGlobalScope
object (i.e. we are creating a nested worker),
set parent worker global scope to outside settings's global object.
Call the JavaScript InitializeHostDefinedRealm() abstract operation with the following customizations:
For the global object, if is shared is true, create a new
SharedWorkerGlobalScope
object. Otherwise, create a new
DedicatedWorkerGlobalScope
object. Let worker global scope be the
created object.
Let realm execution context be the created JavaScript execution context.
Set up a worker environment settings object with realm execution context and outside settings, and let inside settings be the result.
If is shared is true, then:
Set worker global scope's constructor origin to outside settings's origin.
Set worker global scope's constructor url to url.
Set worker global scope's name to name.
Let destination be "sharedworker
" if is
shared is true, and "worker
" otherwise.
Obtain script by switching on the value of options's type
member:
classic
"module
"credentials
member of options, and inside settings.In both cases, to perform the fetch given request, perform the following steps if the is top-level flag is set:
Fetch request, and asynchronously wait to run the remaining steps as part of fetch's process response for the response response.
Set worker global scope's HTTPS state to response's HTTPS state.
Set worker global scope's referrer policy to the result of
parsing the `Referrer-Policy
`
header of response.
Execute the Initialize a global object
's CSP list
algorithm on worker global scope and response. [CSP]
Asynchronously complete the perform the fetch steps with response.
If the algorithm asynchronously completes with null, queue a task to fire an event named error
at worker, and abort these steps. Otherwise, continue the rest of these steps after
the algorithm's asynchronous completion, with script being the asynchronous
completion value.
Associate worker with worker global scope.
Create a new MessagePort
object whose owner is inside settings. Let inside
port be this new object.
Associate inside port with worker global scope.
Entangle outside port and inside port.
Add to worker global
scope's list of the worker's Document
s the
Document
objects in docs.
If parent worker global scope is not null, add worker global scope to the list of the worker's workers of parent worker global scope.
Set worker global scope's type to the value of the type
member of options.
Create a new WorkerLocation
object and associate it with worker global
scope.
Closing orphan workers: Start monitoring the worker such that no sooner than it stops being a protected worker, and no later than it stops being a permissible worker, worker global scope's closing flag is set to true.
Suspending workers: Start monitoring the worker, such that whenever 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.
Set inside settings's execution ready flag.
If script is a classic script, then run the classic script script. Otherwise, it is a module script; run the module script script.
In addition to the usual possibilities of returning a value or failing due to an exception, this could be prematurely aborted by the "kill a worker" or "terminate a worker" algorithms defined below.
Enable outside port's port message queue.
If is shared is false, enable the port message queue of the worker's implicit port.
If is shared is true, then queue a task, using the DOM
manipulation task source, to fire an event named
connect
at worker global scope,
using MessageEvent
, with the data
attribute initialized to the empty string, the ports
attribute initialized to a new frozen array containing inside port, and
the source
attribute initialized to inside
port.
Enable the client message queue of the
ServiceWorkerContainer
object whose associated service worker client is
worker global scope's relevant settings object.
Event loop: Run the responsible event loop specified by inside settings until it is destroyed.
The handling of events or the execution of callbacks by tasks run by the event loop might get prematurely aborted by the "kill a worker" or "terminate a worker" algorithms defined below.
The worker processing model remains on this step until the event loop is destroyed, which happens after the closing flag is set to true, as described in the event loop processing model.
Empty the worker global scope's list of active timers.
Disentangle all the ports in the list of the worker's ports.
Empty the worker's list of
the worker's Document
s.
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):
Set the worker's WorkerGlobalScope
object's closing flag to true.
If there are any tasks queued in the
WorkerGlobalScope
object's event loop's task
queues, discard them without processing them.
Wait a user-agent-defined amount of time.
Abort the script currently running in the worker.
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 tasks queued in the
WorkerGlobalScope
object's event loop's task
queues, discard them without processing them.
Abort the script currently running in the worker.
If the worker's WorkerGlobalScope
object is actually a
DedicatedWorkerGlobalScope
object (i.e. the worker is a dedicated worker), then
empty the port message queue of the port that the worker's implicit port is
entangled with.
The task source for the tasks mentioned above is the DOM manipulation task source.
Whenever an uncaught runtime script error occurs in one of the worker's scripts, if the error
did not occur while handling a previous script error, the user agent must report the
error for that script, with the position (line number
and column number) where the error occurred, using the WorkerGlobalScope
object as
the target.
For shared workers, if the error is still not handled afterwards, the error may be reported to a developer console.
For dedicated workers, if the error is still not handled afterwards, the user agent must queue a task to run these steps:
Let notHandled be the result of firing an
event named error
at Worker
object
associated with the worker, using ErrorEvent
, with the cancelable
attribute initialized to true, the message
, filename
, lineno
, and colno
attributes initialized appropriately, and the error
attribute initialized to null.
If notHandled is true, then the user agent must act as if the uncaught runtime
script error had occurred in the global scope that the Worker
object is in, thus
repeating the entire runtime script error reporting process one level up.
If the implicit port connecting the worker to its Worker
object has been
disentangled (i.e. if the parent worker has been terminated), then the user agent must act as if
the Worker
object had no error
event handler and as
if that worker's onerror
attribute was
null, but must otherwise act as described above.
Thus, error reports propagate up to the chain of dedicated workers up to the
original Document
, even if some of the workers along this chain have been terminated
and garbage collected.
The task source for the task mentioned above is the DOM manipulation task source.
AbstractWorker
abstract interface[NoInterfaceObject, Exposed=(Window,Worker)] interface AbstractWorker { attribute EventHandler onerror; };
The following are the event handlers (and their
corresponding event handler
event types) that must be supported, as event handler IDL attributes, by
objects implementing the AbstractWorker
interface:
Event handler | Event handler event type |
---|---|
onerror | error
|
When the user agent is required to set up a worker environment settings object, given a JavaScript execution context execution context and environment settings object outside settings, it must run the following steps:
Let inherited responsible browsing context be outside settings's responsible browsing context.
Let inherited origin be outside settings's origin.
Let worker event loop be a newly created event loop.
Let realm be the value of execution context's Realm component.
Let worker global scope be realm's global object.
Let settings object be a new environment settings object whose algorithms are defined as follows:
Return execution context.
Return worker global scope's module map.
Return inherited responsible browsing context.
Return worker event loop.
Not applicable (the responsible event loop is not a browsing context event loop).
Return UTF-8.
Return worker global scope's url.
Return a unique opaque origin if worker
global scope's url's scheme is "data
", and inherited
origin otherwise.
Return worker global scope's HTTPS state.
Return worker global scope's referrer policy.
Set settings object's id to a new unique opaque string, settings object's creation URL to worker global scope's url, settings object's target browsing context to null, and settings object's active service worker to null.
Set realm's [[HostDefined]] field to settings object.
Return settings object.
Worker
interface[Constructor(USVString scriptURL, optional WorkerOptions options), Exposed=(Window,Worker)] interface Worker : EventTarget { void terminate(); void postMessage(any message, optional sequence<object> transfer = []); attribute EventHandler onmessage; }; dictionary WorkerOptions { WorkerType type = "classic"; RequestCredentials credentials = "omit"; // credentials is only used if type is "module" }; enum WorkerType { "classic", "module" }; Worker implements AbstractWorker;
Worker
(scriptURL [, options ])Worker
object. scriptURL will be fetched and executed
in the background, creating a new global environment for which worker represents the
communication channel. options can be used to ensure this new global environment
supports JavaScript modules (specify type: "module"
) and if that is
specified, can also be used to specify how scriptURL is fetched through the credentials
option.terminate
()postMessage
(message [, transfer ])
The terminate()
method, when invoked, must
cause the "terminate a worker" algorithm to be run on the worker with which 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 retargeted 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 postMessage()
method's first argument can be structured data:
worker.postMessage({opcode: 'activate', device: 1938, parameters: [23, 102]});
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event handler IDL attributes,
by objects implementing the Worker
interface:
Event handler | Event handler event type |
---|---|
onmessage | message
|
When the Worker(scriptURL,
options)
constructor is invoked, the user agent must run the following
steps:
The user agent may throw a "SecurityError
"
DOMException
and abort these steps if the request violates a policy decision (e.g.
if the user agent is configured to not allow the page to start dedicated workers).
Let outside settings be the current settings object.
Parse the scriptURL argument relative to outside settings.
If this fails, throw a "SyntaxError
" DOMException
and abort these steps.
Let worker URL be the resulting URL record.
Any same-origin URL (including blob:
URLs) can be used. data:
URLs can also be used, but they create a worker with an opaque origin.
Let worker be a new Worker
object.
Create a new MessagePort
object whose owner is outside settings. Let this be the
outside port.
Associate the outside port with worker.
Let docs be the list of relevant Document
objects to
add given outside settings.
Return worker, and run the following step in parallel.
Run a worker given worker, worker URL, outside settings, outside port, and options.
SharedWorker
interface[Constructor(USVString scriptURL, optional DOMString name = "", optional WorkerOptions options), Exposed=(Window,Worker)] interface SharedWorker : EventTarget { readonly attribute MessagePort port; }; SharedWorker implements AbstractWorker;
SharedWorker
(scriptURL [, name [, options ]])SharedWorker
object. scriptURL will be fetched and
executed in the background, creating a new global environment for which sharedWorker
represents the communication channel. name can be used to define the name of that global environment.
options can be used to ensure this new global environment
supports JavaScript modules (specify type: "module"
) and if that is
specified, can also be used to specify how scriptURL is fetched through the credentials
option.port
MessagePort
object which can be used to
communicate with the global environment.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, options)
constructor is invoked, the user agent must run
the following steps:
The user agent may throw a "SecurityError
"
DOMException
and abort these steps if the request violates a policy decision (e.g.
if the user agent is configured to not allow the page to start shared workers).
Let outside settings be the current settings object.
Parse scriptURL relative to outside settings.
If this fails, throw a "SyntaxError
" DOMException
and abort these steps.
Otherwise, let urlRecord be the resulting URL record.
Any same-origin URL (including blob:
URLs) can be used. data:
URLs can also be used, but they create a worker with an opaque origin.
Let worker be a new SharedWorker
object.
Create a new MessagePort
object whose owner is outside settings. Let this be the
outside port.
Assign outside port to the port
attribute of worker.
Let isSecureContext be the result of executing Is environment settings object a secure context? on outside settings.
Run these substeps in parallel:
Let worker global scope be null.
If there exists a SharedWorkerGlobalScope
object whose closing flag is false, constructor origin is
same origin with outside settings's origin, constructor url equals urlRecord, and name is name, then set
worker global scope to that SharedWorkerGlobalScope
object.
data:
URLs create a worker with an opaque origin. Both the constructor origin and
constructor url are
compared so the same data:
URL can be used within an
origin to get to the same SharedWorkerGlobalScope
object, but cannot
be used to bypass the same origin restriction.
If worker global scope is not null, but the user agent has been configured to disallow communication between the worker represented by the worker global scope and the scripts whose settings object is outside settings, then set worker global scope to null.
For example, a user agent could have a development mode that isolates a particular top-level browsing context from all other pages, and scripts in that development mode could be blocked from connecting to shared workers running in the normal browser mode.
If worker global scope is not null, then run these subsubsteps:
Let settings object be the relevant settings object for worker global scope.
If the result of executing Is environment settings object a secure
context? on settings object is not isSecureContext, then
queue a task to fire an event named
error
at worker and abort these subsubsteps.
[SECURE-CONTEXTS]
Associate worker with worker global scope.
Create a new MessagePort
object whose owner is settings object. Let this be
the inside port.
Entangle outside port and inside port.
Queue a task, using the DOM manipulation task source, to
fire an event named connect
at worker global scope,
using MessageEvent
, with the data
attribute initialized to the empty string, the ports
attribute initialized to a new frozen
array containing only inside port, and the source
attribute initialized to inside
port.
Add to worker global
scope's list of the worker's Document
s the
list of relevant Document
objects to add given outside
settings.
If outside settings's global
object is a WorkerGlobalScope
object, add worker global scope
to the list of the worker's workers of outside settings's global object.
Otherwise, run a worker given worker, urlRecord, outside settings, outside port, options, and name.
Return worker.
[NoInterfaceObject, Exposed=(Window,Worker)] interface NavigatorConcurrentHardware { readonly attribute unsigned long long hardwareConcurrency; };
navigator
. hardwareConcurrency
The navigator.hardwareConcurrency
attribute's
getter must return a number between 1 and the number of logical processors potentially available
to the user agent. If this cannot be determined, the getter must return 1.
User agents should err toward exposing the number of logical processors available, using lower values only in cases where there are user-agent specific limits in place (such as a limitation on the number of workers that can be created) or when the user agent desires to limit fingerprinting possibilities.
When a script invokes the importScripts(urls)
method on
a WorkerGlobalScope
object, the user agent must import scripts into worker
global scope given this WorkerGlobalScope
object and urls.
To import scripts into worker global scope, given a
WorkerGlobalScope
object worker global scope and a sequence<DOMString>
urls, run these steps. The algorithm may
optionally be customized by supplying custom perform
the fetch hooks, which if provided will be used when invoking fetch a classic
worker-imported script.
If worker global scope's type is "module
", throw a
TypeError
exception and abort these steps.
Let settings object be the current settings object.
If urls is empty, abort these steps.
Parse each value in urls relative to
settings object. If any fail, throw a "SyntaxError
"
DOMException
and abort these steps.
For each url in the resulting URL records, run these substeps:
Fetch a classic worker-imported script given url and settings object, passing along any custom perform the fetch steps provided. If this succeeds, let script be the result. Otherwise, rethrow the exception.
Run the classic script script, passing the rethrow errors flag.
script will run 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 an exception was thrown or if the script was prematurely aborted, then abort all these steps, letting the exception or aborting continue to be processed by the calling script.
Service Workers is an example of a specification that runs this algorithm with its own options for the perform the fetch hook. [SW]
WorkerNavigator
objectThe navigator
attribute
of the WorkerGlobalScope
interface must return an instance of
the WorkerNavigator
interface, which represents the
identity and state of the user agent (the client):
[Exposed=Worker] interface WorkerNavigator {}; WorkerNavigator implements NavigatorID; WorkerNavigator implements NavigatorLanguage; WorkerNavigator implements NavigatorOnLine; WorkerNavigator implements NavigatorConcurrentHardware;
[Exposed=Worker] interface WorkerLocation { stringifier readonly attribute USVString href; readonly attribute USVString origin; readonly attribute USVString protocol; readonly attribute USVString host; readonly attribute USVString hostname; readonly attribute USVString port; readonly attribute USVString pathname; readonly attribute USVString search; readonly attribute USVString hash; };
A WorkerLocation
object has an associated WorkerGlobalScope
object (a
WorkerGlobalScope
object).
The href
attribute's getter must
return the associated WorkerGlobalScope
object's
url, serialized.
The origin
attribute's getter must
return the Unicode serialization of the
associated WorkerGlobalScope
object's url's origin.
It returns the Unicode rather than the ASCII serialization for
compatibility with MessageEvent
.
The protocol
attribute's getter
must run return the associated WorkerGlobalScope
object's
url's scheme, followed by ":
".
The host
attribute's getter must run
these steps:
Let url be the associated WorkerGlobalScope
object's
url.
If url's host is null, return the empty string.
If url's port is null, return url's host, serialized.
Return url's host, serialized, followed by ":
" and url's port, serialized.
The hostname
attribute's getter
must run these steps:
Let host be the associated WorkerGlobalScope
object's
url's host.
If host is null, return the empty string.
Return host, serialized.
The port
attribute's getter must run
these steps:
Let port be the associated WorkerGlobalScope
object's
url's port.
If port is null, return the empty string.
Return port, serialized.
The pathname
attribute's getter
must run these steps:
Let url be the associated WorkerGlobalScope
object's
url.
If url's cannot-be-a-base-URL flag is set, return the first string in url's path.
Return "/
", followed by the strings in url's path (including empty strings), separated from each other by
"/
".
The search
attribute's getter must
run these steps:
Let query be the associated WorkerGlobalScope
object's
url's query.
If query is either null or the empty string, return the empty string.
Return "?
", followed by query.
The hash
attribute's getter must run
these steps:
Let fragment be the associated WorkerGlobalScope
object's
url's fragment.
If fragment is either null or the empty string, return the empty string.
Return "#
", followed by fragment.