Chrome Extension开发须知

基本概念

Extension是由HTML、CSS、JavaScript和图片等其他资源文件组成的压缩包。它可以增强浏览器体验,实现个性化。

Files

Extension没有做目录的约定,但是它们需要已配置文件的形式写在manifest中。manifest.json中描述了Extension的基本信息、使用能力和重要文件。

manifest.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
{
// Required
"manifest_version": 2,
"name": "My Extension",
"version": "versionString",

// Recommended
"default_locale": "en",
"description": "A plain text description",
"icons": {...},

// Pick one (or none)
"browser_action": {...},
"page_action": {...},

// Optional
"action": ...,
"author": ...,
"automation": ...,
"background": {
// Recommended
"persistent": false,
// Optional
"service_worker":
},
"chrome_settings_overrides": {...},
"chrome_ui_overrides": {
"bookmarks_ui": {
"remove_bookmark_shortcut": true,
"remove_button": true
}
},
"chrome_url_overrides": {...},
"commands": {...},
"content_capabilities": ...,
"content_scripts": [{...}],
"content_security_policy": "policyString",
"converted_from_user_script": ...,
"current_locale": ...,
"declarative_net_request": ...,
"devtools_page": "devtools.html",
"event_rules": [{...}],
"externally_connectable": {
"matches": ["*://*.example.com/*"]
},
"file_browser_handlers": [...],
"file_system_provider_capabilities": {
"configurable": true,
"multiple_mounts": true,
"source": "network"
},
"homepage_url": "http://path/to/homepage",
"import": [{"id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}],
"incognito": "spanning, split, or not_allowed",
"input_components": ...,
"key": "publicKey",
"minimum_chrome_version": "versionString",
"nacl_modules": [...],
"oauth2": ...,
"offline_enabled": true,
"omnibox": {
"keyword": "aString"
},
"optional_permissions": ["tabs"],
"options_page": "options.html",
"options_ui": {
"chrome_style": true,
"page": "options.html"
},
"permissions": ["tabs"],
"platforms": ...,
"replacement_web_app": ...,
"requirements": {...},
"sandbox": [...],
"short_name": "Short Name",
"signature": ...,
"spellcheck": ...,
"storage": {
"managed_schema": "schema.json"
},
"system_indicator": ...,
"tts_engine": {...},
"update_url": "http://path/to/updateInfo.xml",
"version_name": "aString",
"web_accessible_resources": [...]
}
  • manifest_version表明使用manifest的格式版本号,目前是整数2
  • icons推荐使用PNG格式
  • browser_actionpage_action类型二选一,前者用于所有页面,后者用于特定一些页面。
  • background可以指定在后台运行的脚本
  • chrome打头的三个配置可以定制浏览器本身的UI或行为
  • commands快捷键配置
  • content_scripts描述需要声明式插入的规则
  • devtools_page描述自定义的devtools选项卡
  • externally_connectable描述其他能够连接到该Extension的url规则
  • omnibox配置关键词当用户在地址栏输入特定字符时,变成与Extension交互
  • permissions显式声明Extension需要使用的权限

Extension中的文件路径类似HTML中,通常使用相对路径访问。在使用绝对路径时,需要使用chrome-extension://<extensionID>/<pathToFile>风格的路径,可以使用chrome.runtime.getURL()得到某资源的绝对路径。

browser_action

下面是一个browser_action的manifest样例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// browser action
{
"name": "My extension",
...
"browser_action": {
"default_icon": { // optional
"16": "images/icon16.png", // optional
"24": "images/icon24.png", // optional
"32": "images/icon32.png" // optional
},
"default_title": "Google Mail", // optional; shown in tooltip
"default_popup": "popup.html" // optional
},
...
}

browser_action的UI可以包括icon、tooltip、badge、popup。

  • icon,可以是图片文件或HTML5 canvas元素。后者可以动态创建,以提供更流畅的效果。
  • tooltip,即配置文件中的title
  • badge,用于描述Extension工作状态的徽章,最长4个字符,可以调用browserAction API动态修改内容或背景色
  • popup,点击Extension按钮弹窗的窗口,可以包含任意HTML内容,可以在default_popup中定义,或调用API动态修改

browser_action有下面一些最佳实践:

  • 在Extension作用于大多数页面时使用,在作用于少数页面时使用page_action
  • 使用更多彩和重的图标,体现出和轻量级page_action的区别
  • 不要模仿chrome内置图标,会造成误解
  • 注意图标在不同主题背景色下的表现形态
  • 不要使用动图,会引起用户焦虑

page_action

更轻量级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// page action
{
"name": "My extension",
...
"page_action": {
"default_icon": { // optional
"16": "images/icon16.png", // optional
"24": "images/icon24.png", // optional
"32": "images/icon32.png" // optional
},
"default_title": "Google Mail", // optional; shown in tooltip
"default_popup": "popup.html" // optional
},
...
}

UI上没有badge,其余和browser_action一致。另外,在非指定页面下,会有灰度展示。最佳实践同上。

架构

除了manifest描述项目结构外,需要另外一些组件组成完整的Extension功能。

  • background script,Extension的事件handler,用于监听对于Extension来说重要的浏览器事件
  • UI元素,有多种体现形式,如右键菜单,omnibox,点击按钮的弹出层等
  • content scripts,用于和页面交互的js脚本。它可以传递消息给Extension的其他部分
  • options page,用于配置Extension的页面

下面是一些详细介绍

background scripts

有效率的后台脚本应该由浏览器事件触发,执行命令之后卸载。这部分脚本在manifest的background中声明。

1
2
3
4
5
6
7
8
9
{
"name": "Awesome Test Extension",
...
"background": {
"scripts": ["background.js"],
"persistent": false
},
...
}

scripts可以指定需要执行的多个后台脚本。persistent需要指定为false。只有使用了chrome.webRequestAPI的后台脚本才指定persistent为true。

使用上,

  1. 在Extension加载时,初始化一次性配置。
  2. 添加监听函数,一些监听函数还提供更多参数便于筛选事件。
  3. 在监听函数内,实现业务逻辑
  4. 在卸载前,执行持久化数据、释放请求等操作

content scripts

Content scripts运行在页面的执行环境下,通过DOM访问页面元素并和所在的Extension交互。它还可以调用Chrome APIs完成一些原生操作。Content scripts执行在和页面JS脚本相隔离的环境里,两者共享1个DOM。

Content scripts有两种执行方式,命令式插入或声明式插入。前者通过chrome.tabs.executeScript实现,后者通过在manifest中声明实现在访问特定url时自动加载js文件:

1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"css": ["myStyles.css"],
"js": ["contentScript.js"]
}
],
...
}

Content scripts和页面脚本虽然是隔离开的,但共享一个DOM,可以通过window.postMessage沟通和传递消息。

Chrome API

Extension使用特定的Chrome API在浏览器的环境下执行原生操作。API绝大多数都是异步的,这意味着如果想知道操作的结果,需要在回调函数中进行操作。Chrome的所有API都整合在Chrome这个namespace下,根据类型拆分成多个子模块,如chrome.runtime。

页面通信

由于content scripts运行在网页环境下,它通常需要和Extension本身进行通信。

  • 一次性通信,content scripts端使用chrome.runtime.sendMessage,Extension端使用chrome.tabs.sendMessage。接收侧一律使用chrome.runtime.onMessage.addListener
  • 持久性连接,类似上面使用chrome.runtime.connecttabs.connect,详见文档
  • 跨Extension通信,使用chrome.onMessageExternalruntime.onConnectExternal接收事件,发送事件可以使用上面的一次性通信或持久性连接的方式