内容脚本是在网页的上下文中运行的 JavaScript
文件,它们可以通过标准的文档对象模型(DOM)来获取浏览器访问的网页详情,或者作出更改。
如下是内容脚本可以实现的一些功能的例子:
- 在网页中找出未链接的URL,并将它们转换为超链接
- 增加字体大小,使文本更具有可读性
- 发现并处理 DOM 中的微格式数据
然而,内容脚本也有一些限制,它们不能:
-
调用 chrome.* API,除了以下 API:
-
使用所属应用页面中定义的变量或函数
使用网页或其他内容脚本中定义的变量或函数
这些限制并不如看上去那么糟糕,内容脚本可以间接地通过与所属应用交换消息的方式,来使用 chrome.*
API、访问应用数据并请求应用完成操作。内容脚本也可以像所属应用一样向拥有主机权限的站点发出跨域 XMLHttpRequest,另外也可以使用共享的 DOM 与网页通信。有关内容脚本能做什么与不能做什么的更多内部细节,请参见执行环境。
清单文件
如果您的内容脚本代码每一次都需要插入到网页中,请在应用的清单文件中使用content_scripts
字段注册它,如以下例子所示:
{ "name": "我的应用", ... "content_scripts": [ { "matches": ["http://www.google.com/*"], "css": ["mystyles.css"], "js": ["jquery.js", "myscript.js"] } ], ... }
如果您只是有时需要插入代码,应该使用
permissions
字段,如以编程方式插入部分所述。
{ "name": "我的应用", ... "permissions": [ "tabs", "http://www.google.com/*" ], ... }
使用 content_scripts
字段,应用可以向一个页面中插入多个内容脚本,每个内容脚本可以有多个
JavaScript 和 CSS 文件,content_scripts
数组中的每一个项目可以包含如下属性:
名称 | 类型 | 描述 |
---|---|---|
matches |
array of strings | 必选。指定内容脚本要插入到哪些页面中去。有关这些字符串语法的更多细节请参见匹配表达式,有关如何排除 URL 的信息请参见匹配表达式和范围。 |
exclude_matches |
array of strings | 可选。排除不需要插入内容脚本的页面。有关这些字符串语法的更多细节请参见匹配表达式,有关如何排除 URL 的信息请参见匹配表达式和范围。 |
match_about_blank |
boolean | 可选。是否在 about:blank 和about:srcdoc 中插入内容脚本,只有当继承 URL 匹配matches 字段中声明的匹配表达式时才会插入内容脚本。继承 URL为创建框架或窗口的文档 URL。 内容脚本不能在经过沙盒屏蔽的框架中插入。 默认为 false 。 |
css |
array of strings | 可选。要插入匹配页面的 CSS 文件列表,它们将在页面的所有 DOM 构造或显示之前,按照数组中指定的顺序插入。 |
js |
可选。要插入匹配页面的 JavaScript 文件列表,它们将按照数组中指定的顺序插入。 | |
run_at |
string | 可选。控制 js 中的 JavaScript 文件何时插入,可以为“document_start”、”document_end” 或 “document_idle”,默认为 “document_idle”。 如果是 “document_start”,这些文件将在 如果是 “document_end”,文件将在 DOM 完成之后立即插入,但是在加载子资源(如图像与框架)之前插入。 如果是 “document_idle”,浏览器将在 “document_end” 和刚发生 注意:如果使用 “document_idle”,内容脚本不一定会收到 |
all_frames | boolean | 可选。控制内容脚本运行在匹配页面的所有框架中还是仅在顶层框架中。 默认为 false,意味着仅在顶层框架中运行。 |
include_globs | array of string | 可选。在应用 matches 之后仅包含同时匹配这一范围的URL。该属性是为了模拟 Greasemonkey 中的 @include 关键字,有关更多详情请参见下面的匹配表达式和范围。 |
exclude_globs | array of string | 可选。在应用 matches 之后排除匹配这一范围的URL。该属性是为了模拟 Greasemonkey 中的 @exclude 关键字,有关更多详情请参见下面的匹配表达式和范围。 |
匹配表达式和范围
如果页面的 URL 匹配任何 matches
指定的表达式以及任何include_globs
指定的表达式,并且不匹配exclude_matches
和 exclude_globs
指定的表达式,则会在页面中插入内容脚本。因为 matches
属性是必需的,exclude_matches
、include_globs
和 exclude_globs
只能用来限制受到影响的页面。
例如,假设 matches
为 ["http://*.nytimes.com/*"]
:
-
如果
exclude_matches
为["*://*/*business*"]
,那么内容脚本会插入“http://www.nytimes.com/health”,但不会插入“http://www.nytimes.com/business”。 -
如果
include_globs
为["*nytimes.com/???s/*"]
,那么内容脚本会插入“http://www.nytimes.com/arts/index.html”和“http://www.nytimes.com/jobs/index.html”,但不会插入“http://www.nytimes.com/sports/index.html”。 -
如果
exclude_globs
为["*science*"]
,那么内容脚本会插入“http://www.nytimes.com”,但不会插入“http://science.nytimes.com”或“http://www.nytimes.com/science”。
范围(glob)属性与匹配表达式相比遵循不同并且更灵活的语法。所有含有“通配符”星号和问号的
URL
都是可接受的范围字符串,星号(*)匹配任意长度的字符串(包括空字符串),问号(?)匹配任意单个字符。
例如,范围 “http://???.example.com/foo/*” 匹配以下任一 URL:
- “http://www.example.com/foo/bar”
- “http://the.example.com/foo/”
但是不匹配如下 URL:
- “http://my.example.com/foo/bar”
- “http://example.com/foo/”
- “http://www.example.com/foo”
以编程方式插入
如果您的 JavaScript 或 CSS
代码不需要插入匹配的每一个页面时,例如,如果您希望当用户单击浏览器按钮的图标时才运行脚本,以编程方式插入代码就十分有用。
要向页面中插入代码,您的应用必须拥有该页面的主机权限,并且还需要用到chrome.tabs
模块。您可以使用清单文件中的
permissions 字段获得这些权限。
一旦您拥有了相应的权限,您可以通过调用 tabs.executeScript 向页面插入
JavaScript 代码。要插入 CSS 代码,请使用 tabs.insertCSS。
下列代码
当用户单击浏览器按钮时向当前标签页插入并执行 JavaScript 代码。
chrome.browserAction.onClicked.addListener(function(tab) { chrome.tabs.executeScript({ code: 'document.body.style.backgroundColor="red"' }); });
"permissions": [ "activeTab" ],
当浏览器正在显示一个 HTTP
页面,并且用户单击该应用的浏览器按钮时,应用将页面的bgcolor
属性设为’red’。结果,除非页面通过 CSS
设置了背景颜色,页面将变成红色。
通常,您不会直接插入代码(如前面的例子所示),而是将代码放在一个文件中。您可以像这样插入文件的内容:
chrome.tabs.executeScript(null, {file: "content_script.js"});
执行环境
内容脚本在一个称为隔离环境的特殊环境中执行。它们可以访问所在页面的
DOM,但是不能访问当前页面创建的任何 JavaScript
变量或函数。对于每个内容脚本来说,就像没有其他 JavaScript
在当前页面上执行。反过来也是如此:在当前页面运行的 JavaScript
不能调用或访问任何内容脚本定义的函数或变量。
例如,考虑如下简单页面:
<html> <button id="mybutton">单击我</button> <script> var greeting = "您好,"; var button = document.getElementById("mybutton"); button.person_name = "Bob"; button.addEventListener("click", function() { alert(greeting + button.person_name + "。"); }, false); </script> </html>
假设如下内容脚本插入到了 hello.html 中:
var greeting = "您好啊,"; var button = document.getElementById("mybutton"); button.person_name = "Roberto"; button.addEventListener("click", function() { alert(greeting + button.person_name + "。"); }, false);
现在,如果按钮按下,您将会看到两条问候。
隔离环境允许每一个内容脚本更改自己的 JavaScript
环境,而不用担心是否会与页面或其他内容脚本发生冲突。例如,一个内容脚本可以包含
JQuery v1 而页面可以包含 JQuery v2,并且它们互不影响。
隔离环境的另一个重要好处是将页面上的 JavaScript 和应用中的 JavaScript
完全区分开来,这样我们就可以为内容脚本提供额外功能,而这些额外功能不应该从网页中访问,我们也不用担心访问它们的网页。
网页与应用之间共享的 JavaScript 对象值得注意,例如window.onload
事件。每一个隔离环境拥有该对象自己的副本,对该对象赋值只影响这一独立的副本。例如,网页和应用都可以给window.onload
赋值,但是都不能读取另外一方的事件处理器。事件处理器将按照它们赋值的顺序调用。
与嵌入的页面通信
尽管内容脚本的执行环境和所在页面是互相隔离的,但是它们都可以访问页面的
DOM。如果页面想要和内容脚本通信(或者通过内容脚本与应用通信),必须通过共享的
DOM 进行。
可以使用 window.postMessage(或者 window.webkitPostMessage 用于可传输(Transferable)的对象):
var port = chrome.runtime.connect(); window.addEventListener("message", function(event) { // 我们只接受来自我们自己的消息 if (event.source != window) return; if (event.data.type && (event.data.type == "FROM_PAGE")) { console.log("内容脚本接收到:" + event.data.text); port.postMessage(event.data.text); } }, false);
document.getElementById("theButton").addEventListener("click", function() { window.postMessage({ type: "FROM_PAGE", text: "来自网页的问候!" }, "*"); }, false);
在上面的例子中,example.html(不是应用的一部分)向自己发送消息,由内容脚本截获并检查,然后发送至应用进程。通过这种方式,页面建立了与应用之间的通信,通过类似的方式反过来也是可能的。
安全性考虑
当您编写内容脚本时,您应该注意两个安全问题。首先,注意不要向您插入内容脚本的网站引入安全隐患。例如,如果您的内容脚本从另一个网站接收内容(例如通过发出
XMLHttpRequest),一定要注意把内容插入当前页面前过滤可能的跨站脚本攻击。例如,首选 innerText 而不是 innerHTML 插入内容。当您在
HTTPS 页面上获取来自 HTTP 的内容时要特别小心,因为如果用户在不安全的网络环境中,HTTP 内容可能会因为网络中的中间人攻击而遭到破坏。
第二,尽管在隔离环境中运行您的内容脚本提供了某些保护,但是如果您不加区分地使用来自网页的内容,恶意网页仍然可能攻击您的内容脚本。例如,以下形式是危险的:
var data = document.getElementById("json-data") // 警告!可能会执行恶意脚本! var parsed = eval("(" + data + ")")
var elmt_id = ... // 警告!elmt_id可能为"); …恶意脚本… //"! window.setTimeout("animate(" + elmt_id + ")", 200);
因此,改用更安全的不运行脚本的 API:
var data = document.getElementById("json-data") // JSON.parse 不会运行攻击者的脚本。 var parsed = JSON.parse(data);
var elmt_id = ... // 闭包形式的 setTimeout 不会运行脚本。 window.setTimeout(function() { animate(elmt_id); }, 200);
引用应用的文件
使用 extension.getURL 获得应用的文件 URL,您可以像任何其他 URL
一样使用获得的结果,如以下代码所示。
//用来显示 <应用目录>/images/myimage.png 的代码: var imgURL = chrome.extension.getURL("images/myimage.png"); document.getElementById("someImage").src = imgURL;
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/244510.html