JavaScript
Google Chrome
16 July 2013

Доступ к JavaScript веб-страницы из расширения для Chrome

Как известно, расширения для браузера 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 веб-страницы и расширением.

+3
19.6k 62
Comments 15