Как получить доступ к веб-странице DOM, а не к странице расширения DOM?
Я пишу расширение Chrome и пытаюсь наложить <div>
поверх текущей веб-страницы, как только кнопка будет нажата в файле popup.html.
Когда я получаю доступ к document.body.insertBefore
Метод изнутри popup.html он перекрывает <div>
во всплывающем окне, а не на текущей веб-странице.
Нужно ли использовать обмен сообщениями между background.html и popup.html для доступа к DOM веб-страницы? Я хотел бы сделать все в popup.html, а также, если возможно, использовать jQuery.
2 ответа
Как указано в обзоре расширений Chrome: архитектура (которую необходимо прочитать):
Если вашему расширению нужно взаимодействовать с веб-страницами, ему нужен скрипт контента. Сценарий содержимого - это некоторый JavaScript, который выполняется в контексте страницы, загруженной в браузер. Думайте о скрипте контента как о части этой загруженной страницы, а не как о части расширения, с которой он был упакован (его родительское расширение).
Всплывающее окно browserAction - это страница, которую вы видите, когда нажимаете значок на панели инструментов браузера, это страница HTML с chrome-extension://
URL, поэтому, когда вы получаете доступ к его DOM, вы влияете на всплывающее окно. То же самое относится и к странице фона / опций расширения.
Для доступа / манипулирования DOM веб-страницы у вас есть два способа:
Либо объявите скрипты содержимого в manifest.json и используйте обмен сообщениями:
chrome.tabs.sendMessage()
от фоновой / всплывающей страницы к скрипту вставленного контентаchrome.runtime.onMessage
слушатель, который будет выполнять действия на веб-странице и передавать результаты черезsendResponse
обратный вызов согласно документации (примечание: поддерживаются только объекты с поддержкой JSON, такие как числа, строки, массивы, простые объекты, что означает не элементы DOM, не классы и не функции). Если сценарий содержимого должен инициировать связь со страницей расширения, он должен использоватьchrome.runtime.sendMessage()
,Или используйте API Tabs для добавления скрипта контента:
chrome.tabs.executeScript(tabId, details, callback)
Требуемые разрешения:
"tabs"
,"https://www.example.com/*"
(или же"<all_urls>"
и варианты как"*://*/*"
,"http://*/*"
,"https://*/*"
)Лучший выбор, в случае явной активации пользователя, это использовать
"activeTab"
разрешение вместо"tabs"
а также"<all_urls>"
потому что это служит альтернативой для многих применений"<all_urls>"
, но не отображает предупреждающее сообщение во время установки..executeScript()
может использоваться с функцией обратного вызова, которая получает массив последних оцененных выражений в внедренном скрипте содержимого, по одному элементу на каждый кадр, в который он внедряется на вкладке. Chrome используетJSON.parse()
а такжеJSON.stringify()
на результаты внутри, тем самым ограничивая поддерживаемые типы простыми объектами и простыми строковыми значениями, такими как число / строка, или их массивами.
Поэтому он не работает для элементов DOM, функций, пользовательских свойств, методов получения / установки: вам нужно вручную отобразить / извлечь необходимые данные и передать их в виде простого массива / объекта.
Сценарии содержимого выполняются в специальной среде, называемой изолированным миром. Они имеют доступ к DOM страницы, в которую они внедрены, но не к каким-либо переменным или функциям JavaScript, созданным этой страницей. Каждый скрипт содержимого выглядит так, как будто на странице, на которой он запущен, нет другого JavaScript-кода. То же самое верно и в обратном: JavaScript, работающий на странице, не может вызывать какие-либо функции или обращаться к любым переменным, определенным скриптами содержимого.
Все еще возможно пойти на более глубокий уровень и получить доступ к веб-странице переменных / функций JavaScript.
В качестве примера второго метода, давайте покажем этот div при нажатии действия браузера.
Мы будем использовать chrome.tabs.executeScript()
в browserAction
щелкните обработчик, чтобы вставить файл сценария содержимого (или строку литерального кода, если он маленький, см. документацию метода) в DOM этой страницы.
var someVar = {text: 'test', foo: 1, bar: false};
chrome.tabs.executeScript({
code: '(' + function(params) {
document.body.insertAdjacentHTML('beforeend',
'<div style="all:unset; position:fixed; left:0; top:0; right:0; bottom:0;' +
'background-color:rgba(0,255,0,0.3)">' + params.text + '</div>'
);
return {success: true, html: document.body.innerHTML};
} + ')(' + JSON.stringify(someVar) + ');'
}, function(results) {
console.log(results[0]);
});
Как вы можете видеть, мы использовали автоматическое преобразование строк кода функции, чтобы иметь возможность писать введенный код как обычный JavaScript с подсветкой синтаксиса и линированием. Очевидным недостатком является то, что браузер тратит время на синтаксический анализ кода, но обычно это менее 1 миллисекунды, поэтому он незначителен.
Чтобы проиллюстрировать программную инъекцию, давайте добавим этот div при нажатии на действие браузера.
МанифестV2
Простой звонок:
chrome.tabs.executeScript({ code: `(${ inContent1 })()` }); function inContent1() { const el = document.createElement('div'); el.style.cssText = 'position:fixed; top:0; left:0; right:0; background:red'; el.textContent = 'DIV'; document.body.appendChild(el); }
Вызов с параметрами и получение результата:
chrome.tabs.executeScript({ code: `(${ inContent2 })(${ JSON.stringify({ foo: 'bar' }) })` }, ([result] = []) => { if (!chrome.runtime.lastError) { console.log(result); // shown in devtools of the popup window } }); function inContent2(params) { const el = document.createElement('div'); el.style.cssText = 'position:fixed; top:0; left:0; right:0; background:red'; el.textContent = params.foo; document.body.appendChild(el); return { success: true, html: document.body.innerHTML, }; }
В этом примере используется автоматическое преобразование
inContent
код функции в строку, преимущество здесь в том, что IDE может применять подсветку синтаксиса и линтинг. Очевидный недостаток заключается в том, что браузер тратит время на синтаксический анализ кода, но обычно оно составляет менее 1 миллисекунды, поэтому незначительно.
МанифестV3
Не забудьте о разрешениях в manifest.json, см. Другой ответ для получения дополнительной информации.
Простой звонок:
async function tabAddDiv() { const [tab] = await chrome.tabs.query({active: true, currentWindow: true}); await chrome.scripting.executeScript({ target: {tabId: tab.id}, function: inContent1, // see inContent1 in ManifestV2 example above }); }
Звонок и получение результата:
async function tabAddDiv() { const [tab] = await chrome.tabs.query({active: true, currentWindow: true}); let res; try { res = await chrome.scripting.executeScript({ target: {tabId: tab.id}, function: inContent2, // see inContent2 in ManifestV2 example above }); } catch (e) { // handle the error if necessary or just ignore it return; } // res[0] contains results for the main page of the tab document.body.textContent = JSON.stringify(res[0].result); }
Вызов с параметрами и получение результата:
В будущем executeScript будет иметь
args
параметр, см. https://crbug.com/1166720, тем временем вам придется использовать обмен сообщениями.