Как получить доступ к веб-странице 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 веб-страницы у вас есть два способа:

  1. Либо объявите скрипты содержимого в manifest.json и используйте обмен сообщениями:

    chrome.tabs.sendMessage() от фоновой / всплывающей страницы к скрипту вставленного контента chrome.runtime.onMessage слушатель, который будет выполнять действия на веб-странице и передавать результаты через sendResponse обратный вызов согласно документации (примечание: поддерживаются только объекты с поддержкой JSON, такие как числа, строки, массивы, простые объекты, что означает не элементы DOM, не классы и не функции). Если сценарий содержимого должен инициировать связь со страницей расширения, он должен использовать chrome.runtime.sendMessage(),

  2. Или используйте 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, тем временем вам придется использовать обмен сообщениями.

Другие вопросы по тегам