О совместимости Chrome-совместимых API расширений

()

У браузера Google Chrome с самого начала его существования есть достаточно удобное API для создания расширений. Появление большого количества браузеров на той же кодовой базе сделало это API массовым. В итоге новое API расширений браузера Firefox под названием WebExtensions заявлено как совместимое с Google Chrome, а в Microsoft Edge начиная с Redstone 1 добавлена поддержка WebExtensions.

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

Логотипы браузеров Google Chrome, Opera, Microsoft Edge и Mozilla Firefox

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

Манифест расширения

Любое расширение начинается с написания манифеста. При написании манифеста расширения надо как минимум указать основной JS-файл. Для большинства браузеров описание выглядит примерно так:

"background": {
	"scripts": ["background.js"],
	"persistent": false
}

Однако для Microsoft Edge надо писать так:

"-ms-preload": {
	"backgroundScript": "background.js"
}

В качестве альтернативы можно описать так:

"background": {
	"page": "background.html",
	"persistent": true
}

И уже в заголовке файла "background.html" подключать "background.js". Этот способ работает во всех браузерах, хотя для Chrome является deprecated и в будущем работать перестанет.

Есть и другие отличия в разрборе манифеста разными браузерами. Кроме того в Firefox есть свои специфичные параметры, которых нет в других браузерах. Например ветка "applications".

Объект браузера

Переходим к рассмотрению собственно API расширений. Расширения пишутся на javascript и большая часть логики реализуется через вызов методов глобального объекта, описывающего браузер. И тут разработчика подстерегает первая проблема: имя объекта зависит от браузера:

  • Google Chrome: "chrome".
  • Mozilla Firefox: рекоммендуется использовать "browser", однако можно использовать и "chrome".
  • Opera: для основного функционала "chrome", для Opera-специфичных функций - "opr".
  • Microsoft Edge: "msBrowser", объект "chrome" вроде как доступен, но возможность его использования не исследовалась в полной мере.

Таким образом разработка расширения начинается с написания "костыля" примерно такого вида:

var browserObj = (function () {
    return window.msBrowser || window.browser || window.chrome;
})();

Может быть на этом различия заканчиваются? Было бы здорово, если так. Но к сожалению различий ещё много и рассмотреть все в рамках небольшого материала не получится. Потому рассмотрим только несколько характерных примеров.

BrowserAction API

Допустим наше расширение использует API browserAction: кнопка на панели браузера, при нажатию на которую появляеся всплывающее окно с веб-страницей. У кнопки есть глобальные свойства и свойства, привязанные к определённым вкладкам. Рассмотрим поведение метода "browserObj.browserAction.setPopup" в зависимости от браузера. Для начала типичный пример использования:

var popupUrl = "popup.html";
browserObj.browserAction.setPopup({popup: popupUrl});

Подводные камни кроются в том, как разные браузеры обрабатывают параметр "popup" и какие ограничения на него накладывают. Firefox позволяет использовать абсолютные и относительные пути. Причём возможно использование как встроенных в расширение страниц, так и внешних URL. В Chrome и Opera возможно только использование внутренних страниц.

Если попробовать указать не абсолютный путь к странице, а относительный то и тут есть отличия в поведении: в Chrome и Opera относительные пути считаются от корневой директории расширения, а в Firefox - от директории, в которой расположен указанный в манифесте "background.html". Таким образом если у нас в манифесте есть такие строки:

"background": {
	"page": "html/background.html",
	"persistent": true
}

А в коде такие:

var popupUrl = "popup.html";
browserObj.browserAction.setPopup({popup: popupUrl});

То для Chrome и Opera это будет эквивалентно:

var popupUrl = browserObj.runtime.getURL("popup.html");
browserObj.browserAction.setPopup({popup: popupUrl});

А для Firefox:

var popupUrl = browserObj.runtime.getURL("html/popup.html");
browserObj.browserAction.setPopup({popup: popupUrl});

Ещё интереснее ситуация будет если попробовать в качестве "popupUrl" указать абсолютный URL. Например так:

var popupUrl = "http://ya.ru";
browserObj.browserAction.setPopup({popup: popupUrl});

Firefox здесь отработает нормально и откроет указанный URL во всплывающем окне. А вот Chrome и Opera раскроют это так:

var popupUrl = browserObj.runtime.getURL("http://ya.ru");
browserObj.browserAction.setPopup({popup: popupUrl});

Ну и разумеется никакой внешний URL открыт не будет. Получается что разработчики Firefox давая разработчикам расширений дополнительные возможности нарушили совместимость с Google Chrome.

Notifications API

В определённых ситуациях расширение может показывать оповещения пользователю используя API notifications. Это API поддерживается во всех браузерах, но большая часть расширенных опций доступна только в Google Chrome. Хорошая таблица совместимости есть в документации от разработчиков Mozilla.

Отдельного внимания заслуживает тот факт что браузер Opera при указании неподдерживаемых опций генерирует ошибку. Например вы хотите показать оповещение и на нём же кнопку настроек, чтобы пользователь мог отключить оповещения в дальнейшем. Вы пишите примерно такой код:

chrome.notifications.create(
	"checkfail",
	type: "basic",
	iconUrl: chrome.extension.getURL("data/plugin_logo.png"),
	title: chrome.i18n.getMessage("pluginTitle"),
	message: chrome.i18n.getMessage("checkFail", check_info),
	buttons: [
		{
			title: chrome.i18n.getMessage("notifyButtonSettings"),
			iconUrl: chrome.extension.getURL("data/notify_icon_settings.png")
		}
	]
});

В Google Chrome всё отлично работает, а вот в Opera этот код приводит к ошибке и последующие инструкции не выполняются, что может быть критично.

Management API

Ещё одна интересная тема связана с идентификаторами расширений. Допустим что наше расширение имеет известные проблемы при работе с некоторыми другими расширениями. По идее тут на помощь должен прийти management API, с помощью которого можно проверить наличие в браузере расширений с определёнными идентификаторами. Однако и тут есть нюансы:

  1. Идентификаторы расширениям присваиваются каталогами расширений и разнятся от браузера к браузеру.
  2. Яндекс.Браузер позволяет ставить расширения как из Opera Addons, так и из Chrome Webstore. Соответственно в случае Яндекс.Браузера надо проверять два набора идентификаторов.
  3. Firefox через management API позволяет управлять только темами, но не расширениями. Да и для тем возвращаются не идентификаторы из каталога расширений, а сгенерированные случайным образом и актуальные только для текущей инсталляции браузера.

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

Каталоги расширений

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

И тут тоже есть некоторые отличия, которые следует иметь ввиду:

  • Chrome Webstore: автоматическая модерация в течении часа, каких-то особых требований к коду нет. Заливается обычный zip-архив с файлами расширения.
  • Mozilla Addons: ручная модерация от часа до недели. Обфусцированый JS не запрещён, но модератору необходимо предоставить исходник и инструкцию по сборке. Заливается обычный zip-архив с файлами расширения.
  • Opera Addons: ручная модерация от суток до четырёх месяцев (возможно и дольше, но автор этих строк с большими сроками не сталкивался). Обфусцированый JS запрещён. Заливается обычный zip-архив с файлами расширения.
  • Windows Store: сильно усложнена сборка: необходимо использовать NodeJS с модулями, доступными только для Windows, и далее ЭЦП с помощью встроенной в Windows утилиты (подробнее).

Резюмируя ограничения каталогов расширений:

  • Если есть необходимость обфусцировать код то от поддержки Opera придётся отказаться. Плюс будут дополнительные сложности с Firefox.
  • Медлительность модераторов Opera лишает разработчиков возможности оперативно доставлять обновления пользователям этого браузера.
  • Необходимость использования специфичных для windows инструментов для сборки расширения перед публикацией в Windows Store автоматически требует наличия лицензии на соответствующую ОС и усложняет работу пользователям других ОС.
  • Учитывая рыночную долю Microsoft Edge и сложность сборки расширений для публикации ряду разработчиков проще будет отказаться от моддержки этого браузера.

Вместо заключения

Отличий и не соответствий в реализациях вобщем-то схожих API очень много и подробно рассматривать их все бессмысленно. Важно что отметить сам факт их существования. Радует что разработчики Mozilla в своей документации приводят таблицы совместимости хотя бы для основных браузеров, однако это не отменяет необходимости тщательно тестировать работу расширения во всех браузерах, для которых планируется поддержка.

Хотя подобная сегментация и усложняет разработку расширений, автор этих строк надеется что со временем ситуация разрешиться сама собой: непопулярные API отомрут, у популярных основные особенности стабилизируются и станут одинаковым во всех браузерах, а различия останутся лишь в совсем мелких деталях, не критичных для большинства разработчиков.

Подписаться на обновления: RSS-лента Telegram канал Twitter

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

Новый комментарий

Жирный текстКурсивный текстПодчёркнутый текстЗачёркнутый текстПрограммный кодСсылкаИзображение




© 2006-2018 Вадим Калинников aka MooSE