From 980c52de41258f6cf2d2360144ea7ca16a19c9f8 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Thu, 10 Feb 2022 06:05:21 -0800 Subject: [PATCH] Disable view flattening when the view has event handlers on Android Summary: The views with touch event props are currently flattened by Fabric core, as we don't take event listeners into account when calculating whether the view should be flattened. This results in a confusing situation when components with touch event listeners (e.g. ` {}} /> `) or ones using `PanResponder` are either ignored (iOS) or cause a crash (Android). This change passes touch event props to C++ layer and uses them to calculate whether the view node should be flattened or not. It also refactors events to be kept as a singular bitset with 32 bit (~`uint32_t`). Changelog: [Changed][General] Avoid flattening nodes with event props Reviewed By: sammy-SC Differential Revision: D34005536 fbshipit-source-id: 96255b389a7bfff4aa208a96fd0c173d9edf1512 --- .../NativeComponent/PlatformBaseViewConfig.js | 55 ++++++- React/Views/RCTViewManager.m | 25 ++++ .../react/uimanager/BaseViewManager.java | 97 ++++++++++++- .../react/uimanager/LayoutShadowNode.java | 6 +- .../uimanager/UIManagerModuleConstants.java | 6 +- .../renderer/components/view/ViewProps.cpp | 19 +-- .../renderer/components/view/ViewProps.h | 6 +- .../components/view/ViewShadowNode.cpp | 4 +- .../renderer/components/view/primitives.h | 41 ++++++ .../components/view/propsConversions.h | 136 ++++++++++++++++++ 10 files changed, 352 insertions(+), 43 deletions(-) diff --git a/Libraries/NativeComponent/PlatformBaseViewConfig.js b/Libraries/NativeComponent/PlatformBaseViewConfig.js index 1487637b03..c3454a3eec 100644 --- a/Libraries/NativeComponent/PlatformBaseViewConfig.js +++ b/Libraries/NativeComponent/PlatformBaseViewConfig.js @@ -38,13 +38,13 @@ const PlatformBaseViewConfig: PartialViewConfigWithoutName = registrationName: 'onAccessibilityAction', }, topPointerEnter: { - registrationName: 'pointerenter', + registrationName: 'onPointerEnter', }, topPointerLeave: { - registrationName: 'pointerleave', + registrationName: 'onPointerLeave', }, topPointerMove: { - registrationName: 'pointermove', + registrationName: 'onPointerMove', }, onGestureHandlerEvent: DynamicallyInjectedByGestureHandler({ registrationName: 'onGestureHandlerEvent', @@ -219,9 +219,31 @@ const PlatformBaseViewConfig: PartialViewConfigWithoutName = position: true, onLayout: true, - pointerenter: true, - pointerleave: true, - pointermove: true, + // Pointer events + onPointerEnter: true, + onPointerLeave: true, + onPointerMove: true, + + // PanResponder handlers + onMoveShouldSetResponder: true, + onMoveShouldSetResponderCapture: true, + onStartShouldSetResponder: true, + onStartShouldSetResponderCapture: true, + onResponderGrant: true, + onResponderReject: true, + onResponderStart: true, + onResponderEnd: true, + onResponderRelease: true, + onResponderMove: true, + onResponderTerminate: true, + onResponderTerminationRequest: true, + onShouldBlockNativeResponder: true, + + // Touch events + onTouchStart: true, + onTouchMove: true, + onTouchEnd: true, + onTouchCancel: true, style: ReactNativeStyleAttributes, }, @@ -456,6 +478,27 @@ const PlatformBaseViewConfig: PartialViewConfigWithoutName = onAccessibilityAction: true, onAccessibilityEscape: true, onAccessibilityTap: true, + + // PanResponder handlers + onMoveShouldSetResponder: true, + onMoveShouldSetResponderCapture: true, + onStartShouldSetResponder: true, + onStartShouldSetResponderCapture: true, + onResponderGrant: true, + onResponderReject: true, + onResponderStart: true, + onResponderEnd: true, + onResponderRelease: true, + onResponderMove: true, + onResponderTerminate: true, + onResponderTerminationRequest: true, + onShouldBlockNativeResponder: true, + + // Touch events + onTouchStart: true, + onTouchMove: true, + onTouchEnd: true, + onTouchCancel: true, }), }, }; diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index a46158dcee..a18174ea4f 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -418,4 +418,29 @@ RCT_EXPORT_SHADOW_PROPERTY(onLayout, RCTDirectEventBlock) RCT_EXPORT_SHADOW_PROPERTY(direction, YGDirection) +// The events below define the properties that are not used by native directly, but required in the view config for new +// renderer to function. +// They can be deleted after Static View Configs are rolled out. + +// PanResponder handlers +RCT_CUSTOM_VIEW_PROPERTY(onMoveShouldSetResponder, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onMoveShouldSetResponderCapture, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onStartShouldSetResponder, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onStartShouldSetResponderCapture, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onResponderGrant, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onResponderReject, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onResponderStart, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onResponderEnd, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onResponderRelease, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onResponderMove, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onResponderTerminate, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onResponderTerminationRequest, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onShouldBlockNativeResponder, BOOL, RCTView) {} + +// Touch events +RCT_CUSTOM_VIEW_PROPERTY(onTouchStart, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onTouchMove, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onTouchEnd, BOOL, RCTView) {} +RCT_CUSTOM_VIEW_PROPERTY(onTouchCancel, BOOL, RCTView) {} + @end diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index 3c9c1eae3b..b48b272d9c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -486,18 +486,103 @@ public abstract class BaseViewManager #include #include +#include #include namespace facebook { @@ -18,6 +19,46 @@ namespace react { enum class PointerEventsMode { Auto, None, BoxNone, BoxOnly }; +struct ViewEvents { + std::bitset<32> bits{}; + + enum class Offset : std::size_t { + // Pointer events + PointerEnter = 0, + PointerMove = 1, + PointerLeave = 2, + + // PanResponder callbacks + MoveShouldSetResponder = 3, + MoveShouldSetResponderCapture = 4, + StartShouldSetResponder = 5, + StartShouldSetResponderCapture = 6, + ResponderGrant = 7, + ResponderReject = 8, + ResponderStart = 9, + ResponderEnd = 10, + ResponderRelease = 11, + ResponderMove = 12, + ResponderTerminate = 13, + ResponderTerminationRequest = 14, + ShouldBlockNativeResponder = 15, + + // Touch events + TouchStart = 16, + TouchMove = 17, + TouchEnd = 18, + TouchCancel = 19, + }; + + constexpr bool operator[](const Offset offset) const { + return bits[static_cast(offset)]; + } + + std::bitset<32>::reference operator[](const Offset offset) { + return bits[static_cast(offset)]; + } +}; + enum class BackfaceVisibility { Auto, Visible, Hidden }; enum class BorderStyle { Solid, Dotted, Dashed }; diff --git a/ReactCommon/react/renderer/components/view/propsConversions.h b/ReactCommon/react/renderer/components/view/propsConversions.h index 6d7233f50c..502b14704b 100644 --- a/ReactCommon/react/renderer/components/view/propsConversions.h +++ b/ReactCommon/react/renderer/components/view/propsConversions.h @@ -463,5 +463,141 @@ static inline CascadedRectangleEdges convertRawProp( return result; } +static inline ViewEvents convertRawProp( + const PropsParserContext &context, + RawProps const &rawProps, + ViewEvents const &sourceValue, + ViewEvents const &defaultValue) { + ViewEvents result{}; + using Offset = ViewEvents::Offset; + + result[Offset::PointerEnter] = convertRawProp( + context, + rawProps, + "onPointerEnter", + sourceValue[Offset::PointerEnter], + defaultValue[Offset::PointerEnter]); + result[Offset::PointerMove] = convertRawProp( + context, + rawProps, + "onPointerMove", + sourceValue[Offset::PointerMove], + defaultValue[Offset::PointerMove]); + result[Offset::PointerLeave] = convertRawProp( + context, + rawProps, + "onPointerLeave", + sourceValue[Offset::PointerLeave], + defaultValue[Offset::PointerLeave]); + + // PanResponder callbacks + result[Offset::MoveShouldSetResponder] = convertRawProp( + context, + rawProps, + "onMoveShouldSetResponder", + sourceValue[Offset::MoveShouldSetResponder], + defaultValue[Offset::MoveShouldSetResponder]); + result[Offset::MoveShouldSetResponderCapture] = convertRawProp( + context, + rawProps, + "onMoveShouldSetResponderCapture", + sourceValue[Offset::MoveShouldSetResponderCapture], + defaultValue[Offset::MoveShouldSetResponderCapture]); + result[Offset::StartShouldSetResponder] = convertRawProp( + context, + rawProps, + "onStartShouldSetResponder", + sourceValue[Offset::StartShouldSetResponder], + defaultValue[Offset::StartShouldSetResponder]); + result[Offset::StartShouldSetResponderCapture] = convertRawProp( + context, + rawProps, + "onStartShouldSetResponderCapture", + sourceValue[Offset::StartShouldSetResponderCapture], + defaultValue[Offset::StartShouldSetResponderCapture]); + result[Offset::ResponderGrant] = convertRawProp( + context, + rawProps, + "onResponderGrant", + sourceValue[Offset::ResponderGrant], + defaultValue[Offset::ResponderGrant]); + result[Offset::ResponderReject] = convertRawProp( + context, + rawProps, + "onResponderReject", + sourceValue[Offset::ResponderReject], + defaultValue[Offset::ResponderReject]); + result[Offset::ResponderStart] = convertRawProp( + context, + rawProps, + "onResponderStart", + sourceValue[Offset::ResponderStart], + defaultValue[Offset::ResponderStart]); + result[Offset::ResponderEnd] = convertRawProp( + context, + rawProps, + "onResponderEnd", + sourceValue[Offset::ResponderEnd], + defaultValue[Offset::ResponderEnd]); + result[Offset::ResponderRelease] = convertRawProp( + context, + rawProps, + "onResponderRelease", + sourceValue[Offset::ResponderRelease], + defaultValue[Offset::ResponderRelease]); + result[Offset::ResponderMove] = convertRawProp( + context, + rawProps, + "onResponderMove", + sourceValue[Offset::ResponderMove], + defaultValue[Offset::ResponderMove]); + result[Offset::ResponderTerminate] = convertRawProp( + context, + rawProps, + "onResponderTerminate", + sourceValue[Offset::ResponderTerminate], + defaultValue[Offset::ResponderTerminate]); + result[Offset::ResponderTerminationRequest] = convertRawProp( + context, + rawProps, + "onResponderTerminationRequest", + sourceValue[Offset::ResponderTerminationRequest], + defaultValue[Offset::ResponderTerminationRequest]); + result[Offset::ShouldBlockNativeResponder] = convertRawProp( + context, + rawProps, + "onShouldBlockNativeResponder", + sourceValue[Offset::ShouldBlockNativeResponder], + defaultValue[Offset::ShouldBlockNativeResponder]); + + // Touch events + result[Offset::TouchStart] = convertRawProp( + context, + rawProps, + "onTouchStart", + sourceValue[Offset::TouchStart], + defaultValue[Offset::TouchStart]); + result[Offset::TouchMove] = convertRawProp( + context, + rawProps, + "onTouchMove", + sourceValue[Offset::TouchMove], + defaultValue[Offset::TouchMove]); + result[Offset::TouchEnd] = convertRawProp( + context, + rawProps, + "onTouchEnd", + sourceValue[Offset::TouchEnd], + defaultValue[Offset::TouchEnd]); + result[Offset::TouchCancel] = convertRawProp( + context, + rawProps, + "onTouchCancel", + sourceValue[Offset::TouchCancel], + defaultValue[Offset::TouchCancel]); + + return result; +} + } // namespace react } // namespace facebook