Unusual behaviour in Firefox when using ‘preventDefault()’ on ‘mousedown’ event

Firefox completely blocks css :active pseudo-class when using PreventDefault() on mousedown event, unlike other browsers.

This is undefined behaviour, and can lead to some hard to fix bugs, especially when you had no idea Firefox implemented this.

In the example below, we have two buttons, both with :active set to change the background color. The top one has ‘preventDefault()’ on ‘mousedown’, the bottom does not.

In Firefox, the :active state is prevented. If you are not on Firefox both elements should be pink when clicked.

The ‘click’ event still fires in all browsers.

Why does this happen?

Because it was not specified at the time when browsers were implementing, exactly how and when to trigger the pseudo-classes. As Boris Zbarsky said 7 years ago:


The spec doesn’t define anything about when things are actually considered “active”.

Boris Zbarsky – Mozilla Engineer

So it was up to them to figure it out.

Github user ‘FremyCompany’ breaks down what is happening:

Firefox adds “active” as a “default action” of mousedown (hence cancellable), while active is already set before firing mousedown in the other browsers…


FWIW, the processing for “:active” in Edge happens in the input/output message pipeline directly, (way) before event deciding to create a “mousedown” event.

FremyCompany

When might this be an issue?

If you want to prevent an element gaining focus on ‘click’ but still allow users to tab to this element. The usual way of achieving this is to simply use ‘preventDefault()’, on the ‘mousedown’ event. Like I have done in the previous example.

While this works great, it completely blocks any styling you applied to the elements ‘:active’ state.

A workaround

One workaround is to detect if the user is using Firefox browser on pageload. Then apply custom styles on ‘mousedown’, and ‘mouseup’ events.

First check for Firefox, and set a flag:

let browser = { 
  isFirefox: false, 
  check(){ 
    if (typeof InstallTrigger !== 'undefined') { 
      this.isFirefox = true; 
      //you might also want to add a class to the body object        
      document.body.classList.add('firefox'); 
     } 
   } 
} 
browser.check();

This work by seeing if the InstallTrigger object exists. This is a Firefox specific web API:

It is used for triggering the download and installation of an add-on (or anything packaged in an .xpi file) from a Web page, using JavaScript code to kick off the install process.

MDN

Add some event listeners:

document.addEventListener('mousedown', (e)=> {
  event.target.classList.add('ff-active');
});
document.addEventListener('mouseup', (e)=> {
  event.target.classList.remove('ff-active');
});

And then update our css:

.btn:active,
.btn.ff-active {
  background-color: pink;
}

It works!:

Well, kind of..What happens when you ‘mousedown’ on the first element, and them move your mouse away from it? It gets stuck with the ‘:active’ styling. We need to handle that also.

By tracking ‘:active’ element:

document.addEventListener('mousedown', (e) => {
  e.target.classList.add('ff-active');
  browser.ffActiveEl = e.target;
});
document.addEventListener('mouseup', (e) => {
  browser.ffActiveEl.classList.remove('ff-active');
});

Final result:

As you can see, Firefox users can now click on the first element without gaining focus, or tab to either elements. While including ‘:active’ style when clicking.

You might also noticed another issue, Firefox keeps the element ‘:hover’ state active when you ‘mousedown’ on it, even if you leave the element, until you let go. This behaviour is also disabled with ‘preventDefault()’. That is something to take into account, if it affects your use case.

When will this be ‘fixed’?

We don’t know. But, people at Mozilla are aware of it, and open to changing the behaviour it seems:


I am not wedded to the current Firefox behavior, and am willing to change it, but not willing to change it twice. Hence it would be good to have a clear spec for what the behavior should be.


Boris Zbarsky – Mozilla Engineer

But, that was in 2018, and Mozilla currently has the priority set to ‘P5 normal’ (with P1 being highest, P5 lowest). So who knows how long it might take until then.