Gecko:Mouse Wheel Scrolling
Gecko can have multiple scrollable frames in its content. This page documents the detail of mouse wheel scrolling rules in Gecko.
This first version is written for Gecko 1.9.2. But this document isn't stable, so, when this document will be updated for new behavior, the writer must write the version clearly.
Gecko honors the mouse cursor position
Basically, turning mouse wheel scrolls a scrollable view under the mouse cursor. This behavior was designed in bug 97283.
Note that on Windows, most mouse drivers honors keyboard focus rather than the mouse cursor position. However, Internet Explorer and other web browsers honor the cursor position too. So, it's not a problem, probably.
When there are two or more scrollable frames under the mouse cursor, only one frame should be scrolled. Gecko decides one scrollable frame for the target by following rules:
- Finds a frame under the mouse cursor.
- Checks whether the frame is scrollable to the direction by user operation.
- If the frame is scrollable, Gecko scrolls the frame.
- Otherwise, repeats the steps with its ancestor frames.
There is a special case. If gecko finds a drop down frame of a select element, it stops going up the frame hierarchy.
When it reaches to fixed position frame, it tries to scroll root frame of the content (Bug 258006).
Mouse wheel transaction
By above rules, users can scroll one scrollable frame smoothly. However, when two or more scrollable frames are nested, we need additional rules.
For example, there is a page body which is scrollable and has a scrollable sub-frame (e.g., listbox of select element with multiple attribute, div element with overflow property, iframe element). When a user is scrolling down the sub-frame, it will be reached to the end of its content. However, some mouse wheel events which were fired after that will scroll the body unexpectedly.
And there is another example, when the user is scrolling the page body, the sub-frame may come under the mouse cursor. Then, the sub-frame intercepts the mouse wheel events unexpectedly.
We can fix these problems by a simple idea. That is, all users may want to scroll only one scrollable frame at one time. So, we can assume that two or more mouse wheel events which are fired between short term should scroll only one scrollable frame. The frame must be under the cursor at the first mouse wheel event.
The rules for end of a transaction:
- When no mouse wheel events are fired during specified term after latest mouse wheel event. The length is defined by mousewheel.transaction.timeout. The value means millisecond and the default value is 1500.
- When the mouse cursor moves to outside of the targeted scrollable frame.
- When the mouse cursor moves inside the targeted frame. However, we need to ignore some mouse move events which are fired unexpectedly when the user turned wheel. mousewheel.transaction.ignoremovedelay defines the time of ignoring mouse move events before and after a mouse wheel event. The value is millisecond, the default value is 100.
- When a keydown or keyup or keypress event is fired.
- When a mouse button down or mouse button up or click or double click event is fired.
- When a context menu key event or dragdrop_drop event is fired.
- When a target frame is destroyed.
Note that the transaction is updated only when a mouse wheel event scrolls the target frame actually. I.e., when a user keeps to turn mouse wheel to unscrollable direction, the transaction will be timed out. This is important for touchpad users, see bug 442774.
DOM mouse wheel events and Default action
And also, there are two legacy mouse wheel events which are non-standard. One is DOMMouseScroll event. This is typically fired when mouse wheel is turned one or more lines or pages. The other is MozMousePixelScroll. This event tells web applications how much pixels should be scrolled. It's always fired after a DOMMouseScroll event, however, it may be fired without DOMMouseScroll event when native mouse wheel event causes less than one line or page.
On Mac, native events tell Gecko how much lines should be scrolled first. After that, one or more native pixel scroll events may be fired for smooth scroll. widget/cocoa dispatches widget::WheelEvent with nsIDOMWheelEvent::DOM_DELTA_PIXEL if the mouse (or touchpad) tells Gecko how much pixels should be scrolled. Otherwise, i.e., it tells Gecko only how much lines should be scrolled, it dispatches the event with nsIDOMWheelEvent::DOM_DELTA_LINE.
On Windows, native events tell Gecko how much lines or pages should be scrolled. So, widget/windows dispatches widget::WheelEvent with nsIDOMWheelEvent::DOM_DELTA_LINE or nsIDOMWheelEvent::DOM_DELTA_PAGE. And either the deltaX or deltaY values may be non-integer value.
On the other platforms, widget dispatches widget::WheelEvent with nsIDOMWheelEvent::DOM_DELTA_LINE.
If neither wheel, DOMMouseScroll nor MozMousePixelScroll is consumed by peventDefault(), nsEventStateManager performs a default action. There are four default actions: scroll, going back or forward history, zoom in or out, and doing nothing.
So, the contents can prevent scrolling. Then, mouse wheel transaction isn't updated.
Windows version of Gecko 1.9.2 and earlier doesn't fire DOM mouse wheel events when the mouse cursor is on a windowed plug-in and it consumes native mouse wheel events. This is going to be improved in bug 483136.
Override system of system scroll speed
We're providing an override mechanism of system scroll speed because the default system scrolling speed of Windows is slower than WebKit's scrolling speed. This was suggested for alternative way of the acceleration system (see next section).
This is enabled in default settings only on Windows. mousewheel.system_scroll_override_on_root_content.enabled can switch it.
On Windows, only when the system scroll speed settings are not customized by user or mouse driver, this overrides the scrolling speed. On the others, this always overrides the speed.
The ratio can be specified by hidden prefs. mousewheel.system_scroll_override_on_root_content.vertical.factor is for vertical scrolling event. mousewheel.system_scroll_override_on_root_content.horizontal.factor is for horizontal scrolling event. The values are used as 1/100 (i.e., the default value 200 means 2.0).
nsEventStateManager multiplies the scrolling speed by the ratio when it executes to scroll a root scrollable view of a document. So, DOMMouseScroll event's delta value has never been overwritten by this.
See also bug 513817.
See Firefox/Projects/AcceleratedScrolling for the detail.
This has many problems, so, this has been disabled in default settings.
Acceleration is computed after the override system is done.
Limitation of scroll amount
Even if nsMouseScroll event has larger delta than the target frame size, Gecko scrolls it just one page because if some lines are skipped, the user may not find the lines. This is especially serious problem on listbox.
This limitation doesn't change the DOM event's delta vales. This is used only at handling the default action.
Stating with Mozilla 19 (Firefox 19) or later, this limitation is ignored when the delta_multiplier_* pref for the event value equals or is larger than 100000 (x1000). This allows users to scroll to start or end of the page with only one manipulation.
Minimum scroll amount at scrolling by line
When a wheel event whose deltaMode indicates line scroll scrolls a content, nsEventStateManager refers the line height of the content. However, if the line height is zero, e.g., font-size: 0; is specified, scroll is never performed.
Against this issue, Mozilla 17 (Firefox 17) or later supports mousewheel.min_line_scroll_amount pref. If line height of the scroll target is smaller than this value in pixels, a wheel event which represents one line scroll scrolls this amount.
However, this pref doesn't affect to the DOM event's delta values. This is used only at handling the default action.
Preferences for customizing delta values and default action
There are some prefs which can customize the delta values or default action. These prefs are applied only to trusted events.
Mozilla 17 (Firefox 17) or later
Starting Mozilla 17 (Firefox 17), following new prefs are available.
- Customizing deltaX value of "wheel" event. The value 100 means 1.0. In nsEventStateManager, the deltaX value which is caused by a native event multiplies by the pref value. When two or more modifier keys are held, "default" pref is used.
- Customizing deltaY value of "wheel" event. The value 100 means 1.0. In nsEventStateManager, the deltaY value which is caused by a native event multiplies by the pref value. When two or more modifier keys are held, "default" pref is used.
- Customizing deltaZ value of "wheel" event. The value 100 means 1.0. In nsEventStateManager, the deltaZ value which is caused by a native event multiplies by the pref value. When two or more modifier keys are held, "default" pref is used. NOTE: deltaZ value never becomes non-zero if it's a trusted event. So, these prefs never work actually.
- Customizing default action of "wheel" event. The value 0 means "Do nothing", 1 means "Scroll contents", 2 means "Go back or forward in the history", 3 means "Zoom in or out the contents". You cannot specify different action to vertical wheel operation and horizontal wheel operation because both deltaX and deltaY values can be non-zero same time. So, specifying different action doesn't make sense.
Note that unlike 16 or earlier, you cannot specify the unit of delta values such as scrolling by pixel, line or page. I.e., you cannot specify the deltaMode value of DOM WheelEvent with any preferences. On Windows, the scroll unit is by line or page with mouse wheel. That depends on the system settings. On Mac, the scroll unit is by pixel or line. That depends on whether the device supports pixel scroll or not. On Linux, the scroll unit is always by line.
Mozilla 16 (Firefox 16) or earlier
Mozilla 16 (Firefox 16) or earlier, following prefs are available.
- If the value is true, native delta value is used. Otherwise, it's false, numlines pref value replaces the delta value. If two or modifier keys are pressed, a pref of them is used. If Shift key is pressed, "withshiftkey" is used. Otherwise, if Control key is pressed, "withcontrolkey" is used. Otherwise, if Alt key is pressed, "withaltkey" is used. Otherwise, "withmetakey" is used.
- If "sysnumlines" is false, the pref value replaces native delta value. If two or modifier keys are pressed, a pref of them is used. If Shift key is pressed, "withshiftkey" is used. Otherwise, if Control key is pressed, "withcontrolkey" is used. Otherwise, if Alt key is pressed, "withaltkey" is used. Otherwise, "withmetakey" is used.
- These prefs specify the default action of wheel events. The value 0 means "Scroll by lines", 1 means "Scroll by pages", 2 means "Go back or forward in the history", 3 means "Zoom in or out contents", 4 means "Scroll by pixels". If two or modifier keys are pressed, a pref of them is used. If Shift key is pressed, "withshiftkey" is used. Otherwise, if Control key is pressed, "withcontrolkey" is used. Otherwise, if Alt key is pressed, "withaltkey" is used. Otherwise, "withmetakey" is used.
Hack for Thinkpad Trackpoint scrolling (Windows)
The Thinkpad Trackpoint scrolling drivers make certain assumptions about Gecko's internal window structures that are no longer valid as of Gecko 1.9.2. For backwards compatibility a hack has been inserted that creates fake invisible scrollbars so that the Trackpoint drivers will continue to work. Because this hack can itself break other drivers that make other assumptions about Gecko's window structures, Gecko uses the hack only when a Trackpoint driver is detected. Gecko looks for the Trackpoint driver by checking for the existence of several registry keys that various versions of the Trackpoint driver use.
The preference ui.trackpoint_hack.enabled controls whether or not Gecko will activate the Trackpoint hack. The default value of -1 tells Gecko to try to detect the Trackpoint driver and to use the hack if one is found. A value of 0 tells Gecko to never use the hack, and a value of 1 tells Gecko to always use the hack. It is our hope that after this issue is resolved by the driver manufacturer that this hack can be removed in future versions of Gecko.
See bug 507222 for more details.
- We don't send mouse wheel events to windowless plug-ins (bug 359403).
- When a user is turning the mouse wheel but moving the mouse cursor, the transaction will not be finished even if the cursor is moved far.
- When a user finished scrolling a sub-frame but the sub-frame is clipped by its ancestor frame, the user doesn't still watch the all contents of the sub-frame (bug 428350).