这篇博客以讲解Chrome的 Content Scripts 扩展开发过程为主,并在该过程中穿插与Greasemonkey用户脚本的比较。本人初次尝试Content Scripts类型的扩展,有不足之处,请不吝指出。

前言

现在使用Chrome浏览器的用户越来越多,在写用户脚本的时候有时候必须得同时考虑多个浏览器的兼容情况(当然比起前端开发要简单多了)。我一般仅考虑Firefox和Chrome两个浏览器,原因有以下几个:

  1. 相比之下,两者的用户群体比较大,因为会寻找用户脚本的朋友一般都在用这些浏览器,很少是IE的,至少是同样采用Webkit内核的搜狗、傲游等。
  2. 两者的内核中对JavaScript的实现相对比较遵循W3C规范,因此只要尽量避免使用特定于某个浏览器的功能,一般的情况脚本都可以通用。
  3. 两者的用户脚本格式比较接近,前期是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脚本(或者扩展)以以下的方式存在:
Chrome扩展结构
其中顶部圈住的那串字符串是该扩展的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工具。