如何开发Chrome扩展程序
# 什么是Chrome扩展程序
Chrome扩展程序又称为谷歌浏览器插件,是基于诸如HTML、JavaScript和CSS等web技术构建的定制浏览体验的小型软件程序,允许用户根据个人需求或喜好定制Chrome的功能和行为,使用Chrome插件可以为Chrome浏览器带来一些功能性的扩展,进而提高Chrome的使用体验。
# 如何开发Chrome扩展程序
# 1. Manifest.json
每个扩展程序都需要有一个Manifest.json配置文件,用来配置插件所有的配置项,该文件必须放在根目录。以下为一些常见的配置项,完整说明文档请参考官方文档。
{
// *必选项 指定软件包所需的清单文件格式的版本, 在Chrome 17或更低版本中设置1,Chrome 18或更高的版本设置2
"manifest_version": 2,
// *必选项 名称(最大45个字符)
"name": "Hello Extension",
// 简称
"short_name": "Extension"
// *必选项 版本
"version": "1.0.0",
// 默认语言环境
"default_locale": "en",
// 描述
"description": "第一个Chrome 插件",
// 图标
"icons": {
"16": "img/icon.png",
"48": "img/icon.png",
"128": "img/icon.png"
},
// 浏览器右上角图标设置
"browser_action": {
// 图标,可选
"default_icon": {
"16": "images/icon16.png",
"24": "images/icon24.png",
"32": "images/icon32.png"
},
// 图标悬停时的标题,可选
"default_title": "这是一个示例Chrome插件",
// 用户点击图标弹出的窗口
"default_popup": "popup.html"
},
// browser_action、page_action不能同时存在
// 当某些特定页面打开才显示的图标,具体怎么显示通过api控制https://developer.chrome.com/extensions/pageAction
/*"page_action":{
"default_icon": {
"16": "images/icon16.png",
"24": "images/icon24.png",
"32": "images/icon32.png"
},
"default_title": "我是pageAction",
"default_popup": "popup.html"
},*/
// 后台脚本,会一直常驻在后台的JS或页面
"background": {
// 2种指定方式,可以指定一个页面,也可以指定一组js脚本。如果指定JS,那么会自动生成一个背景页
// "page": "background.html"
"scripts": ["js/background.js"]
},
// 需要直接注入页面的JS
"content_scripts": [
{
"matches": [
// 匹配的地址
["http://*/*", "https://*/*"],
//"<all_urls>" //表示匹配所有地址
],
// 多个JS按顺序注入
"js": [
"js/jquery.js",
"js/content-script.js"
],
// CSS注入需要注意不影响全局样式
"css": [
"css/content.css"
],
// 代码注入的时机,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle
"run_at": "document_start"
},
// content-script可以配置多个规则
{
"matches": [
"<all_urls>"
],
"js": [
"js/content.js"
]
}
],
// 权限
"permissions": [
"contextMenus", // 右键菜单
"tabs", // 标签
"notifications", // 通知
"webRequest", // web请求
"webRequestBlocking",
"storage", // 插件本地存储
"http://*/*", // 可访问的网站
"https://*/*" // 可访问的网站
],
// 允许页面能够直接访问插件的资源列表,如果不设置是无法直接访问的
// 可以使用通配符指定
"web_accessible_resources": [
"js/main.js",
"images/*.png"
],
// 插件主页
"homepage_url": "https://",
// 覆盖浏览器默认页面
"chrome_url_overrides": {
// 覆盖浏览器默认的新标签页
"newtab": "newtab.html"
// 书签管理器
"bookmarks": "bookmarks.html"
// 历史记录
"history": "history.html"
},
// Chrome40以前的插件选项页写法
"options_page": "options.html",
// Chrome40以后的插件选项页写法,如果2个都写,新版Chrome只认后面这一个
"options_ui": {
"page": "options.html",
// 添加一些默认的样式,推荐使用
"chrome_style": true
},
// 向地址栏注册一个关键字以提供搜索建议,只能设置一个关键字
"omnibox": {
"keyword": "aString"
},
// DevTools扩展程序可以为Chrome DevTools添加功能,像Vue Devtools
"devtools_page": "devtools.html"
}
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# 2. background
事件是浏览器触发器,例如导航到新页面、删除书签或关闭选项卡。扩展是基于事件的程序,用于修改或增强Chrome浏览体验。而background则是扩展在其后台中监视这些事件,对指定的指令作出反应的脚本。
background是一个常驻的页面(如果在配置文件指定background为js,那么会自动生成一个背景页),它的生命周期是插件中所有类型页面中最长的,随着浏览器的打开而打开,随着浏览器的关闭而关闭。所以通常把需要一直运行、启动就运行的、全局的代码放在background里面。
background的权限非常高,几乎可以调用所有的Chrome扩展API(除了devtools),而且它可以无限制跨域,也就是可以跨域访问任何网站而无需要求对方设置CORS
。
# 注册后台脚本
后台脚本在Manifest.json中的"background"
字段注册。两种指定方式,可以指定一个页面,也可以指定一组JS脚本。如果指定JS,那么会自动生成一个背景页。
// Manifest.json
{
"background": {
"scripts": ["background.js","main.js"],
//"page": "background.html"
"persistent": false
},
}
2
3
4
5
6
7
8
长时间挂载后台可能会影响性能,可以通过指定"persistent":true
指定脚本在需要时加载,空闲时关闭。
# 初始化扩展
监听runtime.onInstalled
事件以初始化安装扩展
chrome.runtime.onInstalled.addListener(function() {
// do something
});
2
3
# 设置监听器
chrome.runtime.onInstalled.addListener(function() {
chrome.contextMenus.create({
"id": "sampleContextMenu",
"title": "Sample Context Menu",
"contexts": ["selection"],
onclick: function (e) {
// do something
}
});
});
// This will run when a bookmark is created.
chrome.bookmarks.onCreated.addListener(function() {
// do something
});
// 注意:不要异步注册侦听器,因为它们不会被正确触发。
// 例如以上代码错误写法
chrome.runtime.onInstalled.addListener(function() {
// ERROR! Events must be registered synchronously from the start of the page.
chrome.bookmarks.onCreated.addListener(function() {
// do something
});
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3. content-scripts
content-scripts
是一种注入到普通页面执行的脚本,虽然名字带有scripts,但是也可以注入CSS(需要注意不影响全局的样式)。
# 注册content-scripts
{
"content_scripts": [
{
"matches": [
// 匹配的地址
["http://*/*", "https://*/*"],
//"<all_urls>" //表示匹配所有地址
],
"js": [
"js/jquery.js",
"js/content-script.js"
],
"css": [
"css/content.css"
],
// 代码注入的时机,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle
"run_at": "document_start"
},
// content-script可以配置多个规则
{
"matches": [
"<all_urls>"
],
"js": [
"js/content.js"
]
}
],
}
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
# 向页面注入JS脚本
content-scripts
可以访问到页面的DOM,但是不能访问页面的JS,DOM也不能调用它(即无法在DOM中通过绑定事件的方式调用content-script
中的代码)。如果需要访问页面的JS,可以通过DOM操作的方式,向页面注入JS脚本,例如:
let jsPath = 'js/inject.js';
let tempScript = document.createElement('script');
tempScript.setAttribute('type', 'text/javascript');
// chrome.extension.getURL(jsPath)获取inject.js的绝对路径
tempScript.src = chrome.extension.getURL(jsPath);
document.head.appendChild(tempScript);
2
3
4
5
6
需要注意的是,普通页面访问插件的资源,需要在Manifest.json配置该资源是可以被普通页面访问的
{
"web_accessible_resources": ["js/inject.js"],
}
2
3
另外,因为需要直接操作DOM,所以需要在DOM加载完成后才能执行,如果配置项中"run_at":"document_start"
,可以监听页面加载完毕再执行上面的代码
document.addEventListener('DOMContentLoaded', function(){
let jsPath = 'js/inject.js';
let tempScript = document.createElement('script');
tempScript.setAttribute('type', 'text/javascript');
// chrome.extension.getURL(jsPath)获取inject.js的绝对路径
tempScript.src = chrome.extension.getURL(jsPath);
document.head.appendChild(tempScript);
});
2
3
4
5
6
7
8
# 4. popup
popup是点击地址栏右侧插件图标(需要用户自行操作固定)弹出的小窗口,分为browser_action
和page_action
两种。browser_action
是显示在浏览器右上角的图标,在所有的页面都会显示;page_action
是当某些特定页面打开才显示的图标;两种不能同时存在。
popup可以包含任意的HTML内容,并且会自适应大小。可以通过default_popup
字段来指定popup页面,也可以调用chrome.browserAction.setPopup(object details, function callback)
方法来设置。
需要注意的是,popup是用户点击图标打开,焦点离开会立即关闭,所有popup页的生命周期一般比较短,需要长时间运行的代码不建议放在popup,可以通过chrome.extension.getBackgroundPage()
获取background,调用background的方法。
# 注册popup
{
// 浏览器右上角图标设置
"browser_action": {
// 图标,可选
"default_icon": {
"16": "images/icon16.png",
"24": "images/icon24.png",
"32": "images/icon32.png"
},
// 图标悬停时的标题,可选
"default_title": "这是一个示例Chrome插件",
// 用户点击图标弹出的窗口
"default_popup": "popup.html"
},
// browser_action、page_action不能同时存在
// 当某些特定页面打开才显示的图标,具体怎么显示通过api控制https://developer.chrome.com/extensions/pageAction
/*"page_action":{
"default_icon": {
"16": "images/icon16.png",
"24": "images/icon24.png",
"32": "images/icon32.png"
},
"default_title": "我是pageAction",
"default_popup": "popup.html"
},*/
}
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
# 5. 本地存储
本地存储提供了chrome.storage
和chrome.storage.sync
两种可供选择,区别在于chrome.storage.sync
可以跟随当前登录用户自动同步到服务器,在其他电脑可以同步使用。
# 权限说明
需要注意的是,使用本地存储需要在Manifest.json
声明权限
{
// 权限
"permissions": [
"storage", // 插件本地存储
],
}
2
3
4
5
6
# 操作Storage
// 存储数据
chrome.storage.sync.set({ 'key': value }, (res) => {
console.log(res);
});
// 获取数据
// 第一个参数是获取的数据的key,可以是一个字符串,也可以是一个数组,包括多个key
chrome.storage.sync.get(['key1', 'key2', "key3"], (res) => {
console.log(res)
});
2
3
4
5
6
7
8
9
10
# 6. 消息通信
# chrome.tabs.sendMessage
可用于popup或者background向content发送消息,示例:
/**
* 发送消息到content_scripts
* @param {Object} 消息内容
* @param {Function} 接收回复的回调函数
*/
function sendMessageToContentScript(message, callback) {
// 获取当前打开的窗口id
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
let tabItem = tabs[0]
let id = tabItem?.id || ''
if (id === '') return;
// 发送消息
chrome.tabs.sendMessage(id, message, function (response) {
if (callback) callback(response);
});
});
}
sendMessageToContentScript({ cmd: 'test', value: 'test msg' }, function (response) {
console.log(response);
});
/**
* 接收消息
* @param {Function}
*/
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
// 通过request.cmd判断不同的消息
if (request.cmd == 'test') {
console.log(request)
sendResponse('收到了');
// do something
}
if (request.cmd == 'a') {
console.log(request)
sendResponse('收到了');
// do something
}
});
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
# chrome.runtime.sendMessage
可用于content向popup或者background发送消息,示例:
// 发送消息
chrome.runtime.sendMessage({msg: "来自content的消息"});
// 接收消息
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
console.log(request);
sendResponse("收到");
});
2
3
4
5
6
7
8
# chrome.extension.getBackgroundPage
可用于popup直接调用background中的js方法或者直接访问background的DOM,示例:
let bg = chrome.extension.getBackgroundPage();
bg.test(); // 访问bg的函数
console(bg.document); // 访问bg的DOM
2
3
# window.postMessage
可用于注入页面的脚本与content通信,示例:
// 发送
window.postMessage({"msg": '你好!'}, '*');
// 接收
window.addEventListener("message", function(e) {
console.log(e.data);
}, false);
2
3
4
5
6
7
# 7. 调试
# background
background是一个常驻的页面,但是用户不能直接访问,调试需要通过扩展程序管理器打开相应的背景页,通过chrome://extensions/
打开扩展程序管理器,打开开发者模式,点击相应插件的背景页,如下图所示:
调试器如下图所示:
需要注意的是: 1.每次修改代码,都需要重新加载插件; 2.可以通过
chrome-extension://插件id/background.html
直接打开background,但是打开的background不是在后台常驻的,所以调试还是需要通过上面的方法打开。
# popup
右键地址栏右边的插件图标,选择审查弹出内容,即可打开调试器
# content-script
content-script及其插入到页面运行的脚本都是在页面运行的,所以调试在相应的页面打开调试器即可。