虽然 JavaScript 看上去很简单,但用 JavaScript 开发大型程序,却对开发人员本身提出了很高的要求。因为 JavaScript 是一种比较灵活的语言,就当前版本的 JavaScript 来说,其本身并没有提供很好的进行大型开发的结构,所以就要求开发人员必须具备很强的能力,对那些复杂的结构进行模块化的处理。如果仅仅使用面向过程进行 开发,就会在开发时“捉襟见肘”,这就要求开发人员必须采用面向对象的方式进行开发。可以说,从网页下的 JavaScript 开发到扩展下的 JavaScript 开发,是一种由轻量级到重量级的转变。
由于 JavaScript 语言只内置了几个与本地访问无关的对象,而对于桌面开发来说,显然不能满足要求。因为桌面开发需要访问大量的本地和网络资源,包括文件,剪贴板,Socket,浏览器本身等各种资源。而 XPCOM 为面向桌面的开发提供了这种可能,并且它使开发出的扩展程序可以跨平台的运行,而不用依赖于某个特殊的操作系统。只有使用 XPCOM,我们的扩展才可以做出实用的功能,没有 XPCOM,本地与远程的资源整合可以说是不可能。虽然扩展开发是用 JavaScript 来做的,但每个封装的对象或函数可能都要调用 XPCOM 对象来完成特定的功能。可以这么说,离开 XPCOM,我们寸步难行。
第1部份 扩展的结构及 Chrome 注册本章就扩展程序的结构和 Chrome 注册机制,予以比较详细的讲解,只有当你对这部分的内容清楚以后,才能正确的对扩展项目进行配置。
1.1 扩展程序的结构扩展程序是一种以 XPI 做为其扩展名的文件,实际上它只是一个 ZIP 格式的文件,扩展名不同而矣。所有的扩展程序,包括上一节提到的那些扩展都具有相似的内部结构。你可下载一个现成的扩展(比如:demoex),将其扩展名改名成 ZIP,并对其解压缩,你就会看以其内部的组织结构。下面对其内部组织结构进行解释说明。
1.1.1 扩展的顶级结构一个标准的扩展程序,解压缩后会生成以下几个目录:
- chrome:Mozilla 规定扩展必须具备的目录。
- components:可选目录,用于存放自定义的 XPCOM 组件文件。由于大多数的扩展根本没必要自己定义 XPCOM 组件,因此,在没有自定义 XPCOM 组件的情况下,此目录是不用存在的;
- defaults:负责存放一些默认的设置数据,其下还会包含子目录,以分别对默认数据进行存储;
另外,其下一般还会具备 2 个特殊的文件:
- install.rdf:它是一个 RDF/XML 格式的文件,用于描述当前扩展的注册信息和附加信息等。扩展在安装时,负责安装扩展的程序会自动分析此文件的信息,然后将这些信息注册到 ThinkMail 系统下。此文件必须被命名为 install.rdf,并置于扩展压缩包的顶级目录下;
- chrome.manifest:负责将扩展的各种包注册到 ThinkMail 的 chrome 系统中。
如果你在别人编写的扩展中看到了除此之外的其它目录和文件,这应该是扩展开发者的一种个人行为,而不是必须的。
1.1.2 扩展的二级结构chrome 目录下一般有以下 3 个目录。
- content:用于存储负责描述扩展界面的 XUL 文件和完成实际逻辑功能的 JS 文件;
- locale:用于存储负责本地化处理的字符串数据文件,这些文件中的本地化字符串内容会被 content 目录中的文件所引用。如果某个扩展没有对本地化进行处理,那么它是可以省略的;
- skin:用于存储负责美化界面外观的样式表文件和图片文件,这些文件中的样式和图片会被 content 目录中的文件所引用。如果扩展没有使用单独的样式表文件和图片,那么它也是可以被省略的;
下面我们再来看一下这些目录下的所存储的内容:
- content:目录下可能还会包含一个与扩展名称相同或相近的子目录,用这个子目录来存储以上提到的界面和代码文件。对于 locale 和 skin 目录,你可能也会看到再包含一个与扩展同名的子目录的规则;
- locale:目录下还会有针对不同语言的子目录,这些子目录会被起成如“en-US”,“zh-CN”这种用来区分“语言-国家/地区” 的名称。通过这种国际上标准的语言区分方式,ThinkMail 会根据其自身的语言,选择一个最合适的语言目录让 content 中的文件进行引用。这样做的结果就是,同一个扩展,在编写了不同的语言包之后,它会根据 ThinkMail的语言来进行自适应。
- skin:目录下还会有针对不同的 ThinkMail主题命名的目录,如“classic”,“modern”等。不过,一般情况下,我们只创建针对 classic 的“皮肤”。皮肤的适应方式与语言一样的,ThinkMail会根据当前的主题样式来选择一种最适合它的皮肤。
上面提到的这些目录让 ThinkMail的扩展组织结构显示得十分“冗余而复杂”。其实,这种看上去效率不高的组织方式却十分利于扩展程序的维护和降低耦合度。
1.2 install.rdf 文件- install.rdf 文件位于扩展的顶级目录下,用于描述当前扩展的作者信息,升级地址,设置入口,版本兼容信息等。在安装扩展时,ThinkMail 会分析其中的信息。它的标准格式如下:
- <?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>
- <!-- thinkmail -->
- <em:targetApplication>
- <Description>
- <em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id>
- <em:minVersion>1.0</em:minVersion>
- <em:maxVersion>19.*</em:maxVersion>
- </Description>
- </em:targetApplication>
- </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#">
- 负责引入命名空间。而位于头部分下面的 Description 标记,则是此文件的实质性描述。下面对 Description 下的子标功能给予说明。
复制代码 1.2.1 必需的标记
em:id它用来唯一的标识某个扩展,支持GUID 格式或格式为 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 工具来生成。
em:name此标记的内容会被显示在扩展管理器中。它被允许由多个单词组成,且中间可以包含空格。
em:version此标记的内容是一个版本字符串,用来表示扩展的当前版本,它会被显示在扩展管理器中。示例如下:
<em:version>2.0</em:version><em:version>1.0.2</em:version><em:version>0.4.1.2005090112</em:version>em:type此标记正是为区别安装包的类型设计的。它的内容是一个整型值,并且只允许以下几个值出现:
2 Extensions4 Themes8 Locale16 Plugin对于扩展类型的安装包,type 标记的内容显然要被指定为 2。
em:targetApplication此标记及其子标记用来指明所适用的 Mozilla 平台。由于你开发的扩展可能适用多个 Mozilla 平台,所以你需要确切地指定它所适用的平台。通过它的 em:id 子标记,可以指明所适用的平台;通过 em:minVersion,em:maxVersion 子标记可以进一步所适用平台的版本范围。需要注意的是,如果你编写的扩展在安装时弹出了禁止安装的窗口,多半是由于 em:targetApplication 书写有问题造成的,特别是 em:maxVersion 设置过低的问题。
下面将一些常用的 ThinkMail 平台对应的id 。
ThinkMail {3550f703-e582-4d05-9a08-453d09bdfdc6}
1.2.2 可选的标记em:creator此标记的内容用来指明扩展作者的名字。
em:description此标记的内容用来描述扩展的功能或特性,它会被显示在扩展管理器中。但是,建议你在书写此内容时尽量简明扼要。
em:contributor此标记的内容用来指明扩展贡献者的名字。如果你在扩展开发过程中,还得到了其他的人帮助,或者用到了其他人的源代码,请在此处写明贡献者的信息。并且,此标记允许存在多个,而 em:creator 标记只允许存在一个。
em:homepageURL此标记的内容用来指明扩展或作者的主页地址。
em:updateURL基于 ThinkMail 扩展的最大好处就是可以自动升级,此标记的内容正是用来指明版本检测时的 URL 地址。ThinkMail给扩展提供这样一种升级机制,它通过强制方式或定期检测方式请求升级描述文件,从而获取到有关扩展自身的升级信息,进而对扩展进行升级操作。
具体支持方案?
- em:optionsURL
- 此标记的内容用来指明当前扩展的选项设置入口,并且要以 chrome:// 地址方式进行指定。由于扩展的一些选项值要由用户来设置,所以扩展的作者有必要为扩展提供一个设置窗口。在扩展管理器中,通过“右键/设置”菜单项,我们 可以对不同的扩展打开不同的设置窗口,这就是此标记完成的功能。但前提是,扩展作者必须要为所开发的扩展编写设置窗口,否则此标记不是必须的。
- em:aboutURL
- 此标记的内容用来指明自定义“about(关于)”窗口的地址。同 em:optionsURL 标记一样,你必须编写并以 chrome:// 地址方式来提供此信息。
复制代码 em:iconURL此标记的内容用来指明扩展在扩展管理器中显示的图标,它同样要以 chrome:// 地址方式提供。虽然是图标,但它却经常用 PNG 格式的文件来保存。如果你没有指定自己的图标,ThinkMail 将采用预定义的图案来表示你的扩展。
em:targetPlatform主要用来限制所运行的操作系统(OS)类型。由于某些扩展有二进制兼容的限制,所以就要限制所安装到的 OS 平台。
1.3 有关 Chrome1.3.1 Chrome 的定义Chrome 是一组应用程序窗口的用户界面元素集合,它们位于窗口的内容区域之外。工具栏,菜单栏,进度条和窗口标题栏都是这些元素的例子,它们都是典型的 chrome 的一部分。
1.3.2 Chrome 提供者chrome 提供者是一种为特定的窗口类型提供 chrome 的资源供给机制。从工具栏按钮上的图片到负责描述显示在窗口上的文本,内容和窗口本身的文件都是由 chrome 提供者提供的,这些提供者在一起工作,为特定的窗口提供了一组完整的 chrome。
chrome提供者共有 3 种类型,分别是 Content,Locale 和 Skin。当安装包的 content,locale 和 skin 目录真正注册到 chrome 系统以后,它们下面的文件就成了 chrome 提供者。这些提供者就可以完成以上提到的那些功能,但前提是,必须通过一种机制来访问这些提供者,这就是 Chrome URL 要完成的功能。
1.3.3 Chrome URL像已有的其它 URL 协议一样,我们可以通过 chrome:// 这种形式的 URL 协议来访问那些 chrome 提供者(换句话说,就是已经注册在 chrome 系统下的资源)。其实,那些资源是以物理文件或目录方式被存储,通过地址映射机制以 chrome 地址来访问的。但是我们根本不用管其物理地址是什么,ThinkMail 下的扩展及其自身都是以这种映射过的地址来访问的。这样做的最大好处就是屏蔽了文件系统的多样性,为 Mozilla 的跨平台运行打下了基础。同 http 和 ftp 地址格式不一样,Mozilla 对 chrome 地址格式做了严格的规定,如下:
chrome://<package name>/<part>/<file.name><package name> 用来指明访问的扩展名称,这个名称在 chrome.manifest 文件被定义。比如,
content demoex chrome/content/它的 <package name> 是 demoex。
<part> 用来指明访问的 chrome 包类型,它分别允许 content,locale 和 skin 这 3 种固定的类型。
<file.name> 用来指明访问的文件名称,因为前面的 <package name> 和 <part> 已经对文件的路径做了限定,所以,这个文件相对来说是某个明确的文件。
当我们通过这种方式访问 content 类型的文件时,那些文件将被固定的访问;而当我们访问 locale 或 skin 类型的文件时,ThinkMail 会根据自身的语言和皮肤,选择一个最合适的文件来动态访问,这一问题在前面已经提过了。但前提是,你必须提供足够多的语言包和皮肤包供其选择。
如果 <part> 下面还有子目录,你可以像访问文件一样,对子目录下的文件进行相对访问,就像下面这样:
chrome://browser/content/bookmarks/bookmarksManager.xul另外,chrome 地址还有一种缩略格式,它只被应用在一些特殊的情况下。如下:
chrome://navigator/content/
这看上去是在访问某个目录, 实际上它是
chrome://navigator/content/navigator.xul
的缩写。ThinkMail 会根据你访问的 chrome 资源类型来自动补齐后面的文件名称,规则是
<file.name> =<package name> + (.xul|.dtd|.css)
这种形式的 chrome 地址多用于访问比较规则的包资源。
1.4 扩展安装的实现原理以上内容已经对扩展的结构做了详细的解释,但有必要解释一下 Firemail 在安装扩展时都做了些什么,这对你了解扩展是如何被注册和工作的都十分有利。在你了解了扩展的安装原理之后,手动注册扩展也不再是什么难以理解的事情了。
1.4.1 注册XPCOM组件到Gecko17在Gecko 17开始,我们需要编辑一个文件“chrome.manifests”,来显示告诉XPCOM系统要注册那个组件。下面是一个例子:
samplecomp.manifest中的内容示例如下:
binary-component HelloWorld.dll
interfaces MyComponent.xpt
1.5 chrome.manifest 文件采用纯文本格式的 chrome.manifest 使 chrome 资源的注册更加方便,并且更利用扩展的开发。
下面先让我们看一个 chrome.manifest 的示例,如下:
content necko jar:comm.jar!/content/necko/ xpcnativewrappers=yeslocale 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/ 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.xuloverlay chrome://navigator/content/pageInfo.xul chrome://pippki/content/PageInfoOverlay.xul content pippki jar:pippki.jar!/content/pippki/ xpcnativewrappers=yeslocale pippki en-US jar:en-US.jar!/locale/en-US/pippki/content global-platform jar:toolkit.jar!/content/global-platform/ platformskin global classic/1.0 jar:classic.jar!/skin/classic/global/override chrome://global/content/netError.xhtml jar:embedder.jar!/global/content/netError.xhtmlcontent inspector jar:inspector.jar!/content/inspector/ xpcnativewrappers=no所有的规则是由每个文本行头部的“指令”来标识的,这些指令的解释如下
1.5.1 注册指令content它用来注册一个 content 类型的包,如下:
content packagename uri/to/files/[flags]
packagename 是所注册的包名称,同 chrome 地址中的 <package name>含义一样。
uri/to/files/ 用来指明这个包资源的操作系统路径,它的格式比较多。对于 %profile% 下的扩展,我们知道在安装之后,ThinkMail 会将顶级结构解压缩生成二级 JAR 文件。对于这种相对路径下的 JAR 文件,我们要采用如:
jar:chrome/cview.jar!/content/cview/
这种格式的 uri 来指明。“jar:”用来指明此 uri 指向的是某个 JAR 文件的内部地址。还有一个特殊的“resource:/”用来代表 ThinkMail应用程序的根目录,例如:
jar:resource:/chrome/pipnss.jar!/content/pipnss/
locale它用来注册一个 locale 类型的包,格式如下:
locale packagename localenameuri/to/files/ [flags]
localename 用来指明所注册的语言类型,如: en-US,zh-CN 等,其它的同上。
skin它用来注册一个 skin 类型的包,格式如下:
skin packagename skinnameuri/to/files/ [flags]
skinname 用来指明所注册的皮肤包类型,如: classic/1.0,其它的同上。
overlay它用来指明 XUL 的“覆盖”规则。
overlaychrome://URI-to-overlay chrome://overlay-URI [flags]
示例:
overlaychrome://messenger/content/messenger.xul chrome://mintrayr/content/messenger/messenger.xul
style它用来指明 CSS 的“覆盖”规则,这是新加入的特性。通过这个新规则你可以为已有的界面文件引入新的样式表效果。
style chrome://URI-to-stylechrome://stylesheet-URI
示例:
style chrome://messenger/content/messenger.xulchrome://demoex/skin/masterpasswordplus.css
override在某些时候,你可能会有完全“重载”或者说是替换某个 chrome 文件的要求,这条指令就可以完成这种要求。
overridechrome://package/type/original-uri.whatever new-resolved-URI
示例:
overridechrome://mintrayr-icon/skin/appicon64.png chrome://branding/content/icon64.png
overridechrome://mintrayr/skin/common.css chrome://mintrayr/skin/common-fx4.cssapplication={ec8030f7-c20a-464f-9b0e-13a3a9e97384} appversion>=4.0b7os=winnt
manifest可以指定一个次级清单文件。方便区分功能注册或注册不同平台文件。
manifest subdirectory/foo.manifest [flags]
示例:
manifest components/helloworld.manifest
binary-componentMozilla平台用于注册二进制组件,考虑到兼容性,需要用abi标志。
格式如下:
binary-component components/mycomponent.dll [flags]
示例:
binary-component components/HelloWorld.dll
interfaces用于加载类型库文件(typelibfile),此文件是由XPIDL文件生成。
格式如下:
interfaces component/mycomponent.xpt [flags]
示例:
interfacescomponents/MyComponent.xpt
component用于告诉系统,此XPCOM组件是通过脚本JavaScript实现,此CID必须和组件中的ClassID一致。
格式如下:
component {00000000-0000-0000-0000-000000000000} components/mycomponent.js [flags]
示例:
component{ACA545EE-6023-4BF5-B6AB-352BC34EC284} helloworld.js
contract映射合约ID(一个可读字符串)到ClassID,表示一个特定的实现。合约ID(contract ID)一般代表一个组件。
格式如下:
contract @foobar/mycontract;1 {00000000-0000-0000-0000-000000000000} [flags]
示例:
contract@mozilla.richinfo.com/helloworld;1 {ACA545EE-6023-4BF5-B6AB-352BC34EC284}
categoryCategory
这里主要用于生成对应ContractIDs
的别名,对别名列表进行管理,以更好的支持对组件的访问等操作,详细参考:categorymanager格式如下:
category category entry-name value [flags]
示例:
categoryprofile-after-change helloword @mozilla.richinfo.com/helloworld;1
1.5.2 注册指令中的 Flags标识在上面的注册指令中,你会发现有个 [flags] 标识经常出现行尾 ,这个标识多用来标识特定的 chrome 包属性或限定当前行命令是否可用。下面对那些可以做为 [flags] 标识的内容一一说明。
application默认是为ThinkMail平台提供的扩展,固ID都固定为如下,或省略不写。
application=app-ID
ThinkMail的ID为{3550f703-e582-4d05-9a08-453d09bdfdc6}。
appversion它是一个条件类型的标识,用来限定注册包所应用 ThinkMail 平台的版本范围,如下:
appversion=version
appversion<version
appversion<=version
appversion>version
appversion>=version
version 是 ThinkMail 平台的版本,你可以通过 ThinkMail的 关于窗口,查看它的确切版本号。如果某个注册包的范围限定需要多个条件,你同样可以像 application 一样,将多个条件写在同一行。
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-platformjar:toolkit.jar!/toolkit/content/global-platform/ platform
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 类型的包将忽略此标识。
abiApplication Binary Interface,应用二进制接口。
如果一个组件只兼容特定的ABI,则可以用此指令来标记,如:
binary-component component/myLib.dll abi=WINNT_x86-MSVC binary-component component/myLib.so abi=Linux_x86-gcc3
1.6 chrome.manifest示例content demoex chrome/content/ locale demoex zh-CN chrome/locale/zh-CN/locale demoex en-US chrome/locale/en-US/ skin demoex classic/1.0 chrome/skin/ overlay chrome://messenger/content/messenger.xul chrome://demoex/content/messenger/messenger.xul style chrome://messenger/content/messenger.xul chrome://demoex/skin/masterpasswordplus.css # iconoverride chrome://mintrayr-icon/skin/appicon64.png chrome://branding/content/icon64.png
1.7 更多详细信息参考