第三章 扩展的结构及 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 格式。RDF(Resource 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>
这里的 package 和 sampleext 都需要注意。首先,package 用来说明它描述的是一个 content 类型的包。对于 locale 和 skin 类型的包,应把它替换成 locale 和 skin,虽然这看上去与包目录的命名有些不一致。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.1</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:minVersion,em: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%&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.rdf 和 install.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/
What a joke you are, not only do you have to lie to WS, you also have to lie to us in order to make your story more believable. ,
Comment by John25 — 2009 年 10 月 10 日 @ 23:35:58
How will you react if you are in a despairing mode when you experience the anxiety of anomaly? ,
Comment by Barbara83 — 2009 年 10 月 23 日 @ 5:27:47