我正在创建一个内容脚本作为浏览器扩展。
我希望更改某些页面的功能,因此我确定我需要停止某些事件。通常情况下,可以在事件上注册一个事件监听器并调用
stopImmediatePropagation
,但这只适用于你的事件监听器是第一个注册的监听器,而我的作为内容脚本不是。
在继续我自己的几乎可行的复杂解决方案之前,我想问一下你是否看到了这个问题的一些简单解决方案。如果是这样,帖子的其余部分就没有必要了。
我找到的解决方案是有两个脚本。第一个在页面加载和替换
prototype.addEventListener
之前执行,以便在首次调用时,创建一个 firstListener
函数并首先作为我选择的事件类型的侦听器添加。然后调用实际的addEventListener
,并将addEventListener
方法切换回原来的。const injection = document.createElement("script");
injection.textContent = `HTMLSelectElement.prototype.originalAddEventListener = HTMLSelectElement.prototype.addEventListener;
HTMLSelectElement.prototype.addEventListener = function(a, b, c) {
this.firstListener = event => { };
this.originalAddEventListener("change", event => this.firstListener(event));
this.originalAddEventListener(a, b, c);
this.addEventListener = this.originalAddEventListener;
};`;
(document.head || document.documentElement).appendChild(injection);
injection.remove();
第二个脚本包含我的扩展的主要功能,然后可以在页面加载后正常加载。它可以找到所需的元素并用他们想要的任何东西替换他们的
firstListener
方法。document.body.querySelectorAll("select").forEach(element => {
element.firstListener = event => event.stopImmediatePropagation(); //I could, of course, have more complicated processing logic here
});
问题是,出于某种原因,第二个脚本不会覆盖
firstListener
方法。但是,从控制台调用相同的脚本是可行的。为什么会这样,我该如何解决?
显然,内容脚本与网页有些隔离,可以清楚地看到页面内容,这导致代码在控制台中运行,但在我的内容脚本中不起作用。 Firefox,至少,提供了一些方法来克服这个问题。
简单地使用
element.wrappedJSObject.firstListener
确实允许我重新定义函数,但是现在网页缺少调用这个新函数的必要权限,因为它属于我的内容脚本。相反,exportFunction(firstListenerFunc, element, { defineAs: "firstListener" })
成功了。
然而,在了解
exportFunction
之后,我改写了第一个脚本:
const origAddEventListener = HTMLSelectElement.prototype.addEventListener;
exportFunction(tempAddEventListener, HTMLSelectElement.prototype, { defineAs: "addEventListener" });
function tempAddEventListener(type, ev, options) {
this.firstListener = event => { };
origAddEventListener.call(this, "change", event => this.firstListener(event));
exportFunction(origAddEventListener, this, { defineAs: "addEventListener" });
this.addEventListener(type, ev, options);
}
这使我能够将
firstListener
保留为仅存在于我的内容脚本中的函数,并使页面对象与其原始形式几乎相同,至少从他们的角度来看是这样。
有人提请我注意解决此问题的一种通常更简单的方法,即使用鲜为人知的事件处理阶段“捕获”。
这个答案主要依赖于这里的信息,如果你想对这个问题有更彻底的解释。
就我而言,我想停止某些
change
元素上的select
事件。大多数已注册的事件在“冒泡”阶段被调用,该阶段从目标元素开始并通过其每个父元素向上传播。capture
选项设置为true
,然后从那里调用stopImmediatePropagation
。document.body.querySelectorAll("select").forEach(dropdown => {
dropdown.parentElement.addEventListener("change", event => {
if (event.target !== dropdown) { //Make sure that this event was indeed on the element we wanted to track
return;
}
//Insert whatever other logic here
event.stopImmediatePropagation();
}, { capture: true });
});
“捕获”阶段发生在“冒泡”阶段之前,并且顺序相反。所以它从
html
开始,然后是body
,然后是div
,然后是我们的select
,作为例子。如果在捕获阶段有一些事件侦听器侦听此事件,您始终可以将事件侦听器添加到更靠近文档根的位置以拦截这些事件。