понедельник, 19 сентября 2011 г.

Немного о Chrome Extensions

Не так давно, когда я ещё не знал что есть кнопочка "Сохранить" в Клавогонках при создании игры, меня очень парило постоянное ручное выставление режима в "Одиночный, 5 секунд". Подумалось тогда, что можно написать расширение для хрома, которое будет делать это за меня.
Собственно, на это и был потрачен вчерашний вечер.


Итак, писать расширения для хрома, как оказалась - довольно тривиальная задача. Если есть позывы к веб-разработке, то это не составляет труда. Процесс доступно описан здесь.

Ваше расширение характеризуется файлом manifest.json, в котором содержатся настройки расширения, всяческие описания, версии, требуемые привилегии, список файлов и так далее.

Изначально я собирался сделать попап окошечко, в котором можно выставлять дефолтные настройки, которые автоматом будут применены при загрузке страницы "Своя игра" на клавогонках.

Попап, который позволяет выбирать тип игры и таймаут, хранящий свои настройки в HTML5 localStorage, сваялся в течение 15 минут. Все было отлично, кроме того, что он никак не влиял на страницу. Покопавшись чуть далее я выстроил для себя следующую архитектуру расширений (посколько всё это я изучал очень поверхностно, прошу относится к моим словам с долей сомнения):

1. Есть фоновая страница (background.html), которая может стать центром функционала приложения, так как все остальные элементы имеют доступ к ней.
2. Попап. Тут, вроде, все и так понятно, ничего особенного.
3. Контент скрипты - скрипты, которые встраиваются в текущую страницу.
4. Остальные страницы расширения: настройки и т. п.

Ага! Чтобы модифицировать страницу клавогонок, необходим контент скрипт. Хранить настройки я решил в фоновой странице, к которой буду обращаться из попапа и из контент скрипта.

Я это быстренько накидал и потратил ещё примерно час на то, чтобы поднять обмен сообщениями между различными элементами расширения, для синхронизации настроек. На тот момент мне было неизвестно, что localStorage один и тот же для всех элементов расширения (что, в общем-то, я так и не проверил).

К сожалению, обмен сообщениями у меня так и не удался, но мне уже пришла в голову следующая идея — зачем задавать дефолтные настройки, если с тем же успехом можно просто хранить последние использовавшиеся? Это немного сокращает функционал расширения, но зато соответствует принципу KISS :-P

Я забил на попап и фоновую страницу, сосредоточил всю логику в контент скрипте. Сам скрипт прост до безобразия:


jQuery.noConflict();


(function ($) {
$(function () {
var type = localStorage.type || $('type').value;
var timeout = localStorage.timeout || $('timeout').value;

$('#type').change(function (){
localStorage.type = $(this).val();
}).val(localStorage.type).change();

  $('#timeout').change(function (){
localStorage.timeout = $(this).val();
}).val(localStorage.timeout).change();
});
})(jQuery);


Несмотря на то, что это окончательный вариант, предыдущие отличались несущественно — в основном я пытался решить проблему, о которой сейчас скажу.

Те, кто бывали на клавогонках и создавали собственные игры, знают, что при смене типа игры немного меняется и интерфейс. Например, таймаут 5 секунд доступен только в "Одиночном заезде". Поэтому, мне было необходимо вызывать родное событие onchange() элемента type. Как только я это сделал, скрипт стал неотвратимо падать в одном и том же месте. Он сообщал, что метод update(), который вызывается уже в родном скрипте по событию, неопределен. Тем не менее, дебаггер без проблем до него доходил, вызывая все методы на этом же элементе вполне себе успешно.

Особенно меня смутило то, что когда в брейкпоинте я руками проверял и доступность и вызываемость этого метода, хром увещевал что да, вот метод и вот уже его результат, но упрямо продолжал падать прямо на следующей строке. Обманщик!

Обычно это свидетельствует о том, что разработчик забыл подключить библиотеку, где объявлена данная функция. Но нет, вот же:
<script src="/js/prototype-1.6.0.3.js" type="text/javascript"></script>
Всё включено! Да и если бы она была не подключена, упало бы намного раньше, ведь это не первое что выполняется с использованием прототайпа.

Чуть позже я, внимательно почитав про контент скрипты, узнал, что они выполняются в собственной песочнице и не имеют доступа к скриптам из родной страницы. Но вот же, я получаю доступ, вызываю функции, всё не работает работает! Как это так?

Я уже успел подумать и на неправильный контекст this и на то, что jquery'вский доллар перекрывает доллар из прототайпа. Но нет, дебаггер же вновь уверил меня в том, что всё в полном порядке: this и там, и там есть DOMWindow родительского окна, а доллар - функция из библиотеки прототайпа, а не jquery. Я даже использовал noConflict() на всякий случай, как видно выше.

Ничего не помогало. В тот момент правильным решением выглядело даже переписывание родного скрипта на jquery и запуск его из контент-скрипта. Но совесть не позволяет делать настолько некрасиво (сознаюсь, я чуть-чуть попробовал =)). Немного лучше показалось простое подключение прототайпа той же версии в контент скрипт и копипасты исходного кода без переписывания.

На самом деле, примерно в этот момент до меня и дошло. Ведь песочница! Мой скрипт запускал родной успешно в своём контексте, где прототайп взаправду не подключен, доллар там был всё-таки jquery'вский, просто он не возвращал таких результатов, чтобы что-то упало, а дебаггер, видимо, просто обо всём этом не догадывался, и показывал мне всё в свете того, как будто я запускался из контекста страницы, где, естественно, есть прототайп и нет jquery.

Тут же я удалил всё лишнее, и вместе с jquery в контент скрипт подключил prototype с клавогонок.

Вуаля.