Gecko:Mouse Wheel Scrolling: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
No edit summary
Line 1: Line 1:
= Overview =
= Overview =


Gecko can have multiple scrollable frames in its content. This documents the detail of mouse wheel scrolling rules in Gecko.
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.
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.
Line 26: Line 26:
By above rules, users can scroll one scrollable frame smoothly. However, when two or more scrollable frames are nested, we need additional rules.
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 an 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.
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.
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.
Line 44: Line 44:
* When a target frame is destroyed.
* 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 an user keeps to turn mouse wheel to unscrollable direction, the transaction will be timed out. This is important for touchpad users, see [https://bugzilla.mozilla.org/show_bug.cgi?id=442774 bug 442774].
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 [https://bugzilla.mozilla.org/show_bug.cgi?id=442774 bug 442774].


= DOMMouseScroll event vs. Scrolling =
= DOM mouse wheel events and Scrolling and other default action =


A native mouse wheel event causes DOMMouseScroll events. The widget code dispatches an nsMouseScroll event when it handles a native mouse wheel event. Then, nsPresShell dispatches DOMMouseScroll events first. If preventDefault() wasn't called, nsEventStateManager tries to scroll a frame by the above rules.
The specification of DOM Level 3 Events defines "wheel" event. We're working on this in [https://bugzilla.mozilla.org/show_bug.cgi?id=719320 bug 719320]. All web developers should handle only this event after it's fixed.


So, the contents can prevent scrolling. At that time, mouse wheel transaction isn't updated.
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 typically fired after a DOMMouseScroll event, however, it may be fired before DOMMouseScroll event when native mouse wheel event causes less than one line or page on Windows.


Note that DOMMouseScroll events are fired by the actual cursor position. So, by the mouse wheel transaction system, the scroll target and the target of the DOMMouseScroll event may be different even if the target of DOMMouseScroll is scrollable.
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. Therefore, DOMMouseScroll event is fired first on Mac.


Windows version of Gecko 1.9.2 and earlier doesn't fire DOMMouseScroll 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 [https://bugzilla.mozilla.org/show_bug.cgi?id=483136 bug 483136].
On Windows, native events tell Gecko how much lines or pages should be scrolled. If the scroll amount is not integer, Gecko dispatches a MozMousePixelScroll event first and stores the amount. When accumulated scroll amount becomes one or more, it dispatches DOMMouseScroll.
 
On the other platforms, widget only dispatches DOMMouseScroll event and nsEventStateManager automatically dispatches MozMousePixelScroll event.
 
If neither 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 [https://bugzilla.mozilla.org/show_bug.cgi?id=483136 bug 483136].


= Override system of system scroll speed =
= Override system of system scroll speed =
Line 95: Line 103:


* We don't send mouse wheel events to windowless plug-ins ([https://bugzilla.mozilla.org/show_bug.cgi?id=359403 bug 359403]).
* We don't send mouse wheel events to windowless plug-ins ([https://bugzilla.mozilla.org/show_bug.cgi?id=359403 bug 359403]).
* When an 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 is turning the mouse wheel but moving the mouse cursor, the transaction will not be finished even if the cursor is moved far.
* When an 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 ([https://bugzilla.mozilla.org/show_bug.cgi?id=428350 bug 428350]).
* 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 ([https://bugzilla.mozilla.org/show_bug.cgi?id=428350 bug 428350]).

Revision as of 01:00, 3 July 2012

Overview

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:

  1. Finds a frame under the mouse cursor.
  2. Checks whether the frame is scrollable to the direction by user operation.
  3. If the frame is scrollable, Gecko scrolls the frame.
  4. 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.

Gecko manages this transaction by nsMouseWheelTransaction which is in nsEventStateManager.cpp. This was implemented in bug 312831.

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 Scrolling and other default action

The specification of DOM Level 3 Events defines "wheel" event. We're working on this in bug 719320. All web developers should handle only this event after it's fixed.

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 typically fired after a DOMMouseScroll event, however, it may be fired before DOMMouseScroll event when native mouse wheel event causes less than one line or page on Windows.

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. Therefore, DOMMouseScroll event is fired first on Mac.

On Windows, native events tell Gecko how much lines or pages should be scrolled. If the scroll amount is not integer, Gecko dispatches a MozMousePixelScroll event first and stores the amount. When accumulated scroll amount becomes one or more, it dispatches DOMMouseScroll.

On the other platforms, widget only dispatches DOMMouseScroll event and nsEventStateManager automatically dispatches MozMousePixelScroll event.

If neither 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.

Acceleration system

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.

The limitation is computed after the overriding and the acceleration are computed. So, the DOMMouseScroll event has original delta value.

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.

Issues

  • 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).