虽然现在的工作主要是Android平台的开发,但是对于Web的兴趣总是无法割舍。只可惜因为不是日常工作的一部分,许多Web技术其实学得都不精湛,也常常会淡忘了解过的知识。好在有了Chrome这个强大的前端神器,观察页面布局、学习脚本源码、调试代码样式都变得异常自如,也让学习Web变得方便很多。
前阵子为了完成一些功能,学习了一下Chrome扩展程序(Chrome Extensions)开发的基本内容和步骤,觉得为自己的Web技术找到了新的用武之地。不用再纠结和苦恼如果设计发开出功能全面界面吸引人的应用或系统;做出一些插件来扩展喝补强当前页面功能也是很有意义的。比如常看一些网页被旁边的广告栏打扰了,可以写个插件把display
设成none
;如果在线看一些文档,也可以做个插件用来标记哪些内容比较重要然后呈现时做个高亮显示。
开发Chrome扩展程序也非常容易上手,只要会HTML,CSS和JavaScript就可以开始了。不需要学习新的语言,也不需要了解新的布局方式,也不用知道新的IDE,还不用编译。当然Chrome扩展程序的基本架构和组成结构还是需要额外了解的,这样才能知道每个部分要如何实现,保证扩展程序能流畅运行。
Chrome扩展程序的官方文档让人感觉有点凌乱,可能是扩展程序开发算不上热门内容,也可能是Chrome的版本升级太频繁,所以没有很好地整理文档。本文就来总结这些知识内容与大家分享,也方便自己以后回顾。
入门准备和基础概念
既然是开发Chrome扩展程序,自然需要Chrome浏览器,然后下载一些示例程序。一般的Chrome扩展程序是.crx文件,但实质上它只是一个压缩文件,随时可以以zip格式来解压查看里面的内容。但是为了开发方便,Chrome也支持直接上传文件夹,但是要先打开”开发模式”。启用方式很简单,只要开打Chrome浏览器,在Extensions窗口下(可通过菜单开打,也可以在地址栏里输入chrome://extensions
来进入)勾选Develop mode就可以了:
这样就可以直接上传文件夹来安装在开发中的扩展程序了。如果开发完成也通过这里进行打包然后发布到Chrome商店上,不过这部分不在本文中介绍。
对比直接上传的和从商店上安装的扩展程序,可以发现几个明显的不同:
以文件夹格式上传的,会显示Loaded from项来指示扩展程序在文件系统中的位置,另外还可以通过刷新或者点击Reload链接按钮来更新修改过的扩展程序。而安装的则无法这么简单的来更新和升级了。
当然其他的内容都是一样的:名字、版本号这些接下来会介绍如何定义;权限(Permissions)的内容比较多本文也不会涉及,可以在官方文档中了解。还有ID这一项,它是Chrome指定给每个扩展程序的唯一标示,可以用它在Chrome浏览器中通过链接chrome-extension://<extensionID>/<pathToFile>
打开扩展程序中的任意文件。比如Evernote的选项页面就是:chrome-extension://pioclpoplcdbaefihamjohnefbikjilc/options.html
对于没有打包成.crx文件的扩展程序,Chrome会根据文件夹所在位置来指定生成ID;在第一次打包扩展程序时,Chrome会生成一个Private Key文件,之后就可以用同样的Key来标示同一个程序的版本升级,Chrome也就会赋予它同样的ID。之后也会介绍这个ID还有其他的用处。
基本结构组成
组织文件(Manifest File)
在开发Chrome扩展程序会创建许多.html、.css、.js文件,这些文件对于程序来说除非后缀能区分出类型外,其他的都是一致。因此就需要一个配置文件来告诉程序每个文件都应该在什么时候使用,这就是manifest.json所要做的事情。它就像Android开发里的AndroidManifest.xml告诉程序哪些类是用作Activity,哪些类是用来当Service。
manifest.json里有很多的配置信息,但不是每一项都是必须的,这里会介绍必要的配置项目,文章后边会介绍主要结构的配置方式和内容。
manifest.json
在Chrome扩展程序中,其他文件都可以任意起文件名,只有manifest.json是唯一指定了文件名和文件格式的文件,程序会通过manifest.json的配置信息来设定相关内容。其中下边的内容是相对比较重要的信息:
[codesyntax lang=”javascript” lines=”normal”]
{ // Required "manifest_version": 2, "name": "My Extension", "version": "versionString", // Recommended "description": "A plain text description", "icons": {...}, }
[/codesyntax]
这些设置除了manifest_vesion要使用固定值2,因为不同的版本manifest的格式有一定的区别。其他的都是自定义的内容,这些主要是对该Chrome扩展程序基本的信息作介绍,当上传到Chrome的网上商店时也方便用户查找到这个程序,并能知道程序的基本功能。
交互界面(Interaction Interface)
一个面向用户的程序,总是要有交互界面来响应用户的动作,为用户提供选择菜单,激活特殊的效果等等。Chrome扩展程序也有这些界面,它们如同一般的网页一样,使用HTML来布局、用CSS了设定样式,只是他们在浏览器的特定位置出现。
Browser Action
Browser Action是扩展程序在Chrome浏览器上长期驻留的按钮,可以直接触发某个事件方法,或者打开一个弹出框来展示内容。比如1Password的弹出框会要求用户输入主密码,在之后曾可以选择快速登录的列表。
如果想让Chrome扩展程序有Browser Action,就需要在manifest.json里面定义browser_action
:
[codesyntax lang=”javascript” lines=”normal”]
"browser_action": { "default_icon": ... // optional "default_title": "My Extension", // optional; shown in tooltip "default_popup": "popup.html" // optional }
[/codesyntax]
看起来browser_action
的配置信息都是可选,那是因为这些内容除了在配置文件里设定还可以在代码中通过chrome.browserAction
下的方法来动态设置。但是要想让扩展程序在Chrome浏览器上呈现相应的Browser Action按钮,就必须在配置中申明browser_action
以便让程序知道需要使用Browser Action,即使browser_action
对应的值只是一个空对象,否则chrome.browserAction
将会是undefined
。
一般的Browser Action的图标和标题基本也是固定的,所以也会在配置文件中直接设置。如果按下Browser Action的只是想触发某个事件,那么可以不用定义default_popup
,而是在代码中设定onClicked
即可:
[codesyntax lang=”javascript” lines=”no”]
chrome.pageAction.onClicked.addListener(function(tab) { ... })
[/codesyntax]
值得注意的是如果Browser Action设置了弹出框,那么其onClicked
事件里的Listener就不会触发。
如果设置了弹出窗口,想要调试它也很容易。只要在对应的扩展程序的图标上右击选择Inspect Popup,或者在弹出窗口显示时在上边右键选择Inspect Element就会打开独立的调试窗口来查看样式布局,输入脚本指令。
Page Action
Page Action基本跟Browser Action一样,只是申明是要用page_action
:
[codesyntax lang=”javascript” lines=”normal”]
"page_action": { "default_icon": ... // optional "default_title": "My Extension", // optional; shown in tooltip "default_popup": "popup.html" // optional }
[/codesyntax]
还有呈现的位置不同,它出现在地址栏的位置上。Chrome上加星收藏的功能就是非常好的例子: (Chrome 48将Page Action也放到了地址栏右侧工具条上,见《Chrome插件的Page Action的变化》)
另外chrome.pageAction
提供的接口里有show(tabId)
和hide(tabId)
两个方法,也就是说Page Action的按钮可以动态的显示或隐藏。而Browser Action的按钮只能通过enable([tabId])
启用,或利用disable([tabId])
来禁用。所以Chrome开发文档里也给出了如何选用两者的建议:
- Do use page actions for features that make sense for only a few pages.
- Don’t use page actions for features that make sense for most pages. Use browser actions instead.
- Don’t constantly animate your icon. That’s just annoying.
总的来说就是如果按钮需要随特定的页面变化(比如只在部分页面呈现),那么就使用Page Action,每个页面上的按钮会是相对独立,并有针对性的图标或事件;如果在所有页面都需呈现时就选用Browser Action。所以Chrome自带的加星收藏功能虽然在每个页面上有,但是有些页面可能要以中空的星表示未被收藏,有些则需要用高亮的星表示已被收藏,所以将它放在了地址栏的位置作为Page Action。
Options Page
上边两个Action Page都是在特定的位置展示弹出窗口,而Options Page则是纯粹常规的页面,只是这个页面不是来自于网络而是本地扩展程序,所以它的“协议”是chrome-extension
,“域名”是扩展程序的ID。
Options Page其实不是那么必要,之前也说过通过chrome-extension://<extensionID>/<pathToFile>
可以转到扩展程序中的任意文件,所以可以从任意地方开打一个页面,比如直接从上边两种Action的按钮、或者弹出窗口上设置链接。只不过如果在配置文件中设置了”options_page”一项,那么在扩展程序的管理页面中,那个扩展程序就会显示Options链接连到指定的页面上。
申明Options Page的方法如下:
[codesyntax lang=”javascript” lines=”normal”]
"options_page": "options.html"
[/codesyntax]
官方文档说在即将到来的Chrome40里,新版本的Options Page开始要像前面两者的一项使用弹出窗口将页面直接显示在扩展程序的管理页面上。但其实也只是在配置文件里使用了不同的字段而已(可惜当前下载到的发布版本只有38.0.2125.111,所以还没验证过效果):
[codesyntax lang=”javascript” lines=”normal”]
"options_ui": { // Required. "page": "options.html", // Recommended. "chrome_style": true, // Not recommended; only provided for backwards compatibility, // and will be unsupported in a future version of Chrome (TBD). "open_in_tab": true }
[/codesyntax]
后台服务(Background Service)
Chrome扩展程序的交互界面,跟普通页面一样遵循着所见即所得的规矩,当页面关闭或弹出窗口消失,上边的一切都会随着消失和回收,不会留到下一次继续使用。虽然Chrome扩展程序也在交互界面将部分内容写回到本文文件系统,然后下去再读取回来,但这样做肯定没有直接从内存中读写来的快。所以一般的程序在开启后总是会占用一部分内存来保存当前需要使用的数据,而Chrome用来完成这项任务的就是Background Page。
Background Page
Background Page不用与用户进行交互,所以它不需要界面,也因此一般只要定义JavaScript文件即可。当然也可以包含HTML文件,但一般只是为其他部件提供模板,不用来呈现。前面有提到Browser Action和Page Action可以设置监听onclicked
事件的回调方法,这些方法都是在Bakcground Page中定义和设置的。因为其它部件当其关闭后,所有对象都被销毁回收,回调方法也同样不存在了。
以前Background Page的真的是被是一个页面,定义使用的字段是background_page
,其值是扩展程序中的一个页面,在页面内可以引用任意的JavaScript文件。但是现在Background Page更趋向于一个服务(Service),遵从如下定义方式:
[codesyntax lang=”javascript” lines=”normal”]
"background": { "scripts": ["background.js"], "page": "background.html" }
[/codesyntax]
配置background
的值时,scripts
和page
只能选用其中一个,不可同时定义。不过page
的使用应该只是为了对以前的兼容,让原来用HTML文件做Background Page的扩展程序依然可以继续使用。注意到scripts
是一个array,方便开发者配置多个JavaScript文件,程序会按定义顺序逐个加载这些脚本文件。
实际上对于scripts
,Chrome也是自动生成一个HTML文件,然后将定义的每个JavaScript引入到页面中。
当定义了扩展程序的Background Page之后,该扩展程序在Chrome的管理页面就会有指向其定义的Background Page调试窗口(非页面窗口)的链接。如果定义的是page
,链接会以其指定的文件名来显示。通过打开的调试窗口可以方便地调试Background Page上方法,观察变量的信息。
Event Page
不过很多时候Background Page只是用来注册一些监听事件,不需要在运行中保留任何数据内容,所以Chrome又引进了Event Page,它跟Background Page基本完全一样。只是Background Page会随着浏览器一起存在,而Event Page的生命周期是在一定时间没有使用就会被Chrome销毁。但是另一方面,它注册的监听事件的回调函数依然会在事件发生时被正常调用,而且在回调函数被调用时。这也是它被称作Event Page的原因。
一个简单的例子体现Background Page和Event Page在持久性上的区别:假如脚本中有个全局变量来计算Browser Action的Button被点击的次数,然后设置了onClicked
事件的监听方法里自增那个全局变量。点击多次后变量增加到某个值,然后离开Chrome浏览器但不关闭,一段事件之后再回来点击。如果用Background Page,那么变量会继续按照之前留下的值往上增加;若是用Event Page,那变量则会从新从0开始增加。
定义Event Page的方式,就是在Background Page的定义中加一项"persistent": false
。如果把值改成true
,又会变回Background Page。
[codesyntax lang=”javascript” lines=”normal”]
"background": { "scripts": ["eventPage.js"], "persistent": false }
[/codesyntax]
使用Event Page也会在扩展程序的管理页面看到打开调试Event Page窗口的链接,同时有时候还会在链接旁看到Inactive的标识,那就说明这个时候Event Page被Chrome给销毁了。
页面内容(Page Content)
之前介绍的内容,都是讲Chrome扩展程序自身的程序结构,如果仅仅只是这些就没有必要了解怎么开发了,因为任何普通网页程序都可以做。使用Chrome扩展程序的关键就在于可以破坏(不,是“优化”)别人已有的网页。所以必须有个可以控制Chrome浏览器所呈现页面内容的功能,而这部分就是Content Scripts的任务。
Content Scripts
Content Scripts和Background Page一样没有可视的界面,只是Background Page是对应整个Chrome浏览器创建一个对象,Content Scripts则对应每一个页面都分别创建独立的对象。Content Scripts的强大之处就是可以直接访问页面的DOM对象,更可怕的是还可以进行修改:可以添加脚本事件,可以定义样式表来覆盖原有样式,可以使用脚本增删标签元素,可以修改袁术属性等等等。
不过Chrome也对Content Scripts有一些限制,那就是它与页面的脚本,以及其他扩展程序的Content Scripts存在于完全独立的不同环境中,就像是两个平行空间,不能互相访问对方的变量和方法。
虽然都可以同时操作页面的DOM对象,但是页面上创建的对象变量以及引用的库无法在Content Scripts中被调用。比如页面引用了jQuery库,会有jQuery
变量和$
变量可以,但在Content Scripts里这两个变量却是undefined
。对于事件绑定如此,页面上设置了onclick
的回调函数,在Content Scripts端它依然是null
。这样的设计就大大减少了脚本注释的危害,同时也可以防止脚本冲突,比如不同的Scripts可以引入不同版本的jQuery而不会相互影响。(注意的是对于DOM的事件,如果是用户真是交互操作引发的事件,依然会触发在每个脚本里所绑定的回调方法。)
Content Scripts的配置项目比较多,主要的还是js
和css
这两个用来自定义化的内容,还有matches
来只是脚本所需要作用的页面,Chrome不想让脚本在所有的页面都被执行,毕竟很多时候扩展程序不是对所有的页面都有意思。当然也有例外,比如Evernote就希望对所有页面都有截取功能,所以可以用"matches": [ "*://*/*" ]
来匹配所有页面。
[codesyntax lang=”javascript” lines=”normal”]
"content_scripts": [ { "matches": ["http://www.google.com/*"], "css": ["mystyles.css"], "js": ["jquery.js", "myscript.js"] } ]
[/codesyntax]
另外注意到content_scripts
对应的是array,也就是说可以定义多个不同的对象来匹配不同的页面并进行针对性的设置不同的脚本和样式。这样就不许自己在开发是在代码里检测当前页面信息来选择要执行些代码了。跟Background Page一样,js
和css
也是array,所以可以按顺序指定所需的文件。
Content Scripts可以算是属于实际页面的一部分,所以对它的调试在常规的开发工具窗口就能找到。位于Sources标签下的Content Scripts子标签中,就能看到以扩展程序ID的文件夹,里面放置的就是作用在当前页面的脚本和样式。
不过页面的脚本环境和Content Scripts的独立性,造成了一问题就是在Console里直接输入指令它是在页面的脚本环境中。只有当Content Scripts中断点会激活,使脚本运行停在上边时,Console的指令才会运行在扩展程序的的脚本环境中。Content Scripts不像Background Page和Browser Actions那样有一个独立调试窗口(或者说作者暂时还未发现打开方式)。
其他
除了可以控制普通的页面,Chrome也允许扩展插件来重载一些特殊的页面,比如新建页面、收藏夹页面、浏览历史窗口。注意,并不是对这些页面进行功能增加,而是整个页面替换成扩展程序指定的页面。启用方式就是在配置文件里定义chrome_url_overrides
内容:
[codesyntax lang=”javascript” lines=”normal”]
"chrome_url_overrides" : { "bookmarks": "myBookmarks.html" "history": "myHistory.html" "newtab": "myNewtab.html" }
[/codesyntax]
三个页面不需要同时定义,想要自定义某个页面就申明该在配置文件中即可。之后在扩展程序页面的脚本中可以使用系统提供的chrome.bookmarks、chrome.history这些API来实现需要的功能。
想问一下博主,用的是什么wordpress主题,博客很漂亮,很整洁,可否将主题分享一下,将不胜感激,如果博主同意,请发送到我的邮箱