Почему chrome.tabs.executeScript() необходим для изменения DOM текущего веб-сайта и как я могу использовать jQuery для достижения того же эффекта?
ОБНОВЛЕНИЕ: я получил так много негативов в последнее время, что это действительно старый вопрос (2,5 года). Я даже больше не использую jquery. Поэтому, пожалуйста, дайте ему отдохнуть!
Я уже сделал несколько расширений для Chrome, но это первый раз, когда мне нужно что-то изменить на веб-сайте, на который сейчас смотрит пользователь, чтобы реагировать на нажатия кнопок, размещенные во всплывающем окне расширения.
Как я понял, выполнение некоторых строк jQuery не имеет никакого эффекта. Что работает, так это метод, который я нашел в примере расширения страницы Google для разработчиков: chrome.tabs.executeScript()
но я понятия не имею, зачем это нужно.
Должна быть какая-то базовая концепция, о которой я не знаю. Кто-нибудь может мне это объяснить? А я тоже могу выполнить jQuery (который загружен)?
Полный пример:
$('#change_button').on('click', function() {
//this doesen't work
$("body").css({backgroundColor: 'blue'});
//but this line does
chrome.tabs.executeScript(null, {code:"document.body.style.backgroundColor='red'"});
});
На самом деле мне очень нужен jQuery, чтобы внести еще некоторые изменения в DOM и ответить на них, а именно:
if( $(".list,.list-header-name").hasClass("tch"))
{
$(".list,.list-header-name").addClass("tch");
}
3 ответа
Javascript, который вы используете в своем расширении Chrome, запускается либо на фоновой странице, либо на некоторой всплывающей странице. Это НЕ страница в вашем браузере, где вы были при запуске расширения. Вот почему вам нужно выполнить скрипт внутри определенной вкладки.
Чтобы лучше это увидеть, щелкните правой кнопкой мыши на кнопке вашего расширения и выберите "Проверить всплывающее окно". То же самое для фоновой страницы, перейдите в chrome://extensions к вашему (предположительно) распакованному расширению и нажмите на фоновую страницу, и у вас есть инструменты разработчика, чтобы увидеть, что происходит.
Чтобы упаковать некоторые ресурсы в расширение Chrome, чтобы впоследствии их можно было использовать для прямого доступа с веб-страниц, вы можете использовать Доступные веб-ресурсы. По сути, вы объявляете файлы, которые вы хотите сделать доступными на веб-страницах, затем вы можете загрузить их или вставить их, используя URL-адрес вида "chrome-extension://[PACKAGE ID]/[PATH]". Чтобы использовать этот механизм и сделать упакованную библиотеку jQuery доступной для веб-страниц, мы добавляем:
"web_accessible_resources": [
"jquery-2.2.2.min.js"
]
в manifest.json.
Чтобы получить доступ к веб-страницам из расширения, необходимо добавить разрешения для него:
"permissions" : [
"tabs",
[...]
"<all_urls>",
"file://*/*",
"http://*/*",
"https://*/*"
],
также в manifest.json.
Вот функция, которая загружает jQuery на страницу определенной вкладки, присваивая ей имя ex$, чтобы оно не конфликтовало с другими библиотеками или версиями jQuery.
function initJqueryOnWebPage(tab, callback) {
// see if already injected
chrome.tabs.executeScript(tab.id,{ code: '!!window.ex$'},function(installed) {
// then return
if (installed[0]) return;
// load script from extension (the url is chrome.extension.getUrl('jquery-2.2.2.min.js') )
chrome.tabs.executeScript(tab.id,{ file: 'jquery-2.2.2.min.js' },function() {
// make sure we get no conflicts
// and return true to the callback
chrome.tabs.executeScript(tab.id,{ code: 'window.ex$=jQuery.noConflict(true);true'},callback);
});
});
}
Эта функция загружает jQuery из расширения на веб-страницу, а затем выполняет обратный вызов, когда она установлена. С тех пор можно использовать то же самое chrome.tabs.executeScript(tab.id,{ code/file:
запускать скрипты с помощью jQuery на удаленной странице.
Поскольку он использует метод executeScript с опцией file, ему не требуется объявление web_accessible_resources для скрипта
Из хорошего комментария ниже я узнал о среде исполнения. executeScript запускается в "изолированном мире", так что у него нет доступа к глобальным переменным, и все, что выполняется, не создает чего-то, к чему может получить доступ веб-страница. Таким образом, вся сложная функция, описанная выше, является излишней. Вы по-прежнему можете использовать эту систему, если вы не хотите взаимодействовать с кодом на странице, а просто с его DOM и не беспокоиться о любых конфликтах в коде
Но расширение имеет доступ к документу вкладки. Вот функция, которая использует механизм web_accessible_resources, описанный вначале, для создания тега сценария на веб-странице, который затем загружает библиотеку jQuery, которая упакована в расширение:
// executeScript's a function rather than a string
function remex(tabId, func,callback) {
chrome.tabs.executeScript(tabId,{ code: '('+func.toString()+')()' },callback);
}
function initJqueryOnWebPage(tab, callback) {
// the function to be executed on the loaded web page
function f() {
// return if jQuery is already loaded as ex$
if (window.ex$) return;
// create a script element to load jQuery
var script=document.createElement('script');
// create a second script tag after loading jQuery, to avoid conflicts
script.onload=function() {
var noc=document.createElement('script');
// this should display in the web page's console
noc.innerHTML='window.ex$=jQuery.noConflict(true); console.log(\'jQuery is available as \',window.ex$);';
document.body.appendChild(noc);
};
// use extension.getURL to get at the packed script
script.src=chrome.extension.getURL('jquery-2.2.2.min.js');
document.body.appendChild(script);
};
// execute the content of the function f
remex(tab.id,f,function() {
console.log('jQuery injected'); // this should log in the background/popup page console
if (typeof(callback)=='function') callback();
});
}
Отправлять сценарии для executeScripts громоздко, поэтому я использовал выше функцию revx. Обратите внимание, что существует разница между ммексом, который выполняет функцию в среде "изолированного мира", и кодом, выполняемым вкладкой, когда к документу добавляется тег сценария. Однако в обоих случаях на самом деле выполняются строки, поэтому будьте осторожны, не пытайтесь переносить объекты или функции в разных контекстах.
Два последовательных вызова службы памяти будут выполняться в их собственном контексте, поэтому даже если вы загрузите jQuery в одном, он не будет доступен в следующем. Однако можно использовать ту же систему, что и в initJqueryOnWebPage, что означает добавление кода в скрипт на странице. Вот немного кода, используйте его так же, как remex
:
function windowContextRemex(tabId,func,callback) {
var code=JSON.stringify(func.toString());
var code='var script = document.createElement("script");'+
'script.innerHTML = "('+code.substr(1,code.length-2)+')();";'+
'document.body.appendChild(script)';
chrome.tabs.executeScript(tabId, {
code : code
}, function () {
if (callback)
return callback.apply(this, arguments);
});
}
Итак, здесь я продемонстрировал:
- выполнение кода в изолированном контексте, который имеет доступ только к DOM страницы, открытой на вкладке
- выполнение упакованных скриптов в том же контексте, что и выше
- инъекционные скрипты, которые запускаются в контексте страницы
Чего не хватает:
- хороший способ связывания кода, так что вам не нужно создавать элемент script вручную
- загрузка файлов CSS через insertCSS (аналогична executeScript с параметром файла, но загружаемая непосредственно на странице
Всплывающая страница живет в контексте расширения, в то время как DOM на текущей веб-странице - это другой контекст, они представляют собой разные процессы, поэтому им нужны некоторые способы общения, tabs.executeScript
способ вставить код на страницу программно
Если вы сильно зависите от jQuery, вместо прямой вставки кода вы можете сначала вставить файл, а затем использовать метод jquery.
chrome.tabs.executeScript(null, {file: "jquery.js"}, function() {
chrome.tabs.executeScript(null, {code:"$("body").css({backgroundColor: 'blue'})");
Помимо внедрения программирования, вы также можете внедрить свои скрипты контента, указав matches
поле в manifest.json
, Таким образом, когда пользователь нажимает кнопку на всплывающей странице, вы можете использовать передачу сообщений для связи между процессом расширения и сценариями содержимого, а затем использовать сценарии содержимого для управления текущей страницей.
То, что вам не хватает, это то, что javascript всплывающего окна влияет только на DOM всплывающего окна. Чтобы повлиять на DOM вкладки, вам нужно использовать скрипт содержимого (либо из манифеста, либо путем его внедрения).
Предположим, у вас есть это как popup.html:
<!doctype html>
<html>
<head>
<title>popup html</title>
<style>
body { padding: 3em; }
button { width: 10em; margin: 1ex; }
</style>
</head>
<body>
<p>
<button id="onlyPopup">Color the popup</button>
<button id="affectWebpage">Color the webpage</button>
<button id="jqWebpage">Inject jQuery</button>
</p>
<script src="jquery.min.js"></script>
<script src="popup.js"></script>
</body>
</html>
И это как popup.js:
$('#onlyPopup').click(function() {
$("body").css('backgroundColor','blue');
});
$("#affectWebpage").click(function() {
chrome.tabs.executeScript(null, {code:"document.body.style.backgroundColor='red'"});
});
$("#jqWebpage").click(function() {
chrome.tabs.executeScript(null, {file:"jquery.min.js"}, callback);
});
function callback() {
chrome.tabs.executeScript(null, {code:"$('body').css('backgroundColor','yellow');"});
}
Таким образом, ваш оригинальный код "не работает" на самом деле работает, но не так, как вы ожидаете. Он окрашивает фон всплывающего окна, потому что там выполняется код. Чтобы выполнить код jQuery на вкладке, вам нужно сначала внедрить jQuery, а затем запустить ваш код (и единственное разрешение, которое нужно, это activeTab
). Искушение будет
$("#jqWebpage").click(function() { // don't do this
chrome.tabs.executeScript(null, {file:"jquery.min.js"});
chrome.tabs.executeScript(null, {code:"$('body').css('backgroundColor','yellow');"});
});
Но это не сработает из-за асинхронности. Эти две строки будут выполняться до того, как файл будет полностью введен. Это означает, что вторая строка кода может (и, вероятно, будет работать) до полной загрузки jQuery с неизвестными последствиями. Вместо этого вам нужно использовать параметр обратного вызова, чтобы выполнить нужный скрипт после завершения загрузки jQuery.