Skip to main content
一個擴充功能執行在多個彼此隔離的情境裡。service worker 持有特權程式碼,content script 看得見頁面,popup 或 options 頁面承載介面。它們不共用記憶體,因此每個跨越邊界的值都是以訊息的形式跨越的。本頁說明每個方向該用哪個呼叫、決定非同步回覆能否送達的規則,以及一次失敗的交換會印出哪些主控台行。

傳遞訊息的三種方式

chrome.runtime.sendMessage 一次性。它會送達每一個帶有 runtime.onMessage 監聽器的擴充功能情境,也就是 service worker、popup、options 頁面,以及任何其他已開啟的擴充功能頁面。它不會送達 content script。 chrome.tabs.sendMessage 一次性,瞄準一個分頁。從 service worker 或另一個擴充功能頁面呼叫它,去送達那個分頁裡的 content script,還可以透過 frameId 只送達一個 frame。content script 必須已經在那裡執行。 chrome.runtime.connectchrome.tabs.connect 一個保持開啟的具名 port。兩端可以隨時投遞訊息,兩端也都透過 port.onDisconnect 得知拆除。 在你自己的情境之間傳訊息不需要額外權限。它需要的是對面有一個活著的監聽器。

非同步回應規則

runtime.onMessage 監聽器預設是同步回覆的。它一回傳,通道就關閉,之後再呼叫 sendResponse 會被丟棄。回傳 true 才是讓通道保持開啟的做法:
由這條規則可以推出兩點。一個 async 監聽器函式回傳的是 Promise,而 Promise 不是 true,因此在 Chromium 上,一個稍後才呼叫 sendResponseasync 監聽器仍然會關閉通道。請像上面那樣在監聽器內部做非同步工作並回傳 true。在呼叫端這一側,省略回呼時 chrome.runtime.sendMessage 會回傳 Promise,所以那裡直接 await 即可,與以上這些無關。 給每則訊息一個明確的 type,在處理之前驗證酬載,並在做特權工作之前檢查 sender。content script 是你的程式碼,但它轉發的資料來自你無法控制的頁面。
長時間的交換改用 port,port 帶有名字,因此一個監聽器可以分辨它的呼叫端:

各瀏覽器差異

在 Safari 上,在你授予擴充功能網站存取權之前 content script 不會執行,因此在那之前 tabs.sendMessage 沒有接收方。請見 Safari 如果你的原始碼是以 browser.* 撰寫,同時也要建置 Chromium 目標,請傳入 --polyfill。請見 跨瀏覽器相容

你會看到的主控台錯誤

把你看到的那一行複製去搜尋。每一行對應一個原因。 Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist. 沒有任何一方在監聽。要嘛目標情境沒有 runtime.onMessage 監聽器,要嘛對 tabs.sendMessage 而言,還沒有 content script 被注入那個分頁。在擴充功能載入之前就已開啟的分頁在重新載入前沒有 content script,而像 chrome:// 或擴充功能商店這樣的受限頁面永遠不會有。請先用 chrome.scripting.executeScript 注入,或者處理這次失敗,做法請見 在執行階段注入腳本 Unchecked runtime.lastError: The message port closed before a response was received. 監聽器收到了訊息,卻在沒有讓通道保持開啟的情況下回傳了。請從監聽器回傳 true,或者同步作答。 Uncaught Error: Extension context invalidated. 擴充功能重新載入時,舊的 content script 還在頁面裡執行。那段程式碼現在指向一個不再存在的執行階段,它發出的每個 chrome.* 呼叫都會擲出錯誤。請重新載入分頁。在 extension dev 期間,這發生在一次被歸類為完整重新載入的變更之後,重新載入與 HMR 對此有說明。 Attempting to use a disconnected port objectonDisconnect 觸發之後,仍有人向這個 port 投遞訊息。請在 onDisconnect 處理常式裡清掉你的參照,需要時再開啟一個新的 port。

Extension.js 的做法

Extension.js 編譯呼叫這些 API 的程式碼,並不包裝它們。協定是你自己的。工具鏈改變的是開發循環:
  • 一次被歸類為 content-scripts 的變更會重新注入受影響的項目並拆除上一次的掛載,因此監聽器是被取代而不是被疊加。一次被歸類為 full 的變更會重新載入擴充功能,而重新載入前就留在頁面上的 content script 會變成上面那種 Extension context invalidated 情況。請重新載入分頁。
  • extension dev 之下,從 scripts/ 資料夾注入的腳本會在編輯後被重播,因此你在被注入腳本裡開啟的 port 會由新的副本重新開啟。請見 特殊資料夾
  • service worker 與 content script 的主控台輸出會被轉發到同一個通道,因此跨越邊界的訊息更容易在單一串流裡追蹤。
把特權工作留在 service worker 裡,讓 content script 保持狹窄,讓酬載保持小。轉發整頁快照的 content script 比只轉發功能所需三個欄位的那個更慢,攻擊面也更大。

參見