[拡張機能]サイトブロッカーの作り方 -webNavigationの使い方-

2023/10/31に公開

webNavigationを使ったサンプルコードとして、サイトブロッカーを実装してみます。3つのファイルで動きます。次の内容のファイルを作成し、拡張機能を読み込み、Youtubeに訪れてみてください。blocked.htmlにリダイレクトされます。拡張機能が作れます。

manifest.json
{
  "manifest_version": 3,
  "name": "Site Blocker",
  "version": "1.0",
  "permissions": ["webNavigation", "tabs"], // ①
  "background": {
    "service_worker": "background.js"
  },
  "host_permissions": ["*://*/*"]
}
background.js
chrome.webNavigation.onBeforeNavigate.addListener(details => { // ②
  const blockedSites = ["youtube.com", "twitter.com"]; // ③
  if (blockedSites.some(site => details.url.includes(site))) { //④
    chrome.tabs.update(details.tabId, { url: "blocked.html" }); // ⑤
  }
}, { url: [{ urlMatches: "http://*/*" }, { urlMatches: "https://*/*" }] }); // ⑥

blocked.html
<!DOCTYPE html>
<html>
<head>
  <title>Blocked!</title>
</head>
<body>
  <h1>This site is blocked.</h1>
</body>
</html>

webNavigation

ナビゲーションとは、ユーザーが指定したURLにアクセスして、そのページを表示するまでの一連のプロセスのことをいいます。ページが画面に表示されるまでには様々な段階があり、それに対応したイベントが発生しています。これら発生する一連のイベントを追跡するための機能がwebNavigationAPIです。

例えばこのサイトブロッカーではonBeforeNavigateイベントを検知したときに実行するイベントリスナーを定義しています。

webNavigation.onBeforeNavigate

onBeforeNavigateイベントはページの読み込みが発生する前に発生するイベントです。このサイトブロッカーではページ読み込みが発生する前に別ページにリダイレクトしているので違和感なく動作しています。しかし以下のようにonComplatedイベント発生時にイベントリスナーを追加するとサイトが読み込まれた後にリダイレクトされて少し不自然なサイトブロッカーになります。

background.js
chrome.webNavigation.onCompleted.addListener()

このように、webNavigationAPIで扱うイベントはブラウザーの視覚的な変化に対応したものに関連します。(webNavigationAPIに似たものにwebRequestAPIがありますが、こちらはHTTPレイヤーからの下位レベルのビュー寄りです。)

次の図はナビゲーションの発生するイベントのフロー図です。再掲しますがナビゲーションとは、ユーザーが指定したURLにアクセスして、そのページを表示するまでの一連のプロセスのことをいいます。

イベント 説明
onCreatedNavigationTarget 新しいタブやウィンドウが作成されたタイミング
onBeforeNavigate リンクをクリックした直後などナビゲーションを開始した直後に発生するイベント。ページ読み込みが行われる前のタイミング。
onCommitted ナビゲーションが確定し、URLに移動することが決定し、ドキュメントがロードされる直前に発生するイベント。
onDOMContentLoaded DOMツリーの構築が完了したが、画像やCSSなどのリソースは読み込まれていないタイミング
onHistoryStateUpdated History API を使ってURLを更新するタイミング。SPAのサイトでページ移動するときなどがある。
onReferenceFragmentUpdated URIフラグメントが変更されたタイミング。URIフラグメントとはページ内リンクなどURLの後ろに#に続くもの。
onCompleted ページとリソースが完全に読み込まれたタイミング
onErrorOcurred ナビゲーションの途中でエラーが発生したタイミング

多くの場合、必要になるのはonBeforeNavigateonComplatedでしょう。

これらのイベントについて少し補足します。
history.pushstate() はReact RouterやVue Routerなどのルーティングライブラリで使われる。これらのライブラリでフロントエンドを作っているとき、ページ遷移時は再度読み込みではなく History APIを使って行われる。ReactやVueで作られるサイトのページ遷移の検出に役立つ。一方、ページの内容が実際にどう変わったのかはイベント自体からは直接わからないため、ページのDOMを解析したり、特定の条件に基づいて動作を変える追加のロジックが必要。onHistoryStateUpdated イベントはブラウザが

onReferenceFragmentUpdatedイベントとonHistoryStateUpdatedイベントはonCompleteイベント発生後でも発生します。

④ details

②のコールバック関数ではdetailsという引数にナビゲーションの情報が格納されます。このdetailsに格納されるプロパティは検知したイベントによって異なります。下の表はonBeforeNavigateイベントので利用できるプロパティです。

プロパティ 説明
tabId ナビゲーションが行われるタブのID
url 移動さきのURL
frameId ナビゲーションが行われようとするフレームを示す。1はiframeであり、2,3,...はネストされたiframeを指す。0は最上位の閲覧コンテキストで、現在見ているページ全体で移動されていると考えるといい。
timeStamp ナビゲーションを開始している時刻

https://runebook.dev/ja/docs/web_extensions/api/webnavigation/onbeforenavigate

下の表はonCompleteイベントで利用できるプロパティです。
https://developer.chrome.com/docs/extensions/reference/webNavigation/#event-onCompleted

chrome.tabs.update

background.js
chrome.tabs.update(details.tabId, { url: "blocked.html" }); // ⑤

この箇所でページを更新しています。tab.updateを使うとタブの情報を編集できるのですがここではURLをパラメーターに渡してページ移動させています。また、webNavigation.onBeforeNavigateイベントと組み合わせて、ページが読み込まれる前に用意したページに移動せさることでリダイレクトを実現しています。

⑥ {url: [{urlMatches: }]}

webNavigation.onBeforeNavigateの第二引数であるフィルタリング条件を定義しています。こここでは

{ url: [{ urlMatches: "http://*/*" }, { urlMatches: "https://*/*" }] });

と定義しているので、コールバック関数で定義したイベントリスナーはonNavigateイベントが発生してかつ、httpかhttpsで始まるURLに移動するときにのみ実行されます。このようなフィルタリング条件を設定すると、chrome://file://など他のプロトコルを使用するURLに対して反応しなくなり、コードを実行する範囲を最小限にします。

参考サイト

MDN

Discussion