--- title: コンテンツスクリプト slug: Mozilla/Add-ons/WebExtensions/Content_scripts tags: - WebExtensions translation_of: Mozilla/Add-ons/WebExtensions/Content_scripts ---
コンテンツスクリプトは、特定のウェブページのコンテキストで実行される拡張機能の一部です(拡張機能の一部であるバックグラウンドスクリプトや、ウェブサイト自体の一部であるスクリプト、例えば {{HTMLElement("script")}} 要素みたいなものと対をなすような)。
バックグラウンドスクリプトはすべての WebExtension JavaScript API にアクセスできますが、ウェブページのコンテンツに直接アクセスすることはできません。だからあなたの拡張機能がそれを行う必要がある場合は、コンテンツスクリプトが必要です。
通常のウェブページで読み込まれたスクリプトと同様に、コンテンツスクリプトは、標準の DOM API を使用してページのコンテンツを読み取り、変更することができます。
コンテンツスクリプトは、WebExtension API の小さなサブセット にしかアクセスできませんが、メッセージングシステムを使用して バックグラウンドスクリプトと通信し、WebExtension API に間接的にアクセスすることができます。
コンテンツスクリプトは次のドメインでブロックされるのに注意してください:
このドメインのページにコンテンツスクリプトを挿入しようとすると、そのスクリプトは失敗し、ページは CSP エラーをログに記録します。
addons.mozilla.org を含む制限のために、ユーザーはインストール後すぐに拡張機能を試して、動かないのに気付くだけでしょう! 適切な警告を追加するか、addons.mozilla.org
から動かす onboarding page を追加したくなるでしょう。
letfoo
や window.foo = "bar"
にて、コンテンツスクリプトのグローバルスコープで追加された値は、1408996 のバグによって消えることがあります。
次の 3 つの方法のいずれかを使用して、ウェブページにコンテンツスクリプトを読み込むことができます。
manifest.json
の content_scripts
キーを使用して、URL が指定されたパターンにマッチするページをロードするたびにコンテンツスクリプトを読み込むようブラウザーに依頼できます。tabs.executeScript()
API を使用すると、ユーザーがブラウザーアクションをクリックした場合など、必要なときにコンテンツスクリプトを特定のタブに読み込むことができます。フレームごと、拡張機能ごとのグローバルスコープしかありません。これは 1 つのコンテンツスクリプトの変数は、読み込み方にかかわらず、他のコンテンツスクリプトからアクセスできることになります。
方法 (1) と (2) ではマッチパターンを使って表現された URL のスクリプトだけを読み込みできます。
方法 (3) では、拡張機能と一緒にパッケージされたページのスクリプトも読み込みできますが、"about:debugging" や "about:addons"のような権限つきページにはスクリプトを読み込めません。
コンテンツスクリプトは、普通のページスクリプトと同様に、ページの DOM にアクセスして修正できます。ページスクリプトにてなされた DOM の変更を見ることもできます。
しかし、コンテンツスクリプトは "DOM のきれいな見た目" を取得します。これはつまり:
Firefox では、この挙動は Xray vision と呼ばれます。
例えば、次のウェブページを考えます:
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head> <body> <script src="page-scripts/page-script.js"></script> </body> </html>
"page-script.js" スクリプトは次を実行します:
// page-script.js // add a new element to the DOM let p = document.createElement("p"); p.textContent = "This paragraph was added by a page script."; p.setAttribute("id", "page-script-para"); document.body.appendChild(p); // define a new property on the window window.foo = "This global variable was added by a page script"; // redefine the built-in window.confirm() function window.confirm = function() { alert("The page script has also redefined 'confirm'"); }
今度は拡張機能がページにコンテンツスクリプトを挿入します:
// content-script.js // can access and modify the DOM let pageScriptPara = document.getElementById("page-script-para"); pageScriptPara.style.backgroundColor = "blue"; // can't see page-script-added properties console.log(window.foo); // undefined // sees the original form of redefined properties window.confirm("Are you sure?"); // calls the original window.confirm()
その反対もまた真です; ページスクリプトはコンテンツスクリプトが追加した JavaScript プロパティを見られません。
このことからすると、コンテンツスクリプトは予言できる振る舞いをする DOM プロパティに依存していて、ページスクリプトにて定義された変数とコンテンツスクリプトで定義する変数の衝突は心配しなくていいです。
この振る舞いの実用的な結論として、コンテンツスクリプトはページに読み込まれたいかなる JavaScript ライブラリーにもアクセスしません。なので例えば、ページに jQuery が含まれても、コンテンツスクリプトは見ることができません。
コンテンツスクリプトから JavaScript ライブラリを使いたい場合、ライブラリ自身を、使う方のコンテンツスクリプトと並べて挿入するべきです:
"content_scripts": [ { "matches": ["*://*.mozilla.org/*"], "js": ["jquery.js", "content-script.js"] } ]
記: Firefox ではコンテンツスクリプトからページスクリプトによって生成された JavaScript オブジェクトにアクセスしたり、ページスクリプトにコンテンツスクリプトの JavaScript オブジェクトを公開できるようにする API が提供されます。
詳しくはページスクリプトとオブジェクトを共有するのページを見てください。
標準 DOM API に加え、コンテンツスクリプトは次の WebExtension API を使用できます:
extension
から:
runtime
から:
i18n
から:
すべてから:
コンテンツスクリプトは通常の window.XMLHttpRequest
と window.fetch()
API を使ってリクエストを作成できます。
Firefox では、コンテンツスクリプトの (例えば、fetch()
を使った) リクエストは、拡張機能のコンテキストで起こるので、ページコンテンツを参照する URL を絶対URL で提供せねばなりません。
Chrome では、リクエストはページのコンテ/api
は https://[現在のペー
に送られます。 ジの URL]/api
コンテンツスクリプトは拡張機能の他の部分と同一のクロスドメイン権限を取得します: よって拡張機能が manifest.json
の permissions
キーを使ってあるドメインのクロスドメインアクセスを要求している場合、コンテンツスクリプトも同様にそのドメインのアクセスを取得します。
これはより多く権限付けられた XHR に晒して、コンテンツスクリプトでインスタンスを取得することで達成し、その副作用としてページ自体からのリクエストがそうであるように Origin
と Referer
ヘッダーがセットされず、リクエストからクロスオリジンな性質を隠すことが好ましいことがよくあります。
バージョン 58 以降、コンテンツ自体から送られたかのようなリクエストを必要とする拡張機能は content.XMLHttpRequest
と content.fetch()
を代わりに使うことができます。
クロスブラウザー拡張機能にとってこれらの存在は機能検出となります。
コンテンツスクリプトは WebExtension API の大半を直接には使用できませんが、メッセージ API を用いて拡張機能のバックグラウンドスクリプトと通信できて、それゆえにバックグラウンドスクリプトがアクセスできるすべての API に間接的にアクセスできます。
バックグラウンドスクリプトとコンテンツスクリプトが通信する 2 つのパターンがあります: (オプションなレスポンスつきの)ワンオフメッセージを送るのと、お互いに息の長いコネクションを確立して、そこでメッセージを交換するのとです。
レスポンスが必須でないワンオフメッセージを送るには、次の API を使います:
コンテンツスクリプト内 | バックグラウンドスクリプト内 | |
---|---|---|
送信メッセージ | browser.runtime.sendMessage() |
browser.tabs.sendMessage() |
受信メッセージ | browser.runtime.onMessage |
browser.runtime.onMessage |
例えば、ウェブページでのクリックイベントをリッスンするコンテンツスクリプトがここにあります。クリックがリンク上である場合、ターゲット URL をバックグラウンドページにメッセージします。
// content-script.js window.addEventListener("click", notifyExtension); function notifyExtension(e) { if (e.target.tagName != "A") { return; } browser.runtime.sendMessage({"url": e.target.href}); }
バックグラウンドスクリプトはこのメッセージをリッスンして、notifications
API を使って通知を表示します:
// background-script.js browser.runtime.onMessage.addListener(notify); function notify(message) { browser.notifications.create({ "type": "basic", "iconUrl": browser.extension.getURL("link.png"), "title": "You clicked a link!", "message": message.url }); }
この例のコードは Github の notify-link-clicks-i18n のサンプルから簡単に適用できます。
ワンオフメッセージの送信は、バックグラウンドスクリプトとコンテンツスクリプトとで多くのメッセージを交換する場合はめんどくさいです。なので代替パターンは、この 2 つのコンテキスト間で寿命の長いコネクションを確立して、メッセージ交換にこのコネクションを使うことです。
いずれの側にも runtime.Port
オブジェクトがあり、メッセージ交換に使えます。
コネクションを作成するには:
runtime.onConnect
にてコネクションをリッスンする。tabs.connect()
(コンテンツスクリプトに接続する場合)runtime.connect()
(バックグラウンドスクリプトに接続する場合)これは runtime.Port
オブジェクトを返します。
runtime.onConnect
リスナーには自身の runtime.Port
オブジェクトが渡される。それぞれがポートを持ったら、両方が:
runtime.Port.postMessage()
でメッセージを送ってruntime.Port.onMessage
でメッセージを受信できるようになる。例えば、ロードしたらすぐに、このコンテンツスクリプトは:
myPort
変数に Port
を格納するmyPort
のメッセージをリッスンする(ログに出す)myPort
を使ってメッセージを送る// content-script.js var myPort = browser.runtime.connect({name:"port-from-cs"}); myPort.postMessage({greeting: "hello from content script"}); myPort.onMessage.addListener(function(m) { console.log("In content script, received message from background script: "); console.log(m.greeting); }); document.body.addEventListener("click", function() { myPort.postMessage({greeting: "they clicked the page!"}); });
対応するバックグラウンドスクリプトは:
portFromCS
という名前の変数にポートを格納するportFromCS
を使ってコンテンツスクリプトにメッセージを送る// background-script.js var portFromCS; function connected(p) { portFromCS = p; portFromCS.postMessage({greeting: "hi there content script!"}); portFromCS.onMessage.addListener(function(m) { console.log("In background script, received message from content script") console.log(m.greeting); }); } browser.runtime.onConnect.addListener(connected); browser.browserAction.onClicked.addListener(function() { portFromCS.postMessage({greeting: "they clicked the button!"}); });
同時に複数のコンテンツスクリプトが通信する場合、各接続を配列に格納するのが良いかもしれません。
// background-script.js var ports = [] function connected(p) { ports[p.sender.tab.id] = p //... } browser.runtime.onConnect.addListener(connected) browser.browserAction.onClicked.addListener(function() { ports.forEach(p => { p.postMessage({greeting: "they clicked the button!"}) }) });
ワンオフとコネクションベースのメッセージの選択は、拡張機能がどうメッセージを利用すると期待されるかに依存します。
推奨のベストプラクティスは次の通りです:
次のときにワンオフメッセージを使用…
次のときにコネクションベースメッセージを使用…
既定では、コンテンツスクリプトはページスクリプトが作成したオブジェクトにアクセスできませんが、DOM window.postMessage
と window.addEventListener
API を使ってページスクリプトと通信できます。
例えば:
// page-script.js var messenger = document.getElementById("from-page-script"); messenger.addEventListener("click", messageContentScript); function messageContentScript() { window.postMessage({ direction: "from-page-script", message: "Message from the page" }, "*");
// content-script.js window.addEventListener("message", function(event) { if (event.source == window && event.data && event.data.direction == "from-page-script") { alert("Content script received message: \"" + event.data.message + "\""); } });
これの完全な動作サンプルは、GitHub のデモページを訪れて指示に従ってください。
この方法で信頼できないウェブコンテンツと相互作用するには細心の注意が必要です!
拡張機能は強力な力を持つコードの権限があり、敵意のあるウェブページは簡単にこの力にアクセスします。
細かい例を作るには、メッセージを受け取ったコンテンツスクリプトがこのようなことを行うと仮定してください:
// content-script.js window.addEventListener("message", function(event) { if (event.source == window && event.data.direction && event.data.direction == "from-page-script") { eval(event.data.message); } });
今やページスクリプトはコンテンツスクリプトのすべての権限でコードを実行できます。
eval()
を呼ぶ場合、コンテンツスクリプトのコンテキストで動作します。 window.eval()
を呼ぶ場合、ページのコンテキストで動作します。, it runs code in the context of the content script.
例えば、こんなコンテンツスクリプトを考えてみます:
// content-script.js window.eval('window.x = 1;'); eval('window.y = 2'); console.log(`In content script, window.x: ${window.x}`); console.log(`In content script, window.y: ${window.y}`); window.postMessage({ message: "check" }, "*");
このコードは単に変数 x と y を、window.eval()
と eval()
を用いて作成し、値をログに出し、ページにメッセージします。
メッセージの受信に際し、ページスクリプトは同じ変数をログに出します:
window.addEventListener("message", function(event) { if (event.source === window && event.data && event.data.message === "check") { console.log(`In page script, window.x: ${window.x}`); console.log(`In page script, window.y: ${window.y}`); } });
Chrome では、こんな出力が生成されます:
In content script, window.x: 1 In content script, window.y: 2 In page script, window.x: undefined In page script, window.y: undefined
Firefox では、こんな出力が生成されます:
In content script, window.x: undefined In content script, window.y: 2 In page script, window.x: 1 In page script, window.y: undefined
同じことは setTimeout()
、setInterval()
、Function()
にも言えます。
ページのコンテキストでコードを実行するときは特に注意してください!
ページの環境が悪意をはらんだウェブページにコントロールされ、期待しない方法であなたが操作するオブジェクトを再定義するかもしれません。
// page.js redefines console.log var original = console.log; console.log = function() { original(true); }
// content-script.js calls the redefined version window.eval('console.log(false)');