Chrome 开发 - 第一部分:扩展程序





5.00/5 (4投票s)
本文介绍如何编写扩展程序,以自定义、访问和增强 Chrome 浏览器的功能。
1. 引言
根据 W3Counter 的数据,Chrome 是世界上最受欢迎的浏览器。许多人用它来访问网站,但很少有人知道如何通过扩展来增强其功能。扩展提供了丰富而激动人心的功能,例如自定义 Chrome 的外观、与当前页面交互,或向浏览器外部的应用程序发送消息。
本文将探讨 Google 用于开发 Chrome 扩展的 API。JavaScript 是本次讨论的核心,因此在继续之前,您应该对这门语言有扎实的了解。本系列的第二篇文章将解释如何编写 Chrome 应用。
2. Chrome 扩展概述
Chrome 扩展是一个压缩文件夹,其中的文件用于配置 Chrome 的行为或外观。有三个要点需要理解:
- 每个扩展都必须有一个名为 manifest.json 的文件。该文件描述了扩展的功能,并指明其如何与浏览器交互。
- 扩展的行为由 JavaScript 文件配置,这些文件分为两种类型。后台脚本可以访问 Chrome 的所有功能,但不能与浏览器的文档交互。内容脚本可以读取或修改浏览器的文档,但只能访问 Chrome 的部分功能。
- 在 Chrome 能够访问扩展之前,该文件夹必须被压缩成一个名为 *.crx 的特殊归档文件。然后,它必须被加载到浏览器中。
本节首先讨论 manifest.json 的格式。然后介绍一个简单的扩展,并解释如何将其加载到 Chrome 中。
2.1 扩展清单(The Extension Manifest)
manifest.json 的格式基于 JavaScript 对象表示法 (JSON)。它在一对大括号内定义了一系列属性。许多不同的属性可以添加到 manifest.json 中,您可以在这里查看完整列表。只有三个是必需的:
manifest_version
— 清单格式的版本,应设置为2
name
— 标识扩展名称的字符串version
— 标识扩展版本的字符串
为了进一步描述扩展,推荐使用两个属性:
description
— 描述扩展的字符串icons
— 一个或多个代表扩展的图像
下面的 JSON 展示了一个基本清单的样子:
{ "manifest_version": 2, "name": "Trivial extension example", "version": "1.0", "description": "This extension doesn't do anything", "icons": { "16": "images/smiley_sm.png", "48": "images/smiley_med.png", "128": "images/smiley_lg.png" } }
icons
属性需要解释。根据 Google 的建议,每个扩展至少应提供三个图像:
- 128x128 图像 — 在安装期间和 Google 网上应用店中显示。
- 48x48 图像 — 在 Chrome 开发者信息中心显示。
- 16x16 图像 — 作为扩展页面的网站图标(favicon)显示。
在示例清单中,icons
有三个属性。每个属性将一个图像尺寸(128
、48
或 16
)与一个图像文件关联起来。示例图像都是 PNG 格式,但 Chrome 也支持 BMP、GIF、ICO 和 JPEG 格式。
非简单的扩展必须定义额外的属性。表 1 列出了十个属性及其用途。
表 1:Chrome 扩展清单的可选属性(节选)
字段 | 目的 |
---|---|
content_scripts |
定义内容脚本 |
background |
定义后台脚本 |
permissions |
标识扩展可用的权限 |
browser_action |
定义浏览器操作 |
page_action |
定义页面操作 |
chrome_settings_overrides |
覆盖 Chrome 浏览器的设置 |
chrome_ui_overrides |
覆盖 Chrome 用户界面的某些方面 |
content_security_policy |
定义与扩展内容相关的规则 |
externally_connectable |
可以连接到该扩展的扩展和应用 |
存储 |
为写入接收到的数据分配回调函数 |
其中一些属性用于标识定义扩展行为的资源,例如 content_scripts
和 background
。其他属性则提供配置扩展属性的设置,例如 permissions
和 chrome_settings_overrides
。本文将讨论这些属性的一个子集,包括 content_scripts
、background
、permissions
、browser_action
和 page_action
。
2.2 加载扩展
Chrome 可以轻松地将新扩展加载到浏览器中。扩展通过 Chrome 开发者信息中心进行管理,可通过在 Chrome 浏览器中访问 chrome://extensions 到达。信息中心的右上角有一个名为“开发者模式”的复选框,图 1 显示了勾选该复选框后信息中心的样子。
图 1:开发者模式下的 Chrome 开发者信息中心
在左侧,“加载已解压的扩展程序...”按钮可以选择一个未压缩的文件夹并将扩展加载到 Chrome 中。理解加载过程的最佳方式是完成一个基本示例。
本文附带的 example_code.zip 归档文件包含一个名为 basic_demo 的文件夹,其中有一个简单的 manifest.json 文件和一些图像文件。可以通过以下四个步骤将此扩展加载到 Chrome 中:
- 下载 example_code.zip 并解压缩归档文件。
- 在 Chrome 中,通过导航到 chrome://extensions 打开 Chrome 开发者信息中心。
- 点击“加载已解压的扩展程序...”按钮,并选择解压缩后归档文件中的 basic_demo 文件夹。
- 按“确定”加载扩展。
当扩展加载后,信息中心的扩展列表中会添加一个新条目。图 2 显示了新条目的样子。
图 2:Chrome 开发者信息中心的新条目
“重新加载”链接特别有用。当扩展文件发生更改后,点击此链接会自动用更新后的文件重新加载扩展。
2.3 打包扩展
许多开发者通过Chrome 网上应用店向他人提供他们的扩展。这需要将扩展打包成一个名为 *.crx 的签名归档文件。这可以通过点击信息中心中的“打包扩展程序...”按钮来完成(参见图 1)。当 Chrome 打包扩展时,它会创建两个文件:一个包含扩展的 *.crx 文件和一个包含私钥的 *.pem 文件。当您想更新打包的扩展或将其上传到 Chrome 网上应用店时,需要此密钥。
如果扩展已经打包,就不能通过点击“重新加载”链接来更新。相反,您需要增加扩展的版本号并重新打包扩展。关于扩展打包的完整讨论可以在这里找到。
3. 扩展脚本
如前所述,Chrome 扩展可能包含定义其行为的 JavaScript 文件。这些文件分为两种类型:内容脚本和后台脚本。本节将讨论这两种类型的脚本。
如果 manifest.json 将一个或多个文件与 content_scripts
属性关联,那么这些文件就是内容脚本。它们可以访问浏览器的文档,但只能使用 API 的一部分功能。每当浏览器的页面发生变化时,内容脚本就会执行。
如果 manifest.json 将一个或多个文件与 background
属性关联,那么这些文件就是后台脚本。这些脚本可以访问 Chrome 扩展 API 提供的所有功能,但它们无法访问浏览器中打开的文档。通常,后台脚本中的代码仅在响应特定事件时执行。
3.1 内容脚本
在 manifest.json 内部,content_scripts
可以与一个对象数组关联。每个对象包含一系列属性,包括以下内容:
matches
— 一个 URL 模式数组或<all_urls>
js
— 当浏览器的 URL 匹配其中一个模式时要注入的 JavaScript 文件数组css
— 当浏览器的 URL 匹配其中一个模式时要注入的 CSS 文件数组all_frames
— 指示脚本是否应注入到页面的顶层框架或所有框架中
例如,下面的 manifest.json 表明,每当浏览器打开以 http://www.google.com 开头的页面时,都应注入 scripts/jquery-2.2.4.min.js 和 scripts/contentscript.js。
{ ... "content_scripts": [ { "matches": ["http://www.google.com/*"], "js": ["scripts/jquery-2.2.4.min.js", "scripts/contentscript.js"] } ] }
JavaScript 文件按所列顺序注入,这意味着 contentscript.js 可以访问 jQuery 的功能。对 jQuery 的完整描述超出了本文的范围,但这个网站提供了适当的介绍。
为了了解内容脚本如何工作,假设将以下代码插入到 contentscript.js 中:
$(document).ready(function() { alert($(document).find("title").text()); });
如果该扩展被加载到 Chrome 中,每当页面的 URL 与清单中 matches
属性的模式匹配时,浏览器将显示一个警告框,显示当前页面的标题。
内容脚本无法读取或修改当前页面中的 JavaScript 变量/函数,也无法访问 Chrome 的许多功能。但它们可以访问四个 API 中的一组有限的函数:chrome.i18n
、chrome.extension
、chrome.runtime
和 chrome.storage
。下面的讨论将探讨内容脚本可用的两个功能:获取扩展信息和访问本地存储。
3.1.1 获取扩展信息
Chrome 扩展中的脚本可以通过顶层 chrome
对象的属性来访问浏览器及其功能。这些包括 chrome.bookmarks
、chrome.tabs
和 chrome.cookies
。chrome
的字段通常被称为 API,完整的 API 列表可以在这里找到。
最重要的 API 之一是 chrome.runtime
,它使得响应事件和获取扩展信息成为可能。内容脚本无法访问 chrome.runtime
的所有功能,但它可以获取有关其扩展的信息。表 2 列出了 chrome.runtime
中实现此功能的属性。
表 2:用于内容脚本的 chrome.runtime 信息属性
属性 | 信息 |
---|---|
id |
扩展的唯一标识符 |
getManifest() |
返回一个包含 manifest.json 内容的字符串 |
getURL(String path) |
将路径转换为扩展内部的完整 URL |
如图 1 所示,Chrome 为其每个扩展分配一个唯一的字符串标识符。chrome.runtime
的 id
字段以字符串形式提供此标识符。
通常,扩展的 URL 是 chrome-extension://id,其中 id 是扩展的唯一标识符。如果内容脚本需要其扩展中文件或文件夹的完整 URL,可以调用 getURL
并传入该文件或文件夹的相对路径。例如,getURL("images")
返回 chrome-extension://id/images。
3.1.2 本地存储访问
chrome.storage
API 使扩展能够在客户端计算机上加载和存储数据。使用此 API 时,需要注意四点:
- 要使用该 API,扩展的清单必须将其
permissions
字段设置为包含字符串"storage"
的数组。 - 数据存储在未加密的数据库文件中,因此该 API 不适用于机密信息。
- 脚本可以通过调用
StorageArea
对象的函数来加载和存储数据。 - 有三种类型的
StorageArea
可用:chrome.sync
、chrome.local
和chrome.managed
。
如第三点所述,需要了解的核心对象是 StorageArea
。表 3 列出了该对象提供的五个函数。
表 3:StorageArea 函数
函数 | 描述 |
---|---|
set(Object items, Function callback) |
存储给定的项目 |
get(String|String[]|Object keys, Function callback) |
返回由键/键们标识的数据 |
getBytesInUse(String|String[] keys, Function callback) |
返回存储由键/键们标识的数据所需的字节数 store the data identified by the key/keys |
remove(String|String[] keys, function) |
移除由键/键们标识的项目 |
clear(Function callback) |
移除存储中的所有项目 |
与常规数据库一样,StorageArea
中的数据使用键值对进行加载和存储。以下代码存储一个键值对,其键为 browser
,值为 chrome
:
chrome.storage.local.set({"browser": "chrome"}, function() { console.log("Data stored"); });
在对 set
的调用中,第一个参数定义了要存储的数据。第二个参数定义了一个在操作完成时要调用的函数。所有 StorageArea
函数都接受回调函数作为参数,但只有 get
和 getBytesInUse
是必需的。以下代码演示了如何调用 get
来访问与键 browser
关联的值。
chrome.storage.local.get("browser", function(data) { console.log("Result: " + data.browser); });
当读取操作完成时,回调函数将data变量设置为接收到的对象,并通过 pair.browser
访问该值。
前面的示例通过访问 storage.local
执行了加载/存储操作。这是可以访问的三种 StorageArea
之一:
storage.local
— 提供高达 5,242,880 字节的存储空间。只能在本地设备上访问。storage.sync
— 提供高达 102,400 字节的存储空间(最多 512 个项目,每个项目 8,192 字节)。如果启用了 Chrome 同步,用户可以在任何运行 Chrome 的设备上访问此数据。storage.managed
— 项目由域管理员设置。此区域对扩展是只读的。
虽然内存大小有限,但对于在多个设备上运行 Chrome 的用户来说,storage.sync
非常方便。可以通过转到“设置”,使用 Google 帐户登录,并选择应同步的数据类型来启用 Chrome 同步。如果未启用 Chrome 同步,storage.sync
的工作方式与 storage.local
类似。
3.2 后台脚本
顾名思义,后台脚本在后台运行并响应事件。默认情况下,这些脚本在扩展启动时加载,并且只要扩展运行,它们就会占用资源。但可以配置后台脚本,使其仅在需要时加载。这可以通过将 background
属性的 persistent
属性设置为 false
来实现。manifest.json 中的以下声明显示了如何进行配置:
"background": { "scripts": ["bground.js"], "persistent": false }
与内容脚本不同,后台脚本可以访问 Chrome API 的所有功能。本节将探讨三个重要功能:事件处理、标签页访问和消息传递。
3.2.1 事件处理
就像 JavaScript 可以响应用户事件一样,chrome.runtime
API 也可以响应与扩展和 Chrome 浏览器相关的事件。这些事件包括扩展的安装、执行和卸载。
后台脚本可以通过 chrome.runtime
的属性响应这些事件。表 4 列出了其中的五个属性以及触发其事件的发生情况。
表 4:chrome.runtime 的事件(节选)
事件 | 触发器 |
---|---|
onStartup |
每次扩展开始执行时触发 |
onInstalled |
当扩展或 Chrome 安装或更新时触发 |
onSuspend |
在扩展即将被卸载前触发 |
onSuspendCancelled |
当扩展的卸载被取消时触发 |
onUpdateAvailable |
当扩展有可用更新时触发 |
这些属性中的每一个都有一个 addListener
函数,该函数接受一个回调函数。当相应的事件发生时,将调用此函数。大多数回调不接受任何参数,但与 onInstalled
关联的回调会接收一个具有三个属性的对象:
reason
— 安装事件的原因。可以设置为"install"
、"update"
、"chrome_update"
或"shared_module_update"
。previousVersion
— 一个字符串,标识安装/更新前的版本。id
— 如果共享模块被更新,则为共享模块的标识符。
举个例子,后台脚本中的以下代码会记录安装事件的原因:
chrome.runtime.onInstalled.addListener(function(obj) { console.log("Installation event: " + obj.reason); });
如果后台脚本执行此代码,消息将不会打印在当前网页的控制台中。扩展的后台脚本在一个特殊的页面中运行,可以通过进入 Chrome 开发者信息中心来访问。点击信息中心中的背景页链接可以打开背景页。
了解 onUpdateAvailable
和 onInstalled
之间的区别很重要。如果扩展有可用更新但由于扩展正在运行而无法安装,则会触发 onUpdateAvailable
事件。如果更新可用并且可以安装,则在扩展更新后会触发 onInstalled
事件。
3.2.2 标签页
chrome.tabs
API 提供了多种用于在浏览器中创建、修改和移除标签页的函数。表 5 列出了五个有用的函数,并对每个函数进行了简要描述。
表 5:chrome.tabs 的函数(节选)
函数 | 描述 |
---|---|
query(Object queryInfo, Function callback) |
提供一个或多个满足给定条件的标签页 |
update(Integer id, Object updateProps, Function callback) |
修改具有给定 ID 的标签页的属性 |
create(Object createProps, Function callback) |
在浏览器中创建一个新标签页 |
remove(Integer|Integer[] id, Function callback) |
从窗口中移除一个或多个标签页 |
executeScript(Integer id, Object details, Function callback) |
在给定的标签页中注入 JavaScript 代码或 CSS |
query
函数提供一个或多个与浏览器窗口中打开的标签页对应的 Tab
对象。它的第一个参数定义了一组用于选择标签页的条件,第二个参数指定一个回调函数来接收匹配的 Tab
对象。queryInfo
对象提供了许多用于选择标签页的属性,所有这些属性都是可选的:
index
— 标签页在窗口中的位置title
— 标签页的标题url
— 标签页的 URL 模式active
/highlighted
/pinned
/audible
/muted
— 表示标签页状态的布尔属性currentWindow
/lastFocusedWindow
— 标签页是否在当前或上一个窗口中
例如,以下代码中的回调函数会接收一个包含一个对象的数组:即当前窗口中活动标签页对应的 Tab
对象。
chrome.tabs.query({currentWindow: true, active: true}, function (tabs) { ... });
Tab
对象包含许多提供有关底层浏览器标签页信息的属性。表 6 列出了其中的十二个,完整列表可以在这里找到。
表 6:Tab 对象的属性(节选)
属性 | 数据类型 | 描述 |
---|---|---|
id |
整数 |
标签页的唯一标识符 |
index |
整数 |
标签页在窗口中的位置(从0开始) |
title |
字符串 |
标签页的标题 |
url |
字符串 |
标签页中显示的 URL |
windowId |
整数 |
标签页窗口的唯一标识符 |
active/pinned/ highlighted/audible |
布尔值 |
浏览器标签页的特性 |
width/height |
整数 |
标签页的尺寸 |
openerTabId |
整数 |
打开此标签页的标签页 ID |
这些字段是只读的。要更改标签页的属性,脚本需要调用表 5 中的 update
函数。第二个参数是一个对象,其属性包含标签页属性的名称和所需值。可以更改的属性是 url
、active
、highlighted
、pinned
、muted
和 openerTabId
。这些属性的类型与表 6 中列出的属性相同。
例如,以下代码找到当前窗口中的活动标签页并打开 Google 主页:
chrome.tabs.query({currentWindow: true, active: true}, function (tabs) { chrome.tabs.update(tabs[0].id, {url: "http://www.google.com"}); });
要修改标签页的 URL、标题或图标,扩展必须请求修改浏览器标签页的权限。这可以通过在清单的 permissions
字段中添加一个 "tabs"
字符串来实现。本节末尾讨论的示例代码演示了其工作原理。
表 5 中的 create
函数向当前浏览器窗口添加一个新标签页。第二个参数的属性初始化标签页的特性:url
、active
、index
、pinned
、windowId
和 openerTabId
。index 值决定了标签页的位置,并且将被限制在零和窗口中标签页数量之间。
remove
函数根据其 ID 关闭一个或多个标签页。例如,以下代码查找并关闭所有打开到 Chrome 开发者文档中页面的标签页。
chrome.tabs.query({url: "https://developer.chrome.com/*"}, function(tabs) { for(tab = 0; tab < tabs.length; tab++) { chrome.tabs.remove(tabs[tab].id); } });
表 5 中的最后一个函数 executeScript
,将 JavaScript 代码或 CSS 样式注入到标签页中。第二个参数指定了代码/CSS 以及注入方式。该对象有六个属性,所有这些都是可选的:
code
— 要注入到标签页中的 JavaScript 代码或 CSS 规则file
— 包含要注入的 JavaScript 代码或 CSS 规则的文件allFrames
— 如果为 true,JavaScript/CSS 将被注入到标签页的每个框架中,而不仅仅是顶层框架frameId
— 应该注入 JavaScript/CSS 的框架 ID(默认为 0,即顶层框架)matchAboutBlank
— 如果为 true,JavaScript/CSS 将被注入到 about:blank 和 about:srcdoc 框架中runAt
— JavaScript/CSS 应该最早在何时注入(document_start
、document_idle
或document_end
)
这是一个强大的函数,因为它允许后台脚本在特定标签页中动态执行内容脚本。例如,以下代码在索引为 1 的标签页中执行 tab.js 中的 JavaScript。
chrome.tabs.query({index: 1}, function (tabs) { chrome.tabs.executeScript(tabs[0].id, {file: "tab.js"}); });
为了成功注入代码或 CSS,清单必须请求访问该标签页 URL 的权限。这可以通过在清单的 permissions
字段中添加一个 URL 模式来实现。例如,下面的清单代码请求访问 Chrome 标签页和 Google 页面的权限:
"permissions": ["tabs", "https://www.google.com/*"]
3.2.3 脚本间传递消息
如前所述,后台脚本可以访问整个 Chrome API,但不能访问网页的内容。内容脚本可以访问网页的内容,但只能访问 Chrome API 的有限部分。为了让扩展充分利用这两种脚本,它需要一种在它们之间传输数据的方式。这称为消息传递。消息传递可用于在不同扩展之间以及扩展和应用之间进行通信。但本节重点关注在同一扩展的脚本之间传递消息。
chrome.runtime
API 提供了两种在扩展脚本之间传递消息的机制。第一种涉及一次传输一条消息。第二种则创建一个长期连接,用于发送和接收消息。
表 7 列出了使消息传递成为可能的 chrome.runtime
函数。它们可以在后台脚本和内容脚本中调用,并且经常用于在两种类型的脚本之间传输数据。
表 7:用于脚本间通信的函数
函数 | 信息 |
---|---|
sendMessage(String id, any message, Object options, Function callback) |
向具有给定 ID 的扩展发送消息 |
onMessage.addListener (Function callback) |
分配一个函数来接收传入的消息 |
connect(String id, Object connectInfo) |
创建一个到具有给定 ID 的扩展的持久连接 |
onConnect.addListener (Function callback) |
分配一个函数以通过连接接收数据 |
sendMessage
函数向扩展的后台脚本发送一条消息。它有四个参数:
id
— 接收消息的扩展的 ID。如果发送方和接收方属于同一个扩展,则可以省略此参数。message
— 要发送的消息。可以是任何类型,通常是常规的 JSON 对象。options
— 可选的配置对象。仅在向扩展外部发送数据时需要。callback
— 可选函数,用于接收接收方的响应(如果可用)。
例如,以下代码向其扩展内的脚本发送一个简单对象。回调函数记录接收方响应的 data
字段。
chrome.runtime.sendMessage({data: "Hi there!"}, function(response) { console.log(response.msg); });
另一个脚本可以使用 onMessage.addListener
监听传入消息。此函数需要一个接收三个参数的回调函数:
message
— 传入的消息sender
— 一个标识发送方的MessageSender
对象。它有五个可选字段:id
、frameId
、tab
、url
和tlsChannelId
sendResponse
— 一个可以调用以向发送方提供响应的函数
以下代码演示了如何使用 onMessage.addListener
。每当收到消息时,脚本会打印消息的 data
字段和发送方标签页的 ID。响应由一个简单对象组成。
chrome.runtime.onMessage.addListener( function(message, sender, sendResponse) { console.log("Message: " + message.data); console.log("Sender's tab ID: " + sender.tab.id); sendResponse({msg: "Hi yourself!"}); } );
前面的代码可以向后台脚本发送消息,但如果接收方是内容脚本,则存在一个问题:需要标识执行内容脚本的标签页。在这种情况下,发送方需要调用 chrome.tabs.sendMessage
而不是 chrome.runtime.sendMessage
。这两个函数几乎相同,但 chrome.tabs.sendMessage
的第一个参数是接收消息的标签页的 ID。
如果双方需要相互发送多条消息,建立一个长连接会更高效。要连接到后台脚本,chrome.runtime.connect
函数接受两个参数:
id
— 接收消息的扩展的 ID。如果发送方和接收方属于同一个扩展,则可以省略此参数。connectInfo
— 一个可选的配置对象。这可能包含两个属性:连接的名称 (name
) 和一个布尔值,用于标识是否应发送 TLS 通道 (includeTlsChannelId
)。第二个属性仅在与扩展外部通信时需要。
要连接到内容脚本,chrome.tabs.connect
函数接受三个参数。第一个是接收方标签页的 ID,第二个和第三个参数与 chrome.runtime.connect
的 id
和 connectInfo
参数相同。两个 connect
函数都返回一个 Port
,它代表两方之间的长连接。以下代码展示了如何使用 chrome.tabs.connect
:
chrome.tabs.query({currentWindow: true, active: true}, function (tabs) { chrome.tabs.connect(tabs[0].id); });
后台脚本和内容脚本可以使用 onConnect.addListener
监听连接。这需要一个回调函数,该函数接收代表新连接的 Port
。一个 Port
对象有七个属性,表 8 列出了每一个。
表 8:Port 对象的属性
属性 | 数据类型 | 描述 |
---|---|---|
名称 |
字符串 |
端口的名称(由 connect 函数设置) |
postMessage |
函数 |
向另一方发送消息 |
onMessage |
对象 |
为传入消息添加一个监听器 |
disconnect |
函数 |
终止连接 |
onDisconnect |
对象 |
为断开连接添加一个监听器 |
disconnect |
函数 |
终止连接 |
sender |
MessageSender |
发起连接的一方(可选) |
与前面讨论的 sendMessage
函数一样,postMessage
以 JSON 对象的形式传输数据。区别在于 postMessage
只有一个参数:消息对象。这在以下代码中有所体现,该代码在连接建立后立即发布一条消息:
chrome.runtime.onConnect.addListener(function(port) { port.postMessage({"name": "value"}); });
Port
的 onMessage
对象的工作方式与 chrome.runtime.onMessage
相同。它提供了一个 addListener
方法,该方法定义了一个在收到消息时调用的回调函数。回调函数没有理由访问响应函数,因为 Port
通信使得响应变得不必要。以下代码展示了如何使用 Port
的 onMessage
对象:
port.onMessage.addListener(function(message) { console.log("Message: " + message.name); });
任何一方都可以调用 disconnect()
来终止连接。此外,onDisconnect
对象提供了一个 addListener
函数,当脚本不再连接到 Port
时会做出响应。
3.3 示例应用
本文的示例归档中包含一个名为 script_demo 的文件夹。它包含一个演示内容脚本如何与后台脚本通信的扩展。更具体地说,它演示了事件处理、存储访问和消息传递。
script_demo/scripts 文件夹包含两个脚本:content.js 和 background.js。content.js 中的代码作为内容脚本运行,如下所示:
// Connect to background script var port = chrome.runtime.connect(); // Listen for response port.onMessage.addListener(function(message, sender) { // Print and store message data console.log("Received color: " + message.color); chrome.storage.local.set(message, function() { port.postMessage({"status": "Communication successful!"}); }); });
此脚本首先创建到后台脚本的连接。然后它等待消息,向控制台打印一条消息,并使用 chrome.storage
API 存储消息的数据。存储消息后,它会向后台脚本发送一条消息。后台脚本的代码如下所示:
// Wait for connection from content script chrome.runtime.onConnect.addListener(function(port) { // Send data through port port.postMessage({"color": "green"}); // Wait for incoming message port.onMessage.addListener(function(message) { // Retrieve data from storage chrome.storage.local.get("color", function(obj) { if (obj.color === "green") { console.log("Status: " + message.status); } }); }); });
后台脚本等待传入连接,向内容脚本发布一条消息,然后等待消息到达。当它收到消息时,后台脚本访问本地存储,检查返回的值,并向控制台打印一条消息。
在结束这个主题之前,关于扩展的脚本,我想提三点:
- 与其从后台脚本向内容脚本发送消息,后台脚本使用
chrome.tabs.executeScript
执行脚本可能更方便。后台脚本可以在executeScript
的回调函数中访问处理结果。 - 确保清单的权限设置正确。如果脚本需要访问存储,
"storage"
应该在权限数组中。如果脚本需要修改标签页的 URL、标题或图标,"tabs"
应该在数组中。 onMessage
和onConnect
中的 "o" 总是小写的。我不小心把它打成了大写,花了一个多小时才找出扩展不工作的原因。
4. 操作 (Actions)
在 Chrome 扩展中,一个操作由工具栏中的一个图标表示,当点击该图标时,会触发扩展的反应。每个扩展最多可以有一个操作,这可以是浏览器操作或页面操作。浏览器操作无论浏览器中打开哪个页面都保持活动状态。页面操作仅对特定页面有效。
4.1 浏览器操作
可以通过在 manifest.json 中添加 browser_action
属性来定义扩展的浏览器操作。这可以包含三个定义操作方面的属性(所有都是可选的):
default_icon
— 操作的图标可以是 HTML5 画布或静态图像。静态图像的大小必须为 19x19 或 38x38 像素,支持的格式包括 BMP、GIF、ICO、JPEG 或 PNG。对于未打包的扩展,仅支持 PNG 格式。default_title
— 分配一个工具提示,当用户的鼠标悬停在图标上时显示。default_popup
— 标识一个显示弹出菜单的 HTML 文件。
弹出窗口是用户点击操作时出现的页面。页面的外观使用与 default_popup
属性关联的 HTML 页面进行设置。例如,以下 HTML 在弹出窗口中定义了三个复选框:
<!DOCTYPE html> <html> <body style="width: 80px"> <input type="checkbox" id="opt1">Option 1<br> <input type="checkbox" id="opt2">Option 2<br> <input type="checkbox" id="opt3">Option 3<br> </body> </html>
图 3 显示了最终弹出窗口的样子。
图 3:带有弹出窗口的浏览器操作
如果浏览器操作没有弹出窗口,后台脚本可以在操作被点击时做出响应。这可以通过调用 chrome.browserAction.onClicked.addListener
来实现。它接受一个回调函数,该函数返回代表操作被点击时活动标签页的 Tab
对象。例如,以下代码在每次点击浏览器操作时打印当前标签页的 URL。
chrome.browserAction.onClicked.addListener(function(tab) { console.log("The tab's URL is " + tab.url); });
chrome.browserAction
API 提供的功能不仅仅是事件处理。它的函数可以获取有关操作的信息并动态配置其外观。表 9 列出了该 API 的函数并提供了每个函数的描述。
表 9:chrome.browserAction API 的函数
函数 | 描述 |
---|---|
enable(Integer tabId) |
为给定标签页启用浏览器操作 |
disable(Integer tabId) |
为给定标签页禁用浏览器操作 |
getTitle(Object props, Function callback) |
通过回调函数获取操作的标题 |
setTitle(Object props) |
设置操作的标题 |
setIcon(Object props) |
设置操作的图标 |
getBadgeText(Object props, Function callback) |
通过回调函数获取操作的徽章文本 |
setBadgeText(Object props) |
设置操作的徽章文本 |
getBadgeBackgroundColor (Object props, Function callback) |
获取操作徽章的背景颜色 通过回调函数 |
setBadgeBackgroundColor (Object props) |
设置操作徽章的背景颜色 |
getPopup(Object props, Function callback) |
通过回调函数获取操作的弹出文档 callback function |
setPopup(Object props) |
设置操作的弹出文档 |
通常,浏览器操作对于浏览器中的每个标签页具有相同的外观和行为。但这些函数可以指定应该访问哪个标签页。前两个函数根据标签页的 ID 启用或禁用浏览器操作。禁用的操作在工具栏中显示为灰色。
大多数 get*XYZ*
和 set*XYZ*
函数的工作方式相同。getter 函数接受一个可选对象,该对象标识函数适用于哪个标签页。第二个参数是一个回调函数,用于接收所需信息。例如,以下代码展示了如何调用 getTitle
来获取活动标签页的操作标题。
chrome.tabs.query({currentWindow: true, active: true}, function (tabs) { chrome.browserAction.getTitle({tabId: tabs[0].id}, function (title) { console.log("Title: " + title); }); });
像 setTitle
这样的 setter 函数接受一个类似的配置对象,但第一个属性提供了新的配置数据。例如,以下代码为索引为零的标签页设置浏览器操作的标题。
chrome.tabs.query({index: 0}, function (tabs) { chrome.browserAction.setTitle({title: "New title", tabId: tabs[0].id}); });
浏览器操作可以在其图标旁边打印文本。这称为徽章,可以使用 setBadgeText
函数进行配置。例如,以下代码在操作图标上打印 HI
。
chrome.browserAction.setBadgeText({text: "HI"});
图 4 显示了带有徽章的操作的样子。
图 4:带有徽章的浏览器操作
默认情况下,操作的徽章显示在一个红色矩形内。矩形的颜色可以使用 setBadgeBackgroundColor
进行更改。它接受一个配置对象,其 color
属性可以是一个 CSS 颜色字符串,也可以是一个包含四个 0 到 255 之间整数的 RGBA 顺序数组。例如,绿色可以表示为 "#0f0"
、"#00ff00"
或 [0, 255, 0, 255]
。以下代码展示了如何使用它。
chrome.browserAction.setBadgeBackgroundColor({color: "#0F0"});
如果浏览器操作有弹出窗口,getPopup
提供弹出窗口的 URL。可选的配置对象接受一个标签页 ID,回调函数以字符串形式接收 URL。以下代码展示了如何使用它。
chrome.browserAction.getPopup({}, function(url) { console.log("The popup's URL is " + url); });
chrome.browserAction
没有提供与弹出窗口通信或获取其状态的任何函数。但这可以使用前面讨论的消息传递机制来实现。与弹出窗口的通信将在本节末尾讨论。
4.2 页面操作
页面操作与浏览器操作几乎相同,可以有图标、标题和弹出窗口。在 manifest.json 中,唯一的区别是 browser_action
字段被替换为 page_action
。如下所示:
"page_action": { "default_icon": { "19": "images/icon_19.png", "38": "images/icon_38.png" }, "default_title": "Page action", "default_popup": "popup.html" }
两种操作类型的主要区别在于,页面操作默认是禁用的(灰色)。要启用页面操作,后台脚本需要使用标签页 ID 调用 chrome.pageAction.show
。这意味着页面操作一次只能为一个标签页启用。同样,chrome.pageAction.hide
会为指定的标签页禁用页面操作。
一个例子将阐明其工作原理。假设一个页面操作只应在标签页的 URL 包含字符串 google
时启用。后台脚本可以通过 chrome.tabs.onUpdate
事件响应标签页更改,当标签页更改时,脚本可以检查 URL。如下面的代码所示:
// Assign a callback to respond to tab changes chrome.tabs.onUpdated.addListener(testUrl); function testUrl(id, info, tab) { if (tab.url.indexOf("google") !== -1) { chrome.pageAction.show(id); } };
请注意,为了使此代码正常工作,必须将 "tabs"
字符串放在清单的 permissions
数组中。
除了 show
和 hide
,chrome.pageAction
中的函数与 chrome.browserAction
中的函数基本相似。有两个重要的区别:
- 标签页 ID 属性从不是可选的。页面操作总是针对单个标签页进行配置。
- 页面操作不支持徽章,因此没有像
setBadgeText
或getBadgeBackgroundColor
这样的函数。
页面操作中的弹出窗口的工作方式与浏览器操作中的类似。下面的讨论将解释两种弹出窗口如何与后台脚本通信。
4.3 弹出窗口通信
弹出页面是使用 HTML 配置的,这意味着它们可以使用 <script>
标签包含 JavaScript 代码。方便的是,弹出页面中的脚本可以访问后台脚本的所有功能。这意味着弹出脚本可以使用消息传递与其他脚本通信。
弹出通信在本文示例归档中包含的 action_demo 扩展中进行了演示。popup.html 页面有三个复选框,其 ID 分别为 opt1
、opt2
和 opt3
。它执行 popup.js 中的代码,如下所示:
// Create listeners for the three checkboxes document.getElementById("opt1").addEventListener("change", opt1changed); document.getElementById("opt2").addEventListener("change", opt2changed); document.getElementById("opt3").addEventListener("change", opt3changed); // Send a message when the first box changes function opt1changed() { var box1 = document.getElementById("opt1"); chrome.runtime.sendMessage({box: 1, checked: box1.checked}); } // Send a message when the second box changes function opt2changed() { var box2 = document.getElementById("opt2"); chrome.runtime.sendMessage({box: 2, checked: box2.checked}); } // Send a message when the third box changes function opt3changed() { var box3 = document.getElementById("opt3"); chrome.runtime.sendMessage({box: 3, checked: box3.checked}); }
当三个复选框中的任何一个被选中或取消选中时,popup.js 调用 chrome.runtime.sendMessage
。这会向后台脚本发送一条消息,该消息标识了复选框的编号及其状态。后台脚本代码如下所示:
// Configure the browser action chrome.runtime.onInstalled.addListener(function(obj) { chrome.browserAction.setBadgeText({text: "YES"}); chrome.browserAction.setBadgeBackgroundColor({color: "#00F"}); }); // Wait for an incoming message chrome.runtime.onMessage.addListener( // Receive a message from the popup script function(message, sender, sendResponse) { console.log("Box" + message.box + " checked state: " + message.checked); } );
此后台脚本使用 chrome.browserAction
API 设置操作的徽章文本和背景颜色。然后它通过调用 chrome.runtime.onMessage.addListener
等待消息。当收到消息时,脚本会向控制台记录一条消息。
此扩展演示了弹出窗口如何接收用户设置并将其发送给扩展。但有一个问题:每次关闭弹出窗口时,其页面都会重置。为了弥补这一点,可以通过访问前面讨论的 chrome.storage
API 来持久存储弹出窗口的状态。
历史
2016年6月17日 - 文章初次提交