Chrome 插件开发——基本概念

2,846 阅读7分钟
插件,即浏览器支持的扩展程序,和 web 页面一样,也是由 HTML、JS、CSS 编写的。与 web 页面相比,浏览器给插件提供了更多 API(如更改书签、截取屏幕、获取 cookie),功能十分强大,极大提升了用户体验。通常,安装后的插件会显示在浏览器地址栏旁边,下图的谷歌邮箱插件展示出当前 google 邮箱的未读邮件数量。

本文介绍运行在 Chrome 浏览器中的插件。Firefox 浏览器也能兼容 Chrome 插件的大部分 API,对 Chrome 插件进行适当的转换后,就可以应用到 Firefox 浏览器中。

插件结构

插件都必须有一个配置文件(manifest.json),用于描述插件的重要信息。插件可以包含不同的模块(页面或脚本),如下图所示。

不同插件模块有其特定功能,根据实际情况,一个插件可能包含一个或多个模块。图中标记为蓝色背景的模块都运行在同一个进程中,即插件进程。

Manifest.json 配置文件

[Manifest.json](https://developer.chrome.com/extensions/manifest) 配置文件,必须放在插件根目录中,用于描述插件、定义插件行为等。浏览器给插件提供的 API,需要在 manifest.json 中声明后方可使用,以帮助用户识别恶意插件。开发者可以通过查阅 chrome 官方的插件 API 文档,来查看某个 API 是否需要在 manifest 中声明以及如何声明。以下是一个典型的 manifest.json 示例:
// manifest.json
{
  /* required */
  "manifest_version": 2, // 固定值,manifest 版本号
  "name": "Page Color Changer", // 插件名称
  "version": "2.0", // 插件版本

  /*  recommended */
  "description": "Change your page background color", // 插件描述
  "icons": {
    "16": "icon.png", // 插件页面的 favicon
    "48": "icon.png", // 插件管理页展示的图标
    "128": "icon.png" // Chrome 商店中展示的图标
  },

  /* optional */
  "default_locale": "en", // 默认国际化语言,若插件包含 _locales 目录,必须设定默认值
  "permissions": ["<all_urls>", "storage", "cookies"], // 允许插件访问的 url 和 API
  "background": {
    "scripts": ["background.js"], // background 页面脚本
    "persistent": false // background 页面是否长驻
  },
  "options_page": "options.html", // option 页面
  "browser_action": {
    "default_icon": "icon.png", // 插件在浏览器工具栏中展示的图标
    "default_title": "Change page color", // hover 图标展示的文字
    "default_popup": "popup.html" // 点击图标展示的 popup 页面
  },
  "content_scripts": [{
    "matches": ["<all_urls>"], // 指定可以注入脚本的页面
    "js": ["content_script.js"] // 注入 web 页面的 content script 脚本
  }],
  "content_security_policy": "script-src 'self' https://ajax.googleapis.com; object-src 'self'" // 安全策略,类似于 web 页面的 CSP
} 
Note:Chrome 不会执行插件页面的内嵌的 js 脚本,包括内联的 event handlers。Js 代码需通过 src 引入,可以是插件自带的 js,也可以是外部 js。

插件模块

Popup 弹窗页

如果设置了 Popup 页,插件图标将会在浏览器工具栏中显示 。以前是支持显示在地址栏的,之后出于安全考虑取消了。点击浏览器工具栏中的插件图标,弹出的页面就是 popup 页,是与用户交互最多的页面,运行在浏览器为插件分配的进程中。

Popup 页和普通 web 页面一样,都是 HTML 文件,可引用 CSS 和 JS,但是不能执行内联的 JS 脚本,由 manifest.json 中指定,也可通过 browserAction.setPopup 或 pageAcction.setPopup 设定。

方式一:manifest.json 中指定
{
    ...
    "browser_action": {
      "default_popup": "popup.html"
    }
    ...
  }
方式二:动态设定。推荐使用 browserAction. setPopup,以保证不管在哪个页面插件都是可点击的。而 pageAction.setPopup 只有在指定页面才可点击弹出。
chrome.storage.local.get('signed_in', function(data) {
    if (data.signed_in) {
      chrome.browserAction.setPopup({popup: 'popup.html'});
    } else {
      chrome.browserAction.setPopup({popup: 'popup_sign_in.html'});
    }
  });
Popup 页有以下几个限制:
  1. 只能通过用户点击插件图标弹出,无法通过程序自动弹出
  2. 页面大小是自适应的,但宽度最大 800px,高度最大 600px
  3. 失焦即关闭。用户点击 popup 页面以外区域,或者在 popup 页面内打开新页面,都会使它关闭

Content script 内容脚本

Content script 是插件注入到 web 页面运行的脚本(JS 或 CSS),虽是插件的一部分,却以沙盒模式运行在 web 页面 render 进程中。它的运行环境与 web 页面是完全隔离的,不能访问彼此的全局变量。Content script 与 web 页面共享 DOM 节点,既可改变 web 页面的 DOM,又可与父插件进行通信,常常充当 web 页面和插件之间的桥梁。
Content script 注入的过程是这样的:
  1. 插件加载时,浏览器把插件声明的 content script 内容保存起来
  2. Web 页面对应的 render 进程启动完成时,通知浏览器,获取浏览器之前收集的 content script 内容
  3. Web 页面加载过程中,根据 content script 匹配规则和指定的加载时机,将其交给 JavaScript 引擎,在一个隔离环境中执行
Content script 有两种注入方式:manifest.json 声明、代码中注入。
方式一:manifest.json 声明,这些脚本将按顺序注入 web 页面运行,其它配置属性还包括 all_iframes、exclude_matches 等
{
 ...
 "content_scripts": [
   {
     "matches": ["http://*.nytimes.com/*"], // Required,允许往哪些页面注入脚本
     "css": ["myStyles.css"], // css 列表,在 DOM 生成或展示前注入
     "js": ["contentScript.js"], // js 列表
     "run_at": "document_idle" // js 注入时机,还支持 domcument_start、document_end
   }
 ],
 ...
}
方式二:代码中注入
chrome.tabs.executeScript({file: 'contentScript.js'});
由于 content script 运行在 web 页面中,它能直接使用的插件 API 很少,只有 i18n,storage,runtime (connect, getManifest, getURL, id, onConnect, onMessage, sendMessage) 这几个 API。但它可以用通信的方式让插件的其它页面代替执行更多 API。

Background 后台页

顾名思义,background 页就是运行在插件后台的页面,用户感觉不到它的存在。然而,background 页扮演着中心角色,常用于监听浏览器事件(如导航到新页面、移除书签、关闭页面等),或者保存插件当前的运行状态。Background 页在 manifest.json 中配置,如下:
chrome.tabs.executeScript({file: 'contentScript.js'});// manifest.json
{
  ...
  "background": {
    "scripts": ["background.js"], // 多个 js 会按顺序加载
    "persistent": false // 是否长驻内存
  },
  ...
}
根据 persist 属性,background 页面有两种形式:
  • Persist = true(默认)—— 在插件启用后长驻内存中,不管用户有没有在使用插件
  • Persist = false —— 按需加载,不需要时就销毁,也称 event page
按需加载的 background 页可在以下情况加载:
  1. 插件首次安装时,或更新版本时
  2. Background 页面监听的某个事件被触发
  3. 接收自家 ontent script 或兄弟插件发送来的消息
  4. 同一插件的其它页面调用了 runtime.getBackgroundPage
Background 页面加载后,只要它还有动作,比如调用 Chrome API 或发起请求,就会保持运行。如果没有更多操作,一般几秒就销毁了。此外,如果存在可见界面或消息通道还未关闭,也不会销毁。

Options 选项页

选项页给用户提供了配置插件的入口,可通过右击插件图标出现的「Options」菜单进入,也可在插件管理页面中选择「Extension options」进入。通过调用存储 API 把用户配置持久化到本地,插件其它模块在运行时根据需要读取。Options 页默认在新 tab 打开,也支持内嵌的打开方式,交互形式和模态框很像。但内嵌的方式并不常用,可用的 API 也更少。

DevTool 页

插件可以通过 DevTool 页给浏览器的开发者工具添加新面板(如 Redux 开发者工具),或者扩展已有的面板(如 Elements 面板)。DevTool 页运行在 DevTool 进程中,在 DevTool 窗口打开时创建一个实例,窗口关闭时销毁。 Manifest.json 中配置方式如下:
{
  ...
  "devtools_page": "devtools.html",
  ...
}
和 Content script 类似,虽然没有运行在插件进程中,但做为插件的一部分,DevTool 页仍然可以和父插件通信。DevTool 页能访问大部分插件 API,及 DevTool 独有的 API。

Tab 页

Tab 页和 options 页类似,运行在插件进程中,像普通页面一样正常打开,其 URL 包含了所在插件的 ID。需要使用 Tab 页的场景不多,了解一下就可以了。

示例

示例插件(https://github.com/inkie/Tutorial/tree/master/change-page-color)实现了让更改 web 页面背景颜色的功能。虽然功能很小,但它包括了 popup 页、Content script、background 页、options 页、tab 页、devtools 页,便于了解插件各个模块。目录结构如下:
└── page_color_changer    
├── manifest.json    
├── assets    
│   ├── icon.png    
├── background    
│   ├── background.js    
├── content    
│   ├── content_script.js    
├── devtools    
│   ├── devtools.html    
│   ├── devtools.js    
├── options    
│   ├── options.html    
│   ├── options.js    
├── pages    
│   ├── tab1.html    
│   ├── tab2.html    
│   ├── main.js    
├── popup    
│   ├── popup.html    
│   ├── popup.js