基于 Mozilla 的扩展开发

基于 Mozilla 的扩展开发

目录

前言

我在今年才刚开始接触 Firefox 和“扩展(Extension)”这些概念,许多的内容也是在学习中探索。我感觉到,由于这些技术在国内很少有系统的介绍,引入的书籍性翻译资料几乎为零,只有在国内几个知名程序员的 Blog 上有些介绍,其余寥寥无几。苦于没有现成的中文资料可供参考,我不得不在英文很差的情况下,翻看了部分国外的资料,并且通过读源代码的方式来增加自己对扩展开发的理解。

出于开源开发的思想,我想把自己已有的开发经验做些总结,并在未来加入自己新的理解,维护好这篇文档。希望这篇文档能对你 Mozilla 下的开发有所帮助,我将尽我之力将复杂的技术阐述清楚。

在此,我要特别感谢我所在的亿邮公司和领导,没有他们的支持,我不可能利用工作时间对已知的技术做如此系统的整理;我还要感谢我的同事,没有他们的“锱珠必较”,我也不可能将那些复杂的技术描述得比较通俗易懂。同时,我还也要感谢你的阅读,如果你能对此文档的不足之处有所补充,或对错误提出指正,此文档将会被更好的维护,谢谢!

第一章 技术介绍

扩展开发(Extension development)是由于 Mozilla 技术的实现而被引入的。那段浏览器大战的是是非非,我不想做过多的介绍。Netscape 浏览器源代码在被送到开源组织之后,非盈利性的“Mozilla基金会(Mozilla Foundation)”成立了。也正是在重写了 Netscape 的源代码之后,才有的 Mozilla 浏览器,而所有的这些技术也正是基于这个“脱胎换骨”的 Mozilla Suit 而来的。直到近几年,Mozilla 基金会为了使 Mozilla 浏览器更加精简高效,它的开发者们决定将浏览器和邮件客户端程序独立出来,Mozilla Firefox 和 Mozilla Thunderbird 等就此产生。

(注:以下除非特殊声明,对基于 Mozilla 内核的应用程序一律用 Mozilla 来称呼;对 Firefox 或 Thunderbird 等程序只做特殊称呼,又由于它们也是基于 Mozilla 内核的应用程序,所以它们也可用 Mozilla 来称呼。)

扩展的开发也多是基于以上提到的几个应用程序的,扩展程序的目的是为了增强浏览器本身的功能。因为 Mozilla 本身只支持一些浏览器所具有的普遍的功能,显然不能满足一些用户的特殊需求。但由于 Mozilla 本身的框架非常好,它所提供的资源已经远远超出了一个浏览器的范畴,更多的情况下倒像是一个“平台”。因为它提供了大量的开发接口,高度的模块化和可扩展性。所以,这就为我们在其下进行各种开发提供了很强的基础。下面对 Mozilla 所支持的各种技术做一些简单的介绍。

1.1 扩展开发所涉的技术

XUL:它是“XML 化的用户界面语言(XML User Interface Language)”的缩写,这是一种以平台无关性为目标,用来描述用户界面的语言,现在被广泛地应用于 Mozilla 平台。再有,Mozilla 本身的界面就是用 XUL 进行描述的。

CSS:它是“层叠样式表(Cascading Style Sheets)”的缩写,这是一种可以通过规则来控制 HTML/XUL/XML 等显示外观的语言。

DOM:它是“文档对象模型(Document Object Model)”的缩写,这是一个允许通过脚本来动态访问和更新 HTML/XML 文档的内容,结构和样式的接口。

XPCOM:它是“跨平台组件对象模型(Cross -platform Component Object Model)”的缩写,它很像微软的提出的组件模型技术,但它是跨平台的,即其运行环境可以不依赖于某种特定的操作系统平台。

XPConnect:一种将 XPCOM 与 JavaScript 连接起来的技术。该技术允许组件被脚本化,而且能够用 JavaScript 来进行组件的开发。

XBL:它是“可扩展绑定语言 (Extensible Binding Language)”的缩写。

RDF:它是“资源定义框架(Resource Definition Framework)”的缩写。Mozilla 使用这种文件格式来保存扩展的注册信息和描述信息等。

JavaScript:由 Netsacpe 公司的 Brendan Eich 创造的一种解释型语言,它主要用来进行基于浏览器下的脚本应用开发。因为 Mozilla 内置了 JavaScript 解释器,所以使用 JavaScript 开发扩展,就成为编程语言的首选。虽然,你也可以使用 C++,Perl,Python 等进行扩展的开发,但这些语言的先天优势都明显不足。

对于扩展开发来说,必须掌握的技术有 XUL,CSS,DOM,XPCOM,JavaScipt,而对于 XBL,XPConnet,RDF 做简单了解即可。我们可以看到,做扩展开发涉及到了这么多的技术,那它们都分别负责什么功能呢?

1.2 各种技术所负责的功能

当我们在自己的机器上安装了某个扩展之后,我们可以看到,那些扩展工作起来就像是浏览器的一部分,与浏览器一起工作得很协调。虽然以上的许多技术都是由 W3C 这样的互联网组织提出的,但 Mozilla 却用它们来做桌面开发。因为,这些扩展确切来说全都是程序,是一种面向桌面应用的程序,而不是网页一类的东西。

有了以上的这个观点,就不难解释它们所负责的功能了,下面分别给予解释。

XUL,CSS 都是用来负责控制程序的界面,一个是用来描述界面,一个用来在被描述的界面上加入一些界面效果(如:字体颜色,是否透明,边框大小等)。可以看出,它们更多的功能是负责与用户打交道,所以用户在评价某个扩展程序的好坏时,多半会提及它的界面设计是否合理。如果想要使我们的扩展与用户交互得更合理,还需要你对应用于桌面开发的人机交互有所了解;

DOM 主要用来为 JavaScritp 提供一个 HTML/XML 的文档操作接口,并且,它也可以用来操作 CSS。由于扩展的界面是由 XUL 定义的,而 XUL 只是 XML 的一个特殊应用,所以我们也可以通过 DOM 来对扩展界面进行“动态”操作(如:按钮的禁止与否,动态装载数据等)。同时,又有许多的文件和数据会采用 XML 进行存储和传输,所以创建和分析 XML 文档又显得尤为重要。可以看出,通过 DOM 接口,我们可以将程序的逻辑处理部分与界面表现部分有机的结合起来。

JavaScipt 是扩展开发的核心要求,它主要用来实现程序的业务逻辑描述。在很长一段时间里,大家都对 JavaScript 有着不正确的认识。直到最近一段时间,才有所改观。确切的说,JavaScipt 在扩展开发中起着粘合剂一样的功能。利用 JavaScript 开发扩展有着先天的优势,因为 Mozilla 本身除了底层部分是用 C++ 编写以外,大量的代码全部采用 JavaScript 编写。再有,由于 JavaScript 是非编译性的,所以即使你下载的是安装包程序,你仍然可以查看并修改其中的 JavaScript 源代码,这一点符合 Mozilla 的开源特性。

虽然 JavaScript 看上去很简单,但用 JavaScript 开发大型程序,却对开发人员本身提出了很高的要求。因为 JavaScript 是一种比较灵活的语言,就当前版本的 JavaScript 来说,其本身并没有提供很好的进行大型开发的结构,所以就要求开发人员必须具备很强的能力,对那些复杂的结构进行模块化的处理。如果仅仅使用面向过程进行开发,就会在开发时“捉襟见肘”,这就要求开发人员必须采用面向对象的方式进行开发。可以说,从网页下的 JavaScript 开发到扩展下的 JavaScript 开发,是一种由轻量级到重量级的转变。

由于 JavaScript 语言只内置了几个与本地访问无关的对象,而对于桌面开发来说,显然不能满足要求。因为桌面开发需要访问大量的本地和网络资源,包括文件,剪贴板,Socket,浏览器本身等各种资源。而 XPCOM 为面向桌面的开发提供了这种可能,并且它使开发出的扩展程序可以跨平台的运行,而不用依赖于某个特殊的操作系统。只有使用 XPCOM,我们的扩展才可以做出实用的功能,没有 XPCOM,本地与远程的资源整合可以说是不可能。虽然扩展开发是用 JavaScript 来做的,但每个封装的对象或函数可能都要调用 XPCOM 对象来完成特定的功能。可以这么说,离开 XPCOM,我们寸步难行。

要想对以上的技术依次做很深入的解释简直是太难了,因为每种技术本身和它的应用都足以写上厚厚的一大本书。我现在还不打算做这么系统的工程,我打算从一个示例入手,将这个看上去很复杂的问题说得简单点儿。

第二章 开发平台的准备

“工欲君欲善其事,必先利其器”。Firefox 就是我们的开发环境,因为所做的开发都是基于 Firefox 的。那么首先,我们要配置好所在的开发环境。合理而有效地配置好整体的开发环境和指定的项目环境,可以令你的开发快速而高效。本章节将讲解如何配置整体的开发环境,后面的章节还将对指定项目的环境配置给予讲解。

2.1 安装开发工具

首先,我们需要下载一个开发平台。虽然 Mozilla Suit 在很长一段时间内还将被使用,但它将不再是扩展开发的重点对象,因此作者希望你的开发环境是 Firefox。那么首先,你需要在自己的机器上下载并安装 Firefox 浏览器。对于开发人员来说,我们有必要在第一时间了解到 Firefox 的更新状态,下面的链接地址可以让你快速地定位新版本的下载位置。

http://ftp.mozilla.org/pub/mozilla.org/firefox/releases/

由于作者在写这篇文档时,Firefox 正存在着高版本和低版本的区别,这是由于它分别采用了主分支和子分支的内核造成的。在这种情况下,你可能需要下载两个不同版本的 Firefox 进行开发。但是,如果你对扩展的开发还不熟悉的话,作者不建议你这么做,我希望你下载一个公认的比较稳定的版本,并且下文提到的那些扩展在上面工作得很好。

当你下载完成并进行安装时,你需要选择“自定义(Custom)”安装模式。因为在这种模式下,我们可以选择并安装“开发工具(Developer Tools)”,这一点对于开发人员尤其重要。接下来,我们要下载并安装辅助开发的“工具性扩展”。下面,先对这些工具性扩展进行简单的介绍。

JavaScript Debugger:又被称为 Venkman,它是 Mozilla 平台下的脚本调试工具。Firefox 下的网页及扩展开发,都要求使用 JavaScrit 的调试工具。其实不仅仅是开发扩展,对于步入某一未知开发领域的开发人员,掌握并使用语言调试工具都可以起到快速入门,有效开发的功效,这一点是无庸置疑的。

Console Filter:JavaScript 控制台(JavaScript Console)的信息辅助过滤工具。由于我们在开发过程中,难免会出现这样或那样的编写错误,而这些信息都被写入到 JavaScript 控制台中。通过这个工具,我们可以在 JavaScript 控制台中对那些错误信息进行有效的过滤和定位,这样就可以加快解决错误的速度。

Component Viewer:XPCOM 组件查看器。通过此工具,你可以了解到 Mozilla 系统下已经安装的所有 XPCOM 组件信息,适合高级别的开发。

Extension Developer:辅助开发扩展的扩展,用来帮助你建立扩展开发项目。它提供了一套工具用来满足 Mozilla 的扩展开发要求,虽然实际上它并不是很好使。在此,作者只建议你使用其中的部分功能,如:“Reload all Chrome”。因为此工具的“Extension Builder”功能还有一些问题,并且不利于你了解扩展的注册机制。

以上提到的这些扩展,你可以在下面的两上网站下载和获取到帮助信息:

安装完成之后,你需要熟悉一下这些扩展的使用方法。这些扩展中,最难掌握的可能就是 JavaScript Debugger 了。你可能需要很长的时间来摸索它的使用技巧,并且随着你对它的熟悉,你会发现不太好使的 Venkman,功能其实是很强大的。而 Console Filter 工具则被集成到 JavaScript 控制台里,并且它还提供了一些简单的过滤命令,你可以在它的发布页面里看到详细的使用方法。对于 Extension Developer 工具,作者经常使用它的“Reload all Chrome”功能。此功能可以让 Firefox 重新装载所有的功能代码,以此使 Firefox 重置为启动状态,这包括所有的扩展代码和组件代码;这样我们就不必在修改代码错误之后,重新启动 Firefox 了,因为只有在重新启动时,那些被修改过的代码才会生效。

2.2 创建 Profile 及配置环境变量

Firefox 是一个允许创建多个 Profile 的浏览器,不同的 Profile 被分别进行管理。其实,不同的 Profile 被对应到不同的配置目录,由配置目录的子目录和文件来维护这个 Profile 的设置。对于不同操作系统和 Mozilla 平台来说,这个目录是有所区别下的,下面列出 Firefox 的 Profile 目录在不同操作系统下的位置,其它类型的 Mozilla 平台,类推即可。

操作系统Proile 对应的目录
Windows 9x/Me C:\Windows\Application Data\Mozilla\Firefox\Profiles\<Profile name>\
Windows 9x/Me, alternate C:\Windows\Profiles\<Windows login/user name>\Application Data\Mozilla\Firefox\Profiles\<Profile name>\
Windows NT 4.x C:\Winnt\Profiles\<Windows login/user name>\Application Data\Mozilla\Firefox\Profiles\<Profile name>\
Windows 2000/XP C:\Documents and Settings\<Windows login/user name>\Application Data\Mozilla\Firefox\Profiles\<Profile name>\ 或
%APPDATA%\Mozilla\Firefox\Profiles\<Profile name>\
Unix ~/.mozilla/firefox/<Profile name>/
Mac OS X ~/Library/Mozilla/Firefox/Profiles/<Profile name>/ 或
~/Library/Application Support/Firefox/Profiles/<Profile name>/

(注:文档的其它部分将使用 %profile% 来代表以上的 Profile 目录,使用 %app% 来代表 Mozilla 程序的安装目录。)

由于在开发扩展时,我们希望一个 Profile 用来做开发,一个 Profile 用来测试扩展的发布版本。所以,我们要配置不同的 Profile 来满足这种要求。

一般把默认的 Profile 做为开发的 Profile,所以,我们不用做什么特殊的设置。你可以把上面提到的那些扩展,全部安装到此 Profile 下。之后,关于开发的所有内容都将对此展开。

那么,我们还要创建一个新的 Profile 来完成扩展的安装,或者说是发布测试工作。通过下面的两个步骤,你可以实现这种需求。

首先,我们需要在命令行方式下,定位到 Firefox 的安装目录,通过运行

firefox -P

我们可以启动 Firefox 的 Profile 管理器窗口,利用此窗口,你可以建立一个负责测试安装的 Profile。当然,如果你还有别的要求,你也可以建立更多的 Profile。

然后,假设你通过上面的步骤建立了一个名为“test”的 Profile,如果你想启用这个 Profile,你需要通过

set MOZ_NO_REMOTE=1
firefox -P test

这两行命令来启用这个非默认启动状态的 Profile。很显然,这两条命令可以写成一个 Shell 脚本或做成一个批处理文件。那么,在你下次再启用这个 Profile 的时候就会轻松许多。

对于负责开发的 Profile,我们还要更改其中的某些设置项,以使其更利于扩展的开发。下面列出了几个需要更改的设置项,并且给出了解释。

  • javascript.options.showInConsole = true :将 chrome 类型的错误提示信息显示在 JavaScript 控制台中,这样可以方便扩展的调试;
  • nglayout.debug.disable_xul_cache = true :令 chrome 下的 XUL 修改不会被 cache,以便不重新启动 Mozilla 也可以查看到页面布局的更改;
  • browser.dom.window.dump.enabled = true :将 window.dump() 方法打开,这样 dump 信息就被打印在标准控制台上。此时,你还需要通过“-console”参数来启动 Firefox。只有这样,所有的 dump 信息才会被输出到标准控制台上;
  • javascript.options.strict = true :允许严格的 Javascript 警告出现在 JavaScript 控制台中。对于你自己的扩展,这样的设置可能会显示出更多的错误提示信息。

通过在地址栏中键入 about:config,你可以打开 Mozilla 内置的配置界面,这样你就可以更改那些设置项的值。在更改完这些设置项之后,我们会在以后的开发中更加得心应手。

第三章 扩展的结构及 Chrome 注册

上一章讲解了如何准备开发平台,但仅有开发平台还是不够的。本章就扩展程序的结构和 Chrome 注册机制,予以比较详细的讲解,只有当你对这部分的内容清楚以后,才能正确的对扩展项目进行配置。如果你对这方面已经非常清楚了,你可以跳过下面的内容。

3.1 扩展程序的结构

在 Mozilla 下安装的扩展程序是一种以 XPI 做为其扩展名的文件,实际上它只是一个 ZIP 格式的文件,扩展名不同而矣。所有的扩展程序,包括上一节提到的那些扩展都具有相似的内部结构。你可下载一个现成的扩展(比如:Venkman),将其扩展名改名成 ZIP,并对其解压缩,你就会看以其内部的组织结构。下面对其内部组织结构进行解释说明。

3.1.1 扩展的顶级结构

一个标准的扩展程序,解压缩后会生成以下几个目录:

  • chrome:Mozilla 规定扩展必须具备的目录。其下有一个 JAR 文件,此文件中保存着完成扩展主要功能的文件,后面将做更进一步的说明;
  • components:约定俗成的可选目录,用于存放自定义的 XPCOM 组件文件。由于大多数的扩展根本没必要自己定义 XPCOM 组件,因此,在没有自定义 XPCOM 组件的情况下,此目录是不用存在的;
  • defaults:负责存放一些默认的设置数据,其下还会包含子目录,以分别对默认数据进行存储;

另外,其下一般还会具备 3 个特殊的文件:

  • install.rdf:它是一个 RDF/XML 格式的文件,用于描述当前扩展的注册信息和附加信息等。扩展在安装时,负责安装扩展的程序会自动分析此文件的信息,然后将这些信息注册到 Mozilla 系统下。此文件必须被命名为 install.rdf,并置于扩展压缩包的顶级目录下;
  • install.js:负责安装扩展的脚本,此文件可选。一般情况下,install.rdf 完全可以胜任扩展的安装注册工作。但是,如果有些扩展要在安装时做一些额外的准备工作,则要通过一个称为 XPInstall 的机制来完成,那些负责额外工作的代码则要被固定地写到此文件中;
  • chrome.manifest:负责将扩展的各种包注册到 Mozilla 的 chrome 系统中。Gecko 1.8 内核新引入的机制,用来代替原有的 contents.rdf 文件;

如果你在别人编写的扩展中看到了除此之外的其它目录和文件,这应该是扩展开发者的一种个人行为,而不是必须的。

3.1.2 扩展的二级结构

在上提到的 chrome 目录下,会一个与当前扩展名称相近或相同的,扩展名为 JAR 的文件,此文件用来组织扩展的核心功能。第一章已经说过,编写一个扩展就像编写一个桌面程序一样,你需要构造扩展的外观并且编写完成逻辑功能的代码。那么,这些外观和代码就都被存储在此文件中。解压缩此 JAR 文件之后,一般会生成以下 3 个目录。

  • content:用于存储负责描述扩展界面的 XUL 文件和完成实际逻辑功能的 JS 文件;
  • locale:用于存储负责本地化处理的字符串数据文件,这些文件中的本地化字符串内容会被 content 目录中的文件所引用。如果某个扩展没有对本地化进行处理,那么它是可以省略的;
  • skin:用于存储负责美化界面外观的样式表文件和图片文件,这些文件中的样式和图片会被 content 目录中的文件所引用。如果扩展没有使用单独的样式表文件和图片,那么它也是可以被省略的;

其实,那些目录下的文件不一定是直接存储在它下面的,甚至它还有可能被存储在一个不相干的目录下。这是因为,Mozilla 下的扩展开发有一些固定的和约定俗成的东西。对于那些非固定的规定,你可以不遵守。就比如说,有的扩展可能将以上负责不同功能的文件混合放在 content 目录下。但是,作者不建议你这么做,这种方式只会给扩展的维护带来困难。

下面我们再来看一下这些目录下的所存储的内容:

  • content:目录下可能还会包含一个与扩展名称相同或相近的子目录,用这个子目录来存储以上提到的界面和代码文件。对于 locale 和 skin 目录,你可能也会看到再包含一个与扩展同名的子目录的规则;
  • locale:目录下还会有针对不同语言的子目录,这些子目录会被起成如“en-US”,“zh-CN”这种用来区分“语言-国家/地区”的名称。通过这种国际上标准的语言区分方式,Mozilla 会根据其自身的语言,选择一个最合适的语言目录让 content 中的文件进行引用。这样做的结果就是,同一个扩展,在编写了不同的语言包之后,它会根据 Mozilla 的语言来进行自适应。其实,这是 Mozilla 的功劳,我们仅仅是提供了不同的语言包文件,选择并适应的工作是由 Mozilla 来完成的;
  • skin:目录下还会有针对不同的 Mozilla 主题命名的目录,如“classic”,“modern”等。不过,一般情况下,我们只创建针对 classic 的“皮肤”。皮肤的适应方式与语言一样的,Mozilla 会根据当前的主题样式来选择一种最适合它的皮肤。

上面提到的这些目录让 Mozilla 的扩展组织结构显示得十分“冗余而复杂”。其实,这种看上去效率不高的组织方式却十分利于扩展程序的维护和降低耦合度。再有,在每一个直接存储文件的子目录下,你会见到一个固定的名为 contents.rdf 的文件,它是一个特殊的文件,格式同样是 RDF/XML 的,扩展的注册和工作都要靠此文件来完成。

3.2 contents.rdf 文件

我们先抛开那些形式上的目录结构,了解一下最重要的东西。content,locale,skin 这三个目录都被称为package),那么什么是包呢?在 Mozilla 下,package)就是一组文件集合,它的内容和功能就像上面对 content,locale,skin 等目录描述的那样。包可以被注册到 Mozilla 系统下,并且一旦被注册,它其中的文件就可以通过一种被称为 chrome 的地址协议进行访问。包可以包含任意类型的文件,这些文件可以被分别放置于不同的子目录下。包的表现形式既可以是目录也可以是 JAR 文件,但常以 JAR 做为表现形式,同时 contents.rdf 文件是必须的。那么,contents.rdf 的确切功能又是什么呢?

contents.rdf 文件就是用来分别描述这些包的,它描述了每种包的结构和负责完成的功能。确切的说,它其中的信息是为包的注册服务的。扩展在安装时,负责安装扩展的程序会分析它的内容,并将包注册到 Mozilla 系统中。只有在包被注册到 Mozilla 系统之后,它才可以进行正常的工作,才可以被通过 chrome 地址协议进行访问。本章的后面将对 chrome 地址协议和扩展的安装原理做更一步的解释,现在只说明 contents.rdf 文件的结构。

再有,由于基于 Gecko 1.8 内核的 Mozilla 程序采用一种新的方式来进行包的注册,所以 contents.rdf 其实已经被废弃掉了。新方式通过一个位于扩展顶级目录的 chrome.manifest 文件来完成同样的功能,它是一个格式十分简单的纯文本文件。但为了保证扩展能够兼容 Gecko 1.8 以前的版本,我们还要在扩展中使用 contents.rdf 文件格式,直到它真正的被废弃掉。

我们已经了解到,contents.rdf 的文件格式是 RDF/XML 格式的,它是一种通过 XML 描述的 RDF 格式。RDFResource Description Framework,译为“资源描述框架”)主要用来描述资源之间的关系,并且可以用许多格式来表示,但常用 XML 格式进行表示。Mozilla 也只是应用了这样一种格式来描述它的包资源。前面提到的 3 个包属于 3 种不同类型的资源,所以在进行描述时也会有所区别。并且这些包的描述格式是相对固定的,你完全可以通过修改下面的模板文件来生成你的 contents.rdf 文件。

3.2.1 适用于 content 包的 contents.rdf 文件

示例格式如下:

<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:chrome="http://www.mozilla.org/rdf/chrome#">

  <!-- list all the packages being supplied by this directory -->
  <RDF:Seq about="urn:mozilla:package:root">
    <RDF:li resource="urn:mozilla:package:sampleext"/>
  </RDF:Seq>

  <!-- package information -->
  <RDF:Description RDF:about="urn:mozilla:package:sampleext" chrome:name="sampleext">
  <!--  additionally for Mozilla Suite and old Firefox/Thunderbird versions include:
        chrome:extension="true"
        chrome:displayName="Sample Extension"
        chrome:author="Your Name Here"
        chrome:authorURL="http://sampleextension.mozdev.org/"
        chrome:description="A sample extension with advanced features"
        chrome:settingsURL="chrome://sampleext/content/settings.xul" -->
  </RDF:Description>

  <!-- overlay information -->
  <RDF:Seq about="urn:mozilla:overlays">
    <RDF:li resource="chrome://browser/content/browser.xul"/>
  </RDF:Seq>

  <RDF:Seq about="chrome://browser/content/browser.xul">
    <RDF:li>chrome://sampleext/content/overlay.xul</RDF:li>
  </RDF:Seq>
</RDF:RDF>

下面对以上的一些格式做解释说明,先看一下它的文件头部。

<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:chrome="http://www.mozilla.org/rdf/chrome#">

上面这一段格式相对固定,它主要是用来引入 RDF 和 chrome 命名空间。接下来

<!-- list all the packages being supplied by this directory -->
<RDF:Seq about="urn:mozilla:package:root">
  <RDF:li resource="urn:mozilla:package:sampleext"/>
</RDF:Seq>

这里的 packagesampleext 都需要注意。首先,package 用来说明它描述的是一个 content 类型的包。对于 locale 和 skin 类型的包,应把它替换成 localeskin,虽然这看上去与包目录的命名有些不一致。sampleext 是用来唯一标识扩展的名称,Mozilla 系统靠它来识别是哪个扩展的注册信息。后面还有好几处出现了同样的内容,你都需要注意。对于 locale 和 skin 则不能写能 locale:sampleext 或 skin:sampleext 这种“类推”出来的格式,它们的格式后面做说明。

文件的中间部分

<RDF:Description RDF:about="urn:mozilla:package:sampleext" chrome:name="sampleext">
<!--  additionally for Mozilla Suite and old Firefox/Thunderbird versions include:
      chrome:extension="true"
      chrome:displayName="Sample Extension"
      chrome:author="Your Name Here"
      chrome:authorURL="http://sampleextension.mozdev.org/"
      chrome:description="A sample extension with advanced features"
      chrome:settingsURL="chrome://sampleext/content/settings.xul" -->
</RDF:Description>

<!-- --> 标识中的内容已经说明,它是适用于 Mozilla Suite 和老版本 Firefox/Thunderbird 的附加信息,所以对于内核较老的 Mozilla 必须被写成:

<RDF:Description RDF:about="urn:mozilla:package:sampleext" chrome:name="sampleext">
      chrome:extension="true"
      chrome:displayName="Sample Extension"
      chrome:author="Your Name Here"
      chrome:authorURL="http://sampleextension.mozdev.org/"
      chrome:description="A sample extension with advanced features"
      chrome:settingsURL="chrome://sampleext/content/settings.xul">
</RDF:Description>

对于新版本的 Mozilla,<!-- --> 中的内容可以忽略。这段内容主要用来描述扩展的一些附加信息,如作者和扩展的显示名称等。其实在 install.rdf 文件中,存在同样的一段描述信息。因而,这里再做描述显得有些罗嗦,作者也不建议你在 contents.rdf 中加入那些附加的信息。

文件的结尾部分

<RDF:Seq about="urn:mozilla:overlays">
  <RDF:li resource="chrome://browser/content/browser.xul"/>
</RDF:Seq>

<RDF:Seq about="chrome://browser/content/browser.xul">
  <RDF:li>chrome://sampleext/content/overlay.xul</RDF:li>
</RDF:Seq>

这是 content 类型的 contents.rdf 文件所描述的关键部分。Mozilla 下的扩展之所以能够和浏览器整合在一起,像一个程序一样工作,是因为它采用了一种被称为“覆盖Overlays)”的技术。你的扩展可以在 Mozilla 的某个已有界面上,再组合上另外的 XUL 界面元素,而不会与之产生任何的冲突和不协调。这种技术与其说是“覆盖”,不如说是“合并”更合适。因为,原有的界面元素会根据加上去的界面元素自动调整自己的位置,以适应变化。同时,“覆盖”技术还为扩展程序的运行提供了“入口点”,这也正是我们编写的扩展能够运行的原因。

在上面的内容中,第一个 RDF:Seq 标记指明要对 Mozilla 的界面进行覆盖;而要对哪些界面进行覆盖,则通过它的子标记 RDF:li 标记进行描述,如:

<RDF:li resource="chrome://browser/content/browser.xul"/>

它意思是要覆盖负责描述浏览器界面的 browser.xul 文件,这个文件就是通过 chrome 地址协议进行指定的,后面你还将看到在许多情况下,我们都需要通过 chrome 地址协议来访问注册到 Mozilla 系统下的包资源。如果你还想覆盖其它的界面文件,你只需像上面一样指定多个 RDF:li 标识。

第二个 RDF:Seq 对那些被覆盖的文件做更进一步的描述,它描述了指定的目标文件会被当前包下的哪几个文件所覆盖。如:

<RDF:Seq about="chrome://browser/content/browser.xul">
  <RDF:li>chrome://sampleext/content/overlay.xul</RDF:li>
</RDF:Seq>

意思是用 overlay.xul 覆盖 browser.xul 文件,并且 overlay.xul 也是以 chrome 方式进行定位的,或者说是以已经注册后的地址进行定位的。如果还有更多的界面文件要覆盖到 browser.xul 上,照上面格式书写即可。

3.2.2 适用于 locale 包的 contents.rdf 文件

示例格式如下:

<?xml version="1.0"?>
<RDF:RDF xmlns:chrome="http://www.mozilla.org/rdf/chrome#"
         xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">

  <RDF:Seq about="urn:mozilla:locale:root">
     <RDF:li resource="urn:mozilla:locale:en-US"/>
  </RDF:Seq>

  <RDF:Description about="urn:mozilla:locale:en-US"
          chrome:author="Me"
          chrome:displayName="English(US)"
          chrome:name="en-US">
    <chrome:packages>
      <RDF:Seq about="urn:mozilla:locale:en-US:packages">
        <RDF:li resource="urn:mozilla:locale:en-US:sampleext"/>
      </RDF:Seq>
    </chrome:packages>
  </RDF:Description>
</RDF:RDF>

我们可以看到,locale 包的 contents.rdf 文件的与 content 包的 contents.rdf 文件没有什么太大的区别,只有文件前面的 RDF:li,其 resource 中写的是 locale:en-US,注意区别前面 content 中的 package:sampleext。后面的 RDF:Description 格式相对固定,只是你要注意将 RDF:li 中的 sampleext 替换成与上面一致的扩展名称。另外,由于这一类型的 contents.rdf 是用来描述语言包的,所以,你必须要处理好相应的语言描述信息。

3.2.3 适用于 skin 包的 contents.rdf 文件

示例格式如下:

<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:chrome="http://www.mozilla.org/rdf/chrome#">

  <RDF:Seq about="urn:mozilla:skin:root">
    <RDF:li resource="urn:mozilla:skin:classic/1.0" />
  </RDF:Seq>

  <RDF:Description about="urn:mozilla:skin:classic/1.0">
    <chrome:packages>
      <RDF:Seq about="urn:mozilla:skin:classic/1.0:packages">
        <RDF:li resource="urn:mozilla:skin:classic/1.0:sampleext" />
      </RDF:Seq>
    </chrome:packages>
  </RDF:Description>
</RDF:RDF>

我们可以看到,它与 locale 包的 contents.rdf 文件差不多,格式也很相近,只是 RDF:Description 标记中的属性少了一些。因为,这两种资源的描述几乎是采用一致的格式进行注册的,你只要注意 skin:classic/1.0 即可。

以上只是给出一些标准的格式,随着你对扩展开发的深入,你会发现一些特殊的格式及应用。另外,由于这个文件是 XML 的,所以作者提醒你保存成不带文件头标记(BOM)的 UTF-8 格式,并且建议你在创建完此文件之后,用浏览器再验证一下文件的格式和编码问题。

3.3 install.rdf 文件

install.rdf 文件位于扩展的顶级目录下,用于描述当前扩展的作者信息,升级地址,设置入口,版本兼容信息等。在安装扩展时,Mozilla 会分析其中的信息。它的标准格式如下:

<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:em="http://www.mozilla.org/2004/em-rdf#">

    <Description about="urn:mozilla:install-manifest">
        <em:id>{XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}</em:id>
        <em:name>Sample Extension</em:name>
        <em:version>1.0</em:version>
        <em:type>2</em:type>
        <!-- optional items -->
        <em:creator>Your Name Here</em:creator>
        <em:description>A sample extension with advanced features</em:description>
        <em:contributor>A person who helped you</em:contributor>
        <em:contributor>Another one</em:contributor>
        <em:homepageURL>http://sampleextension.mozdev.org/</em:homepageURL>
        <em:updateURL>http://sampleextension.mozdev.org/update.rdf</em:updateURL>
        <em:optionsURL>chrome://sampleext/content/settings.xul</em:optionsURL>
        <em:aboutURL>chrome://sampleext/content/about.xul</em:aboutURL>
        <em:iconURL>chrome://sampleext/skin/mainicon.png</em:iconURL>

        <!-- Firefox -->
        <em:targetApplication>
            <Description>
                <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
                <em:minVersion>0.9</em:minVersion>
                <em:maxVersion>1.5.0.*</em:maxVersion>
            </Description>
        </em:targetApplication>

         <!-- This is not needed for Firefox 1.1 and later. Only include this 
              if you want to make your extension compatible with older versions -->
        <em:file>
            <Description about="urn:mozilla:extension:file:sampleext.jar">
                <em:package>content/</em:package>
                <!-- optional items -->
                <em:skin>skin/classic/</em:skin>
                <em:locale>locale/en-US/</em:locale>
                <em:locale>locale/zh-CN/</em:locale>
            </Description>
        </em:file>
    </Description>
</RDF>

我们先看一下此文件的头部分:

<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:em="http://www.mozilla.org/2004/em-rdf#">

同 contents.rdf 文件的头部分一样,它也是负责引入命名空间。而位于头部分下面的 Description 标记,则是此文件的实质性描述。下面对 Description 下的子标功能给予说明。

3.3.1 必需的标记

3.3.1.1 em:id

它用来唯一的标识某个扩展,此标记内容的要求在 Firefox 1.5 版本的前后还发生了变化。在 1.5 以前的版本中,em:id 标记的内容被要求必须以 GUID 格式进行表示;而在 1.5 及后续版本中,你还可以使用一种格式为 extension@domain 的 id。如下示例了两种不同的格式:

<em:id>myextension@mysite.com</em:id>
<em:id>{daf44bf7-a45e-4450-979c-91cf07434c3d}</em:id>

对于 GUID 格式的 id,我们可以通过一些工具来生成。在 Windows 下,我们可以用微软的 guidgen.exe 工具来生成。如果你在类 Unix 的系统下做开发,你可以用系统自带的 uuidgen 工具来生成。

3.3.1.2 em:name

此标记的内容会被显示在扩展管理器中。它被允许由多个单词组成,且中间可以包含空格。

3.3.1.3 em:version

此标记的内容是一个版本字符串,用来表示扩展的当前版本,它会被显示在扩展管理器中。示例如下:

<em:version>2.0</em:version>
<em:version>1.0.2</em:version>
<em:version>0.4.1.2005090112</em:version>

同时,Mozilla 还对版本字符串的格式做出了规定。如果这对这方面感兴趣,请你查阅 Mozilla 文档。

3.3.1.4 em:type

从 Firefox 1.5 版本以后,此标记被引入,它主要用来表示当前安装包的安装类型 (注意不要和包 [package] 弄混)。其实,在 Firefox 的 Theme 包结构里,同样要用 install.rdf 文件来描述其安装信息。而安装程序在处理安装时,要根据安装包的类型做不同的处理,此标记正是为区别安装包的类型设计的。

它的内容是一个整型值,并且只允许以下几个值出现:

2	Extensions
4	Themes
8	Locale
16	Plugin

对于扩展类型的安装包,type 标记的内容显然要被指定为 2。

3.3.1.5 em:targetApplication

此标记及其子标记用来指明所适用的 Mozilla 平台。由于你开发的扩展可能适用多个 Mozilla 平台,所以你需要确切地指定它所适用的平台。通过它的 em:id 子标记,可以指明所适用的平台;通过 em:minVersionem:maxVersion 子标记可以进一步所适用平台的版本范围。需要注意的是,如果你编写的扩展在安装时弹出了禁止安装的窗口,多半是由于 em:targetApplication 书写有问题造成的,特别是 em:maxVersion 设置过低的问题。

如果你编写的扩展适用于多个平台,你可以通过书写多个 em:targetApplication 标记来进行限定。下面将一些常用的 Mozilla 平台与 id 之前的对应关系列出:

Firefox			{ec8030f7-c20a-464f-9b0e-13a3a9e97384}
Thunderbird		{3550f703-e582-4d05-9a08-453d09bdfdc6}
Nvu			{136c295a-4a5a-41cf-bf24-5cee526720d5}
Mozilla Suite		{86c18b42-e466-45a9-ae7a-9b95ba6f5640}
SeaMonkey		{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}
Sunbird			{718e30fb-e89b-41dd-9da7-e25a45638b28}
Netscape Browser	{3db10fab-e461-4c80-8b97-957ad5f8ea47}
Flock Browser		{a463f10c-3994-11da-9945-000d60ca027b}

3.3.2 可选的标记

3.3.2.1 em:creator

此标记的内容用来指明扩展作者的名字。如果你没有提供下面的 em:aboutURL 标记,它将被显示在 Firefox 预定义的 about 窗口中。

3.3.2.2 em:description

此标记的内容用来描述扩展的功能或特性,它会被显示在扩展管理器中。但是,建议你在书写此内容时尽量简明扼要。

3.3.2.3 em:contributor

此标记的内容用来指明扩展贡献者的名字。如果你在扩展开发过程中,还得到了其他的人帮助,或者用到了其他人的源代码,请在此处写明贡献者的信息。并且,此标记允许存在多个,而 em:creator 标记只允许存在一个。如果你没有提供下面的 em:aboutURL 标记,它将被显示在 Firefox 预定义的 about 窗口中。

3.3.2.4 em:homepageURL

此标记的内容用来指明扩展或作者的主页地址。

3.3.2.5 em:updateURL

基于 Mozilla 扩展的最大好处就是可以自动升级,此标记的内容正是用来指明版本检测时的 URL 地址。Mozilla 给扩展提供这样一种升级机制,它通过强制方式或定期检测方式请求升级描述文件,从而获取到有关扩展自身的升级信息,进而对扩展进行升级操作。

为了配合扩展的升级检测操作,Mozilla 还提供了一些环境变量。虽然一般情况下,我们书写的升级检测地址都是固定的,但 Mozilla 提供了一些特殊的环境变量。通过这些环境变量,Mozilla 可以对不同的扩展生成不同的升级检测地址。下面只是一个示例:

<em:updateURL>http://www.foo.com/update.cgi?id=%ITEM_ID%&amp;version=%ITEM_VERSION%</em:updateURL>

有关这些环境变量的详细用法,请到 Mozilla 网址查阅。

3.3.2.6 em:optionsURL

此标记的内容用来指明当前扩展的选项设置入口,并且要以 chrome:// 地址方式进行指定。由于扩展的一些选项值要由用户来设置,所以扩展的作者有必要为扩展提供一个设置窗口。在扩展管理器中,通过“右键/设置”菜单项,我们可以对不同的扩展打开不同的设置窗口,这就是此标记完成的功能。但前提是,扩展作者必须要为所开发的扩展编写设置窗口,否则此标记不是必须的。

3.3.2.7 em:aboutURL

此标记的内容用来指明自定义“about(关于)”窗口的地址。同 em:optionsURL 标记一样,你必须编写并以 chrome:// 地址方式来提供此信息。如果你没有自定义 about 窗口,Firefox 会调用预定义的 about 窗口,并把以上的 em:name,em:version,em:creator,em:description,em:contributor 等信息显示在里面。

3.3.2.8 em:iconURL

此标记的内容用来指明扩展在扩展管理器中显示的图标,它同样要以 chrome:// 地址方式提供。虽然是图标,但它却经常用 PNG 格式的文件来保存。如果你没有指定自己的图标,Firefox 将采用预定义的图案来表示你的扩展。

3.3.2.9 em:targetPlatform

从 Firefox 1.5 版本以后,此标记被引入,它主要用来限制所运行的操作系统(OS)类型。由于某些扩展有二进制兼容的限制,所以就要限制所安装到的 OS 平台。关于此标记的详细用法,请查阅 Mozilla 网站的资料。

3.3.3 已经废弃的标记

3.3.3.1 em:file

从 Firefox 1.5 版本以后,此标记就被废弃了,这是由于引入了 chrome.manifest 文件。有关此标记的解释,作者将其放到了“扩展安装的实现原理”一节。

3.4 有关 Chrome

在 Mozilla 下做开发,你听到最多的词可能就是 Chrome,这个词在 Mozilla 下用的实在很滥。因为有许多地方都使用了这个词,并且每个地方的用法及其解释都不相同,我们要根据所使用的环境来确定 Chrome 这个词所代表的含义。那么,通常的情况下,Chrome 是什么意思呢?

3.4.1 Chrome 的定义

Chrome 是一组应用程序窗口的用户界面元素集合,它们位于窗口的内容区域之外。工具栏,菜单栏,进度条和窗口标题栏都是这些元素的例子,它们都是典型的 chrome 的一部分。

3.4.2 Chrome 提供者

chrome 提供者是一种为特定的窗口类型(如浏览器窗口)提供 chrome 的资源供给机制。从工具栏按钮上的图片到负责描述显示在窗口上的文本,内容和窗口本身的文件都是由 chrome 提供者提供的,这些提供者在一起工作,为特定的窗口提供了一组完整的 chrome。

chrome 提供者共有 3 种类型,分别是 Content,Locale 和 Skin,也就是以前提到的 3 种包资源。当安装包的 content,locale 和 skin 目录真正注册到 chrome 系统以后,它们下面的文件就成了 chrome 提供者。这些提供者就可以完成以上提到的那些功能,但前提是,必须通过一种机制来访问这些提供者,这就是 Chrome URL 要完成的功能。

3.4.3 Chrome URL

像已有的其它 URL 协议一样,我们可以通过 chrome:// 这种形式的 URL 协议来访问那些 chrome 提供者(换句话说,就是已经注册在 chrome 系统下的资源)。其实,那些资源是以物理文件或目录方式被存储,通过地址映射机制以 chrome 地址来访问的。但是我们根本不用管其物理地址是什么,Mozilla 下的扩展及其自身都是以这种映射过的地址来访问的。这样做的最大好处就是屏蔽了文件系统的多样性,为 Mozilla 的跨平台运行打下了基础。同 http 和 ftp 地址格式不一样,Mozilla 对 chrome 地址格式做了严格的规定,如下:

chrome://<package name>/<part>/<file.name>

<package name> 用来指明访问的扩展名称,这个名称在 content 类型的 contents.rdf 文件中被定义,或是被 chrome.manifest 文件所定义。比如,下面的 contents.rdf 文件内容

<RDF:Seq about="urn:mozilla:package:root">
  <RDF:li resource="urn:mozilla:package:sampleext"/>
</RDF:Seq>

它的 <package name> 是 sampleext。你同样会在 chrome.manifest 文件中看到类似的 <package name> 命名。

<part> 用来指明访问的 chrome 包类型,它分别允许 content,locale 和 skin 这 3 种固定的类型。

<file.name> 用来指明访问的文件名称,因为前面的 <package name> 和 <part> 已经对文件的路径做了限定,所以,这个文件相对来说是某个明确的文件。

当我们通过这种方式访问 content 类型的文件时,那些文件将被固定的访问;而当我们访问 locale 或 skin 类型的文件时,Mozilla 会根据自身的语言和皮肤,选择一个最合适的文件来动态访问,这一问题在前面已经提过了。但前提是,你必须提供足够多的语言包和皮肤包供其选择。

如果 <part> 下面还有子目录,你可以像访问文件一样,对子目录下的文件进行相对访问,就像下面这样:

chrome://browser/content/bookmarks/bookmarksManager.xul

而这些 <part> 下的子目录不用再定义 contents.rdf 文件,因为 <part> 所对应的目录已经定义了 contents.rdf 文件,重复定义是没有意义的。另外,chrome 地址还有一种缩略格式,它只被应用在一些特殊的情况下。如下:

chrome://navigator/content/

这看上去是在访问某个目录, 实际上它是

chrome://navigator/content/navigator.xul

的缩写。Mozilla 会根据你访问的 chrome 资源类型来自动补齐后面的文件名称,规则是

<file.name> = <package name> + (.xul|.dtd|.css)

这种形式的 chrome 地址多用于访问比较规则的包资源。下面再列出一些 Firefox 固有的 chrome 文件,你可以尝试用地址栏访问一下这些 chrome 地址:

主窗口 chrome://browser/content/browser.xul
选项窗口 chrome://browser/content/pref/pref.xul
私有选项窗口 chrome://browser/content/pref/pref-privacy.xul
书签管理器 chrome://browser/content/bookmarks/bookmarksManager.xul
书签面板 chrome://browser/content/bookmarks/bookmarksPanel.xul
历史记录面板 chrome://browser/content/history/history-panel.xul
Javascript 控制台 chrome://global/content/console.xul
管理员密码 chrome://pippki/content/pref-masterpass.xul
下载管理器 chrome://mozapps/content/downloads/downloads.xul

3.5 扩展安装的实现原理

本章的以上内容已经对扩展的结构做了详细的解释,但作者觉着还是有必要解释一下 Mozilla 在安装扩展时都做了些什么,这对你了解扩展是如何被注册和工作的都十分有利。在你了解了扩展的安装原理之后,手动注册扩展也不再是什么难以理解的事情了。

3.5.1 Gecko 1.8 以前版本

Gecko 1.8 以前内核的 Mozilla 在安装扩展时,它会去分析扩展安装包顶级结构下的 install.rdf 文件,将 em:id 标记的内容做为目标目录,解压缩到 %profile% 目录下。将扩展的 id,作者,升级地址,设置入口等信息写入到 %profile%/extensions/Extensions.rdf 文件中,因为此文件主要用来保存扩展管理器(Extension Manager)中的显示信息。

同时,它还会根据 install.rdf 中的 em:file 标记来确定有哪些包要注册到 chrome 系统中。如:

<em:file>
    <Description about="urn:mozilla:extension:file:sampleext.jar">
        <em:package>content/</em:package>
        <em:skin>skin/classic/</em:skin>
        <em:locale>locale/en-US/</em:locale>
        <em:locale>locale/zh-CN/</em:locale>
    </Description>
</em:file>

然后,Mozilla 会把如 sampleext.jar 这样的二级 JAR 文件做为分析的目标,分别读取其内部目录下的 contents.rdf 文件(如上为 sampleext.jar 下的 /content/contents.rdf,/skin/classic/contents.rdf,/locale/en-US/contents.rdf,/locale/zh-CN/contents.rdf)。首先,它会将 contents.rdf 中的那些唯一标识包资源的信息与包的物理地址映射起来,写到 %profile%/chrome/chrome.rdf 文件中,此文件为通过 chrome 地址访问包资源提供了地址映射机制。然后,它会将 content 类型包的 contents.rdf 文件所描述的“覆盖”规则写到 %profile%/overlayinfo/ 目录下的对应文件中(如:browser/content/overlays.rdf),这些文件为扩展程序“覆盖”到 Mozilla 系统提供了一种机制,并为扩展程序的运行提供了“入口点”。

3.5.2 Gecko 1.8 及后续版本

Gecko 1.8 及后续内核的 Mozilla 废弃了那种将所有的 chrome 映射信息都由 %profile%/chrome/chrome.rdf 维护,所有的覆盖信息都由如 %profile%/overlayinfo/browser/content/overlays.rdf 这样的文件进行维护的机制,转而将这两种信息都由扩展自己来维护。因而在扩展内部,不再需要提供 contents.rdf 文件,而只需提供一个即描述了“覆盖”信息,又描述了“地址”映射信息的 chrome.manifest 文件即可。

同时,负责保存扩展 id,作者,升级地址,设置入口等信息的 %profile%/extensions/Extensions.rdf 文件,也被换成了 %profile%/extensions.rdf 文件,且格式有所变化。

我们可以看到,新版本的 Mozilla 在安装扩展时,它不用再将那些 chrome 映射信息,覆盖信息分析索引了,而是由扩展开发者来给它预先生成。看上去是我们多做了一些工作,而实际上是方便了我们对扩展注册的控制。

3.6 chrome.manifest 文件

如上所述,在新版本的 Mozilla 中,使用 contents.rdf 注册包的机制被废弃了,但新版本还将兼容这种“古老”的注册方式。采用纯文本格式的 chrome.manifest 使 chrome 资源的注册更加方便,并且更利用扩展的开发。

下面先让我们看一个 chrome.manifest 的示例,如下:

content       necko                   jar:comm.jar!/content/necko/ xpcnativewrappers=yes
locale        necko       en-US       jar:en-US.jar!/locale/en-US/necko/
content       xbl-marquee             jar:comm.jar!/content/xbl-marquee/
content       pipnss                  jar:pipnss.jar!/content/pipnss/
locale        pipnss      en-US       jar:en-US.jar!/locale/en-US/pipnss/
# Firefox-only
overlay chrome://browser/content/pageInfo.xul           chrome://pippki/content/PageInfoOverlay.xul application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
overlay chrome://communicator/content/pref/preftree.xul chrome://pippki/content/PrefOverlay.xul
overlay chrome://navigator/content/pageInfo.xul         chrome://pippki/content/PageInfoOverlay.xul application=seamonkey@applications.mozilla.org
content       pippki                  jar:pippki.jar!/content/pippki/ xpcnativewrappers=yes
locale        pippki      en-US       jar:en-US.jar!/locale/en-US/pippki/
content       global-platform         jar:toolkit.jar!/content/global-platform/ platform
skin          global      classic/1.0 jar:classic.jar!/skin/classic/global/
override chrome://global/content/netError.xhtml jar:embedder.jar!/global/content/netError.xhtml
content       inspector               jar:inspector.jar!/content/inspector/ xpcnativewrappers=no

仔细查看上面的内容,我们不难发现 contents.rdf 的影子。由于 chrome.manifest 是对 contents.rdf 的改进,确切的说是结合了 install.rdf 文件的产物,所以会有许多已有的特性。同时,Mozilla 还为 chrome.manifest 加入了许多原来没有的新规则。所有的规则是由每个文本行头部的“指令”来标识的,这些指令的解释如下。

3.6.1 注册指令

3.6.1.1 content

它用来注册一个 content 类型的包,如下:

content packagename uri/to/files/ [flags]

packagename 是所注册的包名称,同 chrome 地址中的 <package name> 和原有 contents.rdf 中的包名称含义一样。

uri/to/files/ 用来指明这个包资源的操作系统路径,它的格式比较多。对于 %profile% 下的扩展,我们知道在安装之后,Mozilla 会将顶级结构解压缩生成二级 JAR 文件。对于这种相对路径下的 JAR 文件,我们要采用如:

jar:chrome/cview.jar!/content/cview/

这种格式的 uri 来指明。“jar:”用来指明此 uri 指向的是某个 JAR 文件的内部地址。还有一个特殊的“resource:/”用来代表 Mozilla 应用程序的根目录,例如:

jar:resource:/chrome/pipnss.jar!/content/pipnss/

用来指向位于 %app%/chrome/ 目录下 pipnss.jar 的内部压缩地址。而对于非压缩的目录形式的包,则要通过 file:// 地址来指明,如:

file:///D:/edit/projects/sample_extension/chrome/sample_extension/content/sample_extension/

这是一个位于 Windows 操作系统下的 file:// 地址,生成这种地址很简单,你可以通过浏览器地址栏生成符合要求的 file:// 地址。你或者还可以组成上面的 jar: 前缀来定位磁盘上的某个 JAR 包。

jar:file:///D:/edit/projects/sample_extension.jar!/content/sample_extension/
3.6.1.2 locale

它用来注册一个 locale 类型的包,格式如下:

locale packagename localename uri/to/files/ [flags]

localename 用来指明所注册的语言类型,如: en-US,zh-CN 等,其它的同上。

3.6.1.3 skin

它用来注册一个 skin 类型的包,格式如下:

skin packagename skinname uri/to/files/ [flags]

skinname 用来指明所注册的皮肤包类型,如: classic/1.0,其它的同上。

3.6.1.4 overlay

它用来指明 XUL 的“覆盖”规则,这种格式的规则比 contents.rdf 的更容易让人读懂。

overlay chrome://URI-to-overlay chrome://overlay-URI [flags]
3.6.1.5 style

它用来指明 CSS 的“覆盖”规则,这是新加入的特性。通过这个新规则你可以为已有的界面文件引入新的样式表效果。

style chrome://URI-to-style chrome://stylesheet-URI
3.6.1.6 override

在某些时候,你可能会有完全“重载”或者说是替换某个 chrome 文件的要求,这条指令就可以完成这种要求。

override chrome://package/type/original-uri.whatever new-resolved-URI

3.6.2 注册指令中的 Flags 标识

在上面的注册指令中,你会发现有个 [flags] 标识经常出现行尾 ,这个标识多用来标识特定的 chrome 包属性或限定当前行命令是否可用。下面对那些可以做为 [flags] 标识的内容一一说明。

3.6.2.1 application

它是一个条件类型的标识,用于限定注册包所应用的 Mozilla 平台。由于某些扩展可以被安装在多个 Mozilla 平台下,通过这个条件标识就可以将某个包限定性的应用于某个特定的 Mozilla 平台下。

application=app-ID

本章的“install.rdf 文件”一节已经列举了一些常见的 Mozilla 平台,参考上面列出那些 id,书写此标识即可。如果你有多个平台要限定,你可以将多个条件写在行尾。

3.6.2.2 appversion

它是一个条件类型的标识,用来限定注册包所应用 Mozilla 平台的版本范围,如下:

appversion=version
appversion<version
appversion<=version
appversion>version
appversion>=version

version 是 Mozilla 平台的版本,你可以通过 Mozilla 的 about 窗口,查看它的确切版本号。如果某个注册包的范围限定需要多个条件,你同样可以像 application 一样,将多个条件写在同一行。

3.6.2.3 platform

它是一个关键字标识,用来限定包资源所应用的操作系统类型。由于某些包的使用需要基于特定的操作系统,所以要做这方面的限定。但此标识只能应用在 content 类型的包上,locale 和 skin 类型的包将忽略这个标识。如果对某个 content 包应用了这种标识,你还需要在那个 content 包目录下再建 3 个子目录,将它们分别命名为 win,mac 和 unix,以对应 3 种不同的操作系统 Windows/OS2,OS9/OSX 和 Unix-Like 。在这 3 个目录下,你肯定还要建立一些适用于指定操作系统的文件,如: .dll 或 .sh 文件,所有在这 3 个子目录之外的文件都将被 Mozilla 忽略。如下便是一个应了 platform 的示例:

content global-platform jar:toolkit.jar!/toolkit/content/global-platform/ platform
3.6.2.4 xpcnativewrappers

它是一个条件类型的标识,对于那些要访问所浏览网页内容的 chrome 代码来说,XPCNativeWrapper 是一种代码保护机制。在有些恶意网页中,网页的编写者可以通过重载网页中的某个 DOM 对象的属性或方法来达到代码注入的效果。当有些 chrome 代码要访问当前网页的某个 DOM 元素时,它肯定要获取此元素的属性或调用其下的某个方法,而如果那个 DOM 元素的属性或方法被使用 JavaScript 重写成了某段恶意代码,chrome 代码的执行肯定脱离了本意,并且变成了恶意代码的执行者,这样恶意网页就达到 chrome 注入攻击的效果。

Mozilla 通过一个叫 XPCNativeWrapper 的机制来取那些真实的属性和方法,来避免 chrome 代码的恶意注入。在以往的程序里,你需要通过手动调用 XPCNativeWrapper 对象来取 DOM 元素的属性,这比较麻烦。在 Firefox 1.5 以后,这个保护特性被默认打开,你不再需要使用 XPCNativeWrapper 对象来读取 DOM 属性了。但是,如果你想关闭这个特性,你必须通过指明 xpcnativewrappers=no 条件来实现,我想很少会有人这么做的。

此条件标识只被应用在 content 类型的包上,locale 和 skin 类型的包将忽略此标识。

3.7 Chrome 新机制的应用: Contents.rdf + Install.rdf = Chrome.manifest

下面对以上章节中出现的 contents.rdfinstall.rdf 结合起来做一个 chrome.manifest 转换,看看这时的 chrome.manifest 是什么格式。

overlay	chrome://browser/content/browser.xul	chrome://sampleext/content/overlay.xul
content	sampleext	jar:chrome/sampleext.jar!/content/
skin	sampleext	classic/1.0	jar:chrome/sampleext.jar!/skin/classic/
locale	sampleext	en-US	jar:chrome/sampleext.jar!/locale/en-US/
locale	sampleext	zh-CN	jar:chrome/sampleext.jar!/locale/zh-CN/

第四章 配置项目环境

通过上一章我们已经对扩展的结构和关键的 Chrome 有所了解,在此基础之上,本章将讲解扩展项目的配置方法。许多的扩展开发者可能都有过这样的经历:XUL 元素掌握得差不多了,可是想做一个小项目的时候却不知如何入手。作者希望通过本章节,你能了解到如何合理地配置扩展项目的开发环境。

Mozilla 平台下的项目配置和其它平台下的项目配置有些不同,其下的项目配置需要一个“螺旋式”渐进的过程。首先,我们需要先建立一个框架式扩展;然后,将其以安装方式注册到 Mozilla 下,同时对此扩展涉及到的 chrome 映射做修改;最后,我们才能真正地对项目进行开发。如果你在开发过程又中增加或修改了有关 chrome 注册的信息,你还要重复以上的过程,但那时的扩展已经不能再称为框架式扩展了,这种配置方法非常像软件开发中的“螺旋式开发模型”。由于,Firefox 的新版本与老版本在 chrome 注册方式上不同,所以相应的项目配置方法也会不相同。

4.1 建立框架式扩展

每一个做 Mozilla 扩展开发的人,他的第一个扩展几乎都是根据已有的“模板式”扩展修改而来的。而对于刚开发扩展的人来说,不依赖已有的扩展,从头实现一个自己的扩展几乎是不可能的。那么,就让我们从经典的“Hello World”扩展开始吧。

4.1.1 “Hello World” 的结构

对于一个扩展来说,如果你想让它能分别运行于高版本和低版本的 Mozilla 上,确实是一件让人很头痛的事情。我们必须将那些兼容性的文件都考虑到,特别是有关 chrome 注册的 contents.rdf 文件。下面给出它的目录结构:

helloworld/
  chrome.manifest
  install.rdf
  chrome/
    helloworld/
      content/
        contents.rdf
        overlay.js
        overlay.xul

你必须在磁盘上建立一个对应的目录结构,因为这就是你项目开发的基础,你在项目开发时的所有源代码的都将放在这里,后面将称其之为“项目目录”。

4.1.2 负责注册 Chrome 的文件

chrome.manifest 的内容如下:

overlay	chrome://browser/content/browser.xul	chrome://helloworld/content/overlay.xul
content	helloworld	jar:chrome/helloworld.jar!/content/

install.rdf 的内容如下:

<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
     xmlns:em="http://www.mozilla.org/2004/em-rdf#">

  <Description about="urn:mozilla:install-manifest">
    <em:id>{241b5bc7-a8aa-44a6-a18d-3054dc6047cf}</em:id>
    <em:name>Hello World</em:name>
    <em:version>1.0</em:version>
    <em:type>2</em:type>
    <em:creator>Lewis Lv</em:creator>
    <em:description>The classical demo with "Hello, world!"</em:description>
    <em:homepageURL>http://kb.mozillazine.org/Getting_started_with_extension_development</em:homepageURL>

    <!-- Firefox -->
    <em:targetApplication>
      <Description>
        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
        <em:minVersion>1.0</em:minVersion>
        <em:maxVersion>1.5</em:maxVersion>
      </Description>
    </em:targetApplication>

    <!-- This is not needed for Firefox 1.1 and later. Only include this 
      if you want to make your extension compatible with older versions -->
    <em:file>
      <Description about="urn:mozilla:extension:file:helloworld.jar">
        <em:package>content/</em:package>
      </Description>
    </em:file>
  </Description>
</RDF>

contents.rdf 的内容如下:

<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:chrome="http://www.mozilla.org/rdf/chrome#">

  <RDF:Seq about="urn:mozilla:package:root">
    <RDF:li resource="urn:mozilla:package:helloworld"/>
  </RDF:Seq>

  <!-- package information -->
  <RDF:Description RDF:about="urn:mozilla:package:helloworld"
        chrome:name="helloworld"
        chrome:extension="true"
        chrome:displayName="Hello World"
        chrome:author="Lewis Lv"
        chrome:authorURL="http://kb.mozillazine.org/Getting_started_with_extension_development"
        chrome:description="The Classical Demo With Hello World">
  </RDF:Description>

  <!-- overlay information -->
  <RDF:Seq about="urn:mozilla:overlays">
    <RDF:li resource="chrome://browser/content/browser.xul"/>
  </RDF:Seq>

  <RDF:Seq about="chrome://browser/content/browser.xul">
    <RDF:li>chrome://helloworld/content/overlay.xul</RDF:li>
  </RDF:Seq>
</RDF:RDF>

以上这 3 个文件是负责 chrome 注册的,所以提前给出。如果你在语法上还有什么不清楚的,请你参考上一章的内容。下面将结合着 browser.xul,着重介绍一下 overlay.xul 文件的内容。

4.1.3 overlay.xul

在上一章中,已经介绍过有关覆盖的内容,但是介绍得过于肤浅且没有示例。下面的一段内容摘自《Rapid Application Development with Mozilla》,原文如下:

"The overlay system is quite simple. One XUL document is the master document. This document provides a starting point for the final content. Any other XUL documents are overlays. Overlay content is merged into, or added to, the master document's content. This happens in memory when those documents are loaded and has no effect on the original files."

作者对上面的内容进行了意译,参考如下:

“覆盖系统十分简单,一个文档是 XUL 主文档,它提供了整个内容的入口点;另一个是文档是 XUL 覆盖文档,它描述了覆盖到主文档中的内容。当这些文档被装载时,覆盖内容会被合并或者加入到主文档的内容中。但以上的这一过程只发生在内存中,所以不会影响到原始的物理文件。”

从上面这段内容中,不难反应出一个问题。Mozilla 之所以扩展性很好,是因为它在设计之初就考虑到了如何应对扩展的问题。那些扩展程序在物理上,分别对自己的“覆盖”信息进行管理,互不影响;可一旦运行起来,却都被集成到 Mozilla 上,像一个程序一样协作运行。很显然,这就是 Mozilla 把自己做为一个平台,以此来对外提供接口的途径。下面,让我们看一下“Hello World”的 overlay.xul 实现。

<?xml version="1.0"?>
<overlay id="helloworld-overlay"
         xmlns=http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul>
  <script type="application/x-javascript" src="overlay.js"/>

  <menupopup id="menu_ToolsPopup">
    <menuitem id="helloworld-hello" label="Hello, world!" oncommand="helloWorld();"/>
  </menupopup>
</overlay>

这个文件所实现的功能,就是在 Firefox 的“工具(tools)”下拉菜单中再添加一个“Hello,world!”菜单项。通过点击那个菜单项,执行 helloWorld 函数。效果如下:

menu.jpg

我们可以看到,这个 XML 格式的文件就是 XUL 文件,这是由于它使用了 XUL 标记并引入了 XUL 命名空间。并且,这个专门负责描述“覆盖”的 XUL 文件与其它的 XUL 文件还有所不同,因为它使了一个特殊的 overlay 根标记。可以这么说,这个 XUL 文件就是整个“Hello World”扩展的入口。

此文件在 overlay 根标记中引入了 XUL 的命名空间,它所实现的功能就是: 在默认情况下,overlay 下面的所有子标记会自动继承它的命名空间。

xmlns=http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul

只有这样,此文档的所有标记才会被识别为 XUL 标记。在它的下面,还引用了 overlay.js 脚本文件,引用的方式与在 HTML 中没有什么太大的区别,helloWorld 函数于此文件内部被定义。

<menupopup id="menu_ToolsPopup">
  <menuitem id="helloworld-hello" label="Hello, world!" oncommand="helloWorld();"/>
</menupopup>

这段内容即为对“覆盖”的描述。因为在 chrome://browser/content/browser.xul 中描述了 Firefox 的界面信息,所以当前扩展的覆盖信息就应该参照这个文件来完成,它就是上文中提到的“XUL 主文档”。又由于在 browser.xul 中,“工具(tools)”下拉菜单的 id 被定义成了“menu_ToolsPopup”,所以 overlay.xul 就参照 browser.xul 中的定义,在自己内部定义了

<menupopup id="menu_ToolsPopup">

它的目的是要告诉 Firefox,让 Firefox 比照当前标记,将其下的所有子标记“合并”到对应的“menu_ToolsPopup”标记中。当然,这个“合并”操作不是真的在物理文件上做合并,而是在 Firefox 启动时,在内存中进行“合并”操作。

XUL 覆盖文档”的内容一般都很简单。因为在一般情况下,不需要将太多的界面元素“覆盖”到 Mozilla 原有的界面上。它只是充当一个入口的角色,有的“覆盖文件”甚至连一个描述“覆盖”的标记都没有,扩展中大量的功能都是在如 overlay.js 这样的脚本文件中完成的。因为 JavaScript 可以对 XUL DOM 进行操作,所以它同样可以通过脚本方式动态添加或修改界面元素。

4.1.4 overlay.js

这个文件负责定义 helloWorld 函数。helloWorld 函数实现打开“Hello,world!”窗口的功能,代码如下:

// This is the main function
function helloWorld()
{
    alert("Hello, world!");
}

以上的代码很简单,它只是通过调用 window 对象的 alert 方法来打开一个预定义窗口,内容就是“Hello,world!”。效果如下:

alert.jpg

但在实际的项目中,这种实现肯定是无法满足要求的。你可能要编写许多的函数或对象,甚至编写多个 JS 文件,使它们在一起协调工作,这些文件同样像 overlay.js 一样被 overlay.xul 引用。至于如何编写那些 JS 文件,就是你最大的课题了,但至少现在你知道如何着手做东西了。

4.2 安装并修改 Chrome 映射

我们已经以目录形式建立了扩展,但是如何让它在 Firefox 中运行呢?你可能会想到,将它照扩展的 XPI 格式打包,再安装到 Firefox 上不就可以了吗?这种想法肯定是没错的,但是你打算一直在“打包 -> 安装 -> 修改错误或添加功能 -> 打包 -> 卸载原有/安装 -> 修改错误或添加功能 -> ...” 的过程中循环进行吗?我可不打算这么干,开发效率太低了,我想你的鼠标和键盘也不会“答应”的。正确的方法是,你必须以目录形式注册扩展,以此来进行开发。即使是这样,你都免不了要在开发时重复的关闭和开启 Firefox 进程。

由于,Firefox 1.0 与 Firefox 1.5 版本,在扩展注册方式上有许多的不同。同样是手动注册扩展,后者非常简单,而前者则有些复杂,本章开始时提到的“螺旋式”配置,也只是针对后者来说的。为了能在 Gecko 1.8 以前版本(对应 Firefox 1.0)的 Mozilla 中配置项目,作者还是希望你能掌握这些技巧,以应对扩展的快速开发。需要注意的是,你每次修改完 chrome 注册信息,都需要重新启动 Firefox,因为 Firefox 只在启动时才会加载那些注册信息。

4.2.1 Firefox 1.0 及以前版本

为什么非要从安装扩展开始呢?手动注册扩展有什么困难吗?答案是,如果使用“公认”的方法,在 Firefox 1.0 下手动注册扩展其实很简单。但是它将影响并破坏每个 Profile 的独立性,并且有可能破坏 Firefox 程序本身。这种公认的方法就是修改 %app%/chrome/installed-chrome.txt 文件,在这个文件里,你只需要简单地添加几行扩展的注册信息即可。但在这里,作者不打算讲解这种方式的实现,只是怕你破坏掉 Profile 的独立性及 Firefox 本身。这种方法的缺点如下:

  • 注册的所有扩展信息会被写入到 %app% 目录下的文件中,所以在所有的 Profile 下都是“可见的”,即可以通过 chrome 地址进行访问。这不利于配置独立的 Profile 来分别进行开发和测试;
  • 另一个负面影响就是,“安上去容易,卸下去难”。如果你想卸载通过这种方法注册的扩展,你必须手动删除许多的注册信息,稍不留意,就会破坏 Firefox 本身;
  • 所注册的扩展在 Firefox 扩展管理器中不可见。上一章在讲“扩展安装的实现原理”时提到过,位于扩展管理器中的信息是被写入 %profile%/extensions/Extensions.rdf 文件中的。但是通过这种方式手动注册的扩展,其信息只被写入 %app% 下的相关文件中,所以在扩展管理器中是不可能见到的,并且你还无法“真实地”对设置窗口和关于窗口进行测试(原因是,虽然你可以通过 chrome 地址来访问设置窗口和关于窗口,但它显然不是被放到“真实的”环境进行测试的);

通过独立的 Profile 来注册扩展,显然可以将其影响范围降到最小,并且开发中的扩展与发布安装时的扩展,其运行状态没有区别。但对独立的 Profile 做手动的扩展注册简单太麻烦了,上一章讲扩展的安装原理时已经提到过 Mozilla 所做的工作。那些扩展的注册信息“东拆西拆”的,被放到许多的文件中,并且注册语法很复杂。相比之下,“Firefox 本身却认为这些注册扩展的操作没有什么”。我们正好利用扩展能自动安装的特性,让 Firefox 自己来安装这些扩展,然后,我们再对那些“至关重要”的 chrome 地址映射做修改,即可完成项目配置的要求。

4.2.1.1 打包扩展

第一步,我们先要将扩展从目录形式转换为 XPI 安装包形式,所使用的打包工具就是 ZIP 压缩软件。常用的 ZIP 压缩软件有 WinRAR,WinZIP;Unix-Like 操作系统下必须使用 zip 工具,gzip 的格式是不符合要求的。如果工具没问题了,我们还必须将扩展按下面的格式进行打包:

helloworld.xpi/
  chrome.manifest
  install.rdf
  chrome/
    helloworld.jar/
      content/
        contents.rdf
        overlay.js
        overlay.xul

helloworld.xpihelloworld.jar 使用的都是 ZIP 格式,只不过是扩展名不同而矣。如果你打包时的结构出现了问题,扩展是不能被 Firefox 正常安装的,所以你要特别小心。

4.2.1.2 扩展的安装

第二步,将扩展安装到 Firefox 上。提醒你一下,你别打算在扩展管理器中找到任何安装扩展的按钮。安装扩展很简单,你可以通过“文件(File)/ 打开文件(Open File)”来指定被安装的扩展,也可以将扩展拖拽到 Firefox 内容区域或扩展管理器窗口中,Firefox 会自动弹出安装提示窗口。

4.2.1.3 修改 Chrome 地址映射

我们知道安装后的扩展,其 chrome 地址映射信息是被写入到 %profile%/chrome/chrome.rdf 文件中的。因此,我们将那些映射的地址改成项目目录地址即可。如下是作者 chrome.rdf 中的扩展映射信息片段:

<RDF:Description RDF:about="urn:mozilla:package:helloworld"
                 c:baseURL="jar:file:///C:/Documents%20and%20Settings/lewislv/Application%20Data/Mozilla/Firefox/Profiles/gv8xfmu1.dev/extensions/%7B241b5bc7-a8aa-44a6-a18d-3054dc6047cf%7D/chrome/helloworld.jar!/content/"
                 c:locType="profile"
                 c:name="helloworld"
                 c:extension="true"
                 c:displayName="Hello World"
                 c:author="Lewis Lv"
                 c:authorURL="http://kb.mozillazine.org/Getting_started_with_extension_development"
                 c:description="The Classical Demo With Hello World" />

上面出现的 c:baseURL 属性,它所指向的是 content 类型包的物理地址。当然,这个地址是位于 %profile%/extensions/{241b5bc7-a8aa-44a6-a18d-3054dc6047cf}/helloworld.jar 中的压缩地址。由于我们要以目录形式开发扩展,所以要将它替换成项目目录中的 content 包所在地址。比如:

<RDF:Description RDF:about="urn:mozilla:package:helloworld"
                 c:baseURL="file:///D:/edit/projects/helloworld/chrome/helloworld/content/"
                 c:locType="profile"
                 c:name="helloworld"
                 c:extension="true"
                 c:displayName="Hello World"
                 c:author="Lewis Lv"
                 c:authorURL="http://kb.mozillazine.org/Getting_started_with_extension_development"
                 c:description="The Classical Demo With Hello World" />

照这样替换以后,Firefox 再进行地址映射时,它会到这个目录下去映射那些 chrome 文件,而不会再去 helloworld.jar 中映射对应的文件。如果你的扩展还注册了其它类型的包资源,你都需要一一替换。现在,你即使将 helloworld.jar 删除了,也不会有任何的问题了。

4.2.2 Firefox 1.5 及后续版本

由于作者在写这篇文档时,主要的开发工具“JavaScript Debugger”还没有 Firefox 1.5 的兼容版本,所以作者主要工作在 Firefox 1.0 下。实际上,作者在同一系统下安装了两个版本的 Firefox,它们共享同一套 Profile。如果你也像作者一样,我希望你联系着上面的 Firefox 1.0 配置来进行 Firefox 1.5 的配置,因为这时你仅需对安装后的 chrome.manifest 文件做修改,即可完成配置项目环境的要求。

原有的 chrome.manifest 中

overlay	chrome://browser/content/browser.xul	chrome://helloworld/content/overlay.xul
content	helloworld	jar:chrome/helloworld.jar!/content/

我们需要将其照下面的修改

overlay	chrome://browser/content/browser.xul	chrome://helloworld/content/overlay.xul
content	helloworld	file:///D:/edit/projects/helloworld/chrome/helloworld/content/

如果你发现 Firefox 好像有记忆性,它总是会先到 helloworld.jar 中进行映射。那么,你需要确认一下是否已经将 nglayout.debug.disable_xul_cache 选项置成 true 了。

如果你只使用 Firefox 1.5 做开发,你还可以使用一种非常简单的方法进行注册。你没必要像 Firefox 1.0 中那样,经历“打包 -> 安装 -> 修改 chrome”的过程,你只需建立一个“指向文件”,并对项目目录下的 chrome.manifest 做些修改即可。方法如下:

  1. 打开 %porfile%/extensions 目录;
  2. 建立一个与你所开发扩展 id 名称相同的纯文本文件,如: {241b5bc7-a8aa-44a6-a18d-3054dc6047cf}。需要注意的是,此文件没有任何的扩展名。你需要将项目目录的路径保存其中,如:“D:\edit\project\helloworld\”,这个路径的格式必须是操作系统路径,而不能是 file:// 地址格式;

除此之外,你还要更改项目目录下的 chrome.manifest 文件。因为,Firefox 在启动时会到项目目录中来查找描述了 chrome 映射信息的 chrome.manifest 文件。你只需将它的 chrome 映射像上面做修改即可,这时你给出的地址既可以是相对的,也可以是绝对的。

相对地址格式: content	helloworld	chrome/helloworld/content/
绝对地址格式: content	helloworld	file:///D:/edit/projects/helloworld/chrome/helloworld/content/

但是这样有一个不好的地方,项目目录中的 chrome.manifest 与发布时的 chrome.manifest 内容不再一样了。你必须在发布时,另外再建立一个适用于发布的 chrome.manifest 文件。为了避免这个问题,作者还有另外一种方法,它可能更适合处理开发项目的无关性。

  1. 同样,打开 %porfile%/extensions 目录;
  2. 建立一个与你所开发扩展 id 名称相同的目录,将你项目目录下的 install.rdf 和 chrome.manifest 文件复制到其中;
  3. 以绝对地址格式修改 chrome.manifest 中的 chrome 映射地址,将其指向开发项目的对应目录;

以这种方式注册的扩展,项目目录下的内容最大限度地与环境无关。因此,你不用再考虑建立一个适合于发布的 chrome.manifest 文件了。

通过以上的这些工作, 我们的项目环境终于被配置完成。可以这么说,现在真的可以着手开发项目了。

4.3 开始真正的开发

上面的“Hello World”实现得过于简单了,它只使了一个简单的 alert 窗口来显示“Hello,world!”文本。下面,我们打算用定义自己的窗口来显示那些信息,其中还会包含一些图片和 CSS。当然,我们要对已有的内容做修改。

4.3.1 加入 Skin 包资源

一个正常的扩展一般都会有 skin 资源,只有加入了 skin 资源,界面才会更美观。同时,也有利于 CSS 与 XUL 描述的分离。下面,我们要像打补丁一样,在原有文件的基础上做修改。

首先,我们要对 install.rdf 文件做修改。在此文件的 Description 标记下,加入“关于”和“图标”的 chrome 地址。

<em:description>The classical demo with "Hello, world!"</em:description>
<em:aboutURL>chrome://helloworld/content/helloworld.xul</em:aboutURL>
<em:iconURL>chrome://helloworld/skin/icon.png</em:iconURL>
<em:homepageURL>http://kb.mozillazine.org/Getting_started_with_extension_development</em:homepageURL>

通过 em:iconURL 标记,位于扩展管理器的扩展将具有一个用来“标识”自己的图标。同时,通过 em:aboutURL 标记,你可以在扩展管理器中打开自定义的“关于”窗口。效果如下:

extmange.jpg

当然,以上的增加的这些内容,你都要提前有所规划。我们的主要目的是加入 skin 类型的包资源,因此还要在 install.rdf 中加入下面的内容:

<Description about="urn:mozilla:extension:file:helloworld.jar">
  <em:package>content/</em:package>
  <em:skin>skin/classic/</em:skin>
</Description>

然后,为了应对新版的 Firefox,我们还在 chrome.manifest 加入 skin 包的注册信息。

skin	helloworld	classic/1.0	jar:chrome/helloworld.jar!/skin/classic/

最后,我们必须在项目目录下创建对应的 skin 包目录,并且加入必要的文件。当然,所加入的这些文件,与你的设计需要有关。在这里,作者打算加入两个图片文件,一个用来做图标(icon.png),一个用来做 Logo(helloworld.png),如下:

icon.png helloworld.png

还要加入一个 CSS 文件,用来保存分离的 Style 定义(helloworld.css);同时,还有必不可少的 contents.rdf 文件。下面是变化后的目录结构:

helloworld/
  chrome.manifest
  install.rdf
  chrome/
    helloworld/
      content/
        contents.rdf
        overlay.js
        overlay.xul
      skin/
        classic/
          contents.rdf
          helloworld.css
          icon.png
          helloworld.png

我们还要创建 skin 的 contents.rdf 文件,如下:

<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:chrome="http://www.mozilla.org/rdf/chrome#">

  <RDF:Seq about="urn:mozilla:skin:root">
    <RDF:li resource="urn:mozilla:skin:classic/1.0" />
  </RDF:Seq>

  <RDF:Description about="urn:mozilla:skin:classic/1.0">
    <chrome:packages>
      <RDF:Seq about="urn:mozilla:skin:classic/1.0:packages">
        <RDF:li resource="urn:mozilla:skin:classic/1.0:helloworld" />
      </RDF:Seq>
    </chrome:packages>
  </RDF:Description>
</RDF:RDF>

在 Firefox 1.0 下,如果你仅有那些资源文件,而忽略了定义 contents.rdf 文件,那是资源文件也是不能被识别的。现在,所有关于 skin 的资源已经定义完毕了。需要说明的是,对于 helloworld.css 的内容,我们要结合 helloworld.xul 文件来进行定义。

4.3.2 新一轮的 Chrome 配置

本章的开始部分已经说过,“如果你在开发过程又中增加或修改了有关 chrome 注册的信息,你还要重复以上的过程”。那么,根据你当前的 Firefox 版本,你可能还要重复进行“打包 -> 安装 -> 修改 chrome”的过程,或者你只需简单的配置修改。

你应该明白,这些修改是必须的,除非你完全清楚低版本的 Extensions.rdf,chrome.rdf 或高版本的 extensions.rdf 的格式要求。

4.3.3 创建自定义的“Hello World”窗口

如果你的 XUL“理论”已经相当不错了,或者至少写过简单的 XUL 的话,我想你已经急不可耐了。因为,绕了这么一大圈子,我们终于可以写一个运行在 chrome 状态下的 XUL 文件了,并且它还可以调用 skin 类型的 chrome 资源。

4.3.3.1 helloworld.xul

它就是那个用来显示“Hello World”的窗口文件,与其配合完成功能的文件还有上面的 helloworld.css 和后面的 helloworld.js,helloworld.xul 文件的内容如下:

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://helloworld/skin/" type="text/css"?>

<window id="helloWorld"
  xmlns=http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul
  title="Hello World Demo"
  orient="vertical"
  onload="onLoad();">

  <script type="application/x-javascript" src="helloworld.js" />

  <commandset>
    <command id="cmd_close" oncommand="window.close();"/>
  </commandset>

  <keyset>
    <key id="key_colse" keycode="VK_ESCAPE" command="cmd_close"/>
  </keyset>

  <groupbox align="center" orient="horizontal" id="infobox">
    <vbox>
      <hbox align="baseline" id="titlebox">
        <text value="Hello, world!" id="title"/>
        <spacer style="width:1em;"/>
        <text value="version 1.0" id="version"/>
      </hbox>
      <vbox align="end" id="creatorbox">
        <text value="Created By:" id="createby"/>
        <text class="url" id="creator" onclick="contactCreator();"/>
      </vbox>
      <vbox align="end" id="homebox">
        <text value="Home Page:" id="homepagetitle"/>
        <text class="url" id="homepage" onclick="openHomePage();"/>
      </vbox>
    </vbox>

    <spacer flex="1"/>
    <image src="chrome://helloworld/skin/helloworld.png" id="logo"
        onclick="openHomePage();"/>
  </groupbox>
  <separator/>
  <hbox align="end">
    <spacer flex="1"/>
    <button label="Close" command="cmd_close" id="btnClose"/>
  </hbox>
</window>

以上 XUL 标记描述了一个窗口及其中所显示的内容。同以前的 overlay.xul 文件不同,它是用 window 做为根标记的。XUL 和 HMTL 在格式上有许多相同之处,每个标记都有自己所负责的功能,并且有些标记还可以在 HTML 中找到功能对应的标记。我不打算去讲解那些 XUL 标记的用法和细节,因为许多内容在 XULPlanet 上都可以找到相应的参考。如果你对上面的哪个标记不清楚,你可以在这个网站查阅到详细的用法。

如果你对 XUL 还只是一知半解,以上的有些内容可能会让你不知所措。因为,上面确实用到了些比较“高级”的 XUL 技巧,目的只是为了让界面更加美观。如果你仅仅是学习 XUL,你不用建立任何的扩展项目,简单的 XUL 加一些 CSS 和 JavaScript 即可。

4.3.3.2 helloworld.css
text{
    padding:2px;
}

#helloWorld{
    background-color: #FFFFFF;
    margin: 1em;
}

#infobox{
    border: 2px dotted #FF1010;
}

#title{
    font-weight: bold;
    font-size: x-large;
}

#version{
    font-weight: bold;
    color: #909090;
}

#titlebox{
    margin: 20px;
    border-bottom: 5px solid #FF1010;
}

#createby, #homepagetitle{
    font-weight: bold;
}

#creatorbox{
    margin-top:10px;
    margin-right:20px;
    margin-bottom:10px;
}

#homebox{
    margin-right:20px;
    margin-bottom:10px;
}

#btnClose{
    font-weight: bold;
}

#logo{
    margin-right:20px;
    -moz-opacity: 0.5;
}

#logo:hover{
    -moz-opacity: 1;
    cursor: pointer;
}

.url:hover{
    color: #FF1010;
    cursor: pointer;
    text-decoration: underline;
}

你可看到 XUL 与 HMTL 一样,都需要 CSS 来负责那些“样式化”的东西,语法与 HTML 一样。为了让 helloworld.xul 能引用这些样式,必须在 helloworld.xul 中加入样式表引用的“处理指令”,如下:

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://helloworld/skin/" type="text/css"?>

上面的样式表引用地址是上一章介绍过的 chrome 缩略格式。与 HTML 中的 CSS 不太一样,这里会要求在 hellworld.xul 中的许多标记中加入 id 属性,而不是 class 属性,目的是为了利用 id 来针对性地控制指定标记的外观。

4.3.3.3 helloworld.js

在 helloworld.xul 中,你可以看到有些标记被加入了 onclick 事件属性,与 HTML 标记中的事件属性一样,它是为了能将“界面”与“功能”结合起来。那些被封装在 JS 文件中的函数用来完成指定的功能,如: 打开主页或邮件客户端程序。helloworld.js 的内容如下:

// define constants
const HOMEPAGE = http://developer.mozilla.org;
const CREATOR = "Lewis Lv";
const CREATOR_EMAIL = "lewislv@gmail.com";

// This function use to initialize window's elements
function onLoad()
{
    var homepage = document.getElementById("homepage");
    homepage.setAttribute("value", HOMEPAGE);
    homepage.setAttribute("tooltiptext", HOMEPAGE);

    var creator = document.getElementById("creator");
    creator.setAttribute("value", CREATOR);
    creator.setAttribute("tooltiptext", CREATOR_EMAIL);
}

// This function use to open homepage
function openHomePage()
{
    window.opener != null ? window.opener.open(HOMEPAGE) : window.open(HOMEPAGE);
}

// This function use to open mail application
function contactCreator()
{
    var mailURL = "mailto:" + CREATOR_EMAIL;
    var _blank = window.opener != null ? window.opener.open(mailURL) : window.open(mailURL);
    _blank.close();
}

以上的内容只是简单地定义了 3 个函数,分为用来处理窗体装载时的事件,用户点击“主页”时的事件,用户点击“作者”时的事件。负责处理窗口装载的函数 onLoad,它利用 DOM 接口来操纵所显示的内容,将主页地址和作者名称填充到窗口界面上。而 openHomePage 和 contactCreator 则是利用新窗口来打开主页地址和邮件客户端程序。

4.3.3.4 自定义窗口的启动

通过上面的修改,“Hello World”项目又增加了几个文件。现在,再来让我们来看一下它的结构。

helloworld/
  chrome.manifest
  install.rdf
  chrome/
    helloworld/
      content/
        contents.rdf
        helloworld.js
        helloworld.xul
        overlay.js
        overlay.xul
      skin/
        classic/
          contents.rdf
          helloworld.css
          icon.png
          helloworld.png

但是,我们还要将前面的 overlay.js 做修改,因为它只是简单地调用了 alert 来显示“Hello,world!”内容。我们必须让它打开自定义的“Hello World”窗口,因此,我们必须对 overlay.js 中的 helloWorld 函数做修改。修改后的代码如下:

// This is the main function
function helloWorld()
{
    window.open("chrome://helloworld/content/","helloworld","chrome,centerscreen");
}

通过点击“工具(tools)”菜单下的“Hello, world!”菜单项,或扩展管理器中的 “关于(about)”菜单项,它将显示下面的效果。

custom.jpg

至此,“Hello World”项目就已经结束了。它只是一个简单的演示,你需要把它按 XPI 格式打包,用你负责测试的 Profile 来对其进行安装测试;或者你还可以把它安装到任何一台装有 Firefox 的机器上,看看是否和你在项目环境下运行的一样。

4.3.4 示例下载

附录

I. 参考网址

posted @ 2011-04-07 17:42  许明吉博客  阅读(1933)  评论(0编辑  收藏  举报