From 7a88278b79818b0ac9ab454c8c6ebff511fe7fd6 Mon Sep 17 00:00:00 2001 From: Mustaq Ahmed Date: Wed, 29 Jan 2025 16:08:50 -0500 Subject: [PATCH] Update boundary event behavior for a stationary pointing device. (#532) PEWG decided in #529 that the PointerEvents spec should match the UIEvent spec behavior for non-moving pointing device: boundary events should fire after a layout change, and no move events should fire. --- index.html | 69 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/index.html b/index.html index 35e4b69..ccd93cc 100644 --- a/index.html +++ b/index.html @@ -389,6 +389,7 @@

Chorded button interactions

Instead, chorded button presses can be detected by inspecting changes to the button and buttons properties. The button and buttons properties are inherited from the {{MouseEvent}} interface, but with a change in semantics and values, as outlined in the following sections.

The modifications to the button and buttons properties apply only to pointer events. However for click, auxclick and contextmenu the value of button and buttons MUST follow [[UIEVENTS]], as is the case for compatibility mouse events .

+

The button property

To identify button state transitions in any pointer event (and not just {{GlobalEventHandlers/pointerdown}} and {{GlobalEventHandlers/pointerup}}), the button property indicates the device button whose state change fired the event.

@@ -406,6 +407,7 @@

The button property

During a mouse drag, the value of the button property in a {{GlobalEventHandlers/pointermove}} event will be different from that in a mousemove event. For example, while moving the mouse with the right button pressed, the {{GlobalEventHandlers/pointermove}} events will have the button value -1, but the mousemove events will have the button value 2.
+

The buttons property

The buttons property gives the current state of the device buttons as a bitmask (same as in MouseEvent, but with an expanded set of possible values).

@@ -423,6 +425,7 @@

The buttons property

+

The primary pointer

In a multi-pointer (e.g. multi-touch) scenario, the isPrimary property is used to identify a master pointer amongst the set of active pointers for each pointer type.

@@ -443,7 +446,7 @@

Firing events using the PointerEvent interface

To fire a pointer event named |e| means to [=fire an event=] named |e| using PointerEvent whose attributes are set as defined in {{PointerEvent}} Interface and Attributes and Default Actions.

If the event is not a {{GlobalEventHandlers/gotpointercapture}}, {{GlobalEventHandlers/lostpointercapture}}, click, auxclick or contextmenu event, run the process pending pointer capture steps for this PointerEvent.

-

The target object at which the event is fired is determined as follows:

+

Determine the target at which the event is fired as follows:

+
+

Boundary events caused by layout changes

+

A pointing device that moved relative to the screen surface or underwent some change in any of its properties fires various events as defined in Pointer Event types. For a stationary pointing device (that neither moved relative to the screen surface nor underwent any change in any properties), the user agent MUST fire certain boundary events after a layout change that affected the hit test target for the pointer, see {{GlobalEventHandlers/pointerover}}, {{GlobalEventHandlers/pointerenter}}, {{GlobalEventHandlers/pointerout}} and {{GlobalEventHandlers/pointerleave}} for details. The user agent MAY delay the firing of these boundary events because of performance reasons (e.g. to avoid too many hit-tests or layout changes caused by boundary event listeners).

+
A stationary pointing device (that neither moved relative to the screen surface nor underwent any change in any properties) never fires a {{GlobalEventHandlers/pointermove}} event.
+
+

Converting between tiltX / tiltY and altitudeAngle / azimuthAngle

Pointer Events include two complementary sets of attributes to express the orientation of a transducer relative to the X-Y plane: tiltX / tiltY (introduced in the original Pointer Events specification), and azimuthAngle / altitudeAngle (adopted from the Touch Events - Level 2 specification).

@@ -716,19 +725,33 @@

Converting between tiltX / tiltY and altitud

+

Pointer Event types

Below are the event types defined in this specification.

In the case of the primary pointer, these events (with the exception of {{GlobalEventHandlers/gotpointercapture}} and {{GlobalEventHandlers/lostpointercapture}}) may also fire compatibility mouse events.

The pointerover event

-

The user agent MUST fire a pointer event named {{GlobalEventHandlers/pointerover}} when a pointing device is moved into the hit test boundaries of an element. Note that setPointerCapture() or releasePointerCapture() might have changed the hit test target. Also note that while a pointer is captured it is considered to be always inside the boundaries of the capturing element for the purpose of firing boundary events. The user agent MUST also fire this event prior to firing a {{GlobalEventHandlers/pointerdown}} event for devices that do not support hover (see {{GlobalEventHandlers/pointerdown}}).

+

The user agent MUST fire a pointer event named {{GlobalEventHandlers/pointerover}} when any of the following occurs:

+
    +
  • The step to determine the target of a pointer detects that the pointer has moved into an element.
  • +
  • A layout change causes the hit test boundaries of an element to move underneath an uncaptured pointer of a stationary pointing device.
  • +
  • Before the user agent fires a {{GlobalEventHandlers/pointerdown}} event for a device that does not support hover (see {{GlobalEventHandlers/pointerdown}}).
  • +
+

The pointerenter event

-

The user agent MUST fire a pointer event named {{GlobalEventHandlers/pointerenter}} when a pointing device is moved into the hit test boundaries of an element or one of its descendants, including as a result of a {{GlobalEventHandlers/pointerdown}} event from a device that does not support hover (see {{GlobalEventHandlers/pointerdown}}). Note that setPointerCapture() or releasePointerCapture() might have changed the hit test target. Also note that while a pointer is captured it is considered to be always inside the boundaries of the capturing element for the purpose of firing boundary events. This event type is similar to {{GlobalEventHandlers/pointerover}}, but differs in that it does not bubble.

+

The user agent MUST fire a pointer event named {{GlobalEventHandlers/pointerenter}} when any of the following occurs:

+
    +
  • The step to determine the target of a pointer detects that the pointer has moved into an element or one of its descendants.
  • +
  • A layout change causes the hit test boundaries of an element or one of its descendants to move underneath an uncaptured pointer of a stationary pointing device.
  • +
  • Before the user agent fires a {{GlobalEventHandlers/pointerdown}} event for a device that does not support hover (see {{GlobalEventHandlers/pointerdown}}).
  • +
+
This event type is similar to {{GlobalEventHandlers/pointerover}} but with two differences: {{GlobalEventHandlers/pointerenter}} does not bubble, and its dispatch considers the hit test boundaries of even descendant elements.
There are similarities between this event type, the mouseenter event described in [[UIEVENTS]], and the CSS :hover pseudo-class described in [[CSS21]]. See also the {{GlobalEventHandlers/pointerleave}} event.
+

The pointerdown event

The user agent MUST fire a pointer event named {{GlobalEventHandlers/pointerdown}} when a pointer enters the active buttons state. For mouse, this is when the device transitions from no buttons depressed to at least one button depressed. For touch, this is when physical contact is made with the digitizer. For pen, this is when the pen either makes physical contact with the digitizer without any button depressed, or transitions from no buttons depressed to at least one button depressed while hovering.

@@ -736,6 +759,7 @@

The pointerdow

For input devices that do not support hover, the user agent MUST also fire a pointer event named {{GlobalEventHandlers/pointerover}} followed by a pointer event named {{GlobalEventHandlers/pointerenter}} prior to dispatching the {{GlobalEventHandlers/pointerdown}} event.

Authors can prevent the firing of certain compatibility mouse events by canceling the {{GlobalEventHandlers/pointerdown}} event (if the isPrimary property is true). This sets the PREVENT MOUSE EVENT flag on the pointer. Note, however, that this does not prevent the mouseover, mouseenter, mouseout, or mouseleave events from firing.

+

The pointermove event

The user agent MUST fire a pointer event named {{GlobalEventHandlers/pointermove}} when a pointer changes any properties that don't fire @@ -745,6 +769,7 @@

The pointermov The coalesced events information will be exposed via the getCoalescedEvents() method for the single dispatched {{GlobalEventHandlers/pointermove}} event. The final coordinates of such events should be used for finding the target of the event.

+

The pointerrawupdate event

The user agent MUST fire a pointer event @@ -775,6 +800,7 @@

The pointerraw A {{GlobalEventHandlers/pointerrawupdate}} listener should only be added if JavaScript needs high frequency events and can handle them just as fast. In these cases, there is probably no need to listen to other types of pointer events.

+

The pointerup event

The user agent MUST fire a pointer event named {{GlobalEventHandlers/pointerup}} when a pointer leaves the active buttons state. For mouse, this is when the device transitions from at least one button depressed to no buttons depressed. For touch, this is when physical contact is removed from the digitizer. For pen, this is when the pen is removed from the physical contact with the digitizer while no button is depressed, or transitions from at least one button depressed to no buttons depressed while hovering.

@@ -783,39 +809,47 @@

The pointeru

The user agent MUST also implicitly release the pointer capture if the pointer is currently captured.

For mouse (or other multi-button pointer devices), this means {{GlobalEventHandlers/pointerdown}} and {{GlobalEventHandlers/pointerup}} are not fired for all of the same circumstances as mousedown and mouseup. See chorded buttons for more information.

+

The pointercancel event

The user agent MUST fire a pointer event named {{GlobalEventHandlers/pointercancel}} when it detects a scenario to suppress a pointer event stream.

The values of the following properties of the {{GlobalEventHandlers/pointercancel}} event MUST match the values of the last dispatched pointer event with the same pointerId: width, height, pressure, tangentialPressure, tiltX, tiltY, twist, altitudeAngle, azimuthAngle, pointerType, isPrimary, and the coordinates inherited from [[UIEVENTS]]. The coalescedEvents and predictedEvents lists in the {{GlobalEventHandlers/pointercancel}} event MUST be empty, and the event's {{Event/cancelable}} attribute MUST be false.

+

The pointerout event

The user agent MUST fire a pointer event named {{GlobalEventHandlers/pointerout}} when any of the following occurs:

    -
  • The pointing device is moved out of the hit test boundaries of an element. Note that setPointerCapture() or releasePointerCapture() might have changed the hit test target and while a pointer is captured it is considered to be always inside the boundaries of the capturing element for the purpose of firing boundary events.
  • -
  • After firing the {{GlobalEventHandlers/pointerup}} event for a device that does not support hover (see {{GlobalEventHandlers/pointerup}}).
  • +
  • The step to determine the target of a pointer detects that the pointer has moved out of an element.
  • +
  • A layout change causes the hit test boundaries of an element to move away from underneath an uncaptured pointer of a stationary pointing device.
  • +
  • After the user agent fires a {{GlobalEventHandlers/pointerup}} event for a device that does not support hover (see {{GlobalEventHandlers/pointerup}}).
  • The user agent has detected a scenario to suppress a pointer event stream.
+

The pointerleave event

The user agent MUST fire a pointer event named {{GlobalEventHandlers/pointerleave}} when any of the following occurs:

    -
  • The pointing device is moved out of the hit test boundaries of an element and all of its descendants. Note that setPointerCapture() or releasePointerCapture() might have changed the hit test target and while a pointer is captured it is considered to be always inside the boundaries of the capturing element for the purpose of firing boundary events.
  • -
  • After firing the {{GlobalEventHandlers/pointerup}} event for a device that does not support hover (see {{GlobalEventHandlers/pointerup}}).
  • +
  • The step to determine the target of a pointer detects that the pointer has moved out of an element and all of its descendants.
  • +
  • A layout change causes the hit test boundaries of an element and all of its descendants to move away from underneath an uncaptured pointer of a stationary pointing device.
  • +
  • After the user agent fires the {{GlobalEventHandlers/pointerup}} event for a device that does not support hover (see {{GlobalEventHandlers/pointerup}}).
  • The user agent has detected a scenario to suppress a pointer event stream.
-

This event type is similar to {{GlobalEventHandlers/pointerout}}, but differs in that it does not bubble and that it MUST not be fired until the pointing device has left the boundaries of the element and the boundaries of all of its descendants.

-
There are similarities between this event type, the mouseleave event described in [[UIEVENTS]], and the CSS :hover pseudo-class described in [[CSS21]]. See also the pointerenter event.
+
This event type is similar to {{GlobalEventHandlers/pointerout}} but with two differences: {{GlobalEventHandlers/pointerleave}} does not bubble, and its dispatch considers the hit test boundaries of even descendant elements.
+
There are similarities between this event type, the mouseleave event described in [[UIEVENTS]], and the CSS :hover pseudo-class described in [[CSS21]]. See also the {{GlobalEventHandlers/pointerenter}} event.
+

The gotpointercapture event

The user agent MUST fire a pointer event named {{GlobalEventHandlers/gotpointercapture}} when an element receives pointer capture. This event is fired at the element that is receiving pointer capture. Subsequent events for that pointer will be fired at this element. See the setting pointer capture and process pending pointer capture sections.

+

The lostpointercapture event

The user agent MUST fire a pointer event named {{GlobalEventHandlers/lostpointercapture}} after pointer capture is released for a pointer. This event MUST be fired prior to any subsequent events for the pointer after capture was released. This event is fired at the element from which pointer capture was removed. All subsequent events for the pointer except click, auxclick, and contextmenu events follow normal hit testing mechanisms (out of scope for this specification) for determining the event target. See the releasing pointer capture, implicit release of pointer capture, and process pending pointer capture sections.

+

The click, auxclick, and contextmenu events

This section is an addition to click, @@ -830,6 +864,7 @@

Event attributes

  • If the events are generated by a non-pointing device (such as voice recognition software or a keyboard interaction), pointerId MUST be -1 and pointerType MUST be an empty string.
  • +

    Event coordinates

    As noted in {{PointerEvent}}, [[[CSSOM-VIEW]]] proposes to redefine the various coordinate properties (screenX, screenY, pageX, pageY, clientX, @@ -839,6 +874,7 @@

    Event coordinates

    change in [[[CSSOM-VIEW]]] only for {{PointerEvent}} MUST convert the various coordinate properties for the click, auxclick, and contextmenu to long values (as defined in the original [[[UIEVENTS]]]) using Math.floor [[ECMASCRIPT]].

    +

    Event dispatch

    A click, auxclick or contextmenu event MUST follow the dispatch process defined in the [[UIEVENTS]] spec except that the event target is overridden using the algorithm below:

    @@ -899,6 +935,7 @@

    Extensions to the `Element` interface

    +

    Extensions to the `GlobalEventHandlers` mixin

    @@ -988,6 +1025,7 @@

    Extensions to the `Navigator` interface

    maxTouchPoints is often used to ensure that the interaction model of the content can be recognized by the current hardware. UI affordances can be provided to users with less capable hardware. On platforms where the precise number of touch points is not known, the minimum number guaranteed to be recognized is provided. Therefore, it is possible for the number of recognized touch points to exceed the value of maxTouchPoints.
    +

    Declaring direct manipulation behavior

    As noted in Attributes and Default Actions, viewport manipulations (panning and zooming) cannot be suppressed by canceling a pointer event. Instead, authors must declaratively define which of these behaviors they want to allow, and which they want to suppress, using the touch-action CSS property.

    @@ -1113,6 +1151,7 @@

    Introduction

    Example of a custom slider control that chooses a value by sliding the thumb element back and forth. After {{GlobalEventHandlers/pointerdown}} on the thumb, pointer capture can be used to allow the user to slide the thumb even if the pointer drifts off of it.
    +

    Setting pointer capture

    Pointer capture is set on an |element| of type {{Element}} by calling the element.setPointerCapture(pointerId) method. @@ -1138,12 +1177,14 @@

    Releasing pointer capture

  • For the specified pointerId, clear the pending pointer capture target override, if set.
  • +

    Implicit pointer capture

    Inputs that implement direct manipulation interactions for panning and zooming (such as touch or stylus on a touchscreen) SHOULD behave exactly as if setPointerCapture() was called on the target element just before the invocation of any {{GlobalEventHandlers/pointerdown}} listeners. The hasPointerCapture API may be used (eg. within any {{GlobalEventHandlers/pointerdown}} listener) to determine whether this has occurred. If releasePointerCapture() is not called for the pointer before the next pointer event is fired, then a {{GlobalEventHandlers/gotpointercapture}} event will be dispatched to the target (as normal) indicating that capture is active.

    This is a breaking change from [[PointerEvents]], but does not impact the vast majority of existing content. In addition to matching typical platform UX conventions, this design for implicit capture enables user agents to make a performance optimization which prevents the need to invoke hit-testing on touch movement events without explicit developer opt-in (consistent with the performance properties of existing dominant native and web APIs for touch input).
    In addition, user agents may implement implicit pointer capture behavior for all input devices on specific UI widgets such as input range controls (allowing some finger movement to stray outside of the form control itself during the interaction).
    +

    Implicit release of pointer capture

    Immediately after firing the {{GlobalEventHandlers/pointerup}} or {{GlobalEventHandlers/pointercancel}} events, @@ -1165,6 +1206,7 @@

    releasePointerCapture() method has been called if any element is set to be captured or pending to be captured.

    +

    Coalesced and predicted events

    @@ -1395,6 +1437,7 @@

    Populating and maintaining the coalesced and predicted events lists

    +

    Compatibility mapping with mouse events

    The vast majority of web content existing today codes only to Mouse Events. The following describes an algorithm for how the user agent MAY map generic pointer input to mouse events for compatibility with this content.

    @@ -1448,6 +1491,7 @@

    Tracking the effective position of the legacy mouse pointer

    of the legacy mouse pointer moves back inside Button 1.

    +

    Mapping for devices that support hover

    Whenever the user agent is to dispatch a pointer event for a device that supports hover, it SHOULD run the following steps:

    @@ -1467,6 +1511,7 @@

    Mapping for devices that support hover

  • If the pointer event dispatched was {{GlobalEventHandlers/pointerup}} or {{GlobalEventHandlers/pointercancel}}, clear the PREVENT MOUSE EVENT flag for this pointerType.
  • +

    Mapping for devices that do not support hover

    Some devices, such as most touchscreens, do not support hovering a coordinate (or set of coordinates) while not in the active state. Much existing content coded to mouse events assumes that a mouse is producing the events and thus certain qualities are generally true:

    @@ -1531,6 +1576,7 @@

    Mapping for devices that do not support hover

    +

    Security and privacy considerations

    This appendix discusses security and privacy considerations for Pointer Events implementations. The discussion is limited to security and privacy issues that arise directly from implementation of the event model, APIs and events defined in this specification.

    @@ -1557,6 +1603,7 @@

    Security and privacy considerations

  • Does not allow downgrading default security characteristics.
  • +

    Glossary

    @@ -1606,6 +1653,7 @@

    Glossary

    A program, such as a browser or content authoring tool, normally running on a client machine, which acts on a user's behalf in retrieving, interpreting, executing, presenting, or creating content.
    +

    Acknowledgments

    Many thanks to lots of people for their proposals and recommendations, some of which are incorporated into this document. The group's Chair acknowledges contributions from the following past and present group members and participants: @@ -1648,6 +1696,7 @@

    Acknowledgments

    Special thanks to those that helped pioneer the first edition of this model, including especially: Charu Chandiram, Peter Freiling, Nathan Furtwangler, Thomas Olsen, Matt Rakow, Ramu Ramanathan, Justin Rogers, Jacob Rossi, Reed Townsend and Steve Wright.

    +

    Revision history

    The following is an informative summary of substantial and major editorial changes between publications of this specification, relative to the [[PointerEvents3]] specification.