65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2016年6月17日

CPOL

29分钟阅读

viewsIcon

24129

downloadIcon

279

本文介绍如何编写扩展程序,以自定义、访问和增强 Chrome 浏览器的功能。

1. 引言

根据 W3Counter 的数据,Chrome 是世界上最受欢迎的浏览器。许多人用它来访问网站,但很少有人知道如何通过扩展来增强其功能。扩展提供了丰富而激动人心的功能,例如自定义 Chrome 的外观、与当前页面交互,或向浏览器外部的应用程序发送消息。

本文将探讨 Google 用于开发 Chrome 扩展的 API。JavaScript 是本次讨论的核心,因此在继续之前,您应该对这门语言有扎实的了解。本系列的第二篇文章将解释如何编写 Chrome 应用。

2. Chrome 扩展概述

Chrome 扩展是一个压缩文件夹,其中的文件用于配置 Chrome 的行为或外观。有三个要点需要理解:

  1. 每个扩展都必须有一个名为 manifest.json 的文件。该文件描述了扩展的功能,并指明其如何与浏览器交互。
  2. 扩展的行为由 JavaScript 文件配置,这些文件分为两种类型。后台脚本可以访问 Chrome 的所有功能,但不能与浏览器的文档交互。内容脚本可以读取或修改浏览器的文档,但只能访问 Chrome 的部分功能。
  3. 在 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 有三个属性。每个属性将一个图像尺寸(1284816)与一个图像文件关联起来。示例图像都是 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_scriptsbackground。其他属性则提供配置扩展属性的设置,例如 permissionschrome_settings_overrides。本文将讨论这些属性的一个子集,包括 content_scriptsbackgroundpermissionsbrowser_actionpage_action

2.2 加载扩展

Chrome 可以轻松地将新扩展加载到浏览器中。扩展通过 Chrome 开发者信息中心进行管理,可通过在 Chrome 浏览器中访问 chrome://extensions 到达。信息中心的右上角有一个名为“开发者模式”的复选框,图 1 显示了勾选该复选框后信息中心的样子。

图 1:开发者模式下的 Chrome 开发者信息中心

Figure 1: The Chrome Developer Dashboard

在左侧,“加载已解压的扩展程序...”按钮可以选择一个未压缩的文件夹并将扩展加载到 Chrome 中。理解加载过程的最佳方式是完成一个基本示例。

本文附带的 example_code.zip 归档文件包含一个名为 basic_demo 的文件夹,其中有一个简单的 manifest.json 文件和一些图像文件。可以通过以下四个步骤将此扩展加载到 Chrome 中:

  1. 下载 example_code.zip 并解压缩归档文件。
  2. 在 Chrome 中,通过导航到 chrome://extensions 打开 Chrome 开发者信息中心。
  3. 点击“加载已解压的扩展程序...”按钮,并选择解压缩后归档文件中的 basic_demo 文件夹。
  4. 按“确定”加载扩展。

当扩展加载后,信息中心的扩展列表中会添加一个新条目。图 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.i18nchrome.extensionchrome.runtimechrome.storage。下面的讨论将探讨内容脚本可用的两个功能:获取扩展信息和访问本地存储。

3.1.1 获取扩展信息

Chrome 扩展中的脚本可以通过顶层 chrome 对象的属性来访问浏览器及其功能。这些包括 chrome.bookmarkschrome.tabschrome.cookieschrome 的字段通常被称为 API,完整的 API 列表可以在这里找到。

最重要的 API 之一是 chrome.runtime,它使得响应事件和获取扩展信息成为可能。内容脚本无法访问 chrome.runtime 的所有功能,但它可以获取有关其扩展的信息。表 2 列出了 chrome.runtime 中实现此功能的属性。

表 2:用于内容脚本的 chrome.runtime 信息属性
属性 信息
id 扩展的唯一标识符
getManifest() 返回一个包含 manifest.json 内容的字符串
getURL(String path) 将路径转换为扩展内部的完整 URL

如图 1 所示,Chrome 为其每个扩展分配一个唯一的字符串标识符。chrome.runtimeid 字段以字符串形式提供此标识符。

通常,扩展的 URL 是 chrome-extension://id,其中 id 是扩展的唯一标识符。如果内容脚本需要其扩展中文件或文件夹的完整 URL,可以调用 getURL 并传入该文件或文件夹的相对路径。例如,getURL("images") 返回 chrome-extension://id/images。

3.1.2 本地存储访问

chrome.storage API 使扩展能够在客户端计算机上加载和存储数据。使用此 API 时,需要注意四点:

  1. 要使用该 API,扩展的清单必须将其 permissions 字段设置为包含字符串 "storage" 的数组。
  2. 数据存储在未加密的数据库文件中,因此该 API 不适用于机密信息。
  3. 脚本可以通过调用 StorageArea 对象的函数来加载和存储数据。
  4. 有三种类型的 StorageArea 可用:chrome.syncchrome.localchrome.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 函数都接受回调函数作为参数,但只有 getgetBytesInUse 是必需的。以下代码演示了如何调用 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 关联的回调会接收一个具有三个属性的对象:

  1. reason — 安装事件的原因。可以设置为 "install""update""chrome_update""shared_module_update"
  2. previousVersion — 一个字符串,标识安装/更新前的版本。
  3. id — 如果共享模块被更新,则为共享模块的标识符。

举个例子,后台脚本中的以下代码会记录安装事件的原因:

chrome.runtime.onInstalled.addListener(function(obj) {
  console.log("Installation event: " + obj.reason);
});

如果后台脚本执行此代码,消息将不会打印在当前网页的控制台中。扩展的后台脚本在一个特殊的页面中运行,可以通过进入 Chrome 开发者信息中心来访问。点击信息中心中的背景页链接可以打开背景页。

了解 onUpdateAvailableonInstalled 之间的区别很重要。如果扩展有可用更新但由于扩展正在运行而无法安装,则会触发 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 函数。第二个参数是一个对象,其属性包含标签页属性的名称和所需值。可以更改的属性是 urlactivehighlightedpinnedmutedopenerTabId。这些属性的类型与表 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 函数向当前浏览器窗口添加一个新标签页。第二个参数的属性初始化标签页的特性:urlactiveindexpinnedwindowIdopenerTabId。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_startdocument_idledocument_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 对象。它有五个可选字段:idframeIdtaburltlsChannelId
  • 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.connectidconnectInfo 参数相同。两个 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"});
});

PortonMessage 对象的工作方式与 chrome.runtime.onMessage 相同。它提供了一个 addListener 方法,该方法定义了一个在收到消息时调用的回调函数。回调函数没有理由访问响应函数,因为 Port 通信使得响应变得不必要。以下代码展示了如何使用 PortonMessage 对象:

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

后台脚本等待传入连接,向内容脚本发布一条消息,然后等待消息到达。当它收到消息时,后台脚本访问本地存储,检查返回的值,并向控制台打印一条消息。

在结束这个主题之前,关于扩展的脚本,我想提三点:

  1. 与其从后台脚本向内容脚本发送消息,后台脚本使用 chrome.tabs.executeScript 执行脚本可能更方便。后台脚本可以在 executeScript 的回调函数中访问处理结果。
  2. 确保清单的权限设置正确。如果脚本需要访问存储,"storage" 应该在权限数组中。如果脚本需要修改标签页的 URL、标题或图标,"tabs" 应该在数组中。
  3. onMessageonConnect 中的 "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 数组中。

除了 showhidechrome.pageAction 中的函数与 chrome.browserAction 中的函数基本相似。有两个重要的区别:

  1. 标签页 ID 属性从不是可选的。页面操作总是针对单个标签页进行配置。
  2. 页面操作不支持徽章,因此没有像 setBadgeTextgetBadgeBackgroundColor 这样的函数。

页面操作中的弹出窗口的工作方式与浏览器操作中的类似。下面的讨论将解释两种弹出窗口如何与后台脚本通信。

4.3 弹出窗口通信

弹出页面是使用 HTML 配置的,这意味着它们可以使用 <script> 标签包含 JavaScript 代码。方便的是,弹出页面中的脚本可以访问后台脚本的所有功能。这意味着弹出脚本可以使用消息传递与其他脚本通信。

弹出通信在本文示例归档中包含的 action_demo 扩展中进行了演示。popup.html 页面有三个复选框,其 ID 分别为 opt1opt2opt3。它执行 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日 - 文章初次提交

© . All rights reserved.