我正在编写Chrome扩展程序,并且只要在popup.html文件中单击按钮,就会尝试在<div>
上覆盖当前网页。
当我从popup.html中访问document.body.insertBefore
方法时,它会覆盖弹出窗口中的<div>
,而不是当前网页。
我是否必须使用background.html和popup.html之间的消息来访问网页的DOM?我想在popup.html中做所有事情,并且如果可能的话也要使用jQuery。
答案 0 :(得分:101)
如Chrome extensions overview: architecture中所述(必读):
如果您的扩展程序需要与网页进行交互,那么它需要一个内容脚本。内容脚本是一些JavaScript,它在已加载到浏览器中的页面的上下文中执行。将内容脚本视为该加载页面的一部分,而不是作为其打包的扩展(其父扩展)的一部分。
browserAction弹出窗口是您单击浏览器工具栏中的图标时看到的页面,它是一个带有chrome-extension://
URL的HTML页面,因此当您访问其DOM时,您正在影响弹出窗口。这同样适用于扩展的背景/选项页面。
要访问/操作网页DOM,您有两种方法:
在 manifest.json 中声明content script(s)并使用messaging:
chrome.tabs.sendMessage()
从您的后台/弹出页面到注入的内容脚本的chrome.runtime.onMessage
侦听器,它将根据文档在网页上执行操作并通过sendResponse
回调传输结果(注意:仅限支持JSON可用的对象,如数字,字符串,数组,简单对象,这意味着不是DOM元素,不是类,不是函数)。如果内容脚本需要启动与扩展页面的通信,则应使用chrome.runtime.sendMessage()
。
或使用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:您需要手动映射/提取所需数据并将其传递到一个简单的数组/对象中。
内容脚本在称为孤立世界的特殊环境中执行。他们可以访问注入页面的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。
简单调用:
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毫秒,可以忽略不计。
不要忘记 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,同时你必须使用消息传递。