訊息傳遞

由於內容指令碼是在網頁環境 (而非擴充功能) 中執行,因此通常需要透過某種方式與擴充功能的其餘部分進行通訊。舉例來說,RSS 閱讀器擴充功能可能會使用內容指令碼偵測網頁上是否有 RSS 動態消息,然後通知背景頁面以顯示該網頁的網頁動作圖示。

擴充功能及其內容指令碼之間的通訊是使用訊息傳遞。雙方都能監聽其他端的訊息,並在相同的管道上回覆。訊息可包含任何有效的 JSON 物件 (空值、布林值、數字、字串、陣列或物件)。您可以透過簡易的 API 處理一次性要求,而較為複雜的 API 可讓您有長期連線,以共用情境交換多則訊息。如果您知道擴充功能 ID,也可以傳送訊息至其他擴充功能,詳情請參閱「跨擴充功能訊息」一節。

簡易的一次性要求

如果您只需要傳送單一訊息給擴充功能的其他部分 (並視需要接收回應),應使用簡化的 runtime.sendMessagetabs.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 事件處理常式。

注意:如果多個頁面正在監聽 onMessage 事件,則只有第一個特定事件呼叫 sendResponse() 才會成功傳送回應。並忽略對該事件的所有其他回應。
注意:sendResponse 回呼只有在同步使用,或是事件處理常式傳回 true 以表示會非同步回應時,才算有效。如果沒有任何處理常式傳回 true,或 sendResponse 回呼經過垃圾收集,系統就會自動叫用 sendMessage 函式的回呼。

長期連線

有時候,擁有超過單一要求和回應的對話會很有用。在此情況下,您可以從內容指令碼開啟長期頻道,也可以分別使用 runtime.connecttabs.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.connectruntime.connectruntime.connectNative 時,系統會建立通訊埠。此通訊埠可立即用來透過 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.onMessageExternalruntime.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」、「*.com」、「*.co.uk」和「*.appspot.com」等主機名稱模式。在網頁上,使用 runtime.sendMessageruntime.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.onMessageExternalruntime.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 應用程式如何與原生應用程式進行通訊。如需更多範例和有關查看原始碼的說明,請參閱範例