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 导入、字体或新的静态资源后,重新审阅这份列表。
创建一个把扩展字体加载进页面的项目,这是这个键最小的完整示例:

参见