コンテンツ スクリプトは拡張機能ではなくウェブページのコンテキストで実行されるため、多くの場合、拡張機能の他の部分となんらかの方法で通信する必要があります。たとえば、RSS リーダーの拡張機能では、コンテンツ スクリプトを使用してページ上の RSS フィードの存在を検出し、そのページにページ アクション アイコンを表示するためにバックグラウンド ページに通知できます。
拡張機能とそのコンテンツ スクリプト間の通信は、メッセージの受け渡しによって機能します。どちらの側も相手側から送信されたメッセージをリッスンし、同じチャネルで応答できます。メッセージには、任意の有効な JSON オブジェクト(null、ブール値、数値、文字列、配列、オブジェクト)を含めることができます。1 回限りのリクエスト用のシンプルな API と、長時間の接続を維持して複数のメッセージを共有コンテキストで交換できるようにする複雑な API があります。ID がわかっている場合は、別の拡張機能にメッセージを送信することもできます。これについては、クロス拡張メッセージのセクションをご覧ください。
シンプルな 1 回限りのリクエスト
拡張機能の別の部分に 1 つのメッセージを送信するだけで(必要に応じてレスポンスを返す)、簡素化された runtime.sendMessage または tabs.sendMessage を使用する必要があります。これにより、JSON でシリアル化可能な 1 回限りのメッセージをコンテンツ スクリプトから拡張機能に、またはその逆に送信できます。オプションのコールバック パラメータを使用すると、相手側からのレスポンスを処理できます(存在する場合)。
コンテンツ スクリプトからリクエストを送信する場合、次のようになります。
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
拡張機能からコンテンツ スクリプトにリクエストを送信する方法はよく似ていますが、送信先のタブを指定する必要があります。この例では、選択したタブのコンテンツ スクリプトにメッセージを送信する方法を示しています。
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
受信側では、runtime.onMessage イベント リスナーを設定してメッセージを処理する必要があります。コンテンツ スクリプトまたは拡張機能のページからも同じです。
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello")
sendResponse({farewell: "goodbye"});
}
);
上記の例では、sendResponse
は同期的に呼び出されています。sendResponse
を非同期で使用する場合は、return true;
を onMessage
イベント ハンドラに追加します。
sendResponse
コールバックは、同期的に使用されている場合、または非同期で応答することを示すためにイベント ハンドラが true
を返した場合のみ有効です。true を返すハンドラがない場合、または sendResponse
コールバックがガベージ コレクションの対象である場合、sendMessage
関数のコールバックが自動的に呼び出されます。長期間の接続
1 回のリクエストとレスポンスよりも長く続く会話が便利な場合もあります。この場合は、runtime.connect または tabs.connect を使用して、存続期間が長いチャンネルをコンテンツ スクリプトから拡張機能ページ(またはその逆)で開くことができます。チャネルに名前を付けることもできます。これにより、接続の種類を区別できます。
ユースケースの 1 つとして、自動フォーム入力拡張機能があります。コンテンツ スクリプトは、特定のログインに対応する拡張機能ページへのチャネルを開き、ページ上の各入力要素の拡張機能にメッセージを送信して、記入するフォームデータをリクエストできます。共有接続により、拡張機能は、コンテンツ スクリプトから送信される複数のメッセージをリンクする共有状態を維持できます。
接続の確立時に、両端に runtime.Port オブジェクトが割り当てられます。このオブジェクトは、その接続を介したメッセージの送受信に使用されます。
コンテンツ スクリプトからチャンネルを開き、メッセージを送信して確認する手順は次のとおりです。
var port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
if (msg.question == "Who's there?")
port.postMessage({answer: "Madame"});
else if (msg.question == "Madame who?")
port.postMessage({answer: "Madame... Bovary"});
});
拡張機能からコンテンツ スクリプトにリクエストを送信する方法はよく似ていますが、接続するタブを指定する必要があります。上記の例では、接続の呼び出しを tabs.connect に置き換えてください。
受信側の接続を処理するには、runtime.onConnect イベント リスナーを設定する必要があります。これは、コンテンツ スクリプトや拡張機能ページでも同じように見えます。拡張機能の別の部分が「connect()」を呼び出すと、このイベントは、接続を介したメッセージの送受信に使用できる runtime.Port オブジェクトとともに呼び出されます。受信接続に応答する方法を以下に示します。
chrome.runtime.onConnect.addListener(function(port) {
console.assert(port.name == "knockknock");
port.onMessage.addListener(function(msg) {
if (msg.joke == "Knock knock")
port.postMessage({question: "Who's there?"});
else if (msg.answer == "Madame")
port.postMessage({question: "Madame who?"});
else if (msg.answer == "Madame... Bovary")
port.postMessage({question: "I don't get it."});
});
});
ポートの寿命
ポートは拡張機能のさまざまな部分間の双方向通信手段として設計されており、(トップレベルの)フレームを最小部分と見なします。tabs.connect、runtime.connect、または runtime.connectNative を呼び出すと、Port が作成されます。このポートは、postMessage を介して相手側にメッセージを送信するためにすぐに使用できます。
タブに複数のフレームがある場合、tabs.connect を呼び出すと、runtime.onConnect イベントが複数回(タブ内のフレームごとに 1 回)呼び出されます。同様に、runtime.connect を使用した場合、onConnect イベントが複数回(拡張プロセスのフレームごとに 1 回)発生する可能性があります。
開いているポートごとに個別の状態を維持している場合など、接続が閉じられたタイミングを把握する必要がある場合があります。そのためには、runtime.Port.onDisconnect イベントをリッスンします。このイベントは、チャネルの反対側に有効なポートがない場合に発生します。これは、次の状況で発生します。
- 相手側に runtime.onConnect のリスナーはありません。
- ポートを含むタブがアンロードされる(タブに移動した場合など)。
connect
が呼び出されたフレームがアンロードされた。- ポートを受信したすべてのフレーム(runtime.onConnect 経由)がアンロードされた。
- runtime.Port.disconnect は相手側から呼び出されます。
connect
呼び出しの結果がレシーバー側の複数のポートで、disconnect()
がこれらのポートのいずれかで呼び出された場合、onDisconnect
イベントは送信側のポートでのみ発生し、他のポートでは発生しません。
クロス拡張メッセージ
Messaging API は、拡張機能の異なるコンポーネント間でメッセージを送信するだけでなく、他の拡張機能と通信するためにも使用できます。これにより、他の拡張機能が活用できる公開 API を公開できます。
受信リクエストと接続のリッスンは、runtime.onMessageExternal または runtime.onConnectExternal メソッドを使用する以外は内部ケースの場合と似ています。それぞれの例を次に示します。
// For simple requests:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.id == blocklistedExtension)
return; // don't allow this extension access
else if (request.getTargetData)
sendResponse({targetData: targetData});
else if (request.activateLasers) {
var success = activateLasers();
sendResponse({activateLasers: success});
}
});
// For long-lived connections:
chrome.runtime.onConnectExternal.addListener(function(port) {
port.onMessage.addListener(function(msg) {
// See other examples for sample onMessage handlers.
});
});
同様に、別の拡張機能にメッセージを送信する方法は、拡張機能内でメッセージを送信する場合と似ています。唯一の違いは、通信する拡張機能の ID を渡す必要があることです。次に例を示します。
// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
function(response) {
if (targetInRange(response.targetData))
chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
}
);
// Start a long-running conversation:
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);
ウェブページからのメッセージの送信
クロス拡張メッセージと同様に、アプリや拡張機能では通常のウェブページからのメッセージを受信して応答できます。この機能を使用するには、まず、通信するウェブサイトを manifest.json で指定する必要があります。例:
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
これにより、指定した URL パターンに一致するすべてのページに Messaging API が公開されます。URL パターンには、少なくともセカンドレベル ドメインを含める必要があります。「*」、「*.com」、「*.co.uk」、「*.appspot.com」などのホスト名パターンは禁止されています。ウェブページから runtime.sendMessage または runtime.connect API を使用して、特定のアプリまたは拡張機能にメッセージを送信します。例:
// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
if (!response.success)
handleError(url);
});
アプリや拡張機能から、runtime.onMessageExternal API または runtime.onConnectExternal API を使用して、ウェブページからのメッセージをリッスンできます。これは、クロス拡張メッセージと同様です。接続を開始できるのはウェブページのみです。次に例を示します。
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.url == blocklistedWebsite)
return; // don't allow this web page access
if (request.openUrlInEditor)
openUrl(request.openUrlInEditor);
});
ネイティブ メッセージング
拡張機能とアプリは、ネイティブ メッセージング ホストとして登録されたネイティブ アプリとメッセージを交換できます。この機能について詳しくは、ネイティブ メッセージングをご覧ください。
セキュリティ上の考慮事項
コンテンツ スクリプトの信頼性が低い
拡張機能の背景ページほど、コンテンツ スクリプトの信頼性は低くなります(たとえば、悪意のあるウェブページによって、コンテンツ スクリプトが実行されるレンダラ プロセスが不正使用される可能性があります)。コンテンツ スクリプトのメッセージは攻撃者によって作成された可能性があると想定し、すべての入力を検証してサニタイズします。コンテンツ スクリプトに送信されたデータがウェブページに漏洩すると仮定します。コンテンツ スクリプトから受信したメッセージによってトリガーできる特権アクションの範囲を制限する。
クロスサイト スクリプティング
コンテンツ スクリプトや他の拡張機能からメッセージを受信する際に、スクリプトがクロスサイト スクリプティングの被害に遭わないように注意する必要があります。このアドバイスは、拡張機能のバックグラウンド ページ内で実行されているスクリプトと、他のウェブオリジン内で実行されているコンテンツ スクリプトに当てはまります。特に、以下のような危険な API は使用しないでください。
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// WARNING! Might be evaluating an evil script!
var resp = eval("(" + response.farewell + ")");
});
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = response.farewell;
});
代わりに、スクリプトを実行しない、より安全な API の使用をおすすめします。
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// JSON.parse does not evaluate the attacker's scripts.
var resp = JSON.parse(response.farewell);
});
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// innerText does not let the attacker inject HTML elements.
document.getElementById("resp").innerText = response.farewell;
});
例
メッセージを介したコミュニケーションの簡単な例については、examples/api/messaging ディレクトリをご覧ください。ネイティブ メッセージングのサンプルには、Chrome アプリがネイティブ アプリと通信する仕組みが示されています。その他の例とソースコードの表示方法については、サンプルをご覧ください。