Как известно, расширения для браузера Chrome работают каждое в своей песочнице. Для внедрения в веб-страницу существует механизм content script-ов, когда javascript код внедряется в страницу, и имеет доступ к DOM дереву. Этот механизм позволяет работать с контентом страницы, изменять внешний вид, запускать произвольный JS, обмениваться данными с фоновым процессом расширения.
Но одну вещь механизм content script не позволяет делать — получать доступ к javascript-окружению веб-страницы.
Когда я делал расширение для скробблинга прослушиваемой музыки из vk.com на last.fm, для мониторинга состояния плеера я сначала использовал парсинг DOM элементов плеера) Это было ужасно, и после первого же изменения в верстке всё сломалось. Поэтому пришлось придумать что-то понадёжнее. Поиски в интернете привели к простому и, казалось бы, очевидному механизму внедрения своего кода в веб-страницу:
1. Из контент скрипта создаём в DOM дереве страницы элемент «script» со своим кодом.
//исполнить скрипт vk_inner в контексте vk.com
var script=document.createElement('script');
script.type='text/javascript';
script.src=chrome.extension.getURL("js/vk_inner.js");
2. Код производит патчинг функций, работа с которыми необходима. Для патчинга используем метод addCallListener, найденный где-то на просторах интернета. Он заменяет собой целевую функцию, и вызывает обработчики перед и после её запуска:
Function.addCallListener = function(func, callbacks) {
var successNumber = 0,
errorNumber = 0,
name = func.name;
return function() {
var args = [].slice.call(arguments);
var result, error;
var props = {
args: args,
self: this,
name: name
}
callbacks.before && callbacks.before(props);
try {
result = func.apply(this, arguments);
props.successNumber = ++successNumber;
props.result = result;
props.status = 'success';
callbacks.success && callbacks.success(props);
} catch (e) {
props.errorNumber = ++errorNumber;
props.error = e;
props.status = 'error';
callbacks.error && callbacks.error(props);
}
callbacks.after && callbacks.after(props);
return result;
}
}
3. Производим патчинг нужных функций. В случае vk.com это функция audioPlayer.onPlayProgress, она вызывается каждую секунду во время проигрывания:
var ARTIST_NUM = 5;
var TITLE_NUM = 6;
var artistElem = document.getElementById('vkScrobblerArtist');
var titleElem = document.getElementById('vkScrobblerTrackTitle');
//вешаем слушатель прогресса песни
audioPlayer.onPlayProgress = Function.addCallListener(audioPlayer.onPlayProgress, {
after: function(props) {
//сохраняем исполнителя и песню
artistElem.innerHTML = audioPlayer.lastSong[ ARTIST_NUM ];
titleElem.innerHTML = audioPlayer.lastSong[ TITLE_NUM ];
}
});
Теперь при работе плеера в элементах artistElem и titleElem будут находиться актуальные название песни и её исполнитель. Остаётся только периодически проверять их содержимое и делать с ним что угодно. Можно избавиться от периодической проверки содержимого элементов, если использовать механизм событий jQuery. Таким образом можно устроить обмен любыми сериализуемыми данными между js веб-страницы и расширением.
Автор: Houston