pacify.ru \ firefox-xpi

Как написать restartless-расширение для Firefox (xpi)

январь 2013 года

Расширения для firefox иногда [ошибочно] называются плагинами.
Restartless extensions также называются "bootstrapped extensions".
Для написания одного плагина "с нуля" мне потребовалось около полутора месяцев.
До этого я на JavaScript ничего серьезного не писал, и расширения браузеров - тем более.

В чём суть задачи, и в чём сложности

Наиболее авторитетные люди в этой области - Mark Finkle, Wladimir Palant, Erik Vold, ...
И эти, и другие разработчики, борются со сложностями использования Firefox extensions API.
Сложности здесь, как правило, в неустойчивости API - оно время от времени меняется, и в том, что документацию приходится собирать по всему Интернету. Грамотной документацией разработчики firefox себя не утруждают.

Задача состоит в разработке расширения, которое будет реагировать на открытие определенных HTML-страниц в браузере. Например, модифицировать HTML-код страницы сайта для более удобного использования этого сайта.
Решение опробовано на Firefox 17.x - 18.x.

Решение задачи

Конспект основных вопросов, возникающих по ходу написания xpi-расширения.

1. Как использовать глобальные атомарные переменные и табличные данные в mozilla extension?

2. Как скачивать config.xml/time.txt, и как их парсить?

3. Как оформлять HTML-код страницы?

4. Как упаковать расширение, и где его расположить?

Как использовать глобальные атомарные переменные и табличные данные в mozilla extension?

1. Не путать DOM globalStorage и mozStorage: "Storage is not the same as the DOM:Storage feature which can be used by web pages to store persistent data or the Session store API (an XPCOM storage utility for use by extensions)".

2. "DOM globalStorage" is no longer supported in Firefox 13+, см. Support Forum, thread.

3. Глобальное хранилище mozStorage создается через mozIStorageService. Эти данные сохраняются SQLite-базе, в профиле firefox. Возможно также использование баз данных MySQL, ODBC, PostgreSQL.

4. Использовать открытое соединение с базой можно использовать для выполнения SQL-операторов через executeSimpleSQL(). После записи данных в таблицу, перед их чтением, следует делать commitTransaction(). Начинать транзакцию следует с beginTransaction().

5. Записывать часть конфигурационных данных можно в глобальные переменные типа string, integer или boolean через XPCOM-интерфейс к Preferences system. К этим переменным можно установить обработчики событий (preference observers), которые доступны через интерфейсы nsIPrefService и nsIPrefBranch. См. setIntPref(). Значения этих переменных запоминаются в файле prefs.js, который находится в профиле firefox'а. По-видимому, если браузер "грохнется", актуальные значения не сохраняются в этот файл.

* При запоминании целого числа в prefs, может понадобиться функция parseInt() [см. примеры кода].

6. В общем-то, это конечно "гнилое" дело - юзать SQL (mozIStorageConnection) в аддоне. Но что не поделаешь ради решения задачи. Вставка в таблицу SQLite выполняется оператором INSERT. Удалить все записи перед этим можно оператором DELETE (см. The Truncate Optimization). В целом, по операторам SQLite, можно посмотреть Keyword Index. Проверка на совпадение с каким-либо элементом списка: statement = cx.createStatement(SELECT) + statementexecuteStep() + statement.row.NAME. О том, как использовать SELECT, см. на developer.mozilla.org.

7. См. также mozIndexedDB (webkitIndexedDB/indexedDB).

Как скачивать config.xml/time.txt, и как их парсить?

- Обработчик готовности к скачиванию XML-файла можно делать не глобальным, а в виде анонимной функции. Хотя в JavaScript и нельзя присвоить функцию по значению. [Как я слышал в интернете, PHP наоборот - присваивает по значению, и происходит лишнее копирование участков памяти.]

function loadXMLDoc(url){ req.open("GET", url, true); req.onreadystatechange = function (e) {...}; req.send(null); }

- При парсинге XML на JavaScript может возникнуть вопрос - как получить текстовое значение тэга. Разница между .text, .nodeValue и .textContent мне не понятна. Но надо использовать именно textContent, чтобы не возвращалось null/undefined/мусор:

var ts = xml.getElementsByTagName("timeout"); if (ts) {LOG("timeout: " + ts[0].textContent);}

См. также руководство на w3schools.com. Примечание: последнее время люди не парсят на "голом" javascript, а используют jQuery.

- В цикле "пробежаться" по нодам XML-дерева можно так:

var ds = xml.getElementsByTagName("domain"); if (ds) { for (var i = 0; i < ds.length; i++) { LOG("d: "+ds[i].textContent); } }

- Вычисление соответствия домена списку можно делать с помощью булевой переменной.

var x = false; // не объект new Boolean()!

Как оформлять HTML-код страницы?

- При оформлении функций на JavaScript, следует учитывать различие между 'var' и 'let'.

- Наша "модификация" страниц, при их открытии, будет заключаться в выводе в подвале страницы специального footer-а ("баннера"). Этот footer можно выводить как отдельный div с атрибутами position=fixed; bottom:0; background-color:#xxxxxx; opacity:1.0; z-index:65520;, а контент страницы - заключать в два вложенных div'а, внешний из которых - с атрибутами height: 100%; height: auto !important; min-height: 100%;

<div style="height: 100%; height: auto !important; min-height: 100%;"> <div style="font-size:24pt;margin-bottom:60px; margin-left:150px;"> 1234567890123456789012345678901234567890123456789012345678901234567890 1234567890123456789012345678901234567890123456789012345678901234567890 </div> </div> <div style="position:fixed; left:0; bottom:0; height:50px; border-top: 10px solid white; width: 100%; background-color:#9f9fdf; opacity:1.0; z-index: 65520;"> foo </div>

* Отображение контента "фрейма" можно ограничивать через css overflow.
* Все атрибуты injected-кода определять через !important, чтобы на вёрстку не влияли предустановки сайта;

Как упаковать расширение, и где его расположить?

"C:\Program Files\7-Zip\7z.exe" a -tzip ../domainck-firefox.xpi *
7z a -tzip ../domainck-firefox.xpi *

- Расширение будет обновляться из addons.mozilla.org, AMO (официального сайта для программного обеспечения Mozilla). Разместить дополнение в каталоге можно здесь


> Заглавная <