The Art of IT

2005 年 12 月 5 日

基于 Mozilla 的扩展开发 : 配置项目环境

Filed under: JavaScript, Mozilla — itart @ 10:55:59

第四章 配置项目环境

通过上一章我们已经对扩展的结构和关键的 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 示例下载

2 Comments »

  1. 不错的文章,讲得深入前出,非常明白。:)
    mozillazine.org的Knowledge中已经有类似的文章,你可以参考:
    http://kb.mozillazine.org/Extension_development

    Comment by Jason — 2005 年 12 月 12 日 @ 22:22:37

  2. 本文的内容是综合了 Mozilla 上的好几篇文章写出来的,为此我已经查阅了 kb, developer 上的相关内容。目的就是怕有所遗漏,但 Mozilla 上的有些知识经常重复,因此可能不太适合学习,并且过些内容过于细致,所以就没有过分的提及。并且,我还会继续关注权威文章的。

    另外,你可以在索引页的“后记”中下载本地版本,这样看起来就比较方便多了。然而,许多人好像都没有注意到那个后记。

    Comment by itart — 2005 年 12 月 13 日 @ 9:07:41


RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.