XUL приложения

Валентина Ванеева
"Slackware Linux в Томске"

2006-02-14

Любое приложение из семейства Mozilla может выполнять не только свои прямые функции, но и функции платформы, на основе которой вы можете писать свои приложения - расширения. Разработка таких приложений отличается прежде всего тем, что она весьма быстра.

Конечно, это касается только расширений относительно высокого уровня - тех, для создания которых достаточно функциональности платформы и XUL/XBL/JavaScript. Именно эти технологии мы и рассмотрим.

XUL

XUL - аббревиатура от XML User Interface Language, XML-язык описания пользовательского интерфейса. XUL используется во всех основных продуктах семейства Mozilla. Особенности этого языка определяют легкость работы с ним:

  • это XML, следовательно, если вы знакомы с основами XML, вы знакомы с основами XUL; XML-синтаксис понятен и прост; для этих же целей XML используется и в Qt, и в GTK, и в Avalon, что доказывает эффективность применения XML в разработке пользовательских интерфейсов;
  • в распоряжении разработчика находится большой набор уже готовых элементов управления, при этом создавать свои элементы управления очень можно как на основе стандартых, так и с нуля, все равно это будет несложно (впрочем, это уже более заслуга XBL, и об этом ниже);
  • при создании интерфейса не нужно думать о том, как приложение будет выглядеть в какой-либо ОС, об этом за вас уже подумали; при использовании стандартных элементов управления можно также не беспокоиться о том, как интерфейс будет выглядеть с разными темами;
  • правильно спроектированный интерфейс легко локализуем.

Рассмотрим пример XUL-файла:

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
<?xml-stylesheet href="chrome://helloworld/skin/hello.css" type="text/css"?>
<!DOCTYPE window SYSTEM "chrome://helloworld/locale/hello.dtd">
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&title.label;">
    <vbox align="center">
        <description flex="1" class="bound"/>
        <button label="&close.label;" oncommand="close();"/>
    </vbox>
</window>

Разберем его построчно.

  1. Стандартное объявление XML-файла.
  2. Загрузка таблицы стилей. Мы загружаем глобальную таблицу стилей, которая определяет внешний вид и поведение основных элементов управления. Это объявление не будет меняться, даже если пользователь установит собственную тему.
  3. Загрузка таблицы стилей. Эта таблица стилей может и отсутствовать, однако порой нужно как-то изменить вид или поведение элементов управления, кроме того, XBL-привязки подключаются через CSS.
  4. Указание DTD с сущностями. Сущности используются для того, чтобы можно было локализовать приложение, не трогая разметки интерфейса. В зависимости от выбранного языка пользователя будет загружаться нужный DTD-файл. Если расширение не содержит локализации на нужный язык, используется язык по умолчанию, то есть американский английский. Вот так может выглядеть наш DTD-файл:
    <!ENTITY title.label "Hello World">
    <!ENTITY close.label "Close">
  5. Корневой элемент XUL-документа с указанием пространства имен и заголовком. Наиболее часто, помимо window, используются dialog и wizard. Обратите внимание, что в заголовке вместо текста указана XML-сущность.
  6. Элемент vbox. Практически все элементы графического интерфейса основываются на модели блоков, более того большинство на самом деле являются box-ами с расширенной функциональностью. vbox выстраивает свое содержимое вертикально, hbox - горизонтально, однако это всего лишь синонимы для элемента box с разными значениями атрибута orient.
  7. Элемент description может содержать как другие элементы, так и обычный текст. Значение атрибута flex означает, что элемент может занимать больше места, чем это ему необходимо - растягиваться до внутренних границ родительского элемента. Атрибут class имеет тот же смысл, что и в HTML, в данном случае мы сами придумали название класса (это нам понадобится позже).
  8. Элемент, означающий простую кнопку. Кнопок на самом деле великое множество - переключатели, флажки и т.д.; кнопки могут содержать изображения. Все, как в HTML! Атрибут label содержит надпись на кнопке, а атрибут oncommand задает обработчик события command - нажатия на кнопку. Так как это стандартная функция, нам не нужно было импортировать файл с ее телом.

Такой файл представляет собой простейшее самостоятельно окно.

Снимок простейшего окна
Рис. 1. Снимок простейшего окна

Однако часто интерфейс расширения должен встраиваться в интерфейс основного приложения, например, при добавлении кнопки на панель инструментов, меню или пунктов меню и т. д. В таких случаях пользуются оверлеями. Оверлеи - это также XUL-файлы, но они содержат только те части интерфейса, которые должны изменить интерфейс основного приложения, при этом элементы интерфейса могут добавляться, изменяться и удаляться. Пример:

<?xml version="1.0"?>
<!DOCTYPE overlay SYSTEM "chrome://helloworld/locale/overlay.dtd">
<overlay id="helloworld-overlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <script src="overlay.js"/>
    <menupopup id="menu_HelpPopup">
        <menuitem id="helloworld-hello" label="&helloworld;"
        oncommand="HelloWorld.onMenuItemCommand(event);"/>
    </menupopup>
</overlay>

Разберем его построчно.

  1. Уже знакомое нам объявление DTD-файла для разрешения сущностей.
  2. Начало фрагмента, содержащего изменения интерфейса с указанием пространства имен и идентификатор оверлея.
  3. Загрузка сценария с обработчиком (и вообще с нужными нам функциями).
  4. Следующие две строчки означают, что изменению подвергнется меню с идентификатором menu_HelpPopup: мы добавим еще один пункт меню, при выборе которого будет вызван указанный обработчик.

Добавление пункта меню с помощью оверлея
Рис. 2. Добавление пункта меню с помощью оверлея

Для того чтобы оверлеи действительно заработали, их нужно зарегистрировать в chrome [1].

JavaScript

XUL определяет внешний вид приложения, но оно должно еще и что-то делать. Логика приложений/расширений пишется на JavaScript (хотя на самом деле можно пользоваться не только им, но интерпретатор JavaScript является частью Gecko - ядра, и это проще всего). Это достаточно простой и популярный язык, а веб-разработчикам его и вовсе не нужно будет осваивать специально. Документация по JavaScript доступна как на сайте Mozilla, так и на многих других сайтах. Это стандартизованный язык (см. набор стандартов ECMAScript).

Вот как может выглядеть файл, импортируемый нами в оверлее выше:

var HelloWorld = {
    onLoad: function() {
        // initialization code
        this.initialized = true;
    },
    onMenuItemCommand: function() {
        window.open("chrome://helloworld/content/hello.xul", "", "chrome");
    }
};
window.addEventListener("load", function(e) { HelloWorld.onLoad(e); }, false);
  1. onLoad: мы определяем объект HelloWorld, при загрузке которого выполняется инициализация.
  2. onMenuItemCommand: обработчик события выбора пункта меню открывает новое окно (в нашем случае это будет самое первое окно с кнопкой).
  3. Подписываемся на оповещение о загрузке окна для выполнения кода инициализации.

При написании сценариев для расширений нужно помнить, что имена функций должны быть уникальными, либо нужно прямо указывать, для какого именно объекта вызывается функция. Причина в том, как определяется область видимости.

Во-первых, есть традицонное определение области видимости переменных - локальные и глобальные. Во-вторых, простой набор инструкций, заключенных в фигурные скобки ({}) не создает отдельной области видимости. В-третьих, допустимо использование переменных, объявленных хоть где-нибудь в данном блоке кода, например, следующий код не вызовет исключения:

function f() {
    alert(x); // значение "undefined"
    var x = 1;
    alert(x); // значение "1"
}

Наконец, есть понятие цепочек областей видимости. Цепочка представляет собой упорядоченный список объектов. При вызове функции интерпретатор просматривает все объекты по порядку вплоть до глобального объекта (например, window) и как только находит подходящую функцию, вызывает ее. Таким образом можно пользоваться функциями, определенными в других объектах.

Кроме того, в JavaScript поддерживаются замыкания (closures), а в конструкторах для создания наследования используются прототипы, а не классы. С недавних пор в сценариях можно использовать текст в UTF-8.

XBL

XUL позволяет как определять новые элементы, отсутствующие в стандартном наборе (см. архив toolkit.jar в каталоге chrome/), так и переопределять внешний вид, содержимое и поведение стандартных. Для этого используется XBL, XML Binding Language.

Вернемся к самом первому XUL-файлу. Напомню, что там были такие строчки:

<?xml-stylesheet href="chrome://helloworld/skin/hello.css" type="text/css"?>
...
<description flex="1" class="bound"/>

Обе они нужны нам в данном случае только для того, чтобы переопределить элемент description, то есть создать новую XBL-привязку.

Обычно элемент description используется примерно так:

<description>
    Здесь может быть текст.
    <box>Или другие элементы.</box>
</description>

Мы создадим новый класс (bound) таких элементов, который будет содержать изображение. У нас может получиться такой XBL-файл:

<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <binding id="bound">
        <content>
            <xul:hbox>
                <xul:image
                src="chrome://helloworld/skin/penguin_group.gif"/>
            </xul:hbox>
        </content>
    </binding>
</bindings>

Разберем его.

  1. Корневой элемент у этого файла - bindings. Один файл может содержать несколько привязок (binding).
  2. У каждой привзяки должен быть уникальный в этом файле идентификатор.
  3. Элемент content определяет, что на самом деле будет показываться. В нашем случае это изображение.

Подключаются привязки в CSS-файлах. Нам нужно будет написать:

description.bound {
    -moz-binding: url('chrome://helloworld/skin/hello.xml#bound');
}

Кроме того, у элемента binding могут быть следующие потомки:

  • resources - для подключения дополнительных стилей и изображений;
  • implementation - для определения методов, которые можно будет вызывать для создаваемого (изменяемого) элемента управления, и свойств;
  • handlers - для определения обработчиков событий.

Также у binding есть примечательные атрибуты:

  • extends указывает, что привязка расширяет функциональность другого элемента, который используется как базовый;
  • inherits определяет, будет ли привязка использовать стили, связанные с переопределяемым элементом изначально.

Сборка и установка расширений

Теперь нужно собрать расширение в единое целое. Для начала расположим наши файлы в правильном порядке:

chrome/
	helloworld.jar <- ZIP-архив
		content/
			hello.xul
		    	overlay.js
			overlay.xul
		locale/
			en-US/
				hello.dtd
				overlay.dtd
		skin/
			hello.css
		hello.xml
		penguin_group.gif
chome.manifest
install.rdf

Каталоги content, locale и skin могут называться и по-другому, но принято называть их именно так. Они содержат неизменную часть приложения, файлы локализации и файлы внешнего вида соответственно. Расширение также может содержать только content - просто расширять функциональность, - только locale - пакет локализации - или только skin - тема. helloworld.jar - обычный ZIP-архив.

Из chrome.manifest основное приложение сможет узнать, какие именно части содержит регистрируемое расширение. Нам нужно зарегистрировать все, включая один оверлей:

content  helloworld  jar:chrome/helloworld.jar!/content/
locale   helloworld  en-US        jar:chrome/helloworld.jar!/locale/en-US/
skin     helloworld  classic/1.0  jar:chrome/helloworld.jar!/skin/
overlay  chrome://browser/content/browser.xul  chrome://helloworld/content/overlay.xul

Файл 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>helloworld@mozilla.doslash.org</em:id>
        <em:name>Hello World (Firefox 1.5 edition)</em:name>
        <em:version>1.0.1</em:version>
        <em:description>Modified classic first extension from MozillaZine KB</em:description>
        <em:creator>Nickolay Ponomarev</em:creator>
        <em:contributor>Put your name here</em:contributor>
        <em:targetApplication>
        <!-- Firefox -->
            <Description>
                <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
                <em:minVersion>1.5</em:minVersion>
                <em:maxVersion>1.5.0.*</em:maxVersion>
            </Description>
        </em:targetApplication>
    </Description>
</RDF>

Теперь все содержимое каталога кладем в ZIP-архив с именем helloworld.xpi. Все!

Менеджер расширений с новым расширением
Рис. 3. Менеджер расширений с новым расширением

О чем не было сказано (самое интересное)

XulRunner

На самом деле на XUL можно писать не только расширения к Firefox, Thunderbird, Seamonkey и т. д., на XUL можно писать и полноценные приложения. С 2005 г. разработчики mozilla.org предлагают пользоваться также Xulrunner, который позволяет это делать. При этом:

  • такое приложение запускается в отдельном процессе;
  • совершенно не зависит от Firefox/Thunderbird/..., поэтому они могут быть даже не установлены.

Есть и минусы, например, вам придется включать в свой дистрибутив сам Xulrunner, а значит, объем дистрибутива станет примерно на 13 Мб больше.

XUL и веб

XUL-файлы вместе с необходимыми сценариями и стилями можно также загружать прямо из веба. Так можно получить полноценный интерфейс, уже знакомый и понятный пользователю. К сожалению, эта функциональность будет доступна только пользователям браузеров семейства Mozilla, что не всегда приемлемо. Зато эти браузеры сделают за вас всю самую скучную работу.

Также существует проект по интеграции XUL и JSF.

RDF (и datasources вообще) и шаблоны

При создании некоторых приложений сложно заранее узнать, какое именно количество данных придется отображать (например, вы пишете клиент к базе данных). В этом случае можно использовать XUL-шаблоны: вы просто указываете, какие данные будут получены, как их отображать и откуда из брать. В будущем, возможно, шаблоны будут заменены на XSLT-преобразования. RDF (Resource Description Framework) - XML-формат, который используется для хранения различных данных. К сожалению, на данный момент такие источники данных - единственные, которые разработчики могут использовать сразу же. Однако уже сейчас доступно расширение mozStorage для работы с SQLite.

О защите

Расширения можно подписывать. В Firefox (начиная с версии 1.5) можно осуществлять безопасный доступ из XUL-приложений к незащищенному содержимому - веб-страницам, тогда можно быть уверенным, что нужные функции не будут замещены сценариями с веб-страницы.

XPCOM

XPCOM - Cross-platform Component Object Model. Как только вам перестанет хватать стандартной функциональности платформы (скажем, вы хотите реализовать поддержку нового протокола), вам придется искать или писать самостоятельно нужный XPCOM-компонент. Это уже более низкий уровень разработки, который потребует от вас знания C++, но и здесь нет ничего сложного, тем более что на сайте mozilla.org достаточно документации. Создав такой компонент, вы можете легко получить нужную функциональность в обычном JavaScript-сценарии.

Инструменты разработки

  • XulDev, среда разработки
  • EclipseXUL, модуль для среды разработки Eclipse
  • XulUnit, платформа для тестирования XUL-приложений
  • Venkman, отладчик JavaScript
  • DOM Inspector, инструмент для просмотра дерева DOM любых веб-документов или XUL-приложений
  • Поддерживаемые платформы

    • Linux
    • Windows
    • MacOS X
    • FreeBSD
    • Solaris
    • OS/2
    • Любая другая, на которой вы сможете собрать Mozilla

    Да, если вы вдруг думаете, что на XUL можно писать только расширения, вы ошибаетесь. На XUL уже написаны: несколько браузеров, почтовый клиент, HTML-редактор, календарь, IRC-клиент, Jabber-клиент, отладчик JavaScript и полноценная среда разработки - Komodo.

    Дополнительная информация

    Англоязычные ресурсы:

    Русскоязычные ресурсы:

    Файлы, использующиеся в тексте:

    Примечания

    [1] Возможное происхождение термина chrome.

    From Jargon File (4.3.1, 29 Jun 2001) [jargon]:
    
      chrome n. [from automotive slang via wargaming] Showy features added to
         attract users but contributing little or nothing to the power of a
         system. "The 3D icons in Motif are just chrome, but they certainly are
         _pretty_ chrome!" Distinguished from {bells and whistles} by the fact
         that the latter are usually added to gratify developers' own desires for
         featurefulness. Often used as a term of contempt.