Skip to main content
在你另行宣告之前,擴充功能裡的每個檔案都只屬於你的擴充功能。網頁無法載入它,執行在該頁面裡的 content script 也不行。web_accessible_resources 這個 manifest 鍵就是把特定檔案開放給特定來源的白名單。本頁說明哪些呼叫端需要項目、物件形式如何運作、glob 與執行階段 URL 的行為,以及缺少項目時主控台會印出哪些行。

檔案被讀取的三種方式,以及哪一種需要宣告

來自擴充功能頁面。 popup、options 頁面、side panel 或 devtools 頁面執行在擴充功能來源上。它可以讀取套件裡的任何檔案,完全不涉及 web_accessible_resources 項目。 來自 content script。 腳本本身的程式碼已經在執行,但它引入的資源並不因此被涵蓋。指向 chrome-extension:// URL 的 img 元素、fetchFontFace 或動態 import() 都是資源載入,因此該檔案必須被列出。 來自頁面自己的程式碼。 頁面 MAIN world 中的任何東西,包括你用 world: "MAIN" 注入的腳本,都屬於網頁程式碼。它需要該檔案被列出,也需要頁面來源落在 matches 之內。 要記住的規則是:瀏覽器依誰去取這個 URL 來判斷,而不是依誰寫了這段程式碼。

Manifest 片段

Manifest V3 接受一個物件陣列。每個物件把一組 resources 與允許讀取它們的 matches 樣式配對:
  • resources 列出相對於擴充功能根目錄的路徑。相對路徑會從存放 manifest.json 的資料夾開始解析。
  • matches 限定哪些頁面來源可以讀取這些檔案。在功能允許的範圍內把它寫得盡量窄。
  • use_dynamic_url 會要求瀏覽器給出一個按工作階段輪換的 URL,讓頁面無法用固定 id 為你的擴充功能建立指紋。請把它當作 Chromium 欄位。
resources 裡允許使用 glob,Extension.js 會原樣保留它們。它不會把 fonts/*.woff2 展開成明確的檔案清單,因此產出的 manifest 與你的原始碼帶著同一個樣式。當你已經知道檔名時,明確列出仍然比較安全。 在正規化過程中,Extension.js 會丟棄沒有 resources 陣列的 Manifest V3 項目,因為瀏覽器無法據此運作。

在執行階段建立 URL

不要手寫 chrome-extension:// URL。向執行階段索取:
/images/logo.png 這樣的根絕對路徑是穩定寫法,也是 路徑解析 會為你改寫的形式。在 content script 裡,同樣的呼叫是到達該檔案的唯一正確方式,因為注入的樣式表或元素中的裸 /images/logo.png 會相對於宿主頁面解析。CSS 用一個網頁字型走完了這個情境。 在 Firefox 上這個輔助函式更重要。Firefox 為每次安裝在 moz-extension:// 來源裡產生隨機 UUID,因此你從某個設定檔複製來的 URL 在其他設定檔裡都是錯的。

各瀏覽器差異

在 Safari 上,只啟用擴充功能還不夠。在你授予網站存取權之前,頁面上不會執行 content script,因此根本不會有人來請求這個資源。啟用與授權步驟請見 Safari 如果你的原始碼是以 browser.* 撰寫,同時也要建置 Chromium 目標,請傳入 --polyfill,讓該命名空間在那裡存在。請見 跨瀏覽器相容

你會看到的主控台錯誤

把你看到的那一行複製去搜尋。每一行對應一個原因。 Denying load of chrome-extension://<id>/images/logo.png. Resources must be listed in the web_accessible_resources manifest key. 該檔案不在任何 resources 陣列裡,或者請求它的頁面在 matches 之外。加上這個路徑,然後確認樣式涵蓋了該頁面來源。 GET chrome-extension://invalid/ net::ERR_FAILED 同一次拒絕在網路面板中的樣子。Chromium 會把被封鎖的擴充功能 URL 改寫成 chrome-extension://invalid/,因此請求裡看不到 id。要修的是 manifest 項目,而不是這次 fetch。 Security Error: Content at https://example.com/ may not load or link to moz-extension://<uuid>/images/logo.png. 同一個問題在 Firefox 上的形式。附加元件沒有列出該檔案,因此頁面不被允許連結到它。 Failed to load resource: net::ERR_FILE_NOT_FOUND 項目是對的,但產物裡沒有這個檔案。到 dist/<browser>/ 檢查你宣告的那個確切路徑。 Uncaught ReferenceError: browser is not defined 一次 browser.runtime.getURL 呼叫落到了沒有 polyfill 的 Chromium 目標上。請用 --polyfill 建置,或者改呼叫 chrome.runtime.getURL

Extension.js 的做法

Extension.js 會把你宣告的內容與建置發現的內容合併,然後為每個目標正規化結果:
  • 當執行階段需要頁面讀取這些資源時,content script 匯入的資源、content script 的 CSS 產物以及產出的字型會被自動加入。
  • 路徑會為產物正規化。Extension.js 會去掉 public/ 前綴和開頭的斜線,因此項目指向檔案真正落地的位置。
  • glob 保持原樣,帶連接埠或連接埠萬用字元的比對樣式(例如 http://localhost:3000/*)會被接受而不是被拒絕。
  • 在開發中,Extension.js 會為項目集合打補丁,讓重新載入與熱模組替換所需的資源保持可達。該補丁僅用於開發。請見 重新載入與 HMR
自動合併很方便,但它不是審閱。發布前請讀一遍產出的 dist/<browser>/manifest.json,確認公開出去的集合正是你打算公開的集合:
  • matches 限定在確實需要該檔案的網域上。
  • 優先使用明確的資源清單,而不是寬鬆的 glob。
  • 讓敏感檔案留在白名單之外,改從擴充功能頁面提供它們。
  • 每次新增 content script 匯入、字型或新的靜態資源後,重新審閱這份清單。
建立一個把擴充功能字型載入頁面的專案,這是這個鍵最小的完整範例:

參見