【问题标题】:Access global js variables from js injected by a chrome extension从 chrome 扩展注入的 js 访问全局 js 变量
【发布时间】:2017-10-22 01:50:33
【问题描述】:

我正在尝试创建一个具有侧面板的扩展程序。此侧面板将包含可根据主机页面状态执行操作的按钮。

我跟着this example 注入了侧面板,我可以连接一个按钮 onClick 监听器。但是,我无法访问全局 js 变量。在开发人员控制台中,在主机页面的范围内,我能够看到我所追求的变量(变量名称 - 配置)。但是当我进入侧面板 (popup.html) 的上下文时,我收到以下错误 -
VM523:1 未捕获的 ReferenceError:未定义配置。似乎 popup.html 也在单独的线程中运行。

如何访问按钮的 onClick 处理程序的全局 js 变量?

我的代码:

ma​​nifest.json

{
    "manifest_version": 2,

    "name": "Hello World",
    "description": "This extension to test html injection",
    "version": "1.0",
    "content_scripts": [{
        "run_at": "document_end",
        "matches": [
            "https://*/*",
            "http://*/*"
        ],
        "js": ["content-script.js"]
    }],
    "browser_action": {
        "default_icon": "icon.png"
    },
    "background": {
        "scripts":["background.js"]
    },
    "permissions": [
        "activeTab"
    ],
    "web_accessible_resources": [
        "popup.html",
        "popup.js"
    ]
}

background.js

chrome.browserAction.onClicked.addListener(function(){
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
        chrome.tabs.sendMessage(tabs[0].id,"toggle");
    })
});

content-script.js

chrome.runtime.onMessage.addListener(function(msg, sender){
    if(msg == "toggle"){
        toggle();
    }
})

var iframe = document.createElement('iframe'); 
iframe.style.background = "green";
iframe.style.height = "100%";
iframe.style.width = "0px";
iframe.style.position = "fixed";
iframe.style.top = "0px";
iframe.style.right = "0px";
iframe.style.zIndex = "9000000000000000000";
iframe.frameBorder = "none"; 
iframe.src = chrome.extension.getURL("popup.html")

document.body.appendChild(iframe);

function toggle(){
    if(iframe.style.width == "0px"){
        iframe.style.width="400px";
    }
    else{
        iframe.style.width="0px";
    }
}

popup.html

<head>
<script src="popup.js"> </script>
</head>
<body>
<h1>Hello World</h1>
<button name="toggle" id="toggle" >on</button>
</body>

popup.js

document.addEventListener('DOMContentLoaded', function() {
  document.getElementById("toggle").addEventListener("click", handler);
});

function handler() {
  console.log("Hello");
  console.log(config);
}

【问题讨论】:

  • 我无权访问主机页面。主机页面不将其存储在 chrome.storage.local
  • 扩展JS无法与页面JS交互。他们在文档中告诉你。
  • @wOxxOm 感谢您的链接。我遇到过,但有些人对此感到困惑。你能帮我理解吗。我必须注入另一个脚本来提取 JS 变量并发送消息?
  • 或者我应该将此脚本注入内容脚本并以不同的方式连接 onClick 侦听器?
  • 好的,谢谢,如何从按钮调用该脚本?

标签: google-chrome-extension


【解决方案1】:

由于内容脚本在“孤立的世界”中运行,页面的 JS 变量无法直接从扩展程序中访问,您需要 run code in page's main world

警告! DOM 元素无法提取为元素,因此只需发送其innerHTML 或其他属性即可。只能提取与 JSON 兼容的数据类型(字符串、数字、布尔值、null 和这些类型的数组/对象),没有循环引用。

1。现代 Chrome 95 或更新版本中的 ManifestV3

这是您的扩展弹出/后台脚本中的全部代码:

async function getPageVar(name, tabId) {
  const [{result}] = await chrome.scripting.executeScript({
    func: name => window[name],
    args: [name],
    target: {
      tabId: tabId ??
        (await chrome.tabs.query({active: true, currentWindow: true}))[0].id
    },
    world: 'MAIN',
  });
  return result;
}

用法:

(async () => {
  const v = await getPageVar('foo');
  console.log(v);
})();

另见how to open correct devtools console

2。旧版 Chrome 中的 ManifestV3 和 ManifestV2

我们将提取变量并通过 DOM 消息传递将其发送到内容脚本。然后内容脚本可以将消息中继到 iframe 或弹出/背景页面中的扩展脚本。

  • ManifestV3 适用于 Chrome 94 或更早版本需要两个单独的文件

    内容脚本:

    const evtToPage = chrome.runtime.id;
    const evtFromPage = chrome.runtime.id + '-response';
    
    chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
      if (msg === 'getConfig') {
        // DOM messaging is synchronous so we don't need `return true` in onMessage
        addEventListener(evtFromPage, e => {
          sendResponse(JSON.parse(e.detail));
        }, {once: true});
        dispatchEvent(new Event(evtToPage));
      }
    });
    
    // Run the script in page context and pass event names
    const script = document.createElement('script');
    script.src = chrome.runtime.getURL('page-context.js');
    script.dataset.args = JSON.stringify({evtToPage, evtFromPage});
    document.documentElement.appendChild(script);
    

    page-context.js 应该暴露在 manifest.json 的web_accessible_resourcesexample

    // This script runs in page context and registers a listener.
    // Note that the page may override/hook things like addEventListener... 
    (() => {
      const el = document.currentScript;
      const {evtToPage, evtFromPage} = JSON.parse(el.dataset.args);
      el.remove();
      addEventListener(evtToPage, () => {
        dispatchEvent(new CustomEvent(evtFromPage, {
          // stringifying strips nontranferable things like functions or DOM elements
          detail: JSON.stringify(window.config),
        }));
      });
    })();
    
  • ManifestV2 内容脚本:

    const evtToPage = chrome.runtime.id;
    const evtFromPage = chrome.runtime.id + '-response';
    
    // this creates a script element with the function's code and passes event names
    const script = document.createElement('script');
    script.textContent = `(${inPageContext})("${evtToPage}", "${evtFromPage}")`;
    document.documentElement.appendChild(script);
    script.remove();
    
    // this function runs in page context and registers a listener
    function inPageContext(listenTo, respondWith) {
      addEventListener(listenTo, () => {
        dispatchEvent(new CustomEvent(respondWith, {
          detail: window.config,
        }));
      });
    }
    
    chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
      if (msg === 'getConfig') {
        // DOM messaging is synchronous so we don't need `return true` in onMessage
        addEventListener(evtFromPage, e => sendResponse(e.detail), {once: true});
        dispatchEvent(new Event(evtToPage));
      }
    });
    
  • 同一选项卡中扩展 iframe 脚本的使用示例:

    function handler() {
      chrome.tabs.getCurrent(tab => {
        chrome.tabs.sendMessage(tab.id, 'getConfig', config => {
          console.log(config);
          // do something with config
        });
      });  
    }
    
  • 弹窗脚本或后台脚本的使用示例:

    function handler() {
      chrome.tabs.query({active: true, currentWindow: true}, tabs => {
        chrome.tabs.sendMessage(tabs[0].id, 'getConfig', config => {
          console.log(config);
          // do something with config
        });
      });  
    }
    

所以,基本上:

  1. iframe 脚本获取自己的选项卡 ID(或弹出/背景脚本获取活动选项卡 ID)并向内容脚本发送消息
  2. 内容脚本向先前插入的页面脚本发送 DOM 消息
  3. 页面脚本侦听该 DOM 消息并将另一条 DOM 消息发送回内容脚本
  4. 内容脚本将其作为响应发送回扩展脚本。

【讨论】:

  • 非常感谢,代码可以正常工作。我遇到了一些小麻烦,因为配置没有作为 event.details 传递。我相信这是因为它的成员之一是函数指针。我可以通过有选择地传递我需要的参数而不是整个配置来解决问题。
  • CustomEvent 和简单的Event 有什么区别
  • CustomEvent 允许在detail 中传递自定义数据。
  • 是否可以在页面加载之前使用相同的逻辑在窗口对象上设置变量,例如在 document_start 上?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-12
  • 2012-03-25
相关资源
最近更新 更多