ServiceWorker を無効化する

投稿日 2020/03/28 更新日 2022/02/12

はじめに

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 ServiceWorker.
//              Unregister the registered Service Worker.
//              If ServiceWorker was registered, it clears the cache.
//              You can use it as a whitelist by setting @exclude.
//              It is not possible to completely reject registration at the timing of executing the user script.
// @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.2.3
// @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 権限不足エラーの注意書きを追記
// @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==

(function(w) {
  // Reject to register a ServiceWorker
  if ('serviceWorker' in navigator) {
    w.ServiceWorkerContainer.prototype.register = function(scriptURL, options) {
      return new Promise((resolve, reject) => {
        //console.log('Reject to register a ServiceWorker.');
        reject(new Error('Reject to register a ServiceWorker.'));
      });
    };
    // Note: It may be registered before `document-start`.
    // Note: A permission error occurs on the page accessing the register() Promise in Firefox.
    //       This is because the context script Promise is accessed from the page script.
    //       I can't figure out how to get around this in user scripts.
    //       The WebExtensions version works around this.
  }

  // Unregister the registered Service Worker
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.getRegistrations().then((registrations) => {
      if (registrations.length != 0) {
        for (let i=0; i<registrations.length; i++) {
          registrations[i].unregister();
          //console.log('ServiceWorker unregister.');
        }
        caches.keys().then((keys) => {
          Promise.all(keys.map((key) => { caches.delete(key); })).then(() => {
            //console.log('caches delete.');
          });
        });
      }
    });
  }
})(unsafeWindow || window);
// Note: It is not working on Firefox (Tampermonkey / Violetmonkey) because of the following error.
//       Error "Uncaught (in promise) DOMException: The operation is insecure."
//       Use it with Firefox (Greasemonkey) or Chrome (Tampermonkey / Violetmonkey).

拡張機能で 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 (Android) / Safari (iOS) に Reject Service Worker は、未対応です。

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

参考

コメント

asiamoth さんのコメント...

Service Workerディレクトリの肥大に悩んでいたため、「Reject Service Worker」を使わせていただきました。素晴らしい!

ただ一つ難点を言わせていただくと、ホワイト・リストの有効な指定方法が分かりにくかったため、どこかに明記してほしかったです。
「ドメイン名のみ」ですよね? 「https://example.com/*」など色々と試していて見つけました。

(ついでに「Whitelist」の名称も変えた方が無難かと……)

toshi さんのコメント...

ご使用ありがとうございます。以下に質問の回答を記載します。

> 「ドメイン名のみ」ですよね?
ドメイン名のみです。正確には、ドメイン名の改行区切りです。
言い訳になりますが、ブラウザアクションから設定すれば、チェック(ON/OFF)のみで設定できます。ServiceWorkerは、ページと同一ドメイン(サブドメインも不可)上にしか配置できないため、ページのドメイン以外を指定する必要はありません。そのため、ブラウザアクション操作のみで作業を完結できます。現状のオプション画面は、補助機能であり、確認・保存・一括設定用です。(作者の利用目的が既に達成されているうえに、利用者が少なすぎてやるきがでないため、)GUI周りを作り込めていないのが根本の原因です。気が向いたら改善します。

> (ついでに「Whitelist」の名称も変えた方が無難かと……)
Allowlistが一般的になったら変更します。

Unknown さんのコメント...

着眼点がナイスでした。