我正在尝试创建一个具有侧面板的扩展。此侧面板将具有按钮,这些按钮将根据主页面状态执行操作。
我按照这个例子注入了侧面板,并且我能够在Click监听器上连接一个按钮。但是,我无法访问全局js变量。在开发人员控制台中,在主页面的范围内,我可以看到我想要的变量(变量的名称-config)。但是当我指向侧面板(popup.html)的上下文时,我得到以下错误-
VM523:1未捕获的引用错误:未定义配置。popup.html似乎也在一个单独的线程中运行。
如何访问按钮的onClick处理程序的全局js变量?
我的代码:
manifest.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");
})
});
内容脚本.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);
}
由于内容脚本在"孤立的世界";页面的JS变量不能从扩展直接访问,您需要在页面的主世界中运行代码。
警告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);
})();
另请参阅如何打开正确的devtools控制台。
2.旧版Chrome和ManifestV2中的ManifestV3
我们将提取变量,并通过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_resources
中公开,例如。// 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 }); }); }
所以,基本上:
- iframe脚本获取自己的选项卡id(或者弹出/后台脚本获取活动选项卡id),并向内容脚本发送消息
- 内容脚本向先前插入的页面脚本发送DOM消息
- 页面脚本侦听该DOM消息,并将另一个DOM消息发送回内容脚本
- 内容脚本将其作为对扩展脚本的响应发送回