内容脚本是在网页环境中运行的文件。使用标准 Document 对象模型 (DOM) 结合使用,它们能够读取浏览器访问的网页的详细信息, 对它们进行更改,并将信息传递给其父级扩展程序。
了解内容脚本的功能
内容脚本可以直接访问以下扩展程序 API:
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
内容脚本无法直接访问其他 API。但可以通过与扩展程序的其他部分互发消息来间接访问这些信息。
您也可以使用
API,例如 fetch()
。为此,您需要将其声明为
可通过网络访问的资源。请注意,这样也会将资源公开给
在同一网站上运行的第一方或第三方脚本。
在孤立世界中工作
内容脚本位于隔离的世界中,因此内容脚本可以对其 并且不与网页或其他扩展程序冲突的 JavaScript 环境内容脚本。
扩展程序可能会使用类似于以下示例的代码在网页中运行。
webPage.html
<html>
<button id="mybutton">click me</button>
<script>
var greeting = "hello, ";
var button = document.getElementById("mybutton");
button.person_name = "Bob";
button.addEventListener(
"click", () => alert(greeting + button.person_name + "."), false);
</script>
</html>
该扩展程序可以使用 注入脚本部分。
content-script.js
var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
"click", () => alert(greeting + button.person_name + "."), false);
实施这项更改后,这两项提醒会在用户点击按钮时依次显示。
注入脚本
使用静态声明进行注入
在 manifest.json 中,为应自动处理的脚本使用静态内容脚本声明 在一组知名网页上运行
静态声明的脚本在清单中的 "content_scripts"
键下注册。
此类文件可包含 JavaScript 文件和/或 CSS 文件。所有自动运行的内容脚本必须指定
匹配模式。
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"css": ["my-styles.css"],
"js": ["content-script.js"]
}
],
...
}
名称 | 类型 | 说明 |
---|---|---|
matches |
字符串数组 | 必需。指定要将此内容脚本注入哪些页面。如需详细了解这些字符串的语法,请参阅匹配模式 和匹配模式和 glob,了解如何排除 网址。 |
css |
字符串数组 | 可选。要注入到匹配页面的 CSS 文件列表。这些是 在构建或显示任何 DOM 之前,按照它们在此数组中出现的顺序注入 。 |
js |
|
可选。要注入到匹配页面的 JavaScript 文件的列表。文件极客 会按照它们在此数组中出现的顺序进行注入。此列表中的每个字符串都必须包含 扩展程序根目录中资源的相对路径。前导斜杠 (`/`) 为 已自动剪辑。 |
run_at |
RunAt | 可选。指定应在何时将脚本注入网页。默认值为
document_idle 。 |
match_about_blank |
布尔值 | 可选。脚本是否应注入到 about:blank 帧中
其中的父框架或 Opener 框架与
matches 。默认值为 false。 |
match_origin_as_fallback |
布尔值 |
可选。脚本是否应在
由匹配源创建,但其网址或源不能直接
与模式匹配。这包括采用不同架构的帧,如
about: 、data: 、blob: 和
filesystem: 。另请参阅
在相关帧中注入。
|
world |
ExecutionWorld |
可选。用于执行脚本的 JavaScript 环境。默认值为 ISOLATED 。另请参阅
在孤立的世界中工作。
|
使用动态声明进行注入
当内容脚本的匹配模式 未知或不应总是在已知主机上注入内容脚本的情况。
在 Chrome 96 中引入,动态声明类似于静态声明
声明不同,但使用以下代码向 Chrome 注册内容脚本对象:
方法,而不是在 chrome.scripting
命名空间中
manifest.json.通过 Scripting API,扩展程序开发者
更改为:
与静态声明一样,动态声明可以包括 JavaScript 文件和/或 CSS 文件。
service-worker.js
chrome.scripting
.registerContentScripts([{
id: "session-script",
js: ["content.js"],
persistAcrossSessions: false,
matches: ["*://example.com/*"],
runAt: "document_start",
}])
.then(() => console.log("registration complete"))
.catch((err) => console.warn("unexpected error", err))
service-worker.js
chrome.scripting
.updateContentScripts([{
id: "session-script",
excludeMatches: ["*://admin.example.com/*"],
}])
.then(() => console.log("registration updated"));
service-worker.js
chrome.scripting
.getRegisteredContentScripts()
.then(scripts => console.log("registered content scripts", scripts));
service-worker.js
chrome.scripting
.unregisterContentScripts({ ids: ["session-script"] })
.then(() => console.log("un-registration complete"));
以编程方式注入
对于需要为响应事件或特定事件而运行的内容脚本,请使用程序化注入 。
要以程序化方式注入内容脚本,您的扩展程序需要以下项目的主机权限:
以及它尝试注入脚本的网页。可通过
将其作为您的扩展程序清单的一部分请求,或暂时使用 "activeTab"
。
以下是基于 activeTab 的扩展程序的不同版本。
manifest.json:
{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
内容脚本可作为文件注入。
content-script.js
document.body.style.backgroundColor = "orange";
service-worker.js:
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["content-script.js"]
});
});
函数正文也可以作为内容脚本进行注入和执行。
service-worker.js:
function injectedFunction() {
document.body.style.backgroundColor = "orange";
}
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target : {tabId : tab.id},
func : injectedFunction,
});
});
请注意,注入的函数是
chrome.scripting.executeScript()
调用,而不是原始函数本身。因此,函数的
正文必须保持独立;引用函数之外的变量会导致内容
脚本来抛出 ReferenceError
。
作为函数注入时,您还可以将参数传递给函数。
service-worker.js
function injectedFunction(color) {
document.body.style.backgroundColor = color;
}
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target : {tabId : tab.id},
func : injectedFunction,
args : [ "orange" ],
});
});
排除匹配项和 glob
如需自定义指定的网页匹配,请在声明式中添加以下字段 注册。
名称 | 类型 | 说明 |
---|---|---|
exclude_matches |
字符串数组 | 可选。不包括本内容脚本本应注入的网页 。请参阅匹配模式,详细了解 这些字符串。 |
include_globs |
字符串数组 | 可选。在 matches 之后应用,以便仅包含那些同时符合
匹配此 glob。这旨在模拟 @include
Greasemonkey 关键字。 |
exclude_globs |
字符串数组 | 可选。在 matches 之后应用,以排除匹配此值的网址
glob。旨在模拟 @exclude
Greasemonkey 关键字。 |
如果同时满足以下两个条件,系统就会将内容脚本注入到网页中:
- 其网址与任何
matches
格式和任何include_globs
格式匹配。 - 该网址也不符合
exclude_matches
或exclude_globs
格式。 由于matches
属性是必需的,因此exclude_matches
、include_globs
和exclude_globs
只能用于限制哪些网页会受到影响。
以下扩展程序会将内容脚本注入 https://www.nytimes.com/health
而不是进入 https://www.nytimes.com/business
中。
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"exclude_matches": ["*://*/*business*"],
"js": ["contentScript.js"]
}
],
...
}
service-worker.js
chrome.scripting.registerContentScripts([{
id : "test",
matches : [ "https://*.nytimes.com/*" ],
excludeMatches : [ "*://*/*business*" ],
js : [ "contentScript.js" ],
}]);
glob 属性遵循的语法与匹配模式不同且更灵活。可接受的 glob
字符串是指可能包含“通配符”的网址星号和问号星号 (*
)
匹配任意长度的任何字符串,包括空字符串,而问号 (?
) 匹配
任意单个字符。
例如,glob https://???.example.com/foo/\*
与以下任一项匹配:
https://www.example.com/foo/bar
https://the.example.com/foo/
但是,它与以下内容不匹配:
https://my.example.com/foo/bar
https://example.com/foo/
https://www.example.com/foo
此扩展程序会将内容脚本注入 https://www.nytimes.com/arts/index.html
,并
https://www.nytimes.com/jobs/index.htm*
,但不属于
https://www.nytimes.com/sports/index.html
:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"include_globs": ["*nytimes.com/???s/*"],
"js": ["contentScript.js"]
}
],
...
}
此扩展程序会将内容脚本注入 https://history.nytimes.com
,并
https://.nytimes.com/history
,但不包含到https://science.nytimes.com
或
https://www.nytimes.com/science
:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
为了实现正确的范围,可以加入其中的一项、全部或其中一部分。
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"exclude_matches": ["*://*/*business*"],
"include_globs": ["*nytimes.com/???s/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
运行时间
run_at
字段用于控制何时将 JavaScript 文件注入网页。首选的
默认值为 "document_idle"
。如需了解其他可能的实现,请参阅 RunAt 类型
值。
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"run_at": "document_idle",
"js": ["contentScript.js"]
}
],
...
}
service-worker.js
chrome.scripting.registerContentScripts([{
id : "test",
matches : [ "https://*.nytimes.com/*" ],
runAt : "document_idle",
js : [ "contentScript.js" ],
}]);
名称 | 类型 | 说明 |
---|---|---|
document_idle |
字符串 | 首选。尽可能使用 "document_idle" 。浏览器 选择在 "document_end" 之间以及之后立即注入脚本的时间
window.onload
事件触发。注入的确切时间取决于文档的复杂程度以及
所需时间,针对网页加载速度进行了优化。内容脚本 在 "document_idle" 运行的不需要监听
window.onload 事件后,这些监听器一定会在 DOM 完成后运行。如果
脚本肯定需要在 window.onload 之后运行,该扩展程序可以检查
onload 已通过使用 document.readyState 触发
属性。 |
document_start |
字符串 | 脚本在来自 css 的任何文件之后、任何其他 DOM 之前注入
构建的产品或运行任何其他脚本。 |
document_end |
字符串 | 脚本在 DOM 完成后立即注入, 图片和帧都已加载完毕 |
指定帧
"all_frames"
字段允许扩展程序指定是否应将 JavaScript 文件和 CSS 文件
注入到符合指定网址要求的所有帧中,或仅注入到
标签页。
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
service-worker.js
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
名称 | 类型 | 说明 |
---|---|---|
all_frames |
布尔值 | 可选。默认为 false ,表示只有顶层帧
匹配。如果指定 true ,所有帧都会注入,即使
帧不是标签页中的最顶层的帧。系统会单独检查每个帧的网址
要求。如果不满足网址要求,则不会注入到子框架中。 |
注入到相关帧中
扩展程序可能希望在与匹配内容相关的帧中运行脚本。 但它们本身并不相符出现这种情况的常见情形是 表示网址由匹配的框架创建,但其网址不是 本身匹配脚本的指定模式。
如果扩展程序想将
具有 about:
、data:
、blob:
和 filesystem:
架构。在这种情况下,
网址与内容脚本的格式不符(并且,如果为 about:
和
data:
,请勿在网址中添加父级网址或来源
(如 about:blank
或 data:text/html,<html>Hello, World!</html>
)。
但是,这些帧仍可与正在创建的帧相关联。
为了注入到这些帧中,扩展程序可以指定
"match_origin_as_fallback"
属性(位于
清单。
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.google.com/*"],
"match_origin_as_fallback": true,
"js": ["contentScript.js"]
}
],
...
}
如果指定且设为 true
,Chrome 将会查看
帧的发起者来确定帧是否匹配,而不是在
框架本身的网址请注意,这也可能不同于
目标帧的 origin(例如,data:
网址的源为 null)。
帧的发起者是创建目标或在目标中导航的帧 帧。虽然这通常是直接父级或开场白,但也可能不是(如 例如框架在 iframe 内导航的情况)。
由于比较结果会比较发起者帧的 origin,因此发起者帧会进行比较
位于该源的任何路径上。为了澄清这一点
需要使用 "match_origin_as_fallback"
指定的任何内容脚本
设置为 true
也可指定 *
的路径。
如果同时指定了 "match_origin_as_fallback"
和 "match_about_blank"
,
"match_origin_as_fallback"
优先。
与嵌入页面的通信
尽管内容脚本的执行环境和托管它们的网页是相互独立的 但会共享对页面 DOM 的访问权限。如果网页想与 内容脚本,或者通过内容脚本的扩展,则必须通过共享 DOM 执行此操作。
使用 window.postMessage()
可以实现以下示例:
content-script.js
var port = chrome.runtime.connect();
window.addEventListener("message", (event) => {
// We only accept messages from ourselves
if (event.source !== window) {
return;
}
if (event.data.type && (event.data.type === "FROM_PAGE")) {
console.log("Content script received: " + event.data.text);
port.postMessage(event.data.text);
}
}, false);
example.js
document.getElementById("theButton").addEventListener("click", () => {
window.postMessage(
{type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);
非扩展程序网页 example.html 会向自身发布消息。此邮件被拦截并且 然后发布到扩展程序进程中。这样,网页 建立与扩展进程的通信渠道。反过来则可以 类似的方法。
访问扩展程序文件
要从内容脚本访问扩展文件,您可以调用
chrome.runtime.getURL()
,可获取扩展程序素材资源的绝对网址,如以下示例所示 (content.js
):
content-script.js
let image = chrome.runtime.getURL("images/my_image.png")
如需在 CSS 文件中使用字体或图片,您可以使用 @@extension_id
来构建网址,如以下示例 (content.css
) 所示:
content.css
body {
background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');
}
@font-face {
font-family: 'Stint Ultra Expanded';
font-style: normal;
font-weight: 400;
src: url('chrome-extension://__MSG_@@extension_id__/fonts/Stint Ultra Expanded.woff') format('woff');
}
所有资产必须在 manifest.json
文件中声明为网络可访问资源:
manifest.json
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}
确保安全
虽然隔离的世界提供了一层保护,但使用内容脚本可能会造成
或网页中存在的漏洞如果内容脚本从
单独的网站(例如通过调用 fetch()
),请务必按照
跨站脚本攻击。仅通过 HTTPS 进行通信
避免"man-in-the-middle"攻击。
请务必过滤出恶意网页。例如,以下模式就很危险, Manifest V3 禁止:
content-script.js
const data = document.getElementById("json-data"); // WARNING! Might be evaluating an evil script! const parsed = eval("(" + data + ")");
content-script.js
const elmt_id = ... // WARNING! elmt_id might be '); ... evil script ... //'! window.setTimeout("animate(" + elmt_id + ")", 200);
相反,建议使用不会运行脚本且更安全的 API:
content-script.js
const data = document.getElementById("json-data") // JSON.parse does not evaluate the attacker's scripts. const parsed = JSON.parse(data);
content-script.js
const elmt_id = ... // The closure form of setTimeout does not evaluate scripts. window.setTimeout(() => animate(elmt_id), 200);