Владельцам расширений (а также приложений) для Хрома уже пора бы задуматься над поддержкой второй версии манифеста.
Если кто не в курсе, то не так давно были объявлены новые изменения и нововведения в разработку расширений для браузера.
Далее будет выборочный перевод двух страниц и мой способ использования шаблонизатора изнутри песочницы.
* Далее старыми расширениями буду называть расширения и приложения с версией манифеста 1 (или вообще без версии).
Чтобы расширения были менее подвержены XSS уязвимостям были внедрены общие принципы CSP. В общем CSP являет собой механизм белых и черных списков касательно ресурсов, которые загружаются и выполняются расширением. С помощью CSP можно установить только необходимые разрешения для расширения и таким образом повысить его безопасность.
Эта политика является дополнительным уровнем защиты над правами на доступ к ресурсам (host permissions)
Установить политику безопасности можно в строковом параметре
Если не установлена версия манифеста (
Если кто не в курсе, то не так давно были объявлены новые изменения и нововведения в разработку расширений для браузера.
Далее будет выборочный перевод двух страниц и мой способ использования шаблонизатора изнутри песочницы.
Сначала немного о планах Гугла по поддержке старых расширений
* Далее старыми расширениями буду называть расширения и приложения с версией манифеста 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 выполнятся не будет. Так же не будут выполнятся инлайновые блоки и инлайновые обработчики событий (). Если вы в коде по какой-то причине передавали строку в качестве первого аргумента функций 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 getTemplatevar 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:
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"]
}
}
