From 87749470ccf596c5b3bc06fe46ba3239b684fd1b Mon Sep 17 00:00:00 2001 From: Peter Abbondanzo Date: Sun, 3 Aug 2025 18:04:43 -0700 Subject: [PATCH] Ensure active touches are swept before accepting a child native gesture (#52995) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52995 Fabric retains views by ID when `JSTouchDispatcher` receives a touch event, but does not sweep these same views if a child native gesture is started between the `ACTION_DOWN` and `ACTION_UP` actions of the touch. As a result, we never end up calling into that view's manager's `onDropViewInstance` method and can't perform reliable teardown of the view since it's stuck in this "touched" state. This is change adds a new condition to check if `JSTouchDispatcher` should sweep active touches when a child native gesture is started, and only applies the check to `ReactSurfaceView` to start. The check is also only enabled if the `sweepActiveTouchOnChildNativeGesturesAndroid` flag is set. Changelog: [Internal] Reviewed By: jehartzog Differential Revision: D79230277 fbshipit-source-id: c15b888ec932319f1bda05b8ef5eec39e5d08710 --- .../facebook/react/runtime/ReactSurfaceView.kt | 3 ++- .../react/uimanager/JSTouchDispatcher.kt | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactSurfaceView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactSurfaceView.kt index 0948dc932ee..a4e0807fbea 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactSurfaceView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactSurfaceView.kt @@ -116,7 +116,8 @@ public class ReactSurfaceView(context: Context?, private val surface: ReactSurfa */ override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) { val eventDispatcher = surface.eventDispatcher ?: return - jsTouchDispatcher.onChildStartedNativeGesture(ev, eventDispatcher) + jsTouchDispatcher.onChildStartedNativeGesture( + ev, eventDispatcher, surface.reactHost?.currentReactContext) childView?.let { jsPointerDispatcher?.onChildStartedNativeGesture(it, ev, eventDispatcher) } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.kt index 86250eaa604..b28b674581a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.kt @@ -13,6 +13,8 @@ import com.facebook.common.logging.FLog import com.facebook.infer.annotation.Assertions import com.facebook.react.bridge.ReactContext import com.facebook.react.common.ReactConstants +import com.facebook.react.common.annotations.UnstableReactNativeAPI +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import com.facebook.react.uimanager.common.UIManagerType import com.facebook.react.uimanager.events.EventDispatcher import com.facebook.react.uimanager.events.TouchEvent @@ -33,9 +35,19 @@ public class JSTouchDispatcher(private val viewGroup: ViewGroup) { private val touchEventCoalescingKeyHelper: TouchEventCoalescingKeyHelper = TouchEventCoalescingKeyHelper() + @OptIn(UnstableReactNativeAPI::class) public fun onChildStartedNativeGesture( androidEvent: MotionEvent, eventDispatcher: EventDispatcher + ) { + onChildStartedNativeGesture(androidEvent, eventDispatcher, null) + } + + @UnstableReactNativeAPI + public fun onChildStartedNativeGesture( + androidEvent: MotionEvent, + eventDispatcher: EventDispatcher, + reactContext: ReactContext?, ) { if (childIsHandlingNativeGesture) { // This means we previously had another child start handling this native gesture and now a @@ -46,6 +58,12 @@ public class JSTouchDispatcher(private val viewGroup: ViewGroup) { dispatchCancelEvent(androidEvent, eventDispatcher) childIsHandlingNativeGesture = true + + if (targetTag != -1 && ReactNativeFeatureFlags.sweepActiveTouchOnChildNativeGesturesAndroid()) { + val surfaceId = UIManagerHelper.getSurfaceId(viewGroup) + sweepActiveTouchForTag(surfaceId, targetTag, reactContext) + } + targetTag = -1 }