Skip to content

Commit

Permalink
W3CPointerEvents: properly update hit path during native gestures (#3…
Browse files Browse the repository at this point in the history
…7638)

Summary:
Pull Request resolved: #37638

Changelog: [Android] [Fixed]  - W3CPointerEvents: properly update hit path during native gestures

Per [the W3C spec](https://www.w3.org/TR/pointerevents/#the-pointercancel-event), we need to fire pointerout and pointerleave after firing a pointercancel. However, in cases where the pointer doesn't physically leave the target after a cancel (e.g. scrolling by clicking and dragging), we would never re-fire a pointerenter event once the native gesture was completed. This change fixes the bug by clearing out the last hit path (and other relevant state) for the pointer when we start handling a native gesture. Then we'll re-fire a pointerenter as expected upon the next motion event (due to the logic in handleHitStateDivergence).

Note: this bug only affected hovering pointers (e.g. mouse) because for non-hovering pointers the native gesture won't end unless the pointer is physically removed (i.e. finger is lifted).

Reviewed By: javache

Differential Revision: D46226021

fbshipit-source-id: c583771b99efd1480b51e44bb0bf0a9f3a97779b
  • Loading branch information
Alex Danoff authored and facebook-github-bot committed Jun 1, 2023
1 parent b50874c commit 047200b
Showing 1 changed file with 26 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ public void onChildStartedNativeGesture(

dispatchCancelEventForTarget(childView, motionInRoot, eventDispatcher);
mChildHandlingNativeGesture = childView.getId();

// clear "previous" state since interaction was canceled
resetPreviousStateForMotionEvent(motionEvent);
}

private MotionEvent convertMotionToRootFrame(View childView, MotionEvent childMotion) {
Expand All @@ -84,6 +87,28 @@ private MotionEvent convertMotionToRootFrame(View childView, MotionEvent childMo
return motionInRoot;
}

private void updatePreviousStateFromEvent(MotionEvent event, PointerEventState eventState) {
// Caching the event state so we have a new "last"
mLastHitPathByPointerId = eventState.getHitPathByPointerId();
mLastEventCoordinatesByPointerId = eventState.getEventCoordinatesByPointerId();
mLastButtonState = event.getButtonState();

// Clean up any stale pointerIds
Set<Integer> allPointerIds = mLastEventCoordinatesByPointerId.keySet();
mHoveringPointerIds.retainAll(allPointerIds);
}

private void resetPreviousStateForMotionEvent(MotionEvent event) {
int activePointerId = event.getPointerId(event.getActionIndex());
if (mLastHitPathByPointerId != null) {
mLastHitPathByPointerId.remove(activePointerId);
}
if (mLastEventCoordinatesByPointerId != null) {
mLastEventCoordinatesByPointerId.remove(activePointerId);
}
mLastButtonState = 0;
}

public void onChildEndedNativeGesture() {
// There should be only one child gesture at any given time. We can safely turn off the flag.
mChildHandlingNativeGesture = -1;
Expand Down Expand Up @@ -368,14 +393,7 @@ public void handleMotionEvent(
return;
}

// Caching the event state so we have a new "last"
mLastHitPathByPointerId = eventState.getHitPathByPointerId();
mLastEventCoordinatesByPointerId = eventState.getEventCoordinatesByPointerId();
mLastButtonState = motionEvent.getButtonState();

// Clean up any stale pointerIds
Set<Integer> allPointerIds = mLastEventCoordinatesByPointerId.keySet();
mHoveringPointerIds.retainAll(allPointerIds);
updatePreviousStateFromEvent(motionEvent, eventState);
}

private static boolean isAnyoneListeningForBubblingEvent(
Expand Down

0 comments on commit 047200b

Please sign in to comment.