想注入就注入:编程式脚本注入
本教程共 45 篇 · 第 16 篇 · 更新于 2026-08-13 · 约 3 分钟阅读
本节目标:理解「声明式注册」和「编程式注入」两条路线,学会用
browser.scripting.executeScript按需注入 WXT 构建出的脚本,并拿到main函数的返回值。
第 11 章讲的 matches 注册,是「声明式」:浏览器按规则自动注入。但有些场景不适合声明式——比如用户点了一下按钮才注入,或者注入条件要运行时判断。这时候要用「编程式注入」。
声明式 vs 编程式
两条路线的差别很直观:
| 维度 | 声明式(matches) | 编程式(executeScript) |
|---|---|---|
| 触发方式 | 浏览器自动 | 代码主动调用 |
| 时机 | 页面加载时 | 任意时刻 |
| 适用场景 | 固定站点固定逻辑 | 用户操作触发、条件注入 |
声明式简单可靠,但不够灵活;编程式把控制权完全交给你。两者可以混用,不是二选一。
scripting API 基础
编程式注入的核心是 browser.scripting.executeScript。它属于浏览器原生 API(Chrome 88+ / Firefox 101+,MV3 可用),WXT 的官方文档把基础用法指向浏览器文档:Chrome 见 https://developer.chrome.com/docs/extensions/reference/api/scripting,Firefox 见 MDN。
使用前需要在 manifest 里声明权限:
export default defineConfig({
manifest: {
permissions: ["scripting"],
host_permissions: ["https://example.com/*"],
},
});
scripting 权限之外,还要有目标站点的主机权限(host_permissions),否则浏览器拒绝执行。权限的细节在 §21 展开。
一次典型调用长这样:
// entrypoints/background.ts
const results = await browser.scripting.executeScript({
target: { tabId },
files: ["content-scripts/example.js"],
});
Note原始 API 返回的是
InjectionResult[]数组,每个 frame 一个结果,main的返回值在result字段里(results[0].result)。官方示例为了简洁直接打印整个返回值,实际取值时留意一下。
WXT 的返回值约定
WXT 的脚本入口(内容脚本、unlisted 脚本)都遵循同一个约定:main 函数返回什么,executeScript 就能拿到什么。这让「注入 + 取结果」变成了一件自然的事:
// entrypoints/background.ts
const res = await browser.scripting.executeScript({
target: { tabId },
files: ["content-scripts/example.js"],
});
console.log(res); // "Hello John!"
// entrypoints/example.content.ts
export default defineContentScript({
registration: "runtime",
main(ctx) {
console.log("Script was executed!");
return "Hello John!";
},
});
注意 files 里写的是构建后的路径。WXT 会把内容脚本输出到 content-scripts/*.js,unlisted 脚本输出到 {name}.js(如 §13 所述)。想确认实际路径,构建一次后看 .output/{browser}-{mv}/ 目录即可。
registration: ‘runtime’
上面例子里的 registration: "runtime" 值得单独讲。默认内容脚本是 registration: "manifest",即构建时写进 manifest,浏览器自动注入。改成 "runtime" 后,脚本不会出现在 manifest 的 content_scripts 里,完全由你在代码中通过 executeScript 按需执行。
这个模式和 §13 的 unlisted 脚本很像,区别是:unlisted 脚本不经过 defineContentScript(没有 matches 等配置),而 registration: "runtime" 的内容脚本保留完整的入口配置,只是把「何时注入」交给了你。
典型用途:需要精确控制注入时机和频次的功能,比如用户开启后才注入、或每帧只注入一次。
其他常用能力
browser.scripting 命名空间下还有几个常用方法:
insertCSS/removeCSS:按需注入/移除样式,比整页 style 标签更干净registerContentScripts/updateContentScripts/unregisterContentScripts:运行时动态注册、更新、注销声明式脚本,适合「规则由用户配置」的功能getRegisteredContentScripts:查询当前已注册的脚本
Tip每次
executeScript注入的脚本都独立运行。同一个页面注入多次,会得到多个实例,记得在脚本里做好幂等判断(比如检查标记元素是否已存在),避免重复初始化。
怎么选
- 功能对固定站点生效、无交互门槛 → 声明式(
registration: "manifest") - 用户点击后才注入、或需要拿返回值 → 编程式(
executeScript) - 规则动态变化(如用户自定义注入站点)→
registerContentScripts运行时管理
先声明式,按需升级成编程式,是多数扩展的自然演进路径。
小结
executeScript按需注入,还能从执行结果里取回执。- 声明式(manifest 注册)与运行时(
registration: 'runtime')各有取舍:声明式简单,运行时灵活。 - 演进路径:先声明式跑通,按需再升级成编程式。