mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Refactor conditional event emitting to the C++ layer (#38674)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/38674 Changelog: [Internal] - Refactor conditional pointer event emitting to the C++ layer Some background: early on in the implementation of Pointer Events a concern was brought up that events related to hovering pointers could saturate the JS thread if they were fired all the time unconditionally, so as a mitigation we would check in native to see if listeners in the tree were listening for those events and only fire them if there were listeners. Now since we're going to be moving some of the event derivation logic to the C++ layer we need to receive all the events — but recreate the conditional firing in the C++ layer so we can still avoid saturating the JS thread. That's what this diff does. The only change I see being potentially contraversial is the fact that I needed a way to turn an `EventTarget` (the only information I receive regarding which node the event is firing on) to its cooresponding `ShadowNode` which I did in the method `GetShadowNodeFromEventTarget`. It essentially does the exact same thing the `getNodeFromInternalInstanceHandle` method in `ReactNativePublicCompat.js`, but in C++ against the JSI API. I don't know if there's a better way to do this but this was the best one I came up with that actually works. Reviewed By: NickGerleman Differential Revision: D47852371 fbshipit-source-id: 6c00c2fcfdfd49314c96d044d36272e028e074ff
This commit is contained in:
committed by
Facebook GitHub Bot
parent
86968c1104
commit
7cd69962d0
@@ -396,28 +396,6 @@ 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<RCTComponentViewProtocol>)view).props;
|
||||
if (SharedViewProps viewProps = std::dynamic_pointer_cast<ViewProps const>(props)) {
|
||||
return viewProps->events[eventType];
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
static BOOL IsAnyViewInPathListeningToEvent(NSOrderedSet<RCTReactTaggedView *> *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.
|
||||
@@ -634,8 +612,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act
|
||||
{
|
||||
for (const auto &activePointer : activePointers) {
|
||||
PointerEvent pointerEvent = CreatePointerEventFromActivePointer(activePointer, eventType, _rootComponentView);
|
||||
NSOrderedSet<RCTReactTaggedView *> *eventPathViews = [self handleIncomingPointerEvent:pointerEvent
|
||||
onView:activePointer.componentView];
|
||||
[self handleIncomingPointerEvent:pointerEvent onView:activePointer.componentView];
|
||||
|
||||
SharedTouchEventEmitter eventEmitter = GetTouchEmitterFromView(
|
||||
activePointer.componentView,
|
||||
@@ -648,12 +625,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act
|
||||
break;
|
||||
}
|
||||
case RCTPointerEventTypeMove: {
|
||||
BOOL hasMoveEventListeners =
|
||||
IsAnyViewInPathListeningToEvent(eventPathViews, ViewEvents::Offset::PointerMove) ||
|
||||
IsAnyViewInPathListeningToEvent(eventPathViews, ViewEvents::Offset::PointerMoveCapture);
|
||||
if (hasMoveEventListeners) {
|
||||
eventEmitter->onPointerMove(pointerEvent);
|
||||
}
|
||||
eventEmitter->onPointerMove(pointerEvent);
|
||||
break;
|
||||
}
|
||||
case RCTPointerEventTypeEnd: {
|
||||
@@ -792,11 +764,9 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act
|
||||
PointerEvent event = CreatePointerEventFromIncompleteHoverData(
|
||||
pointerId, pointerType, clientLocation, screenLocation, offsetLocation, modifierFlags);
|
||||
|
||||
NSOrderedSet<RCTReactTaggedView *> *eventPathViews = [self handleIncomingPointerEvent:event onView:targetView];
|
||||
[self handleIncomingPointerEvent:event onView:targetView];
|
||||
SharedTouchEventEmitter eventEmitter = GetTouchEmitterFromView(targetView, offsetLocation);
|
||||
BOOL hasMoveEventListeners = IsAnyViewInPathListeningToEvent(eventPathViews, ViewEvents::Offset::PointerMove) ||
|
||||
IsAnyViewInPathListeningToEvent(eventPathViews, ViewEvents::Offset::PointerMoveCapture);
|
||||
if (eventEmitter != nil && hasMoveEventListeners) {
|
||||
if (eventEmitter != nil) {
|
||||
eventEmitter->onPointerMove(event);
|
||||
}
|
||||
}
|
||||
@@ -831,10 +801,9 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act
|
||||
|
||||
// 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 (shouldEmitOutEvent && eventEmitter != nil) {
|
||||
if (eventEmitter != nil) {
|
||||
eventEmitter->onPointerOut(event);
|
||||
}
|
||||
}
|
||||
@@ -847,20 +816,14 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act
|
||||
// we reverse iterate (now from target to root), actually emitting the events.
|
||||
NSMutableOrderedSet<UIView *> *viewsToEmitLeaveEventsTo = [NSMutableOrderedSet orderedSet];
|
||||
|
||||
BOOL hasParentLeaveListener = NO;
|
||||
for (RCTReactTaggedView *taggedView in [currentlyHoveredViews reverseObjectEnumerator]) {
|
||||
UIView *componentView = taggedView.view;
|
||||
|
||||
BOOL shouldEmitEvent = componentView != nil &&
|
||||
(hasParentLeaveListener || IsViewListeningToEvent(taggedView, ViewEvents::Offset::PointerLeave));
|
||||
BOOL shouldEmitEvent = componentView != nil;
|
||||
|
||||
if (shouldEmitEvent && ![eventPathViews containsObject:taggedView]) {
|
||||
[viewsToEmitLeaveEventsTo addObject:componentView];
|
||||
}
|
||||
|
||||
if (shouldEmitEvent && !hasParentLeaveListener) {
|
||||
hasParentLeaveListener = YES;
|
||||
}
|
||||
}
|
||||
|
||||
for (UIView *componentView in [viewsToEmitLeaveEventsTo reverseObjectEnumerator]) {
|
||||
@@ -873,10 +836,9 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act
|
||||
|
||||
// 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 (shouldEmitOverEvent && eventEmitter != nil) {
|
||||
if (eventEmitter != nil) {
|
||||
eventEmitter->onPointerOver(event);
|
||||
}
|
||||
}
|
||||
@@ -888,12 +850,10 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act
|
||||
// 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 &&
|
||||
(hasParentEnterListener || IsViewListeningToEvent(taggedView, ViewEvents::Offset::PointerEnter));
|
||||
BOOL shouldEmitEvent = componentView != nil;
|
||||
|
||||
if (shouldEmitEvent && ![currentlyHoveredViews containsObject:taggedView]) {
|
||||
SharedTouchEventEmitter eventEmitter =
|
||||
@@ -902,10 +862,6 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act
|
||||
eventEmitter->onPointerEnter(event);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldEmitEvent && !hasParentEnterListener) {
|
||||
hasParentEnterListener = YES;
|
||||
}
|
||||
}
|
||||
|
||||
[_currentlyHoveredViewsPerPointer setObject:eventPathViews forKey:@(pointerId)];
|
||||
|
||||
@@ -62,6 +62,10 @@ struct ViewEvents {
|
||||
ClickCapture = 31,
|
||||
GotPointerCapture = 32,
|
||||
LostPointerCapture = 33,
|
||||
PointerDown = 34,
|
||||
PointerDownCapture = 35,
|
||||
PointerUp = 36,
|
||||
PointerUpCapture = 37,
|
||||
};
|
||||
|
||||
constexpr bool operator[](const Offset offset) const {
|
||||
|
||||
+22
-8
@@ -616,18 +616,32 @@ static inline ViewEvents convertRawProp(
|
||||
"onClickCapture",
|
||||
sourceValue[Offset::ClickCapture],
|
||||
defaultValue[Offset::ClickCapture]);
|
||||
result[Offset::GotPointerCapture] = convertRawProp(
|
||||
result[Offset::PointerDown] = convertRawProp(
|
||||
context,
|
||||
rawProps,
|
||||
"onGotPointerCapture",
|
||||
sourceValue[Offset::GotPointerCapture],
|
||||
defaultValue[Offset::GotPointerCapture]);
|
||||
result[Offset::LostPointerCapture] = convertRawProp(
|
||||
"onPointerDown",
|
||||
sourceValue[Offset::PointerDown],
|
||||
defaultValue[Offset::PointerDown]);
|
||||
result[Offset::PointerDownCapture] = convertRawProp(
|
||||
context,
|
||||
rawProps,
|
||||
"onLostPointerCapture",
|
||||
sourceValue[Offset::LostPointerCapture],
|
||||
defaultValue[Offset::LostPointerCapture]);
|
||||
"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)
|
||||
|
||||
// PanResponder callbacks
|
||||
result[Offset::MoveShouldSetResponder] = convertRawProp(
|
||||
|
||||
+168
-17
@@ -7,9 +7,80 @@
|
||||
|
||||
#include "PointerEventsProcessor.h"
|
||||
|
||||
#include <react/renderer/components/view/ViewShadowNode.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
static PointerEventTarget retargetPointerEvent(
|
||||
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<ViewEvents::Offset> eventTypes) {
|
||||
if (auto viewShadowNode = traitCast<ViewShadowNode const *>(&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<ViewEvents::Offset> 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(
|
||||
PointerEvent const &event,
|
||||
ShadowNode const &nodeToTarget,
|
||||
UIManager const &uiManager) {
|
||||
@@ -59,6 +130,72 @@ 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,
|
||||
@@ -79,14 +216,18 @@ 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);
|
||||
eventDispatcher(runtime, eventTarget, type, priority, pointerEvent);
|
||||
auto shadowNode = GetShadowNodeFromEventTarget(runtime, *eventTarget);
|
||||
if (shadowNode != nullptr &&
|
||||
ShouldEmitPointerEvent(*shadowNode, type, uiManager)) {
|
||||
eventDispatcher(runtime, eventTarget, type, priority, pointerEvent);
|
||||
}
|
||||
eventTarget->release(runtime);
|
||||
|
||||
// Implicit pointer capture release
|
||||
@@ -156,28 +297,38 @@ 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);
|
||||
eventDispatcher(
|
||||
runtime,
|
||||
retargeted.target.get(),
|
||||
"topLostPointerCapture",
|
||||
ReactEventPriority::Discrete,
|
||||
retargeted.event);
|
||||
auto shadowNode = GetShadowNodeFromEventTarget(runtime, *retargeted.target);
|
||||
if (shadowNode != nullptr &&
|
||||
ShouldEmitPointerEvent(
|
||||
*shadowNode, "topLostPointerCapture", uiManager)) {
|
||||
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);
|
||||
eventDispatcher(
|
||||
runtime,
|
||||
retargeted.target.get(),
|
||||
"topGotPointerCapture",
|
||||
ReactEventPriority::Discrete,
|
||||
retargeted.event);
|
||||
auto shadowNode = GetShadowNodeFromEventTarget(runtime, *retargeted.target);
|
||||
if (shadowNode != nullptr &&
|
||||
ShouldEmitPointerEvent(
|
||||
*shadowNode, "topGotPointerCapture", uiManager)) {
|
||||
eventDispatcher(
|
||||
runtime,
|
||||
retargeted.target.get(),
|
||||
"topGotPointerCapture",
|
||||
ReactEventPriority::Discrete,
|
||||
retargeted.event);
|
||||
}
|
||||
retargeted.target->release(runtime);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user