Web Workerをローカル環境でも使う

Web Workerはブラウザ上でバックグラウンドスレッドを使う仕組みです。UIの応答性を保ったまま画像処理のように時間のかかる処理を実行できるため、画像比較ツールでは積極的に利用しています。

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

のように簡単にワーカーを生成できますが、問題はChromeのローカル環境(ローカルにあるHTMLファイルをブラウザで開いた状態)です。次のようなエラーになってしまいます(例外が発生)。

Failed to construct 'Worker':
cannot be accessed from origin 'null'.

これを回避する方法を紹介します。なお、ググるChrome--allow-file-access-from-filesという起動オプションを付ければ解決するという情報が見つかりますが、これをユーザーに要求するのはちょっと面倒なので、ここでは特別な手間が要らない方法を示します。

var newWorkerViaBlob = function(relativePath) {
  var baseURL = window.location.href.replace(/\\/g, '/').replace(/\/[^\/]*$/, '/');
  var array = ['importScripts("' + baseURL + relativePath + '");'];
  var blob = new Blob(array, {type: 'text/javascript'});
  var url = window.URL.createObjectURL(blob);
  return new Worker(url);
};
var worker = newWorkerViaBlob('worker.js');

これだけです。これはChromeを含むほとんどのブラウザで動きますが、今度はMSIEのローカル環境でSecurityErrorというエラーになります(例外が発生)。
そこで次のコードが結論です。

var newWorker = function(relativePath) {
  try {
    return newWorkerViaBlob(relativePath);
  } catch (e) {
    return new Worker(relativePath);
  }
};
var worker = newWorker('worker.js');

1つの方法で例外が発生したら別の方法を試す、というわけです。
ちなみに、Firefoxでは new Worker と newWorkerViaBlob のどちらでも動きますが、パスに".."が含まれる場合(../worker.jsとか)は new Worker だとダメなようです。このとき例外は発生せずに静かに無視されてしまう(スクリプトはロードされず、中身のない動かないワーカーが生成される)ので、上に書いた try〜catch の順序を逆にするとうまくいかないので注意が必要です。