web_accessible_resources 这个 manifest 键就是把特定文件开放给特定来源的白名单。本页说明哪些调用方需要条目、对象形式如何工作、glob 与运行时 URL 的行为,以及缺少条目时控制台会打印哪些行。
文件被读取的三种方式,以及哪一种需要声明
来自扩展页面。 popup、options 页面、side panel 或 devtools 页面运行在扩展来源上。它可以读取包里的任何文件,完全不涉及web_accessible_resources 条目。
来自 content script。 脚本自身的代码已经在运行,但它引入的资源并不因此被覆盖。指向 chrome-extension:// URL 的 img 元素、fetch、FontFace 或动态 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 导入、字体或新的静态资源后,重新审阅这份列表。

