Владельцам расширений (а также приложений) для Хрома уже пора бы задуматься над поддержкой второй версии манифеста.
Если кто не в курсе, то не так давно были объявлены новые изменения и нововведения в разработку расширений для браузера.
Далее будет выборочный перевод двух страниц и мой способ использования шаблонизатора изнутри песочницы.
Сначала немного о планах Гугла по поддержке старых расширений
* Далее старыми расширениями буду называть расширения и приложения с версией манифеста 1 (или вообще без версии).
- Начиная с Chrome 21 блокируется создание новых расширений с первой версией манифеста, но разрешаются обновления существующих расширений до старой версии манифеста.
- С выходом Chrome 23 (начало ноября) у Веб-сторе будет заблокировано обновление на расширения со старыми версиями манифеста. Хром перестанет запаковывать старые расширения и загружать распакованные для разработки.
- Первая четверть 2013 — старые расширения уже будет не найти в Веб-сторе. Разработчикам об этом сообщат по email.
- Вторая четверть 2013 — из Веб-стора будут удалены все старые расширения, а разработчикам придет еще одно уведомление. Но Хром пока будет загружать и запускать установленные расширения с манифестом версии 1.
- Третья четверть 2013 — Хром перестанет загружать и запускать старые расширения.
Различия версий манифеста 1 и 2
- Политика безопасности контента (content security policy) по умолчанию установлена в
`script-src 'self'; object-src 'self'
. Это по сути самое важное обновление. О нем немного позже. - Все ресурсы расширения теперь недоступны по URL
chrome-extension://[PACKAGE ID]/[PATH]
. Т.е. вы не сможете подключить скрипт или картинку с расширения с других страниц кроме самого расширения. Но чтобы обойти этот недостаток появилось свойствоweb_accessible_resources
, в котором можно указать массив с путями к нужным ресурсам. - Вместо свойства
background_page
(которое было строкой), теперь пишемbackground
, которое должно содержать объект со свойствомscripts
илиpage
. - Изменения в browser actions:
- поле
browser_actions
заменено наbrowser_action
, а APIchrome.browserActions
— наchrome.browserAction
. - удалено свойство
icons
изbrowser_action
. Вместо него нужно использоватьdefault_icon
илиchrome.browserAction.setIcon
. - удалено свойство
name
изbrowser_action
. Вместо него нужно использоватьdefault_title
илиchrome.browserAction.setTitle
. - удалено свойство
popup
изbrowser_action
. Вместо него нужно использоватьdefault_popup
илиchrome.browserAction.setPopup
. - свойство
default_popup
вbrowser_action
должно быть строкой, а не объектом
- поле
- Изменения в page actions:
- поле
page_actions
заменено наpage_action
, а APIchrome.pageActions
— наchrome.pageAction
. - удалено свойство
icons
изpage_action
. Вместо него нужно использоватьdefault_icon
илиchrome.pageAction.setIcon
. - удалено свойство
name
изpage_action
. Вместо него нужно использоватьdefault_title
илиchrome.pageAction.setTitle
. - удалено свойство
popup
изpage_action
. Вместо него нужно использоватьdefault_popup
илиchrome.pageAction.setPopup
. - свойство
default_popup
вpage_action
должно быть строкой, а не объектом. - Удалено
chrome.self
из API, теперь нужно использоватьchrome.extension
.
- поле
- Больше нет
chrome.extension.getTabContentses
иchrome.extension.getExtensionTabs
. Вместо них нужно использоватьchrome.extension.getViews({ "type": "tab" })
. - Вместо
Port.tab
используемPort.sender
.
Политика безопасности контента (Content Security Policy или CSP)
Чтобы расширения были менее подвержены XSS уязвимостям были внедрены общие принципы CSP. В общем CSP являет собой механизм белых и черных списков касательно ресурсов, которые загружаются и выполняются расширением. С помощью CSP можно установить только необходимые разрешения для расширения и таким образом повысить его безопасность.
Эта политика является дополнительным уровнем защиты над правами на доступ к ресурсам (host permissions)
Установить политику безопасности можно в строковом параметре content_security_policy
в manifest.json
.
Если не установлена версия манифеста (manifest_version
), то по умолчанию нет никакой политики безопасности контента. Но для второй версии манифеста она установлена по умолчанию со значением script-src 'self'; object-src 'self'
. Поэтому есть некоторые ограничения. Например, функция eval
выполнятся не будет. Так же не будут выполнятся инлайновые <script>
блоки и инлайновые обработчики событий (<button onclick="...">
). Если вы в коде по какой-то причине передавали строку в качестве первого аргумента функций setTimeout
и setInterval
, то это тоже придется исправить.
Так же скрипты, подключаемые с других ресурсов (с CDN, например), нужно сохранить локально.
Смягчение ограничений политики безопасности
К сожалению нет способа смягчить ограничения на выполнение инлайновых скриптов. Но есть возможность подключать сторонние скрипты (правда, только по https). Для этого нужно указать домен в content_security_policy
, например так:
{
...,
"content_security_policy": "script-src 'self' https://example.com; object-src 'self'",
...
}
“Что же делать если нужно выполнять eval?” или “Как подключить шаблонизатор?”
Сначала вы можете подумать — зачем мне eval? Но эта функция нужна почти для всех шаблонизаторов (заметьте, new Function()
тоже не работает). Здесь нам поможет песочница (sandbox).
Можно создать отдельную страницу, где будут выполнятся все небезопасные операции (например, eval) и запускать ее изнутри песочницы (на песочницу по умолчанию CSP не распространяется).
Так же есть возможность передавать данные между расширением и песочницей через метод postMessage()
.
Далее расскажу как я подключал underscore шаблонизатор
- Создадим файл
sandboxed/template-renderer.html
с таким вот контентомtemplate-renderer.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Sandboxed Template Renderer</title> <script src="/js/libs/underscore/underscore-min.js"></script> </head> <body> <script> var templates = {}; window.addEventListener('message', function (event) { var template; if (typeof templates[event.data.templateName] == 'undefined') { template = _.template(event.data.template); templates[event.data.templateName] = template; } else { template = templates[event.data.templateName]; } event.source.postMessage({ id: event.data.id, result: template(event.data.context) }, event.origin); }); </script> </html>
- В манифесте добавим этот html в песочницу
{ ..., "sandbox": { "pages": ["sandboxed/template-renderer.html"] }, ... }
- Создадим функцию которая будет обращаться к нашей песочнице за рендерингом шаблона
function getTemplate
var getTemplate = (function(){ var iframe = document.createElement('iframe'), callbacks = []; iframe.src = 'sandboxed/template-renderer.html'; iframe.style.display = 'none'; document.body.appendChild(iframe); window.addEventListener('message', function (event) { callbacks.forEach(function (item, idx) { if (item && item.id == event.data.id) { item.callback(event.data.result); delete callbacks[idx]; } }); }); return function (templateName, template) { return function (context, callback) { var id = Math.random(); callbacks.push({ id: id, callback: callback }); iframe.contentWindow.postMessage({ id: id, templateName: templateName, template: template, context: context }, '*'); }; }; }());
- Шаблонизатор готов к использованию
// получаем функцию, которая будет рендерить шаблон в зависимости от контекста var template = getTemplate('templateId', templateContent); // одно плохо - теперь результат получаем асинхронно template({text: 'Hello world'}, function (html) { // выводим html на страницу // можно было передать $('body').html в качестве второго параметра, // но решил написать так для большей наглядности $('body').html(html); });
Это решение — первое что пришло в голову. Наверное есть способ сделать это лучше (красивее). Буду рад увидеть предложения в комментариях.
И на последок, мой manifest.json
:
{
"name": "Twittext",
"description": "A lightweight Google Chrome extension for Twitter",
"background": {
"page": "background.html"
},
"manifest_version": 2,
"browser_action": {
"default_icon": "img/icon_19.png",
"default_title": "Twittext",
"default_popup": "popup.html"
},
"icons": {
"128": "img/icon_128.png",
"19": "img/icon_19.png",
"48": "img/icon_48.png"
},
"options_page": "options.html",
"version": "1.6.1",
"permissions": [
"tabs",
"background",
"https://api.twitter.com/",
"https://userstream.twitter.com/"
],
"sandbox": {
"pages": ["sandboxed/template-renderer.html"]
}
}
Автор: utf