Выполнение кода на уровне страницы из Background.js и возврат значения
У меня есть веб-страница с собственными скриптами и переменными, которые мне нужно выполнить и получить возвращаемые значения из Background.js моего расширения.
Я понимаю (я думаю!), Что для взаимодействия с веб-страницей это должно быть сделано через chrome.tabs.executeScript или ContentScript, но потому что код должен выполняться в контексте исходной страницы (чтобы иметь область видимости) к сценариям и переменным), его нужно сначала ввести на страницу.
После этого замечательного поста Роба В. я могу вызвать скрипт / переменные на уровне страницы, но я изо всех сил пытаюсь понять, как таким образом возвращать значения.
Вот что у меня так далеко...
Код веб-страницы (с которым я хочу взаимодействовать):
<html>
<head>
<script>
var favColor = "Blue";
function getURL() {
return window.location.href;
}
</script>
</head>
<body>
<p>Example web page with script content I want interact with...</p>
</body>
</html>
manifest.json:
{
// Extension ID: behakphdmjpjhhbilolgcfgpnpcoamaa
"name": "MyExtension",
"version": "1.0",
"manifest_version": 2,
"description": "My Desc Here",
"background": {
"scripts": ["background.js"]
},
"icons": {
"128": "icon-128px.png"
},
"permissions": [
"background",
"tabs",
"http://*/",
"https://*/",
"file://*/", //### (DEBUG ONLY)
"nativeMessaging"
]
}
background.js
codeToExec = ['var actualCode = "alert(favColor)";',
'var script = document.createElement("script");',
' script.textContent = actualCode;',
'(document.head||document.documentElement).appendChild(script);',
'script.parentNode.removeChild(script);'].join('\n');
chrome.tabs.executeScript( tab.id, {code:codeToExec}, function(result) {
console.log('Result = ' + result);
} );
Я понимаю, что код в настоящее время просто "предупреждает" переменную favColor (это был всего лишь тест, чтобы убедиться, что я вижу, как он работает). Однако, если я когда-нибудь попытаюсь вернуть эту переменную (либо оставив ее в качестве последнего оператора, либо сказав "return favColor"), обратный вызов executeScript никогда не будет иметь значения.
Итак, здесь, кажется, (по крайней мере) три уровня:
- background.js
- контентные скрипты
- реальная веб-страница (содержащая скрипты / переменные)
... и я хотел бы знать, каков рекомендуемый способ говорить с уровня 1 до уровня 3 (выше) и возвращать значения?
Заранее спасибо: о)
2 ответа
Вы совершенно правы в понимании трехслойного разделения контекста.
- Фоновая страница - это отдельная страница, поэтому она не разделяет JS или DOM с видимыми страницами.
- Сценарии содержимого изолированы от контекста JS веб-страницы, но имеют общий DOM.
- Вы можете ввести код в контекст страницы, используя общий DOM. Он имеет доступ к контексту JS, но не к API Chrome.
Чтобы общаться, эти слои используют разные методы:
Фон <-> Обсуждение контента через Chrome API.
Наиболее примитивным является обратный вызов executeScript
, но это непрактично для всего, кроме однострочников.
Распространенным способом является использование Messaging.
Необычно, но можно общаться с помощью chrome.storage
И его onChanged
событие.
Страница <-> Расширение не может использовать те же методы.
Поскольку внедренные сценарии контекста страницы технически не отличаются от собственных сценариев страницы, вы ищете методы для веб-страницы для взаимодействия с расширением. Доступны 2 метода:
Хотя страницы имеют очень, очень ограниченный доступ к
chrome.*
API, тем не менее, они могут использовать Messaging для связи с расширением. Это достигается за счет"externally_connectable"
метод.Я недавно подробно описал этот ответ. Короче говоря, если ваше расширение объявило, что домену разрешено общаться с ним, и домен знает идентификатор расширения, оно может отправить внешнее сообщение на расширение.
Положительным моментом является непосредственное общение с расширением, но недостатком является требование к определенным доменам из белого списка, из которого вы его используете, и вам необходимо отслеживать идентификатор вашего расширения (но, поскольку вы вводите код, вы можете предоставить код с удостоверением личности). Если вам нужно использовать его на любом домене, это не подходит.
Другое решение - использовать DOM Events. Поскольку DOM является общим для сценария содержимого и сценария страницы, событие, созданное одним, будет видимым для другого.
Документация демонстрирует, как использовать
window.postMessage
для этого эффекта; Использование пользовательских событий концептуально более понятно.Я снова ответил об этом раньше.
Недостатком этого метода является требование, чтобы скрипт контента действовал как прокси. Что-то вроде этого должно присутствовать в скрипте контента:
window.addEventListener("PassToBackground", function(evt) { chrome.runtime.sendMessage(evt.detail); }, false);
в то время как фоновый скрипт обрабатывает это с
chrome.runtime.onMessage
слушатель.
Я призываю вас написать отдельный скрипт контента и вызвать executeScript
с file
атрибут вместо code
и не полагаться на его обратный вызов. Обмен сообщениями чище и позволяет возвращать данные в фоновый скрипт более одного раза.
Подход в ответе Xan (использование событий для общения) является рекомендуемым подходом. Однако реализовать концепцию (и безопасным способом!) Сложнее.
Поэтому я укажу, что можно синхронно возвращать значение со страницы в скрипт контента. Когда <script>
тег со встроенным скриптом вставляется на страницу, скрипт сразу и синхронно выполняется (до .appendChild(script)
метод возвращает).
Вы можете воспользоваться этим поведением, используя внедренный сценарий, чтобы назначить результат объекту DOM, к которому может обращаться сценарий содержимого. Например, перезаписывая текстовое содержимое текущего активного <script>
тег. Код в <script>
тег выполняется только один раз, поэтому вы можете назначить любой мусор на содержимое <script>
тег, потому что он больше не будет анализироваться как код. Например:
// background script
// The next code will run as a content script (via chrome.tabs.executeScript)
var codeToExec = [
// actualCode will run in the page's context
'var actualCode = "document.currentScript.textContent = favColor;";',
'var script = document.createElement("script");',
'script.textContent = actualCode;',
'(document.head||document.documentElement).appendChild(script);',
'script.remove();',
'script.textContent;'
].join('\n');
chrome.tabs.executeScript(tab.id, {
code: codeToExec
}, function(result) {
// NOTE: result is an array of results. It is usually an array with size 1,
// unless an error occurs (e.g. no permission to access page), or
// when you're executing in multiple frames (via allFrames:true).
console.log('Result = ' + result[0]);
});
Этот пример пригоден для использования, но не идеален. Прежде чем использовать это в своем коде, убедитесь, что вы реализуете правильную обработку ошибок. В настоящее время, когда favColor
не определено, скрипт выдает ошибку. Следовательно, текст скрипта не обновляется, а возвращаемое значение неверно. После реализации правильной обработки ошибок этот пример будет достаточно надежным.
И пример едва читаем, потому что сценарий составлен из строки. Если логика довольно большая, но содержание скрипта в отдельном файле и использовать chrome.tabs.executeScript(tab.id, {file: ...}, ...);
,
когда actualCode
становится длиннее нескольких строк, я предлагаю заключить код в литерал функции и объединить его с '('
а также ')();
чтобы вам было проще писать код без добавления кавычек и обратной косой черты в actualCode
(в основном "Метод 2b" ответа, который вы цитировали в вопросе).
chrome.browserAction.onClicked.addListener(function(tab) {
// No tabs or host permissions needed!
console.log('Turning ' + tab.url + ' red!');
chrome.tabs.executeScript({
file: 'index.js'
});
});
Здесь index.js - это обычный js-файл для ввода в браузер.
#index.js
alert("Hello from api");