由於內容指令碼是在網頁的環境中執行,而非擴充功能,因此通常需要某種方式與擴充功能的其餘部分通訊。舉例來說,RSS 閱讀器擴充功能可能會使用內容指令碼偵測網頁上是否有 RSS 動態消息,然後通知背景網頁,以便顯示該網頁的網頁動作圖示。
擴充功能與內容指令碼之間的通訊是透過訊息傳遞進行。雙方都可以監聽從另一端傳送的訊息,並在同一個管道上回覆。訊息可以包含任何有效的 JSON 物件 (空值、布林值、數字、字串、陣列或物件)。您可以透過簡單的 API 提出一次性要求,也可以使用較複雜的 API 建立長期連線,與共用情境交換多則訊息。如果知道其他擴充功能的 ID,也可以傳送訊息給該擴充功能,詳情請參閱跨擴充功能訊息一節。
簡單的一次性要求
如果只需要將單一訊息傳送至擴充功能的其他部分 (並視需要取得回覆),請使用簡化的 runtime.sendMessage 或 tabs.sendMessage。這可讓您從內容指令碼傳送一次性 JSON 可序列化訊息至擴充功能,反之亦然。選用的回呼參數可讓您處理另一端的任何回應。
從內容指令碼傳送要求如下所示:
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 函式的回呼。長期連線
有時,持續對話比單一要求和回覆更有用。 在這種情況下,您可以使用 runtime.connect 或 tabs.connect,分別從內容指令碼開啟擴充功能頁面的長期管道,反之亦然。管道可以選擇性命名,方便區分不同類型的連線。
其中一個用途可能是自動填寫表單的擴充功能。內容指令碼可以為特定登入作業開啟擴充功能頁面的管道,並為網頁上的每個輸入元素傳送訊息給擴充功能,要求填入表單資料。擴充功能可透過共用連線,保留連結內容指令碼傳來多則訊息的共用狀態。
建立連線時,每個端點都會取得 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 事件 (分頁中的每個框架各一次)。同樣地,如果使用 runtime.connect,onConnect 事件可能會觸發多次 (擴充功能程序中的每個影格各觸發一次)。
您可能想瞭解連線何時關閉,例如為每個開啟的通訊埠維護個別狀態時。為此,您可以監聽 runtime.Port.onDisconnect 事件。如果管道另一端沒有有效的連接埠,就會觸發這個事件。這會在下列情況發生:
- 另一端沒有 runtime.onConnect 的接聽程式。
- 含有通訊埠的分頁已卸載 (例如分頁已導覽)。
- 呼叫
connect的影格已卸載。 - 收到通訊埠的所有影格 (透過 runtime.onConnect) 都已卸載。
- 另一端呼叫 runtime.Port.disconnect。請注意,如果
connect呼叫在接收端產生多個通訊埠,且在其中任一通訊埠上呼叫disconnect(),則onDisconnect事件只會在傳送端的通訊埠觸發,不會在其他通訊埠觸發。
跨擴充功能傳訊
除了在擴充功能的不同元件之間傳送訊息,您也可以使用訊息 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/*"]
}
這樣一來,只要網頁符合您指定的網址模式,就能使用訊息 API。網址模式至少須包含第二層網域,也就是說,系統禁止使用「*」、「*.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 或 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 應用程式如何與原生應用程式通訊。如需更多範例及查看原始碼的說明,請參閱「範例」。