内容脚本

内容脚本是在网页环境中运行的文件。通过使用标准的文档对象模型 (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 } boolean 可选。脚本是否应注入到 about:blank 帧中(其父帧或起始帧与 matches 中声明的模式之一匹配)。默认设置为 false

排除匹配项和 glob

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

名称 类型 说明
exclude_matches {: #excluded_matches } 字符串数组 可选。不包括此内容脚本将被注入的网页。要详细了解这些字符串的语法,请参阅匹配模式
include_globs {: #include_globs } 字符串数组 可选。在 matches 之后应用,以仅包含也与此 glob 匹配的网址。用于模拟 @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 属性遵循与匹配模式不同且更灵活的语法。可接受的 glob 字符串是指可能包含“通配符”星号和问号的网址。星号 * 匹配任何长度的字符串(包括空字符串),而问号 ?匹配任何单个字符。

例如,glob http:// ??? .example.com/foo/ * 可以匹配以下任意一项:

  • http:// www .example.com/foo /bar
  • http:// .example.com/foo /

不过,它与以下内容匹配:

  • http:// my .example.com/foo/bar
  • http:// 示例 .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/ collaboration

{
  "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 } string 首选。尽可能使用 "document_idle"

浏览器会选择一个时间,在 "document_end" 之间以及 windowonload 事件触发后立即注入脚本。注入的确切时间取决于文档的复杂程度和加载需要多长时间,并且针对网页加载速度进行了优化。

"document_idle" 运行的内容脚本不需要监听 window.onload 事件,它们一定会在 DOM 完成后运行。如果脚本确实需要在 window.onload 之后运行,则扩展程序可以使用 document.readyState 属性检查 onload 是否已触发。
document_start {: #document_start } string 系统会在 css 中的任何文件之后、构建任何其他 DOM 或运行任何其他脚本之前注入脚本。
document_end {: #document_end } string 脚本会在 DOM 完成后、子资源(例如图片和帧)加载之前立即注入。

指定框架

"all_frames" 字段允许该扩展程序指定应将 JavaScript 和 CSS 文件注入到符合指定网址要求的所有框架中,还是仅注入标签页中最顶层的框架。

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://*.nytimes.com/*"],
      "all_frames": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}
名称 类型 说明
all_frames {: #all_frames } boolean 可选。默认值为 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);