Pull to refresh

Пишем своё расширение для браузера Mozilla Firefox

Reading time 5 min
Views 61K
Итак, после обновления Firefox до 19 версии, полностью отвалилось горячо любимое расширение Яндекс.Бар. Не забуду напомнить, что Яндекс.Бар был заменен Яндекс.Элементами, которые понравились чуть больше, чем никому, поэтому и получили свои заслуженные 2 бала из 5ти.

Почему не понравились? Заменили адресную строку, стало неудобно просматривать почту, заменили закладки и убрали корректор адресной строки (под предлогом установки Punto Switcher, который может и хорош для обычного работника, но никак не для программиста. Поэтому и был удален почти сразу же, как установлен. Да и если можно было бы настроить, то всё равно желание пропало).

Немного погодя было решено создать свое подобное расширение, которое будет включать в себя такие плюшки, как просмотр почты и корректор адресной строки. Чтож, раз не ты, так кто другой?



Первым делом решено было не создавать свой велосипед и воскресить Яндекс.Бар, который не хотел работать в 19 версии браузера. В интернете подсказали, что расширение — это обычный zip архив. Открыли, посмотрели, ужаснулись и закрыли. Воскресить не удалось, даже при всем желании.

Тогда заходим в центр разработчика: builder.addons.mozilla.org. Я предпочел орудовать в веб-редакторе, хоть местами он иногда не очень гладко работал. Посмотрев на другие расширения, позаимствовав код и немного поняв весь смысл сея устройства, началось сначала всё со стенобитной машины и закончилось надфилем.

Билдер включает в себя 3 раздела: это раздел со скриптами (Lib), раздел с загружаемым контентом (картинки, стили и скрипты) и раздел с готовыми библиотеками (Libraries)

Кстати, вот документация: addons.mozilla.org/en-US/developers/docs/sdk/latest, добротно написанная.
Старт расширения начинается с загрузки файла main.js.
Вызывается функция: exports.main.

Пример файла main.js:
const tabs = require("tabs");

exports.main = function (options) {
    
    tabs.on("ready", function(tab){
      tab.attach({
        contentScript: "document.addEventListener('click', function(e) { \
				var target = e.target; \
				if(target.tagName == 'A') { \
                    var mail_to = target.href.match(/^mailto:(.*)/i); \
					if(mail_to != null) { \
						e.preventDefault(); \
						var form = document.createElement('form'); \
						form.setAttribute('action','http://mail.yandex.ru/neo2/#compose/mailto=' + mail_to[1]); \
						form.setAttribute('target','_blank'); \
						document.getElementsByTagName('body')[0].appendChild(form); \
						form.submit(); \
						form.parentNode.removeChild(form); \
					} \
				} \
			}, false);"
      });
    });
}

Что же за магия происходит в этом коде?

Первым делом подключается модуль tabs.
В данном случае он служит для того, чтобы можно было добавлять свой JavaScript код в страницу браузера.
Т.е. что у нас: при событии документа onready происходит добавление любого JavaScript кода в тело документа. В данном примере добавляется обработчик ссылок, у которых адрес начинается с mailto.

Ладно, давайте что-нибудь посложнее сделаем. Добавим-ка свою кнопку в верхний бар!
Опять же, не будем строить велосипеды, а с чистой совестью возьмем уже готовую библиотеку Toolbar Button Complete.

В ней же есть пример добавления кнопки в бар браузера. Я думаю, не стоит его сюда вываливать, т.к. там многоватенько кода.
Итак, кнопка есть, иконку поставили, всё вроде хорошо, но не очень. Как же у нас в Яндекс.Баре было? Ах да, напротив иконки еще и счетчик непрочитанных сообщений был.
Тут я разузнал несколько путей добавления счетчика:
  • универсальный, но более легкий (с помощью стилей)
  • не слишком универсальный, но не такой простой, как первый (с помощью canvas)

Второй способ, правда, нашелся методом тыка в интернет. Но я взял первый.
Нам известно, что верхний бар — это такой же набор элементов со своими классами, идентификаторами, свойствами и способами работы с ними.

Методом тыка типа:
for(var val in document.getElementById('yandex-menu')) {
   console.log(val);
}

было обнаружено, что методы в точности совпадают с теми, что мы обычно используем при работе с элементами сайта. Но замечу, что по стандарту браузер не знает, что такое ни document, ни window в расширениях (да и еще есть отличия).

Пример решения:
var wuntils = require('sdk/window/utils');
var window = wuntils.getMostRecentBrowserWindow();
var document = window.document;

Замечу, что разработка билдера не стоит на месте и если раньше способ получения активного окна был таким:
var winUtils = require("window-utils");
for (window in winUtils.windowIterator()) {
   if ("chrome://browser/content/browser.xul" != window.location)  return;
   console.log("An open window! " + window.location);
}
то сейчас всё намного легче (пример я выше привел).

Чтож, немного рассказав о особенностях, вернусь к добавлению счетчика для кнопки.
Умные люди подсказали, что по стандарту стиль поля label у кнопки равен display: none;, поэтому как-то нужно было внедрить свой css код в бар. Решение, как оказалось, не сложное (советую завернуть в файл, который будет инклюдится по мере надобности):
const { Cc, Ci } = require('chrome');
const { when: unload } = require('sdk/system/unload');
 
var ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
 
/* Helper that registers style sheets and remembers to unregister on unload */
exports.addXULStylesheet = function addXULStylesheet(url) {
    var uri = newURI(url);
	var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
	
	sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
	unload(function () {
		if (sss.sheetRegistered(uri, sss.USER_SHEET)) {
			sss.unregisterSheet(uri, sss.USER_SHEET);
		}
	});
    
    return sss;
};
 
function newURI(uriStr, base) {
	try {
		var baseURI = base ? ios.newURI(base, null, null) : null;
		return ios.newURI(uriStr, null, baseURI);
	}
	catch (e) {
		if (e.result === chrome.Cr.NS_ERROR_MALFORMED_URI) {
			throw new Error("malformed URI: " + uriStr);
		} else if (e.result === chrome.Cr.NS_ERROR_FAILURE ||
			e.result === chrome.Cr.NS_ERROR_ILLEGAL_VALUE) {
			throw new Error("invalid URI: " + uriStr);
		}
	}
	return null;
}


И в функцию exprorts.main добавляем что-то вроде (хотя добавлять можете куда угодно):
stylesheet.addXULStylesheet(data.url("stylesheet.css"));

не забыв создать в контенте файл stylesheet.css.

У меня файл содержит примерно следующее:
#yandex-mail {
    min-width: 16px;
}
#yandex-mail .toolbarbutton-text { 
    float: right !important;
    display: inline-block !important;
    font-size: 13px;
    padding-left:20px;
    background: url(data:image/png;base64,iVB.............OCYII=) no-repeat left center;
}
#yandex-mail .toolbarbutton-icon { 
    display: none;
}

Почему мы скрываем иконку и добавляем фон? Всё потому, что если этого не сделать, то блоки всегда отображаются как display: block, какие бы значения я не выставлял (кстати, может кто знает по этой теме?) Поэтому и приходится так хитрить.

Также столкнулся с вопросом загрузки контента с других сайтов и парсинг xml.
С первым быстро разобрался, далеко ходить не надо: Request
А вот со вторым пришлось повозиться.

Как мы знаем, получить dom xml документа можно с помощью нескольких функций:
  • XMLHttpRequest — отпал, т.к. выдало ошибку кроссдоменного запроса (может я не так что-то делал?)
  • DOMParser — но тут тоже пришлось повозиться

В чем собственно возня: как и с получением window, так и тут:
var {Cc, Ci} = require("chrome");
var parser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
var dom = parser.parseFromString(xmlPrepare (text), "application/xml");


Вот так создание расширений для Firefox ничем не отличается от создания плагинов для jQuery :)

Кстати, конечное творение на сей день: CustomYandexBar, пока находится на проверке. Исходники, в них много чего полезного.

Если кому-нибудь не понравится, что использую «их» картинки, бренд или т.п. — пишите.
Tags:
Hubs:
+25
Comments 48
Comments Comments 48

Articles