内容脚本

内容脚本是在网页环境中运行的文件。通过使用标准的 Document 对象模型 (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。

在隔离的世界中工作

内容脚本位于隔离的世界中,因此内容脚本可以对其 并且不与网页或其他内容脚本冲突

扩展程序可使用类似于以下示例的代码在网页上运行。

<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 } 布尔值 可选。脚本是否应注入到 about:blank 帧中,其中父级帧或打开器帧与 matches 中声明的某个模式匹配。默认设置为 false

排除匹配项和通配符

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

名称 类型 说明
exclude_matches {: #exclude_matches } 字符串数组 可选。不包括这些内容脚本本来会被注入的网页。如需详细了解这些字符串的语法,请参阅匹配模式
include_globs {: #include_globs } 字符串数组 可选。在 matches 之后应用,以仅包含也与此正则表达式匹配的网址。旨在模拟 @include Greasemonkey 关键字。
exclude_globs {: #exclude_globs } 字符串数组 可选。在 matches 之后应用,以排除与此 glob 匹配的网址。旨在模拟 @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:// 我的 .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.com,并 http://.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 进行通信 &quot;man-in-the-middle&quot;攻击。

请务必过滤恶意网页。例如,以下模式就很危险:

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