• tag: firefox XUL GUI
  • date: 2018-02-25

桌面应用与 web 应用

以前很多软件都是桌面(GUI)应用,如文本编辑器,音乐/视频播放器,小游戏等。 浏览器(也是桌面应用)只是用来浏览互联网上的资讯。 后来 web 页面开始提供越来越丰富的功能(借助 JavaScript/CSS/web 后台服务等), 称为浏览器端 web 应用(以下简称“web 应用”),并能够取代一些桌面应用的功能。

web 应用有许多优点: 1. 简单易用。访问 URL 即可使用,免安装,轻量级(许多功能由 web 后台服务提供)。 2. 安全。因为访问 web 应用太容易(没有安全校验或很容易绕过), 浏览器必须限制 web 应用的能力(提供通用的安全限制),避免其恶意危害用户。

当然能力与安全之间难以权衡,浏览器插件(如 flash 插件)可以扩展浏览器的能力,但又可能带来新的安全隐患。

插件可以提供更强的能力,但增加了安全校验,只允许使用通过安全校验(显式手动安装并信任)的插件。

  1. 更容易提供一致的界面与使用习惯。因为都是基于浏览器 web 页面开发。

但有些应用始终更适合使用桌面应用实现,如查看 ZIP 等打包文件,浏览器可能不具备相应功能, 上传数据到 web 后台过于复杂(代价太大)并且有数据泄露风险。

本来桌面应用开发与 web 应用开发是两套独立发展的技术栈(前者可能更大而全,后者标准统一)。 成熟的桌面应用开发技术,如 eclipse WindowBuilder, wxWidgets, Qt 等通常都是使用标记语言(XML 等)定义 GUI 布局, 使用编程语言 (C++/Java 等)访问控件,响应事件操作等。 现在 web 应用开发技术也越来越成熟和流行,并且也具备使用插件扩展功能的能力, 能不能使用一套技术栈同时适用桌面应用开发和 web 应用开发呢, 基于浏览器与 web 应用的广泛流行,即标记语言使用 HTML,编程语言使用 JavaScript。 这样可以减轻应用开发人员的学习成本,并更有利于合力把一套技术栈做到最好。

Mozilla 为开发 firefox 而开发的 XUL (XML User Interface Language, 发音是 zool, 有点像 cool) 语言和 平台 同时也是一套通用的桌面应用开发平台, 是否可以借助这套平台使用 HTML/JavaScript 开发桌面应用呢?

也可以使用浏览器插件实现桌面应用的功能,但有时(与浏览器无关的功能)作为独立应用会更完美, 使用应用不必先打开浏览器,可以按需定制布局、菜单和操作方式等,当然可以按需引入/借鉴浏览器相关功能。

早期 firefox 支持 XUL 等多种方式开发插件和扩展, firefox 57 后将只支持 WebExtensions 开发扩展, 这是一种更通用和现代的技术,更容易开发跨浏览器扩展。 浏览器扩展和插件的界限会更清晰(?),其与通用桌面应用的界限也会更清晰(?)。

MDN 官网有一篇很棒的 XUL 教程, 是我初学 XUL 的重要参考,非常有帮助,但其中一些信息可能已经过时。 本文按我自己的思路(针对桌面应用开发)重新梳理。

开发环境

XUL 是基于 XML 扩展的标记语言, XULRunner (文档已移动到 Archive,未来可能变化?) 则包含运行 XUL+XPCOM 应用的运行时 (runtime) 及相关工具, 可从 ftp.mozilla.org 下载。 下载 xulrunner sdk, 解压到安装目录即可(如 ~/opt/,解压后文件夹名为 xulrunner-sdk)。

XUL Explorer 是 XUL 的一个简单开发测试环境, 其本身也是用 XUL 开发的,代码在 svn 上,貌似已不再维护, 最新版本是 1.0a1pregithub 上有个 patch 过的版本, 看了下也只是轻微改动(?),主要是做了构建,及打包了 windows 下的 xulrunner,添加 README.txt 说明。 貌似也不再维护了,最后更新时间是 2013 年 2 月。

使用 git svn 拉取 svn 上的项目代码,由于整个 svn 仓库非常庞大,直接拉取将非常耗时。 查看 svn 仓库日志,找到第一个有效提交版本是 3748,最后一次提交版本是 20489

$ svn log --stop-on-copy https://svn.mozilla.org/projects/xul-explorer/ --limit 1
------------------------------------------------------------------------
r20489 | mfinkle@mozilla.com | 2008-12-05 22:59:43 +0800 (五, 2008-12-05) | 2 行

preprocess controllers.js
make the mac options dialog modeless
------------------------------------------------------------------------

$ svn log --stop-on-copy https://svn.mozilla.org/projects/xul-explorer/ | tail -n 15
------------------------------------------------------------------------
r3748 | mfinkle@mozilla.com | 2007-05-03 08:20:56 +0800 (四, 2007-05-03) | 1 行

test first commit
------------------------------------------------------------------------
r2932 | preed@mozilla.com | 2007-03-26 08:23:04 +0800 (一, 2007-03-26) | 2 行

Bug 374697: Add trunk and tags direcotries for xul-explorer project.

------------------------------------------------------------------------
r2925 | preed@mozilla.com | 2007-03-26 06:44:37 +0800 (一, 2007-03-26) | 2 行

Bug 374697: Add directory for xul-explorer project.

------------------------------------------------------------------------

拉取代码:

git init xul-explorer
cd xul-explorer/
git svn init -s --no-minimize-url https://svn.mozilla.org/projects/xul-explorer/
git svn fetch -r 3748:20489
git checkout -b trunk origin/trunk
git filter-branch -f --commit-filter ' GIT_AUTHOR_EMAIL="${GIT_AUTHOR_NAME}" ; GIT_COMMITTER_EMAIL="${GIT_COMMITTER_NAME}" ; git commit-tree "$@" ' HEAD
git update-ref -d refs/original/refs/heads/trunk

但看了下代码需要构建才能运行(?),也没有简单的构建说明。 为简单起见直接拉取 github 上的构建后的版本,但因为与最新 xulrunner 版本不匹配没法运行,

$ ~/opt/xulrunner-sdk/bin/xulrunner application.ini 

(process:17807): GLib-CRITICAL **: g_slice_set_config: assertion 'sys_page_size == 0' failed
Error: Platform version '41.0.2' is not compatible with
minVersion >= 1.9.0.3
maxVersion <= 1.9.0.3

简单尝试手动调大 maxVersion 也是不能正常运行的,所以用不了 XUL Explorer,放弃。

XUL 主页还看到有个 Spket IDE,可作为 eclipse 插件安装, update site: http://www.agpad.com/update/ 。 这是个商业应用,非商业用途可以免费使用,包含 XUL 和 JavaScript 编辑器等功能。

应用和 package 结构

参考:Getting started with XULRunner, 示例代码 XULRunner-Examples, 目录结构说明 Structure of an installable bundle, 一个应用的基本目录结构如下:

hello-xul/
├── defaults/
│   └── preferences/
│       └── prefs.js
├── application.ini
└── chrome.manifest

示例文件 application.ini 内容如下:

[App]
Name=hello-xul
Version=1.0
BuildID=20180225
ID=hello-xul@hanyong.github.io

[Gecko]
MinVersion=41
MaxVersion=99.*

[XRE]
EnableExtensionManager=1

使用 xulrunner 运行上述应用,xulrunner 立即退出,因为此时还是空应用:

~/opt/xulrunner-sdk/bin/xulrunner application.ini 

应用拆分为模块化的 package, 参考文档 Chrome registration 和教程 XUL Structure , package 的内容可拆分为 3 类,使用相关 Manifest instructions 定义其所在目录。

  • Content,包含窗口描述和脚本等主要文件。

    content packagename uri/to/files/ [flags]
  • Locale,区域(国际化)相关文件。

    locale packagename localename uri/to/files/ [flags]
  • Skin,皮肤(主题)相关文件。

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

应用根目录下的 chrome.manifest 是应用主 manifest 文件,可使用 manifest 指令加载 package 的 manifest 文件。

manifest subdirectory/foo.manifest [flags]

其中相对路径为相对于 manifest 文件本身的路径。

参考教程 The Chrome URL, 注册之后的文件可使用 chrome URL 访问:

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

如:chrome://messenger/content/messenger.xul

显示窗口

  • Creating a Window 知道可使用 window.open() 传入 Chrome URL 打开窗口。

    window.open("chrome://navigator/content/navigator.xul", "bmarks", "chrome,width=600,height=300");
  • 从 Getting started with XULRunner, Set up preferences 知道可配置 toolkit.defaultChromeURI 指定应用启动时打开的窗口 URL。

  • 从 XULRunner tips, Useful Chrome URLs 可看到一些系统自带的窗口 URL,如 JavaScript Console: chrome://global/content/console.xul 。

默认 prefs.js 添加如下配置尝试打开 JavaScript Console 窗口:

pref("toolkit.defaultChromeURI", "chrome://global/content/console.xul");

/* debugging prefs, disable these before you deploy your application! */
pref("browser.dom.window.dump.enabled", true);
pref("javascript.options.showInConsole", true);
pref("javascript.options.strict", true);
pref("nglayout.debug.disable_xul_cache", true);
pref("nglayout.debug.disable_xul_fastload", true);

测试运行程序,果然出现 JavaScript Console 窗口,同时还有一些报错:

  • 一条信息显示 XPCOM libdbusservice.so 已经被注册,怀疑是跟 firefox 冲突。 退出 firefox 进程重试,还是一样,这个是静态注册的(?)。

一些文档说 firefox 附带了 XUL 运行时,可使用 firefox -app application.ini 运行应用, man firefox 没看到这个选项,但测试是会加载应用的(内部隐藏选项?另外 firefox -chrome chrome://xxx 未测试成功), 但窗口未显示,看到如下报错(不知是否有关?):

JavaScript error: jar:file:///usr/lib/firefox/omni.ja!/components/nsPrompter.js, line 350: NS_ERROR_NOT_AVAILABLE: Cannot call openModalWindow on a hidden window

简单 Hello World 示例

chrome/ 目录下新建一个名为 hello-xul 的 package。 应用完整目录结构如下:

hello-xul/
├── chrome/
│   └── hello-xul/
│       ├── content/
│       │   ├── main.js
│       │   └── main.xul
│       └── chrome.manifest
├── defaults/
│   └── preferences/
│       └── prefs.js
├── application.ini
└── chrome.manifest

新建 package chrome.manifest 内容如下:

content hello-xul content/

package content/ 目录下添加 main.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="hello-xul-window"
	title="Hello XUL"
	width="800px"
	height="500px"
	>
	<script type="application/x-javascript" src="chrome://hello-xul/content/main.js"/>
	<button label="JavaScript Console" oncommand="showJsConsole();" />
</window>
  • 注意:窗口必须设置 widthheight, window.open() 参数(对应 toolkit.defaultChromeFeatures) 可覆盖 XUL 文件中的配置,但如果两处都不指定则窗口无法正常显示。
  • 相关操作逻辑在 main.js 脚本中定义(脚本是可选的,可以没有)。
  • 添加一个按钮打开 JavaScript Console 窗口。oncommand 属性名必须为全小写(看着有点难受)。

main.js 内容如下:

function showJsConsole() {
	window.open("chrome://global/content/console.xul", "bmarks", "chrome,width=600,height=300");
}

至此 package 开发完成。 主应用 chrome.manifest 添加如下内容加载 package:

manifest chrome/hello-xul/chrome.manifest

prefs.js 修改应用默认显式 package 窗口:

pref("toolkit.defaultChromeURI", "chrome://hello-xul/content/main.xul");

至此示例应用开发完成,代码见 git 仓库 hello-xul tag 10-hello

使用 xulrunner 运行,成功显示 "Hello XUL" 窗口,同时可打开 JavaScript Console 窗口,输出与之前相同。 使用 firefox -app 运行,成功显示了 "Hello XUL" 窗口,但打开 JavaScript Console 窗口失败,输出与之前类似。 firefox 关闭 "Hello XUL" 窗口后进程未正常退出(xulrunner 会退出), 经测试如果不操作打开 JavaScript Console 窗口则是可以正常退出的,与其打开失败有关。 可见 firefox 附带的 XUL 运行时是可以正常使用的,只是其可能与 xulrunner 有差异(版本更新?)。

TODO: 如何集成使用各种 XUL 平台自带的组件?