如何访问网页DOM而不是扩展页面DOM?

时间:2010-12-26 00:02:27

标签: google-chrome google-chrome-extension

我正在编写Chrome扩展程序,并且只要在popup.html文件中单击按钮,就会尝试在<div>上覆盖当前网页。

当我从popup.html中访问document.body.insertBefore方法时,它会覆盖弹出窗口中的<div>,而不是当前网页。

我是否必须使用background.html和popup.html之间的消息来访问网页的DOM?我想在popup.html中做所有事情,并且如果可能的话也要使用jQuery。

2 个答案:

答案 0 :(得分:101)

Chrome extensions overview: architecture中所述(必读):

  

如果您的扩展程序需要与网页进行交互,那么它需要一个内容脚本。内容脚本是一些JavaScript,它在已加载到浏览器中的页面的上下文中执行。将内容脚本视为该加载页面的一部分,而不是作为其打包的扩展(其父扩展)的一部分。

browserAction弹出窗口是您单击浏览器工具栏中的图标时看到的页面,它是一个带有chrome-extension:// URL的HTML页面,因此当您访问其DOM时,您正在影响弹出窗口。这同样适用于扩展的背景/选项页面。

要访问/操作网页DOM,您有两种方法:

  1. manifest.json 中声明content script(s)并使用messaging

    chrome.tabs.sendMessage()从您的后台/弹出页面到注入的内容脚本的chrome.runtime.onMessage侦听器,它将根据文档在网页上执行操作并通过sendResponse回调传输结果(注意:仅限支持JSON可用的对象,如数字,字符串,数组,简单对象,这意味着不是DOM元素,不是类,不是函数)。如果内容脚本需要启动与扩展页面的通信,则应使用chrome.runtime.sendMessage()

  2. 或使用Tabs API注入内容脚本
    chrome.tabs.executeScript(tabId, details, callback)

    • 必填permissions"tabs""https://www.example.com/*"
      (或"<all_urls>"以及"*://*/*""http://*/*""https://*/*"

    • 等变体
    • 在明确的用户激活情况下,更好的选择是使用"activeTab" permission而不是"tabs""<all_urls>",因为它可以替代"<all_urls>"的许多用途1}},但在安装期间不显示任何警告消息

    • .executeScript()可以与回调函数一起使用,该回调函数接收注入的内容脚本中最后评估的表达式的数组,每个帧中的一个元素在注册表中注入。 Chrome在内部对结果使用JSON.parse()JSON.stringify(),从而将支持的类型限制为普通对象和简单的可字符串值,如数字/字符串或其数组。
      因此它不适用于DOM元素,函数,自定义属性,getter / setter:您需要手动映射/提取所需数据并将其传递到一个简单的数组/对象中。

  3.   

    内容脚本在称为孤立世界的特殊环境中执行。他们可以访问注入页面的DOM,但不能访问页面创建的任何JavaScript变量或函数。它将每个内容脚本视为在其运行的页面上没有执行其他JavaScript。反过来也是如此:页面上运行的JavaScript无法调用任何函数或访问内容脚本定义的任何变量。

    仍有可能更深入access the webpage JavaScript variables/functions



    作为第二种方法的示例,让我们在点击浏览器操作时显示div。
    我们将在browserAction点击处理程序中使用chrome.tabs.executeScript()将内容脚本文件(或文字代码字符串,如果它很小,请参阅方法的文档)注入该页面的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]);
    });
    

    正如您所看到的,我们使用了函数代码的自动字符串转换,以便能够将注入的代码编写为具有语法高亮和linting的普通JavaScript。显而易见的缺点是浏览器浪费时间来解析代码,但通常它不到1毫秒,因此可以忽略不计。

答案 1 :(得分:1)

为了说明程序化注入,让我们在点击浏览器操作时添加该 div。

ManifestV2

  • 简单调用:

    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 可以应用语法高亮和 linting。明显的缺点是浏览器会浪费时间解析代码,但通常不到1毫秒,可以忽略不计。

ManifestV3

不要忘记 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,同时你必须使用消息传递。