Особенности разработки расширений Google Chrome

Доброго времени суток.
Изначально этот пост планировался как очередной tutorial для начинающих. Но поверхностный поиск показал, что в этом смысла нет. Потому решил просто поделиться с начинающими и не очень опытом в некоторых вопросах. Это будет некий псевдо-tutorial с решениями ответами на не очевидные (для некоторых) вопросы.
Идеи в посте не претендуют на особую уникальность или новизну, но многие вопросы мне вначале самому было решить непросто. Надеюсь, кому-то мои знания принесут пользу. Всех заинтересовавшихся прошу под кат.

Intro


Я, будучи web-разработчиком, уже давно в качестве хобби увлекся расширениями. Чисто из интереса, и для решения некоторых задач. Тем более, под Chrome, наверное, их разрабатывать проще всего. Уже «заимел» достаточно опыта, потому частью его решил поделиться с вами в этом посте.

Ответы на частые вопросы


Расширения Chrome представляют собой набор обычных HTML-страниц и некоторых дополнительных файлов. Даже в popup-окне предоставляется полноценная среда HTML. Потому вопрос о возможности использования CSS и каких либо JS-фреймворков отпадает сам собой. Можно. Главное не переусердствовать, ибо на производительность это повлияет аналогичным образом (особенно, на загрузку popup).
В конфигурационных файлах используется полноценный валидный JSON. Будьте внимательны, например, с trailing commas. Ибо на эти самые запятые браузер ругается ещё на этапе сборки расширения, и отладочная информация дается криво. Я люблю писать в свободном стиле, особенно люблю эти самые запятые в конце конструкций, потому не раз уже мучился с устранением подобных ошибок.
AJAX-запросы из-под расширения кросс-доменны сами по себе без никаких танцев с бубном (да, многие знакомые разработчики, далекие от расширений, реально удивлялись и спрашивали по поводу кросс-доменности). Достаточно только прописать соответствующие разрешения в манифесте.
Кстати, что касается этих же разрешений. Как показывает практика, в зависимости от сложности расширения достаточно либо одного-двух адресов, либо лучше сразу давать доступ ко всем адресам. Особенно в расчете на последующее развитие.
Также я лично много сталкивался с вопросами работоспособности расширений под разными браузерами на движке Chromium. После перехода на манифест версии 2 сразу отпадают все браузеры ниже 18-й версии. Начиная с 18 и дальше, если не использовать какие-то особые API или HTML5, работоспособность можно почти гарантировать в любом варианте. Требования к брендовым выпускам (например, Yandex Интернет) такие же. Тот же Mail.ru не работает (до сих пор толкают 17-ю версию).

Мультиязычность


Не знаю, как обстоят дела в других браузерах, но в Chrome возможность поддержки нескольких языков заложена в сами расширения, без необходимости как-то явно указывать и обрабатывать текущую локаль. С одной стороны это удобно, с другой – для организации ручного переключения языка нужно изобретать свой велосипед.
Предложенное Google решение пусть не идеальное и не совсем удобное, но вполне работоспособное.
Для поддержки мультиязычности в папке с исходниками расширения необходимо создать папку _locales (да, именно в таком виде, с символом подчёркивания). Внутри на каждый язык создается папка, в которую добавляется единственный файл messages.json.
Содержимое файла имеет следующий вид:

"title": {
	"message":"blah-blah-blah"
},
...

Заголовки выбираются свободно, главное не повторяться. Плюс, существуют пару зарезервированных заголовков. Можно объявить сообщения «appName» и «appDesc», которые потом будут так же автоматически подставляться в зависимости от локали в название и описание расширения в браузере. Для этого в манифесте нужно указать следующие параметры:

	"name": "__MSG_appName__",
	"description": "__MSG_appDesc__",

Также, в случае введения в расширение мультиязычности, в манифесте должен быть объявлен параметр «default_locale».
Для получения значения конкретной строки используется функция
chrome.i18n.getMessage('');. Для удобства её можно обернуть в две следующие функции.

function _writeMessage(a) { document.write(_getMessage(a)) } function _getMessage(a) { return (chrome.i18n.getMessage(a)) }

Думаю, нет необходимости как-то подробно их объяснять. В теле HTML в нужных местах ставить, например, такие вызовы:
Правда, после недавних изменений в политике безопасности расширений использование этих функций, особенно первой, стало невозможным. В процессе переделки старых расширений был изобретен следующий велосипед. Во всех местах, где надо было поставить мультиязычную строку, нужным элементам был добавлен некий класс и дополнительный атрибут, указывающий ID нужной строки. При инициализации страницы достаточно получить все такие элементы и для каждого проставить нужное содержимое. Например, так:

var m = document.getElementsByClassName('mopts');
for (var i = 0, l = m.length; i < l; i++){
	m[i].innerHTML = _getMessage(m[i].getAttribute('mid'));
}

Конечно, такой вариант можно оптимизировать, расширить на проставку innerHTML и title, использовав, например, querySelector (либо тот же jQuery), но в моем случае этого предложенного варианта было достаточно.

Контент-скрипты


Они же user-scripts. Для тех, кто впервые слышит (да ну?) – это пользовательские CSS-стили или JS-скрипты, которые после загрузки вставляются в тело документа для улучшений или предоставления каких-то функций. Расширения тоже могут добавлять на страницы свои скрипты.
Расширения также могут добавлять в открываемые страницы свои скрипты. При этом есть возможности по выбору момента внедрения(перед загрузкой документа, после) и выборочного внедрения в страницы (фильтр по URL).
Для добавления своих скриптов необходимо в манифесте объявить секцию "content_scripts", и для каждого скрипта добавить параметры внедрения в страницы. Детальнее об этом можно узнать в документации.
Есть некоторые нюансы выполнения JS-контент-скриптов. Они выполняются в окне документа, но в своем изолированном контексте. При этом есть полный доступ к DOM и к объекту window (но не к переменным и функциям страницы). Также в контексте скриптов доступна часть API chrome.extension, что позволяет полностью взаимодействовать с фоновой страницей расширения.
Что касается выполнения скриптов непосредственно в контексте страницы – никто не запрещает контент-скрипту создать объект script, вписать туда нужное содержимое и прицепить его к DOM. На базе этого даже можно реализовать взаимодействие между контент-скриптом и скриптами страницы (например, для случая, когда контент-скрипт внедряет конечный скрипт на страницу, но тот в процессе выполнения должен как-то общаться с самим расширением).
Для этого в тело документа необходимо добавить скрытый элемент (теми же методами DOM, или использовать существующий), и добавить JS-код следующего содержания:

var customEvent=document.createEvent('Event');
customEvent.initEvent('myevent',true,true);
//...
//вдруг понадобилось что-то передать или просто сделать запрос к расширению
document.getElementById('my_gateway').innerHTML = 'какие-то нужные данные';
document.getElementById('my_gateway').dispatchEvent(customEvent);

В этом коде на странице создается произвольное событие. Когда скрипту нужно как-то обратиться к расширению, в этот DIV можно записать необходимые данные и вызвать это событие.
Контент-скрипт должен подписаться на прослушку этого события на соответствующем элементе DOM.

document.getElementById('my_gateway').addEventListener('myevent', function() {
//получаем данные
var data = document.getElementById('my_gateway').innerHTML;
//выполняются необходимые действия и запросы к расширению
},false);

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

Монетизация


По монетизации расширений есть предложения в официальной документации, и даже соответствующее API в Chrome Webstore. Но лично я не увидел в этом никакого смысла, исходя из того, что расширения браузер хранит в исходных кодах в своих папках. В итоге, рассматривая какую-то грань полезности расширения, либо монетизация не принесет ощутимых результатов, либо всегда найдется знающий программист, который спокойно сможет взять исходники из папок браузера и получить работающий бесплатный аналог. Да, может помочь обфускация кода, но и она не настолько эффективна. Либо её будет просто разобрать, либо потраченные силы на запутывание не будут того стоить. Более того, когда я смотрел возможность размещения платных расширений в магазине, платежи можно было принимать только в ограниченных странах (ясное дело, Россия и смежные туда не входили). Как сейчас обстоят дела – честно, не знаю.
Исходя из всего этого, я для себя решил, что лучше всего добавить кнопку добровольных пожертвований. Вариантов реализации тут масса. Взглянуть на тот же AdBlock. Решение автора мне нравится.

Outro


На этом всё. Большинство из этой информации и более того можно почерпнуть в документации (хоть, может, не так прозрачно и доходчиво). Есть ещё одна тема по поводу работы с cookies, вкладками и обработки HTTP-запросов, но этому я решил посвятить отдельный пост.

Ссылки


Chrome extensions: Getting strarted
Описание методов API
Панель разработчика (именно здесь можно добавлять и публиковать свои расширения)
Справка Chrome Web Store
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 2

    0
    Какие сумбурные обрывки из документации официальной документации (которая, кстате, пояснеет все эти моменты гораздо лучше).
      0
      Но лично я не увидел в этом никакого смысла, исходя из того, что расширения браузер хранит в исходных кодах в своих папках. В итоге, рассматривая какую-то грань полезности расширения, либо монетизация не принесет ощутимых результатов, либо всегда найдется знающий программист, который спокойно сможет взять исходники из папок браузера и получить работающий бесплатный аналог. Да, может помочь обфускация кода, но и она не настолько эффективна.

      JScrambler с привязкой к домену(Chrome appId) в помощь… если денег хватит :)

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое