首页 / WXT 浏览器扩展框架教程 / 想注入就注入:编程式脚本注入

WXT 浏览器扩展框架教程

想注入就注入:编程式脚本注入

本教程共 45 篇 · 第 16 篇 · 更新于 2026-08-13 · 约 3 分钟阅读

WXTscriptingexecuteScript编程式注入权限runtime注册

本节目标:理解「声明式注册」和「编程式注入」两条路线,学会用 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')各有取舍:声明式简单,运行时灵活。
  • 演进路径:先声明式跑通,按需再升级成编程式。