メインコンテンツへスキップ
記事一覧に戻る
programmingadalab focus 開発記 (5/5)

Web アプリとブラウザ拡張を連携させる — postMessage ブリッジと declarativeNetRequest

学習アプリのポモドーロと連動して、フォーカス中だけ誘惑サイトをブロックする拡張機能「adalab shield」の連携設計を解説。postMessage によるプロトコル設計、なりすまし対策、declarativeNetRequest でのページ読み込み前ブロックを紹介します。

2026-06-169 min readAdabana Saki
-
Chrome 拡張Manifest V3declarativeNetRequestpostMessagecontent scriptTypeScriptadalab shieldadalab focus
シリーズ中級者〜上級者

adalab focus 開発記

学習管理 PWA「adalab focus」の開発で得た知見を解説。ローカルファースト設計、オフライン同期、サーバーレス認証、タイマーの状態設計、ブラウザ拡張との連携まで

進捗5 / 5

Web アプリとブラウザ拡張を連携させる — postMessage ブリッジと declarativeNetRequest

adalab focus 開発記、最終回です。

ポモドーロで「25 分集中する」と決めても、YouTube は同じブラウザの隣のタブにいます。意志力で勝てないなら仕組みで勝とう、ということで作ったのが拡張機能 adalab shield です。

  • フォーカス開始 → 誘惑サイト(YouTube・TikTok など)を自動ブロック
  • 休憩開始 → 自動で解除
  • ブロック画面には「フォーカスの残り時間」と「いま取り組んでいるタスク名」を表示

今回はこの Web アプリ ↔ 拡張機能のリアルタイム連携をどう設計したかを書きます。

連携の全体像

Web ページと拡張機能は直接は話せません。間に立てるのが content script です。

Text

Web アプリ側はタイマー状態が変わるたびに postMessage を投げるだけです:

TypeScript

この「拡張が無くても何も壊れない」という性質が postMessage 連携の良いところです。アプリは拡張の存在を仮定せず、ただ状態を放送し続けます。

postMessage は「誰でも投げられる」前提で受ける

content script 側の受信には注意が要ります。window.postMessageページ上の任意のスクリプトが投げられるので、性悪説で検証します:

TypeScript

parsePayload では phase が既知の値か、endTime が数値か、taskTitle が 200 文字以内か、まで見ています。タスク名はブロック画面に表示される文字列なので、長さ制限は表示崩れ対策と注入対策を兼ねます。

ブロックは JS ではなくネットワーク層で

「ブロック」を content script で document.body を覆って実現する拡張も多いのですが、それだとページの読み込み自体は走るので、一瞬サムネイルが見えたり、音が鳴り始めたりします。誘惑のブロックとしては致命的です。

Manifest V3 の declarativeNetRequest (DNR) を使うと、リクエストの段階でリダイレクトできます:

TypeScript
Text

休憩フェーズに入ったら動的ルールを全削除すれば解除完了です。さらにブロックページ側の JS が「休憩が始まったか」を監視していて、始まった瞬間に ?u= で渡された元 URL へ自動で戻します。ブロックは厳しく、復帰はシームレスに、です。

ブロック画面に「あと何分か」を出す

ただ「ブロックしました」と言われるより、**「あと 12 分で休憩です。いまは『過去問 2023』の時間」**と言われる方が、人は納得して戻れます。

postMessage で受け取った endTimetaskTitle は拡張のストレージに入っているので、ブロックページはそれを読んでカウントダウンを表示するだけです。タイマー本体と通信し続ける必要はありません。endTime という絶対時刻を渡しているからで、ここでも前回の「タイムスタンプ方式」が効いています。

逆方向:拡張のポップアップからタイマーを操作する

連携は逆方向もあります。拡張のポップアップから「タイマー開始」「タスク完了」を押せるリモコン機能です。経路はさっきの逆再生ですが、リクエスト/レスポンスの対応付けが必要になるので requestId を発行しています:

TypeScript

postMessage には「返事」という概念がないので、相関 ID と 3 秒のタイムアウトを自前で持つ、古典的な RPC の再発明です。地味ですが、これがないと「押したのに何も起きない(ように見える)」ポップアップになります。

E2E テストは「拡張を実際に読み込んで」やる

この連携、ユニットテストだけでは守れません。manifest の matches 設定ミス 1 つで全部沈黙するからです。Playwright は拡張を読み込んだ Chromium を起動できるので、E2E で本物の流れを通しています:

TypeScript

「フォーカス開始したら example.com がブロックページにリダイレクトされる」が CI で毎回検証されるのは、かなりの安心感があります。

まとめ

設計判断理由
連携は postMessage の一方的な放送拡張が無くても何も壊れない疎結合
受信側は送信元・型・値域を全部検証postMessage はページ上の誰でも投げられる
ブロックは DNR でネットワーク層誘惑コンテンツのバイトを 1 つも届かせない
endTime は絶対時刻で渡す受け取った側が通信なしでカウントダウンできる
E2E は拡張を実機読み込みmanifest 設定ミスはユニットテストで捕まらない

これで adalab focus 開発記シリーズは完結です。アプリも拡張も実際に動くものが公開されているので、興味があれば触ってみてください。

リンク