Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Manifest V3 Service Workers: Why Your Chrome Extension Keeps Losing State

2026年10月29日に「Dev.to」が公開したITニュース「Manifest V3 Service Workers: Why Your Chrome Extension Keeps Losing State」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Manifest V3拡張機能で状態が消えるのは、Service Workerがアイドル時に停止し、メモリ上のデータがリセットされるため。対策は、リスナーを起動時に登録し、永続データは`chrome.storage`に保存、定期処理には`chrome.alarms`を使うことだ。

ITニュース解説

システムエンジニアを目指す初心者がChrome拡張機能開発で直面する可能性のある重要な変更点について解説する。Chrome拡張機能は、ブラウザの機能を拡張し、ユーザー体験を向上させるためのプログラムだ。これまで「Manifest V2」という形式で開発されてきたが、セキュリティやパフォーマンス向上のため、Google Chromeは「Manifest V3」という新しい形式への移行を進めている。この移行に伴い、拡張機能の内部動作に大きな変更があり、特に「状態が失われる」という問題が発生しやすくなっている。

これまでのManifest V2では、「バックグラウンドページ」と呼ばれる常に実行されるプロセスで、拡張機能の主要なロジックを処理していた。しかし、Manifest V3では、このバックグラウンドページが「サービスワーカー」というものに置き換わった。サービスワーカーは、常に動いているわけではなく、特定のイベント(例えば、ユーザーが拡張機能のアイコンをクリックした、ウェブページからメッセージが届いたなど)が発生したときにだけ起動し、イベント処理が完了して一定時間(30秒間)何もしないと自動的に終了する。

この自動終了の仕組みが、拡張機能が状態を失う主な原因となる。サービスワーカーが終了すると、そのサービスワーカーのメモリ上に保持されていた変数(グローバル変数やモジュールスコープの変数)はすべてリセットされ、次回の起動時にはまっさらな状態から始まる。まるで幽霊のように、ある時は動くのに、しばらくするとデータが消えてしまうように見える現象は、実はこのサービスワーカーのライフサイクルによるものだ。

サービスワーカーのライフサイクルにはいくつか厳密なルールがある。まず、30秒間アイドル状態(イベントを受信したりAPIを呼び出したりしない状態)が続くと、ワーカーは終了する。また、単一のイベント処理やAPI呼び出しが5分を超えて実行されると、強制的に終了させられる。さらに、fetch()という外部リソースを取得する処理で、30秒以内に応答がない場合も強制終了の対象となる。これらの挙動は、拡張機能が常にリソースを消費し続けないようにするための設計思想に基づいている。

そのため、開発者はこのライフサイクルを考慮してコードを記述する必要がある。サービスワーカーがいつでも終了する可能性があるという前提で、拡張機能の状態管理を行うことが重要だ。

まず、最も重要なのは「イベントリスナーの登録」だ。拡張機能は、ユーザーの操作やブラウザのイベントに反応して動作する。これらのイベントを監視するための「リスナー」は、サービスワーカーが起動した直後、スクリプトの評価(上から順にコードが実行されること)が完了する前にすべて登録されていなければならない。もしリスナーの登録が、非同期処理(例えば、ストレージから設定を読み込む処理など)の完了を待ってから行われると、ワーカーが起動した最初のイベントを受け取ることができず、拡張機能が反応しないように見えてしまう。この問題を解決するには、リスナーはコードのトップレベルで(非同期処理を待たずに)即座に登録し、そのリスナーの処理内で必要な設定やデータを非同期で読み込むようにする。

次に、「状態の永続化」についてだ。サービスワーカーの変数は揮発性であるため、永続的に保持したいデータはブラウザのストレージ機能を利用する必要がある。Chrome拡張機能では、いくつかのストレージオプションが提供されている。例えば、ブラウザを閉じるとデータが消える一時的なセッションデータを保存するchrome.storage.session、ブラウザの再起動や拡張機能の更新後も残る永続的なデータを保存するchrome.storage.local、ユーザーのChromeアカウントと同期され、複数のデバイスで共有できる設定を保存するchrome.storage.syncなどだ。大規模な構造化データの場合はIndexedDB、ウェブから取得した画像などのキャッシュにはCacheStorageを使う。これらのストレージを適切に使い分け、モジュールスコープの変数はあくまで一時的なキャッシュとして扱い、真のデータ源とはしない。また、chrome.storageへの読み書きは非同期で行われるため、同時に複数のイベントが発生した場合のデータの競合にも注意が必要だ。

「タイマー処理」もManifest V3で大きな変更点の一つだ。JavaScript標準のsetTimeoutやsetIntervalは、ワーカーが終了すると一緒に停止してしまうため、アイドル期間(30秒)を超えるようなタイマーは正常に機能しない。代わりに、ブラウザ自体が管理するchrome.alarms APIを使用する。chrome.alarmsで設定されたアラームは、ワーカーがスリープ状態でも指定された時間にブラウザがワーカーを起動させ、イベントを配信してくれる。これは、定期的なデータ同期など、バックグラウンドで時間を管理する必要がある処理に非常に有効だ。アラームは拡張機能のインストール時や更新時に作成し、onAlarmイベントリスナーで処理を行うのが一般的だ。

「長時間の処理」にも対応が必要だ。5分を超える処理は強制終了されるため、大きなデータ処理やファイルアップロードのような長時間かかるタスクは、一度にすべて実行しようとせず、小さなバッチに分割して処理する設計が求められる。例えば、処理待ちの項目をストレージに保存しておき、定期的なアラームでワーカーを起動させ、毎回一定数の項目だけを処理し、残りは次のアラームに任せるようなキュー方式が有効だ。これにより、途中でワーカーが終了しても、それまでに処理した分は保持され、次の起動時に中断したところから再開できる。また、ウェブからのデータ取得(fetch())が30秒を超える場合も同様に、拡張機能が直接完了を待つのではなく、サーバー側で長時間処理を行い、その結果を後でポーリングで確認するなどの工夫が必要となる。

「拡張機能の通信」にもライフサイクルの影響がある。コンテンツスクリプト(ウェブページに挿入されるスクリプト)やポップアップからサービスワーカーへの接続(chrome.runtime.connect())は、接続が開いている間はワーカーのライフタイムを維持する。しかし、ポップアップが閉じたり、コンテンツスクリプトのあるタブが閉じたり移動したりすると、この接続は切断され、ワーカーは再びアイドル状態に戻り、30秒で終了する。そのため、接続が切断された場合の処理(onDisconnectハンドラ)を実装し、必要に応じて再接続を試みるロジックを組み込むことが重要だ。また、WebSocket接続はChrome 116以降でワーカーのライフタイムを延長できるようになったが、これも永続的なものではないため、再接続を考慮した堅牢な設計が不可欠だ。

最後に、「DOM(Document Object Model)操作」についてだ。サービスワーカーはウェブページのようなDOM構造を持たないため、HTMLのパース(解析)やオーディオ再生、クリップボードへのアクセスなど、DOMを必要とする処理は直接実行できない。このような場合は、chrome.offscreen APIを使って「オフスクリーン文書」と呼ばれる隠されたウェブページをプログラムで作成し、その中でDOM操作を行う。オフスクリーン文書は一時的な用途に限り、作業が完了したら閉じることで、不要なリソース消費を防ぐ。

これらの変更は、開発者が拡張機能をデバッグする際にも影響を及ぼす。ChromeのDevTools(開発者ツール)をサービスワーカーにアタッチしている間は、Chromeはワーカーを終了させないため、上記のようなライフサイクルに起因するバグは再現しない。このため、「自分の環境では問題なく動く」と誤解しやすい。正確なテストを行うには、DevToolsを閉じた状態で、ワーカーがスリープしている状態から機能を使用し、期待通りに動作するかを確認する必要がある。

Manifest V3への移行は、セキュリティとパフォーマンスを向上させるための重要なステップだ。最初は戸惑うかもしれないが、サービスワーカーのライフサイクルを理解し、状態の永続化、適切なイベントリスナーの登録、chrome.alarmsの活用、長時間の処理の分割、堅牢な通信設計、オフスクリーン文書の利用といった原則に従うことで、安定した拡張機能を開発できるようになる。これらのポイントを押さえることが、Manifest V3での開発を成功させるための鍵となる。

関連コンテンツ

関連IT用語