Javascript Madness Intro

Javascript Madness: Mouse Events

Jan Wolter
Jan 28, 2009

Introduction

This document summarizes the results of some browser tests done during the development of Web Paint-by-Number, a Javascript application that makes intense use of mouse events. It documents inconsistancies in the way different browsers implement mouse events.

The tests were done with the intention of learning just enough to write the code I needed to write. Many, many browsers have been tested over the years. The most recent test on each platform were done with the browser versions in the table below.

Windows Macintosh Linux
Internet Explorer 8.0.6001.18702 5.2 -
Firefox 3.0.5
(Gecko 1.9.0.5)
3.5.7
(Gecko 1.9.1.7)
3.5.7
(Gecko 1.9.1.7)
Opera 10.00 9.10 10.10
Safari 4.0.3 4.0.4
(WebKit 531.21.8)
-
Chrome 3.0.195.38
(Webkit 532.0)
4.0.249.49 beta
(Webkit 532.5)
4.0.249.43
(Webkit 532.5)
Epiphany - - 2.28.2
(Webkit 531.2+)
Konqueror - - 4.3.1

Tests were also performed on Netscape, Mozilla, Camino, Seamonkey and K-Meleon. These all use the same layout engine as Firefox, called "Gecko" and behave the same way as versions of Firefox with the same Gecko version, just as Safari, Chrome, Ephiphay all use the WebKit engine. In this article we will generally refer to Gecko and Webkit version numbers instead of any particular Gecko-based browser version numbers. See the Gecko/Webkit version table for the mapping between browser version numbers and layout engine version numbers.

Previous versions of this document included coverage of the iCab 3 browser, but more recent versions of iCab use WebKit, and so presumably behave exactly like the other WebKit browsers. Since it is unlikely that many web developers will want to go out of their way to support iCab 3, that material has been removed from this document and archived in a separate report on iCab 3.

The script used to collect the test results reported here is available at http://unixpapa.com/js/testmouse.html.

Mouse Buttons and Default Events

When you click on something displayed by your browser, the browser generates some mouse events. Normally these events trigger some default action by the browser, like following a link or bringing up a context menu. But it is possible to set up Javascript event handler functions that called get instead. These can perform some special action, and then they can either cancel the default action, or allow it to go forward.

All browsers let you disable the default action of the left mouse button. I typically do this by ending the event handling function with the following code, which should cover all browsers:

    if (event.preventDefault)
        event.preventDefault();
    else
        event.returnValue= false;
    return false;

However, in some browsers the default actions of the right and middle mouse buttons cannot be disabled, or can only be disabled by changing browser settings. Such changes to browser settings typically effect all pages viewed through the browser, and typically cannot be done from Javascript. Since few users will want to do this, it pretty much means that the those mouse events are not usable from Javascript.

Netscape 4 pioneered this philosophy by making it impossible to disable the URL paste that occurs with a middle click, and requiring a config file edit to disable the context menus that come up with a right click. Firefox and the other Gecko browsers have released control of the right mouse button, but the default action of a middle click can only be disabled by a config file edit. (Update: in the last version of Firefox I tested, config options could be changed by going to the "about:config" URL, but no combination of settings there seemed sufficient to release control of the middle mouse button to Javascript).

The middle and right mouse buttons have never been fully usable in any version of Opera. The precise manner in which they were broken has, however, changed many times over the years:

The middle mouse button in Opera is broken too. It triggers an events, but the default action cannot be disabled. No fiddling with the manner of its brokeness has occured so far.

None of the WebKit browsers seem to have any problem with this. All mouse buttons can be used on all platforms.

So here's the final score card:

Can default mouse actions be disabled?
Left ButtonMiddle ButtonRight Button
Internet Explorer
Safari, Chrome, etc
Konqueror
YesYesYes
Firefox YesConfig file edit onlyYes
Opera 7.23-7.54 YesOnly if user has enabled an obscure option
Other Opera Versions YesNoNo

Event Triggering - Single Clicks

The events that are associated with mouse clicks are:

The mousedown and mouseup events are meant mostly for situations where you depress the mouse button, move the mouse and then release it, say when a section of text is selected or when an object is dragged.

The click and dblclick events are meant mostly for situations where you are simply clicking on a link or a button. A click event normally occurs if there is a mousedown and a mouseup event on the same object. If you move the mouse to point to something else between mousedown and mouseup, then there will be no click event.

The contextmenu event is triggered on some browsers when you right click on something and it's default action is to bring up the browser's standard context menu. I think that in theory this event isn't really a mouse event. It might also be triggered by a keyboard shortcut. However it is usually associated with mouse clicks in practice.

If you do a single mouse click of the left mouse botton, every modern browser I've checked does exactly the same thing. When the mouse goes down, a mousedown event is triggered. When the mouse goes back up, first a mouseup event and then a click event is triggered. Very nice.

Single Click of Left Mouse Button
All Browsers
DOWNmousedown
UPmouseup
click

So, here's what happens when you do click on the right mouse button:

Single Click of Right Mouse Button
Internet Explorer
Safari
Chrome ≥ 1.0 (Windows)
Gecko browsers
Chrome (Linux)
Konqueror Chrome 0.2  Opera  If "right click" option enabled Ephiphany 2.28
Opera 7.23-7.54 Opera ≥ 8.0
DOWN mousedown mousedown
contextmenu
mousedown mousedown  - mousedown mousedown contextmenu
UP mouseup
contextmenu
mouseup mouseup contextmenu
mouseup
 -  - mouseup mouseup

There are no click events for right button clicks in any browser.

Opera normally generates no events for the right mouse button. For some past versions there was an option that could be turned on to make them generate events, but that no longer exists.

Notice that the contextmenu event does not exist in all browsers, and occurs at different times in different browsers. I think this relates to the different behavior of the context menus in different browsers. For example, in Firefox, the context menu comes up when the mouse key is depressed, while, in IE and Chrome, it don't come up until the mouse is released.

There is not a lot of consistency in the behavior of the WebKit browsers on this, though the only one that is distinctly bad is Epiphany, which fails to send a mousedown. The others vary only on the timing of the contextmenu event.

The Opera versions that give you no mouseup are not that much a concern because they are old and the default actions of right clicks can't be disabled from Javascript anyway.

The question of what happens on middle clicks is less academic than it used to be. In the olden days, only Unix machines commonly had three button mice. But now most Windows mice have a scroll wheel between the two buttons that can be clicked and acts as a middle button. However, Firefox and Opera still have default actions associated with middle mouse buttons that cannot be disabled from Javascript, So only in Internet Explorer, Konqueror, and the WebKit browsers does the middle mouse button really work.

Single Click of Middle Mouse Button
Internet Explorer
WebKit
Gecko browsers
Opera ≥ 8.0
Konqueror
Opera < 8.0
DOWN mousedown mousedown mousedown
UP mouseup
click
mouseup  -

Note that there is some disagreement about whether or not there should be a click event for middle buttons and older versions of Opera were broken, but otherwise things are pretty sensible.

Event Triggering - Double Clicks

Now let's turn our attention to double-clicks. Even if you just want to treat double-clicks as two consecutive clicks, you need to pay attention to the weirdnesses here, because strange things happen in many browsers when two clicks happen to be close enough together to register as a double click. Here's what we see with a double click on the left mouse button:

Double Click of Left Mouse Button
Internet Explorer Gecko ≥ 1.7
Opera ≥ 9.10
WebKit ≥ 412 (Win & Mac)
Gecko 1.6
(Windows and Linux)
Webkit (Linux)
Konqueror
Webkit 312 (Mac)
Opera ≥ 8.0 < 9.10 Opera < 8.0
DOWN mousedown mousedown mousedown mousedown mousedown mousedown
UP mouseup
click
mouseup
click
mouseup
click
mouseup
click
mouseup
click
mouseup
click
DOWN  - mousedown mousedown
mousedown
mousedown dblclick
click
click
dblclick
UP mouseup
dblclick
mouseup
click
dblclick
mouseup
click
dblclick
mouseup
dblclick
mouseup mouseup

Internet Explorer has the annoying property of not giving a mousedown on the second click of a double click. In fact there is no event fired then at all.

Ancient versions of the Mozilla browsers made up for IE's lack of a mousedown event on the second click by sending two, so that overall, a double click sent three mousedown events. Macintosh versions of Gecko never had this problem and always behaved the same way that Gecko 1.7 does.

To my astonishment and disgust, new Linux versions of the WebKit browsers Chrome and Epiphany have the same double mousedown bug on double clicks. This bug has been reported to Google.

What older versions of Opera were up to is hard to imagine. That the first click event was on a mouseup, but the second was on mousedown was just the beginning of the madness.

Luckily, recent versions of the Mozilla browser and Opera have adopted sane behaviors, and the older versions are mostly out of circulation by now (Gecko 1.6 was last used in Firefox 0.8). The Safari developers wisely chose this as one of their rare instances of incompatibility with IE and chose to emulate Gecko instead.

Right button double click events are listed in the next table. Newer versions of Opera are not listed here, because it is no longer possible to execute a right button double click in Opera. The first click cannot be stopped from activating the context menu, so the second click is caught by the context window, not the browser window.

Double Click of Right Mouse Button
Internet Explorer
Safari
Chrome 1.0
Gecko ≥ 1.7 Chrome 0.2 Gecko 1.6
(Windows and Linux)
Chrome (Linux)
Epiphany 2.28 Konqueror
DOWN mousedown mousedown
contextmenu
mousedown mousedown
contextmenu
contextmenu mousedown
UP mouseup
contextmenu
mouseup contextmenu
mouseup
mouseup mouseup mouseup
DOWN mousedown mousedown
contextmenu
mousedown mousedown
contextmenu
mousedown
contextmenu
contextmenu
contextmenu
mousedown
UP mouseup
contextmenu
mouseup contextmenu
mouseup
mouseup mouseup mouseup

Happily, IE doesn't omit the second mousedown event in this case, but old versions of Gecko and new Linux WebKit browsers stutter on it just as they do on all the other buttons. Epiphany combines the stutter with missing mousedown events to achieve glorious new levels of brokeness.

If we ignore the stutters, then all browsers treat this simply as two clicks in a row, not a doubleclick.

Opera is omitted from this table because it normally doesn't generate right click events. In the old versions which had an option to enable right clicks, double clicks were just treated as two single clicks.

Finally, here are the events for double-clicks on the middle button, a button that isn't actually usable Gecko and Opera. Like the right click case, this is mostly just treated as two single clicks.

Double Click of Middle Mouse Button
Internet Explorer WebKit
(Win & Mac)
Gecko ≥ 1.7
Opera ≥ 8.0
Konqueror
Gecko 1.6
(Windows and Linux)
WebKit
(Linux)
Opera < 8.0
DOWN mousedown mousedown mousedown mousedown mousedown mousedown
UP mouseup
click
mouseup
click
mouseup mouseup
click
mouseup  -
DOWN mousedown mousedown mousedown mousedown
mousedown
mousedown
mousedown
mousedown
UP mouseup
click
mouseup
click
dblclick
mouseup mouseup mouseup
click
dblclick
 -

This is pretty much what you'd expect except that here, for once, the WebKit browsers do not perfectly emulate IE. Of course, it makes perfect sense that dblclick should be fired if click is, and firing an extra event is unlikely to cause compatibility problems, so this is probably a good choice.

Identifying Mouse Buttons

When Javascript recieves a mouse click event, it is generally interesting to know which mouse button was clicked. The event object that is passed to Javascript contains two fields that may be used to check this, event.which and event.button. These will contain numerical values to identify the mouse button.

It used to be that every browser used a different combination of values to indicate the buttons, but there has been a gradual convergence on a standard now, so all browsers except IE work alike:

Mouse button ID values in various browser
Internet Explorer Netscape 4 Gecko ≥ 1.0
Webkit ≥ 523
Opera ≥ 8.0
Konqueror ≥ 4.3
Gecko 0.9 Opera < 8.0 Konqueror ≤ 3.5 Webit 412
event.button LEFT
BUTTON
1* undefined 0 1 1 1 1
MIDDLE
BUTTON
4* undefined 1 2 3 4  
RIGHT
BUTTON
2* undefined 2 3 2 2  
event.which LEFT
BUTTON
undefined 1 1 1 1 1 1
MIDDLE
BUTTON
undefined 2 2 2 3 2  
RIGHT
BUTTON
undefined 3 3 3 2 3  
* for mousedown and mouseup only, zero for click, dblclick and contextmenu events.

The event.which value was originally used in Netscape, and the event.button value was originally used in Internet Explorer. Later browers used both, and messed them both up. Event.button was an especially hopeless mess, with different browsers returning 1, 2, 3 or 4 for a middle mouse click. Event.which is better, except that old versions of Opera got the middle and right mouse buttons mixed up, and IE doesn't return it at all.

Note that IE returns useful values in event.button only on mousedown and mouseup events. It is not possible to identify the mouse button for a click, dblclick or contextmenu event because event.button will be zero regardless of which button was pressed.

A further IE oddity becomes apparent if you hold down more than one mouse button at a time. On mousedown events, IE returns the boolean-or of the values for all mouse buttons currently depressed. So if you hold down both the left and right mouse buttons, the second mousedown event will have event.button=3. IE does not do this on mouseup events, and no other browsers do this on any events.

Newer versions of most browsers except IE have converged on column three of this table, which is the W3C standard. It's encouraging to increasingly see standards taking hold where they exist.

Some mice now have more buttons than three buttons. I've done a few tests of this on a Linux system with a five button mouse. In Gecko and Opera, the extra buttons have the same values as the left mouse button, event.which=1, and event.button=0. In Konqueror 3.5 both extra buttons give event.which=65536, and event.button=0, which are at least different from any of the other three buttons, but may well be intended as error codes.

The end result? While there is no browser-independent way to recognize which mouse button is which for all the browsers surveyed, you can come extremely close. The following test works for all browsers except Opera 7. (It mixes up the right and middle mouse buttons in Opera 7, but Opera 7 is pretty old, there were only a few versions where the right and middle buttons even worked, and then only if the user set an obscure option, so you really don't care.) It also doesn't correctly handle simultaneous clicks of multiple mouse buttons in IE. To do that your application needs to track which buttons were reported to be down in previous mousedown events so it can figure out which one was added.

    if (event.which == null)
       /* IE case */
       button= (event.button < 2) ? "LEFT" :
                 ((event.button == 4) ? "MIDDLE" : "RIGHT");
    else
       /* All others */
       button= (event.which < 2) ? "LEFT" :
                 ((event.which == 2) ? "MIDDLE" : "RIGHT");
Click here with various mouse buttons to test

If you don't have any other compelling reason to prefer one event over another, it's probably generally best to attach your event handlers to mouseup. IE has annoying little problems with the others (click doesn't identify the mouse button, and mousedown ORs together mouse button values and isn't fired on the second click of a double click) and WebKit versions of Epiphany fail to send mousedown. Triggering on mouseup also means that users get a last chance to change their mind about clicking on something - they can move their mouse off the control before releasing the button.

Finding Mouse Positions

So, you want to know the coordinates where a mouse click occurred? The mouse event object does include coordinates. In fact there may be as many as six different sets of coordinates in some browsers, and, naturally, they are a swamp of browser incompatibilities. Luckily, I don't have to document them in detail, because Quirksmode already has. I will however give you the short answer, which depends on which coordinates you want:

Mouseover and Mouseout

There are some other mouse events. The mouseover and mouseout events are triggered whenever the mouse moves on to or off of an object. The mousemove event is triggered whenever the mouse is moved. These events are handled pretty consistently in different browsers so far as I have been able to tell, but there are some oddities.

A test script for the mouseover and mouseout events is available at http://unixpapa.com/js/testover.html. The test script shows two nested <DIV> blocks, with some text inside the inner one. It's important to note that in all browsers, when the mouse moves from the outer block into the inner block, there is a mouseout event fired for the outer block. The concept is that the mouse is never "in" more than one object at a time, and the one it is "in" is the frontmost of those in whose enclosing box it falls.

Internet Explorer has some additional events, mouseenter and mouseleave which fire only when you cross the outer boundaries of objects. These are more convenient for many applications, however no other browser supports these events.

Safari 3 and other modern Webkit browsers seem to handle mouseover and mouseout correctly, but older versions of Safari had real problems. They also generated mouseover and mouseout events for text nodes. So, on the test page, when you moved the mouse over the text inside the inner <DIV> you would get a mouseout event on the <DIV> and then a mouseover event on the text. I don't think other browsers can even fire events on text nodes.

In Safari 1.3, the mouseover event fires when the mouse pointer enters the rectangular box surrounding the text. This was strange but understandable. In Safari 2.0, when you swept the mouse pointer over some text in a <DIV> you would get dozens and dozens of mouseover and mouseout events. At first I thought they were triggering events on the actual boundaries of the letters, but that didn't seem to be the case. Then I noticed that on the text you get only mouseout events, and on the <DIV> you only get mouseover events, so basically the firing of these events as you go over text was just completely wacky.

In applications it's usually the spurious mouseout events on the <DIV> that cause problems. In Safari 2.0, you only get spurious mouseover events, which are usually less of a problem. The best general solution I've found for Safari 1.3 is to have the event handler on the <DIV> check if the mouse is still actually within the bounds of the <DIV> and ignore the event if it is. Since finding mouse coordinates and block borders is pretty browser dependent too, and takes a bit of computation, I tend to perform that test only on Safari.

Drag and Text Selection Events

Some browsers have special support for dragging and dropping HTML options using the mouse. I've never wanted to drag and drop anything, so I haven't really studied these and do not have any data on cross browser compatibility data. Reading the Mozilla drag and drop documentation might be a start for those interested in this.

However, even if you don't want to drop and drag anything, it is worth knowing a bit about these things, because they can interfere with ordinary mousing around. Sometimes the browser will decide that the user is trying to do a drag, and everything will go wrong. Events like mousemove and mouseover stop firing when the browser thinks a drag is in progress.

In most browsers, just disabling the browser default action on the mousedown event (see above for how to do this). seems to disable this. I'm pretty sure that in older versions of gecko it was necessary to disable the default action on the dragstart event, but this no longer seems necessary.

There are also events fired in some browsers when text is selected with the mouse. These also may be undesirable if you are trying to have something else happen with mouse clicks. I've never had any luck disabling these, though I've heard that canceling the browser default on the selectstart event can help with this on some browsers.