Understanding event listeners can be challenging, particularly when working with third-party sites and Chrome extensions. The complexity of the issue is even more evident when considering how different types of events are fired and propagate. This post explores a specific challenge experienced while handling mousedown and pointerdown events, leading to some fascinating browser behavior discoveries.
In this particular scenario, a developer was trying to listen for a mousedown event on a document object in a Chrome extension. The event was set to use capturing (useCapture: true
), implying that it should fire immediately, regardless of where the user clicks on the document.
However, this was not the case. The mousedown event fired correctly for most elements on the page, but it did not fire when the user clicked on certain elements in a specific section of the page. These elements were part of a third-party site that the extension was working with.
The first hypotheses revolved around the firing order of the event listeners and the use of stopPropagation()
. However, it was clarified that stopPropagation()
does not affect event handlers when useCapture
is set to true. This is because useCapture
effectively reverses the firing order of the event handlers, meaning the event listener at the highest level (the document object in this case) is fired first.
Upon further investigation, it was discovered that switching the event from mousedown to pointerdown solved the problem. By using getEventListeners()
, it was observed that the document object had pointerdown
events defined also at the capture phase. This sparked the theory that if a pointerdown
event is fired, the mousedown
event might not be fired at all.
Upon recreating the situation, it was found that the third-party site called preventDefault()
in their pointerDown
event, which was also using useCapture: true
. This seemed to prevent the mousedown
event from firing.
While one might think that stopPropagation()
or stopImmediatePropagation()
should have been the methods to block the event, it turned out that they didn't affect this scenario. That's because preventDefault()
cancels the event without necessarily stopping its propagation. In this case, both the pointerdown
and mousedown
handlers were set at the capturing phase on the same element (document), and were not bubbling events.
Furthermore, it was discovered that the browser fires pointerdown
before mousedown
. If the pointerdown
handler cancels the event with preventDefault()
, the mousedown
event does not fire.
Interestingly, even when the mousedown
event did not fire because of the preventDefault()
in the pointerdown
event, the click
event still did fire. This was particularly interesting because the click
event is typically fired after the mouseup
event, which, in turn, is generally fired after the mousedown
event.
The challenge of working with event listeners in a Chrome extension, especially with third-party sites, requires a deep understanding of JavaScript events. It can lead to the discovery of unique browser behaviors, such as the precedence of pointerdown
over mousedown
and the effect of preventDefault()
.
Through this process, it was demonstrated how changing the type of the event listener from mousedown
to pointerdown
can resolve issues when dealing with third-party sites within Chrome extensions. It's important to consider these points when developing extensions and working with event listeners in JavaScript.
Working with event listeners can be tricky, but with careful exploration and understanding of how different event types
transform the following question and its discovered solution from an IRC chat (delimited by backticks) into a concise blog post:
[09:51:30] <bttf> I need help with event listeners. I have a mousedown event attached to the document object, with useCapture: true. However, it _does not_ fire when I click on a particular set of elements on a page ... It's a third party site, and I'm working within a chrome extension. However, any other part of the page fires the mousedown event as expected. Since I'm using the capturing phase, it should be fired always and immediately, right?
[09:51:30] <bttf> What could be blocking it?
[10:07:57] <deepSleep> can you get your listener to fire first?
[10:09:45] <stenno> stoppropagation earlier will prevent that
[10:16:14] <bttf> stopPropogation does not affect event handlers that have useCapture set to true
[10:16:36] <bttf> useCapture essentially reverses the firing order - the highest event listener is fired first
[10:17:00] <bttf> it fires first for all other elements on teh page .... except for these few elements in the hero banner section
[10:19:42] <bttf> ok - i figured it out. honestly it's fkin weird. i think there is some undocumented (or hard to find behavior) wrt chrome and mousedown vs pointerdown events
[10:20:28] <deepSleep> how did you solve it bttf
[10:20:38] <bttf> when i switch to pointerdown, errthang works. i noticed the document obj had pointerdown events defined also at capture phase (by using getEventListeners) .... it may be that if pointerdown event is fired that mousedown is not??
[10:21:22] <bttf> i would expect pointerdown and mousedown to be fired simultaneously
[11:07:55] <bttf> deepSleep i recreated it ..... i think the third party site called preventDefault in their pointerDown event which was also using useCapture: true
[11:07:56] eXtr3m0 (~eXtr3m0@user/eXtr3m0) joined the channel
[11:08:33] <stenno> wouldn't that be stoppropagation and not preventdefault?
[11:08:54] <bttf> neither stoppropagation nor stopimmediatepropogation blocked it
[11:09:13] <stenno> preventdefault doesn't prevent propagation
[11:09:23] <bttf> its techinically not propagating?
[11:09:32] <bttf> preventDefault cancels the event however
[11:09:49] <bttf> these two event handlers are not in the bubbling phase - they are capturing, the same element (document)
[11:10:43] <bttf> so the browser fires pointerdown before mousedown apparently, and since the pointerdown handler cancels the event, mousedown does not fire. i have a brand new app made to repro this
[11:10:52] <deepSleep> bttf, cool cool
[11:11:24] <deepSleep> interesting behavior
[11:13:41] <bttf> omg it gets more interesting lol
[11:13:56] <bttf> so mousedown does not fire ..... but click DOES lol
[11:14:04] <bttf> i hate this
[11:14:18] <stenno> click is after mouseup, right?
[11:14:23] <stenno> hmm
[11:15:04] <bttf> yea i think its mouse down --> mouse up --> click