1. 10 Web 工作线程
    1. 10.1 概述
      1. 10.1.1 涵盖范围
      2. 10.1.2 示例
        1. 10.1.2.1 数字密集型计算的后台 worker
        2. 10.1.2.2 使用 JavaScript 模块作为 worker
        3. 10.1.2.3 共享 worker 概述
        4. 10.1.2.4 通过共享 Worker 来共享状态
        5. 10.1.2.5 委托
        6. 10.1.2.6 Providing libraries
      3. 10.1.3 教程
        1. 10.1.3.1 创建专用工作线程
        2. 10.1.3.2 与专用 Worker 通信
        3. 10.1.3.3 共享工作线程
    2. 10.2 基础设施
      1. 10.2.1 全局作用域
        1. 10.2.1.1 WorkerGlobalScope 通用接口
        2. 10.2.1.2 专用 Worker 与 DedicatedWorkerGlobalScope 接口
        3. 10.2.1.3 共享 worker 与 SharedWorkerGlobalScope 接口
      2. 10.2.2 事件循环
      3. 10.2.3 The worker's lifetime
      4. 10.2.4 Processing model
      5. 10.2.5 Runtime script errors
      6. 10.2.6 创建 Worker
        1. 10.2.6.1 The AbstractWorker mixin
        2. 10.2.6.2 Script settings for workers
        3. 10.2.6.3 Dedicated workers and the Worker interface
        4. 10.2.6.4 Shared workers and the SharedWorker interface
      7. 10.2.7 Concurrent hardware capabilities
    3. 10.3 Worker 中可用的 API
      1. 10.3.1 Importing scripts and libraries
      2. 10.3.2 The WorkerNavigator interface
      3. 10.3.3 The WorkerLocation interface

10 Web 工作线程

Web_Workers_API

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome Android18+WebView Android4+Samsung Internet1.0+Opera Android11+

Web_Workers_API/Using_web_workers

10.1 概述

10.1.1 涵盖范围

This section is non-normative.

本标准定义了独立于任何 UI 脚本在后台运行脚本的 API。

这使得长时间运行的脚本成为可能,它们不会因响应用户点击或其他交互而中断。 也使得耗时任务成为可能,它们不必为了保持页面可响应而立即返回。

Workers (这里指这些后台脚本)相对重量级,不适合大量使用。 例如,为400万像素图片的每一像素启动一个 Worker 就可能不太合适。 下面的例子演示了一些合适的 workers 使用方式。

通常 workers 应该有较长的生命期,较高的启动性能消耗,而且每个实例都会产生较高的内存消耗。

10.1.2 示例

This section is non-normative.

Worker 有着广泛的用途,下面的几节展示了其中的一些。

10.1.2.1 数字密集型计算的后台 worker

This section is non-normative.

workers 最简单的使用方式是在不打断 UI 的情况下执行计算密集型的任务。

本例子中,主文档启动(spawns )了一个 worker 来(naïvely)计算质数, 然后逐渐地显示新发现的质数。

主页面如下:

<!DOCTYPE HTML>
<html lang="en">
 <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>

Worker() 构造函数的调用创建了一个 worker, 并返回一个表示该 worker 的 Worker 对象,该对象用来与 worker 进行通信。 该对象的 onmessage 事件处理函数允许 从 worker 接受消息。

worker 本身如下:

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);
}

这些代码就是未经优化的寻找质数算法。找到质数时使用 postMessage() 方法来 向页面发送消息。

在线浏览该示例

10.1.2.2 使用 JavaScript 模块作为 worker

This section is non-normative.

目前我们所有例子的 workers 都在运行 经典脚本 。Workers 也可以用 模块脚本 实例化, 通常有如下好处:使用 JavaScript import 声明来引入其他模块的能力; 默认的严格模式;顶层声明不会污染 worker 的全局作用域。

注意相比于经典脚本,基于模块的 workers 遵循不同的跨域内容限制, 不同于经典 workers,模块 workers 可以使用跨域脚本实例化,只要使用 CORS 协议把该脚本暴露出来。 另外,在模块 worker 中 importScripts() 方法将自动失效;JavaScript import 声明通常是更好的选择。

本例子中,主文档使用了一个 worker 来做主线程外的图像操作。 它引入了另一个模块中的过滤器。

主页面如下:

<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<title>Worker example: image decoding</title>

<p>
  <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>
</p>

<p>
  <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>
</p>

<canvas id="output"></canvas>

<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>

worker 文件如下:

import * as filters from "./filters.js";

self.onmessage = e => {
  const { imageData, filter } = e.data;
  filters[filter](imageData);
  self.postMessage(imageData, [imageData.data.buffer]);
};

它引入了文件 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;
  }
};

在线浏览该示例

10.1.2.3 共享 worker 概述

SharedWorker

Firefox29+Safari5–6.1Chrome4+
Opera10.6+Edge79+
Edge (Legacy)NoInternet ExplorerNo
Firefox Android33+Safari iOS5.1–7Chrome AndroidNoWebView AndroidNoSamsung Internet4.0–5.0Opera Android11–14

This section is non-normative.

这一节通过一个 Hello World 例子来介绍共享 Worker。由于每个 Worker 可以有多个连接, 共享 Worker 的 API 略有不同。

第一个例子展示了如何连接到 worker 以及 worker 如何向连接的页面发回消息。 收到的消息显示在日志中。

这是 HTML 页面:

<!DOCTYPE HTML>
<html lang="en">
<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>

这是 JavaScript worker:

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

在线查看本示例


第二个例子在两方面扩展了上一个例子:首先使用 addEventListener() 来接收事件,取代了 事件处理函数 IDL 属性。 其次,向 Worder 发送 一个事件使得 Worker 以另一个事件回复。 收到的消息仍然显示在日志中。

这是 HTML 页面:

<!DOCTYPE HTML>
<html lang="en">
<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>

这是 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
  }
}

在线查看本示例


最后这个例子展示两个页面如何连接到同一个 Worker; 这里第二个页面仅仅是第一个页面中的 iframe,但同样的原则对 一个 顶级浏览环境 中的完全独立的页面仍然适用。

这是外部 HTML 页面:

<!DOCTYPE HTML>
<html lang="en">
<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>

这是内部 HTML 页面:

<!DOCTYPE HTML>
<html lang="en">
<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>

这是 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');
  }
}

在线查看本示例

10.1.2.4 通过共享 Worker 来共享状态

This section is non-normative.

本例子中,可以同时打开多个窗口(查看器)来浏览同一个地图。 在一个 Worker 的协调下,所有窗口共享同样的地图信息。 每个查看器都可以独立地随意移动,一旦在地图上设置了任何数据,其他查看器也都会更新。

主页面很普通,只是提供了打开查看器的入口:

<!DOCTYPE HTML>
<html lang="en">
 <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>

查看器就有些意思了:

<!DOCTYPE HTML>
<html lang="en">
 <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>

关于查看器的写法有几个值得一提的关键点。

多监听器。 上述代码加载了多个事件监听器,每个都会检查该消息是否和它相关。 本例子中没太大差别,但如果多个作者都希望使用同一接口与 Worker 通信, 该设计将产生较为独立的代码,否则所有的变更都得在同一事件处理函数中进行。

以这种方式注册事件处理函数也让你可以在处理完后注销特定的监听器, 就像本例子中的 configure() 方法一样。

这是最终的 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;
  }
}

连接到多个页面。 脚本中使用 onconnect 时间监听器来监听多个连接。

直接通道。 当 Worker 从查看器收到一条带有另一个查看器名称的"msg"消息时, 它会在这两者之间建立一个直接的连接,使得这两个查看器可以不经由 Worker 代理而直接通信。

在线查看该示例

10.1.2.5 委托

This section is non-normative.

随着多核 CPU 的流行,可以将计算密集型任务分割到多个 Worder 中来得到更好的性能。 在本示例中,一个对1到10,000,000的所有数字进行操作的计算密集型的任务移交给了10个子 worker。

主页面如下,它只是产出结果报告:

<!DOCTYPE HTML>
<html lang="en">
 <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>

Worker 本身如下:

// 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!
}

它包含了一个循环来启动子 Worker,然后定义另一个等待所有子 Worker 响应的处理函数。

子 Worker 的实现如下:

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

它们在两个事件中接收到两个数字,在这两个数字指定的范围上执行计算,然后向父 Worker 报告结果。

在线查看该示例

10.1.2.6 Providing libraries

This section is non-normative.

Suppose that a cryptography library is made available that provides three tasks:

Generate a public/private key pair
Takes a port, on which it will send two messages, first the public key and then the private key.
Given a plaintext and a public key, return the corresponding ciphertext
Takes a port, to which any number of messages can be sent, the first giving the public key, and the remainder giving the plaintext, each of which is encrypted and then sent on that same channel as the ciphertext. The user can close the port when it is done encrypting content.
Given a ciphertext and a private key, return the corresponding plaintext
Takes a port, to which any number of messages can be sent, the first giving the private key, and the remainder giving the ciphertext, each of which is decrypted and then sent on that same channel as the plaintext. The user can close the port when it is done decrypting content.

The library itself is as follows:

function handleMessage(e) {
  if (e.data == "genkeys")
    genkeys(e.ports[0]);
  else if (e.data == "encrypt")
    encrypt(e.ports[0]);
  else if (e.data == "decrypt")
    decrypt(e.ports[0]);
}

function genkeys(p) {
  var keys = _generateKeyPair();
  p.postMessage(keys[0]);
  p.postMessage(keys[1]);
}

function encrypt(p) {
  var key, state = 0;
  p.onmessage = function (e) {
    if (state == 0) {
      key = e.data;
      state = 1;
    } else {
      p.postMessage(_encrypt(key, e.data));
    }
  };
}

function decrypt(p) {
  var key, state = 0;
  p.onmessage = function (e) {
    if (state == 0) {
      key = e.data;
      state = 1;
    } else {
      p.postMessage(_decrypt(key, e.data));
    }
  };
}

// support being used as a shared worker as well as a dedicated worker
if ('onmessage' in this) // dedicated worker
  onmessage = handleMessage;
else // shared worker
  onconnect = function (e) { e.port.onmessage = handleMessage; }


// the "crypto" functions:

function _generateKeyPair() {
  return [Math.random(), Math.random()];
}

function _encrypt(k, s) {
  return 'encrypted-' + k + ' ' + s;
}

function _decrypt(k, s) {
  return s.substr(s.indexOf(' ')+1);
}

Note that the crypto functions here are just stubs and don't do real cryptography.

This library could be used as follows:

<!DOCTYPE HTML>
<html lang="en">
 <head>
  <meta charset="utf-8">
  <title>Worker example: Crypto library</title>
  <script>
   const cryptoLib = new Worker('libcrypto-v1.js'); // or could use 'libcrypto-v2.js'
   function startConversation(source, message) {
     const messageChannel = new MessageChannel();
     source.postMessage(message, [messageChannel.port2]);
     return messageChannel.port1;
   }
   function getKeys() {
     let state = 0;
     startConversation(cryptoLib, "genkeys").onmessage = function (e) {
       if (state === 0)
         document.getElementById('public').value = e.data;
       else if (state === 1)
         document.getElementById('private').value = e.data;
       state += 1;
     };
   }
   function enc() {
     const port = startConversation(cryptoLib, "encrypt");
     port.postMessage(document.getElementById('public').value);
     port.postMessage(document.getElementById('input').value);
     port.onmessage = function (e) {
       document.getElementById('input').value = e.data;
       port.close();
     };
   }
   function dec() {
     const port = startConversation(cryptoLib, "decrypt");
     port.postMessage(document.getElementById('private').value);
     port.postMessage(document.getElementById('input').value);
     port.onmessage = function (e) {
       document.getElementById('input').value = e.data;
       port.close();
     };
   }
  </script>
  <style>
   textarea { display: block; }
  </style>
 </head>
 <body onload="getKeys()">
  <fieldset>
   <legend>Keys</legend>
   <p><label>Public Key: <textarea id="public"></textarea></label></p>
   <p><label>Private Key: <textarea id="private"></textarea></label></p>
  </fieldset>
  <p><label>Input: <textarea id="input"></textarea></label></p>
  <p><button onclick="enc()">Encrypt</button> <button onclick="dec()">Decrypt</button></p>
 </body>
</html>

A later version of the API, though, might want to offload all the crypto work onto subworkers. This could be done as follows:

function handleMessage(e) {
  if (e.data == "genkeys")
    genkeys(e.ports[0]);
  else if (e.data == "encrypt")
    encrypt(e.ports[0]);
  else if (e.data == "decrypt")
    decrypt(e.ports[0]);
}

function genkeys(p) {
  var generator = new Worker('libcrypto-v2-generator.js');
  generator.postMessage('', [p]);
}

function encrypt(p) {
  p.onmessage = function (e) {
    var key = e.data;
    var encryptor = new Worker('libcrypto-v2-encryptor.js');
    encryptor.postMessage(key, [p]);
  };
}

function encrypt(p) {
  p.onmessage = function (e) {
    var key = e.data;
    var decryptor = new Worker('libcrypto-v2-decryptor.js');
    decryptor.postMessage(key, [p]);
  };
}

// support being used as a shared worker as well as a dedicated worker
if ('onmessage' in this) // dedicated worker
  onmessage = handleMessage;
else // shared worker
  onconnect = function (e) { e.ports[0].onmessage = handleMessage };

The little subworkers would then be as follows.

For generating key pairs:

onmessage = function (e) {
  var k = _generateKeyPair();
  e.ports[0].postMessage(k[0]);
  e.ports[0].postMessage(k[1]);
  close();
}

function _generateKeyPair() {
  return [Math.random(), Math.random()];
}

For encrypting:

onmessage = function (e) {
  var key = e.data;
  e.ports[0].onmessage = function (e) {
    var s = e.data;
    postMessage(_encrypt(key, s));
  }
}

function _encrypt(k, s) {
  return 'encrypted-' + k + ' ' + s;
}

For decrypting:

onmessage = function (e) {
  var key = e.data;
  e.ports[0].onmessage = function (e) {
    var s = e.data;
    postMessage(_decrypt(key, s));
  }
}

function _decrypt(k, s) {
  return s.substr(s.indexOf(' ')+1);
}

Notice how the users of the API don't have to even know that this is happening — the API hasn't changed; the library can delegate to subworkers without changing its API, even though it is accepting data using message channels.

View this example online.

10.1.3 教程

10.1.3.1 创建专用工作线程

This section is non-normative.

创建专用 Worker 需要一个指向 JavaScript 文件的 URL。使用该 URL 作为唯一一个参数来调用Worker() 构造函数,这时会创建并返回一个 Worker:

var worker = new Worker('helper.js');

为了使你的 Worker 脚本被解析为 模块脚本 而不是 经典脚本,你需要使用一个略微不同的函数签名:

var worker = new Worker('helper.js', { type: "module" });
10.1.3.2 与专用 Worker 通信

This section is non-normative.

专用 Worker 其实是 MessagePort 对象,因此支持MessagePort的所有特性。 比如发送结构化数据、传输二进制数据、以及传输其他 Port。

使用 Worker 对象上的 onmessage 事件处理器 IDL 属性 来接收专用 Worker 的消息:

worker.onmessage = function (event) { ... };

你也可以使用 addEventListener() 方法。

在创建专用 Worker 时,它隐式使用的 MessagePortport message queue 会被默认开启。所以没有Worker 接口上没有等价于 MessagePort 接口的 start() 的方法。

使用 postMessage() 方法来 向 Worker 发送 数据。该通信通道可以发送结构化数据, 如果要高效地发送 ArrayBuffer 对象 (通过直接传输它们而不是克隆后发送),在第二个参数上提供它们的列表。

worker.postMessage({
  operation: 'find-edges',
  input: buffer, // an ArrayBuffer object
  threshold: 0.6,
}, [buffer]);

使用 onmessage 事件处理器 IDL 属性在 Worker 中接受消息。

onmessage = function (event) { ... };

你也可以使用 addEventListener() 方法。

上述两种方式中,数据总是在事件对象的 data 属性中提供。

使用 postMessage() 来回复消息,它同样支持结构化数据。

postMessage(event.data.input, [event.data.input]); // transfer the buffer back
10.1.3.3 共享工作线程

This section is non-normative.

共享 worker 是通过创建时的脚本 URL 来识别的。也可以显式地指定名字, 这会使得共享 Worker 可以启动多个实例。

共享 worker 作用域限制在 origin 下。使用同一 Worker 名字的两个站点不会冲突。 然而同一站点中,尝试使用相同的 Worker 名来引用不同的脚本 URL 将会失败。

使用 SharedWorker() 构造函数来创建共享 Worker。 该构造函数使用脚本 URL 作为第一个参数, Worker 名(如果有的话)作为第二个参数。

var worker = new SharedWorker('service.js');

与共享 worker 通信需要显式地使用 MessagePort 对象。 SharedWorker() 构造函数返回的对象的 port属性保有一个对 port 的引用。

worker.port.onmessage = function (event) { ... };
worker.port.postMessage('some message');
worker.port.postMessage({ foo: 'structured', bar: ['data', 'also', 'possible']});

在共享 worker 内,新的客户会使用 connect 事件 来声明,新来的客户的 port 由事件对象的 source 属性给出。

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
};

10.2 基础设施

Worker 共有两种:专用 Worker 和共享 Worker。 前者一经创建就会链接到其创建者,但在专用 Worker 中可以使用消息端口与其他浏览环境或 Worker 进行通信。 然而共享 Worker 是有名字的,一经创建任何同的脚本都可以获取该 Worker 的引用并与之通信。

10.2.1 全局作用域

全局作用域是即 worker 的内部。

10.2.1.1 WorkerGlobalScope 通用接口
[Exposed=Worker] 
interface WorkerGlobalScope : EventTarget {
  readonly attribute WorkerGlobalScope self;
  readonly attribute WorkerLocation location;
  readonly attribute WorkerNavigator navigator;
  undefined importScripts(USVString... urls);

  attribute OnErrorEventHandler onerror;
  attribute EventHandler onlanguagechange;
  attribute EventHandler onoffline;
  attribute EventHandler ononline;
  attribute EventHandler onrejectionhandled;
  attribute EventHandler onunhandledrejection;
};

WorkerGlobalScope 对象有一个与之关联的 owner setDocumentWorkerGlobalScope 对象的 集合)。 初始为空,当创建或获取 Worker 时填充。

拥有者不止一个,它是一个 集合 来适应 SharedWorkerGlobalScope 对象。

WorkerGlobalScope 对象有一个与之关联的 worker setWorkerGlobalScope 对象的 集合)。 它初始为空,当创建或获取其他 Worker 时填充。

WorkerGlobalScope 对象有一个与之关联的 type ("classic" 或 "module")。在创建时设置。

WorkerGlobalScope 对象有一个与之关联的 url (null 或一个 URL)。初始值为 null。

WorkerGlobalScope 对象有一个与之关联的 名称(一个字符串)。在创建过程中设置。

名称 可以对每个 WorkerGlobalScope 的子类有不同的语义。 对于 DedicatedWorkerGlobalScope 的实例, 它就是一个简单的开发者提供的名字,主要用于调试用途。 对于 SharedWorkerGlobalScope 的实例,它允许通过 SharedWorker() 构造器获得一个通用共享 Worker 的引用。 对于 ServiceWorkerGlobalScope 对象,名字没有意义(同样地,它也根本没有通过 JavaScript API 暴露出来)。

WorkerGlobalScope 对象有一个与之关联的 referrer 策略 (一个 referrer 策略)。初始值为空字符串。

WorkerGlobalScope 对象有一个与之关联的 嵌入策略 (一个 嵌入策略)。

WorkerGlobalScope 对象有一个与之关联的 CSP 列表。 初始值为空列表。

WorkerGlobalScope 对象有一个与之关联的 模块映射。 初始值为空的 模块映射

WorkerGlobalScope 对象有一个与之关联的 跨域隔离能力 布尔,初始为 false。

workerGlobal . self

WorkerGlobalScope/self

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera11.5+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android34+Safari iOS5.1+Chrome Android40+WebView Android37+Samsung Internet4.0+Opera AndroidYes
返回 workerGlobal
workerGlobal . location

WorkerGlobalScope/location

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera11.5+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome Android40+WebView Android37+Samsung Internet4.0+Opera AndroidYes
返回 workerGlobalWorkerLocation 对象。
workerGlobal . navigator

WorkerGlobalScope/navigator

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera11.5+Edge79+
Edge (Legacy)17+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome Android40+WebView Android37+Samsung Internet4.0+Opera AndroidYes
返回 workerGlobalWorkerNavigator object。
workerGlobal . importScripts(urls...)

WorkerGlobalScope/importScripts

Support in all current engines.

Firefox4+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS3.2+Chrome Android18+WebView Android37+Samsung Internet1.0+Opera Android11+
获取 urls 中的每一个 URL,按照传入的顺序一个接一个地执行它们并返回 (如果有错误则抛出异常)。

self 属性必须返回 WorkerGlobalScope 对象自己。

location 属性必须 返回与之关联的 WorkerGlobalScope 对象WorkerGlobalScope 对象的 WorkerLocation 对象。

虽然 WorkerLocation 对象在 WorkerGlobalScope 对象之后创建, 但是因它对脚本不可见所以不会产生问题。


下面是实现 WorkerGlobalScope 接口的对象必须 (以 事件处理器 IDL 属性 的方式) 支持的 事件处理器 (以及它们对应的事件处理器事件类型):

事件处理器 事件处理器事件类型
onerror

WorkerGlobalScope/onerror

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera11.5+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome Android40+WebView Android37+Samsung Internet4.0+Opera AndroidYes
error
onlanguagechange

WorkerGlobalScope/onlanguagechange

Support in all current engines.

Firefox74+Safari4+Chrome4+
Opera11.5+Edge79+
Edge (Legacy)12+Internet ExplorerYes
Firefox AndroidNoSafari iOS5.1+Chrome Android40+WebView Android37+Samsung Internet4.0+Opera AndroidYes
languagechange
onoffline

WorkerGlobalScope/onoffline

Support in all current engines.

Firefox29+SafariYesChrome4+
Opera?Edge79+
Edge (Legacy)NoInternet ExplorerNo
Firefox Android29+Safari iOSYesChrome Android40+WebView Android40+Samsung Internet4.0+Opera Android?
offline
ononline

WorkerGlobalScope/ononline

Support in all current engines.

Firefox29+SafariYesChrome4+
Opera?Edge79+
Edge (Legacy)NoInternet ExplorerNo
Firefox Android29+Safari iOSYesChrome Android40+WebView Android40+Samsung Internet4.0+Opera Android?
online
onrejectionhandled rejectionhandled
onunhandledrejection unhandledrejection
10.2.1.2 专用 Worker 与 DedicatedWorkerGlobalScope 接口
[Global=(Worker,DedicatedWorker),Exposed=DedicatedWorker]
interface DedicatedWorkerGlobalScope : WorkerGlobalScope {
  void postMessage(any message, optional sequence<object> transfer = []);

  void close();

  attribute EventHandler onmessage;
};

DedicatedWorkerGlobalScope 对象的表现就像它有一个与之关联的隐式 MessagePort 一样。该端口是创建 Worker 时建立的通道的一部分,但没有暴露出来。 禁止在DedicatedWorkerGlobalScope之前垃圾回收该对象。

该端口收到的所有消息必须立即传给 DedicatedWorkerGlobalScope 对象。

dedicatedWorkerGlobal . postMessage(message [, transfer ])

DedicatedWorkerGlobalScope/postMessage

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome AndroidYesWebView Android37+Samsung InternetYesOpera Android11+
克隆 message 并传送给与 dedicatedWorkerGlobal 关联的 Worker 对象。transfer 可以传一个不需克隆而直接传输的对象列表。
dedicatedWorkerGlobal . close()

DedicatedWorkerGlobalScope/close

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera11.5+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome Android18+WebView Android37+Samsung Internet1.0+Opera Android11.5+
终止 dedicatedWorkerGlobal

DedicatedWorkerGlobalScope 对象上的 postMessage() 方法被调用时必须表现得就像立即使用同样的参数列表调用了该端口上的 同名方法 一样, 并且返回同样的返回值。

给定 workerGlobal关闭 worker 的步骤如下:

  1. 忽略所有已添加到 workerGlobal事件循环任务列表 中的 任务

  2. 设置 workerGlobalclosing 标志为 true。(这将阻止后续的任务入队。)

close() 方法被调用时, 必须 关闭 DedicatedWorkerGlobalScope 上的 Worker。


下面是实现 DedicatedWorkerGlobalScope 接口的对象必须 (以 事件处理器 IDL 属性 的方式) 支持的 事件处理器 (以及它们对应的事件处理器事件类型):

事件处理器 事件处理器事件类型
onmessage

DedicatedWorkerGlobalScope/onmessage

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome AndroidYesWebView Android37+Samsung InternetYesOpera Android11+
message
onmessageerror

DedicatedWorkerGlobalScope/onmessageerror

Firefox57+Safari?Chrome60+
Opera47+Edge79+
Edge (Legacy)18Internet Explorer?
Firefox Android57+Safari iOS?Chrome Android60+WebView Android60+Samsung Internet8.0+Opera Android44+
messageerror

出于 application cache 网络模型的目的,专用 worker 是 创建它的 cache host 的一个扩展。

10.2.1.3 共享 worker 与 SharedWorkerGlobalScope 接口
[Global=(Worker,SharedWorker),Exposed=SharedWorker]
interface SharedWorkerGlobalScope : WorkerGlobalScope {
  readonly attribute DOMString name;

  undefined close();

  attribute EventHandler onconnect;
};

SharedWorkerGlobalScope 对象有与之相关联的 构造器 origin构造器 url,以及 credentials。它们在创建 SharedWorkerGlobalScope 对象时初始化,是 运行 Worker 算法的一部分。

共享 worker 通过它们的 SharedWorkerGlobalScope 对象上的 connect 事件接受每个连接的消息端口。

sharedWorkerGlobal . name

SharedWorkerGlobalScope/name

Firefox55+SafariNoChromeYes
Opera10.6+EdgeYes
Edge (Legacy)NoInternet ExplorerNo
Firefox Android55+Safari iOSNoChrome Android40+WebView AndroidYesSamsung Internet4.0+Opera AndroidYes
返回 sharedWorkerGlobalname,即给到 SharedWorker 构造函数的值。 通过复用同一个名字,多个 SharedWorker 对象可以对应于同一个共享工作线程(以及 SharedWorkerGlobalScope)。
sharedWorkerGlobal . close()

SharedWorkerGlobalScope/close

Support in all current engines.

Firefox29+Safari5+Chrome4+
Opera11.5+Edge79+
Edge (Legacy)NoInternet ExplorerNo
Firefox Android29+Safari iOS7+Chrome Android18+WebView Android37+Samsung Internet1.0+Opera Android11+
终止 sharedWorkerGlobal

name 属性必须返回 SharedWorkerGlobalScope 对象的 name。使用 SharedWorker 构造器, 可以通过 name 的值获取该 worker 的引用。

close() 方法被调用时,必须 关闭SharedWorkerGlobalScope 对象上的 worker。


下面是实现 SharedWorkerGlobalScope 接口的对象必须 (以 事件处理器 IDL 属性 的方式) 支持的 事件处理器 (以及它们对应的事件处理器事件类型):

事件处理器 事件处理器事件类型
onconnect

SharedWorkerGlobalScope/onconnect

Firefox29+SafariNoChrome4+
Opera10.6+Edge79+
Edge (Legacy)NoInternet ExplorerNo
Firefox Android29+Safari iOS?Chrome Android18+WebView AndroidYesSamsung Internet1.0+Opera AndroidYes
connect

10.2.2 事件循环

工作线程事件循环任务队列任务 只有事件、回调和网络活动。 这些 工作线程事件循环运行一个工作线程 算法创建。

每个 WorkerGlobalScope 对象也有一个 closing 标志,初始值 必须为 false, 但在 可以 被下面的处理模型一节中的算法 设为 true。

一旦 WorkerGlobalScopeclosing 标志被设为 true, 事件循环任务队列 必须 忽略后续添加的 任务 (已经在队列中的任务不受影响,除非另有说明)。 也就是说一旦 closing 标志位 true, 定时器会停止触发,所有正在进行的后台操作的通知会被扔掉。

10.2.3 The worker's lifetime

Workers communicate with other workers and with browsing contexts through message channels and their MessagePort objects.

Each WorkerGlobalScope object 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.

Given an environment settings object o when creating or obtaining a worker, the relevant owner 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 dedicated worker), then the relevant owner is that global object. Otherwise, o specifies a global object that is a Window object, and the relevant owner is the responsible document specified by o.


A worker is said to be a permissible worker if its WorkerGlobalScope's owner set is not empty or:

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 its owners are either Document objects that are fully active or active needed workers.

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.

10.2.4 Processing model

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, and a WorkerOptions dictionary options, it must run the following steps.

  1. Let is shared be true if worker is a SharedWorker object, and false otherwise.

  2. Let owner be the relevant owner to add given outside settings.

  3. Let parent worker global scope be null.

  4. If owner is a WorkerGlobalScope object (i.e., we are creating a nested dedicated worker), then set parent worker global scope to owner.

  5. Let agent be the result of obtaining a dedicated/shared worker agent given outside settings and is shared. Run the rest of these steps in that agent.

    For the purposes of timing APIs, this is the official moment of creation of the worker.

  6. Let realm execution context be the result of creating a new JavaScript realm given agent and the following customizations:

  7. Let worker global scope be the global object of realm execution context's Realm component.

    This is the DedicatedWorkerGlobalScope or SharedWorkerGlobalScope object created in the previous step.

  8. Set up a worker environment settings object with realm execution context and outside settings, and let inside settings be the result.

  9. Set worker global scope's name to the value of options's name member.

  10. Append owner to worker global scope's owner set.

  11. If parent worker global scope is not null, then append worker global scope to parent worker global scope's worker set.

  12. If is shared is true, then:

    1. Set worker global scope's constructor origin to outside settings's origin.

    2. Set worker global scope's constructor url to url.

    3. Set worker global scope's type to the value of options's type member.

    4. Set worker global scope's credentials to the value of options's credentials member.

  13. Let destination be "sharedworker" if is shared is true, and "worker" otherwise.

  14. Obtain script by switching on the value of options's type member:

    "classic"
    Fetch a classic worker script given url, outside settings, destination, and inside settings.
    "module"
    Fetch a module worker script graph given url, outside settings, destination, the value of the 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:

    1. Set request's reserved client to inside settings.
    2. Fetch request, and asynchronously wait to run the remaining steps as part of fetch's process response for the response response.

    3. Set worker global scope's url to response's url.

    4. Set worker global scope's referrer policy to the result of parsing the `Referrer-Policy` header of response.

    5. If response's url's scheme is a local scheme, then set worker global scope's embedder policy to owner's embedder policy.

    6. Otherwise, set worker global scope's embedder policy to the result of obtaining an embedder policy from response.

    7. If worker global scope's embedder policy is "require-corp" and is shared is true, then set agent's agent cluster's cross-origin isolated to true.

      This really ought to be set when the agent cluster is created, which requires a redesign of this section.

    8. If the result of checking a global object's embedder policy with worker global scope, owner, and response is false, then set response to a network error.

    9. Set worker global scope's cross-origin isolated capability to agent's agent cluster's cross-origin isolated.

    10. If is shared is false and owner's cross-origin isolated capability is false, then set worker global scope's cross-origin isolated capability to false.

    11. If is shared is false and response's url's scheme is "data", then set worker global scope's cross-origin isolated capability to false.

      This is a conservative default for now, while we figure out how workers in general, and data: URL workers in particular (which are cross-origin from their owner), will be treated in the context of permissions policies. See w3c/webappsec-permissions-policy issue #207 for more details.

    12. Initialize a global object's CSP list given worker global scope and response. [CSP]

    13. Asynchronously complete the perform the fetch steps with response.

    If the algorithm asynchronously completes with null or with a script whose error to rethrow is non-null, then:

    1. Queue a global task on the DOM manipulation task source given worker's relevant global object to fire an event named error at worker.

    2. Run the environment discarding steps for inside settings.

    3. Return.

    Otherwise, continue the rest of these steps after the algorithm's asynchronous completion, with script being the asynchronous completion value.

  15. Associate worker with worker global scope.

  16. Let inside port be a new MessagePort object in inside settings's Realm.

  17. Associate inside port with worker global scope.

  18. Entangle outside port and inside port.

  19. Create a new WorkerLocation object and associate it with worker global scope.

  20. 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.

  21. 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.

  22. Set inside settings's execution ready flag.

  23. 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 terminate a worker algorithm defined below.

  24. Enable outside port's port message queue.

  25. If is shared is false, enable the port message queue of the worker's implicit port.

  26. If is shared is true, then queue a global task on DOM manipulation task source given worker global scope 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.

  27. Enable the client message queue of the ServiceWorkerContainer object whose associated service worker client is worker global scope's relevant settings object.

  28. 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 terminate a worker algorithm 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.

  29. Empty the worker global scope's list of active timers.

  30. Disentangle all the ports in the list of the worker's ports.

  31. Empty worker global scope's owner set.


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):

  1. Set the worker's WorkerGlobalScope object's closing flag to true.

  2. If there are any tasks queued in the WorkerGlobalScope object's relevant agent's event loop's task queues, discard them without processing them.

  3. Abort the script currently running in the worker.

  4. 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.

User agents may invoke the terminate a worker algorithm when a worker stops being an active needed worker and the worker continues executing even after its closing flag was set to true.

10.2.5 Runtime script errors

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:

  1. Let notHandled be the result of firing an event named error at the 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.

  2. 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.

10.2.6 创建 Worker

10.2.6.1 The AbstractWorker mixin

AbstractWorker

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome Android18+WebView Android4.4+Samsung Internet1.0+Opera Android11+
interface mixin 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

AbstractWorker/onerror

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome Android18+WebView Android4.4+Samsung Internet1.0+Opera Android11+
error
10.2.6.2 Script settings for workers

To set up a worker environment settings object, given a JavaScript execution context execution context and environment settings object outside settings:

  1. Let inherited origin be outside settings's origin.

  2. Let realm be the value of execution context's Realm component.

  3. Let worker global scope be realm's global object.

  4. Let settings object be a new environment settings object whose algorithms are defined as follows:

    The realm execution context

    Return execution context.

    The module map

    Return worker global scope's module map.

    The responsible document

    Not applicable (the responsible event loop is not a window event loop).

    The API URL character encoding

    Return UTF-8.

    The API base URL

    Return worker global scope's url.

    The origin

    Return a unique opaque origin if worker global scope's url's scheme is "data", and inherited origin otherwise.

    The referrer policy

    Return worker global scope's referrer policy.

    The embedder policy

    Return worker global scope's embedder policy.

    The cross-origin isolated capability

    Return worker global scope's cross-origin isolated capability.

  5. Set settings object's id to a new unique opaque string, creation URL to worker global scope's url, top-level creation URL to null, target browsing context to null, and active service worker to null.

  6. If worker global scope is a DedicatedWorkerGlobalScope object, then set settings object's top-level origin to outside settings's top-level origin.

  7. Otherwise, set settings object's top-level origin to an implementation-defined value.

    See Client-Side Storage Partitioning for the latest on properly defining this.

  8. Set realm's [[HostDefined]] field to settings object.

  9. Return settings object.

10.2.6.3 Dedicated workers and the Worker interface

Worker

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome Android18+WebView Android4+Samsung Internet1.0+Opera Android11+
[Exposed=(Window,DedicatedWorker,SharedWorker)]
interface Worker : EventTarget {
  constructor(USVString scriptURL, optional WorkerOptions options = {});

  undefined terminate();

  undefined postMessage(any message, sequence<object> transfer);
  undefined postMessage(any message, optional PostMessageOptions options = {});
  attribute EventHandler onmessage;
  attribute EventHandler onmessageerror;
};

dictionary WorkerOptions {
  WorkerType type = "classic";
  RequestCredentials credentials = "same-origin"; // credentials is only used if type is "module"
  DOMString name = "";
};

enum WorkerType { "classic", "module" };

Worker includes AbstractWorker;
worker = new Worker(scriptURL [, options ])

Worker/Worker

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome Android18+WebView Android4+Samsung Internet1.0+Opera Android11+
Returns a new 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 define the name of that global environment via the name option, primarily for debugging purposes. It can also 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.
worker . terminate()

Worker/terminate

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome Android18+WebView Android4+Samsung Internet1.0+Opera Android11+
Aborts worker's associated global environment.
worker . postMessage(message [, transfer ] )

Worker/postMessage

Support in all current engines.

FirefoxYesSafariYesChromeYes
Opera47+EdgeYes
Edge (Legacy)12+Internet Explorer10+
Firefox AndroidYesSafari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera Android44+
worker . postMessage(message [, { transfer } ] )
Clones message and transmits it to worker's global environment. transfer can be passed as a list of objects that are to be transferred rather than cloned.

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(message, transfer) and postMessage(message, options) methods on Worker objects act as if, when invoked, they immediately invoked the respective postMessage(message, transfer) and postMessage(message, options) 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

Worker/onmessage

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOS5.1+Chrome Android18+WebView Android4+Samsung Internet1.0+Opera Android11+
message
onmessageerror

Worker/onmessageerror

Firefox57+Safari?Chrome60+
Opera47+Edge79+
Edge (Legacy)18Internet ExplorerNo
Firefox Android57+Safari iOS?Chrome Android60+WebView Android60+Samsung Internet8.0+Opera Android44+
messageerror

When the Worker(scriptURL, options) constructor is invoked, the user agent must run the following steps:

  1. The user agent may throw a "SecurityError" DOMException if the request violates a policy decision (e.g. if the user agent is configured to not allow the page to start dedicated workers).

  2. Let outside settings be the current settings object.

  3. Parse the scriptURL argument relative to outside settings.

  4. If this fails, throw a "SyntaxError" DOMException.

  5. 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.

  6. Let worker be a new Worker object.

  7. Let outside port be a new MessagePort in outside settings's Realm.

  8. Associate the outside port with worker.

  9. Run this step in parallel:

    1. Run a worker given worker, worker URL, outside settings, outside port, and options.

  10. Return worker.

10.2.6.4 Shared workers and the SharedWorker interface

SharedWorker

Firefox29+Safari5–6.1Chrome4+
Opera10.6+Edge79+
Edge (Legacy)NoInternet ExplorerNo
Firefox Android33+Safari iOS5.1–7Chrome AndroidNoWebView AndroidNoSamsung Internet4.0–5.0Opera Android11–14
[Exposed=Window]
interface SharedWorker : EventTarget {
  constructor(USVString scriptURL, optional (DOMString or WorkerOptions) options = {});

  readonly attribute MessagePort port;
};
SharedWorker includes AbstractWorker;
sharedWorker = new SharedWorker(scriptURL [, name ])

SharedWorker/SharedWorker

Firefox29+Safari5–6.1Chrome4+
Opera10.6+Edge79+
Edge (Legacy)NoInternet ExplorerNo
Firefox Android33+Safari iOS5.1–7Chrome AndroidNoWebView AndroidNoSamsung Internet4.0–5.0Opera Android11–14
Returns a new 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.
sharedWorker = new SharedWorker(scriptURL [, options ])
Returns a new SharedWorker object. scriptURL will be fetched and executed in the background, creating a new global environment for which sharedWorker represents the communication channel. options can be used to define the name of that global environment via the name option. It can also 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. Note that attempting to construct a shared worker with options whose type or credentials values mismatch an existing shared worker will cause the returned sharedWorker to fire an error event and not connect to the existing shared worker.
sharedWorker . port

SharedWorker/port

Firefox29+Safari5–6.1Chrome4+
Opera10.6+Edge79+
Edge (Legacy)NoInternet ExplorerNo
Firefox Android33+Safari iOS5.1–7Chrome AndroidNoWebView AndroidNoSamsung Internet4.0–5.0Opera Android11–14
Returns sharedWorker's 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.

A user agent has an associated shared worker manager which is the result of starting a new parallel queue.

Each user agent has a single shared worker manager for simplicity. Implementations could use one per origin; that would not be observably different and enables more concurrency.

When the SharedWorker(scriptURL, options) constructor is invoked:

  1. Optionally, throw a "SecurityError" DOMException if the request violates a policy decision (e.g. if the user agent is configured to not allow the page to start shared workers).

  2. If options is a DOMString, set options to a new WorkerOptions dictionary whose name member is set to the value of options and whose other members are set to their default values.

  3. Let outside settings be the current settings object.

  4. Parse scriptURL relative to outside settings.

  5. If this fails, throw a "SyntaxError" DOMException.

  6. 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.

  7. Let worker be a new SharedWorker object.

  8. Let outside port be a new MessagePort in outside settings's Realm.

  9. Assign outside port to the port attribute of worker.

  10. Let callerIsSecureContext be true if outside settings is a secure context; otherwise, false.

  11. Enqueue the following steps to the shared worker manager:

    1. Let worker global scope be null.

    2. 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 equals the value of options's name member, 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.

    3. 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.

    4. If worker global scope is not null, then check if worker global scope's type and credentials match the options values. If not, queue a task to fire an event named error and abort these steps.

    5. If worker global scope is not null, then run these subsubsteps:

      1. Let settings object be the relevant settings object for worker global scope.

      2. Let workerIsSecureContext be true if settings object is a secure context; otherwise, false.

      3. If workerIsSecureContext is not callerIsSecureContext, then queue a task to fire an event named error at worker and abort these steps. [SECURE-CONTEXTS]

      4. Associate worker with worker global scope.

      5. Let inside port be a new MessagePort in settings object's Realm.

      6. Entangle outside port and inside port.

      7. 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.

      8. Append the relevant owner to add given outside settings to worker global scope's owner set.

    6. Otherwise, in parallel, run a worker given worker, urlRecord, outside settings, outside port, and options.

  12. Return worker.

NavigatorConcurrentHardware

Firefox48+Safari10.1–11Chrome37+
Opera24+Edge79+
Edge (Legacy)15+Internet ExplorerNo
Firefox Android48+Safari iOS10.3–11Chrome Android37+WebView Android37+Samsung Internet3.0+Opera Android24+
interface mixin NavigatorConcurrentHardware {
  readonly attribute unsigned long long hardwareConcurrency;
};
self . navigator . hardwareConcurrency

NavigatorConcurrentHardware/hardwareConcurrency

Firefox48+Safari10.1–11Chrome37+
Opera24+Edge79+
Edge (Legacy)15+Internet ExplorerNo
Firefox Android48+Safari iOS10.3–11Chrome Android37+WebView Android37+Samsung Internet3.0+Opera Android24+

Returns the number of logical processors potentially available to the user agent.

(This is a tracking vector.) 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.

10.3 Worker 中可用的 API

10.3.1 Importing scripts and libraries

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.

  1. If worker global scope's type is "module", throw a TypeError exception.

  2. Let settings object be the current settings object.

  3. If urls is empty, return.

  4. Parse each value in urls relative to settings object. If any fail, throw a "SyntaxError" DOMException.

  5. For each url in the resulting URL records, run these substeps:

    1. 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.

    2. Run the classic script script, with the rethrow errors argument set to true.

      script will run until it either returns, fails to parse, fails to catch an exception, or gets prematurely aborted by the terminate a worker algorithm 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]

10.3.2 The WorkerNavigator interface

WorkerNavigator

Support in all current engines.

Firefox3.5+SafariYesChrome1+
OperaYesEdge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera AndroidYes

The 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 includes NavigatorID;
WorkerNavigator includes NavigatorLanguage;
WorkerNavigator includes NavigatorOnLine;
WorkerNavigator includes NavigatorConcurrentHardware;

10.3.3 The WorkerLocation interface

WorkerLocation

Support in all current engines.

Firefox3.5+SafariYesChrome1+
OperaYesEdge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android4+Safari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera AndroidYes

WorkerLocation/toString

Support in all current engines.

Firefox3.5+SafariYesChrome1+
OperaYesEdge79+
Edge (Legacy)NoInternet Explorer?
Firefox Android4+Safari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera AndroidYes
[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).

WorkerLocation/href

Support in all current engines.

Firefox3.5+SafariYesChrome1+
OperaYesEdge79+
Edge (Legacy)NoInternet Explorer?
Firefox Android4+Safari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera AndroidYes

The href attribute's getter must return the associated WorkerGlobalScope object's url, serialized.

WorkerLocation/origin

Support in all current engines.

Firefox3.5+SafariYesChrome1+
OperaYesEdge79+
Edge (Legacy)NoInternet Explorer?
Firefox Android4+Safari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera AndroidYes

The origin attribute's getter must return the serialization of the associated WorkerGlobalScope object's url's origin.

WorkerLocation/protocol

Support in all current engines.

Firefox3.5+SafariYesChrome1+
OperaYesEdge79+
Edge (Legacy)NoInternet Explorer?
Firefox Android4+Safari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera AndroidYes

The protocol attribute's getter must return the associated WorkerGlobalScope object's url's scheme, followed by ":".

WorkerLocation/host

Support in all current engines.

Firefox3.5+SafariYesChrome1+
OperaYesEdge79+
Edge (Legacy)NoInternet Explorer?
Firefox Android4+Safari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera AndroidYes

The host attribute's getter must run these steps:

  1. Let url be the associated WorkerGlobalScope object's url.

  2. If url's host is null, return the empty string.

  3. If url's port is null, return url's host, serialized.

  4. Return url's host, serialized, followed by ":" and url's port, serialized.

WorkerLocation/hostname

Support in all current engines.

Firefox3.5+SafariYesChrome1+
OperaYesEdge79+
Edge (Legacy)NoInternet Explorer?
Firefox Android4+Safari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera AndroidYes

The hostname attribute's getter must run these steps:

  1. Let host be the associated WorkerGlobalScope object's url's host.

  2. If host is null, return the empty string.

  3. Return host, serialized.

WorkerLocation/port

Support in all current engines.

Firefox3.5+SafariYesChrome1+
OperaYesEdge79+
Edge (Legacy)NoInternet Explorer?
Firefox Android4+Safari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera AndroidYes

The port attribute's getter must run these steps:

  1. Let port be the associated WorkerGlobalScope object's url's port.

  2. If port is null, return the empty string.

  3. Return port, serialized.

WorkerLocation/pathname

Support in all current engines.

Firefox3.5+SafariYesChrome1+
OperaYesEdge79+
Edge (Legacy)NoInternet Explorer?
Firefox Android4+Safari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera AndroidYes

The pathname attribute's getter must run these steps:

  1. Let url be the associated WorkerGlobalScope object's url.

  2. If url's cannot-be-a-base-URL flag is set, return the first string in url's path.

  3. Return "/", followed by the strings in url's path (including empty strings), separated from each other by "/".

WorkerLocation/search

Support in all current engines.

Firefox3.5+SafariYesChrome1+
OperaYesEdge79+
Edge (Legacy)NoInternet Explorer?
Firefox Android4+Safari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera AndroidYes

The search attribute's getter must run these steps:

  1. Let query be the associated WorkerGlobalScope object's url's query.

  2. If query is either null or the empty string, return the empty string.

  3. Return "?", followed by query.

WorkerLocation/hash

Support in all current engines.

Firefox3.5+SafariYesChrome1+
OperaYesEdge79+
Edge (Legacy)NoInternet Explorer?
Firefox Android4+Safari iOSYesChrome AndroidYesWebView AndroidYesSamsung InternetYesOpera AndroidYes

The hash attribute's getter must run these steps:

  1. Let fragment be the associated WorkerGlobalScope object's url's fragment.

  2. If fragment is either null or the empty string, return the empty string.

  3. Return "#", followed by fragment.