跳转到主要内容
扩展页面运行在严格的内容安全策略下:不允许 eval,不允许 new Function,也不允许动态编译的代码。当某个功能确实需要这些能力时(模板引擎、代码 playground、运行 AI 生成的片段),Manifest V3 提供了 sandbox 页面:用放弃 chrome.* API 访问权限换取一份更宽松 CSP 的扩展页面。

声明页面

manifest.json
{
  "sandbox": {
    "pages": ["sandbox.html"]
  }
}
Extension.js 会像编译任何其他 manifest 中声明的 HTML 入口一样编译 sandbox.html:直接引用你的 .ts/.tsx 即可构建。入口写法参见 HTML 指南

sandbox 页面能做什么、不能做什么

能力Sandbox 页面
evalnew Function
独立 origin(与其他页面隔离)
chrome.* 扩展 API
直接访问 chrome.storage
与宿主页面进行 postMessage 通信
sandbox 页面运行在一个独立 origin 中,因此它无法读取扩展的 storage、调用扩展 API,也无法触及其他页面的 DOM。所有交互都通过消息传递完成。

嵌入与通信

在一个普通扩展页面中用 iframe 加载 sandbox,然后通过 postMessage 通信:
newtab.html
<iframe id="sandbox" src="sandbox.html"></iframe>
newtab.ts
const frame = document.getElementById("sandbox") as HTMLIFrameElement;

frame.addEventListener("load", () => {
  frame.contentWindow?.postMessage({ template: "Hello {{name}}" }, "*");
});

window.addEventListener("message", (event) => {
  console.log("rendered:", event.data.html);
});
sandbox.ts
window.addEventListener("message", (event) => {
  const render = new Function("name", `return \`${event.data.template}\`;`);
  event.source?.postMessage({ html: render("world") }, { targetOrigin: "*" });
});
嵌入它的页面仍然保留 chrome.* 访问权限,并扮演中间人的角色:它读取 storage、调用 API,并把纯数据传入和传出 sandbox。

别搞混了:background.type

一个常见的混淆:"background": {"type": "module"} 和 sandbox 化毫无关系。它声明 background service worker 是一个 ES 模块,这样 import 语句在注册时就能正常工作。当你的 background 入口使用 ESM 语法时,Extension.js 会自动设置它,因此你几乎不需要手写。如果你的疑惑是 service worker 里的 import 失败,请看 Background 脚本;如果是关于运行动态代码,那你就来对地方了。

自定义 sandbox CSP

你可以通过 manifest 中的 content_security_policy.sandbox 进一步放宽或收紧 sandbox 的 CSP。请把 script-src 收得尽可能窄——只满足该功能所需即可;sandbox 的目的是把动态代码关起来,而不是为了绕过安全审查。安全清单 列出了审核者会关注的点。

另见