1. 10 ウェブワーカー
    1. 10.1 導入
      1. 10.1.1 範囲
      2. 10.1.2
        1. 10.1.2.1 バックグラウンドで計算処理を行うワーカー
        2. 10.1.2.2 JavaScriptモジュールをワーカーとして使用する
        3. 10.1.2.3 共有ワーカーの紹介
        4. 10.1.2.4 共有ワーカーを使用した共有状態
        5. 10.1.2.5 委譲
        6. 10.1.2.6 ライブラリの提供
      3. 10.1.3 チュートリアル
        1. 10.1.3.1 専用ワーカーの作成
        2. 10.1.3.2 専用ワーカーとの通信
        3. 10.1.3.3 共有ワーカー
    2. 10.2 インフラ
      1. 10.2.1 グローバルスコープ
        1. 10.2.1.1 WorkerGlobalScope共通インターフェイス
        2. 10.2.1.2 専用ワーカーおよびDedicatedWorkerGlobalScopeインターフェイス
        3. 10.2.1.3 共有ワーカーおよびSharedWorkerGlobalScopeインターフェイス
      2. 10.2.2 イベントループ
      3. 10.2.3 ランタイムスクリプトエラー
      4. 10.2.4 ワーカーの作成
        1. 10.2.4.1 WorkerSharedWorkerとの両方に存在するプロパティ
        2. 10.2.4.2 専用ワーカーおよびWorkerインターフェイス
        3. 10.2.4.3 共有ワーカーおよびSharedWorkerインターフェイス
      5. 10.2.5 同時ハードウェア機能
    3. 10.3 ワーカーが使用可能なAPI
      1. 10.3.1 WorkerNavigatorインターフェイス
      2. 10.3.2 WorkerLocationインターフェイス

10 ウェブワーカー

Web_Workers_API

Support in all current engines.

Firefox3.5+Safari4+Chrome2+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android?Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android11+

Web_Workers_API/Using_web_workers

10.1 導入

10.1.1 範囲

この仕様は、ユーザインタフェースのスクリプトとは独立して、バックグラウンドでスクリプトを実行するためのAPIを定義する。

これにより、クリックまたはその他のユーザー操作に応答するスクリプトによって中断されない長時間実行スクリプトが可能になり、ページの応答性を維持するために譲歩することなく長時間のタスクを実行できる。

ワーカー(ここではこれらのバックグラウンドスクリプトと呼ぶ)は、比較的重いものであり、大量に使用することは想定されていない。たとえば、4メガピクセルの画像の各ピクセルに対して1つのワーカーを起動するのは不適切である。次節の例では、ワーカーの適切な使用法を示している。

一般に、ワーカーは寿命が長く、起動時のパフォーマンスコストが高く、インスタンスごとのメモリコストが高いと予想される。

10.1.2

ワーカーにはさまざまな用途がある。次のサブセクションでは、この用途のさまざまな例を示す。

10.1.2.1 バックグラウンドで計算処理を行うワーカー

ワーカーの最も簡単な用法は、ユーザーインターフェイスを中断することなく、計算コストの高いタスクを実行することである。

この例では、メイン文書がワーカーを生成して(単純に)素数を計算し、最近検出された素数を順を追って表示する。

メインページは次のとおりである:

<!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オブジェクトを返す。このオブジェクトは、ワーカーとの通信に使用される。そのオブジェクトのonmessageイベントハンドラーによって、コードはワーカーからメッセージを受け取ることができる。

ワーカー自体は次のとおりである:

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モジュールをワーカーとして使用する

これまでの例はすべて、クラシックスクリプトを実行するワーカーを示している。代わりに、ワーカーはモジュールスクリプトを使用してインスタンス化することができる。これには、JavaScript importステートメントを使用して他のモジュールをインポートする機能、デフォルトのstrictモード、ワーカーのグローバルスコープを汚染しないトップレベルの宣言など、通常の利点がある。

importステートメントが使用可能であるため、importScripts()メソッドはモジュールワーカー内で自動的に失敗する。

この例では、メイン文書はワーカーを使用してメインスレッド以外の画像操作を行う。これは、別のモジュールで使用されているフィルターをインポートする。

メインページは次のとおりである:

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

<div id="output"></div>

<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 = () => {
      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.replaceChildren(canvas);
    };
  }

  function sendToWorker() {
    worker.postMessage({ imageData, filter: filter.value });
  }

  function receiveFromWorker(e) {
    context.putImageData(e.data, 0, 0);
  }
</script>

ワーカーファイルは次のようになる:

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 共有ワーカーの紹介

SharedWorker

Support in all current engines.

Firefox29+Safari16+Chrome5+
Opera10.6+Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android33+Safari iOS16+Chrome AndroidNoWebView Android?Samsung Internet4.0–5.0Opera Android11–14

この節では、Hello Worldの例を使用して共有ワーカーを紹介する。共有ワーカーは、各ワーカーが複数の接続を持つことができるため、わずかに異なるAPIを使用する。

この最初の例では、ワーカーに接続する方法と、ワーカーが接続したときにページにメッセージを送り返す方法を示す。受信したメッセージはログに表示される。

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ワーカーは次のとおり:

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

この例をオンラインで見る


この2番目の例は最初の例を拡張したもので、2つの点を変更している。1つ目は、イベントハンドラーIDL属性の代わりにaddEventListener()を使用してメッセージを受信すること、2つ目は、メッセージをワーカー送信して、ワーカーに別のメッセージを送信させることである。受信したメッセージは再びログに表示される。

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ワーカーは次のとおり:

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

この例をオンラインで見る


最後に、この例を拡張して、2つのページが同じワーカーに接続する方法を示す。この場合、2番目のページは最初のページの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ワーカーは次のとおり:

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 共有ワーカーを使用した共有状態

この例では、すべて同じマップを表示する複数のウィンドウ(ビューアー)を開くことができる。すべてのウィンドウは同じマップ情報を共有し、1つのワーカーがすべてのビューアーを調整する。各ビューアーは独立して移動できるが、マップ上にデータを設定する場合、すべてのビューアーが更新される。

メインページは興味深くはなく、単にビューアーを開く方法を提供しているだけである:

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

ビューアーの作成方法については、注目に値する重要な点がいくつかある。

複数のリスナー。単一のメッセージ処理関数の代わりに、ここでのコードは複数のイベントリスナーをアタッチし、それぞれがメッセージに関連するかどうかを確認するためのクイックチェックを実行する。この例では大きな違いはないが、複数の著者が単一のポートを使用して共同作業し、ワーカーと通信したい場合、単一のイベント処理関数をすべて変更する代わりに、独立したコードを使用できる。

この方法でイベントリスナーを登録すると、この例のconfigure()メソッドで行ったように、使用後に特定のリスナーの登録を解除することもできる。

最後に、ワーカーは:

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イベントリスナーを使用して、複数の接続をリッスンする。

ダイレクトチャンネル 。ワーカーは、あるビューアーから別のビューアーを指定する"msg"メッセージを受信すると、2つのビューアー間に直接接続を設定し、ワーカーはすべてのメッセージをプロキシすることなく、2つのビューアーが直接通信できるようにする。

この例をオンラインで見る

10.1.2.5 委譲

マルチコアCPUが普及するにつれて、よりよいパフォーマンスを得る1つの方法は、計算コストの高いタスクを複数のワーカーに分割することである。この例では、1から10,000,000までのすべての数値に対して実行される計算コストの高いタスクが、10のサブワーカーに割り当てられる。

メインページは次のとおりで、結果を報告するだけである:

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

ワーカー自体は次のとおりである:

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

これは、サブワーカーを開始するループと、すべてのサブワーカーが応答するのを待つハンドラーで構成される。

サブワーカーは次のように実装される:

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

2つのイベントで2つの数値を受け取り、指定された数値の範囲に対して計算を実行し、その結果を親に報告する。

この例をオンラインで見る

10.1.2.6 ライブラリの提供

仮に次の3つのタスクを提供する暗号化ライブラリが利用可能になったとする:

公開鍵と秘密鍵のペアを生成する
最初に公開キー、次に秘密キーの2つのメッセージを送信するポートを取得する。
与えられた平文と、公開鍵に対応する暗号文を返す
任意の数のメッセージを送信できるポートを取得する。最初のポートは公開鍵を提供し、残りのポートは平文を提供する。各メッセージは暗号化され、暗号文と同じチャンネルで送信される。ユーザーは、コンテンツの暗号化が完了したらポートを閉じることができる。
暗号文と秘密鍵が与えられた場合、対応する平文を返す
任意の数のメッセージを送信できるポートを取得する。最初のポートは秘密鍵を提供し、残りのポートは暗号文を提供する。各メッセージは復号化され、プレーンテキストと同じチャンネルで送信される。ユーザーは、コンテンツの復号化が完了したらポートを閉じることができる。

ライブラリ自体は次のとおり:

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

ここでの暗号関数は単なるスタブであり、実際の暗号化を行うものではないことに注意する。

このライブラリは次のように使用できる:

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

ただし、APIの新しいバージョンでは、すべての暗号化作業をサブワーカーにオフロードしたいかもしれない。これは、次のように実行できる:

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

小さなサブワーカーは次のようになる。

キーペアを生成する場合:

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

暗号化の場合:

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

復号化の場合:

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

APIのユーザーは、これが起こっていることさえ知る必要がないことに注意する。APIは変更されていない。ライブラリは、メッセージチャンネルを使用してデータを受け入れている場合でも、APIを変更せずにサブワーカーに委任できる。

この例をオンラインで見る

10.1.3 チュートリアル

10.1.3.1 専用ワーカーの作成

ワーカーを作成するには、JavaScriptファイルへのURLが必要である。Worker()コンストラクターは、そのファイルへのURLを唯一の引数として呼び出される。次に、ワーカーが作成されて返される:

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

ワーカースクリプトをデフォルトのクラシックスクリプトではなく、モジュールスクリプトとして解釈する場合、少し異なるシグネチャーを使用する必要がある:

var worker = new Worker('helper.mjs', { type: "module" });
10.1.3.2 専用ワーカーとの通信

専用ワーカーは背後でMessagePortオブジェクトを使用するため、構造化データの送信、バイナリデータの転送、他のポートの転送など、すべての同じ機能をサポートする。

専用ワーカーからメッセージを受信するには、WorkerオブジェクトでonmessageイベントハンドラーIDL属性をする:

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

addEventListener()メソッドを使用することもできる。

専用ワーカーによって使用される暗黙のMessagePortは、作成時にそのポートメッセージキューが暗黙的に有効になるため、WorkerインターフェースにはMessagePortインターフェースのstart()メソッドに相当するものは存在しない。

ワーカーにデータを送信するには、postMessage()メソッドを使用する。構造化データは、この通信チャンネルを介して送信できる。(オブジェクトのクローンを作成するのではなく、オブジェクトを転送することによって、)ArrayBufferオブジェクトを効率的に送信するには、オブジェクトを2番目の引数の配列にリストする。

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

ワーカー内部でメッセージを受信するには、onmessageイベントハンドラーIDL属性を使用する。

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

この場合も、addEventListener()メソッドを使用できる。

いずれの場合も、データはイベントオブジェクトのdata属性で提供される。

メッセージを送り返すには、やはりpostMessage()を使用する。同じ方法で構造化データをサポートする。

postMessage(event.data.input, [event.data.input]); // transfer the buffer back
10.1.3.3 共有ワーカー

SharedWorker

Support in all current engines.

Firefox29+Safari16+Chrome5+
Opera10.6+Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android33+Safari iOS16+Chrome AndroidNoWebView Android?Samsung Internet4.0–5.0Opera Android11–14

共有ワーカーは、その作成に使用されたスクリプトのURL、オプションで明示的な名前を使用可能、によって識別される。この名前は、特定の共有ワーカーの複数のインスタンスを開始するのを可能にする。

共有ワーカーの範囲は、生成元である。同じ名前を使用する2つの異なるサイトは衝突しない。しかし、ページが同じサイト上の別のページと同じ共有ワーカー名を使用しようとするが、スクリプトURLが異なる場合、失敗する。

共有ワーカーの作成は、SharedWorker()コンストラクターを使用して行われる。このコンストラクターは、最初の引数として使用するスクリプトへのURLと、存在する場合、2番目の引数としてワーカーの名前を受け取る。

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

共有ワーカーとの通信は、明示的なMessagePortオブジェクトを使用して行われる。SharedWorker()コンストラクターによって返されたオブジェクトは、そのport属性のポートへの参照を保持する。

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

共有ワーカー内では、ワーカーの新しいクライアントがconnectイベントを使用して通知される。新しいクライアントのポートは、イベントオブジェクトの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 インフラ

この標準では、専用ワーカーと共有ワーカーの2種類のワーカーが定義されている。専用ワーカーは、作成されると作成者にリンクされるが、メッセージポートは、専用ワーカーから他の複数のブラウジングコンテキストまたはワーカーに通信するために使用できない。一方、共有ワーカーは、名前が付けられ、同じ生成元で実行されるスクリプトが作成されると、そのワーカーへの参照を取得して通信できる。Service Workersは第3の種類を定義する。[SW]

10.2.1 グローバルスコープ

グローバルスコープは、ワーカーの"内部"である。

10.2.1.1 WorkerGlobalScope共通インターフェイス

WorkerGlobalScope

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android?Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android11+

WorkerGlobalScopeは、DedicatedWorkerGlobalScopeSharedWorkerGlobalScopeServiceWorkerGlobalScopeなど、特定のタイプのワーカーグローバルスコープオブジェクトの基本クラスとして機能する。

workerGlobal.self
workerGlobalを返す。
workerGlobal.location
workerGlobalWorkerLocationオブジェクトを返す。
workerGlobal.navigator
workerGlobalWorkerNavigatorオブジェクトを返す。
workerGlobal.importScripts(...urls)
urls内の各URLをフェッチし、渡された順序で1つずつ実行してから返す(または何かがうまくいかなかった場合は投げる)。

下記は、WorkerGlobalScopeインターフェイスを実装するすべてのオブジェクトによって、イベントハンドラーIDL属性として、サポートされるイベントハンドラー(および対応するイベントハンドラーイベント型)である:

イベントハンドラーイベントハンドラーイベント型
onerror

WorkerGlobalScope/error_event

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera11.5+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android?Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android?
error
onlanguagechange

WorkerGlobalScope/languagechange_event

Support in all current engines.

Firefox74+Safari4+Chrome4+
Opera11.5+Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS5+Chrome Android?WebView Android37+Samsung Internet?Opera Android?
languagechange
onoffline

WorkerGlobalScope/offline_event

Firefox29+Safari8+ChromeNo
Opera?EdgeNo
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
offline
ononline

WorkerGlobalScope/online_event

Firefox29+Safari8+ChromeNo
Opera?EdgeNo
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
online
onrejectionhandledrejectionhandled
onunhandledrejectionunhandledrejection
10.2.1.2 専用ワーカーおよびDedicatedWorkerGlobalScopeインターフェイス

DedicatedWorkerGlobalScope

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android?Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android11+

DedicatedWorkerGlobalScopeオブジェクトは、内部ポートMessagePort)が関連付けられている。このポートは、ワーカーの作成時に設定されるチャンネルの一部であるが、公開されていない。

dedicatedWorkerGlobal.name

dedicatedWorkerGlobal名前、すなわちWorkerコンストラクターに指定された値を返す。主にデバッグに役立つ。

dedicatedWorkerGlobal.postMessage(message [, transfer ])
dedicatedWorkerGlobal.postMessage(message [, { transfer } ])

messageをクローンしてdedicatedWorkerGlobalに関連付けられたWorkerオブジェクトに送信する。transferは、クローンではなく転送されるオブジェクトのリストとして渡すことができる。

dedicatedWorkerGlobal.close()

dedicatedWorkerGlobalを中止する。

10.2.1.3 共有ワーカーおよびSharedWorkerGlobalScopeインターフェイス

SharedWorkerGlobalScope

Support in all current engines.

Firefox29+Safari16+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS16+Chrome Android?WebView Android37+Samsung Internet?Opera Android11+

共有ワーカーは、各接続のSharedWorkerGlobalScopeオブジェクトの connectイベントを通じてメッセージポートを受け取る。

sharedWorkerGlobal.name

sharedWorkerGlobal名前、すなわちSharedWorkerコンストラクターに指定された値を返す。同じ名前を再利用することによって、複数のSharedWorkerオブジェクトを同じ共有ワーカー(およびSharedWorkerGlobalScope)に対応付けることができる。

sharedWorkerGlobal.close()

sharedWorkerGlobalを中止する。

下記は、SharedWorkerGlobalScopeインターフェイスを実装するすべてのオブジェクトによって、イベントハンドラーIDL属性として、サポートされるイベントハンドラー(および対応するイベントハンドラーイベント型)である:

イベントハンドラーイベントハンドラーイベント型
onconnect

SharedWorkerGlobalScope/connect_event

Support in all current engines.

Firefox29+Safari16+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS16+Chrome Android?WebView Android37+Samsung Internet?Opera Android11+
connect

10.2.2 イベントループ

ワーカーイベントループタスクキューは、タスクとしてイベント、コールバック、およびネットワークアクティビティのみを持つ。

WorkerGlobalScopeオブジェクトにはclosingフラグがあり、最初はfalseであるが、ワーカーが閉じるように要求されたときにtrueに設定できる。

WorkerGlobalScopeclosingフラグがtrueに設定されると、イベントループタスクキューは、それ以降に追加されるタスクを破棄する(キュー上にすでにあるタスクは、特に指定がない限り影響を受けない)。実際には、closingフラグがtrueになると、タイマーの発火が停止し、保留中のすべてのバックグラウンド操作の通知が中断されるなどの動作が行われる。

10.2.3 ランタイムスクリプトエラー

捕捉されなかったランタイムスクリプトエラーがワーカーのスクリプトの1つで発生するたびごとに、そのエラーが以前のスクリプトエラーの処理中に発生したものでなければ、ユーザーエージェントはワーカーのWorkerGlobalScopeオブジェクトに対してそのエラーを報告する

10.2.4 ワーカーの作成

10.2.4.1 WorkerSharedWorkerとの両方に存在するプロパティ

下記は、WorkerWorkerおよびSharedWorkerオブジェクトによってイベントハンドラーIDL属性としてサポートされるイベントハンドラー(および対応するイベントハンドラーイベントタイプ)である:

イベントハンドラーイベントハンドラーイベント型
onerror

ServiceWorker/error_event

Support in all current engines.

Firefox44+Safari11.1+Chrome40+
Opera?Edge79+
Edge (Legacy)17+Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

SharedWorker/error_event

Support in all current engines.

Firefox29+Safari16+Chrome5+
Opera10.6+Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android33+Safari iOS16+Chrome AndroidNoWebView Android?Samsung Internet4.0–5.0Opera Android11–14

Worker/error_event

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android?Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android11+
error
10.2.4.2 専用ワーカーおよびWorkerインターフェイス

Worker

Support in all current engines.

Firefox3.5+Safari4+Chrome2+
Opera10.6+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android?Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android11+
worker = new Worker(scriptURL [, options ])

新しいWorkerオブジェクトを返す。scriptURLがフェッチされてバックグラウンドで実行され、workerが通信チャンネルを表す新しいグローバル環境が作成される。

optionsには、次の値を含めることができる:

worker.terminate()
workerの関連付けられたグローバル環境を中止する。
worker.postMessage(message [, transfer ])
worker.postMessage(message [, { transfer } ])

messageをクローンし、workerのグローバル環境に送信する。transferは、クローンではなく転送されるオブジェクトのリストとして渡すことができる。

postMessage()メソッドの最初の引数は、構造化データにすることができる:

worker.postMessage({opcode: 'activate', device: 1938, parameters: [23, 102]});
10.2.4.3 共有ワーカーおよびSharedWorkerインターフェイス

SharedWorker

Support in all current engines.

Firefox29+Safari16+Chrome5+
Opera10.6+Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android33+Safari iOS16+Chrome AndroidNoWebView Android?Samsung Internet4.0–5.0Opera Android11–14
sharedWorker = new SharedWorker(scriptURL [, name ])

新しいSharedWorkerオブジェクトを返す。scriptURLがバックグラウンドでフェッチおよび実行され、sharedWorkerが通信チャンネルを表す新しいグローバル環境が作成される。nameを使用して、そのグローバル環境のnameを定義できる。

sharedWorker = new SharedWorker(scriptURL [, options ])

新しいSharedWorkerオブジェクトを返す。scriptURLがバックグラウンドでフェッチおよび実行され、sharedWorkerが通信チャンネルを表す新しいグローバル環境が作成される。

optionsには、次の値を含めることができる:

typecredentials、またはextendedLifetimeの値が同じconstructor URLおよびnameの既存の共有ワーカーの値と一致しないoptionsを使用して共有ワーカーを構築しようとすると、戻されたsharedWorkerによってerrorイベントが発火され、既存の共有ワーカーに接続されないことに注意する。

sharedWorker.port

グローバル環境との通信に使用できるsharedWorkerMessagePortオブジェクトを返す。

self.navigator.hardwareConcurrency

ユーザーエージェントが利用可能な論理プロセッサーの数を返す。

10.3 ワーカーが使用可能なAPI

10.3.1 WorkerNavigatorインターフェイス

WorkerNavigator

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera12.1+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android?Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android12.1+

WorkerNavigatorインターフェイスは、次のAPIで構成されるNavigatorインターフェイスのサブセットを実装する:

10.3.2 WorkerLocationインターフェイス

WorkerLocation/toString

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera15+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android?Safari iOS5+Chrome Android?WebView Android37+Samsung Internet?Opera Android14+

WorkerLocation

Support in all current engines.

Firefox3.5+Safari4+Chrome4+
Opera12.1+Edge79+
Edge (Legacy)12+Internet Explorer10+
Firefox Android?Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android12.1+

WorkerLocationインターフェイスはLocationインターフェイスに似ているが、assign()replace()reload()、およびancestorOriginsの各メンバーが存在しない。