Software engineer, musician, content creator
When your mousedown event doesn't fire
May 23rd, 2023

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.

The Issue

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 Investigation

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.

The Discovery

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.

The Explanation

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.

The Twist

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.

Conclusions

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


GPT-4 Prompt

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
© 2023 Adnan Chowdhury