ServiceWorker を無効化する

はじめに

ServiceWorker と言う、この頃話題の機能がブラウザに追加されました。既存のウェブページではできなかった。スマホアプリのような機能をブラウザで実現できる素晴らしい機能です。ただし、ユーザの事前承認なしに登録できます。

ServiceWorker は、スマホアプリのような機能を無承認で実行できるため、便利な半面でとても危険な機能です。ウイルス対策ソフト(Windows Defenderなど)では、一部の ServiceWorker をウイルスとして検出することがあります。 ServiceWorker は、サイトを閉じても動作し続けることが可能なため、ブラウザの動作を重くする原因にもなります。 ServiceWorker は、キャッシュを大量に保存するため、ディスクやメモリの圧迫につながることもあります。

登録済みの ServiceWorker を確認する

次のアドレスにアクセスすれば確認できます。手動による登録解除も可能です。

// Chrome
chrome://serviceworker-internals

// Edge
edge://serviceworker-internals

// Firefox
about:serviceworkers
about:debugging#workers

※Chrome では、 Google 関連サービスの手動による登録解除ができません。
※次の方法を使用することで、 Chrome の Google 関連サービスを登録解除は可能です。

注意(拡張機能の ServiceWorke を登録解除)

Chrome 拡張機能の Manifest V3 対応により、拡張機能のバックグラウンドが Service Worker になりました。拡張機能の Service Worker も上記のページに表示されます。停止や登録解除が可能です。

拡張機能の ServiceWorker を登録解除してしまうと、拡張機能が動作しなくなる可能性があります。次のアドレスから始まる ServiceWorker を登録解除しないでください。

chrome-extension://

ユーザスクリプトで ServiceWorker の登録を拒否する

ServiceWorker の登録関数を置き換えてしまうことで新規登録を拒否します。また、 ServiceWorker は登録解除が可能です。なので、登録済みの ServiceWorker を登録解除します。

仕様

  • ServiceWorker の登録関数を置き換えて新規登録不可にする
  • ServiceWorker が登録済みならば登録解除する
  • ServiceWorker が登録済みならばキャッシュもクリアする
  • @excludeを設定することでホワイトリストとして利用できる
  • @includeの設定を変更することでブラックリストとしても利用できる
    • // @include https://*/*」を指定のサイトに変更する
  • ユーザスクリプトの実行タイミングでは、完全な登録拒否はできない
    • ページへのスクリプト挿入が少し遅れるため
    • 一時的にServiceWorkerを登録されますが、直後に登録解除します
      • ただし、登録解除を妨害された場合、解除できないことがあります
    • 拡張機能版は、ユーザスクリプト版に比べてこの部分がより強力に動作します
      • ただし、拡張機能版は関数の置き換えが確実に発生するため、関数置き換えをページスクリプトに検出されて異常動作を取られると対処できなくなります。
      • なので、異常動作するサイトを含めてホワイトリスト運用する場合、ユーザースクリプト版の方が優れています。

ユーザスクリプトのコード

RejectServiceWorker.user.js// ==UserScript==
// @name        RejectServiceWorker
// @description Reject to register a service worker.
//              Reject to register new service worker by overwriting the register function.
//              If service worker was registered, it unregister the registered service worker.
//              If service worker was registered, it clears the cache.
//              You can use it as a whitelist by setting @exclude.
//              As long as user script execution timing is used, complete reject cannot be achieved.
// @note        ↓↓↓ Add target page URL ↓↓↓
// @include     https://*/*
// @exclude     https://example.com/*
// @note        ↑↑↑ Add target page URL ↑↑↑
// @author      toshi (https://github.com/k08045kk)
// @license     MIT License | https://opensource.org/licenses/MIT
// @version     0.3.0
// @since       0.1.0 - 20200328 - 初版
// @since       0.1.1 - 20200415 - 修正
// @since       0.2.0 - 20200926 - Greasemonkey対応(unsafeWindow経由でwindowのオブジェクトを書き換え)
// @since       0.2.1 - 20210125 - RejectServiceWorkers.user.js → RejectServiceWorker.user.js
// @since       0.2.2 - 20210828 - comment メタデータの見直し
// @since       0.2.3 - 20211013 - comment 権限不足エラーの注意書きを追記
// @since       0.3.0 - 20240326 - Proxy 方式に対応(他、動作不可問題修正)
// @see         https://github.com/k08045kk/UserScripts
// @see         https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html
// @run-at      document-start
// @grant       unsafeWindow
// ==/UserScript==

;(async function(win) {
  let   isExec = false;
  try { isExec = !!navigator.serviceWorker; } catch {}
  if (  isExec === false) {
    // http environmental measures
    return;
  }

  // Reject to register a service worker
  const register = new Proxy(win.ServiceWorkerContainer.prototype.register, {
    apply: () => win.Promise.reject(new win.Error('Reject to register a service worker.')),
  });
  try {
    exportFunction(register, 
                   win.ServiceWorkerContainer.prototype, 
                   {defineAs: 'register'});
  } catch {
    win.ServiceWorkerContainer.prototype.register = register;
  }

  // Unregister the registered service worker
  const registrations = await navigator.serviceWorker.getRegistrations();
  if (registrations.length) {
    // Unregister service worker
    const unregisterPromises = registrations.map(registration => registration.unregister());
    await Promise.all(unregisterPromises);

    // Delete all cache storage
    const keys = await caches.keys();
    const cacheDeletePromises = keys.map(key => caches.delete(key));
    await Promise.all(cacheDeletePromises);
  }
})(unsafeWindow || window);

拡張機能で ServiceWorker をブロック(または、管理)する

次のような拡張機能が既に作成されています。まだ、出始めの機能であるため、探せばもっと良いものがあるかもしれません。

宣伝(筆者作成の拡張機能)

上記ユーザスクリプトと同じような動作をします。拡張機能は、ユーザスクリプトより実行タイミングが早いため、より強力に動作します。

他作者による類似の拡張機能

ブラウザ設定で ServiceWorker を無効化する

Chrome(無効化できない)

Chrome で ServiceWorker を無効にする方法は発見できませんでした。次の記事で紹介されている方法では無効化できないことを確認しています。記事内でも無効化できなかったと記載されています。そのため、現状 Chrome で ServiceWorker を無効化する場合、上記ユーザスクリプトや拡張機能を利用するのが懸命です。

Firefox

Firefox は、about:configからdom.serviceWorkers.enabledfalseとすることで ServiceWorker を無効化できます。ただし、既に登録済みの ServiceWorker は動作するため、事前にabout:serviceworkersからすべての ServiceWorker を登録解除する必要があります。

※設定の反映には、 Firefox の再起動が必要になります。

備考(スクリプト無効で無効化する)

ServiceWorker の登録には、 JavaScript による登録作業が必要となります。そのため、 JavaScript を無効化することで ServiceWorker の登録を拒否できます。

そのため、 NoScript 等による JavaScript の無効化で ServiceWorker の登録を拒否できます。

※登録済みの場合、 ServiceWorker は動作し続けます。

備考(CSP を利用したワーカーの無効化)

コンテンツセキュリティポリシー (CSP) - HTTP | MDN

Content-Security-Policy: worker-src 'none'
<meta http-equiv=Content-Security-Policy content="worker-src 'none'">

上記のような Content-Security-Policy (CSP) のヘッダーまたはメタタグを追加することで、Worker / SharedWorker / ServiceWorker のソースコードを無効なコードとしてブロックできます。ワーカーのソースコードとして、読み込まれません。

※ CSP では、 Worker/SharedWorker もブロックします。
  CSP では、 ServiceWorker だけをブロックできません。
 Worker/SharedWorker のブロックでページ動作が不安定になる可能性があります。
※登録済みの場合、 ServiceWorker は動作し続けます。
※ dataURL もブロックします。

例:uBlock Origin のフィルター

! 一部ページのワーカー登録を拒否する
||example.com^$csp=worker-src 'none'

! すべてのページのワーカー登録を拒否する
*$csp=worker-src 'none'
! 一部ページのワーカー登録拒否を打ち消す
@@||example.com^$csp=worker-src 'none'

簡易説明

  • !: コメント行
  • ||example.com^: ドメイン単位の指定(サブドメインも含む)
  • *: ワイルドカード(すべてのドメインを対象にする)
  • @@: フィルターの効果を打ち消す(例外ルール)
  • $csp=: Content-Security-Policy のメタタグを追加する
  • worker-src 'none': worker-src ディレクティブに空のセットを設定

※詳しくは、別記事を参照:uBlock Origin フィルター覚書(書き方・サンプル)

例:ユーザースクリプト

BlockingWorkerWithCSP.user.js// ==UserScript==
// @name        BlockingWorkerWithCSP
// @description Use CSP to block Worker.
// @note        ↓↓↓ Add target page URL ↓↓↓
// @include     https://example.com/*
// @note        ↑↑↑ Add target page URL ↑↑↑
// @author      toshi (https://github.com/k08045kk)
// @license     MIT License | https://opensource.org/licenses/MIT
// @version     0.1.0
// @since       0.1.0 - 20210828 - 初版
// @see         https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html
// @run-at      document-end
// @grant       none
// ==/UserScript==

(function() {
  // Add CSP meta tag
  var meta = document.createElement('meta');
  meta.setAttribute('http-equiv', 'Content-Security-Policy');
  meta.setAttribute('content', "worker-src 'none'");
  document.head.appendChild(meta);
  // Note: This user script will block all Worker, not just ServiceWorker.
  // Note: It is not possible to stop a Worker that is already running.
})();

簡易説明

  • CSPのメタタグを対象ページに挿入する
  • CSPのメタタグにより、 Worker のソースコードの読み込みをブロックする
    • メタタグ挿入前に ServiceWorker を登録された場合、どうなるか未検証
  • ServiceWorker だけでなく、 Worker/SharedWorker もブロックする
  • 既に実行中の ServiceWorker は停止できません

※uBlock Origin も内部的にはユーザースクリプトと類似処理になります

備考(Cache のみを無効化する)

ServiceWorker を動作させつつ Cache のみを無効化する方法について考えます。

結論から言えば、現状では不可能です。(ServiceWorker を無効化する方が現実的です)

一見すると ServiceWorker の登録関数同様に Cache の関数も上書きすることで実現可能なように思えます。ですが、 ServiceWorker 内部の Cache を上書きする方法がありません。埋め込みスクリプト・ユーザースクリプト・拡張機能は、 ServiceWorker のプロセスを上書きできません。

現実的な方法は、 Cache を無効化するのではなく、 Cache を即座に削除することです。ですが、storage イベントのようなキャッシュの変更を通知するイベントは準備されていません。なので、ページの読み込み・アンロード毎にキャッシュを全削除する方法が現実的です。

ただし、 ServiceWorker 自身でキャッシュしたデータが存在する前提で動作した場合、問題となる可能性があります。キャッシュからの取得失敗時にフェッチ処理を実施する柔軟性のある ServiceWorker であれば問題ありませんが、 ServiceWorker の設計次第ではページが動作しなくなります。

コード例(キャッシュの自動全削除)

DeleteCache.user.js// ==UserScript==
// @name        DeleteCache
// @description Delete all caches.
// @note        ↓↓↓ Add target page URL ↓↓↓
// @include     https://example.com/*
// @note        ↑↑↑ Add target page URL ↑↑↑
// @author      toshi (https://github.com/k08045kk)
// @license     MIT License | https://opensource.org/licenses/MIT
// @version     0.1
// @since       0.1 - 20211231 - 初版
// @see         https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html
// @grant       none
// ==/UserScript==

(() => {
  const deleteCache = async () => {
    for (let key of await caches.keys()) {
      await caches.delete(key);
    }
  };
  window.requestIdleCallback(deleteCache);
  window.addEventListener('pagehide', deleteCache, {capture:true, passive:true});
})();

備考(モバイル環境で ServiceWorker を無効化する)

これまでの方法から、拡張機能がインストールできればモバイル環境でも ServiceWorker を無効化できます。ただし、モバイル環境で拡張機能がインストールできるのは一部のブラウザのみです。次に示すブラウザで拡張機能をインストールできます。

  • Kiwi Browser (Android)
  • Firefox (Android)
  • Safari (iOS)

※ ServiceWorker を無効化した場合、A2HS (Add to Home screen) を使用できません。
※ Firefox (Android) は、おすすめの拡張機能のみインストールできます。
  uBlock Origin が利用できるため、上記 CSP の方法が使用できます。
  Firefox 121 からおすすめ以外の拡張機能もインストールが可能になりました。
  Reject Service Worker も Android Firefox に対応済みです。
Firefox (Android) / Safari (iOS) に Reject Service Worker は、未対応です。

備考(翻訳記事を追加しました)

参考