内容脚本

内容脚本是在网页上下文中运行的文件。通过使用标准的文档对象模型 (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 } 字符串数组 必需。指定将将此内容脚本注入哪些网页。如需详细了解这些字符串的语法,请参阅匹配模式;如需了解如何排除网址,请参阅匹配模式和通配符
css {: #css } 字符串数组 可选。要注入到匹配页面的 CSS 文件的列表。这些元素会按其在此数组中的显示顺序注入,在为网页构建或显示任何 DOM 之前。
js {: #js } 字符串数组 可选。要注入到匹配网页中的 JavaScript 文件的列表。这些参数会按照此数组中的显示顺序注入。
match_about_blank {: #match_about_blank } 布尔值 可选。脚本是否应注入到 about:blank 帧中,其中父级帧或打开器帧与 matches 中声明的某个模式匹配。默认设置为 false

排除匹配项和通配符

您可以通过在清单注册中添加以下字段来自定义指定网页匹配。

名称 类型 说明
exclude_matches {: #exclude_matches } 字符串数组 可选。排除本应注入此内容脚本的网页。如需详细了解这些字符串的语法,请参阅匹配模式
include_globs {: #include_globs } 字符串数组 可选。在 matches 之后应用,以仅包含也与此正则表达式匹配的网址。旨在模拟 @include Greasemonkey 关键字。
exclude_globs {: #exclude_globs } 字符串数组 可选。在 matches 之后应用,用于排除与此正则表达式匹配的网址。旨在模拟 @exclude Greasemonkey 关键字。

如果网页的网址与任何 matches 格式和任何 include_globs 格式匹配,系统会将内容脚本注入该网页,前提是该网址不与 exclude_matchesexclude_globs 格式匹配。

由于 matches 属性是必需的,因此 exclude_matchesinclude_globsexclude_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 属性遵循的语法不同且更灵活。可接受的正则表达式字符串是可能包含“通配符”星号和问号的网址。星号 * 可匹配任何长度的任何字符串,包括空字符串,而问号 ?匹配任意单个字符。

例如,全局通配符 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.htmlhttp://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.comhttp://.nytimes.com/ history,但不会注入 http:// science .nytimes.comhttp://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:

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);