From fa814d4875d924e6835e31339f2012e8b18818ae Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Thu, 23 Jun 2022 11:01:04 -0700 Subject: [PATCH] PointerEvents: Fix dispatch optimization Summary: Changelog: [Internal] Fixing a recent optimization to prevent event dispatches for events that are not listened for. An incorrect hitpath was passed in for `leave` events. Refactored the PointerEvent optimization such that `filterByShouldDispatch` determines what views should dispatch a PointerEvent, and `dispatchEventForViewTargets` to actually dispatch them. We are separating this because the order of dispatch differs between `enter` and `leave` events. Reviewed By: vincentriemer Differential Revision: D37348726 fbshipit-source-id: a09a04df3ae027cce95e0d93a4163c2015fe3fe3 --- .../react/uimanager/JSPointerDispatcher.java | 149 ++++++++++-------- 1 file changed, 85 insertions(+), 64 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 a37ba3fe15a5b0..935941d60e661e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java @@ -20,6 +20,7 @@ import com.facebook.react.uimanager.events.PointerEventHelper.EVENT; import com.facebook.react.uimanager.events.TouchEvent; import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -110,14 +111,17 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp mTouchEventCoalescingKeyHelper.addCoalescingKey(mDownStartTime); if (!supportsHover) { - dispatchNonBubblingEventForPathWhenListened( - EVENT.ENTER, - EVENT.ENTER_CAPTURE, - hitPath, + List enterViewTargets = + filterByShouldDispatch(hitPath, EVENT.ENTER, EVENT.ENTER_CAPTURE, false); + + // Dispatch root -> target, we need to reverse order of enterViewTargets + Collections.reverse(enterViewTargets); + dispatchEventForViewTargets( + PointerEventHelper.POINTER_ENTER, + enterViewTargets, eventDispatcher, surfaceId, - motionEvent, - false); + motionEvent); } boolean listeningForDown = @@ -201,14 +205,16 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp } if (!supportsHover) { - dispatchNonBubblingEventForPathWhenListened( - EVENT.LEAVE, - EVENT.LEAVE_CAPTURE, - hitPath, + List leaveViewTargets = + filterByShouldDispatch(hitPath, EVENT.LEAVE, EVENT.LEAVE_CAPTURE, false); + + // target -> root + dispatchEventForViewTargets( + PointerEventHelper.POINTER_LEAVE, + leaveViewTargets, eventDispatcher, surfaceId, - motionEvent, - false); + motionEvent); } return; } @@ -240,43 +246,47 @@ private static boolean isAnyoneListeningForBubblingEvent( } /** - * Dispatch event only if ancestor is listening to relevant capture event. This should only be - * relevant for ENTER/LEAVE events that need to be dispatched along every relevant view in the hit - * path. + * Returns list of view targets that we should be dispatching events from * - * @param pointerEventType - Should only be ENTER/LEAVE events - * @param hitPath - ViewTargets ordered from target -> root - * @param dispatcher - * @param surfaceId - * @param motionEvent - * @param forceDispatch - Ignore if ancestor is listening and force the event to be dispatched + * @param viewTargets, ordered from target -> root + * @param bubble, name of event that bubbles + * @param capture, name of event that captures + * @param forceDispatch, if true, all viewTargets should dispatch + * @return list of viewTargets filtered from target -> root */ - private static void dispatchNonBubblingEventForPathWhenListened( - EVENT event, - EVENT captureEvent, - List hitPath, - EventDispatcher dispatcher, - int surfaceId, - MotionEvent motionEvent, - boolean forceDispatch) { - - boolean ancestorListening = forceDispatch; - String eventName = PointerEventHelper.getDispatchableEventName(event); - if (eventName == null) { - return; + private static List filterByShouldDispatch( + List viewTargets, EVENT bubble, EVENT capture, boolean forceDispatch) { + if (forceDispatch) { + return viewTargets; } - // iterate through hitPath from ancestor -> target - for (int i = hitPath.size() - 1; i >= 0; i--) { - View view = hitPath.get(i).getView(); - int viewId = hitPath.get(i).getViewId(); - if (ancestorListening - || (i == 0 && PointerEventHelper.isListening(view, event)) - || PointerEventHelper.isListening(view, captureEvent)) { - dispatcher.dispatchEvent(PointerEvent.obtain(eventName, surfaceId, viewId, motionEvent)); + boolean ancestorListening = false; + List dispatchableViewTargets = new ArrayList(viewTargets); + for (int i = viewTargets.size() - 1; i >= 0; i--) { + ViewTarget viewTarget = viewTargets.get(i); + View view = viewTarget.getView(); + if (!ancestorListening + && (PointerEventHelper.isListening(view, capture) + || (i == 0 && PointerEventHelper.isListening(view, bubble)))) { ancestorListening = true; + } else if (!ancestorListening) { + dispatchableViewTargets.remove(i); } } + return dispatchableViewTargets; + } + + private static void dispatchEventForViewTargets( + String eventName, + List viewTargets, + EventDispatcher dispatcher, + int surfaceId, + MotionEvent motionEvent) { + + for (ViewTarget viewTarget : viewTargets) { + int viewId = viewTarget.getViewId(); + dispatcher.dispatchEvent(PointerEvent.obtain(eventName, surfaceId, viewId, motionEvent)); + } } // called on hover_move motion events only @@ -360,31 +370,40 @@ private void handleHoverEvent( // If something has changed in either enter/exit, let's start a new coalescing key mTouchEventCoalescingKeyHelper.incrementCoalescingKey(mHoverInteractionKey); + // target -> root List enterViewTargets = - hitPath.subList(0, hitPath.size() - firstDivergentIndexFromBack); + filterByShouldDispatch( + hitPath.subList(0, hitPath.size() - firstDivergentIndexFromBack), + EVENT.ENTER, + EVENT.ENTER_CAPTURE, + nonDivergentListeningToEnter); + if (enterViewTargets.size() > 0) { - dispatchNonBubblingEventForPathWhenListened( - EVENT.ENTER, - EVENT.ENTER_CAPTURE, + // We want to iterate these from root -> target so we need to reverse + Collections.reverse(enterViewTargets); + dispatchEventForViewTargets( + PointerEventHelper.POINTER_ENTER, enterViewTargets, eventDispatcher, surfaceId, - motionEvent, - nonDivergentListeningToEnter); + motionEvent); } - List exitViewTargets = - mLastHitPath.subList(0, mLastHitPath.size() - firstDivergentIndexFromBack); - if (exitViewTargets.size() > 0) { - // child -> root - dispatchNonBubblingEventForPathWhenListened( - EVENT.LEAVE, - EVENT.LEAVE_CAPTURE, - enterViewTargets, + // target -> root + List leaveViewTargets = + filterByShouldDispatch( + mLastHitPath.subList(0, mLastHitPath.size() - firstDivergentIndexFromBack), + EVENT.LEAVE, + EVENT.LEAVE_CAPTURE, + nonDivergentListeningToLeave); + if (leaveViewTargets.size() > 0) { + // We want to dispatch from target -> root, so no need to reverse + dispatchEventForViewTargets( + PointerEventHelper.POINTER_LEAVE, + leaveViewTargets, eventDispatcher, surfaceId, - motionEvent, - nonDivergentListeningToLeave); + motionEvent); } } @@ -424,14 +443,16 @@ private void dispatchCancelEvent( PointerEventHelper.POINTER_CANCEL, surfaceId, targetTag, motionEvent)); } - dispatchNonBubblingEventForPathWhenListened( - EVENT.LEAVE, - EVENT.LEAVE_CAPTURE, - hitPath, + List leaveViewTargets = + filterByShouldDispatch(hitPath, EVENT.LEAVE, EVENT.LEAVE_CAPTURE, false); + + // dispatch from target -> root + dispatchEventForViewTargets( + PointerEventHelper.POINTER_LEAVE, + leaveViewTargets, eventDispatcher, surfaceId, - motionEvent, - false); + motionEvent); mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime); mDownStartTime = TouchEvent.UNSET;