
Статья ориентирована на разработчиков программного обеспечения для Linux, а также для желающих начать разработку расширений для Firefox. Пользователям же предлагается пройти по ссылке за готовым расширением.
Итак, что нам понадобится:
- Расширение Restartless Restart. В процессе разработки понадобится часто перезагружать браузер для тестирования расширения
- Архиватор, позволяющий редактировать файлы прямо в архиве, без ручной перепаковки. С этим справляется стандартный архиватор в Ubuntu File-Roller
- Текстовый редактор
Собственно, для такого простого расширения хватит. Возможно вам также пригодится расширение Extension Developer.
Теперь нужно создать структуру нашего расширения:
unityfox -- chrome ---- content ------main.xul -- chrome.manifest -- install.rdf
Наше расширение не использует локализацию или настройки, поэтому стандартные директории locale и preferences нам не пригодятся.
Начинается всё с файла install.rdf. В нём принято описывать краткую информацию о расширении: название, описание, версию, авторов, уникальный идентификатор расширения и набор поддерживаемых приложений (одно и то же расширение может работать, например, в Seamonkey и Firefox):
<?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>unityfox@mozilla.org</em:id>
<em:version>0.1.3</em:version>
<em:type>2</em:type>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>4.0</em:minVersion>
<em:maxVersion>6.0a1</em:maxVersion>
</Description>
</em:targetApplication>
<em:name>unityfox</em:name>
<em:description>Integration with Ubuntu Unity panel</em:description>
<em:creator>Lockal</em:creator>
</Description>
</RDF>
Структура файла должна быть понятна без объяснений. Маленькое уточнение: <em:type>2</em:type> показывает, что этот файл является именно расширением, а не темой оформления, например. {ec8030f7-c20a-464f-9b0e-13a3a9e97384} — код браузера Firefox. Нижний предел версии 4.0 обусловлен тем, что мы будем использовать функции js-ctypes для обмена сообщениями с библиотекой Unity.
Завершив написание install.rdf, можно приступить непосредственно к созданию рабочего кода. Для того, чтобы при создании окна выполнялся наш javascript-код из файла main.xul, нужно описать в файле chrome.manifest процедуру наложения (overlay):
Это говорит браузеру загружать наш файл main.xul вместе с интерфейсом браузера browser.xul.content unityfox chrome/content/ overlay chrome://browser/content/browser.xul chrome://unityfox/content/main.xul
Тут стоит остановиться и вспомнить задачу, которую мы решаем. Наша цель — отобразить полосу загрузки и число активных загрузок в панели Unity. К счастью, Unity предоставляет специальный API для этого. Из этого API нам понадобятся функции:
- UnityLauncherEntry *unity_launcher_entry_get_for_desktop_id (char *id);
- void unity_launcher_entry_set_count (UnityLauncherEntry *self, gint64 count);
- void unity_launcher_entry_set_count_visible (UnityLauncherEntry *self, gboolean visible);
- void unity_launcher_entry_set_progress (UnityLauncherEntry *self, gdouble progress);
- void unity_launcher_entry_set_progress_visible (UnityLauncherEntry *self, gboolean visible);
<?xml version="1.0"?>
<overlay id="unityfox"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="text/javascript">
// В расширениях Firefox принято осторожно относиться к глобальной области в xul
if ("undefined" == typeof(unityProgress)) {
var unityProgress = {
setup: function() {
// Загрузим модуль ctypes для обращения к системным библиотекам
Components.utils.import("resource://gre/modules/ctypes.jsm");
// Выполним загрузку библиотеки libunity.so.4
// Нам не нужно, чтобы расширение генерировало ошибки при отсутствии libunity
try {
this.libunity = ctypes.open("libunity.so.4");
} catch(err) { return; }
// Опишем нужные функции libunity
this.getEntry = this.libunity.declare("unity_launcher_entry_get_for_desktop_id",
ctypes.default_abi,
ctypes.voidptr_t,
ctypes.char.ptr);
this.setProgress = this.libunity.declare("unity_launcher_entry_set_progress",
ctypes.default_abi,
ctypes.void_t,
ctypes.voidptr_t,
ctypes.double);
this.setVisibilityP = this.libunity.declare("unity_launcher_entry_set_progress_visible",
ctypes.default_abi,
ctypes.void_t,
ctypes.voidptr_t,
ctypes.int);
this.setVisibilityN = this.libunity.declare("unity_launcher_entry_set_count_visible",
ctypes.default_abi,
ctypes.void_t,
ctypes.voidptr_t,
ctypes.int);
this.setCount = this.libunity.declare("unity_launcher_entry_set_count",
ctypes.default_abi,
ctypes.void_t,
ctypes.voidptr_t,
ctypes.long);
// все основные функции библиотеки libunity работают с UnityLauncherEntry*
this.entry = this.getEntry("firefox.desktop");
this.Cc = Components.classes;
this.Ci = Components.interfaces;
this.IDLM = this.Ci.nsIDownloadManager;
// Обновление панели будет происходить с помощью сервиса download-manager
// Интерфейсы всех встроенных сервисов описаны на https://developer.mozilla.org
this.dlMgr = this.Cc["@mozilla.org/download-manager;1"].getService(this.IDLM);
this.dlMgr.addListener(this);
},
// Функция update вызывается на каждом событии от download-manager
update: function() {
var total = 0, cur = 0, count = 0;
var dls = this.dlMgr.activeDownloads;
while (dls.hasMoreElements()) {
var dl = dls.getNext().QueryInterface(this.Ci.nsIDownload);
// Пропускаем неактивные загрузки и загрузки, время окончания которых неизвестно
if (dl.state != this.IDLM.DOWNLOAD_DOWNLOADING || dl.percentComplete == -1)
continue;
// Считаем общий размер, загруженный размер и число загрузок
total += dl.size;
cur += dl.amountTransferred;
count++;
}
if (total == 0) {
// Если нет загрузок, скрываем счётчик и полосу на панели
this.setVisibilityP(this.entry, 0);
this.setVisibilityN(this.entry, 0);
} else {
// Показываем ход загрузки (от 0 до 1) и число загрузок
this.setProgress(this.entry, cur / total);
this.setCount(this.entry, count);
this.setVisibilityP(this.entry, 1);
this.setVisibilityN(this.entry, 1);
}
},
// Этот объект является nsIDownloadProgressListener по совместительству.
// nsIDownloadManager будет отсылать сообщения нижеописанным функциям
onDownloadStateChange: function() { this.update() },
onStateChange: function() { this.update() },
onProgressChange: function() { this.update() },
onSecurityChange: function() { this.update() }
};
}
// Наконец, инициализируем unityProgress через т. н. анонимное пространство имён
(function() {this.setup();}).apply(unityProgress);
</script>
</overlay>
Расширение готово. Теперь файлы из директории unityfox можно упаковать в zip-архив, изменить расширение файла на .xpi и установить перетаскиванием на окно браузера.
Готовое расширение и список ингредиентов можно найти по адресу addons.mozilla.org/ru/firefox/addon/unityfox. Приятного аппетита!
Текст этой статьи распространяется на условиях лицензии CC BY-SA 3.0