一、什么是Chrome Extension
Chrome Extension
是Google
所提供的在Chrome
浏览器体系里进行开发,增强浏览器功能的一套技术方案,可以采用Web技术
进行开发,主要使用的是HTML
、CSS
和JS
。相关的资源最终打成一个.crx
包,这个.crx
包可以上架Chrome
商店,从而可以进行分发和下载。
二、Chrome Extension的能力
Chrome Extension
提供了许多的API可供我们使用,通过对这些API
的使用,我们可以做到:
- 控制书签、控制下载、控制窗口、控制标签、控制网络请求
- 监听各类事件
- 自定义原声的菜单
- 完善的通信机制
- 等等
三、目录结构与开发调试
Chrome Extension
项目并没有严格地限制目录结构,唯一的限制是要求根目录下需要有manifest.json
文件,这个文件用以申请一些权限、配置一些基础信息。
调试Chrome Extension
也很容易,只需要通过地址栏输入:chrome://extensions
即可进入插件管理页面,如下:
在这个页面里,我们可以打开右上角的开发者模式
,如此便可通过点击左上侧的加载已解压的扩展程序
按钮来直接加载项目目录,这对于开发而言是非常方便的。而在最终部署.crx
包时,则可以使用打包扩展程序
功能。
在开发过程中,当源码有变更时,我们需要手动点击“刷新”图标,来重新加载Chrome Extension
,如下:
四、manifest.json
这个文件是一个Chrome Extension
中最为关键和不可缺少的文件,所有插件相关的配置、权限的申请都通过这个文件完成。它必须放置于项目文件的根目录中,并且以下字段是必填的:
manifest_version
,清单文件的版本,固定为2
即可name
,插件的名称version
,插件的版本
除此之外,常见的配置信息如示例:
{
"manifest_version": 2,
"name": "demo-plugin",
"version": "1.0.0",
"description": "描述信息",
// 图标,最好提供多个尺寸,但是只提供一个尺寸也是可以的
"icons": {
"16": "img/icon.png",
"48": "img/icon.png",
"128": "img/icon.png"
},
// 常驻后台的脚本
"background": {
// 可以用 page 也可以用 scripts 指明
"page": "background.html",
// scripts的情况下,会自动生成一个html页引用scripts
// "scripts": ["js/background.js"]
},
// 浏览器右上角图标对应的脚本,有 browser_action,page_action 和 app
"browser_action": {
"default_icon": "img/icon.png",
// 图标悬停标题(可选)
"default_title": "悬停标题",
"default_popup": "popup.html"
},
// 某些特定页面打开才显示图标(与 browser_action,app 是冲突的,三选一)
// "page_action": {
// "default_icon": "img/icon.png",
// // 图标悬停标题(可选)
// "default_title": "PageAction悬停标题",
// "default_popup": "popup.html"
// }
// 需要直接注入页面的JS
"content_scripts": [
{
// 指定在哪些URL里注入,如"http://*/*","https://*/*",“*://*/*.png"等
// 可以使用"<all_urls>"表明是匹配所有地址
"matches": ["<all_urls>"],
// JS将按照指定的顺序注入
"js": ["js/a.js", "js/b.js", "js/c.js"],
// 注入CSS
"css": ["css/custom.css"],
// 指定注入的时机,可以是 document_start,document_end,document_idle 中的一个
// 分别是 开始加载时、加载结束时、页面空闲时,默认是 document_idle
"run_at": "document_start"
}
],
// 权限申请
"permissions": [
"contextMenus", // 右键菜单
"tabs", // 标签
"notifications", // 通知
"webRequest", // web请求
"webRequestBlocking",
"storage", // 存储
"https://*/*", // 可以通过 executeScript 或 insertCSS 访问的网站
"http://*/*"
],
// 普通页面能够直接访问的资源列表
"web_accessible_resources": ["js/inject.js"],
// 插件主页
"homepage_url": "https://ruphi.cn",
// 覆盖浏览器的默认页面
"chrome_url_overrides": {
// 覆盖默认的新标签页
"newtab": "newtab.html"
},
// 插件配置页
// 这是 Chrome 40 之前支持的写法
// "options_page": "options.html"
// 这是 Chrome 40 之后支持的写法
"options_ui": {
"page": "options.html",
// 是否添加一些默认样式
"chrome_style": true
},
// 向地址栏注册一个关键词,用以提供搜索建议,只能设置一个
"omnibox": {
"keyword": "addto"
},
// 默认语言
"default_locale": "zh_CN",
// devtools 页面的入口,只能指向一个 HTML 文件(不可以是JS)
"devtools_page": "devtools.html"
}
五、脚本类型
在Chrome Extension
中,有background script
,content script
、popup script
,要掌握好Chrome Extension
的开发,分清楚这些脚本之间的差别是很重要的
1、content-scripts
content-scripts
可以向页面注入脚本或者CSS
,借助它可以实现通过配置的方式向指定页面注入JS
和CSS
,因此可以实现广告屏蔽
、页面CSS定制
等等,在manifest.json
里的配置的定义为:
interface Manifest {
// ...
content_scripts: Array<ContentScript>
}
interface ContentScript {
matches: Array<string>,
js: Array<string>,
css: Array<string>
run_at: 'document_start' | 'document_end' | 'document_idle'
}
需要注意的是,如果content_script
里的代码需要在页面加载完成后做一些事情,那么需要指定为document_start
,如下:
document.addEventListener('DOMContentLoaded', () => {
console.log(`I've been executed`)
})
需要注意的是:
content-script
可以跟原始页面共享DOM
,但是不共享JS
(即原页面的JS
,如原页面的某个变量等)content-script
只能访问四种Chrome API
,即为:chrome.extension
chrome.extension.getURL
chrome.extension.inIncognitoContext
chrome.extension.lastError
chrome.extension.onRequest
chrome.extension.sendRequest
chrome.i18n
chrome.runtime
chrome.runtime.connect
chrome.runtime.getManifest
chrome.runtime.getURL
chrome.runtime.id
chrome.runtime.onConnect
chrome.runtime.onMessage
chrome.runtime.sendMessage
chrome.storage
要突破以上的限制,需要:
- 共享JS,需要通过
injected js
实现 - 访问其他API,需要通过通信机制让
background
帮忙调用
2、background
background
是常驻内存的页面,其生命周期是五种脚本中最长的。它跟随浏览器打开和关闭,因此对于那些需要一直运行、启动就运行的代码,可以放在background
里。此外,background
具有非常高的权限,几乎可以调用所有的Chrome扩展API
(除了devtools
),并且可以无限制地跨域,而无需服务器支持CORS
。
其实,所有通过
chrome-extension://id/xxx.html
打开的页面,都可以直接跨域
background
的配置定义为:
interface Manifest {
// ...
background: BackgroundScript
}
interface BackgroundScript {
page?: string,
scripts?: Array<string>,
persistent: boolean
}
特别注意: 由于
background
的生命周期可能太长,长时间挂载在后台可能会影响性能,所以有了event-pages
,即作为background
的补充方案而出现,它是一种特殊的background
,区别在于persistent
参数为false
。event-pages
的生命周期则为:在需要时被加载,在空闲时被关闭。被需要的时机,则可以是第一次安装extension、更新extension或者有content-script
发过来消息时,等等。
3、popup script
这种脚本类型是在点击浏览器右上角图标(通过browser_action
或者page_action
指定)时打开的一个小窗口,在失去焦点后,这个窗口就会关闭。因此可以用来做一些临时性的交互。
在popup script
中,可以包含任意的HTML
内容,而且会自适应内容的大小。它的配置定义为:
interface Manifest {
// ...
browser_action: BrowserAction
}
interface BrowserAction {
default_icon?: string,
default_title?: string,
default_popup: string
}
在权限上,popup script
和background
差不多,最大的不同是生命周期的不同,popup script
的生命周期其与聚焦,止于失焦。可以通过chrome.extension.getBackgroundPage()
获取background
的window
对象
六、展示形式
Chrome Extension
可展示在浏览器不同的地方,这些地方总共有:
1、browserAction
browserAction
展示在浏览器右上角,地址栏右边,示例如图:
配置示例:
{
// ...
"browser_action": {
"default_icon": "img/icon.png",
"default_title": "标题",
"default_popup": "popup.html"
}
}
官方推荐使用19×19px
的图片作为图标,也可以使用更大的图片,但是会被自动压缩。除了在manifest.json
中制定图标之外,也可以通过调用API
交由background
设置,如下:
chrome.browserAction.setIcon({
path: './img/icon2.png'
})
需要注意的是,通过API设置的图片有大小要求,超过190px
的图片将会导致设置失败。在browserAction
中,我们还可以调用setBadgeText
方法,来在图标上展示一段文字(但是有长度限制,中文2个字符,英文4个字符),示例如下:
chrome.browserAction.setBadgeText({ text: 'NEW' })
// 还可以调用以下代码来设置背景颜色
chrome.browserAction.setBadgeBackgroundColor({ color: [255, 0, 0, 255] })
效果如图:
2、pageAction
pageAction
的展示位置和browserAction
相同,但不同的是pageAction
作用于特定页面,而browserAction
作用于浏览器全局。在pageAction
中,当未激活时,会显示为灰色。并且在灰色状态下,点击后只会弹出选项菜单。如图:
可以通过调用以下两个方法来控制图标的隐藏或者显示:
chrome.pageAction.show(tabId)
,显示图标chrome.pageAction.hide(tabId)
,隐藏图标
3、右键菜单
通过chrome.contextMenus
这个API
,我们可以实现为右键菜单添加功能的效果。使用前需要现在manifest.json
里申请权限,最简单的示例代码如下:
manifest.json
{
// ...
"permissions": [
"contextMenus"
]
}
background.js
chrome.contextMenus.create({
title: 'Try to click me',
onclick() {
alert('Hey, you clicked me')
}
})
效果如下:
此外,我们还可以在title
中指定%s
为选中的文本,并对此进行操作,如下:
chrome.contextMenus.create({
title: 'Add: %s',
contexts: ['selection'],
onclick(params) {
console.log(params)
alert(`Added ${params.selectionText}`)
}
})
如此一来,便只有在选中文本的情况下,会添加菜单项:
关于右键菜单,常用的一些API操作则如下:
chrome.contextMenus.create({
type: 'normal', // 可以设置类型,可选值有 normal / checkbox / radio / separator
title: '菜单标题', // 除type为separator外,其他类型中title都是必须设置的,对于 contexts 为 selection 的情况,可以用`%s`拿到选中文本
contexts: ['page'], // 出现菜单的上下文环境,可选值有 all / page / frame / selection / link / editable / image / video / audio
onclick() {
// 定义点击时触发的行为
},
parentId: 1, // 可以通过这个设置嵌套菜单,形成父子菜单
documentUrlPatterns: 'https://*.ruphi.cn/*' // 指定特定页面展示此右键菜单
})
// 可以调用来删除某一个菜单项
chrome.contextMenus.remove(menuItemId)
// 可以调用来删除所有自定义右键菜单
chrome.contextMenus.removeAll()
// 可以调用来更新某一个菜单项
chrome.contextMenus.update(menuItemId, updateProperties)