Service Worker

Takami Torao W3C Service Workers Nightly Chrome 61 #ServiceWorker
  • このエントリーをはてなブックマークに追加

特徴

サービスワーカー (service worker) はクライアント (ブラウザ) のバックグランドで実行される JavaScript 処理。HTML を操作するユーザインターフェースのスクリプトとは別のコンテキストで動作する。現在のところブラウザでのバックグラウンド同期、キャッシュ制御ユーザ通知などを目的とした仕様策定と実装が進められている。

Service Worker は Web Worker の一種として W3C と WHATWG によって定義されている。

Service Worker はブラウザからのイベント通知によって発動する。このため Service Worker の JavaScript ファイルは以下のようなボイラープレートとなるだろう。

// service-worker.js
(function(self){
  self.addEventListener("install", function(e){ ... });
  self.addEventListener("activate", function(e){ ... });
  self.addEventListener("fetch", function(e){ ... });
  self.addEventListener("message", function(e){ ... });
  ...
})(this);

Service Worker はユーザインターフェース (HTML や通常の JavaScript) からブラウザへ登録することができる。

navigator.serviceWorker.register("/service-worker.js").then(function(registration){
  // 成功時の処理
}).catch(function(reason){
  // 失敗時の処理
});

利用中のブラウザにどのような Service Worker が登録されているかは、それぞれのブラウザで表1の URL を入力することで確認することができる。

表1: 対応ブラウザでの SW 確認方法
Chrome chrome://serviceworker-internals
Firefox about:debugging#workers

Service Worker のイベントは ServiceWorkerGlobalScope で実行される。これはユーザインターフェースを実行するメインスレッドとは独立したスレッド空間であり DOM に直接アクセスすることはできない

セキュリティ

Service Worker のインストールとキャッシュ制御は特定のパーミッションを必要とせず Web ページを表示しただけでバックグラウンドで行われる。

Service Worker が中間者攻撃に利用されると通信内容の盗聴、改ざん、セッションの乗っ取りが可能となり利用者を致命的なセキュリティ脆弱に晒すことになる。このため https で暗号化されたサイトの (もしくは localhost でアクセスする) Web ページ上のみで登録することができる。またドメインの異なる Service Worker を登録することはできない。

スコープ

Service Worker は登録時にスコープとして指定したパス以下のリクエストのみを捉えることができる。

スコープは register() 第2引数で {scope:"/foo/bar/"} のように指定することができる。デフォルトのスコープは Service Worker の JavaScript が置かれているディレクトリである。 

上位ディレクトリや別のディレクトリは Service Worker 設置者の権限外である可能性から、スコープは Service Worker を配置したパスと前方一致する必要がある (言い換えると Service Worker の最大影響範囲は Service Worker が置かれているディレクトリである)。ただし例外として Service-Worker-Allowed レスポンスヘッダ付きで返された Service Worker の場合はその一覧から使用することができる。

ライフサイクル

Service Worker のセットアップは以下のステップで行われる。

Installing

Install フェーズの処理は Web アプリの動作に必要なファイルをデバイス側にキャッシュし IndexedDB をセットアップする。

Web ページ上で navigator.serviceWorker.register() が実行されると Service Worker の JavaScript ファイルがダウンロードされ Installing 状態となる。Service Worker の install イベントが正常終了すれば状態は Installed となるが、いずれかのステップでエラーが発生した場合は Redundant となりそれ以降に参照されることはない。

Installed/Activating/Waiting

Activate フェーズでは以前のバージョンで使用されていた古いデータやキャッシュをクリーンナップする。

インストールが完了すると直ちに activating となり activate イベントが実行される。ただし、W以前のバージョンの Service Worker がまだ active な状態で残っていると、それを使用している Web ページが全て開放されるまで waiting 状態となる。

Activated

Activated フェーズに入ると fetchmessage のイベント処理を行うことができる。

新しくインストールされた Service Worker は register() の後に開かれた Web ページに対してのみ有効になる。既に読み込まれているページが存在する場合は再読込を行う必要がある。

Redundant
インストールに失敗したり最新版に置き換えられた Service Worker は Redundant 状態となり破棄される。これは不可逆的な状態である。

既にインストールされている Service Worker ファイルを Web 上で更新しても直ちに反映はされない。この Service Worker のスコープの Web ページが全て閉じられるかスコープ外になるまでステイする。Service Worker が管理する Web ページがなくなった時に Redundant へ移行する。

プログラミング

インストール/アンインストール

Service Worker のインストールとアンインストール、それにコンテンツ要求での呼び出しについて記述する。Chrome のディベロッパーツールのコンソールを表示し (Ctrl+Shift+ i )、以下のボタンを押してログが出力されることを確認する。

HTML 上の基本的な部分は以下のように記述している。

<button id="demo1_install" class="btn btn-primary">INSTALL</button>
<button id="demo1_uninstall" class="btn btn-default">UNINSTALL</button>
<script src="demo/1/myapp.js"></script>

myapp.js は Service Worker の登録処理を行う。

(function(){
  var scope = "/mox/web/service-worker/demo/1/";
  $("#demo1_install").click(function(){
    // サービスワーカーのインストール
    if("serviceWorker" in navigator){
        navigator.serviceWorker.register("demo/1/mysw.js", {scope:scope}).then(function(reg){
          console.log("[APP] Service Worker successfully registered", reg);
        }).catch(function(error){
          alert("Registration failed: " + error);
        });
    } else {
      alert("OOPS! Service Worker is not supported on your browser.")
    }
  });
  $("#demo1_uninstall").click(function(){
    if("serviceWorker" in navigator){
      // サービスワーカーのアンインストール
      navigator.serviceWorker.getRegistration(scope).then(function(reg){
        if(reg != undefined){
          reg.unregister();
          console.log("[APP] Unregister Service Worker", reg);
        } else {
          console.log("[APP] Service Worker is not registered.");
        }
      });
    }
  })
  $("#demo1_reload").click(function(){
    $("#myapp")[0].contentDocument.location.reload(true);
  })
})();

navigator.serviceWorker は Service Worker の登録や解除をおこなうための ServiceWorkerContainer のオブジェクト。Service Worker をサポートしていないブラウザへの対応として参照する前に定義を確認すべきである。

scope は登録する Service Worker が関与するコンテンツ (HTML) のパスを示す。スコープ以下のコンテンツからファイル取得要求が発生したときに Service Worker の fetch イベントが発動する (スコープ外のコンテンツからスコープ内のコンテンツを取得しても発動しないことに注意)。デフォルトのスコープは登録を行った HTML のディレクトリを示す。

Service Worker の登録に成功すると ServiceWorkerRegistration 付きでコールバックされる。失敗した場合は catch に指定した関数へ理由付きでコールバックされる。

ブラウザに登録される Service Worker mysw.js は以下の通り。

(function(self){
  self.addEventListener("install", function(e){
    console.log("[INSTALL] Service Worker successfully installed", e);
  });
  self.addEventListener("activate", function(e){
    console.log("[ACTIVE] Service Worker successfully activated", e);
  });
  self.addEventListener("fetch", function(e){
    console.log("[FETCH] fetch something from Service Worker: " + e.request.url, e);
  });
})(this);

参考文献