一、简介
1. 是什么?
顾名思义,扩展程序就是可以扩展浏览器功能的应用程序。
它基于Web技术(例如HTML,JavaScript和CSS),可以实现浏览器原本无法实现的功能。
2. 能做什么
包括但不限于以下列举:
- 自定义工具栏
- 桌面通知
- 标签页控制
- 网页控制
- 书签控制
- 网络请求控制
- 权限控制
- 各类事件监听
- 配置快捷键
- 右键菜单控制
- 消息传递
3. 举例
很多很多,我的浏览器上已安装的,将近二十了。列一下前端很常用的:
4. 文件介绍
基础目录:
MyExtension ├─ manifest.json // 必须 ├─ imgs // 不必须 │ ├─icon1.png │ ├─icon2.png │ └─icon3.png ├─ popup.html // 不必须 ├─ popup.js // 不必须 ├─ 其他文件 // 不必须
哈,也就是说,只有一个是必须的文件,就可以写一个扩展程序,简单吧。
这个必须的文件就是manifest.json文件。
二、开始开发
1. Hello World
一个简单的扩展程序,只需声明一个manifest.json文件,这个文件需要包含一些必填字段:
manifest_version:代表了manifest文件的版本,目前只可以填写2和3,注意1很少会支持了。浏览器会根据这个值去指定该版本拥有的功能。
name:插件的名称。
version:插件版本。
将manifest.json文件放到一个文件夹内。你的第一个程序就已经写好了。
2. Hello World的安装
打开浏览器的扩展工具管理页(在浏览器地址栏输入chrome://extensions/),启动开发者模式,点击 加载已解压的扩展程序,选择扩展工具所在目录即可安装。或者直接将文件夹拖动到管理页亦可。然后打开程序开关:
好样的,你开发了一个谷歌扩展程序并可以开始使用了!
如果想打包的话,上图中有个按钮<打包扩展程序>,点它上传你的文件包(有个输入框是选填输入私钥文件的,第一次不用填,他主要是更新时用)。以前打包好的文件可以直接拖到扩展程序里运行,但是现在我拖进去,可以安装但是无法打开程序,因为现在要求只有上传的谷歌商店的才可以打开。如何上传因为需要绑定银行卡花钱注册,我这里就不讲了。
3. 做一个有功能的插件
为了快速了解扩展程序,上文开发的Hello World,是一个没有任何功能的废插件,那么如何做一个真正意义上的插件呢?
第一步:了解manifest.json文件的配置。
总体大约50多个配置,还是比较的多。不过对大部分开发者来说,很多配置是不常用的。想都了解的话去官网看吧,我这里先简单介绍一些很常用的。
{
// 必须的字段
"manifest_version": 2, // 用整数表示manifest文件自身格式的版本号
"name": "My Extension",
"version": "1.0",// 版本字符串
// 建议提供的字段
"default_locale": "en", // 国际化的支持
"description": "A plain text description",
"icons": {
"16": "icon16.png", // 扩展程序页面上的网站图标
"32": "icon32.png", // Windows计算机通常需要此大小。提供此选项将防止48x48选项缩小造成的尺寸失真。
"48": "icon48.png", // 显示在扩展程序管理页面上
"128": "icon128.png" // 在安装时和Chrome Webstore中显示
},
// 多选一,或者都不提供
"browser_action": {...},
"page_action": {...},
"app": {...},
// 根据需要提供
"background": { // 会一直常驻的后台JS或后台页面
// 推荐
"persistent": false,
},
"content_scripts": [{...}], // 需要直接注入页面的JS文件
"omnibox": { // 地址栏的快捷搜索
"keyword": "aString"
}, // 多功能地址栏
"permissions": ["tabs"], // 扩展内将使用的一组权限
"commands": {...},// 键盘快捷键
"web_accessible_resources": ["js/inject.js"],// 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
"minimum_chrome_version": "versionString",// 扩展需要的chrome的最小支持版本
"offline_enabled": true, // 指定扩展是否支持脱机运行
"chrome_url_overrides":// 覆盖浏览器默认页面
{
// 覆盖浏览器默认的新标签页 掘金插件有使用
"newtab": "newtab.html"
},
// 有点不常用的
"devtools_page": "devtools.html",// devtools页面入口,注意只能指向一个HTML文件,不能是JS文件
"homepage_url": "http://path/to/homepage",// 这个扩展的主页
"options_ui":
{
"page": "options.html",
// 添加一些默认的样式,推荐使用
"chrome_style": true
},
...
}
应用以上的配置,就已经能开发出很多特定功能的程序了。
其中,有几个需要我重点介绍下,因为他们有比较特殊的能力,而且很常用和关键:background、content_scripts、inject-scripts、browser_action、permissions
background
background可以配置一个页面或多个js,当浏览器打开就开始运行,直到浏览器关闭(插件启动时)。
所以一般将浏览器一启动就运行、一直运行或需要监听浏览器事件的代码放到这里。
{
...
// 一下两个配置只能二选一,可在管理扩展程序页对应的程序上看到<背景页>的文本链接
"background": {
// 指定一个网页,可通过相对路径引入js
"page": "./html/background.html"
// 指定若干个js文件
"scripts": ["./js/background.js"]
}
}
background拥有的权限比较高,几乎可以调用所有的Chrome扩展API(除了devtools),同时拥有直接跨域的能力。所以使用频率比较高。
通常会在js里对浏览器事件进行监听:
通过chrome-extension://<ID>/xx.html可以直接打开后台页
content_scripts
content_scripts配置能够向页面中注入脚本(包括js和css),其中js不会与页面脚本发生冲突(与网页的js在不同的环境作用域);也就是说它和原始页面共享DOM,但是不共享JS(例如某个JS变量)。
content_scripts配置的每一项之间js共享作用域,每项内的js可以注入多个并且共享作用域。
举例:当打开任意一个网页,运行other.js,当打开百度搜索首页,将搜索按钮的颜色改为红色,并增加点击事件:
上例命中2个规则,依次会注入content.js、content2.js、other.js。
content-scripts能够访问的Chrome API的权限较少,只能访问以下四个API:
- chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)
- chrome.i18n
- chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)
- chrome.storage
如果还需要其他API的话,可以通过background.js以通信的方式传递过来。
inject-scripts
content-scripts通常只能操作DOM,但与原始页面的js无法相互访问。inject-scripts正好解决了这一点。
inject-scripts就是通过content-scripts往原始页面中插入script标签,动态引入js资源实现的。
举例:
百度页面登录后全局有bdUser变量,直接通过contentJs无法访问到,但是inject-scripts可以直接拿到。
需要注意的是,在网页中注入js资源必须配置web_accessible_resources,值为需要引入的资源路径数据。
browser_action
browser_action可以配置一个弹窗页面。它的配置包括一个图标、title、和popup页面的相对路径。图标会显示到浏览器右上角,当点击时会跳出弹窗,离开网页也会立即关闭。
主要特点是,完全自定义的页面,自适应大小,可快速方便的与用户交互,声明周期较短,权限较大,并可通过chrome.extension.getBackgroundPage()直接获取background的全局变量。
举例: 点击选项改变百度主题背景色。
permissions
这个就比较简单了,当你发现某个API没有权限使用时,看看这里有没有配置吧。常用的权限包括:
- 访问网页
- 本地存储
- web请求
- 标签
- 通知
- 右键
- 等等
{
...
"permissions": [
"contextMenus", // 右键菜单
"tabs", // 标签
"notifications", // 通知
"webRequest", // web请求
"webRequestBlocking",
"storage", // 插件本地存储
...// 等等
]
}
background、content、inject和popup之间的关系图:
第二步:了解Chrome API。
Chrome 为扩展程序提供了许多特殊用途的API。有很多,但是我们不必所有的记住。初期我们只需要对大部分API有个了解,然后在开发时去查具体怎么用也是可以的。当然,喜欢深入研究和开发较为复杂的程序的伙伴例外。
对本文中涉及到的API我都会做一定的介绍,但API的讲解不是本文的重点,而且有很多API我也没有了解和使用过,也不敢在这里做总结了,还是自行查阅吧。
第三步:了解如何通讯。
主要指background、content_scripts、inject-scripts、browser_action(popup)之间的通信。
popup和backgrobaund之间的通信
popup可以直接调用background中的JS全局变量和方法,也可以直接访问background的DOM:
var bg = chrome.extension.getBackgroundPage();
bg.params(); // 访问bg的变量,注意不能是局部变量
bg.fn(); // 访问bg的方法
console.log(bg.document.body.innerHTML); // 访问bg的DOM
background通常不会访问popup的数据,如果popup已经打开,也可以使用chrome.extension.getViews访问到弹窗:
var views = chrome.extension.getViews({type:'popup'});
if(views.length > 0) { console.log(views[0].location.href); }
popup或者backgrobaund向content之间的通信
popup或backgrobaund向content发送消息:
message可以是任意类型变量,无需JSON解析。需要指定将请求发送至哪个选项卡。
content向backgrobaund或popup发送消息:
injectJs和content之间的通信
有3种方法:
- 通过window.postMessage
- 通过自定义DOM事件来实现
- localStorage
第一种是以前前端使用iframe标签时的信息传递一样:
window.postMessage({"test": '你好!'}, '*');
接收:
window.addEventListener("message", function(e) { console.log(e.data); }, false);
第二种有些麻烦,需要自定义一个事件并绑定到隐藏的div里,通过隐藏的div传递:
inject-script中:
var customEvent = document.createEvent('Event');
customEvent.initEvent('myCustomEvent', true, true);
function fireCustomEvent(data) {
hiddenDiv = document.getElementById('myCustomEventDiv');
hiddenDiv.innerText = data
hiddenDiv.dispatchEvent(customEvent);
}
fireCustomEvent('你好,我是普通JS!');
content-script.js中:
var hiddenDiv = document.getElementById('myCustomEventDiv');
if(!hiddenDiv) {
hiddenDiv = document.createElement('div');
hiddenDiv.style.display = 'none';
document.body.appendChild(hiddenDiv);
}
hiddenDiv.addEventListener('myCustomEvent', function() {
var eventData = document.getElementById('myCustomEventDiv').innerText;
console.log('收到自定义事件消息:' + eventData);
});
这种方法很不友好。
第三种本地存储,需要注意异步问题,可能这边取的时候,那边还没存上。
上边讲到的sendMessage通信方式被称为短链接,其实chrome还提供了另一种通信方式,即长连接(chrome.tabs.connect和chrome.runtime.connect),和WebSocket比较像,不具体介绍了,可直接查看API使用。
通讯方法表(横向看):
| inject | content | popup | background | |
|---|---|---|---|---|
| inject | window.postMessage customEvent | |||
| content | window.postMessage customEvent | chrome.runtime.sendMessage | chrome.runtime.sendMessage | |
| popup | chrome.tabs.sendMessage | chrome.extension.getBackgroundPage | ||
| background | chrome.tabs.sendMessage | chrome.extension.getViews |