javascript 如何防止调用以前添加的事件侦听器?

rsaldnfx  于 2023-04-19  发布在  Java
关注(0)|答案(1)|浏览(200)

我正在创建一个内容脚本作为浏览器扩展。
我想改变一些页面的功能,因此我确定我需要停止某些事件。通常情况下,可以在事件上注册一个事件侦听器并调用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之后,我重写了第一个脚本:

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作为一个仅存在于内容脚本中的函数,并使页面对象与其原始形式保持几乎相同,至少从它们的Angular 来看是这样。

yfwxisqw

yfwxisqw1#

有人让我注意到一种更简单的方法来解决这个问题,使用事件处理中鲜为人知的阶段“捕获”。
这个答案主要依赖于来自here的信息,如果你想要一个更全面的解释。
在我的例子中,我想停止一些select元素上的change事件。大多数注册的事件都是在“Bubbling”阶段被调用的,该阶段从目标元素开始,通过它的每个父元素向上传播。
要停止所有此类事件,只需在目标元素的父元素上注册一个事件侦听器,将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。如果在捕获阶段有一些事件侦听器侦听此事件,您可以始终将事件侦听器添加到更靠近文档根的位置以拦截这些事件。

相关问题