我想在我的扩展中控制youtube.com的播放器:
manifest.json:
{
"name": "MyExtension",
"version": "1.0",
"description": "Gotta catch Youtube events!",
"permissions": ["tabs", "http://*/*"],
"content_scripts" : [{
"matches" : [ "www.youtube.com/*"],
"js" : ["myScript.js"]
}]
}
字符串
myScript.js:
function state() { console.log("State Changed!"); }
var player = document.getElementById("movie_player");
player.addEventListener("onStateChange", "state");
console.log("Started!");
型
问题是,当我播放/暂停YouTube视频时,控制台给我 “已开始!",但没有 “状态已更改!"。
当这段代码被放到控制台时,它工作了。我做错了什么?
7条答案
按热度按时间huwehgph1#
根本原因:
内容脚本是在
ISOLATED
“world”环境中执行的,这意味着它不能访问MAIN
“world”(页面上下文)中的JS函数和变量,也不能公开自己的JS内容,就像你的例子中的state()
方法一样。解决方案:
使用以下方法将代码注入到页面的JS上下文(
MAIN
“world”)中。使用
chrome
API时:·通过
<all_urls>
上的externally_connectable
消息传递允许since Chrome 107。·使用普通内容脚本通过
CustomEvent
消息传递,请参阅下一段。使用普通内容脚本发送消息时:
使用
CustomEvent
,如here,或here,或here所示。简而言之,注入的脚本向正常内容脚本发送消息,该脚本调用chrome.storage
或chrome.runtime.sendMessage
,然后通过另一个CustomEvent消息将结果发送到注入的脚本。不要使用window.postMessage
,因为您的数据可能会破坏具有期望特定格式的侦听器的站点的信息。注意!
该页面可能会重新定义一个内置的原型或一个全局,并从您的私人通信中泄露数据,或者使您注入的代码失败。防范这种情况是复杂的(请参阅Tampermonkey或Violentmonkey的“vault”),因此请确保验证所有接收到的数据。
目录
所以,什么是最好的?对于ManifestV 3,如果代码应该始终运行,则使用声明性方法(#5),或者使用
chrome.scripting
(#4)从扩展脚本(如popup或service worker)进行条件注入,否则使用基于内容脚本的方法(#1和#3)。*内容脚本控制注入:
*扩展脚本控制注入(如后台服务worker或popup脚本):
*声明性注入:
world
-仅限ManifestV 3,Chrome 111+方法一:注入另一个文件(ManifestV 3/MV 2)
当你有很多代码的时候特别好。把代码放在你扩展名的文件中,比如
script.js
。然后像这样把它加载到你的content script中:字符串
js文件必须在
web_accessible_resources
中暴露:型
型
否则,控制台中会出现以下错误:
拒绝加载chrome-extension://[EXTENSIONID]/script. js。资源必须在web_accessible_resources清单键中列出,以便由扩展外部的页面加载。
方法二:插入嵌入代码(MV 2)
当你想快速运行一小段代码时,这个方法很有用。(参见:How to disable facebook hotkeys with Chrome extension?)。
型
注意:template literals仅在Chrome 41及以上版本中支持。如果您希望扩展程序在Chrome 40-中工作,请使用:用途:
型
方法2b:使用函数(MV 2)
对于一大块代码,引用字符串是不可行的。代替使用数组,可以使用一个函数,并进行字符串化:
型
这个方法可以工作,因为字符串和函数上的
+
运算符将所有对象转换为字符串。如果您打算多次使用代码,那么创建一个函数以避免代码重复是明智的。实现可能如下所示:型
注意:由于函数是序列化的,原始的作用域和所有绑定的属性都将丢失!
型
方法三:使用内联事件(ManifestV 3/MV 2)
有时,你想立即运行一些代码,例如在
<head>
元素创建之前运行一些代码。这可以通过插入一个带有textContent
的<script>
标记来完成(参见方法2/2b)。另一种方法是使用内联事件,但不推荐使用。不推荐使用内联事件是因为如果页面定义了禁止内联脚本的内容安全策略,则内联事件侦听器将被阻止。另一方面,由扩展注入的内联脚本仍会运行。如果您仍想使用内联事件,请执行以下操作:
型
注意事项:此方法假设没有其他全局事件侦听器处理
reset
事件。如果有,您也可以选择其他全局事件之一。只需打开JavaScript控制台(F12),键入document.documentElement.on
,然后选择可用的事件。方法四:使用Chrome.scripting API
world
(仅限ManifestV 3)chrome.scripting.executeScript
和world: 'MAIN'
chrome.scripting.registerContentScripts
和world: 'MAIN'
,也允许runAt: 'document_start'
保证页面脚本的早期执行。与其他方法不同,这个方法用于后台脚本或弹出脚本,而不是内容脚本。参见documentation和examples。
方法五:在manifest.json中使用
world
(仅限ManifestV 3)在Chrome 111或更高版本中,您可以在manifest.json中将
"world": "MAIN"
添加到content_scripts
声明中,以覆盖默认值ISOLATED
。脚本按列出的顺序运行。型
注入代码中的动态值(MV 2)
有时候,你需要传递一个任意的变量给注入的函数。例如:
为了注入这段代码,你需要将变量作为参数传递给匿名函数。请确保正确实现它!下面的代码将不工作:
解决方案是在传递参数之前使用
JSON.stringify
。示例:如果你有很多变量,值得使用
JSON.stringify
一次,以提高可读性,如下所示:注入代码中的动态值(ManifestV 3)
然后注入的script.js可以读取它:
要隐藏页面脚本中的参数,您可以将script元素放在closed ShadowDOM中。
args
参数,registerContentScript目前没有(希望将来会添加)。kqqjbcuj2#
Rob W的优秀答案中唯一缺少的是如何在注入的页面脚本和内容脚本之间进行通信。
在接收端(您的内容脚本或注入的页面脚本)添加一个事件侦听器:
字符串
在启动器端(内容脚本或注入页面脚本)发送事件:
型
备注:
cloneInto
(内置函数)显式地将其克隆到目标中,否则它将失败并出现安全违规错误。型
8dtrkrch3#
我还遇到了加载脚本的排序问题,这个问题通过顺序加载脚本来解决,加载基于Rob W's answer。
字符串
用法示例如下:
型
事实上,我对JS有点陌生,所以请随时给我更好的方法。
dfty9e194#
在Content脚本中,我将脚本标记添加到头部,它绑定了一个'onmessage'处理程序,在处理程序中,我使用eval来执行代码。在booth内容脚本中,我也使用onmessage处理程序,所以我可以进行双向通信。Chrome浏览器
字符串
pmListener.js是一个post消息url监听器
型
这样,我可以有2种方式之间的通信CS到真实的DOM。它非常有用的,例如,如果你需要监听webscoket事件,或任何内存中的变量或事件。
yftpprvb5#
您可以使用我创建的一个实用函数来在页面上下文中运行代码并获取返回值。
这是通过将函数序列化为字符串并将其注入到Web页面来完成的。
该实用程序是available here on GitHub。
使用示例-
字符串
5ktev3wc6#
如果你想注入纯函数,而不是文本,你可以使用这个方法:
字符串
你还可以给函数传递参数(不幸的是,没有对象和数组可以被字符串化)。把它添加到baretheses中,像这样:
型
2exbekwf7#
如果你想在注入的代码(ManifestV 3)中使用动态值,并且你注入的脚本的类型是模块,你不能像Rob的回答中描述的那样使用
document.currentScript.dataset
,相反,你可以将参数作为url参数传递,并在注入的代码中检索它们。下面是一个例子:内容脚本:
字符串
注入的代码(在我的例子中是override-script.js):
型