先日、MV3拡張の実践ポイントという記事で「service workerは一定時間で寝る」という仕様の話を書きました。あの記事はそこで終わっているのですが、実際に開発していると次にぶつかる壁があります。その「寝ている」状態をどうやって観察するかです。
私のQR Send拡張でも、ポップアップを開いた瞬間に最新のデータが出てこないという不具合を踏みました。原因を追ううちに、MV3のservice workerを観察しようとすること自体がけっこう厄介だと気付いたので、今回はその話を書きます。
DevToolsを開くと寝ない、というジレンマ
chrome拡張のservice workerをデバッグするとき、まず誰もが使うのが chrome://extensions の「Service Worker」リンクです。クリックするとDevToolsが立ち上がり、consoleやElementsパネルで中身を見られます。ここまでは普通のデバッグ体験ですよね。
ところが、ここに落とし穴があります。DevToolsを開いている間、service workerは停止しません。MV3のservice workerは一定時間アイドルが続くと終了する仕様ですが、DevToolsで接続している間はアクティブな状態を保ち続けるからです。
つまり、「寝てから起きる」という経路で起きるバグは、DevToolsを開いた瞬間に再現しなくなります。私が踏んだポップアップの不具合もまさにこれで、「あれ、さっきは直ったように見えたのにデプロイしたらまた出る」を何度か繰り返しました。DevToolsを開いて確認している間は正常に動くので、原因がわからず、しばらく迷宮入りしていました。
これ、実際にハマってみないと気付きにくい罠だと思います。観察するための道具(DevTools)が、観察対象の状態(寝ている worker)を変えてしまうわけです。
じゃあどうやって「寝てから起きる」バグを再現するか
DevToolsを閉じた状態で待ち、意図的にservice workerが終了するのを待ってから操作する、という地道な方法しか今のところ見つけていません。chrome://serviceworker-internals でも稼働状況は確認できますが、これも見ている間は起こしっぱなしになりがちです。
私は次のような手順で確認しています。同じ症状に心当たりがある方は、まずこの4手順を試してみてください。
- DevToolsを閉じる
- 拡張を操作せず数十秒〜1分ほど放置する(アイドルタイムアウトは公式ドキュメントでも明確な秒数は示されていません)
- そのままポップアップを開いて挙動を見る
- 再現したらそこで初めてDevToolsを開いてconsoleログを見る
手順3と手順4の間で状態が変わってしまうこともあるので、私は再現直後にログをできるだけ多く仕込んでおくようにしています。地味ですが、これが一番確実でした。
offscreen documentがInspect viewsに出てこない、という報告
service workerだけでなく、offscreen documentのデバッグでも似たような報告を見かけます。chrome://extensions の「Inspect views」に一覧が出てこない、あるいは開けてもElementsパネルが空でconsoleが動かない、というものです。
これはchromium-extensionsグループの複数のスレッドで報告されているもので、私自身が完全に同じ症状を体系的に再現できているわけではありません。「〜という報告がある」という紹介にとどめますが、offscreen documentは通常のタブと違って表示上の存在感が薄いため、DevTools側での扱いが不安定になりやすいというのは体感としてもわかります。
offscreen documentは1つの拡張につき1つしか作れない、という制約もあります(すでにdocumentが存在する状態でchrome.offscreen.createDocument()を呼ぶとエラーになります)。デバッグ中に「あれ、documentが作れない」となったら、まず既存のdocumentが残っていないかを疑ってください。この1個しか作れないという制約、地味ですが知らないとハマるので注意してください。私は次のように、作成前に有無を確認してから作るようにしています。これ、意外と使えます。
const existing = await chrome.runtime.getContexts({
contextTypes: ["OFFSCREEN_DOCUMENT"],
});
if (existing.length === 0) {
await chrome.offscreen.createDocument({
url: "offscreen.html",
reasons: ["CLIPBOARD"],
justification: "クリップボード操作のため",
});
}
もう一つ、chrome.runtime.getContexts() を使うとservice worker・popup・offscreen documentなど、拡張が持っている実行コンテキストの一覧を取得できます。
const contexts = await chrome.runtime.getContexts({});
console.log(contexts);
// [{ contextType: "OFFSCREEN_DOCUMENT", documentUrl: "...", ... }, ...]
ここで空配列が返ってくる、という報告もあります。タイミングによってはoffscreen documentがまだ生成中だったり、逆に既に閉じられていたりするだけの可能性もあるので、空配列が返ってきたら「本当にoffscreenが無いのか」「作成タイミングとズレていないか」の両方を疑うようにしています。DevToolsのInspect viewsに出てこない問題と合わせて考えると、目視でもAPIでも、offscreen documentの状態は素直には見えてくれないという前提で設計した方が精神衛生上よさそうです。
offscreen documentではruntime API以外のchrome.*が使えない
デバッグのしにくさとは別に、そもそもoffscreen documentの設計上の制約も一緒に押さえておいた方がいいです。公式ドキュメントによると、offscreen document内で使えるのはruntime系のAPIだけで、storageやtabsのような他のchrome.* APIは基本的に使えません。
これを知らずに「offscreen documentからstorageに直接アクセスすればいいのでは」というコードを書くと、当然動きません。offscreen documentは音声・DOM操作・クリップボード処理などservice workerだけでは完結しない処理を担当させる場所であって、拡張全体のロジックの中心に据える場所ではない、という位置づけです。
設計としては、offscreen documentは「処理はするがデータは持たない」役に徹させて、必要なやり取りはすべてchrome.runtime.sendMessageでservice worker側に投げる形にするのがシンプルでした。
// offscreen.js側
chrome.runtime.sendMessage({ type: "OFFSCREEN_RESULT", payload: result });
// service worker側
chrome.runtime.onMessage.addListener((message) => {
if (message.type === "OFFSCREEN_RESULT") {
// storageへの書き込みなど、他のAPIが必要な処理はここでやる
}
});
runtimeに寄せておけば、Inspect viewsで見えづらい問題があっても、メッセージのやり取り自体はconsoleログで追いやすくなります。デバッグのしにくさを見越して設計を変える、というのは少し本末転倒な気もしますが、実務的には効果がありました。
QR Sendで踏んだ「ポップアップを開いた瞬間に最新が出ない」の正体
冒頭で触れた不具合の話に戻ります。QR Sendはポップアップを開いた瞬間に、service worker側が持っている最新の状態を表示する作りになっています。ところが、たまに古いデータのまま表示されてしまうことがありました。
原因は単純で、service workerが寝ている間にポップアップが開かれると、起動直後の一瞬だけ初期化前の状態を表示してしまっていたことでした。起きた直後のservice workerはまだ最新のデータをストレージから読み込み切っていないタイミングがある、ということです。
この手のバグは、DevToolsを開いて確認している間は再現しません。前述の通り、開いている間はworkerが寝ないので、初期化前の一瞬という条件が発生しないからです。結局、DevToolsを閉じた状態で何度もポップアップを開閉して再現させ、ポップアップ側で「初期化完了」のメッセージを受け取るまでローディング表示を出す、という形で解決しました。
仕組みとしては単純なのですが、再現条件そのものがデバッグツールと衝突しているという点に気付くまでに時間がかかりました。同じような症状に悩んでいる方がいたら、まず「DevToolsを閉じた状態で再現するか」を確認してみることをおすすめします。
AIにこの手のデバッグをさせるなら
以前、chrome-devtools-mcpでAIエージェントに拡張のリロードとconsole確認をやらせるという記事を書きました。今回のservice workerのジレンマは、実はAIに任せる方が相性がいい部分もあります。
「DevToolsを閉じた状態を保ちつつ、一定間隔で状態を確認する」という手順は、人間がやると地味に面倒(DevToolsを開きたくなる誘惑に負けがち)ですが、エージェント経由でconsoleログを取得する形であれば、DevToolsパネル自体を開きっぱなしにする必要がありません。まだ私自身、この組み合わせを体系的に検証できているわけではないので、次に試したときにまた記事にしようと思います。
というわけで、MV3のservice workerとoffscreen documentは、仕様そのものより「観察のしにくさ」の方が実務では厄介だと感じています。
まとめ
MV3のservice workerは仕様として寝る、というだけでなく、観察しようとすると寝なくなるという厄介な性質があります。DevToolsを開いている間は再現しないバグがある、ということを知っているだけでも、無駄に迷宮入りする時間はだいぶ減らせるはずです。offscreen documentまわりの見えにくさも合わせて、同じところでハマっている方の参考になればと思います。
同じ症状で困っている方がいたら、コメントで教えてもらえると嬉しいです。

コメント