From 13461901c07e4d6a2406e2845df7e560e0912143 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Fri, 11 Aug 2023 19:21:11 -0700 Subject: [PATCH] Back out "Refactor conditional event emitting to the C++ layer" (#38973) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/38973 Original commit changeset: 6c00c2fcfdfd Original Phabricator Diff: D47852371 When #38674 and #38959 are combined, they cause Android apps on Fabric to crash. #38959 is the more urgent fix, so backing out #38674. ## Changelog: [General] [Fixed] - Rolling back change that broke e2e tests until we can figure out why the e2e tests are broken Reviewed By: NickGerleman Differential Revision: D48288752 fbshipit-source-id: d9c8b2d1b01ecf6532e92ef6dbce0a5be9c2b57b --- .../React/Fabric/RCTSurfacePointerHandler.mm | 60 +++++- .../renderer/components/view/primitives.h | 4 - .../components/view/propsConversions.h | 30 +-- .../uimanager/PointerEventsProcessor.cpp | 185 ++---------------- 4 files changed, 77 insertions(+), 202 deletions(-) diff --git a/packages/react-native/React/Fabric/RCTSurfacePointerHandler.mm b/packages/react-native/React/Fabric/RCTSurfacePointerHandler.mm index dbf5c8cf85e4a1..2e7338b3aebcea 100644 --- a/packages/react-native/React/Fabric/RCTSurfacePointerHandler.mm +++ b/packages/react-native/React/Fabric/RCTSurfacePointerHandler.mm @@ -396,6 +396,28 @@ static void UpdateActivePointerWithUITouch( activePointer.modifierFlags = uiEvent.modifierFlags; } +static BOOL IsViewListeningToEvent(RCTReactTaggedView *taggedView, ViewEvents::Offset eventType) +{ + UIView *view = taggedView.view; + if (view && [view.class conformsToProtocol:@protocol(RCTComponentViewProtocol)]) { + auto props = ((id)view).props; + if (SharedViewProps viewProps = std::dynamic_pointer_cast(props)) { + return viewProps->events[eventType]; + } + } + return NO; +} + +static BOOL IsAnyViewInPathListeningToEvent(NSOrderedSet *viewPath, ViewEvents::Offset eventType) +{ + for (RCTReactTaggedView *taggedView in viewPath) { + if (IsViewListeningToEvent(taggedView, eventType)) { + return YES; + } + } + return NO; +} + /** * Given an ActivePointer determine if it is still within the same event target tree as * the one which initiated the pointer gesture. @@ -612,7 +634,8 @@ - (void)_dispatchActivePointers:(std::vector)activePointers event { for (const auto &activePointer : activePointers) { PointerEvent pointerEvent = CreatePointerEventFromActivePointer(activePointer, eventType, _rootComponentView); - [self handleIncomingPointerEvent:pointerEvent onView:activePointer.componentView]; + NSOrderedSet *eventPathViews = [self handleIncomingPointerEvent:pointerEvent + onView:activePointer.componentView]; SharedTouchEventEmitter eventEmitter = GetTouchEmitterFromView( activePointer.componentView, @@ -625,7 +648,12 @@ - (void)_dispatchActivePointers:(std::vector)activePointers event break; } case RCTPointerEventTypeMove: { - eventEmitter->onPointerMove(pointerEvent); + BOOL hasMoveEventListeners = + IsAnyViewInPathListeningToEvent(eventPathViews, ViewEvents::Offset::PointerMove) || + IsAnyViewInPathListeningToEvent(eventPathViews, ViewEvents::Offset::PointerMoveCapture); + if (hasMoveEventListeners) { + eventEmitter->onPointerMove(pointerEvent); + } break; } case RCTPointerEventTypeEnd: { @@ -764,9 +792,11 @@ - (void)hovering:(UIHoverGestureRecognizer *)recognizer PointerEvent event = CreatePointerEventFromIncompleteHoverData( pointerId, pointerType, clientLocation, screenLocation, offsetLocation, modifierFlags); - [self handleIncomingPointerEvent:event onView:targetView]; + NSOrderedSet *eventPathViews = [self handleIncomingPointerEvent:event onView:targetView]; SharedTouchEventEmitter eventEmitter = GetTouchEmitterFromView(targetView, offsetLocation); - if (eventEmitter != nil) { + BOOL hasMoveEventListeners = IsAnyViewInPathListeningToEvent(eventPathViews, ViewEvents::Offset::PointerMove) || + IsAnyViewInPathListeningToEvent(eventPathViews, ViewEvents::Offset::PointerMoveCapture); + if (eventEmitter != nil && hasMoveEventListeners) { eventEmitter->onPointerMove(event); } } @@ -801,9 +831,10 @@ - (void)hovering:(UIHoverGestureRecognizer *)recognizer // Out if (prevTargetView != nil && prevTargetTaggedView.tag != targetTaggedView.tag) { + BOOL shouldEmitOutEvent = IsAnyViewInPathListeningToEvent(currentlyHoveredViews, ViewEvents::Offset::PointerOut); SharedTouchEventEmitter eventEmitter = GetTouchEmitterFromView(prevTargetView, [_rootComponentView convertPoint:clientLocation toView:prevTargetView]); - if (eventEmitter != nil) { + if (shouldEmitOutEvent && eventEmitter != nil) { eventEmitter->onPointerOut(event); } } @@ -816,14 +847,20 @@ - (void)hovering:(UIHoverGestureRecognizer *)recognizer // we reverse iterate (now from target to root), actually emitting the events. NSMutableOrderedSet *viewsToEmitLeaveEventsTo = [NSMutableOrderedSet orderedSet]; + BOOL hasParentLeaveListener = NO; for (RCTReactTaggedView *taggedView in [currentlyHoveredViews reverseObjectEnumerator]) { UIView *componentView = taggedView.view; - BOOL shouldEmitEvent = componentView != nil; + BOOL shouldEmitEvent = componentView != nil && + (hasParentLeaveListener || IsViewListeningToEvent(taggedView, ViewEvents::Offset::PointerLeave)); if (shouldEmitEvent && ![eventPathViews containsObject:taggedView]) { [viewsToEmitLeaveEventsTo addObject:componentView]; } + + if (shouldEmitEvent && !hasParentLeaveListener) { + hasParentLeaveListener = YES; + } } for (UIView *componentView in [viewsToEmitLeaveEventsTo reverseObjectEnumerator]) { @@ -836,9 +873,10 @@ - (void)hovering:(UIHoverGestureRecognizer *)recognizer // Over if (targetView != nil && prevTargetTaggedView.tag != targetTaggedView.tag) { + BOOL shouldEmitOverEvent = IsAnyViewInPathListeningToEvent(eventPathViews, ViewEvents::Offset::PointerOver); SharedTouchEventEmitter eventEmitter = GetTouchEmitterFromView(targetView, [_rootComponentView convertPoint:clientLocation toView:targetView]); - if (eventEmitter != nil) { + if (shouldEmitOverEvent && eventEmitter != nil) { eventEmitter->onPointerOver(event); } } @@ -850,10 +888,12 @@ - (void)hovering:(UIHoverGestureRecognizer *)recognizer // or if one of its parents is listening in case those listeners care about the capturing phase. Adding the ability // for native to distinguish between capturing listeners and not could be an optimization to further reduce the number // of events we send to JS + BOOL hasParentEnterListener = NO; for (RCTReactTaggedView *taggedView in [eventPathViews reverseObjectEnumerator]) { UIView *componentView = taggedView.view; - BOOL shouldEmitEvent = componentView != nil; + BOOL shouldEmitEvent = componentView != nil && + (hasParentEnterListener || IsViewListeningToEvent(taggedView, ViewEvents::Offset::PointerEnter)); if (shouldEmitEvent && ![currentlyHoveredViews containsObject:taggedView]) { SharedTouchEventEmitter eventEmitter = @@ -862,6 +902,10 @@ - (void)hovering:(UIHoverGestureRecognizer *)recognizer eventEmitter->onPointerEnter(event); } } + + if (shouldEmitEvent && !hasParentEnterListener) { + hasParentEnterListener = YES; + } } [_currentlyHoveredViewsPerPointer setObject:eventPathViews forKey:@(pointerId)]; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h index 0025e28b5b575e..e3591f7c41f7e3 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h @@ -62,10 +62,6 @@ struct ViewEvents { ClickCapture = 31, GotPointerCapture = 32, LostPointerCapture = 33, - PointerDown = 34, - PointerDownCapture = 35, - PointerUp = 36, - PointerUpCapture = 37, }; constexpr bool operator[](const Offset offset) const { diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/propsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/view/propsConversions.h index a59feff2e86efc..46f3801c599057 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/propsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/propsConversions.h @@ -616,32 +616,18 @@ static inline ViewEvents convertRawProp( "onClickCapture", sourceValue[Offset::ClickCapture], defaultValue[Offset::ClickCapture]); - result[Offset::PointerDown] = convertRawProp( + result[Offset::GotPointerCapture] = convertRawProp( context, rawProps, - "onPointerDown", - sourceValue[Offset::PointerDown], - defaultValue[Offset::PointerDown]); - result[Offset::PointerDownCapture] = convertRawProp( + "onGotPointerCapture", + sourceValue[Offset::GotPointerCapture], + defaultValue[Offset::GotPointerCapture]); + result[Offset::LostPointerCapture] = convertRawProp( context, rawProps, - "onPointerDownCapture", - sourceValue[Offset::PointerDownCapture], - defaultValue[Offset::PointerDownCapture]); - result[Offset::PointerUp] = convertRawProp( - context, - rawProps, - "onPointerUp", - sourceValue[Offset::PointerUp], - defaultValue[Offset::PointerUp]); - result[Offset::PointerUpCapture] = convertRawProp( - context, - rawProps, - "onPointerUpCapture", - sourceValue[Offset::PointerUpCapture], - defaultValue[Offset::PointerUpCapture]); - // TODO: gotPointerCapture & lostPointerCapture (causes issues with - // RawPropsKey for some reason) + "onLostPointerCapture", + sourceValue[Offset::LostPointerCapture], + defaultValue[Offset::LostPointerCapture]); // PanResponder callbacks result[Offset::MoveShouldSetResponder] = convertRawProp( diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/PointerEventsProcessor.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/PointerEventsProcessor.cpp index 74f6504817faba..0538f040aca7d0 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/PointerEventsProcessor.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/PointerEventsProcessor.cpp @@ -7,80 +7,9 @@ #include "PointerEventsProcessor.h" -#include - namespace facebook::react { -static ShadowNode::Shared GetShadowNodeFromEventTarget( - jsi::Runtime &runtime, - EventTarget const &target) { - auto instanceHandle = target.getInstanceHandle(runtime); - if (instanceHandle.isObject()) { - auto handleObj = instanceHandle.asObject(runtime); - if (handleObj.hasProperty(runtime, "stateNode")) { - auto stateNode = handleObj.getProperty(runtime, "stateNode"); - if (stateNode.isObject()) { - auto stateNodeObj = stateNode.asObject(runtime); - if (stateNodeObj.hasProperty(runtime, "node")) { - auto node = stateNodeObj.getProperty(runtime, "node"); - return shadowNodeFromValue(runtime, node); - } - } - } - } - return nullptr; -} - -static bool IsViewListeningToEvents( - ShadowNode const &shadowNode, - std::initializer_list eventTypes) { - if (auto viewShadowNode = traitCast(&shadowNode)) { - auto &viewProps = viewShadowNode->getConcreteProps(); - for (const ViewEvents::Offset eventType : eventTypes) { - if (viewProps.events[eventType]) { - return true; - } - } - } - return false; -} - -static bool IsAnyViewInPathToRootListeningToEvents( - UIManager const &uiManager, - ShadowNode const &shadowNode, - std::initializer_list eventTypes) { - // Check the target view first - if (IsViewListeningToEvents(shadowNode, eventTypes)) { - return true; - } - - // Retrieve the node's root & a list of nodes between the target and the root - auto owningRootShadowNode = ShadowNode::Shared{}; - uiManager.getShadowTreeRegistry().visit( - shadowNode.getSurfaceId(), - [&owningRootShadowNode](ShadowTree const &shadowTree) { - owningRootShadowNode = shadowTree.getCurrentRevision().rootShadowNode; - }); - - if (owningRootShadowNode == nullptr) { - return false; - } - - auto &nodeFamily = shadowNode.getFamily(); - auto ancestors = nodeFamily.getAncestors(*owningRootShadowNode); - - // Check for listeners from the target's parent to the root - for (auto it = ancestors.rbegin(); it != ancestors.rend(); it++) { - auto ¤tNode = it->first.get(); - if (IsViewListeningToEvents(currentNode, eventTypes)) { - return true; - } - } - - return false; -} - -static PointerEventTarget RetargetPointerEvent( +static PointerEventTarget retargetPointerEvent( PointerEvent const &event, ShadowNode const &nodeToTarget, UIManager const &uiManager) { @@ -130,72 +59,6 @@ static ShadowNode::Shared getCaptureTargetOverride( return maybeTarget.lock(); } -/* - * Centralized method which determines if an event should be sent to JS by - * inspecing the listeners in the target's view path. - */ -static bool ShouldEmitPointerEvent( - ShadowNode const &targetNode, - std::string const &type, - UIManager const &uiManager) { - if (type == "topPointerDown") { - return IsAnyViewInPathToRootListeningToEvents( - uiManager, - targetNode, - {ViewEvents::Offset::PointerDown, - ViewEvents::Offset::PointerDownCapture}); - } else if (type == "topPointerUp") { - return IsAnyViewInPathToRootListeningToEvents( - uiManager, - targetNode, - {ViewEvents::Offset::PointerUp, ViewEvents::Offset::PointerUpCapture}); - } else if (type == "topPointerMove") { - return IsAnyViewInPathToRootListeningToEvents( - uiManager, - targetNode, - {ViewEvents::Offset::PointerMove, - ViewEvents::Offset::PointerMoveCapture}); - } else if (type == "topPointerEnter") { - // This event goes through the capturing phase in full but only bubble - // through the target and no futher up the tree - return IsViewListeningToEvents( - targetNode, {ViewEvents::Offset::PointerEnter}) || - IsAnyViewInPathToRootListeningToEvents( - uiManager, - targetNode, - {ViewEvents::Offset::PointerEnterCapture}); - } else if (type == "topPointerLeave") { - // This event goes through the capturing phase in full but only bubble - // through the target and no futher up the tree - return IsViewListeningToEvents( - targetNode, {ViewEvents::Offset::PointerLeave}) || - IsAnyViewInPathToRootListeningToEvents( - uiManager, - targetNode, - {ViewEvents::Offset::PointerLeaveCapture}); - } else if (type == "topPointerOver") { - return IsAnyViewInPathToRootListeningToEvents( - uiManager, - targetNode, - {ViewEvents::Offset::PointerOver, - ViewEvents::Offset::PointerOverCapture}); - } else if (type == "topPointerOut") { - return IsAnyViewInPathToRootListeningToEvents( - uiManager, - targetNode, - {ViewEvents::Offset::PointerOut, - ViewEvents::Offset::PointerOutCapture}); - } else if (type == "topClick") { - return IsAnyViewInPathToRootListeningToEvents( - uiManager, - targetNode, - {ViewEvents::Offset::Click, ViewEvents::Offset::ClickCapture}); - } - // This is more of an optimization method so if we encounter a type which - // has not been specifically addressed above we should just let it through. - return true; -} - void PointerEventsProcessor::interceptPointerEvent( jsi::Runtime &runtime, EventTarget const *target, @@ -216,18 +79,14 @@ void PointerEventsProcessor::interceptPointerEvent( if (overrideTarget != nullptr && overrideTarget->getTag() != eventTarget->getTag()) { auto retargeted = - RetargetPointerEvent(pointerEvent, *overrideTarget, uiManager); + retargetPointerEvent(pointerEvent, *overrideTarget, uiManager); pointerEvent = retargeted.event; eventTarget = retargeted.target.get(); } eventTarget->retain(runtime); - auto shadowNode = GetShadowNodeFromEventTarget(runtime, *eventTarget); - if (shadowNode != nullptr && - ShouldEmitPointerEvent(*shadowNode, type, uiManager)) { - eventDispatcher(runtime, eventTarget, type, priority, pointerEvent); - } + eventDispatcher(runtime, eventTarget, type, priority, pointerEvent); eventTarget->release(runtime); // Implicit pointer capture release @@ -297,38 +156,28 @@ void PointerEventsProcessor::processPendingPointerCapture( auto activeOverrideTag = (hasActiveOverride) ? activeOverride->getTag() : -1; if (hasActiveOverride && activeOverrideTag != pendingOverrideTag) { - auto retargeted = RetargetPointerEvent(event, *activeOverride, uiManager); + auto retargeted = retargetPointerEvent(event, *activeOverride, uiManager); retargeted.target->retain(runtime); - auto shadowNode = GetShadowNodeFromEventTarget(runtime, *retargeted.target); - if (shadowNode != nullptr && - ShouldEmitPointerEvent( - *shadowNode, "topLostPointerCapture", uiManager)) { - eventDispatcher( - runtime, - retargeted.target.get(), - "topLostPointerCapture", - ReactEventPriority::Discrete, - retargeted.event); - } + eventDispatcher( + runtime, + retargeted.target.get(), + "topLostPointerCapture", + ReactEventPriority::Discrete, + retargeted.event); retargeted.target->release(runtime); } if (hasPendingOverride && activeOverrideTag != pendingOverrideTag) { - auto retargeted = RetargetPointerEvent(event, *pendingOverride, uiManager); + auto retargeted = retargetPointerEvent(event, *pendingOverride, uiManager); retargeted.target->retain(runtime); - auto shadowNode = GetShadowNodeFromEventTarget(runtime, *retargeted.target); - if (shadowNode != nullptr && - ShouldEmitPointerEvent( - *shadowNode, "topGotPointerCapture", uiManager)) { - eventDispatcher( - runtime, - retargeted.target.get(), - "topGotPointerCapture", - ReactEventPriority::Discrete, - retargeted.event); - } + eventDispatcher( + runtime, + retargeted.target.get(), + "topGotPointerCapture", + ReactEventPriority::Discrete, + retargeted.event); retargeted.target->release(runtime); }