Chrome 拡張機能の content_scripts で CORS を回避する方法
注意
現在は Manifest V3 が登場しているので、もしかしたら Manifest V3 とはやり方が異なるかもしれないが、備忘録として残しておく。Manifest V3 での方法については気が向いたらいずれ投稿する予定。
概要
Chrome 拡張機能の content_scripts
のスクリプト内で XMLHttpRequest
や fetch
などの非同期通信を実行すると以下のようなエラーが発生する。
XMLHttpRequest cannot load https://example.com/. Origin chrome-extension://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx is not allowed by Access-Control-Allow-Origin
このエラーは CORS というセキュリティ機能によるもので、許可されていない異なるドメインから JavaScript 経由でアクセスしようとしたときにブロックされるというものである。
よくある対応内容
拡張機能からアクセスしたいドメインを permissions
に追加すると送信できるようになる、という解決方法をネットでよく見かける。
"permissions": [
"https://example.com/*"
]
実際、この permissions
の追加は必要なのだが、これだけではうまくいかない。昔はこれだけでもうまくいったのかもしれないが、現在だとうまくいかない。
解決策
簡潔にまとめると、以下のフローでこの問題を解決することができる。上述した permissions
に関してはすでに追加済みとする。
-
manifest.json
のbackground
にスクリプトを追加する -
content_scripts
でchrome.runtime.sendMessage
を使いbackground
に情報を伝達する -
background
でchrome.runtime.onMessage.addListener
を使いcontent_scripts
からの情報を受け取る - 受け取った情報をもとに非同期通信を行う
- 必要に応じて結果を返して
content_scripts
で後処理を行う
以下、具体的に説明する。
manifest.json
の background
にスクリプトを追加する
1. manifest.json
に以下を追加する。permissions
や content_scripts
やそれ以外の項目については割愛する。
{
// 省略
"background": {
"scripts": [
"background.js"
],
"persistent": false
}
// 省略
}
パスは必要に応じて変更すること。上記の例のままであれば、同ディレクトリに background.js
を作成する。
content_scripts
で chrome.runtime.sendMessage
を使い background
に情報を伝達する
2. content_scripts
のスクリプト内で、非同期通信を行いたい部分に以下を追加する。
chrome.runtime.sendMessage(
{
contentScriptQuery: 'post',
endpoint: 'https://example.com'
},
(response) => {
console.log(response);
}
);
chrome.runtime.sendMessage
の第一引数はハッシュで、background
に送りたいデータを指定する。第二引数はコールバック関数を指定する。
background
で chrome.runtime.onMessage.addListener
を使い content_scripts
からの情報を受け取る
3. background
のスクリプト内に以下を追加する。
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (!message) {
sendResponse({
'status': false,
'reason': 'message is missing'
});
}
else (message.contentScriptQuery === 'post') {
fetch(message.endpoint, {
'method': 'GET'
})
.then((response) => {
if (response && response.ok) {
sendResponse(response);
}
})
.catch((error) => {
sendResponse({
'status': false,
'reason': 'failed to fetch'
});
});
}
return true;
});
content_scripts
側で chrome.runtime.sendMessage
を実行すると、こちら側 (background
) の chrome.runtime.onMessage.addListener
の中身がバックグラウンドで実行される。
chrome.runtime.onMessage.addListener
には関数を指定する。その関数が受け取る引数は 3 つ (message, sender, sendResponse)
あり、それぞれ以下のデータが格納される。
-
message
-
chrome.runtime.sendMessage
の第一引数で指定したハッシュ- 今回の例だと
contentScriptQuery
とendpoint
を送っているので、それぞれmessage.contentScriptQuery
とmessage.endpoint
で中身を取得できる - 複数の箇所でそれぞれ異なる非同期通信を行いたい場合は
contentScriptQuery
の中身を変えて条件分岐することで実現できる
- 今回の例だと
-
-
sender
- (調べていないのであとで調べて追記)
-
sendResponse
-
chrome.runtime.sendMessage
側に結果を返すための関数
-
重要事項
chrome.runtime.onMessage.addListener
は、必ず true
を返す必要がある点に注意すること。そうしないとエラーが出て正しく動作しない。
4. 受け取った情報をもとに非同期通信を行う
上記は https://example.com
に GET リクエストを送るだけのシンプルな例だが、実際にはもう少し複雑な通信を行うこともあるだろう。
たとえば HTTP ヘッダやペイロードなどを付与したい場合は chrome.runtime.sendMessage
の第一引数にそれらを追加し、それを message
から参照することで実現できる。
content_scripts
で後処理を行う
5. 必要に応じて結果を返して また、通信した結果を content_scripts
側で扱いたいこともあるだろう。
その場合は上記の例のように sendResponse
の引数に結果を指定する。すると、その結果を chrome.runtime.sendMessage
の第二引数に指定したコールバック関数で受け取ることができる。本記事の chrome.runtime.sendMessage
の例では、受け取った response
をただコンソールに表示するだけだが、ここに行いたい後処理を入れることができる。
まとめ
多少めんどうな実装にはなるが、これで content_scripts
内でも非同期通信を扱うことができるようになる。厳密には content_scripts
は background
に非同期通信を任せているだけなのだが。
Manifest V3 について
筆者はまだ Manifest V3 を試したことがないのだが、軽く調べてみた感じだと、host_permissions
というプロパティを使えば非同期通信が行えるようになるようだ。
Chrome 拡張機能の CORS エラーを回避(Manifest V3)
それから、開発した拡張機能を Chrome Web Store で公開することを検討している場合は、Manifest V3 にすることで審査にかかる期間を短縮できるようだ。
これを機に Manifest V2 で開発した拡張機能を Manifest V3 にアップデートしてみよう。
Discussion