根本原因:
内容脚本在一个“孤立的世界”环境中执行。
解决方案:
使用DOM将代码注入到页面中——这些代码将能够访问页面上下文(“main world”)的函数/变量,或者向页面上下文公开函数/变量(在您的情况下是state()方法)。
如果需要与页面脚本通信,请注意:
使用DOM CustomEvent处理程序。例子:1、2和3。
注意,在页面脚本中需要chrome API:
因为chrome。* api不能在页面脚本中使用,你必须在内容脚本中使用它们,并通过DOM消息传递将结果发送到页面脚本(参见上面的注释)。
Safety warning:
A page may redefine or augment/hook a built-in prototype so your exposed code may fail if the page did it in an incompatible fashion. If you want to make sure your exposed code runs in a safe environment then you should either a) declare your content script with "run_at": "document_start" and use Methods 2-3 not 1, or b) extract the original native built-ins via an empty iframe, example. Note that with document_start you may need to use DOMContentLoaded event inside the exposed code to wait for DOM.
目录
方法1:注入另一个兼容ManifestV3的文件
方法二:注入嵌入式代码- MV2
方法2b:使用MV2函数
方法3:使用内联事件- ManifestV3兼容
方法四:只使用executeScript的world - ManifestV3
方法5:在manifest中使用world。仅支持ManifestV3, Chrome 111+
注入代码中的动态值
方法一:注入另一个文件(ManifestV3/MV2)
当您有大量代码时尤其如此。将代码放在扩展名中的一个文件中,比如script.js。然后像这样在你的内容脚本中加载它:
var s = document.createElement('script');
s.src = chrome.runtime.getURL('script.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
js文件必须在web_accessible_resources中公开:
清单。ManifestV2的json示例
“web_accessible_resources”:“script.js”,
清单。ManifestV3的json示例
“web_accessible_resources”:[{
“资源”:“script.js”,
“匹配”:[" < all_urls >”)
})
如果不是,控制台中会出现以下错误:
拒绝加载chrome-extension://[EXTENSIONID]/script.js。资源必须列在web_accessible_resources清单键中,以便由扩展之外的页面加载。
方法二:注入嵌入式代码(MV2)
当您希望快速运行一小段代码时,此方法非常有用。(参见:如何禁用facebook热键与Chrome扩展?)。
var actualCode = `// Code here.
// If you want to use a variable, use $ and curly braces.
// For example, to use a fixed random number:
var someFixedRandomValue = ${ Math.random() };
// NOTE: Do not insert unsafe variables in this way, see below
// at "Dynamic values in the injected code"
`;
var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.remove();
注意:模板文字只支持Chrome 41及以上版本。如果你想扩展工作在Chrome 40-,使用:
var actualCode = ['/* Code here. Example: */' + 'alert(0);',
'// Beware! This array have to be joined',
'// using a newline. Otherwise, missing semicolons',
'// or single-line comments (//) will mess up your',
'// code ----->'].join('\n');
方法2b:使用函数(MV2)
对于一大块代码,引用字符串是不可行的。不使用数组,可以使用函数,并进行字符串化:
var actualCode = '(' + function() {
// All code is executed in a local scope.
// For example, the following does NOT overwrite the global `alert` method
var alert = null;
// To overwrite a global variable, prefix `window`:
window.alert = null;
} + ')();';
var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.remove();
此方法有效,因为字符串和函数上的+运算符将所有对象转换为字符串。如果您打算多次使用代码,明智的做法是创建一个函数来避免代码重复。实现可能是这样的:
function injectScript(func) {
var actualCode = '(' + func + ')();'
...
}
injectScript(function() {
alert("Injected script");
});
注意:由于函数是序列化的,原来的作用域和所有绑定的属性都丢失了!
var scriptToInject = function() {
console.log(typeof scriptToInject);
};
injectScript(scriptToInject);
// Console output: "undefined"
方法3:使用内联事件(ManifestV3/MV2)
有时,你想要立即运行一些代码,例如在<head>元素创建之前运行一些代码。这可以通过在textContent中插入<script>标记来实现(参见方法2/2b)。
另一种不推荐的方法是使用内联事件。不建议使用,因为如果页面定义了禁止内联脚本的内容安全策略,则内联事件侦听器将被阻止。另一方面,扩展注入的内联脚本仍然可以运行。
如果你仍然想使用内联事件,如下所示:
var actualCode = '// Some code example \n' +
'console.log(document.documentElement.outerHTML);';
document.documentElement.setAttribute('onreset', actualCode);
document.documentElement.dispatchEvent(new CustomEvent('reset'));
document.documentElement.removeAttribute('onreset');
注意:此方法假设没有其他处理重置事件的全局事件侦听器。如果有,你也可以选择其他全球性事件之一。打开JavaScript控制台(F12),输入document.documentElement。打开,并选择可用的事件。
方法四:使用铬。脚本API世界(仅适用于ManifestV3)
Chrome 95或更新版本,Chrome .scripting. executescript with world: 'MAIN'
Chrome 102或更新版本的Chrome .scripting. registercontentscripts带有world: 'MAIN',也允许runAt: 'document_start'来保证页面脚本的早期执行。
与其他方法不同的是,这个方法是用于背景脚本或弹出脚本,而不是用于内容脚本。请参阅文档和示例。
方法5:在manifest中使用world。json(仅适用于ManifestV3)
在Chrome 111或更新版本中,您可以在manifest中的content_scripts声明中添加“world”:“MAIN”。json覆盖默认值isolate。脚本按照列出的顺序运行。
"content_scripts": [{
"js": ["content.js"],
"matches": ["<all_urls>"],
"run_at": "document_start"
}, {
"world": "MAIN",
"js": ["page.js"],
"matches": ["<all_urls>"],
"run_at": "document_start"
}],
注入代码中的动态值(MV2)
有时,需要将任意变量传递给注入的函数。例如:
var GREETING = "Hi, I'm ";
var NAME = "Rob";
var scriptToInject = function() {
alert(GREETING + NAME);
};
要注入此代码,需要将变量作为参数传递给匿名函数。确保正确地执行它!以下选项无效:
var scriptToInject = function (GREETING, NAME) { ... };
var actualCode = '(' + scriptToInject + ')(' + GREETING + ',' + NAME + ')';
// The previous will work for numbers and booleans, but not strings.
// To see why, have a look at the resulting string:
var actualCode = "(function(GREETING, NAME) {...})(Hi, I'm ,Rob)";
// ^^^^^^^^ ^^^ No string literals!
解决方案是使用JSON。在传递参数之前进行Stringify。例子:
var actualCode = '(' + function(greeting, name) { ...
} + ')(' + JSON.stringify(GREETING) + ',' + JSON.stringify(NAME) + ')';
如果有很多变量,那么使用JSON是值得的。Stringify一次,以提高可读性,如下:
...
} + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]).slice(1, -1) + ')';
注入代码中的动态值(ManifestV3)
方法1可以在内容脚本中设置脚本元素的URL:
s.c src = chrome.runtime.getURL('script.js?') + new URLSearchParams({foo: 1});
然后script.js可以读取它:
const params = new URLSearchParams(document.currentScript.src.split('?')[1]);
console.log (params.get(“foo”));
方法4 executeScript有args参数,registerContentScripts目前没有(希望将来会添加)。