Chrome 拡張 manifest v3 で サイトブロッカーを作ってみた
意志の力は弱い、ということで Web サイトの閲覧を禁止する Chrome 拡張を作ってみました。
どうせなら、最新のマニフェスト v3 対応の拡張機能を作ることにします。
マニフェスト v3 について
- 本家ドキュメント
- 日本語記事
マニフェスト v2 との違い
サイトブロッカーの作成にあたり、次の点でマニフェスト v2 との相違がありました。
-
サイトのブロックに chrome.webRequest が使えない
Web Request を使うと、Chrome はネットワーク リクエストに含まれるすべてのデータを、監視している拡張機能に送ります。(中略)すべてのリクエスト データが拡張機能に渡るので、悪意のあるデベロッパーは、いとも簡単にユーザーの認証情報やアカウント、個人情報へのアクセスを悪用できます。
https://developers-jp.googleblog.com/2019/07/web-request-declarative-net-request.html
- 代わりに chrome.declarativeNetRequest を使用する
-
chrome.declarativeNetRequest では、マニフェストで指定した
ルールセット
ファイルに記述した URL をブロックできる(宣言的)
There is a new declarativeNetRequest for network request modification, which provides an alternative for much of the webRequest AP's functionality.
ネットワークリクエストを修正するための新しい宣言型 NetRequest があり、これは webRequest AP の機能の多くを代替するものです。
https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/#when-use-blocking-webrequest
-
繰り返し処理に
setTimeout
が使えない- 代わりに chrome.alarms を使用する
It's common for web developers to perform delayed or periodic operations using the setTimeout or setInterval methods. These APIs can fail in service workers, though, because the scheduler will cancel the timers when the service worker is terminated.
Web 開発者が setTimeout や setInterval メソッドを使って遅延や周期的な操作を行うのはよくあることです。しかし、これらの API はサービス・ワーカーでは失敗することがあります。なぜなら、サービス・ワーカーが終了したときにスケジューラーがタイマーをキャンセルするからです。
https://developer.chrome.com/docs/extensions/mv3/migrating_to_service_workers/
ソースコード
- マニフェスト
manifest.json
- サービスワーカー
background.js
- ルールセット
rules.json
マニフェスト
{
"name": "Site Blocker",
"description": "Site Blocker",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"declarative_net_request": {
"rule_resources": [
{
"id": "ruleset_1",
"enabled": true,
"path": "rules.json"
}
]
},
"permissions": ["declarativeNetRequest", "alarms"]
}
-
background > service_worker
でサービスワーカーにbackground.js
を指定 -
declarative_net_request > rule_resources
でルールセットにrules.json
を指定 - パーミッション
permissions
でdeclarativeNetRequest
とalarms
を指定
サービスワーカー
function updateRuleset() {
const dateObj = new Date();
const isAm = dateObj.getHours() < 12 ? true : false;
if (isAm) {
// 午前
chrome.declarativeNetRequest.updateEnabledRulesets({
enableRulesetIds: ["ruleset_1"],
});
} else {
// 午後
chrome.declarativeNetRequest.updateEnabledRulesets({
disableRulesetIds: ["ruleset_1"],
});
}
}
chrome.alarms.create({ delayInMinutes: 1, periodInMinutes: 1 });
chrome.alarms.onAlarm.addListener(() => {
updateRuleset();
});
- 午前はルールセット
ruleset_1
を有効(仕事に集中) - 午後はルールセット
ruleset_1
を無効(怠けても良いよ) - 拡張機能の読み込み 1 分後(
delayInMinutes: 1
)から、1 分ごとに(periodInMinutes: 1
)、アラームを起動
備考
-
background.js
が存在しなくても、マニフェストとルールセットだけでサイトの閲覧禁止は実現できる - 今回
background.js
を使用している理由は、午前と午後で閲覧禁止の可否を変更したかったため
ルールセット
[
{
"id": 1,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "youtube.com",
"resourceTypes": [
"main_frame",
"sub_frame",
"stylesheet",
"script",
"image",
"font",
"object",
"xmlhttprequest",
"ping",
"csp_report",
"media",
"websocket"
]
}
},
{
"id": 2,
"priority": 1,
"action": { "type": "block" },
"condition": {
"urlFilter": "twitter.com",
"resourceTypes": ["xmlhttprequest"]
}
},
{
"id": 3,
"priority": 1,
"action": { "type": "block" },
"condition": {
"urlFilter": "mail.google.com",
"resourceTypes": [
"main_frame",
"sub_frame",
"stylesheet",
"script",
"image",
"font",
"object",
"xmlhttprequest",
"ping",
"csp_report",
"media",
"websocket"
]
}
},
{
"id": 4,
"priority": 1,
"action": { "type": "block" },
"condition": {
"urlFilter": "feedly.com",
"resourceTypes": ["main_frame"]
}
}
]
- YouTube、Gmail を完全にブロックするのは、難しい。
resourceTypes
を増やしてごまかしている - Twitter は
xmlhttprequest
をブロックする必要があった - Feedly は
main_frame
でブロックできた
Discussion
勉強になりました。試行錯誤して気づいたことお伝えします。長文すいません。
1. キャッシュ
declarativeNetRequest は一部のキャッシュアクセスをブロックしない。
2. ルールの切り替え
ルールの有効/無効の切り替えを行うと、有効に切り替わる際、既に対象ページを開いている場合が問題となる。例えばYouTube視聴中に、ルールが有効になってもダウンロード済みの動画データは再生され続ける。
3. デバッグ
実際に何がブロックされているかは DevTools -> Network で確認できる。
is:from-cache
と入力するとキャッシュアクセスのみ、-is:from-cache
と入力すると、キャッシュアクセス以外を表示できる。4. ブログ記事の不具合
(1) YouTube条件
動画の視聴中、YouTubeのページは次のようなリクエストを送信する。
このリクエストはURLが
"urlFilter": "youtube.com"
に該当しないため、ブロックされない。解決法
ルールを下記のように2つ作成する。
"domains"
の内容はURLではなくリクエストのinitiatorと比較される。2番目のルールは initiator = youtube.com の通信をブロックする。
(2) Twitter条件
"condition": {"urlFilter": "twitter.com", "resourceTypes": ["xmlhttprequest"]}
ではブロックできなかった。"main_frame"
が抜けている。なお、ルールが常に有効なら、Twitterも
main_frame
だけでブロックできた。そうでないなら、(1) YouTube条件 のような修正が必要。