首页 / 浏览器扩展开发入门教程 / content_scripts 声明

浏览器扩展开发入门教程

content_scripts 声明

本教程共 56 篇 · 第 9 篇 · 更新于 2026-08-13 · 约 7 分钟阅读

清单manifestcontent_scripts内容脚本matchesrun_at

本节目标:学完你能看懂并在 manifest.json 里写出 content_scripts 配置,清楚 matches、js、css、run_at 各自控制什么,知道如何让内容脚本只注入到想改的页面、在合适的时机运行。

内容脚本(content script)是扩展里唯一能直接碰网页 DOM 的角色。它通过 manifest.json 里的 content_scripts 字段”声明式”地挂到指定网页上:你不需要写任何注入逻辑,浏览器会在页面加载时按你的声明自动把脚本塞进去。这一节我们只讲声明部分——也就是写在清单里的那些字段,不涉及运行时怎么用 chrome.scripting 动态注入(那是后面的 API 章节)。

9-1 content_scripts 是声明式注入

所谓”声明式”,意思是你只在清单里列清楚规则,剩下的交给浏览器。比如你想给所有 nytimes 文章页加一个夜间模式按钮,不需要自己监听页面、不需要手动判断网址,只要在 content_scripts 里写好匹配规则和文件,浏览器就会在用户打开对应网页时自动加载你的脚本。

content_scripts 的值是一个数组,数组里每一项描述”一组脚本注入到哪些页面”。一个扩展可以声明多组,每组独立配置。下面是一份最小例子:

{
  "name": "My extension",
  "version": "1.0",
  "manifest_version": 3,
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "css": ["my-styles.css"],
      "js": ["content-script.js"]
    }
  ]
}

这里告诉浏览器:凡是匹配 https://.nytimes.com/ 的页面,先注入 my-styles.css,再注入 content-script.js。注意 listed 顺序——css 在 js 之前注入,且 css 在页面任何 DOM 构建或显示之前就生效,适合做样式覆盖。

Note

content_scripts 是”静态声明”。它和后面会讲到的 chrome.scripting.executeScript 动态注入是两套机制。本节只管声明,动态注入留给 API 章节。

9-2 matches:决定脚本注入到哪些页面

matches 是 content_scripts 里唯一必填的字段,类型是字符串数组。它用”匹配模式”(match patterns)描述哪些网址会被注入。匹配模式的写法是三段式:://

几个最常见的写法:

  • "<all_urls>":匹配所有以受支持协议(http/https/file)开头的网址。新手图省事常写这个,但我建议你尽量收窄,权限越小越安全、也越容易过审。
  • "*://*.nytimes.com/*":匹配 nytimes.com 及其所有子域的任意 http/https 页面。
  • "https://*.google.com/*":只匹配 https 子域,http 不匹配。

匹配模式有一些硬规则,写错浏览器会直接拒绝加载:

  1. scheme 位置只有四种合法写法:httphttpsfile,以及通配 *(只覆盖 http 和 https)。wswssftpdata 这类协议在 Chrome 里都不能写进匹配模式。
  2. host 里的 * 只能出现在开头,且要么单独成段,要么紧跟一个点,例如 *.example.com 合法,example.*.com 非法。
  3. path 部分不能缺省:*://*/* 合法,*://* 非法;带尾斜杠的空路径(如 https://*/)是合法的。
  4. 网址里的 # 片段(锚点)不参与匹配,写进模式里反而会失效。

详细对照可以看 MDN 的匹配模式表。我这里只强调最容易踩的点:别漏写 path 的 /*,也别在 host 中间放 *

Tip

想知道你的 matches 写得对不对,最实在的办法是加载扩展后打开目标网页,看脚本有没有跑起来。如果没跑,先怀疑 matches 没匹配上。

9-3 js 与 css:要注入的文件

js 和 css 都是字符串数组,元素是相对扩展根目录的文件路径。浏览器会按数组里的先后顺序注入,路径开头的斜杠 / 会被自动去掉。

{
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "css": ["styles/base.css", "styles/night.css"],
      "js": ["lib/helper.js", "content-script.js"]
    }
  ]
}

这段含义很直观:先注入 base.css、再 night.css;然后依次加载 helper.js 和 content-script.js。顺序很重要——如果你的主脚本依赖 helper 里定义的全局函数,helper 必须排在前面。

这里有两点要提醒:

  1. 文件必须真在扩展包里。声明了却没放对应文件,加载时浏览器会报错。
  2. 内容脚本运行在”隔离世界”(isolated world),它和网页自身的脚本不共享变量。这是安全设计,后面章节会展开。所以 js 里不能直接读网页脚本里定义的全局变量。
Note

css 注入发生在页面 DOM 构建之前,所以适合做”默认样式覆盖”。如果你想在脚本执行后才改样式,也可以在 js 里用 DOM API 动态加。

9-4 run_at:脚本何时注入

run_at 决定脚本在页面生命周期的哪个阶段注入,默认是 document_idle。它是个可选字段,取值有三个:

  • document_start:在 css 之后、但任何 DOM 或其他脚本运行之前注入。适合要做最早拦截、或抢在页面脚本之前改东西的场景。
  • document_end:DOM 构建完成后立即注入,但图片、框架等子资源还没加载完。
  • document_idle:首选值。浏览器会在 document_end 到 window.onload 刚触发这个区间里挑一个优化时机注入,具体时刻取决于页面复杂度,以加载速度为优先。这个时机 DOM 已经完整,所以你的脚本不需要再监听 onload。
{
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "run_at": "document_idle",
      "js": ["contentScript.js"]
    }
  ]
}

大多数情况用 document_idle 就够了。如果你的脚本要操作 DOM,等 DOM 完整再跑最省心。需要”页面一打开就抢先执行”才用 document_start,但那时 DOM 可能还没建好,别急着查元素。

Tip

用 document_idle 时,不用监听 window.onload,脚本保证在 DOM 完成后运行。如果确实要等 onload,可以用 document.readyState 判断它是否已经触发。

9-5 排除与微调字段

除了 matches,还有几个字段帮你精确控制注入范围,全是可选的。

exclude_matches 用来排除。比如匹配整个 nytimes 域,但把后台管理页排掉:

{
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "js": ["contentScript.js"]
    }
  ]
}

include_globs 和 exclude_globs 是 glob 通配,作用顺序在 matches 之后,是 Greasemonkey 风格 @include/@exclude 的模拟。glob 比匹配模式更灵活,比如 ? 匹配单个字符、* 匹配任意片段:

{
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "js": ["contentScript.js"]
    }
  ]
}

all_frames 控制是否注入到子框架(iframe)。默认 false,只注入顶层框架。设为 true 则页面里每个匹配的 iframe 都会注入一份:

{
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "all_frames": true,
      "js": ["contentScript.js"]
    }
  ]
}
Note

这几项组合使用时要小心优先级:matches 先筛,include_globs/exclude_globs 后调,exclude_matches 通常最后排除。逻辑复杂时建议少量多次加载测试,别凭想象。

9-6 world 与 match_about_blank 等进阶字段

还有几个可选字段,日常用得少但值得知道:

  • world:脚本运行的 JavaScript 世界。默认 ISOLATED(隔离世界,和网页脚本互不干扰)。若设 MAIN,则脚本跑在网页自身世界,能直接访问页面脚本的变量和函数——但风险也更高,仅在必须和页面脚本深度交互时使用。
  • match_about_blank:是否注入到 about:blank 框架。默认 false。仅当该框架的父框架或打开者框架命中 matches 时才考虑。
  • match_origin_as_fallback:是否注入到”由匹配来源创建、但 URL 不直接命中模式”的框架,比如 about:、data:、blob:、filesystem: 这类。默认 false。
{
  "content_scripts": [
    {
      "matches": ["https://*.google.com/*"],
      "match_origin_as_fallback": true,
      "js": ["contentScript.js"]
    }
  ]
}

这些字段都属于”特殊场景才用”。初学阶段把 matches、js、css、run_at 四样吃透,已经能覆盖绝大多数需求。

9-7 拼成一份完整清单片段

把这一节讲到的字段合起来,就是 content_scripts 的典型声明。它和前面学的 name、version、action、background 放在同一份 manifest.json 里:

{
  "manifest_version": 3,
  "name": "nytimes 夜间模式",
  "version": "1.0.0",
  "description": "给 nytimes 文章页加一个夜间阅读开关。",
  "action": {
    "default_title": "夜间模式"
  },
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "css": ["styles/night.css"],
      "js": ["content-script.js"],
      "run_at": "document_idle"
    }
  ]
}

到这里,content_scripts 的声明式配置就讲完了。小结一下:matches 决定”注入哪里”,js/css 决定”注入什么文件”,run_at 决定”何时注入”,其余字段都是为精确控制范围而存在。下一章我们讲 permissions 和 host_permissions——内容脚本要操作页面之外的资源(比如跨域请求、读写存储)时,还得靠权限声明来放行。