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
This commit is contained in:
Peter Abbondanzo
2025-08-03 18:04:43 -07:00
committed by Facebook GitHub Bot
parent f2964e17cd
commit 87749470cc
2 changed files with 20 additions and 1 deletions
@@ -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) }
}
@@ -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
}