这篇博客以讲解Chrome的 Content Scripts 扩展开发过程为主,并在该过程中穿插与Greasemonkey用户脚本的比较。本人初次尝试Content Scripts类型的扩展,有不足之处,请不吝指出。
前言
现在使用Chrome浏览器的用户越来越多,在写用户脚本的时候有时候必须得同时考虑多个浏览器的兼容情况(当然比起前端开发要简单多了)。我一般仅考虑Firefox和Chrome两个浏览器,原因有以下几个:
- 相比之下,两者的用户群体比较大,因为会寻找用户脚本的朋友一般都在用这些浏览器,很少是IE的,至少是同样采用Webkit内核的搜狗、傲游等。
- 两者的内核中对JavaScript的实现相对比较遵循W3C规范,因此只要尽量避免使用特定于某个浏览器的功能,一般的情况脚本都可以通用。
- 两者的用户脚本格式比较接近,前期是Chrome兼容Firefox的Greasemonkey扩展的格式,现在两者估计很多地方都是互相借鉴的。
用户脚本安装
Firefox用户要使用用户脚本(UserScripts),首先需要安装Greasemonkey或者Scriptish扩展,而Chrome在非常早的版本中就已经原生支持类似Greasemonkey扩展的功能,因此可以避免安装额外的扩展。用户只要将后缀为.user.js的用户脚本拖拽到浏览器中就可以安装。
而事实上,Chrome会将用户脚本转换成Content Scripts,并以扩展的形式存在(这个说法未必准确)。当你安装完成一个用户脚本之后,可以到以下目录找到你所安装的脚本:
AppData\Local\Google\Chrome\User Data\Default\Extensions
例如,假设编写一个HelloWorld的用户脚本,将其拖拽安装到Chrome中,示例代码如下:
// ==UserScript== // @id hello_world // @name Hello world // @version 0.1 // @namespace http://example.com // @author helloworld // @description Print hello world // @include * // @run-at document-end // ==/UserScript== alert('hello world');
可见,Chrome中用户脚本的Metadata Block(注释部分)与Firefox下GM或者Scriptish扩展所支持的形式是几乎一致的,这也是为什么Firefox下的用户脚本可以很好的兼容Chrome的一个重要的原因。
Content Scripts扩展开发
安装完成后,脚本出现在扩展程序列表中,在Chrome的扩展目录中,该添加的HelloWorld脚本(或者扩展)以以下的方式存在:
其中顶部圈住的那串字符串是该扩展的id,称为appid。manifest.json是JSON格式的manifest文件,提供扩展相关的描述信息,比如版本、名称等。scripts.js就是Content Script,它的内容和最初安装的用户脚本相同。manifest.json的内容如下所示:
{ "content_scripts": [ { "exclude_globs": [ ], "include_globs": [ "*" ], "js": [ "script.js" ], "matches": [ "http://*/*", "https://*/*" ], "run_at": "document_end" } ], "converted_from_user_script": true, "description": "Print hello world", "key": "Z4J7QteIhzSDb9vypy63EZ85P08k8FIg0jgIDtmV4UI=", "name": "Hello world", "version": "0.1" }
这个文件比较简单,其中converted_from_user_script
意思是指该扩展是由用户脚本转换而来,content_scripts中描述了该扩展中的Content Script相关的信息:
js
指名所包含的JavaScript文件,在这里只有一个文件。matches
,exclude_globs
,include_globs
均是用来指名在哪些网页上该脚本生效的匹配模式。run_at
是指该脚本运行的时机,默认为document_end
关于manifest文件的格式,可以参考官方文档:在这里。其中Content Scripts的概念是比较简单的,前面的文档中描述得也比较详细,其实它就是一段注入到页面文件中的JavaScript代码,可以完成一些简单的任务,比如改变页面背景颜色之类的。对于用户脚本来说,Content Scripts的功能已经足够了,也没必要开发一个真正的扩展用到Chrome的诸多API。
国内比较知名的浏览器公司,例如Sogou和360等均采用了Webkit内核,它们的扩展开发都或多或少借鉴了Chrome的形式(其实几乎一样了),不信你可以比较下这两者与上面Chrome的文档的区别:搜狗的开发文档和360的开发文档。
对比Firefox的用户脚本,其实感觉Chrome的更加灵活。
在Greasemonkey中如果要添加一段比较长的CSS样式,可以选择使用GM_addStyle
(一般我选择自己实现该函数,尽量不用GM的API函数),另外一种方法是将样式文件分离到独立的style.css文件中,然后在Metadata Block中使用以下增加一个资源文件,格式为"@resource 名称 地址(相对与用户脚本的地址或者绝对地址)":
// @resource style.css style.css
并在用户脚本中使用GM_getResourceText('style.css')
来获取该资源的文本内容。
如果需要增加图片类型的资源,使用同样的方法在Metadata Block中添加,但是需要用GM_getResourceURL(resourceName)
来获取该资源的地址,图片返回它的Data URL(base64编码内容)。
如果需要依赖额外的JavaScript库,可以使用@require导入,例如jQuery:
// @require jquery.min.js // 或者 // @require ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js
而在Chrome中并不支持@require和@resource这些标记,但是可以使用Content Scripts的形式包装用户脚本,额外的CSS文件和JS库文件可以非常方便的通过manifest.json整合到扩展里,会在扩展生效的时候注入到页面中,参考CC98 Reply Improved脚本的manifest.json:
{ "name": "CC98 Reply Improved", "version": "0.9.7.1", "description": "改进版的CC98快速回复/引用/编辑功能", "update_url": "https://raw.github.com/dangoakachan/cc98/master/reply_improved/updates.xml", "content_scripts": [ { "matches": [ "http://www.cc98.org/dispbbs.asp*", "http://10.10.98.98/dispbbs.asp*" ], "js": ["jquery.min.js", "lscache.js", "reply_improved.user.js"], "css": ["rim.css"], "run_at": "document_end" } ] }
一切都是非常清晰自然,在"js"和"css"字段中增加所需要的js和css文件即可,其余就同GM脚本一样。
对于图片文件,可以将图片文件放到与扩展相同的目录(与manifest.json文件在同一层)中,然后利用Chrome提供的API访问即可,该API为chrome.extension.getURL
,参数为图片所在的相对路径,例如图片放在images/your.png,则通过以下方法 调用:
chrome.extension.getURL('images/your.png');
结束
当脚本功能完成之后,剩下的就是将其打包成扩展.crx形式,关于打包的方法可以参考官方文档中的打包说明。如果想使用例如Shell脚本来自动化打包的过程,可以通过Chrome本身提供的命令行参数:
# chrome --no-message-box --pack-extension=$CHROME_BUILD_DIR \ --pack-extension-key=$SRC_DIR/reply_improved.pem
其中--no-message-box
是指不显示打包完成的提示框,--pack-extension
指定扩展的源代码目录,--pack-extension-key
指定扩展的pem文件。
这里需要注意的是,后两者的目录必须使用绝对路径(我没在Linux下试过,是在Cygwin中运行的)。当然,因为Google对crx的具体格式规范都有详细的说明,因此网上可以找到非常多的辅助打包工具, Kyle L. Huff在该页面中罗列了其中主要的几个工具,其中也包括他自己用C语言开启的buildcrx工具。
Thanks,刚为chrome自带功能的写了个简单的脚本。