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

в 13:15, , рубрики: builder, Firefox, javascript, mozilla, метки: , , ,

Итак, после обновления 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, пока находится на проверке. Исходники, в них много чего полезного.

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

Автор: lampa

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js