内容脚本是在网页上下文中运行的文件。通过使用标准文档对象模型 (DOM),它们能够读取浏览器访问的网页的详细信息、对其进行更改,并将信息传递给其父扩展程序。
了解内容脚本功能
内容脚本可以通过与父扩展程序交换消息来访问父扩展程序使用的 Chrome API。它们还可以通过 chrome.runtime.getURL() 访问扩展程序文件的网址,并像使用其他网址一样使用结果。
// Code for displaying EXTENSION_DIR/images/myimage.png:
var imgURL = chrome.runtime.getURL("images/myimage.png");
document.getElementById("someImage").src = imgURL;
此外,内容脚本可以直接访问以下 Chrome API:
内容脚本无法直接访问其他 API。
在隔离的世界中工作
内容脚本位于隔离的世界中,因此内容脚本可以更改其 JavaScript 环境,而不会与网页或其他内容脚本发生冲突。
扩展程序可以在网页中运行,代码类似于以下示例。
<html>
<button id="mybutton">click me</button>
<script>
var greeting = "hello, ";
var button = document.getElementById("mybutton");
button.person_name = "Bob";
button.addEventListener("click", function() {
alert(greeting + button.person_name + ".");
}, false);
</script>
</html>
该扩展程序可以注入以下内容脚本。
var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener("click", function() {
alert(greeting + button.person_name + ".");
}, false);
如果按下了按钮,则会显示这两个提醒。
隔离的世界不允许内容脚本、扩展程序和网页访问彼此创建的任何变量或函数。这还会使内容脚本能够启用网页不应访问的功能。
注入脚本
以编程方式注入
对于需要在特定情况下运行的内容脚本,请使用程序化注入。
如需注入程序化内容脚本,请在清单中提供 activeTab 权限。 这会授予对有效网站主机的安全访问权限,以及对标签页权限的临时访问权限,从而使内容脚本能够在当前有效标签页上运行,而无需指定跨源权限。
{
"name": "My extension",
...
"permissions": [
"activeTab"
],
...
}
内容脚本可以作为代码注入。
chrome.runtime.onMessage.addListener(
function(message, callback) {
if (message == "changeColor"){
chrome.tabs.executeScript({
code: 'document.body.style.backgroundColor="orange"'
});
}
});
也可以注入整个文件。
chrome.runtime.onMessage.addListener(
function(message, callback) {
if (message == "runContentScript"){
chrome.tabs.executeScript({
file: 'contentScript.js'
});
}
});
以声明方式注入
针对应在指定网页上自动运行的内容脚本使用声明性注入。
以声明方式注入的脚本在清单中 "content_scripts" 字段下注册。它们可以包含 JavaScript 文件、CSS 文件或同时包含这两种文件。所有自动运行的内容脚本都必须指定匹配模式。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"css": ["myStyles.css"],
"js": ["contentScript.js"]
}
],
...
}
| 名称 | 类型 | 说明 |
|---|---|---|
matches {: #matches } |
必需。指定此内容脚本将注入到哪些网页中。如需详细了解这些字符串的语法,请参阅匹配模式;如需了解如何排除网址,请参阅匹配模式和 Glob。 | |
css {: #css } |
可选。要注入到匹配页面中的 CSS 文件列表。这些脚本会按其在此数组中出现的顺序注入,在为网页构建或显示任何 DOM 之前注入。 | |
js {: #js } |
可选。要注入到匹配网页中的 JavaScript 文件列表。这些参数会按照它们在此数组中出现的顺序注入。 | |
match_about_blank {: #match_about_blank } |
布尔值 | 可选。脚本是否应注入到父框架或打开器框架与 matches 中声明的某个模式匹配的 about:blank 框架中。默认设置为 false。 |
排除匹配项和 glob
您可以在清单注册中添加以下字段,以自定义指定的网页匹配。
| 名称 | 类型 | 说明 |
|---|---|---|
exclude_matches {: #exclude_matches } |
可选。排除此内容脚本本应注入到的网页。如需详细了解这些字符串的语法,请参阅匹配模式。 | |
include_globs {: #include_globs } |
可选。在 matches 之后应用,以仅包含也与此 glob 匹配的网址。旨在模拟 @include Greasemonkey 关键字。 |
|
exclude_globs {: #exclude_globs } |
可选。在 matches 之后应用,用于排除与此 glob 匹配的网址。旨在模拟 @exclude Greasemonkey 关键字。 |
如果网页的网址与任何 matches 格式和任何 include_globs 格式相匹配,但同时不与 exclude_matches 或 exclude_globs 格式相匹配,内容脚本就会注入到该网页中。
由于 matches 属性是必需属性,因此 exclude_matches、include_globs 和 exclude_globs 只能用于限制受影响的网页。
以下扩展程序会将内容脚本注入到 http://www.nytimes.com/ health 中,但不会注入到 http://www.nytimes.com/ business 中。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"exclude_matches": ["*://*/*business*"],
"js": ["contentScript.js"]
}
],
...
}
与匹配模式相比,glob 属性采用的语法不同,也更灵活。可接受的 glob 字符串是可能包含“通配符”星号和问号的网址。星号 * 可匹配任意长度的字符串(包括空字符串),而问号 ?匹配任何单个字符。
例如,glob http:// ??? .example.com/foo/ * 与以下任一网址匹配:
- http:// www .example.com/foo /bar
- http:// the .example.com/foo /
不过,它不匹配以下内容:
- http:// my .example.com/foo/bar
- http:// example .com/foo/
- http://www.example.com/foo
此扩展程序会将内容脚本注入到 http:/www.nytimes.com/ arts /index.html 和 http://www.nytimes.com/ jobs /index.html 中,但不会注入到 http://www.nytimes.com/ sports /index.html 中。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"include_globs": ["*nytimes.com/???s/*"],
"js": ["contentScript.js"]
}
],
...
}
此扩展程序会将内容脚本注入到 http:// history .nytimes.com 和 http://.nytimes.com/ history 中,但不会注入到 http:// science .nytimes.com 或 http://www.nytimes.com/ science 中。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
您可以包含其中一个、全部或部分范围,以实现正确的范围。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"exclude_matches": ["*://*/*business*"],
"include_globs": ["*nytimes.com/???s/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
运行时间
JavaScript 文件注入到网页中的时间由 run_at 字段控制。首选和默认字段是 "document_idle",但也可以根据需要指定为 "document_start" 或 "document_end"。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"run_at": "document_idle",
"js": ["contentScript.js"]
}
],
...
}
| 名称 | 类型 | 说明 |
|---|---|---|
document_idle {: #document_idle } |
字符串 | 首选。尽可能使用 "document_idle"。浏览器会选择在 "document_end" 之间以及 windowonload 事件触发后立即注入脚本。注入的确切时间取决于文档的复杂程度和加载时间,并针对网页加载速度进行了优化。以 "document_idle" 运行的内容脚本不需要监听 window.onload 事件,它们保证在 DOM 完成后运行。如果脚本确实需要在 window.onload 之后运行,扩展程序可以使用 document.readyState 属性检查 onload 是否已触发。 |
document_start {: #document_start } |
字符串 | 脚本会在 css 中的任何文件之后注入,但在构建任何其他 DOM 或运行任何其他脚本之前注入。 |
document_end {: #document_end } |
字符串 | 脚本在 DOM 完成后立即注入,但在图片和框架等子资源加载之前注入。 |
指定帧
借助 "all_frames" 字段,扩展程序可以指定是否应将 JavaScript 和 CSS 文件注入到符合指定网址要求的所有框架中,还是仅注入到标签页中的最顶层框架中。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
| 名称 | 类型 | 说明 |
|---|---|---|
all_frames {: #all_frames } |
布尔值 | 可选。默认值为 false,表示仅匹配顶部框架。如果指定为 true,则会注入到所有框架中,即使该框架不是标签页中的最顶部框架也是如此。系统会单独检查每个框架是否满足网址要求,如果不满足,则不会注入到子框架中。 |
与嵌入网页的通信
虽然内容脚本的执行环境与托管它们的网页的执行环境相互隔离,但它们共享对网页 DOM 的访问权限。如果网页希望与内容脚本或通过内容脚本与扩展程序通信,则必须通过共享 DOM 进行通信。
以下示例可使用 window.postMessage 完成:
var port = chrome.runtime.connect();
window.addEventListener("message", function(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);
document.getElementById("theButton").addEventListener("click",
function() {
window.postMessage({ type: "FROM_PAGE", text: "Hello from the webpage!" }, "*");
}, false);
非扩展程序网页 example.html 会向自身发布消息。此消息会被内容脚本拦截并检查,然后发布到扩展程序进程。这样一来,网页便可与扩展程序进程建立通信线路。您也可以通过类似方式反向操作。
确保安全
虽然隔离的世界提供了一层保护,但使用内容脚本可能会导致扩展程序和网页出现漏洞。如果内容脚本从单独的网站接收内容(例如发出 XMLHttpRequest),请务必先过滤内容,以防范跨站脚本攻击,然后再注入内容。仅通过 HTTPS 进行通信,以避免"man-in-the-middle"攻击。
请务必过滤恶意网页。例如,以下模式很危险:
var data = document.getElementById("json-data")
// WARNING! Might be evaluating an evil script!
var parsed = eval("(" + data + ")")
var elmt_id = ...
// WARNING! elmt_id might be "); ... evil script ... //"!
window.setTimeout("animate(" + elmt_id + ")", 200);
建议改用更安全的 API,这些 API 不会运行脚本:
var data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
var parsed = JSON.parse(data);
var elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(function() {
animate(elmt_id);
}, 200);