使用 XUL 实现浏览器扩展,第 1 部分: 使用用户界面特性创建一个 Firefox 浏览器扩展

创建超越 Web 浏览器内置功能的扩展。Mozilla 项目的 XUL 引擎是一种用户界面语言,可以用于扩展 Mozilla 浏览器或者创建独立的应用程序。XUL 可以极其轻松地构建跨平台浏览器扩展,本系列的两篇文章将演示如何实现这一过程。

Uche Ogbuji (uche@ogbuji.net), 合伙人, Fourthought, Inc.

Uche Ogbuji 的照片Uche Ogbuji 是 Zepheira, LLC 的合伙人,这家公司专门提供下一代 Web 技术解决方案。Ogbuji 是 4Suite 的首席开发人员,这是一种面向 XML、RDF 和知识管理应用程序的开放源代码平台;他也是 Versa RDF 查询语言的首席开发人员。Ogbuji 先生是一名计算机工程师和作家。他出生在尼日利亚,现在美国科罗拉多州的波德市工作和生活。您可以通过他的博客 Copia 找到有关他的更多信息。



2007 年 11 月 02 日

Mozilla 项目团队很早就决定标准化 C++ 中的核心开发,但是它需要创建一个组件系统 XPCOM(Cross Platform Component Object Model),用于在不同平台之间共享核心和扩展功能。它还开发了一种为这些组件设计用户界面的方法,其中使用了一种与平台无关的语言,称为 XUL(XML User Interface Language),读作 “zool”。在 XUL 中使用 XML 没有什么特别之处,团队本来要开发一套全新的语法 ,但是认为最好将 XUL 视为一种使用 XML 作为基本语法的编程语言。XUL 利用了一些现有 Web 技术,例如 ECMAcript(其 JavaScript 实现尤为出名)、DOM 和 CSS,因此,Web 开发人员应该可以轻松地掌握。其姊妹语言 XBL (eXtensible Bindings Language)用于控制 XUL 元素的动态行为。XUL 和 XBL 功能非常强大,足以在 Mozilla 组件的基础上开发功能完整的浏览器或者其他完整的跨平台应用程序,但是对于大多数开发人员来讲,XUL 更多地用于扩展或处理现有浏览器或其他用户程序的用户界面(XUL 针对这一用例进行了精心设计)。通常,在开发浏览器扩展时,大多数 Mozilla 浏览器的终极目标就是 Firefox,它是 Mozilla 的一个非常流行的独立浏览器。Firefox 使用 XUL 和 JavaScript 构建。它演示了 XUL 的强大功能,但也支持使用 XUL 构建的扩展。

本文囊括了使用用户界面特性创建大多数扩展所需的基本知识 — XUL、CSS、JavaScript 和 DOM。本文并不是对 XUL 的简介 — 您可以通过 参考资料 中的链接找到此类介绍。本文要求您了解该语言的基本概念并展示如何应用这些内容。您还需要了解 JavaScript、DOM 和 CSS 的基本知识。

XUL 提供了直接的方式

XUL 的优点之一就是,大部分情况下,Mozilla 浏览器处理它的方式与任何其他 Web 内容并无二致。这意味着,您可以在开发阶段快速进行测试和部署,然后考虑将开发成果正确打包为应用程序或完整的扩展。一些有用的工具可用于这类快速开发 — Ted Mielczarek 的 Extension Developer's Extension(参见 参考资料),其中包括很多有用的工具,例如允许在线编辑和预览 XUL 的 Live XUL Editor 。他还在其 Web 站点中提供了一个非常简单的 XUL 页面来执行相同的功能(但不包括应用于通过 Web 加载的 XUL 的安全限制)。大部分情况下,我倾向于对 XUL 开发周期使用更为直接的方式:使用一个良好的文本编辑器进行编辑,并且编辑器最好能够提供可以简化 XML 编写的功能,然后将 XUL 文件直接加载到 Firefox 选项卡中即可。再次进行编辑,做出调整或修补,然后为文件重新加载页面。清单 1(stats.xul)是一个简单的 XUL 示例,演示了这种语言的一些功能。该示例允许您输入一个 URL 并在一个面板中查看结果 Web 页面,而在另一个面板查看页面的统计信息。

清单 1 (stats.xul). 简单的 XUL 示例
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        id="statswindow" title="View Web page stats">
  <vbox flex="1">
    <hbox id="toppanel">
      <label value="Enter a URL:"/>
      <textbox id="url" flex="1" value="http://www.ibm.com/developerworks/web"/>
      <button label="Go!"/>
    </hbox>
    <description value="Current page:"/>
    <hbox flex="1">
      <iframe id="contentview" src="http://www.ibm.com/developerworks/web" flex="2"/>
      <vbox>
        <groupbox>
          <caption label="Stats"/>
          <grid flex="1">
            <columns>
              <column flex="1"/>
              <column flex="1"/>
            </columns>
            <rows>
              <row>
                <label value="Word count"/>
                <textbox class="count" id="wordcount" value="N/A" readonly="true"/>
              </row>
              <row>
                <label value="Character count"/>
                <textbox class="count" id="charcount" value="N/A" readonly="true"/>
              </row>
              <row>
                <label value="Element count"/>
                <textbox class="count" id="elemcount" value="N/A" readonly="true"/>
              </row>
            </rows>
          </grid>
        </groupbox>
        <spacer flex="1"/>
      </vbox>
    </hbox>
  </vbox>
</window>

chrome://global/skin/ 样式表声明使浏览器可以使用 chrome 包中的默认 global.css ,它提供了内置的 Firefox 感观,如果您安装了皮肤,则提供一些稍有不同的外观。该窗口使用一组嵌套的框来组织内容,但是我尽量避免对外观使用过多无用的内容(我将在下一节中进行介绍),而一直对 UI 元素进行逻辑分组。iframe 元素类似于 HTML 中的同名元素,它将创建呈现相关 Web 页面的内容框架,默认为 http://www.ibm.com/developerworks/weburl textbox 的值与此相同。这些值互相匹配才能使默认视图对用户有意义,而目前它们之间还没有联系(我将在下一节实现)。groupbox 可以方便地组织状态条目,每个条目都是只读的文本框,目前的默认值是一个虚拟值。清单 1 的结果是一个简单的静态窗口,由于使用了 iframe,因此仍然提供了非常不错的结果。将其保存到文件 “stats.xul” 中并加载到任何 Mozilla 浏览器窗口。可以使用 “file:” URL 或者是 Firefox 上的 “Open File” 按钮。得到的显示结果应如图 1 所示。

图 1. 在 Firefox 浏览器窗口中加载清单 1
在 Firefox 浏览器中查看清单 1

添加外观样式

您可以在 XUL 中改变 UI 元素的位置,但是大多数情况下,窗口外观的具体细节最好交于样式表处理。与对 HTML 或其他 XML 应用 CSS 一样,对 XUL 应用 CSS 允许您控制常见的属性 — 颜色、字体、空间和大小。清单 2(stats.css)显示了一些声明,用于调整清单 1 中的外观。

清单 2(stats.css). 对清单 1 应用 CSS
window {
  font-family: arial, "lucida console", sans-serif;
}

description {
  padding: 5px;
  color: blue;
}

#toppanel {
  padding: 5px;
}

#contentview {
  padding: 5px;
  border: thin solid black;
}

.count {
  width: 5em;
}

可以看到,样式表规则使用 ID、类和元素选择器匹配清单 1 的各部分内容。通过在 XUL 文件的顶部添加额外的声明便可应用这种样式表,如清单 3 所示,其中仅显示了文件进行更新(添加新的 CSS 声明)后的前四行内容。

清单 3. 使用新添的样式表声明更新清单 1 中前面几行内容
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="stats.css" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"

注意,我并没有删除对 chrome://global/skin/ 的声明。您几乎总是需要这样做,并且浏览器将把全局的皮肤与更为具体的样式表结合起来。图 2 显示将一个使用样式表更新过的 XUL 窗口加载到 Firefox 中。

图 2. Firefox 浏览器显示使用 CSS 的 XUL 窗口
Firefox 浏览器显示使用 CSS 的 XUL 窗口

通过脚本驱动应用程序工作

开发这个示例 XUL 应用程序的最后一步是使控件可以工作。可以通过将控件与行为相绑定来实现。XUL 具有一种姊妹语言 XBL,它允许您通过声明执行大部分绑定,从而减少所需的脚本数量。但是本文将使用引用 JavaScript 函数的事件属性来实现绑定。

清单 4(stats-styled-scripted.xul). 使用 CSS 和脚本声明以及脚本 hook 属性更新后的清单 1
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="stats.css" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        id="statswindow" title="View Web page stats">
  <script type="application/x-javascript" src="stats.js"/>
  <vbox flex="1">
    <hbox>
      <label value="Enter a URL:"/>
      <textbox id="url" flex="1" value="http://www.ibm.com/developerworks/web"/>
      <button label="Go!" oncommand="change_url(event)"/>
    </hbox>
    <description value="Current page:"/>
    <hbox flex="1">
      <iframe id="contentview" src="http://www.ibm.com/developerworks/web" flex="2"/>
      <vbox>
        <groupbox>
          <caption label="Stats"/>
          <grid flex="1">
            <columns>
              <column flex="1"/>
              <column flex="1"/>
            </columns>
            <rows>
              <row>
                <label value="Word count"/>
                <textbox class="count" id="wordcount" value="N/A" readonly="true"/>
              </row>
              <row>
                <label value="Character count"/>
                <textbox class="count" id="charcount" value="N/A" readonly="true"/>
              </row>
              <row>
                <label value="Element count"/>
                <textbox class="count" id="elemcount" value="N/A" readonly="true"/>
              </row>
            </rows>
          </grid>
        </groupbox>
        <spacer flex="1"/>
      </vbox>
    </hbox>
  </vbox>
</window>

注意 window 内新增的 script 元素,以及对 “Go!” 按钮添加的属性 —oncommand="change_url(event)"。清单 5 显示的是所引用的 JavaScript 文件 “stats.js”。

清单 5(stats.js). 处理 “Go!” 按钮的 JavaScript 代码
//Invoked in response to a click on the "Go!" button
function change_url(event)
{
    //Variables for convenient access to specific elements in the XUL
    var urlbox = document.getElementById("url");
    var contentview = document.getElementById("contentview");
    var wordcountbox = document.getElementById("wordcount");
    var charcountbox = document.getElementById("charcount");
    var elemcountbox = document.getElementById("elemcount");

    //Setting this attribute, happens to change the visible contents of the panel
    contentview.setAttribute("src", urlbox.value);

    //Fake up the update code for now, to allow running in Firefox
    wordcountbox.previousSibling.value += " (fake)";
    wordcountbox.value = "1000";
    charcountbox.previousSibling.value += " (fake)";
    charcountbox.value = "100";
    elemcountbox.previousSibling.value += " (fake)";
    elemcountbox.value = "10";
}

清单 4 将 change_url 函数绑定到对 “Go!” 按钮的单击事件中。该函数将接收关于单击的事件信息,不过本例中并没有使用到。全局对象 document 允许您使用 DOM 访问各种 XUL 元素,本例中使用的是 getElementById,可根据惟一 ID 查找元素。XUL 元素当然具备 XML 属性,但是也提供了对象特性,并且某些时候即使这些属性和特性具有类似的名称,但进行查询和处理时却行为各异。例如,我使用代码 urlbox.value 获得 URL 文本框中的文本,而不是使用代码 urlbox.getAttribute("value")。后者通常用于读取文本框的初始内容(“http://www.ibm.com/developerworks/web”),而不管用户登录页面后输入什么内容。类似地,如果希望通过代码更新此类文本框中的可见内容,您应该使用 urlbox.value = newvalue 而不是 urlbox.setAttribute("value", newvalue)。在可以更新函数下方的 count 文本框的代码中,您可以看到这些规则的应用。XUL 参考文档中涵盖了所有这些详细内容,因此请仔细阅读有关所使用的 XUL 元素的内容。

安全性

有关属性和特性的注意事项也适用于 contentview.setAttribute("src", urlbox.value); 行。转到新 URL 的更为正确的方法是:contentview.contentDocument.location.href = urlbox.value;。但是这里存在一个问题。Firefox 对 XUL 文档提供的严格的安全模型意味着,如果您试图访问一些敏感的对象属性,包括 contentview.contentDocument,JavaScript 将抛出一个异常。您需要使用 Error Console 窗口查看错误信息,这些错误可能含混不清并且没有具体说明,例如 “Error: uncaught exception: Permission denied to create wrapper for object of class UnnamedClass”。这是直接使用 XUL 的主要问题。在下一篇文章中,我将展示如何将本文中的粗糙内容调整为一个合适的 Firefox 扩展,它将通过实现正确的 chrome 注册绕过安全限制。目前,代码修改了未受限制的 contentview(可以提供可见结果)的 src 属性,并对 count 文本框进行一些虚构的更改,以向您显示 XUL 脚本的基本设置。


结束语

您已经了解到,XUL 可以和 Web 开发的基本构建块自然地结合。XUL 提供了丰富的 UI 元素,并且每个元素具有大量属性和特性,还提供了一个内容丰富的工具箱用来构建应用程序和扩展。将 XUL 直接加载到浏览器是一种方便的方法,可以快速原型化 XUL 应用程序并为之提供完整的构建思路,但是一旦开始使用脚本处理内容,则可能会遇到不可预知的困难。幸运的是,您可以使用 XULRunner,一种由 Mozilla 开发人员创建的独立的工具。XULRunner 可以在任何浏览器之外运行 XUL 文件,因此可以绕过严格的安全模型。无疑,它只是开发人员的工具,在某些平台上可能难以工作,但是却是开发和部署 XUL 项目的重要方法。在下一篇文章中,我将向您展示如何将本文的代码打包为一个 Firefow 扩展,并对脚本进行更新以利用更低级别的安全限制。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=266368
ArticleTitle=使用 XUL 实现浏览器扩展,第 1 部分: 使用用户界面特性创建一个 Firefox 浏览器扩展
publish-date=11022007