content_scripts 声明
本教程共 56 篇 · 第 9 篇 · 更新于 2026-08-13 · 约 7 分钟阅读
本节目标:学完你能看懂并在 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 构建或显示之前就生效,适合做样式覆盖。
Notecontent_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 不匹配。
匹配模式有一些硬规则,写错浏览器会直接拒绝加载:
- scheme 位置只有四种合法写法:
http、https、file,以及通配*(只覆盖 http 和 https)。ws、wss、ftp、data这类协议在 Chrome 里都不能写进匹配模式。 - host 里的
*只能出现在开头,且要么单独成段,要么紧跟一个点,例如*.example.com合法,example.*.com非法。 - path 部分不能缺省:
*://*/*合法,*://*非法;带尾斜杠的空路径(如https://*/)是合法的。 - 网址里的
#片段(锚点)不参与匹配,写进模式里反而会失效。
详细对照可以看 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 必须排在前面。
这里有两点要提醒:
- 文件必须真在扩展包里。声明了却没放对应文件,加载时浏览器会报错。
- 内容脚本运行在”隔离世界”(isolated world),它和网页自身的脚本不共享变量。这是安全设计,后面章节会展开。所以 js 里不能直接读网页脚本里定义的全局变量。
Notecss 注入发生在页面 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——内容脚本要操作页面之外的资源(比如跨域请求、读写存储)时,还得靠权限声明来放行。