В своём предыдущем топике Bexf — Фрэймворк для создания расширений я пообещал, что напишу продолжение. Моё расширение, написанное на Bexf, опубликовали и в честь этого я решил продолжить тему расширений для Оперы.
В статье: архитектура расширения, интересные моменты в процессе написания Habra Meter(далее HM) на Bexf, публикация расширения.
Архитектура
Изучая тему расширений я просмотрел с десяток исходников и в каждом, как водится, разная архитектура. Архитектура, безусловно, дело каждого, ниже я привожу мой вариант (файловая архитектура HM):
/
images/ // Все изображения, включая иконки
font.png
icon-18.png
icon-64.png
includes/ // Стандартное название. Скрипты, которые будут подключаться Оперой автоматически
vendors/ // Любые внешние библиотеки
Bexf.js
jquery-1.4.4.min.js
jquery.flot-0.6.min.js
css/ // Все CSS
style.css
js/ // все JS
options.js
popup.js
Widget.js
config.xml // Стандартное название. Обязательно. Настройки расширения
index.html // Стандартное название. Обязательно. Индексный файл
options.html // Стандартное название. Опции
popup.html // Всплывающее окно
Стандартные файлы и папки
Папка includes/ включает в себя .js/.css файлы, которые подключаются к определенным страницам Оперой и могут взаимодействовать с расширениями. Каждый скрипт представляет из себя UserScript формата Greasemonkey
// ==UserScript==
// @name --
// @namespace --
// @description --
// @author --
// @include mediafire.com*
// @include http://*.mediafire.com/*
// ==/UserScript==
// Код скрипта
HM не имеет подключаемых скриптов.config.xml — настройки расширения. На описании формата я не буду останавливаться все и так хорошо изложено тут www.opera.com/docs/apis/extensions/configxmlresourceguide
index.html — индексный файл, инициализирующий расширение (название можно сменить)
options.html — опции, при наличии его Опера активирует Настройки расширения. Опции это html страница любого содержания, как и popup.
Написание расширения
Дальше я не буду описывать каждую мелочь, только основные моменты. Код открыт, так что вы все сможете глянуть сами.
Index
Этот файл не имеет вида, так что нет смысла вставлять что-либо в body и подключать CSS. В моём случае скрипт подключат Bexf.js и Widget.js (основной скрипт HM). Widget инициализирует расширение через метод init.
init: function () {
var self = this;
this.font = new global.Image();
this.font.src = "images/font.png";
this.icon = new global.Image();
this.icon.src = "images/icon-18.png";
this.icon.onload = function () {
self.button = $.createButton(self._buttonInitOptions)
.addToPanel();
self.requestUpdates();
self.initTimer();
};
}
Метод подключает необходимые ресурсы: спрайтовый шрифт(font.png) и шаблон кнопки(icon-18.png) о них дальше. Инициализирует кнопку, сразу же её обновляет, запускает таймер обновлений.
В проекте мне понадобилось динамически менять картинку на кнопке для отображения кармы и рейтинга, притом картинка должна была создаваться на canvas. Тут была проблема: картинка максимум 18х18 и поэтому любой стандартный шрифт был бы совершенно нечитаем(впринципе то, что есть сейчас тоже особо не блещет) — создал спрайтовый шрифт 3х5: 0-9, A-F и запятая/точка все параметры букв расписал в fontDemensions. Хоть index.html не имеет вида, но зато имеет document, поэтому оказалось возможными динамически генерировать иконку через canvas и получать её данные через canvas.toDataURL(). Ниже 2 метода, отвечающие за отрисовку:
_drawText: function (ctx, x, y, text) {
text = text.split('');
for (var i = 0, c = text.length, g; i < c; i+= 1) {
g = this.fontDemensions[text[i]];
ctx.drawImage(this.font, g.x, g.y, g.w, g.h, x, y, g.w, g.h);
x += g.w;
}
},
drawIcon: function (karma, rating) {
karma += '';
rating += '';
var canvas = document.createElement('canvas');
// Create an empty canvas element
canvas.setAttribute('width', '18px');
canvas.setAttribute('height', '18px');
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d"), offset;
ctx.drawImage(this.icon, 0, 0);
// Draw karma
offset = 8 - (2 * karma.length) + (karma.indexOf('.') >= 0 ? 2 : 0) + (karma.indexOf(',') >= 0 ? 2 : 0);
this._drawText(ctx, offset, 2, karma);
// Draw rating
offset = 8 - (2 * rating.length) + (rating.indexOf('.') >= 0 ? 2 : 0) + (rating.indexOf(',') >= 0 ? 2 : 0);
this._drawText(ctx, offset, 11, rating);
return canvas.toDataURL();
}
После закрытия окна опций, обновляем кнопку (мб сменился пользователь) $.disconnect(function () {
Widget.requestUpdates.call(Widget);
Widget.initTimer.call(Widget);
});
Больше ничего интересного в Widget.js нет. Остался лишь парсинг страницы хабропользователя(кроссдоменный XHR), сбор статистики за месяц и прочие утилитарные методы.
Опции
options.html — обычный html файл любого содержания, предполагается, что он будет управлять widget.preferences (Bexf.opt) — личные данные расширения.
Файл имеет дефалтную структуру(размечены все поля). А логика его проста: при инициализации загружает значения в поля формы, при нажатии кнопки «сохранить», возвращает данные в widget.preferences.
Всплывающее окно
popup.html — обычный html файл любого содержания(но ограниченного размера) может иметь любое название, попапами управляет кнопка. Количество попапов не ограничено. Попап инициализируется каждый раз (исполняет весь код) когда его открывают. В случае HM в попапе находится график изменения параметров пользователя за последние 30 дней. График рисуется на canvas с помощью jQuery Flot (в первой версии рисовался Google Chart API, больше я к GC API ни ногой). Скрипт попапа парсит данные статистики(т.к. widget.preferences это DOMStorage объект, то его значения не могут быть объектом), сохраненные в Bexf.opt('stat') и переделывает их в формат, который понимает jQuery Flot. Больше ничего интересного.
Тестирование
Разработчики Opera подумали о нас, поэтому тестировать расширения очень просто: кидаем config.xml в браузер и расширение подключается в режиме отладки, его можно Перезагрузить(обновить код)/Отключить/Удалить в менеджере расширений Оперы как и обычное расширение. Как писал lugansk «Dragonfly пока не умеет с ними (Errors) работать» но метод opera.postError работает и его результат можно посмотреть в Error Console (Ctrl + Shift + O) Dragonfly пока не умеет работать с opera.postError из расширений.
Публикация
1. Вам необходимо протестировать расширение
2. Создать аккаунт Оперы (он один на все сервисы) — my.opera.com/community/signup
3. Придумать назание, краткое описание, подробное описание
4. Сделать минимум 1 скриншот
5. Нарисовать иконки 64x64(обязательно) 18x18(если есть кнопки)
6. Придумать версию расширения, например 1.0
7. Упаковать расширение в .oex — переименованный .zip
Как только все готово, логинимся и загружаем расширение, следуя инструкциям мастера загрузки addons.opera.com/developer/extensions/upload
Как только вы загрузили расширение оно попадет на модерацию (1-2 дня) и после этого будет доступно для скачивания. Обновленное расширение также проходит модерацию (1-2дня). И есть мнение, что про очень частых сменах версий модераторы понижают приоритет вашего расширения.
Подробнее о публикации addons.opera.com/developer/guidelines
Почитать
Общее темы по расширениям www.opera.com/addons/extensions/develop
Формат config.xml www.opera.com/docs/apis/extensions/configxmlresourceguide
Режим разработки dev.opera.com/articles/view/opera-extensions-developer-workflow
API расширений www.opera.com/docs/apis/extensions
Bexf — фрэймворк, который использовался при написании HM habrahabr.ru/blogs/opera/111461
Ещё один пример создания расширения dev.opera.com/articles/view/hands-on-building-an-opera-extension
Переводы доков Оперы и примеры от хабрапользователя SonicGD: Обмен сообщениями, Окна, UserJS, Ваше первое расширение для Opera, Кнопки, бэджи и всплывающие окна
Официальную версию(прошедшую модерацию) HM можно найти среди всех расширений оперы addons.opera.com/addons/extensions/details/habra-meter
Последнюю версию, архитектура который представлена в данной статье, можно скачать тут: browser-extensions-framework.googlecode.com/files/habra-meter-1.1.1.oex (это переименованный .zip)
Буду рад ответить на ваши вопросы. Теперь, думаю, вопрос о расширениях Оперы раскрыт на хабре полностью.
PS Не ставьте Habra Meter — постоянное наблюдений кармы приводит к психическим заболеваниям. Это расширение proof of concept моего фрэймворка Bexf