Chromeの拡張機能をManifest V3で開発していて困ったことがありました。
「1分おきに特定のwebページにアクセスして状態を確認する」という処理をService workerにやらせようとしたのですが、1分ごとに処理が走るはずが、なぜかループが止まっていました。
そこでService Workerの状態を確認してみると、「Service Worker(無効)」の文字が…。

観察してみると、どうやらService Workerがループ開始から30秒で自動停止してしまっているようです。
.rchrome
untime.sendMessage
でService Workerにメッセージを送るとService Workerが起動するのですが、時間のかかる処理を走らせていると無効になってしまいます。
再びService workerに対してsendMessageするまで、Service Workerは眠りについてしまうんですね。
原因
色々調べてみると、どうやらこの現象はManifest V3特有の現象で、意図的にService Workerが自動停止するようになっていることがわかりました。
こちらのChromnium開発フォーラムで、以下のように回答されています。
I’ve seen some Chromium developers saying it’s WAI, but let me disagree: this behavior is WAI only for the web because users visit a lot of sites unpredictably and may open a lot of tabs so the aggressive shutdown of workers is the expected and the correct behavior by default.
However, as for the extensions, there’ll be just a few (or maybe a few dozen in rare cases). Extensions are installed knowingly to extend the browser itself so the browser shouldn’t be as aggressive towards them. Maybe instead of “shoot-on-sight” strategy Chrome could switch to informing the users e.g. show the extension’s process memory and CPU usage in the new extension menu.
https://bugs.chromium.org/p/chromium/issues/detail?id=1152255
要するに、メモリを食う恐れがあるから意図的にService Workerを停止させているんですね。
もし複数のアプリケーションが同時にバックグラウンドでService Workerを動作させていたなら、それだけ多くのメモリを消費してしまうので、たしかにこの仕様は仕方がないことなのかもしれません。
とはいえManifest V2ではService Workerを永続化することができていたため、この変更には多くのユーザーが不満を漏らしているのが現状です。
今後何かしらアップデートがあるかも?
対処法
開発側が意図的にService Workerを停止しているのであれば、そもそもService Workerを永続化しなくても良い実装にするのが一番ですよね。
とはいえ、ユースケースによってはやはりService Workerを永続化したい場合があると思います。
例えば私の場合は、「1分おきに特定のwebページにアクセスして状態を確認する」という処理をService Workerにやらせたい。
拡張機能のポップアップやオプションページでも処理を走らせることはできますが、タブなどを閉じると動作が停止してしまうので、やはりバックグラウンドで動作するService Workerにやらせたいんですよね。
対処法を考えてみました。
Manifest V2に戻す
得策とは言えませんが、Manifest V2ならService Workerを永続化するオプションがあったので、それだけで解決します。
しかしManifest V2はすでに非推奨で、いつ使用不可になるのかわからないので、V3の利用を継続すべきでしょう。
Offscreenから空のメッセージを送り続ける
Service Workerは、
でメッセージを受け取ると、停止していたとしても再び動作を再開します。
.rchrome
untime.sendMessage
つまり、停止するよりも早い間隔で定期的に
.rchrome
untime.sendMessage
を呼び続ければ、停止を防ぐことができます。
そのメッセージを送り続ける定期処理を、offscreenにやってもらえば良いのです。
※offscreenは、バックグラウンドでレンダリングされる画面です。通常のページと同じようにhtmlベースで動きますが、通常のページのようにユーザーから操作を行うことはできません。

1. まずoffscreen.html
を準備します。
<html lang="en">
<script src="offscreen.js"></script>
</html>
(スクリプトを動作させたいだけなので、これだけで十分)
2. offscreen.js
// 25秒ごとにchrome.runtime.sendMessageを呼ぶ
setInterval(() => {
chrome.runtime.sendMessage("何でも良いメッセージ");
}, 25 * 1000);
offscreenから25秒ごとにchrome
.runtime.sendMessage
を呼びます。
(30秒で停止してしまうので、それより短い25秒)
ちなみにもしoffscreenをReactで実装するならこんな感じ↓
// offscreen.tsx
export function Offscreen() {
React.useEffect(() => {
keepServiceWorker();
}, []);
return null;
};
async function keepServiceWorkerAlive() {
setInterval(() => {
chrome.runtime.sendMessage("何でも良いメッセージ");
}, 25 * 1000);
}
3. background.js
(Service Worker)
async setUpOffscreen() {
await chrome.offscreen.createDocument({
url: browser.runtime.getURL('offscreen.html'),
reasons: ['BLOBS'],
justification: 'To keep service worker',
});
}
// 拡張機能インストール時にoffscreen作成
chrome.runtime.onInstalled.addListener(() => {
setUpOffscreen();
});
// ブラウザ起動時にoffscreen作成
chrome.runtime.onStartup.addListener(() => {
setUpOffscreen();
});
これで拡張機能インストール時やブラウザ起動時にoffscreenが作成され、そのoffscreenが25秒ごとにsendMessage
を呼び続けるので、Service Workerが停止することはありません。
コメント