From de09bd3b840908ac5cdbf229546a9f8b530b4eb2 Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Mon, 11 Apr 2022 17:17:10 -0700 Subject: [PATCH] Dispatch enter/leave for ancestor hit path Summary: Changelog: [Internal] - Fix pointer event dispatch to also fire enter/leave for ancestors in the hit path. Compared the event order with web on the RNTester W3C pointer example Reviewed By: appden Differential Revision: D35403076 fbshipit-source-id: 726e45e49a901b1d97ad3e20f5898701fd1f763b --- .../react/uimanager/JSPointerDispatcher.java | 90 ++++++++++--------- .../Experimental/W3CPointerEventsExample.js | 4 + 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java index c03aed99c6c..0bf85474793 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java @@ -40,7 +40,7 @@ public class JSPointerDispatcher { // Set globally for hover interactions, referenced for coalescing hover events private long mHoverInteractionKey = TouchEvent.UNSET; - private List mLastHitState = Collections.emptyList(); + private List mLastHitPath = Collections.emptyList(); private final float[] mLastEventCoordinates = new float[2]; public JSPointerDispatcher(ViewGroup viewGroup) { @@ -56,8 +56,10 @@ public class JSPointerDispatcher { return; } - int targetTag = findTargetTagAndSetCoordinates(motionEvent); - dispatchCancelEvent(targetTag, motionEvent, eventDispatcher); + List hitPath = + TouchTargetHelper.findTargetPathAndCoordinatesForTouch( + motionEvent.getX(), motionEvent.getY(), mRootViewGroup, mTargetCoordinates); + dispatchCancelEvent(hitPath, motionEvent, eventDispatcher); mChildHandlingNativeGesture = childView.getId(); } @@ -78,11 +80,15 @@ public class JSPointerDispatcher { int surfaceId = UIManagerHelper.getSurfaceId(mRootViewGroup); int action = motionEvent.getActionMasked(); - int targetTag = findTargetTagAndSetCoordinates(motionEvent); + List hitPath = + TouchTargetHelper.findTargetPathAndCoordinatesForTouch( + motionEvent.getX(), motionEvent.getY(), mRootViewGroup, mTargetCoordinates); + + int targetTag = hitPath.get(0); if (supportsHover) { if (action == MotionEvent.ACTION_HOVER_MOVE) { - handleHoverEvent(motionEvent, eventDispatcher, surfaceId); + handleHoverEvent(motionEvent, eventDispatcher, surfaceId, hitPath); return; } @@ -100,9 +106,12 @@ public class JSPointerDispatcher { mTouchEventCoalescingKeyHelper.addCoalescingKey(mDownStartTime); if (!supportsHover) { - eventDispatcher.dispatchEvent( - PointerEvent.obtain( - PointerEventHelper.POINTER_ENTER, surfaceId, targetTag, motionEvent)); + // Enter root -> child + for (int i = hitPath.size(); i-- > 0; ) { + int tag = hitPath.get(i); + eventDispatcher.dispatchEvent( + PointerEvent.obtain(PointerEventHelper.POINTER_ENTER, surfaceId, tag, motionEvent)); + } } eventDispatcher.dispatchEvent( PointerEvent.obtain(PointerEventHelper.POINTER_DOWN, surfaceId, targetTag, motionEvent)); @@ -113,12 +122,6 @@ public class JSPointerDispatcher { // New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer if (action == MotionEvent.ACTION_POINTER_DOWN) { mTouchEventCoalescingKeyHelper.incrementCoalescingKey(mDownStartTime); - - if (!supportsHover) { - eventDispatcher.dispatchEvent( - PointerEvent.obtain( - PointerEventHelper.POINTER_ENTER, surfaceId, targetTag, motionEvent)); - } eventDispatcher.dispatchEvent( PointerEvent.obtain(PointerEventHelper.POINTER_DOWN, surfaceId, targetTag, motionEvent)); @@ -153,16 +156,18 @@ public class JSPointerDispatcher { PointerEvent.obtain(PointerEventHelper.POINTER_UP, surfaceId, targetTag, motionEvent)); if (!supportsHover) { - eventDispatcher.dispatchEvent( - PointerEvent.obtain( - PointerEventHelper.POINTER_LEAVE, surfaceId, targetTag, motionEvent)); + // Leave child -> root + for (int i = 0; i < hitPath.size(); i++) { + int tag = hitPath.get(i); + eventDispatcher.dispatchEvent( + PointerEvent.obtain(PointerEventHelper.POINTER_LEAVE, surfaceId, tag, motionEvent)); + } } return; } if (action == MotionEvent.ACTION_CANCEL) { - dispatchCancelEvent(targetTag, motionEvent, eventDispatcher); - + dispatchCancelEvent(hitPath, motionEvent, eventDispatcher); return; } @@ -184,7 +189,10 @@ public class JSPointerDispatcher { // called on hover_move motion events only private void handleHoverEvent( - MotionEvent motionEvent, EventDispatcher eventDispatcher, int surfaceId) { + MotionEvent motionEvent, + EventDispatcher eventDispatcher, + int surfaceId, + List hitPath) { int action = motionEvent.getActionMasked(); if (action != MotionEvent.ACTION_HOVER_MOVE) { @@ -209,19 +217,15 @@ public class JSPointerDispatcher { mTouchEventCoalescingKeyHelper.addCoalescingKey(mHoverInteractionKey); } - List currHitState = - TouchTargetHelper.findTargetPathAndCoordinatesForTouch( - x, y, mRootViewGroup, mTargetCoordinates); - // If child is handling, eliminate target tags under handling child if (mChildHandlingNativeGesture > 0) { - int index = currHitState.indexOf(mChildHandlingNativeGesture); + int index = hitPath.indexOf(mChildHandlingNativeGesture); if (index > 0) { - currHitState.subList(0, index).clear(); + hitPath.subList(0, index).clear(); } } - int targetTag = currHitState.size() > 0 ? currHitState.get(0) : -1; + int targetTag = hitPath.size() > 0 ? hitPath.get(0) : -1; // If targetTag is empty, we should bail? if (targetTag == -1) { return; @@ -231,24 +235,25 @@ public class JSPointerDispatcher { // Traverse hitState back-to-front to find the first divergence with mLastHitState // FIXME: this may generate incorrect events when view collapsing changes the hierarchy int firstDivergentIndex = 0; - while (firstDivergentIndex < Math.min(currHitState.size(), mLastHitState.size()) - && currHitState - .get(currHitState.size() - 1 - firstDivergentIndex) - .equals(mLastHitState.get(mLastHitState.size() - 1 - firstDivergentIndex))) { + while (firstDivergentIndex < Math.min(hitPath.size(), mLastHitPath.size()) + && hitPath + .get(hitPath.size() - 1 - firstDivergentIndex) + .equals(mLastHitPath.get(mLastHitPath.size() - 1 - firstDivergentIndex))) { firstDivergentIndex++; } - boolean hasDiverged = firstDivergentIndex < Math.max(currHitState.size(), mLastHitState.size()); + boolean hasDiverged = firstDivergentIndex < Math.max(hitPath.size(), mLastHitPath.size()); // Fire all relevant enter events if (hasDiverged) { // If something has changed in either enter/exit, let's start a new coalescing key mTouchEventCoalescingKeyHelper.incrementCoalescingKey(mHoverInteractionKey); - List enterTargetTags = - currHitState.subList(0, currHitState.size() - firstDivergentIndex); + List enterTargetTags = hitPath.subList(0, hitPath.size() - firstDivergentIndex); if (enterTargetTags.size() > 0) { - for (Integer enterTargetTag : enterTargetTags) { + // root -> child + for (int i = enterTargetTags.size(); i-- > 0; ) { + int enterTargetTag = enterTargetTags.get(i); eventDispatcher.dispatchEvent( PointerEvent.obtain( PointerEventHelper.POINTER_ENTER, surfaceId, enterTargetTag, motionEvent)); @@ -257,8 +262,9 @@ public class JSPointerDispatcher { // Fire all relevant exit events List exitTargetTags = - mLastHitState.subList(0, mLastHitState.size() - firstDivergentIndex); + mLastHitPath.subList(0, mLastHitPath.size() - firstDivergentIndex); if (exitTargetTags.size() > 0) { + // child -> root for (Integer exitTargetTag : exitTargetTags) { eventDispatcher.dispatchEvent( PointerEvent.obtain( @@ -272,13 +278,13 @@ public class JSPointerDispatcher { PointerEvent.obtain( PointerEventHelper.POINTER_MOVE, surfaceId, targetTag, motionEvent, coalescingKey)); - mLastHitState = currHitState; + mLastHitPath = hitPath; mLastEventCoordinates[0] = x; mLastEventCoordinates[1] = y; } private void dispatchCancelEvent( - int targetTag, MotionEvent motionEvent, EventDispatcher eventDispatcher) { + List hitPath, MotionEvent motionEvent, EventDispatcher eventDispatcher) { // This means the gesture has already ended, via some other CANCEL or UP event. This is not // expected to happen very often as it would mean some child View has decided to intercept the // touch stream and start a native gesture only upon receiving the UP/CANCEL event. @@ -288,13 +294,17 @@ public class JSPointerDispatcher { "Expected to not have already sent a cancel for this gesture"); int surfaceId = UIManagerHelper.getSurfaceId(mRootViewGroup); + int targetTag = hitPath.get(0); + // Question: Does cancel fire on all in hit path? Assertions.assertNotNull(eventDispatcher) .dispatchEvent( PointerEvent.obtain( PointerEventHelper.POINTER_CANCEL, surfaceId, targetTag, motionEvent)); - eventDispatcher.dispatchEvent( - PointerEvent.obtain(PointerEventHelper.POINTER_LEAVE, surfaceId, targetTag, motionEvent)); + for (int tag : hitPath) { + eventDispatcher.dispatchEvent( + PointerEvent.obtain(PointerEventHelper.POINTER_LEAVE, surfaceId, tag, motionEvent)); + } mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime); mDownStartTime = TouchEvent.UNSET; diff --git a/packages/rn-tester/js/examples/Experimental/W3CPointerEventsExample.js b/packages/rn-tester/js/examples/Experimental/W3CPointerEventsExample.js index 276aeafad92..85b421c74c3 100644 --- a/packages/rn-tester/js/examples/Experimental/W3CPointerEventsExample.js +++ b/packages/rn-tester/js/examples/Experimental/W3CPointerEventsExample.js @@ -38,9 +38,13 @@ function EventfulView(props: {| const listeners = listen ? { onPointerUp: eventLog('up'), + onPointerUpCapture: eventLog('up capture'), onPointerDown: eventLog('down'), + onPointerDownCapture: eventLog('down capture'), onPointerLeave2: eventLog('leave'), + onPointerLeave2Capture: eventLog('leave capture'), onPointerEnter2: eventLog('enter'), + onPointerEnter2Capture: eventLog('enter capture'), } : Object.freeze({});