diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm index 2783342928d..f207e642a96 100644 --- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -327,6 +327,28 @@ static void RCTSendPaperScrollEvent_DEPRECATED(UIScrollView *scrollView, NSInteg } } +/* + * Returns whether or not the scroll view interaction should be blocked because + * JavaScript was found to be the responder. + */ +- (BOOL)_shouldDisableScrollInteraction +{ + UIView *ancestorView = self.superview; + + while (ancestorView) { + if ([ancestorView respondsToSelector:@selector(isJSResponder)]) { + BOOL isJSResponder = ((UIView *)ancestorView).isJSResponder; + if (isJSResponder) { + return YES; + } + } + + ancestorView = ancestorView.superview; + } + + return NO; +} + - (ScrollViewMetrics)_scrollViewMetrics { ScrollViewMetrics metrics; @@ -366,7 +388,7 @@ static void RCTSendPaperScrollEvent_DEPRECATED(UIScrollView *scrollView, NSInteg { // Historically, `UIScrollView`s in React Native do not cancel touches // started on `UIControl`-based views (as normal iOS `UIScrollView`s do). - return YES; + return ![self _shouldDisableScrollInteraction]; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView diff --git a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index dce5d37e5c9..9a302655b69 100644 --- a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -23,6 +23,7 @@ using namespace facebook::react; UIColor *_backgroundColor; CALayer *_borderLayer; BOOL _needsInvalidateLayer; + BOOL _isJSResponder; NSSet *_Nullable _propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN; } @@ -284,6 +285,16 @@ using namespace facebook::react; } } +- (BOOL)isJSResponder +{ + return _isJSResponder; +} + +- (void)setIsJSResponder:(BOOL)isJSResponder +{ + _isJSResponder = isJSResponder; +} + - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask { [super finalizeUpdates:updateMask]; @@ -310,6 +321,7 @@ using namespace facebook::react; _propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN = nil; _eventEmitter.reset(); + _isJSResponder = NO; } - (void)setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:(NSSet *_Nullable)props diff --git a/React/Fabric/Mounting/RCTComponentViewProtocol.h b/React/Fabric/Mounting/RCTComponentViewProtocol.h index 8183e419202..11493c42798 100644 --- a/React/Fabric/Mounting/RCTComponentViewProtocol.h +++ b/React/Fabric/Mounting/RCTComponentViewProtocol.h @@ -116,6 +116,9 @@ typedef NS_OPTIONS(NSInteger, RNComponentViewUpdateMask) { */ - (facebook::react::SharedProps)props; +- (BOOL)isJSResponder; +- (void)setIsJSResponder:(BOOL)isJSResponder; + /* * This is broken. Do not use. */ diff --git a/React/Fabric/Mounting/RCTMountingManager.h b/React/Fabric/Mounting/RCTMountingManager.h index f610ee965af..ea1e1d8650c 100644 --- a/React/Fabric/Mounting/RCTMountingManager.h +++ b/React/Fabric/Mounting/RCTMountingManager.h @@ -58,6 +58,8 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)sendAccessibilityEvent:(ReactTag)reactTag eventType:(NSString *)eventType; +- (void)setIsJSResponder:(BOOL)isJSResponder forShadowView:(facebook::react::ShadowView)shadowView; + - (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag changedProps:(NSDictionary *)props componentDescriptor:(facebook::react::ComponentDescriptor const &)componentDescriptor; diff --git a/React/Fabric/Mounting/RCTMountingManager.mm b/React/Fabric/Mounting/RCTMountingManager.mm index 4de3f5593bf..b5555757eae 100644 --- a/React/Fabric/Mounting/RCTMountingManager.mm +++ b/React/Fabric/Mounting/RCTMountingManager.mm @@ -259,6 +259,15 @@ static void RCTPerformMountInstructions( }); } +- (void)setIsJSResponder:(BOOL)isJSResponder forShadowView:(facebook::react::ShadowView)shadowView +{ + RCTExecuteOnMainQueue(^{ + UIView *componentView = + [self->_componentViewRegistry findComponentViewWithTag:shadowView.tag]; + [componentView setIsJSResponder:isJSResponder]; + }); +} + - (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag changedProps:(NSDictionary *)props componentDescriptor:(const ComponentDescriptor &)componentDescriptor diff --git a/React/Fabric/Mounting/UIView+ComponentViewProtocol.h b/React/Fabric/Mounting/UIView+ComponentViewProtocol.h index 3cb45b8f7b7..0a9579ed7a5 100644 --- a/React/Fabric/Mounting/UIView+ComponentViewProtocol.h +++ b/React/Fabric/Mounting/UIView+ComponentViewProtocol.h @@ -39,6 +39,8 @@ NS_ASSUME_NONNULL_BEGIN - (facebook::react::SharedProps)props; +- (void)setIsJSResponder:(BOOL)isJSResponder; + - (void)setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:(nullable NSSet *)props; - (nullable NSSet *)propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN; diff --git a/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm b/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm index c10dfbc8654..939faead8dc 100644 --- a/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm +++ b/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm @@ -132,6 +132,17 @@ using namespace facebook::react; return nullptr; } +- (BOOL)isJSResponder +{ + // Default implementation always returns `NO`. + return NO; +} + +- (void)setIsJSResponder:(BOOL)isJSResponder +{ + // Default implementation does nothing. +} + - (void)setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:(nullable NSSet *)propKeys { // Default implementation does nothing. diff --git a/React/Fabric/RCTScheduler.h b/React/Fabric/RCTScheduler.h index d68a01e47c0..55670daca64 100644 --- a/React/Fabric/RCTScheduler.h +++ b/React/Fabric/RCTScheduler.h @@ -34,6 +34,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)schedulerDidSendAccessibilityEvent:(facebook::react::ShadowView const &)shadowView eventType:(std::string const &)eventType; +- (void)schedulerDidSetIsJSResponder:(BOOL)isJSResponder forShadowView:(facebook::react::ShadowView const &)shadowView; + @end /** diff --git a/React/Fabric/RCTScheduler.mm b/React/Fabric/RCTScheduler.mm index bac276ca040..3e7281be63c 100644 --- a/React/Fabric/RCTScheduler.mm +++ b/React/Fabric/RCTScheduler.mm @@ -45,18 +45,10 @@ class SchedulerDelegateProxy : public SchedulerDelegate { [scheduler.delegate schedulerDidDispatchCommand:shadowView commandName:commandName args:args]; } - void schedulerDidSetJSResponder( - SurfaceId surfaceId, - const ShadowView &shadowView, - const ShadowView &initialShadowView, - bool blockNativeResponder) override + void schedulerDidSetIsJSResponder(ShadowView const &shadowView, bool isJSResponder) override { - // Does nothing for now. - } - - void schedulerDidClearJSResponder() override - { - // Does nothing for now. + RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + [scheduler.delegate schedulerDidSetIsJSResponder:isJSResponder forShadowView:shadowView]; } void schedulerDidSendAccessibilityEvent(const ShadowView &shadowView, std::string const &eventType) override diff --git a/React/Fabric/RCTSurfacePresenter.mm b/React/Fabric/RCTSurfacePresenter.mm index 7c752ad7703..e982243d62e 100644 --- a/React/Fabric/RCTSurfacePresenter.mm +++ b/React/Fabric/RCTSurfacePresenter.mm @@ -467,6 +467,11 @@ static BackgroundExecutor RCTGetBackgroundExecutor() [self->_mountingManager sendAccessibilityEvent:tag eventType:eventTypeStr]; } +- (void)schedulerDidSetIsJSResponder:(BOOL)isJSResponder forShadowView:(facebook::react::ShadowView const &)shadowView; +{ + [self->_mountingManager setIsJSResponder:isJSResponder forShadowView:shadowView]; +} + - (void)addObserver:(id)observer { std::unique_lock lock(_observerListMutex); diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp index 181250e0cd9..5d4ff6ae297 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp @@ -1106,11 +1106,9 @@ void Binding::schedulerDidSendAccessibilityEvent( eventTypeStr.get()); } -void Binding::schedulerDidSetJSResponder( - SurfaceId surfaceId, - const ShadowView &shadowView, - const ShadowView &initialShadowView, - bool blockNativeResponder) { +void Binding::schedulerDidSetIsJSResponder( + ShadowView const &shadowView, + bool isJSResponder) { jni::global_ref localJavaUIManager = getJavaUIManager(); if (!localJavaUIManager) { LOG(ERROR) << "Binding::schedulerSetJSResponder: JavaUIManager disappeared"; @@ -1121,27 +1119,24 @@ void Binding::schedulerDidSetJSResponder( jni::findClassStatic(Binding::UIManagerJavaDescriptor) ->getMethod("setJSResponder"); - setJSResponder( - localJavaUIManager, - shadowView.surfaceId, - shadowView.tag, - initialShadowView.tag, - (jboolean)blockNativeResponder); -} - -void Binding::schedulerDidClearJSResponder() { - jni::global_ref localJavaUIManager = getJavaUIManager(); - if (!localJavaUIManager) { - LOG(ERROR) - << "Binding::schedulerClearJSResponder: JavaUIManager disappeared"; - return; - } - static auto clearJSResponder = jni::findClassStatic(Binding::UIManagerJavaDescriptor) ->getMethod("clearJSResponder"); - clearJSResponder(localJavaUIManager); + if (isJSResponder) { + setJSResponder( + localJavaUIManager, + shadowView.surfaceId, + shadowView.tag, + // The closest non-flattened ancestor of the same value if the node is + // not flattened. For now, we don't support the case when the node can + // be flattened because the only component that uses this feature - + // ScrollView - cannot be flattened. + shadowView.tag, + (jboolean) true); + } else { + clearJSResponder(localJavaUIManager); + } } void Binding::registerNatives() { diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h index f2ac6cd479a..23ce763dc1b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h @@ -140,13 +140,9 @@ class Binding : public jni::HybridClass, const ShadowView &shadowView, std::string const &eventType) override; - void schedulerDidSetJSResponder( - SurfaceId surfaceId, - const ShadowView &shadowView, - const ShadowView &initialShadowView, - bool blockNativeResponder) override; - - void schedulerDidClearJSResponder() override; + void schedulerDidSetIsJSResponder( + ShadowView const &shadowView, + bool isJSResponder) override; void setPixelDensity(float pointScaleFactor); diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 3ca4efb45a0..026052ce5ac 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -423,27 +423,14 @@ void Scheduler::uiManagerDidSendAccessibilityEvent( } /* - * Set JS responder for a view + * Set JS responder for a view. */ -void Scheduler::uiManagerDidSetJSResponder( - SurfaceId surfaceId, - const ShadowNode::Shared &shadowNode, - bool blockNativeResponder) { +void Scheduler::uiManagerDidSetIsJSResponder( + ShadowNode::Shared const &shadowNode, + bool isJSResponder) { if (delegate_) { - // TODO: the first shadowView paramenter, should be the first parent that - // is non virtual. - auto shadowView = ShadowView(*shadowNode); - delegate_->schedulerDidSetJSResponder( - surfaceId, shadowView, shadowView, blockNativeResponder); - } -} - -/* - * Clear the JSResponder for a view - */ -void Scheduler::uiManagerDidClearJSResponder() { - if (delegate_) { - delegate_->schedulerDidClearJSResponder(); + delegate_->schedulerDidSetIsJSResponder( + ShadowView(*shadowNode), isJSResponder); } } diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.h b/ReactCommon/react/renderer/scheduler/Scheduler.h index 9f75805ac70..8e0c8db0c5a 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -122,11 +122,9 @@ class Scheduler final : public UIManagerDelegate { void uiManagerDidSendAccessibilityEvent( const ShadowNode::Shared &shadowNode, std::string const &eventType) override; - void uiManagerDidSetJSResponder( - SurfaceId surfaceId, - const ShadowNode::Shared &shadowView, - bool blockNativeResponder) override; - void uiManagerDidClearJSResponder() override; + void uiManagerDidSetIsJSResponder( + ShadowNode::Shared const &shadowView, + bool isJSResponder) override; private: friend class SurfaceHandler; diff --git a/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h b/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h index dabe3638b98..726e7ef937a 100644 --- a/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h +++ b/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h @@ -48,16 +48,9 @@ class SchedulerDelegate { /* * Set JS responder for a view */ - virtual void schedulerDidSetJSResponder( - SurfaceId surfaceId, - const ShadowView &shadowView, - const ShadowView &initialShadowView, - bool blockNativeResponder) = 0; - - /* - * Clear the JSResponder for a view - */ - virtual void schedulerDidClearJSResponder() = 0; + virtual void schedulerDidSetIsJSResponder( + ShadowView const &shadowView, + bool isJSResponder) = 0; virtual ~SchedulerDelegate() noexcept = default; }; diff --git a/ReactCommon/react/renderer/uimanager/UIManager.cpp b/ReactCommon/react/renderer/uimanager/UIManager.cpp index 6b31bf8e990..f13f0e76909 100644 --- a/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -112,18 +112,11 @@ void UIManager::completeSurface( }); } -void UIManager::setJSResponder( - const ShadowNode::Shared &shadowNode, - const bool blockNativeResponder) const { +void UIManager::setIsJSResponder( + ShadowNode::Shared const &shadowNode, + bool isJSResponder) const { if (delegate_) { - delegate_->uiManagerDidSetJSResponder( - shadowNode->getSurfaceId(), shadowNode, blockNativeResponder); - } -} - -void UIManager::clearJSResponder() const { - if (delegate_) { - delegate_->uiManagerDidClearJSResponder(); + delegate_->uiManagerDidSetIsJSResponder(shadowNode, isJSResponder); } } diff --git a/ReactCommon/react/renderer/uimanager/UIManager.h b/ReactCommon/react/renderer/uimanager/UIManager.h index 6dc8a44f968..36f15fa8815 100644 --- a/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/ReactCommon/react/renderer/uimanager/UIManager.h @@ -129,11 +129,9 @@ class UIManager final : public ShadowTreeDelegate { SharedShadowNodeUnsharedList const &rootChildren, ShadowTree::CommitOptions commitOptions) const; - void setJSResponder( - const ShadowNode::Shared &shadowNode, - const bool blockNativeResponder) const; - - void clearJSResponder() const; + void setIsJSResponder( + ShadowNode::Shared const &shadowNode, + bool isJSResponder) const; ShadowNode::Shared findNodeAtPoint( ShadowNode::Shared const &shadowNode, diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index 3dc2495bf7f..59fe6908618 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -263,7 +263,7 @@ jsi::Value UIManagerBinding::get( }); } - if (methodName == "setJSResponder") { + if (methodName == "setIsJSResponder") { return jsi::Function::createFromHostFunction( runtime, name, @@ -273,7 +273,7 @@ jsi::Value UIManagerBinding::get( jsi::Value const &thisValue, jsi::Value const *arguments, size_t count) noexcept -> jsi::Value { - uiManager->setJSResponder( + uiManager->setIsJSResponder( shadowNodeFromValue(runtime, arguments[0]), arguments[1].getBool()); @@ -311,22 +311,6 @@ jsi::Value UIManagerBinding::get( }); } - if (methodName == "clearJSResponder") { - return jsi::Function::createFromHostFunction( - runtime, - name, - 0, - [uiManager]( - jsi::Runtime &runtime, - jsi::Value const &thisValue, - jsi::Value const *arguments, - size_t count) noexcept -> jsi::Value { - uiManager->clearJSResponder(); - - return jsi::Value::undefined(); - }); - } - // Semantic: Clones the node with *same* props and *empty* children. if (methodName == "cloneNodeWithNewChildren") { return jsi::Function::createFromHostFunction( diff --git a/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h b/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h index 5062158f60d..5e993056c42 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h +++ b/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h @@ -52,17 +52,11 @@ class UIManagerDelegate { std::string const &eventType) = 0; /* - * Set JS responder for a view + * Set JS responder for a view. */ - virtual void uiManagerDidSetJSResponder( - SurfaceId surfaceId, - ShadowNode::Shared const &shadowView, - bool blockNativeResponder) = 0; - - /* - * Clear the JSResponder for a view - */ - virtual void uiManagerDidClearJSResponder() = 0; + virtual void uiManagerDidSetIsJSResponder( + ShadowNode::Shared const &shadowNode, + bool isJSResponder) = 0; virtual ~UIManagerDelegate() noexcept = default; }; diff --git a/ReactCommon/react/renderer/uimanager/primitives.h b/ReactCommon/react/renderer/uimanager/primitives.h index 9fb2ea92786..a87deeaa4bc 100644 --- a/ReactCommon/react/renderer/uimanager/primitives.h +++ b/ReactCommon/react/renderer/uimanager/primitives.h @@ -43,6 +43,10 @@ struct ShadowNodeListWrapper : public jsi::HostObject { inline static ShadowNode::Shared shadowNodeFromValue( jsi::Runtime &runtime, jsi::Value const &value) { + if (value.isNull()) { + return nullptr; + } + return value.getObject(runtime) .getHostObject(runtime) ->shadowNode;