From a2dec1619c65900dccde8ed27a8e634745cec62f Mon Sep 17 00:00:00 2001 From: Gregory Moskaliuk Date: Tue, 27 Oct 2026 19:58:18 +0100 Subject: [PATCH 1/4] Build Android touch event payloads in one JNI call TouchesHelper built every touch event payload as nested WritableNativeMaps: for each pointer, eight JNI put calls, then several copies of the same map for the event, changedTouches and touches. Collect the pointer values into a DoubleArray instead and let WritableNativeMap.putTouchPayload build the whole payload in native code in a single JNI call. TouchesHelper still decides which pointers are in changedTouches and touches, as bit masks of pointer indices; the keys, values and value types of the payload are the same as before. Co-Authored-By: Claude Opus 5.5 --- .../react/bridge/WritableNativeMap.kt | 16 +++ .../react/uimanager/events/TouchesHelper.kt | 119 +++++++----------- .../main/jni/react/jni/WritableNativeMap.cpp | 48 +++++++ .../main/jni/react/jni/WritableNativeMap.h | 5 + .../fabric/events/TouchEventDispatchTest.kt | 24 +++- .../shadows/ShadowWritableNativeMap.kt | 46 +++++++ 6 files changed, 180 insertions(+), 78 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.kt index bc30404a4c78..d409094f3f15 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.kt @@ -64,5 +64,21 @@ public class WritableNativeMap : ReadableNativeMap(), WritableMap { private external fun mergeNativeMap(source: ReadableNativeMap) + /** + * Replaces the contents of this map with the payload of a touch event, in one JNI call. The + * payload has 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. + */ + @JvmName("putTouchPayload") + internal external fun putTouchPayload( + pointerData: DoubleArray, + eventPointer: Int, + changedPointers: Int, + touchPointers: Int, + ) + private external fun initHybrid() } 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..5d94c36adc75 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,7 @@ 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.react.bridge.WritableNativeMap import com.facebook.react.uimanager.PixelUtil.pxToDp import com.facebook.systrace.Systrace @@ -20,25 +18,17 @@ import com.facebook.systrace.Systrace 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" - - private const val LOCATION_X_KEY = "locationX" - private const val LOCATION_Y_KEY = "locationY" + // Values per pointer in the data passed to WritableNativeMap.putTouchPayload. + private 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 [WritableNativeMap.putTouchPayload]: 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 +41,27 @@ internal object TouchesHelper { val targetViewCoordinateY = motionEvent.y - event.viewY for (index in 0 until motionEvent.pointerCount) { - val touch = Arguments.createMap() + var 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++] = motionEvent.getX(index).pxToDp().toDouble() + pointerData[field++] = 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()) - - 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()) + pointerData[field++] = locationX.pxToDp().toDouble() + pointerData[field++] = locationY.pxToDp().toDouble() - touches[index] = touch + pointerData[field++] = event.surfaceId.toDouble() + pointerData[field++] = event.viewTag.toDouble() + pointerData[field++] = event.timestampMs.toDouble() + pointerData[field] = motionEvent.getPointerId(index).toDouble() } - return touches + return pointerData } /** @@ -82,8 +70,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 +84,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 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 - } + val pointerData = createPointerData(event) + for (pointer in 0 until pointerCount) { + if (changedPointers and (1 shl pointer) == 0) { + continue + } + val eventData = WritableNativeMap() + eventData.putTouchPayload(pointerData, pointer, changedPointers, touchPointers) eventEmitter.receiveEvent( event.surfaceId, @@ -157,14 +138,4 @@ internal object TouchesHelper { Systrace.endSection(Systrace.TRACE_TAG_REACT) } } - - 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 - } } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.cpp index 086bab13a34b..0a35eb5f080b 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.cpp @@ -7,6 +7,8 @@ #include "WritableNativeMap.h" +#include + using namespace facebook::jni; namespace facebook::react { @@ -89,8 +91,54 @@ void WritableNativeMap::mergeNativeMap(ReadableNativeMap* other) { } } +void WritableNativeMap::putTouchPayload( + alias_ref pointerData, + jint eventPointer, + jint changedPointers, + jint touchPointers) { + throwIfConsumed(); + // Must match TouchesHelper.POINTER_FIELDS and the order documented on + // WritableNativeMap.putTouchPayload. + constexpr jint kPointerFields = 8; + auto length = static_cast(pointerData->size()); + jint pointerCount = length / kPointerFields; + if (length % kPointerFields != 0 || eventPointer < 0 || + eventPointer >= pointerCount) { + throw std::invalid_argument("Invalid touch pointer data"); + } + auto values = pointerData->getRegion(0, length); + auto pointer = [&](jint index) { + const double* fields = values.get() + index * kPointerFields; + folly::dynamic touch = folly::dynamic::object(); + touch.insert("pageX", fields[0]); + touch.insert("pageY", fields[1]); + touch.insert("locationX", fields[2]); + touch.insert("locationY", fields[3]); + touch.insert("targetSurface", static_cast(fields[4])); + touch.insert("target", static_cast(fields[5])); + touch.insert("timestamp", fields[6]); + touch.insert("identifier", fields[7]); + return touch; + }; + + folly::dynamic changedTouches = folly::dynamic::array(); + folly::dynamic touches = folly::dynamic::array(); + for (jint index = 0; index < pointerCount; index++) { + if ((changedPointers & (1 << index)) != 0) { + changedTouches.push_back(pointer(index)); + } + if ((touchPointers & (1 << index)) != 0) { + touches.push_back(pointer(index)); + } + } + map_ = pointer(eventPointer); + map_.insert("changedTouches", std::move(changedTouches)); + map_.insert("touches", std::move(touches)); +} + void WritableNativeMap::registerNatives() { registerHybrid({ + makeNativeMethod("putTouchPayload", WritableNativeMap::putTouchPayload), makeNativeMethod("putNull", WritableNativeMap::putNull), makeNativeMethod("putBoolean", WritableNativeMap::putBoolean), makeNativeMethod("putDouble", WritableNativeMap::putDouble), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.h index d11799824146..819aa9199532 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.h @@ -37,6 +37,11 @@ struct WritableNativeMap : jni::HybridClass pointerData, + jint eventPointer, + jint changedPointers, + jint touchPointers); static void registerNatives(); 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..6353a4d7d1c3 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 @@ -27,7 +27,10 @@ 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.ShadowSoLoader +import com.facebook.testutils.shadows.ShadowWritableNativeMap import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -41,9 +44,18 @@ 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, + ShadowWritableNativeMap::class, + ] +) class TouchEventDispatchTest { private val touchEventCoalescingKeyHelper = TouchEventCoalescingKeyHelper() @@ -558,7 +570,7 @@ class TouchEventDispatchTest { anyInt(), anyLong(), ) - assertThat(startMoveEndExpectedSequence).isEqualTo(argument.allValues) + assertThat(startMoveEndExpectedSequence).isEqualTo(argument.payloads()) } @Test @@ -578,7 +590,7 @@ class TouchEventDispatchTest { anyInt(), anyLong(), ) - assertThat(startMoveCancelExpectedSequence).isEqualTo(argument.allValues) + assertThat(startMoveCancelExpectedSequence).isEqualTo(argument.payloads()) } @Test @@ -598,7 +610,7 @@ class TouchEventDispatchTest { anyInt(), anyLong(), ) - assertThat(startPointerMoveUpExpectedSequence).isEqualTo(argument.allValues) + assertThat(startPointerMoveUpExpectedSequence).isEqualTo(argument.payloads()) } private fun createTouchEvent( @@ -696,5 +708,9 @@ class TouchEventDispatchTest { } } +/** The contents of the captured payloads, which are shadowed native maps. */ +private fun ArgumentCaptor.payloads(): List = + allValues.map { Shadow.extract(it).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/shadows/ShadowWritableNativeMap.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows/ShadowWritableNativeMap.kt index 22eb99076b4e..7624d89c6a04 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows/ShadowWritableNativeMap.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows/ShadowWritableNativeMap.kt @@ -7,6 +7,8 @@ package com.facebook.testutils.shadows +import com.facebook.react.bridge.JavaOnlyArray +import com.facebook.react.bridge.JavaOnlyMap import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableMap @@ -42,4 +44,48 @@ class ShadowWritableNativeMap : ShadowReadableNativeMap(), WritableMap { @Implementation override fun merge(source: ReadableMap): Unit = backingMap.merge(source) @Implementation override fun copy(): WritableMap = backingMap.copy() + + /** Builds the same payload as the native WritableNativeMap::putTouchPayload. */ + @Implementation + fun putTouchPayload( + pointerData: DoubleArray, + eventPointer: Int, + changedPointers: Int, + touchPointers: Int, + ) { + val pointerCount = pointerData.size / TOUCH_POINTER_FIELDS + val changedTouches = JavaOnlyArray() + val touches = JavaOnlyArray() + for (index in 0 until pointerCount) { + if (changedPointers and (1 shl index) != 0) { + changedTouches.pushMap(touch(pointerData, index)) + } + if (touchPointers and (1 shl index) != 0) { + touches.pushMap(touch(pointerData, index)) + } + } + backingMap = + touch(pointerData, eventPointer).apply { + putArray("changedTouches", changedTouches) + putArray("touches", touches) + } + } + + private fun touch(pointerData: DoubleArray, index: Int): JavaOnlyMap { + val field = index * TOUCH_POINTER_FIELDS + return JavaOnlyMap().apply { + putDouble("pageX", pointerData[field]) + putDouble("pageY", pointerData[field + 1]) + putDouble("locationX", pointerData[field + 2]) + putDouble("locationY", pointerData[field + 3]) + putInt("targetSurface", pointerData[field + 4].toInt()) + putInt("target", pointerData[field + 5].toInt()) + putDouble("timestamp", pointerData[field + 6]) + putDouble("identifier", pointerData[field + 7]) + } + } + + private companion object { + private const val TOUCH_POINTER_FIELDS = 8 + } } From ec650b055546bd399504f165fc2ae725910469b9 Mon Sep 17 00:00:00 2001 From: Gregory Moskaliuk Date: Tue, 27 Oct 2026 21:20:17 +0100 Subject: [PATCH 2/4] Update C++ API snapshots for WritableNativeMap::putTouchPayload Co-Authored-By: Claude Opus 5.5 --- scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api | 1 + 4 files changed, 4 insertions(+) diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index ca72d3985fc9..5d0e667d5ab4 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -3762,6 +3762,7 @@ struct facebook::react::WritableNativeMap : public jni::HybridClass val); + public void putTouchPayload(jni::alias_ref pointerData, jint eventPointer, jint changedPointers, jint touchPointers); } template diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api index 2c501ad938e5..979180565124 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api @@ -6805,6 +6805,7 @@ struct facebook::react::WritableNativeMap : public jni::HybridClass val); + public void putTouchPayload(jni::alias_ref pointerData, jint eventPointer, jint changedPointers, jint touchPointers); } template diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 1a105ab8080c..b05061a5d53e 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -3755,6 +3755,7 @@ struct facebook::react::WritableNativeMap : public jni::HybridClass val); + public void putTouchPayload(jni::alias_ref pointerData, jint eventPointer, jint changedPointers, jint touchPointers); } template diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 1a105ab8080c..b05061a5d53e 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -3755,6 +3755,7 @@ struct facebook::react::WritableNativeMap : public jni::HybridClass val); + public void putTouchPayload(jni::alias_ref pointerData, jint eventPointer, jint changedPointers, jint touchPointers); } template From d49c8fde39cfe74b465517437f54646912ec4a0f Mon Sep 17 00:00:00 2001 From: Gregory Moskaliuk Date: Wed, 28 Oct 2026 11:19:48 +0100 Subject: [PATCH 3/4] Build touch payloads in a TouchesHelper native method WritableNativeMap is a generic data structure, so it no longer has a touch-specific putTouchPayload. TouchesHelper gets a static native method instead, registered like TransformHelper, which fills an empty WritableNativeMap through its existing C++ API. Co-Authored-By: Claude Opus 5.5 --- .../react/bridge/WritableNativeMap.kt | 16 ---- .../react/uimanager/events/TouchesHelper.kt | 31 ++++++- .../src/main/jni/react/jni/CMakeLists.txt | 1 + .../src/main/jni/react/jni/OnLoad-common.cpp | 2 + .../src/main/jni/react/jni/TouchesHelper.cpp | 89 +++++++++++++++++++ .../src/main/jni/react/jni/TouchesHelper.h | 21 +++++ .../main/jni/react/jni/WritableNativeMap.cpp | 48 ---------- .../main/jni/react/jni/WritableNativeMap.h | 5 -- .../fabric/events/TouchEventDispatchTest.kt | 2 + .../testutils/shadows/ShadowTouchesHelper.kt | 62 +++++++++++++ .../shadows/ShadowWritableNativeMap.kt | 46 ---------- .../api-snapshots/ReactAndroidDebugCxx.api | 6 +- .../ReactAndroidFrameworksCxx.api | 6 +- .../api-snapshots/ReactAndroidNewarchCxx.api | 6 +- .../api-snapshots/ReactAndroidReleaseCxx.api | 6 +- 15 files changed, 225 insertions(+), 122 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/jni/TouchesHelper.cpp create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/jni/TouchesHelper.h create mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows/ShadowTouchesHelper.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.kt index d409094f3f15..bc30404a4c78 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.kt @@ -64,21 +64,5 @@ public class WritableNativeMap : ReadableNativeMap(), WritableMap { private external fun mergeNativeMap(source: ReadableNativeMap) - /** - * Replaces the contents of this map with the payload of a touch event, in one JNI call. The - * payload has 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. - */ - @JvmName("putTouchPayload") - internal external fun putTouchPayload( - pointerData: DoubleArray, - eventPointer: Int, - changedPointers: Int, - touchPointers: Int, - ) - private external fun initHybrid() } 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 5d94c36adc75..252f96e9832f 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,6 +7,8 @@ package com.facebook.react.uimanager.events +import com.facebook.proguard.annotations.DoNotStrip +import com.facebook.react.bridge.ReactNativeJniCommonSoLoader import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.uimanager.PixelUtil.pxToDp import com.facebook.systrace.Systrace @@ -15,14 +17,19 @@ import com.facebook.systrace.Systrace * Class responsible for generating catalyst touch events based on android * [android.view.MotionEvent]. */ +@DoNotStrip internal object TouchesHelper { + init { + ReactNativeJniCommonSoLoader.staticInit() + } + @JvmField @Deprecated("Not used in New Architecture") val TARGET_KEY: String = "target" - // Values per pointer in the data passed to WritableNativeMap.putTouchPayload. + // Values per pointer in the data passed to nativePutTouchPayload. private const val POINTER_FIELDS = 8 /** - * Fills the pointer data of [WritableNativeMap.putTouchPayload]: for each pointer of the + * Fills the pointer data of [nativePutTouchPayload]: for each pointer of the * [android.view.MotionEvent], in order, pageX, pageY, locationX, locationY, targetSurface, * target, timestamp and identifier. */ @@ -121,7 +128,7 @@ internal object TouchesHelper { continue } val eventData = WritableNativeMap() - eventData.putTouchPayload(pointerData, pointer, changedPointers, touchPointers) + nativePutTouchPayload(eventData, pointerData, pointer, changedPointers, touchPointers) eventEmitter.receiveEvent( event.surfaceId, @@ -138,4 +145,22 @@ internal object TouchesHelper { Systrace.endSection(Systrace.TRACE_TAG_REACT) } } + + /** + * Puts the payload of a touch event into the empty map [payload]: 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 nativePutTouchPayload( + payload: WritableNativeMap, + pointerData: DoubleArray, + eventPointer: Int, + changedPointers: Int, + touchPointers: Int, + ) } 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-common.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp index 8c6787bdde65..59f6013de87f 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp @@ -12,6 +12,7 @@ #include "JReactMarker.h" #include "NativeArray.h" #include "NativeMap.h" +#include "TouchesHelper.h" #include "WritableNativeArray.h" #include "WritableNativeMap.h" @@ -27,6 +28,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { NativeMap::registerNatives(); ReadableNativeArray::registerNatives(); ReadableNativeMap::registerNatives(); + TouchesHelper::registerNatives(); WritableNativeArray::registerNatives(); WritableNativeMap::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..2600e7ccc115 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TouchesHelper.cpp @@ -0,0 +1,89 @@ +/* + * 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 "WritableNativeArray.h" +#include "WritableNativeMap.h" + +using namespace facebook::jni; + +namespace facebook::react { + +namespace { + +// Must match TouchesHelper.POINTER_FIELDS and the order documented on +// TouchesHelper.nativePutTouchPayload. +constexpr jint kPointerFields = 8; + +folly::dynamic createTouch(const double* fields) { + folly::dynamic touch = folly::dynamic::object(); + touch.insert("pageX", fields[0]); + touch.insert("pageY", fields[1]); + touch.insert("locationX", fields[2]); + touch.insert("locationY", fields[3]); + touch.insert("targetSurface", static_cast(fields[4])); + touch.insert("target", static_cast(fields[5])); + touch.insert("timestamp", fields[6]); + touch.insert("identifier", fields[7]); + return touch; +} + +void putTouchPayload( + alias_ref /*unused*/, + WritableNativeMap* payload, + 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++) { + 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)); + } + } + + const double* fields = values.get() + eventPointer * kPointerFields; + payload->putDouble("pageX", fields[0]); + payload->putDouble("pageY", fields[1]); + payload->putDouble("locationX", fields[2]); + payload->putDouble("locationY", fields[3]); + payload->putInt("targetSurface", static_cast(fields[4])); + payload->putInt("target", static_cast(fields[5])); + payload->putDouble("timestamp", fields[6]); + payload->putDouble("identifier", fields[7]); + WritableNativeArray changedTouchesArray(std::move(changedTouches)); + payload->putNativeArray("changedTouches", &changedTouchesArray); + WritableNativeArray touchesArray(std::move(touches)); + payload->putNativeArray("touches", &touchesArray); +} + +} // namespace + +void TouchesHelper::registerNatives() { + javaClassLocal()->registerNatives({ + makeNativeMethod("nativePutTouchPayload", putTouchPayload), + }); +} + +} // 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/main/jni/react/jni/WritableNativeMap.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.cpp index 0a35eb5f080b..086bab13a34b 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.cpp @@ -7,8 +7,6 @@ #include "WritableNativeMap.h" -#include - using namespace facebook::jni; namespace facebook::react { @@ -91,54 +89,8 @@ void WritableNativeMap::mergeNativeMap(ReadableNativeMap* other) { } } -void WritableNativeMap::putTouchPayload( - alias_ref pointerData, - jint eventPointer, - jint changedPointers, - jint touchPointers) { - throwIfConsumed(); - // Must match TouchesHelper.POINTER_FIELDS and the order documented on - // WritableNativeMap.putTouchPayload. - constexpr jint kPointerFields = 8; - auto length = static_cast(pointerData->size()); - jint pointerCount = length / kPointerFields; - if (length % kPointerFields != 0 || eventPointer < 0 || - eventPointer >= pointerCount) { - throw std::invalid_argument("Invalid touch pointer data"); - } - auto values = pointerData->getRegion(0, length); - auto pointer = [&](jint index) { - const double* fields = values.get() + index * kPointerFields; - folly::dynamic touch = folly::dynamic::object(); - touch.insert("pageX", fields[0]); - touch.insert("pageY", fields[1]); - touch.insert("locationX", fields[2]); - touch.insert("locationY", fields[3]); - touch.insert("targetSurface", static_cast(fields[4])); - touch.insert("target", static_cast(fields[5])); - touch.insert("timestamp", fields[6]); - touch.insert("identifier", fields[7]); - return touch; - }; - - folly::dynamic changedTouches = folly::dynamic::array(); - folly::dynamic touches = folly::dynamic::array(); - for (jint index = 0; index < pointerCount; index++) { - if ((changedPointers & (1 << index)) != 0) { - changedTouches.push_back(pointer(index)); - } - if ((touchPointers & (1 << index)) != 0) { - touches.push_back(pointer(index)); - } - } - map_ = pointer(eventPointer); - map_.insert("changedTouches", std::move(changedTouches)); - map_.insert("touches", std::move(touches)); -} - void WritableNativeMap::registerNatives() { registerHybrid({ - makeNativeMethod("putTouchPayload", WritableNativeMap::putTouchPayload), makeNativeMethod("putNull", WritableNativeMap::putNull), makeNativeMethod("putBoolean", WritableNativeMap::putBoolean), makeNativeMethod("putDouble", WritableNativeMap::putDouble), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.h index 819aa9199532..d11799824146 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/WritableNativeMap.h @@ -37,11 +37,6 @@ struct WritableNativeMap : jni::HybridClass pointerData, - jint eventPointer, - jint changedPointers, - jint touchPointers); static void registerNatives(); 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 6353a4d7d1c3..55cef1f4abf0 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 @@ -30,6 +30,7 @@ import com.facebook.testutils.shadows.ShadowArguments import com.facebook.testutils.shadows.ShadowNativeLoader import com.facebook.testutils.shadows.ShadowNativeMap import com.facebook.testutils.shadows.ShadowSoLoader +import com.facebook.testutils.shadows.ShadowTouchesHelper import com.facebook.testutils.shadows.ShadowWritableNativeMap import org.assertj.core.api.Assertions.assertThat import org.junit.After @@ -54,6 +55,7 @@ import org.robolectric.shadow.api.Shadow ShadowNativeLoader::class, ShadowArguments::class, ShadowWritableNativeMap::class, + ShadowTouchesHelper::class, ] ) class TouchEventDispatchTest { 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..0b1d0a3274db --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows/ShadowTouchesHelper.kt @@ -0,0 +1,62 @@ +/* + * 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.WritableMap +import com.facebook.react.bridge.WritableNativeMap +import com.facebook.react.uimanager.events.TouchesHelper +import org.robolectric.annotation.Implementation +import org.robolectric.annotation.Implements + +@Implements(TouchesHelper::class) +class ShadowTouchesHelper { + + companion object { + private const val POINTER_FIELDS = 8 + + /** Builds the same payload as the native TouchesHelper::putTouchPayload. */ + @JvmStatic + @Implementation + fun nativePutTouchPayload( + payload: WritableNativeMap, + pointerData: DoubleArray, + eventPointer: Int, + changedPointers: Int, + touchPointers: Int, + ) { + val pointerCount = pointerData.size / 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) }) + } + } + putTouch(payload, pointerData, eventPointer) + payload.putArray("changedTouches", changedTouches) + payload.putArray("touches", touches) + } + + private fun putTouch(touch: WritableMap, pointerData: DoubleArray, index: Int) { + val field = index * POINTER_FIELDS + touch.putDouble("pageX", pointerData[field]) + touch.putDouble("pageY", pointerData[field + 1]) + touch.putDouble("locationX", pointerData[field + 2]) + touch.putDouble("locationY", pointerData[field + 3]) + touch.putInt("targetSurface", pointerData[field + 4].toInt()) + touch.putInt("target", pointerData[field + 5].toInt()) + touch.putDouble("timestamp", pointerData[field + 6]) + touch.putDouble("identifier", pointerData[field + 7]) + } + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows/ShadowWritableNativeMap.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows/ShadowWritableNativeMap.kt index 7624d89c6a04..22eb99076b4e 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows/ShadowWritableNativeMap.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/shadows/ShadowWritableNativeMap.kt @@ -7,8 +7,6 @@ package com.facebook.testutils.shadows -import com.facebook.react.bridge.JavaOnlyArray -import com.facebook.react.bridge.JavaOnlyMap import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableMap @@ -44,48 +42,4 @@ class ShadowWritableNativeMap : ShadowReadableNativeMap(), WritableMap { @Implementation override fun merge(source: ReadableMap): Unit = backingMap.merge(source) @Implementation override fun copy(): WritableMap = backingMap.copy() - - /** Builds the same payload as the native WritableNativeMap::putTouchPayload. */ - @Implementation - fun putTouchPayload( - pointerData: DoubleArray, - eventPointer: Int, - changedPointers: Int, - touchPointers: Int, - ) { - val pointerCount = pointerData.size / TOUCH_POINTER_FIELDS - val changedTouches = JavaOnlyArray() - val touches = JavaOnlyArray() - for (index in 0 until pointerCount) { - if (changedPointers and (1 shl index) != 0) { - changedTouches.pushMap(touch(pointerData, index)) - } - if (touchPointers and (1 shl index) != 0) { - touches.pushMap(touch(pointerData, index)) - } - } - backingMap = - touch(pointerData, eventPointer).apply { - putArray("changedTouches", changedTouches) - putArray("touches", touches) - } - } - - private fun touch(pointerData: DoubleArray, index: Int): JavaOnlyMap { - val field = index * TOUCH_POINTER_FIELDS - return JavaOnlyMap().apply { - putDouble("pageX", pointerData[field]) - putDouble("pageY", pointerData[field + 1]) - putDouble("locationX", pointerData[field + 2]) - putDouble("locationY", pointerData[field + 3]) - putInt("targetSurface", pointerData[field + 4].toInt()) - putInt("target", pointerData[field + 5].toInt()) - putDouble("timestamp", pointerData[field + 6]) - putDouble("identifier", pointerData[field + 7]) - } - } - - private companion object { - private const val TOUCH_POINTER_FIELDS = 8 - } } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 5d0e667d5ab4..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); @@ -3762,7 +3767,6 @@ struct facebook::react::WritableNativeMap : public jni::HybridClass val); - public void putTouchPayload(jni::alias_ref pointerData, jint eventPointer, jint changedPointers, jint touchPointers); } template diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api index 979180565124..93e5f0cdf12b 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); @@ -6805,7 +6810,6 @@ struct facebook::react::WritableNativeMap : public jni::HybridClass val); - public void putTouchPayload(jni::alias_ref pointerData, jint eventPointer, jint changedPointers, jint touchPointers); } template diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index b05061a5d53e..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); @@ -3755,7 +3760,6 @@ struct facebook::react::WritableNativeMap : public jni::HybridClass val); - public void putTouchPayload(jni::alias_ref pointerData, jint eventPointer, jint changedPointers, jint touchPointers); } template diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index b05061a5d53e..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); @@ -3755,7 +3760,6 @@ struct facebook::react::WritableNativeMap : public jni::HybridClass val); - public void putTouchPayload(jni::alias_ref pointerData, jint eventPointer, jint changedPointers, jint touchPointers); } template From 279fc968ba6b4a78ca8cde92470e425534b570de Mon Sep 17 00:00:00 2001 From: Gregory Moskaliuk Date: Wed, 28 Oct 2026 12:26:04 +0100 Subject: [PATCH 4/4] Create the touch payload map in C++ and register TouchesHelper in reactnativejni Address review: nativeCreateTouchPayload now creates the WritableNativeMap in C++ and returns it, TouchesHelper loads reactnativejni and is registered there like TransformHelper, and the deprecated TARGET_KEY is removed. Co-Authored-By: Claude Opus 5.5 --- .../react/uimanager/events/TouchesHelper.kt | 25 ++++++++----------- .../src/main/jni/react/jni/OnLoad-common.cpp | 2 -- .../src/main/jni/react/jni/OnLoad.cpp | 2 ++ .../src/main/jni/react/jni/TouchesHelper.cpp | 10 +++++--- .../testutils/shadows/ShadowTouchesHelper.kt | 9 ++++--- 5 files changed, 24 insertions(+), 24 deletions(-) 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 252f96e9832f..176373a7de57 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 @@ -8,7 +8,7 @@ package com.facebook.react.uimanager.events import com.facebook.proguard.annotations.DoNotStrip -import com.facebook.react.bridge.ReactNativeJniCommonSoLoader +import com.facebook.react.bridge.ReactNativeJNISoLoader import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.uimanager.PixelUtil.pxToDp import com.facebook.systrace.Systrace @@ -20,16 +20,14 @@ import com.facebook.systrace.Systrace @DoNotStrip internal object TouchesHelper { init { - ReactNativeJniCommonSoLoader.staticInit() + ReactNativeJNISoLoader.staticInit() } - @JvmField @Deprecated("Not used in New Architecture") val TARGET_KEY: String = "target" - - // Values per pointer in the data passed to nativePutTouchPayload. + // Values per pointer in the data passed to nativeCreateTouchPayload. private const val POINTER_FIELDS = 8 /** - * Fills the pointer data of [nativePutTouchPayload]: for each pointer of the + * 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. */ @@ -127,8 +125,8 @@ internal object TouchesHelper { if (changedPointers and (1 shl pointer) == 0) { continue } - val eventData = WritableNativeMap() - nativePutTouchPayload(eventData, pointerData, pointer, changedPointers, touchPointers) + val eventData = + nativeCreateTouchPayload(pointerData, pointer, changedPointers, touchPointers) eventEmitter.receiveEvent( event.surfaceId, @@ -147,20 +145,19 @@ internal object TouchesHelper { } /** - * Puts the payload of a touch event into the empty map [payload]: the fields of pointer - * [eventPointer] and the `changedTouches` and `touches` arrays of the pointers whose bits are set - * in [changedPointers] and [touchPointers]. + * 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 nativePutTouchPayload( - payload: WritableNativeMap, + private external fun nativeCreateTouchPayload( pointerData: DoubleArray, eventPointer: Int, changedPointers: Int, touchPointers: Int, - ) + ): WritableNativeMap } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp index 59f6013de87f..8c6787bdde65 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp @@ -12,7 +12,6 @@ #include "JReactMarker.h" #include "NativeArray.h" #include "NativeMap.h" -#include "TouchesHelper.h" #include "WritableNativeArray.h" #include "WritableNativeMap.h" @@ -28,7 +27,6 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { NativeMap::registerNatives(); ReadableNativeArray::registerNatives(); ReadableNativeMap::registerNatives(); - TouchesHelper::registerNatives(); WritableNativeArray::registerNatives(); WritableNativeMap::registerNatives(); }); 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 index 2600e7ccc115..7f52b1ace1b2 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/TouchesHelper.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TouchesHelper.cpp @@ -19,7 +19,7 @@ namespace facebook::react { namespace { // Must match TouchesHelper.POINTER_FIELDS and the order documented on -// TouchesHelper.nativePutTouchPayload. +// TouchesHelper.nativeCreateTouchPayload. constexpr jint kPointerFields = 8; folly::dynamic createTouch(const double* fields) { @@ -35,9 +35,8 @@ folly::dynamic createTouch(const double* fields) { return touch; } -void putTouchPayload( +local_ref createTouchPayload( alias_ref /*unused*/, - WritableNativeMap* payload, alias_ref pointerData, jint eventPointer, jint changedPointers, @@ -63,6 +62,8 @@ void putTouchPayload( } } + auto jPayload = WritableNativeMap::newObjectJavaArgs(); + auto* payload = jPayload->cthis(); const double* fields = values.get() + eventPointer * kPointerFields; payload->putDouble("pageX", fields[0]); payload->putDouble("pageY", fields[1]); @@ -76,13 +77,14 @@ void putTouchPayload( payload->putNativeArray("changedTouches", &changedTouchesArray); WritableNativeArray touchesArray(std::move(touches)); payload->putNativeArray("touches", &touchesArray); + return jPayload; } } // namespace void TouchesHelper::registerNatives() { javaClassLocal()->registerNatives({ - makeNativeMethod("nativePutTouchPayload", putTouchPayload), + makeNativeMethod("nativeCreateTouchPayload", createTouchPayload), }); } 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 index 0b1d0a3274db..adacabf276dc 100644 --- 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 @@ -21,16 +21,15 @@ class ShadowTouchesHelper { companion object { private const val POINTER_FIELDS = 8 - /** Builds the same payload as the native TouchesHelper::putTouchPayload. */ + /** Builds the same payload as the native TouchesHelper::createTouchPayload. */ @JvmStatic @Implementation - fun nativePutTouchPayload( - payload: WritableNativeMap, + fun nativeCreateTouchPayload( pointerData: DoubleArray, eventPointer: Int, changedPointers: Int, touchPointers: Int, - ) { + ): WritableNativeMap { val pointerCount = pointerData.size / POINTER_FIELDS val changedTouches = JavaOnlyArray() val touches = JavaOnlyArray() @@ -42,9 +41,11 @@ class ShadowTouchesHelper { touches.pushMap(JavaOnlyMap().also { putTouch(it, pointerData, index) }) } } + val payload = WritableNativeMap() putTouch(payload, pointerData, eventPointer) payload.putArray("changedTouches", changedTouches) payload.putArray("touches", touches) + return payload } private fun putTouch(touch: WritableMap, pointerData: DoubleArray, index: Int) {