Выполнение кода на уровне страницы из 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 никогда не будет иметь значения.

Итак, здесь, кажется, (по крайней мере) три уровня:

  1. background.js
  2. контентные скрипты
  3. реальная веб-страница (содержащая скрипты / переменные)

... и я хотел бы знать, каков рекомендуемый способ говорить с уровня 1 до уровня 3 (выше) и возвращать значения?

Заранее спасибо: о)

2 ответа

Решение

Вы совершенно правы в понимании трехслойного разделения контекста.

  • Фоновая страница - это отдельная страница, поэтому она не разделяет JS или DOM с видимыми страницами.
  • Сценарии содержимого изолированы от контекста JS веб-страницы, но имеют общий DOM.
  • Вы можете ввести код в контекст страницы, используя общий DOM. Он имеет доступ к контексту JS, но не к API Chrome.

Чтобы общаться, эти слои используют разные методы:

Фон <-> Обсуждение контента через Chrome API.
Наиболее примитивным является обратный вызов executeScript, но это непрактично для всего, кроме однострочников.
Распространенным способом является использование Messaging.
Необычно, но можно общаться с помощью chrome.storage И его onChanged событие.

Страница <-> Расширение не может использовать те же методы.
Поскольку внедренные сценарии контекста страницы технически не отличаются от собственных сценариев страницы, вы ищете методы для веб-страницы для взаимодействия с расширением. Доступны 2 метода:

  1. Хотя страницы имеют очень, очень ограниченный доступ к chrome.* API, тем не менее, они могут использовать Messaging для связи с расширением. Это достигается за счет "externally_connectable" метод.

    Я недавно подробно описал этот ответ. Короче говоря, если ваше расширение объявило, что домену разрешено общаться с ним, и домен знает идентификатор расширения, оно может отправить внешнее сообщение на расширение.

    Положительным моментом является непосредственное общение с расширением, но недостатком является требование к определенным доменам из белого списка, из которого вы его используете, и вам необходимо отслеживать идентификатор вашего расширения (но, поскольку вы вводите код, вы можете предоставить код с удостоверением личности). Если вам нужно использовать его на любом домене, это не подходит.

  2. Другое решение - использовать 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");
Другие вопросы по тегам