diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index a5fa388742a7..8c91b5df70c8 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -1372,8 +1372,8 @@ public abstract interface class com/facebook/react/bridge/UIManager : com/facebo public abstract fun initialize ()V public abstract fun invalidate ()V public abstract fun markActiveTouchForTag (II)V - public abstract fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V - public abstract fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V + public abstract fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V + public abstract fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V public abstract fun removeUIManagerEventListener (Lcom/facebook/react/bridge/UIManagerListener;)V public abstract fun resolveCustomDirectEventName (Ljava/lang/String;)Ljava/lang/String; public abstract fun resolveView (I)Landroid/view/View; @@ -2275,11 +2275,11 @@ public class com/facebook/react/fabric/FabricUIManager : com/facebook/react/brid public fun onRequestEventBeat ()V public fun prependUIBlock (Lcom/facebook/react/fabric/interop/UIBlock;)V public fun profileNextBatch ()V - public fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V - public fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;I)V - public fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;IZ)V - public fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;IZJ)V - public fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V + public fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V + public fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/ReadableMap;I)V + public fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/ReadableMap;IZ)V + public fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/ReadableMap;IZJ)V + public fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V public fun removeUIManagerEventListener (Lcom/facebook/react/bridge/UIManagerListener;)V public fun resolveCustomDirectEventName (Ljava/lang/String;)Ljava/lang/String; public fun resolveView (I)Landroid/view/View; @@ -3503,6 +3503,7 @@ public final class com/facebook/react/uimanager/NativeViewHierarchyOptimizer { public final class com/facebook/react/uimanager/OnLayoutEvent : com/facebook/react/uimanager/events/Event { public static final field Companion Lcom/facebook/react/uimanager/OnLayoutEvent$Companion; + public synthetic fun getEventData ()Lcom/facebook/react/bridge/ReadableMap; public fun getEventName ()Ljava/lang/String; public static final fun obtain (IIIII)Lcom/facebook/react/uimanager/OnLayoutEvent; public static final fun obtain (IIIIII)Lcom/facebook/react/uimanager/OnLayoutEvent; @@ -4275,8 +4276,8 @@ public class com/facebook/react/uimanager/UIManagerModule : com/facebook/react/b public fun onHostResume ()V public fun prependUIBlock (Lcom/facebook/react/uimanager/UIBlock;)V public fun profileNextBatch ()V - public fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V - public fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V + public fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V + public fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V public fun removeRootView (I)V public fun removeUIManagerEventListener (Lcom/facebook/react/bridge/UIManagerListener;)V public fun resolveCustomDirectEventName (Ljava/lang/String;)Ljava/lang/String; @@ -4679,6 +4680,7 @@ public abstract interface class com/facebook/react/uimanager/events/BatchEventDi public final class com/facebook/react/uimanager/events/ContentSizeChangeEvent : com/facebook/react/uimanager/events/Event { public fun (III)V public fun (IIII)V + public synthetic fun getEventData ()Lcom/facebook/react/bridge/ReadableMap; public fun getEventName ()Ljava/lang/String; } @@ -4695,7 +4697,7 @@ public abstract class com/facebook/react/uimanager/events/Event { public fun getCoalescingKey ()S public fun getEventAnimationDriverMatchSpec ()Lcom/facebook/react/uimanager/events/Event$EventAnimationDriverMatchSpec; protected fun getEventCategory ()I - protected fun getEventData ()Lcom/facebook/react/bridge/WritableMap; + protected fun getEventData ()Lcom/facebook/react/bridge/ReadableMap; public abstract fun getEventName ()Ljava/lang/String; public final fun getSurfaceId ()I public final fun getTimestampMs ()J @@ -4734,7 +4736,7 @@ public final class com/facebook/react/uimanager/events/NativeGestureUtil { } public abstract interface class com/facebook/react/uimanager/events/RCTEventEmitter : com/facebook/react/bridge/JavaScriptModule { - public abstract fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V + public abstract fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V public fun receiveTouches (Ljava/lang/String;Lcom/facebook/react/bridge/WritableArray;Lcom/facebook/react/bridge/WritableArray;)V } @@ -4743,16 +4745,16 @@ public final class com/facebook/react/uimanager/events/RCTEventEmitter$DefaultIm } public abstract interface class com/facebook/react/uimanager/events/RCTModernEventEmitter : com/facebook/react/uimanager/events/RCTEventEmitter { - public fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V - public abstract fun receiveEvent (IILjava/lang/String;ZILcom/facebook/react/bridge/WritableMap;I)V - public fun receiveEvent (IILjava/lang/String;ZILcom/facebook/react/bridge/WritableMap;IJ)V - public fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V + public fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V + public abstract fun receiveEvent (IILjava/lang/String;ZILcom/facebook/react/bridge/ReadableMap;I)V + public fun receiveEvent (IILjava/lang/String;ZILcom/facebook/react/bridge/ReadableMap;IJ)V + public fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V } public final class com/facebook/react/uimanager/events/RCTModernEventEmitter$DefaultImpls { - public static fun receiveEvent (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;IILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V - public static fun receiveEvent (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;IILjava/lang/String;ZILcom/facebook/react/bridge/WritableMap;IJ)V - public static fun receiveEvent (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V + public static fun receiveEvent (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;IILjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V + public static fun receiveEvent (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;IILjava/lang/String;ZILcom/facebook/react/bridge/ReadableMap;IJ)V + public static fun receiveEvent (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;ILjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V public static fun receiveTouches (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;Ljava/lang/String;Lcom/facebook/react/bridge/WritableArray;Lcom/facebook/react/bridge/WritableArray;)V } @@ -5238,6 +5240,7 @@ public final class com/facebook/react/views/image/ImageLoadEvent : com/facebook/ public static final fun createProgressEvent (ILjava/lang/String;II)Lcom/facebook/react/views/image/ImageLoadEvent; public static final fun eventNameForType (I)Ljava/lang/String; public fun getCoalescingKey ()S + public synthetic fun getEventData ()Lcom/facebook/react/bridge/ReadableMap; public fun getEventName ()Ljava/lang/String; } @@ -5924,6 +5927,7 @@ public final class com/facebook/react/views/scroll/ReactScrollViewManager$Compan public final class com/facebook/react/views/scroll/ScrollEvent : com/facebook/react/uimanager/events/Event { public static final field Companion Lcom/facebook/react/views/scroll/ScrollEvent$Companion; public fun canCoalesce ()Z + public synthetic fun getEventData ()Lcom/facebook/react/bridge/ReadableMap; public fun getEventName ()Ljava/lang/String; public static final fun obtain (IILcom/facebook/react/views/scroll/ScrollEventType;FFFFIIII)Lcom/facebook/react/views/scroll/ScrollEvent; public static final fun obtain (ILcom/facebook/react/views/scroll/ScrollEventType;FFFFIIII)Lcom/facebook/react/views/scroll/ScrollEvent; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/EventAnimationDriver.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/EventAnimationDriver.kt index b40e19efa966..431ac82094b5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/EventAnimationDriver.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/EventAnimationDriver.kt @@ -11,7 +11,6 @@ import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.ReadableType import com.facebook.react.bridge.UnexpectedNativeTypeException -import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.EventCategoryDef import com.facebook.react.uimanager.events.RCTModernEventEmitter @@ -29,7 +28,7 @@ internal class EventAnimationDriver( eventName: String, canCoalesceEvent: Boolean, customCoalesceKey: Int, - params: WritableMap?, + params: ReadableMap?, @EventCategoryDef category: Int, ) { requireNotNull(params) { "Native animated events must have event data." } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.kt index 7587d9cc7a89..ac10eaf2de32 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.kt @@ -25,7 +25,7 @@ public interface UIManager : PerformanceCounter { @ThreadConfined(ThreadConfined.UI) @Deprecated( "This legacy surface API will be removed in a future release. " + - "Use ReactHost.createSurface(...) and ReactSurface.start() instead." + "Use ReactHost.createSurface(...) and ReactSurface.start() instead.", ) public fun addRootView(rootView: T, initialProps: WritableMap?): Int @@ -37,7 +37,7 @@ public interface UIManager : PerformanceCounter { @AnyThread @Deprecated( "This legacy surface API will be removed in a future release. " + - "Use ReactHost.createSurface(...) and ReactSurface.start() instead." + "Use ReactHost.createSurface(...) and ReactSurface.start() instead.", ) public fun startSurface( rootView: T, @@ -55,7 +55,7 @@ public interface UIManager : PerformanceCounter { */ @AnyThread @Deprecated( - "This legacy surface API will be removed in a future release. Use ReactSurface.stop() instead." + "This legacy surface API will be removed in a future release. Use ReactSurface.stop() instead.", ) public fun stopSurface(surfaceId: Int) @@ -156,10 +156,11 @@ public interface UIManager : PerformanceCounter { * * @param reactTag tag * @param eventName name of the event - * @param event parameters + * @param event native-backed event parameters; Fabric consumes the payload during dispatch, so it + * must not be reused afterwards */ @Deprecated("", ReplaceWith("receiveEvent(surfaceId, reactTag, eventName, event)")) - public fun receiveEvent(reactTag: Int, eventName: String, event: WritableMap?) + public fun receiveEvent(reactTag: Int, eventName: String, event: ReadableMap?) /** * This method dispatches events from RN Android code to JS. The delivery of this event will not @@ -168,9 +169,10 @@ public interface UIManager : PerformanceCounter { * @param surfaceId * @param reactTag tag * @param eventName name of the event - * @param event parameters + * @param event native-backed event parameters; Fabric consumes the payload during dispatch, so it + * must not be reused afterwards */ - public fun receiveEvent(surfaceId: Int, reactTag: Int, eventName: String, event: WritableMap?) + public fun receiveEvent(surfaceId: Int, reactTag: Int, eventName: String, event: ReadableMap?) /** Resolves Direct Event name exposed to JS from the one known to the Native side. */ @Deprecated("") public fun resolveCustomDirectEventName(eventName: String): String? diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index 2727e880d083..4d43b78a3bbc 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -1180,14 +1180,14 @@ public void updateRootLayoutSpecs( @Deprecated @Override - public void receiveEvent(int reactTag, String eventName, @Nullable WritableMap params) { + public void receiveEvent(int reactTag, String eventName, @Nullable ReadableMap params) { receiveEvent(View.NO_ID, reactTag, eventName, false, params, EventCategoryDef.UNSPECIFIED); } @Deprecated @Override public void receiveEvent( - int surfaceId, int reactTag, String eventName, @Nullable WritableMap params) { + int surfaceId, int reactTag, String eventName, @Nullable ReadableMap params) { receiveEvent(surfaceId, reactTag, eventName, false, params, EventCategoryDef.UNSPECIFIED); } @@ -1212,7 +1212,7 @@ public void receiveEvent( int reactTag, String eventName, boolean canCoalesceEvent, - @Nullable WritableMap params, + @Nullable ReadableMap params, @EventCategoryDef int eventCategory) { receiveEvent( surfaceId, @@ -1248,7 +1248,7 @@ public void receiveEvent( int reactTag, String eventName, boolean canCoalesceEvent, - @Nullable WritableMap params, + @Nullable ReadableMap params, @EventCategoryDef int eventCategory, boolean experimentalIsSynchronous) { receiveEvent( @@ -1284,7 +1284,7 @@ public void receiveEvent( int reactTag, String eventName, boolean canCoalesceEvent, - @Nullable WritableMap params, + @Nullable ReadableMap params, @EventCategoryDef int eventCategory, boolean experimentalIsSynchronous, long eventTimestamp) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/events/EventEmitterWrapper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/events/EventEmitterWrapper.kt index 4a61bf606ad9..5c662ba71242 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/events/EventEmitterWrapper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/events/EventEmitterWrapper.kt @@ -11,8 +11,8 @@ import android.annotation.SuppressLint import com.facebook.jni.HybridClassBase import com.facebook.proguard.annotations.DoNotStripAny import com.facebook.react.bridge.NativeMap +import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.UiThreadUtil -import com.facebook.react.bridge.WritableMap import com.facebook.react.fabric.FabricSoLoader.staticInit import com.facebook.react.uimanager.events.EventCategoryDef @@ -46,14 +46,14 @@ internal class EventEmitterWrapper private constructor() : HybridClassBase() { * Invokes the execution of the C++ EventEmitter. * * @param eventName [String] name of the event to execute. - * @param params [WritableMap] payload of the event + * @param params [ReadableMap] payload of the event * @param eventCategory event category * @param eventTimestamp timestamp when the event was triggered (in milliseconds since boot) */ @Synchronized fun dispatch( eventName: String, - params: WritableMap?, + params: ReadableMap?, @EventCategoryDef eventCategory: Int, eventTimestamp: Long, ) { @@ -64,7 +64,7 @@ internal class EventEmitterWrapper private constructor() : HybridClassBase() { } @Synchronized - fun dispatchEventSynchronously(eventName: String, params: WritableMap?, eventTimestamp: Long) { + fun dispatchEventSynchronously(eventName: String, params: ReadableMap?, eventTimestamp: Long) { if (!isValid) { return } @@ -77,11 +77,11 @@ internal class EventEmitterWrapper private constructor() : HybridClassBase() { * target. * * @param eventName [String] name of the event to execute. - * @param params [WritableMap] payload of the event + * @param params [ReadableMap] payload of the event * @param eventTimestamp timestamp when the event was triggered (in milliseconds since boot) */ @Synchronized - fun dispatchUnique(eventName: String, params: WritableMap?, eventTimestamp: Long) { + fun dispatchUnique(eventName: String, params: ReadableMap?, eventTimestamp: Long) { if (!isValid) { return } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.kt index dad9cd3d5556..565e64249144 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.kt @@ -8,7 +8,7 @@ package com.facebook.react.fabric.events import android.os.SystemClock -import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.ReadableMap import com.facebook.react.fabric.FabricUIManager import com.facebook.react.uimanager.events.EventCategoryDef import com.facebook.react.uimanager.events.RCTModernEventEmitter @@ -22,7 +22,7 @@ internal class FabricEventEmitter(private val uiManager: FabricUIManager) : RCTM eventName: String, canCoalesceEvent: Boolean, customCoalesceKey: Int, - params: WritableMap?, + params: ReadableMap?, @EventCategoryDef category: Int, ) { receiveEvent( @@ -43,7 +43,7 @@ internal class FabricEventEmitter(private val uiManager: FabricUIManager) : RCTM eventName: String, canCoalesceEvent: Boolean, customCoalesceKey: Int, - params: WritableMap?, + params: ReadableMap?, @EventCategoryDef category: Int, eventTimestamp: Long, ) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt index 72b717d243da..87b3dee12e2f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt @@ -18,7 +18,6 @@ import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.RetryableMountingLayerException import com.facebook.react.bridge.UiThreadUtil.assertOnUiThread -import com.facebook.react.bridge.WritableMap import com.facebook.react.fabric.events.EventEmitterWrapper import com.facebook.react.fabric.mounting.mountitems.MountItem import com.facebook.react.touch.JSResponderHandler @@ -342,7 +341,7 @@ internal class MountingManager( reactTag: Int, eventName: String, canCoalesceEvent: Boolean, - params: WritableMap?, + params: ReadableMap?, @EventCategoryDef eventCategory: Int, eventTimestamp: Long, ) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt index 0c8c7bf05502..8053c2a7b0aa 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt @@ -1171,7 +1171,7 @@ internal constructor( reactTag: Int, eventName: String, canCoalesceEvent: Boolean, - params: WritableMap?, + params: ReadableMap?, @EventCategoryDef eventCategory: Int, eventTimestamp: Long, ) { @@ -1252,7 +1252,7 @@ internal constructor( private class PendingViewEvent( private val eventName: String, - private val params: WritableMap?, + private val params: ReadableMap?, @field:EventCategoryDef private val eventCategory: Int, private val canCoalesceEvent: Boolean, private val eventTimestamp: Long, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/interop/InteropEvent.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/interop/InteropEvent.kt index b4008e1fe064..249eb4ea6ee6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/interop/InteropEvent.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/interop/InteropEvent.kt @@ -7,7 +7,7 @@ package com.facebook.react.internal.interop -import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.ReadableMap import com.facebook.react.common.annotations.internal.InteropLegacyArchitecture import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger import com.facebook.react.uimanager.events.Event @@ -20,14 +20,14 @@ import com.facebook.react.uimanager.events.Event @InteropLegacyArchitecture internal class InteropEvent( @get:JvmName("eventName") val interopEventName: String, - @get:JvmName("eventData") val eventData: WritableMap?, + @get:JvmName("eventData") val eventData: ReadableMap?, surfaceId: Int, viewTag: Int, ) : Event(surfaceId, viewTag) { override fun getEventName(): String = interopEventName - override fun getEventData(): WritableMap? = eventData + override fun getEventData(): ReadableMap? = eventData private companion object { init { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/interop/InteropEventEmitter.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/interop/InteropEventEmitter.kt index a4f2aad529aa..a08bbfc19bce 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/interop/InteropEventEmitter.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/interop/InteropEventEmitter.kt @@ -10,7 +10,7 @@ package com.facebook.react.internal.interop import com.facebook.react.bridge.ReactContext -import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.ReadableMap import com.facebook.react.common.annotations.VisibleForTesting import com.facebook.react.common.annotations.internal.InteropLegacyArchitecture import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger @@ -34,7 +34,7 @@ internal class InteropEventEmitter(private val reactContext: ReactContext) : RCT private var eventDispatcherOverride: EventDispatcher? = null @Deprecated("Deprecated in Java") - override fun receiveEvent(targetTag: Int, eventName: String, params: WritableMap?) { + override fun receiveEvent(targetTag: Int, eventName: String, params: ReadableMap?) { val dispatcher = eventDispatcherOverride ?: UIManagerHelper.getEventDispatcher(reactContext) val surfaceId = UIManagerHelper.getSurfaceId(reactContext) dispatcher?.dispatchEvent(InteropEvent(eventName, params, surfaceId, targetTag)) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.kt index 245e1a0e29b2..a65277d07edd 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.kt @@ -551,11 +551,11 @@ public open class UIManagerModule : ReactContextBaseJavaModule, LifecycleEventLi return null } - override fun receiveEvent(reactTag: Int, eventName: String, event: WritableMap?) { + override fun receiveEvent(reactTag: Int, eventName: String, event: ReadableMap?) { receiveEvent(-1, reactTag, eventName, event) } - override fun receiveEvent(surfaceId: Int, reactTag: Int, eventName: String, event: WritableMap?) { + override fun receiveEvent(surfaceId: Int, reactTag: Int, eventName: String, event: ReadableMap?) { assert(false) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.kt index 0b351afa723a..24e1bd3eaea7 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.kt @@ -9,7 +9,7 @@ package com.facebook.react.uimanager.events -import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.ReadableMap import com.facebook.react.common.SystemClock.uptimeMillis /** @@ -152,13 +152,13 @@ public abstract class Event> { } /** Can be overridden by classes when no custom logic for dispatching is needed. */ - protected open fun getEventData(): WritableMap? = null + protected open fun getEventData(): ReadableMap? = null /** * NOTE: This is a transitional method that allows accessing event data from outside the * sublcasses, but inside the package. */ - internal fun internal_getEventData(): WritableMap? = getEventData() + internal fun internal_getEventData(): ReadableMap? = getEventData() protected open fun getEventCategory(): Int = EventCategoryDef.UNSPECIFIED diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.kt index edfd77430ed3..aa970ee2ccbe 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTEventEmitter.kt @@ -9,8 +9,8 @@ package com.facebook.react.uimanager.events import com.facebook.proguard.annotations.DoNotStripAny import com.facebook.react.bridge.JavaScriptModule +import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableArray -import com.facebook.react.bridge.WritableMap import com.facebook.react.common.annotations.internal.InteropLegacyArchitecture /** @@ -28,7 +28,7 @@ public interface RCTEventEmitter : JavaScriptModule { * @param params event params */ @Deprecated("Use [RCTModernEventEmitter.receiveEvent] instead") - public fun receiveEvent(targetTag: Int, eventName: String, params: WritableMap?) + public fun receiveEvent(targetTag: Int, eventName: String, params: ReadableMap?) @Deprecated("Use [EventDispatcher] to dispatch touch events instead") public fun receiveTouches( diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.kt index 2720516397b7..628b5454208a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/RCTModernEventEmitter.kt @@ -7,7 +7,7 @@ package com.facebook.react.uimanager.events -import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.ReadableMap /** * This is a transitional replacement for [RCTEventEmitter] that works with Fabric and non-Fabric @@ -17,6 +17,9 @@ import com.facebook.react.bridge.WritableMap * This interface will *also* be deleted in the distant future and be replaced with a new interface * that doesn't need the old `receiveEvent` method at all. But for the foreseeable future, this is * the recommended interface to use for EventEmitters. + * + * Fabric requires native-backed event payloads and consumes them during dispatch. Callers must not + * reuse a payload after passing it to [receiveEvent]. */ @Suppress("DEPRECATION") public interface RCTModernEventEmitter : RCTEventEmitter { @@ -24,12 +27,12 @@ public interface RCTModernEventEmitter : RCTEventEmitter { "Use RCTModernEventEmitter", ReplaceWith("RCTModernEventEmitter.receiveEvent(surfaceId, targetTag, eventName, params)"), ) - override fun receiveEvent(targetTag: Int, eventName: String, params: WritableMap?) { + override fun receiveEvent(targetTag: Int, eventName: String, params: ReadableMap?) { receiveEvent(-1, targetTag, eventName, params) } @Deprecated("Use the overload with eventTimestamp parameter instead.") - public fun receiveEvent(surfaceId: Int, targetTag: Int, eventName: String, params: WritableMap?) { + public fun receiveEvent(surfaceId: Int, targetTag: Int, eventName: String, params: ReadableMap?) { // We assume this event can't be coalesced. `customCoalesceKey` has no meaning in Fabric. receiveEvent(surfaceId, targetTag, eventName, false, 0, params, EventCategoryDef.UNSPECIFIED) } @@ -41,7 +44,7 @@ public interface RCTModernEventEmitter : RCTEventEmitter { eventName: String, canCoalesceEvent: Boolean, customCoalesceKey: Int, - params: WritableMap?, + params: ReadableMap?, @EventCategoryDef category: Int, ) @@ -58,7 +61,7 @@ public interface RCTModernEventEmitter : RCTEventEmitter { eventName: String, canCoalesceEvent: Boolean, customCoalesceKey: Int, - params: WritableMap?, + params: ReadableMap?, @EventCategoryDef category: Int, eventTimestamp: Long, ) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/SynchronousEventReceiver.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/SynchronousEventReceiver.kt index 7d6cb6c429bf..27d8dcf20e20 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/SynchronousEventReceiver.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/SynchronousEventReceiver.kt @@ -7,7 +7,7 @@ package com.facebook.react.uimanager.events -import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.ReadableMap import com.facebook.react.common.annotations.UnstableReactNativeAPI @UnstableReactNativeAPI @@ -17,7 +17,7 @@ internal interface SynchronousEventReceiver { reactTag: Int, eventName: String, canCoalesceEvent: Boolean, - params: WritableMap?, + params: ReadableMap?, @EventCategoryDef eventCategory: Int, experimentalIsSynchronous: Boolean, ) @@ -34,7 +34,7 @@ internal interface SynchronousEventReceiver { reactTag: Int, eventName: String, canCoalesceEvent: Boolean, - params: WritableMap?, + params: ReadableMap?, @EventCategoryDef eventCategory: Int, experimentalIsSynchronous: Boolean, eventTimestamp: Long, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.kt index 435ac67afbf6..dd952343b35c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.kt @@ -7,9 +7,9 @@ package com.facebook.react.uimanager.events -import com.facebook.react.bridge.Arguments -import com.facebook.react.bridge.WritableArray -import com.facebook.react.bridge.WritableMap +import com.facebook.proguard.annotations.DoNotStrip +import com.facebook.react.bridge.ReactNativeJNISoLoader +import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.PixelUtil.pxToDp import com.facebook.systrace.Systrace @@ -17,28 +17,31 @@ import com.facebook.systrace.Systrace * Class responsible for generating catalyst touch events based on android * [android.view.MotionEvent]. */ +@DoNotStrip internal object TouchesHelper { - @JvmField @Deprecated("Not used in New Architecture") val TARGET_KEY: String = "target" - - private const val TARGET_SURFACE_KEY = "targetSurface" - private const val CHANGED_TOUCHES_KEY = "changedTouches" - private const val TOUCHES_KEY = "touches" - private const val PAGE_X_KEY = "pageX" - private const val PAGE_Y_KEY = "pageY" - private const val TIMESTAMP_KEY = "timestamp" - private const val POINTER_IDENTIFIER_KEY = "identifier" + init { + ReactNativeJNISoLoader.staticInit() + } - private const val LOCATION_X_KEY = "locationX" - private const val LOCATION_Y_KEY = "locationY" + // Keep in sync with the field indices in TouchesHelper.cpp. + const val PAGE_X_INDEX = 0 + const val PAGE_Y_INDEX = 1 + const val LOCATION_X_INDEX = 2 + const val LOCATION_Y_INDEX = 3 + const val TARGET_SURFACE_INDEX = 4 + const val TARGET_INDEX = 5 + const val TIMESTAMP_INDEX = 6 + const val IDENTIFIER_INDEX = 7 + const val POINTER_FIELDS = 8 /** - * Creates catalyst pointers array in format that is expected by RCTEventEmitter JS module from - * given {@param event} instance. This method use {@param reactTarget} parameter to set as a - * target view id associated with current gesture. + * Fills the pointer data of [nativeCreateTouchPayload]: for each pointer of the + * [android.view.MotionEvent], in order, pageX, pageY, locationX, locationY, targetSurface, + * target, timestamp and identifier. */ - private fun createPointersArray(event: TouchEvent): Array { + private fun createPointerData(event: TouchEvent): DoubleArray { val motionEvent = event.getMotionEvent() - val touches = arrayOfNulls(motionEvent.pointerCount) + val pointerData = DoubleArray(motionEvent.pointerCount * POINTER_FIELDS) // Calculate the coordinates for the target view. // The MotionEvent contains the X,Y of the touch in the coordinate space of the root view @@ -51,29 +54,27 @@ internal object TouchesHelper { val targetViewCoordinateY = motionEvent.y - event.viewY for (index in 0 until motionEvent.pointerCount) { - val touch = Arguments.createMap() + val field = index * POINTER_FIELDS // pageX,Y values are relative to the RootReactView // the motionEvent already contains coordinates in that view - touch.putDouble(PAGE_X_KEY, motionEvent.getX(index).pxToDp().toDouble()) - touch.putDouble(PAGE_Y_KEY, motionEvent.getY(index).pxToDp().toDouble()) + pointerData[field + PAGE_X_INDEX] = motionEvent.getX(index).pxToDp().toDouble() + pointerData[field + PAGE_Y_INDEX] = motionEvent.getY(index).pxToDp().toDouble() // locationX,Y values are relative to the target view // To compute the values for the view, we subtract that views location from the event X,Y val locationX = motionEvent.getX(index) - targetViewCoordinateX val locationY = motionEvent.getY(index) - targetViewCoordinateY - touch.putDouble(LOCATION_X_KEY, locationX.pxToDp().toDouble()) - touch.putDouble(LOCATION_Y_KEY, locationY.pxToDp().toDouble()) + pointerData[field + LOCATION_X_INDEX] = locationX.pxToDp().toDouble() + pointerData[field + LOCATION_Y_INDEX] = locationY.pxToDp().toDouble() - touch.putInt(TARGET_SURFACE_KEY, event.surfaceId) - @Suppress("DEPRECATION") touch.putInt(TARGET_KEY, event.viewTag) - touch.putDouble(TIMESTAMP_KEY, event.timestampMs.toDouble()) - touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index).toDouble()) - - touches[index] = touch + pointerData[field + TARGET_SURFACE_INDEX] = event.surfaceId.toDouble() + pointerData[field + TARGET_INDEX] = event.viewTag.toDouble() + pointerData[field + TIMESTAMP_INDEX] = event.timestampMs.toDouble() + pointerData[field + IDENTIFIER_INDEX] = motionEvent.getPointerId(index).toDouble() } - return touches + return pointerData } /** @@ -82,8 +83,9 @@ internal object TouchesHelper { * more efficient manner. * * Touches have to be dispatched as separate events for each changed pointer to make JS process - * them correctly. To avoid allocations, we preprocess touch events in Java world and then convert - * them to native before dispatch. + * them correctly. The pointers are collected once into a [DoubleArray], and each event's payload + * is built from it in a single JNI call, instead of putting every value into a native map one by + * one. * * @param eventEmitter emitter to dispatch event to * @param event the touch event to extract data from @@ -95,52 +97,44 @@ internal object TouchesHelper { "TouchesHelper.sentTouchEventModern(" + event.getEventName() + ")", ) try { - val type = event.getTouchEventType() val motionEvent = event.getMotionEvent() - var touches = createPointersArray(event) - val changedTouches: Array? - - when (type) { + val pointerCount = motionEvent.pointerCount + // Pointers are passed to native as bit masks of their indices. + check(pointerCount < Int.SIZE_BITS) { "Too many pointers: $pointerCount" } + val allPointers = (1 shl pointerCount) - 1 + val changedPointers: Int + val touchPointers: Int + + when (event.getTouchEventType()) { TouchEventType.START -> { - val newPointerIndex = motionEvent.actionIndex - - changedTouches = arrayOf(touches[newPointerIndex]?.copy()) + changedPointers = 1 shl motionEvent.actionIndex + touchPointers = allPointers } TouchEventType.END -> { - val finishedPointerIndex = motionEvent.actionIndex + changedPointers = 1 shl motionEvent.actionIndex /* - * Clear finished pointer index for compatibility with W3C touch "end" events, where the + * Leave out the finished pointer for compatibility with W3C touch "end" events, where the * active touches don't include the set that has just been "ended". */ - val finishedPointer = touches[finishedPointerIndex] - touches[finishedPointerIndex] = null - - changedTouches = arrayOf(finishedPointer) + touchPointers = allPointers and changedPointers.inv() } TouchEventType.MOVE -> { - changedTouches = arrayOfNulls(touches.size) - var i = 0 - while (i < touches.size) { - changedTouches[i] = touches[i]?.copy() - i++ - } + changedPointers = allPointers + touchPointers = allPointers } TouchEventType.CANCEL -> { - changedTouches = touches - touches = arrayOfNulls(0) + changedPointers = allPointers + touchPointers = 0 } } - for (touchData in changedTouches) { + val pointerData = createPointerData(event) + for (pointer in 0 until pointerCount) { + if (changedPointers and (1 shl pointer) == 0) { + continue + } val eventData = - touchData?.let { td -> - val ed = td.copy() - val changedTouchesArray = getWritableArray(/* copyObjects */ true, changedTouches) - val touchesArray = getWritableArray(/* copyObjects */ true, touches) - ed.putArray(CHANGED_TOUCHES_KEY, changedTouchesArray) - ed.putArray(TOUCHES_KEY, touchesArray) - ed - } + nativeCreateTouchPayload(pointerData, pointer, changedPointers, touchPointers) eventEmitter.receiveEvent( event.surfaceId, @@ -158,13 +152,20 @@ internal object TouchesHelper { } } - private fun getWritableArray(copyObjects: Boolean, objects: Array): WritableArray { - val result = Arguments.createArray() - for (obj in objects) { - if (obj != null) { - result.pushMap(if (copyObjects) obj.copy() else obj) - } - } - return result - } + /** + * Creates the payload of a touch event: the fields of pointer [eventPointer] and the + * `changedTouches` and `touches` arrays of the pointers whose bits are set in [changedPointers] + * and [touchPointers]. + * + * @param pointerData 8 values for each pointer, in order: pageX, pageY, locationX, locationY, + * targetSurface, target, timestamp and identifier. + */ + @DoNotStrip + @JvmStatic + private external fun nativeCreateTouchPayload( + pointerData: DoubleArray, + eventPointer: Int, + changedPointers: Int, + touchPointers: Int, + ): ReadableMap } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt index 5229c2af141a..1b58d0879f3d 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt @@ -32,6 +32,7 @@ add_library( OnLoad-common.cpp ReadableNativeArray.cpp ReadableNativeMap.cpp + TouchesHelper.cpp TransformHelper.cpp WritableNativeArray.cpp WritableNativeMap.cpp diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 04ef88c09e98..ba536d2cce35 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -11,6 +11,7 @@ #include #include "InspectorNetworkRequestListener.h" +#include "TouchesHelper.h" #include "TransformHelper.h" #ifndef WITH_GLOGINIT @@ -34,6 +35,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { FLAGS_minloglevel = 0; #endif InspectorNetworkRequestListener::registerNatives(); + TouchesHelper::registerNatives(); TransformHelper::registerNatives(); }); } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/TouchesHelper.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TouchesHelper.cpp new file mode 100644 index 000000000000..ab23fd8f3d76 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TouchesHelper.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "TouchesHelper.h" + +#include +#include + +#include "ReadableNativeMap.h" + +using namespace facebook::jni; + +namespace facebook::react { + +namespace { + +// Keep in sync with the field indices in TouchesHelper.kt. +constexpr jint kPageXIndex = 0; +constexpr jint kPageYIndex = 1; +constexpr jint kLocationXIndex = 2; +constexpr jint kLocationYIndex = 3; +constexpr jint kTargetSurfaceIndex = 4; +constexpr jint kTargetIndex = 5; +constexpr jint kTimestampIndex = 6; +constexpr jint kIdentifierIndex = 7; +constexpr jint kPointerFields = 8; + +folly::dynamic createTouch(const double* fields) { + folly::dynamic touch = folly::dynamic::object(); + // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + touch.insert("pageX", fields[kPageXIndex]); + touch.insert("pageY", fields[kPageYIndex]); + touch.insert("locationX", fields[kLocationXIndex]); + touch.insert("locationY", fields[kLocationYIndex]); + touch.insert("targetSurface", static_cast(fields[kTargetSurfaceIndex])); + touch.insert("target", static_cast(fields[kTargetIndex])); + touch.insert("timestamp", fields[kTimestampIndex]); + touch.insert("identifier", fields[kIdentifierIndex]); + // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return touch; +} + +local_ref createTouchPayload( + alias_ref /*unused*/, + alias_ref pointerData, + jint eventPointer, + jint changedPointers, + jint touchPointers) { + auto length = static_cast(pointerData->size()); + jint pointerCount = length / kPointerFields; + // Pointers are selected by bits of the jint masks. + if (length % kPointerFields != 0 || pointerCount >= 32 || eventPointer < 0 || + eventPointer >= pointerCount) { + throw std::invalid_argument("Invalid touch pointer data"); + } + auto values = pointerData->getRegion(0, length); + + folly::dynamic changedTouches = folly::dynamic::array(); + folly::dynamic touches = folly::dynamic::array(); + for (jint index = 0; index < pointerCount; index++) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + const double* fields = values.get() + index * kPointerFields; + if ((changedPointers & (1 << index)) != 0) { + changedTouches.push_back(createTouch(fields)); + } + if ((touchPointers & (1 << index)) != 0) { + touches.push_back(createTouch(fields)); + } + } + + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + auto payload = createTouch(values.get() + eventPointer * kPointerFields); + payload.insert("changedTouches", std::move(changedTouches)); + payload.insert("touches", std::move(touches)); + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) + return adopt_local( + reinterpret_cast( + ReadableNativeMap::createWithContents(std::move(payload)).release())); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) +} + +} // namespace + +void TouchesHelper::registerNatives() { + javaClassLocal()->registerNatives({ + makeNativeMethod("nativeCreateTouchPayload", createTouchPayload), + }); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/TouchesHelper.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TouchesHelper.h new file mode 100644 index 000000000000..5037ca0edb56 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TouchesHelper.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +namespace facebook::react { + +class TouchesHelper : public jni::JavaClass { + public: + static auto constexpr *kJavaDescriptor = "Lcom/facebook/react/uimanager/events/TouchesHelper;"; + + static void registerNatives(); +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/interop/InteropModuleRegistryTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/interop/InteropModuleRegistryTest.kt index 5e51cda15d0d..ab759f59b311 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/interop/InteropModuleRegistryTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/interop/InteropModuleRegistryTest.kt @@ -11,7 +11,7 @@ package com.facebook.react.bridge.interop import com.facebook.react.bridge.JavaScriptModule -import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.ReadableMap import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults @@ -32,7 +32,7 @@ class InteropModuleRegistryTest { @Suppress("DEPRECATION") private class FakeRCTEventEmitter : RCTEventEmitter { @Deprecated("Use [RCTModernEventEmitter.receiveEvent] instead") - override fun receiveEvent(targetTag: Int, eventName: String, params: WritableMap?) = Unit + override fun receiveEvent(targetTag: Int, eventName: String, params: ReadableMap?) = Unit } @Before diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt index 00fc7e3489db..20a9e9d9b332 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt @@ -15,6 +15,7 @@ import com.facebook.react.bridge.JavaOnlyArray import com.facebook.react.bridge.JavaOnlyMap import com.facebook.react.bridge.ReactTestHelper import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableNativeMap import com.facebook.react.bridge.WritableMap import com.facebook.react.bridge.queue.MessageQueueThread import com.facebook.react.bridge.queue.ReactQueueConfiguration @@ -27,7 +28,11 @@ import com.facebook.react.uimanager.events.TouchEvent import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper import com.facebook.react.uimanager.events.TouchEventType import com.facebook.testutils.shadows.ShadowArguments +import com.facebook.testutils.shadows.ShadowNativeLoader +import com.facebook.testutils.shadows.ShadowNativeMap +import com.facebook.testutils.shadows.ShadowReadableNativeMap import com.facebook.testutils.shadows.ShadowSoLoader +import com.facebook.testutils.shadows.ShadowTouchesHelper import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -41,9 +46,19 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config +import org.robolectric.shadow.api.Shadow @RunWith(RobolectricTestRunner::class) -@Config(shadows = [ShadowSoLoader::class, ShadowArguments::class]) +@Config( + shadows = + [ + ShadowSoLoader::class, + ShadowNativeLoader::class, + ShadowArguments::class, + ShadowReadableNativeMap::class, + ShadowTouchesHelper::class, + ], +) class TouchEventDispatchTest { private val touchEventCoalescingKeyHelper = TouchEventCoalescingKeyHelper() @@ -546,7 +561,7 @@ class TouchEventDispatchTest { for (event in startMoveEndSequence) { eventDispatcher.dispatchEvent(event) } - val argument = ArgumentCaptor.forClass(WritableMap::class.java) + val argument = ArgumentCaptor.forClass(ReadableMap::class.java) verify(eventEmitter, times(4)) .receiveEvent( anyInt(), @@ -558,7 +573,7 @@ class TouchEventDispatchTest { anyInt(), anyLong(), ) - assertThat(startMoveEndExpectedSequence).isEqualTo(argument.allValues) + assertThat(startMoveEndExpectedSequence).isEqualTo(argument.payloads()) } @Test @@ -566,7 +581,7 @@ class TouchEventDispatchTest { for (event in startMoveCancelSequence) { eventDispatcher.dispatchEvent(event) } - val argument = ArgumentCaptor.forClass(WritableMap::class.java) + val argument = ArgumentCaptor.forClass(ReadableMap::class.java) verify(eventEmitter, times(6)) .receiveEvent( anyInt(), @@ -578,7 +593,7 @@ class TouchEventDispatchTest { anyInt(), anyLong(), ) - assertThat(startMoveCancelExpectedSequence).isEqualTo(argument.allValues) + assertThat(startMoveCancelExpectedSequence).isEqualTo(argument.payloads()) } @Test @@ -586,7 +601,7 @@ class TouchEventDispatchTest { for (event in startPointerMoveUpSequence) { eventDispatcher.dispatchEvent(event) } - val argument = ArgumentCaptor.forClass(WritableMap::class.java) + val argument = ArgumentCaptor.forClass(ReadableMap::class.java) verify(eventEmitter, times(6)) .receiveEvent( anyInt(), @@ -598,7 +613,7 @@ class TouchEventDispatchTest { anyInt(), anyLong(), ) - assertThat(startPointerMoveUpExpectedSequence).isEqualTo(argument.allValues) + assertThat(startPointerMoveUpExpectedSequence).isEqualTo(argument.payloads()) } private fun createTouchEvent( @@ -696,5 +711,13 @@ class TouchEventDispatchTest { } } +/** The contents of the captured payloads, which are shadowed native maps. */ +private fun ArgumentCaptor.payloads(): List = + allValues.map { payload -> + assertThat(payload).isInstanceOf(ReadableNativeMap::class.java) + assertThat(payload).isNotInstanceOf(WritableMap::class.java) + Shadow.extract(payload).backingMap + } + private fun IntArray.toPointerProperties(): Array = this.map { PointerProperties().apply { id = it } }.toTypedArray() diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/fakes/FakeUIManager.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/fakes/FakeUIManager.kt index e1e708edcd1b..0631cc864f07 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/fakes/FakeUIManager.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/fakes/FakeUIManager.kt @@ -93,11 +93,11 @@ class FakeUIManager : UIManager, UIBlockViewResolver { } @Deprecated("") - override fun receiveEvent(reactTag: Int, eventName: String, event: WritableMap?) { + override fun receiveEvent(reactTag: Int, eventName: String, event: ReadableMap?) { error("Not yet implemented") } - override fun receiveEvent(surfaceId: Int, reactTag: Int, eventName: String, event: WritableMap?) { + override fun receiveEvent(surfaceId: Int, reactTag: Int, eventName: String, event: ReadableMap?) { error("Not yet implemented") } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows/ShadowTouchesHelper.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows/ShadowTouchesHelper.kt new file mode 100644 index 000000000000..f35f09e0b494 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows/ShadowTouchesHelper.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.testutils.shadows + +import com.facebook.react.bridge.JavaOnlyArray +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableNativeMap +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.TouchesHelper +import org.robolectric.annotation.Implementation +import org.robolectric.annotation.Implements +import org.robolectric.shadow.api.Shadow + +@Implements(TouchesHelper::class) +class ShadowTouchesHelper { + + companion object { + + /** Builds the same payload as the native TouchesHelper::createTouchPayload. */ + @JvmStatic + @Implementation + fun nativeCreateTouchPayload( + pointerData: DoubleArray, + eventPointer: Int, + changedPointers: Int, + touchPointers: Int, + ): ReadableMap { + val pointerCount = pointerData.size / TouchesHelper.POINTER_FIELDS + val changedTouches = JavaOnlyArray() + val touches = JavaOnlyArray() + for (index in 0 until pointerCount) { + if (changedPointers and (1 shl index) != 0) { + changedTouches.pushMap(JavaOnlyMap().also { putTouch(it, pointerData, index) }) + } + if (touchPointers and (1 shl index) != 0) { + touches.pushMap(JavaOnlyMap().also { putTouch(it, pointerData, index) }) + } + } + val payload = JavaOnlyMap() + putTouch(payload, pointerData, eventPointer) + payload.putArray("changedTouches", changedTouches) + payload.putArray("touches", touches) + val nativeMap = Shadow.newInstanceOf(ReadableNativeMap::class.java) + Shadow.extract(nativeMap).backingMap = payload + return nativeMap + } + + private fun putTouch(touch: WritableMap, pointerData: DoubleArray, index: Int) { + val field = index * TouchesHelper.POINTER_FIELDS + touch.putDouble("pageX", pointerData[field + TouchesHelper.PAGE_X_INDEX]) + touch.putDouble("pageY", pointerData[field + TouchesHelper.PAGE_Y_INDEX]) + touch.putDouble("locationX", pointerData[field + TouchesHelper.LOCATION_X_INDEX]) + touch.putDouble("locationY", pointerData[field + TouchesHelper.LOCATION_Y_INDEX]) + touch.putInt("targetSurface", pointerData[field + TouchesHelper.TARGET_SURFACE_INDEX].toInt()) + touch.putInt("target", pointerData[field + TouchesHelper.TARGET_INDEX].toInt()) + touch.putDouble("timestamp", pointerData[field + TouchesHelper.TIMESTAMP_INDEX]) + touch.putDouble("identifier", pointerData[field + TouchesHelper.IDENTIFIER_INDEX]) + } + } +} diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index ca72d3985fc9..97a6a1f652cc 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -2426,6 +2426,11 @@ class facebook::react::TouchEventEmitter : public facebook::react::EventEmitter public void onTouchStart(facebook::react::TouchEvent event) const; } +class facebook::react::TouchesHelper : public facebook::jni::JavaClass { + public static constexpr auto * kJavaDescriptor; + public static void registerNatives(); +} + class facebook::react::TracingDelegate : public facebook::react::jsinspector_modern::HostTargetTracingDelegate { public TracingState getTracingState(); public size_t registerTracingStateListener(facebook::react::TracingStateListener listener); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api index 5e5dad5f5ce5..4de5fdc97f51 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api @@ -4079,6 +4079,11 @@ class facebook::react::TouchEventEmitter : public facebook::react::EventEmitter public void onTouchStart(facebook::react::TouchEvent event) const; } +class facebook::react::TouchesHelper : public facebook::jni::JavaClass { + public static constexpr auto * kJavaDescriptor; + public static void registerNatives(); +} + class facebook::react::TracingDelegate : public facebook::react::jsinspector_modern::HostTargetTracingDelegate { public TracingState getTracingState(); public size_t registerTracingStateListener(facebook::react::TracingStateListener listener); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 1a105ab8080c..05dc03891252 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -2419,6 +2419,11 @@ class facebook::react::TouchEventEmitter : public facebook::react::EventEmitter public void onTouchStart(facebook::react::TouchEvent event) const; } +class facebook::react::TouchesHelper : public facebook::jni::JavaClass { + public static constexpr auto * kJavaDescriptor; + public static void registerNatives(); +} + class facebook::react::TracingDelegate : public facebook::react::jsinspector_modern::HostTargetTracingDelegate { public TracingState getTracingState(); public size_t registerTracingStateListener(facebook::react::TracingStateListener listener); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 1a105ab8080c..05dc03891252 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -2419,6 +2419,11 @@ class facebook::react::TouchEventEmitter : public facebook::react::EventEmitter public void onTouchStart(facebook::react::TouchEvent event) const; } +class facebook::react::TouchesHelper : public facebook::jni::JavaClass { + public static constexpr auto * kJavaDescriptor; + public static void registerNatives(); +} + class facebook::react::TracingDelegate : public facebook::react::jsinspector_modern::HostTargetTracingDelegate { public TracingState getTracingState(); public size_t registerTracingStateListener(facebook::react::TracingStateListener listener);