かなりマイナーな記事です。
なぜこの記事を書いたのか
Chrome拡張機能の仕様には、独自の制約があり、つまずいた部分があったので記事にしました!
特にLPを作る間隔で拡張機能に静的ページを用意しようとしたとき、いろいろつまずきました。。
拡張機能のoptionページとは
Chrome拡張を使用する上で設定などを表示するページのことです。
拡張機能とは独立したファイルとして存在しています。
目的
拡張機能内でユーザーからの問い合わせやサブスクリプションのアップグレードなどを気軽に完結させたいと考えました!
外部のWebサイト(LPなど)に処理を分けるのも一般的です。
しかい、ホスティングや運用コスト、メンテナンスの煩雑さを避けたいという理由から、できる限り拡張機能内で完結させる方針をとりました。
実装するなかで、特にOptionページで困ったりした点を記事にしていきます。
optionページの構造と制限:optionページは1ファイルしかない
拡張機能では、options_page
もしくは options_ui
に設定できるページは1つだけです。
たとえば chrome-extension://[id]/options/index.html
という形のURLのみ。
そのため複数のページを持たせることはできず、ReactやVueなどのSPA構成で、状態管理によりページを切り替える形が一般的です。
その際、状態をURLに反映させるためには、クエリパラメータを使うのがシンプルで相性が良いです。
オプションページはコンテンツスクリプトから直接開けない
Chrome拡張機能で options.html
を開くとき、コンテンツスクリプトから window.open()
を使って chrome-extension://
URL に直接遷移することはできません。
これはセキュリティ上の制限で、ERR_BLOCKED_BY_CLIENT エラーになります。
呼び出し方法 | 可否 | 備考 |
---|---|---|
window.open(chrome.runtime.getURL('options/index.html')) |
❌ | コンテンツスクリプトからはブロックされる |
chrome.runtime.openOptionsPage() |
✅ | 拡張アイコンやポップアップからの利用に最適 |
chrome.tabs.create({ url: chrome.runtime.getURL('options/index.html') }) |
✅ | バックグラウンドスクリプト経由なら有効。唯一の正規手段 |
クエリ付きでオプションページを柔軟に開く方法
柔軟なページ切り替えには、chrome.runtime.sendMessage()
→ chrome.tabs.create()
の流れで、 バックグラウンドスクリプト経由でオプションページを開くのが現実的です。
// content script 側
chrome.runtime.sendMessage({ action: 'open-options', page: 'contact' });
// background script 側
chrome.runtime.onMessage.addListener((message) => {
if (message.action === 'open-options') {
const url = chrome.runtime.getURL(`options/index.html?page=${message.page}`);
chrome.tabs.create({ url });
}
});
SPAであれば、クエリパラメータに応じて表示を出し分ける実装が自然です。
chrome.runtime.openOptionsPage()
は manifest に指定された固定ページしか開けません。 クエリを使いたい場合はchrome.tabs.create()
を選びましょう。
外部JSを埋め込むことができない(Stripeなど)
Chrome拡張機能(Manifest V3)では、Content Security Policy(CSP)によって外部スクリプトの読み込みが制限されています。
とくに options
ページのような拡張機能内のHTMLでは、CDNなど外部から提供されるJavaScriptファイルを `` で読み込むことができません。
以下のようなCSPがデフォルトで適用されます:
{
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
}
}
この制約により、以下のようなケースでエラーになります:
- Stripeの
<stripe-pricing-table>
コンポーネントの埋め込み -
https://js.stripe.com/v3/
を使った Stripe.js の読み込み
なぜなら:
-
script-src
は'self'
のみで外部CDNが含まれていない -
eval()
を使う外部スクリプトは Manifest V3 で完全に禁止されている
つまり、manifest.json の権限設定でどうにかなる問題ではありません。
詳細はChrome公式ドキュメントを参照。
じゃあどうすればいいの?
この制約を回避するには、決済など外部スクリプトを使いたい処理は、拡張機能外のWebページに分離して実装する必要があります。
たとえば、Firebase Hosting や独自ドメインでホストしたページに stripe.js
を読み込んで処理を行い、必要があれば拡張機能とメッセージ通信で連携する構成が現実的です。
🔗 Stripe公式も明記
実際、Stripe公式ドキュメントでも、Chrome拡張ではStripe.jsが使えないことが明記されています。
"Stripe.js は Chrome 拡張機能など、特定の CSP ポリシーを持つ環境では動作しません。"
まとめ
-
options
ページは1ページしか持てず、SPAで状態管理する必要がある - コンテンツスクリプトから直接開けないため、バックグラウンド経由で開く
- 外部JSは読み込めないため、Stripe等の処理は外部ページに分離するしかない
- Stripe.jsは公式にも拡張機能内では動作しないと明記されている
拡張機能だけで完結したい気持ちは強いが、CSPやセキュリティ制約を考えると、必要な処理は適切に外部へ逃す設計判断が重要だと感じました。