From 10aab3b45058f16aa96d44127caff60d354cdba5 Mon Sep 17 00:00:00 2001 From: Kamil Paradowski Date: Wed, 3 Jun 2026 16:04:13 +0200 Subject: [PATCH 1/5] feat: ArrayBuffer support for Java TM --- .../modules/GenerateModuleJavaSpec.js | 14 +- .../modules/GenerateModuleJniCpp.js | 6 +- .../modules/__test_fixtures__/fixtures.js | 2 +- .../GenerateModuleJavaSpec-test.js.snap | 58 +++++- .../GenerateModuleJniCpp-test.js.snap | 30 +++- .../GenerateModuleJniH-test.js.snap | 7 + .../facebook/react/bridge/CxxCallbackImpl.kt | 42 ++++- .../react/bridge/CxxCallbackRejectImpl.kt | 24 +++ .../com/facebook/react/bridge/PromiseImpl.kt | 6 +- .../react/bridge/ZeroCopyByteBufferHolder.kt | 18 ++ .../src/main/jni/react/jni/CMakeLists.txt | 1 + .../src/main/jni/react/jni/JCallback.cpp | 107 +++++++++++ .../src/main/jni/react/jni/JCallback.h | 59 +++++-- .../main/jni/react/jni/JMutableDataBuffer.h | 50 ++++++ .../main/jni/react/jni/JZeroCopyByteBuffer.h | 62 +++++++ .../src/main/jni/react/jni/OnLoad-common.cpp | 3 + .../main/jni/react/jni/TurboModuleJSError.h | 56 ++++++ .../core/ReactCommon/TurboModule.h | 1 + .../android/ReactCommon/JavaTurboModule.cpp | 167 ++++++++++-------- .../platform/android/SampleTurboModule.kt | 48 +++++ .../modules/NativeSampleTurboModule.js | 4 + .../TurboModule/SampleTurboModuleExample.js | 37 ++++ .../api-snapshots/ReactAndroidDebugCxx.api | 5 + .../api-snapshots/ReactAndroidNewarchCxx.api | 5 + .../api-snapshots/ReactAndroidReleaseCxx.api | 5 + 25 files changed, 706 insertions(+), 111 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxCallbackRejectImpl.kt create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ZeroCopyByteBufferHolder.kt create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/jni/JCallback.cpp create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/jni/JMutableDataBuffer.h create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/jni/JZeroCopyByteBuffer.h create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/jni/TurboModuleJSError.h diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js index 58331fb2651f..d59c3d6b1890 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js @@ -268,9 +268,8 @@ function translateFunctionParamToJavaType( imports.add('com.facebook.react.bridge.Callback'); return wrapOptional('Callback', isRequired); case 'ArrayBufferTypeAnnotation': - throw new Error( - `${createErrorMessage(realTypeAnnotation.type)} ArrayBuffer is only supported for C++ TurboModules.`, - ); + imports.add('java.nio.ByteBuffer'); + return wrapOptional('ByteBuffer', isRequired); default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error(createErrorMessage(realTypeAnnotation.type)); @@ -366,9 +365,8 @@ function translateFunctionReturnTypeToJavaType( imports.add('com.facebook.react.bridge.WritableArray'); return wrapOptional('WritableArray', isRequired); case 'ArrayBufferTypeAnnotation': - throw new Error( - `${createErrorMessage(realTypeAnnotation.type)} ArrayBuffer is only supported for C++ TurboModules.`, - ); + imports.add('java.nio.ByteBuffer'); + return wrapOptional('ByteBuffer', isRequired); default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error(createErrorMessage(realTypeAnnotation.type)); @@ -452,9 +450,7 @@ function getFalsyReturnStatementFromReturnType( case 'ArrayTypeAnnotation': return 'return null;'; case 'ArrayBufferTypeAnnotation': - throw new Error( - `${createErrorMessage(realTypeAnnotation.type)} ArrayBuffer is only supported for C++ TurboModules.`, - ); + return 'return null;'; default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error(createErrorMessage(realTypeAnnotation.type)); diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js index c05b97f0b2d6..0ee063f20cd7 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js @@ -217,7 +217,7 @@ function translateReturnTypeToKind( case 'ArrayTypeAnnotation': return 'ArrayKind'; case 'ArrayBufferTypeAnnotation': - throw new Error('ArrayBuffer is only supported for C++ TurboModules.'); + return 'ArrayBufferKind'; default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( @@ -306,7 +306,7 @@ function translateParamTypeToJniType( case 'FunctionTypeAnnotation': return 'Lcom/facebook/react/bridge/Callback;'; case 'ArrayBufferTypeAnnotation': - throw new Error('ArrayBuffer is only supported for C++ TurboModules.'); + return 'Ljava/nio/ByteBuffer;'; default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( @@ -392,7 +392,7 @@ function translateReturnTypeToJniType( case 'ArrayTypeAnnotation': return 'Lcom/facebook/react/bridge/WritableArray;'; case 'ArrayBufferTypeAnnotation': - throw new Error('ArrayBuffer is only supported for C++ TurboModules.'); + return 'Ljava/nio/ByteBuffer;'; default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( diff --git a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js index 2b1db52e4ba9..96525da69e8e 100644 --- a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js @@ -2678,7 +2678,7 @@ const ARRAY_BUFFER_NATIVE_MODULE: SchemaType = { ], }, moduleName: 'SampleTurboModule', - excludedPlatforms: ['iOS', 'android'], + excludedPlatforms: ['iOS'], }, }, }; diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJavaSpec-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJavaSpec-test.js.snap index dc2df9db0645..f1ecb2dca933 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJavaSpec-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJavaSpec-test.js.snap @@ -41,7 +41,63 @@ public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaMo } `; -exports[`GenerateModuleJavaSpec can generate fixture array_buffer_native_module 1`] = `Map {}`; +exports[`GenerateModuleJavaSpec can generate fixture array_buffer_native_module 1`] = ` +Map { + "java/com/facebook/fbreact/specs/NativeSampleTurboModuleSpec.java" => " +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJavaSpec.js + * + * @nolint + */ + +package com.facebook.fbreact.specs; + +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import java.nio.ByteBuffer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaModule implements TurboModule { + public static final String NAME = \\"SampleTurboModule\\"; + + public NativeSampleTurboModuleSpec(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public @Nonnull String getName() { + return NAME; + } + + @ReactMethod(isBlockingSynchronousMethod = true) + @DoNotStrip + public abstract ByteBuffer getArrayBuffer(); + + @ReactMethod + @DoNotStrip + public abstract void voidArrayBuffer(ByteBuffer arg); + + @ReactMethod + @DoNotStrip + public abstract void voidNullableArrayBuffer(@Nullable ByteBuffer arg); + + @ReactMethod + @DoNotStrip + public abstract void promiseArrayBuffer(Promise promise); +} +", +} +`; exports[`GenerateModuleJavaSpec can generate fixture complex_objects 1`] = ` Map { diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniCpp-test.js.snap index c4285ae56f65..6a07fc1ede36 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniCpp-test.js.snap @@ -51,10 +51,38 @@ Map { namespace facebook::react { +static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, ArrayBufferKind, \\"getArrayBuffer\\", \\"()Ljava/nio/ByteBuffer;\\", args, count, cachedMethodId); +} +static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidArrayBuffer\\", \\"(Ljava/nio/ByteBuffer;)V\\", args, count, cachedMethodId); +} -std::shared_ptr array_buffer_native_module_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) { +static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidNullableArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidNullableArrayBuffer\\", \\"(Ljava/nio/ByteBuffer;)V\\", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"promiseArrayBuffer\\", \\"(Lcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId); +} + +NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms) + : JavaTurboModule(params) { + methodMap_[\\"getArrayBuffer\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer}; + methodMap_[\\"voidArrayBuffer\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_voidArrayBuffer}; + methodMap_[\\"voidNullableArrayBuffer\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_voidNullableArrayBuffer}; + methodMap_[\\"promiseArrayBuffer\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseArrayBuffer}; +} +std::shared_ptr array_buffer_native_module_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) { + if (moduleName == \\"SampleTurboModule\\") { + return std::make_shared(params); + } return nullptr; } diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniH-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniH-test.js.snap index c46009a84e33..daa41686b1c4 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniH-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniH-test.js.snap @@ -86,6 +86,13 @@ Map { namespace facebook::react { +/** + * JNI C++ class for module 'NativeSampleTurboModule' + */ +class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public JavaTurboModule { +public: + NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms); +}; JSI_EXPORT diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxCallbackImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxCallbackImpl.kt index 8554580f1cb4..bd6b8bd30d2e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxCallbackImpl.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxCallbackImpl.kt @@ -9,14 +9,54 @@ package com.facebook.react.bridge import com.facebook.jni.HybridClassBase import com.facebook.proguard.annotations.DoNotStrip +import java.nio.ByteBuffer -/** Callback impl that calls directly into the cxx bridge. Created from C++. */ +/** + * Callback impl that calls directly into the cxx bridge. Created from C++. + * A single direct [ByteBuffer] bypasses folly::dynamic for zero-copy `jsi::ArrayBuffer` + * on the JS thread (see [JCxxCallbackImpl]). + */ @DoNotStrip public class CxxCallbackImpl @DoNotStrip private constructor() : HybridClassBase(), Callback { override fun invoke(vararg args: Any?) { + val singleArg = args.singleOrNull() + if (singleArg is ByteBuffer) { + requireDirectByteBuffer(singleArg) + nativeInvokeWithByteBuffer(singleArg) + return + } + + val hasByteBuffer = args.any { arg -> + if (arg is ByteBuffer) { + requireDirectByteBuffer(arg) + true + } else { + false + } + } + + if (hasByteBuffer) { + throw IllegalArgumentException( + "Callbacks support at most one direct ByteBuffer argument passed alone. " + + "Use promise.resolve(buffer) or callback.invoke(buffer)." + ) + } + @Suppress("UNCHECKED_CAST") nativeInvoke(Arguments.fromJavaArgs(args as Array)) } private external fun nativeInvoke(arguments: NativeArray) + + private external fun nativeInvokeWithByteBuffer(buffer: ByteBuffer) + + private companion object { + private fun requireDirectByteBuffer(buffer: ByteBuffer) { + check(buffer.isDirect) { + "Only direct ByteBuffers (ByteBuffer.allocateDirect) can be passed to a native " + + "callback or promise. Use ByteBuffer.allocateDirect() instead of ByteBuffer.wrap() " + + "or ByteBuffer.allocate()." + } + } + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxCallbackRejectImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxCallbackRejectImpl.kt new file mode 100644 index 000000000000..8ddfbcab7547 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxCallbackRejectImpl.kt @@ -0,0 +1,24 @@ +/* + * 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.react.bridge + +import com.facebook.jni.HybridClassBase +import com.facebook.proguard.annotations.DoNotStrip + +/** C++-backed promise reject callback (error maps only; folly::dynamic path). */ +@DoNotStrip +internal class CxxCallbackRejectImpl @DoNotStrip private constructor() : + HybridClassBase(), Callback { + + override fun invoke(vararg args: Any?) { + @Suppress("UNCHECKED_CAST") + nativeInvoke(Arguments.fromJavaArgs(args as Array)) + } + + private external fun nativeInvoke(arguments: NativeArray) +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.kt index 31cafcc70c2a..d0d4f903b37b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.kt @@ -26,7 +26,11 @@ constructor(private var resolve: Callback?, private var reject: Callback?) : Pro */ override fun resolve(value: Any?) { resolve?.let { callback -> - callback.invoke(value) + try { + callback.invoke(value) + } catch (e: Exception) { + reject(e) + } resolve = null reject = null } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ZeroCopyByteBufferHolder.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ZeroCopyByteBufferHolder.kt new file mode 100644 index 000000000000..c35a260f9845 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ZeroCopyByteBufferHolder.kt @@ -0,0 +1,18 @@ +/* + * 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.react.bridge + +import com.facebook.jni.HybridClassBase +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Retains a native [jsi::MutableBuffer] owner while TurboModule JNI passes a zero-copy + * [java.nio.ByteBuffer] view to Java. + */ +@DoNotStrip +internal class ZeroCopyByteBufferHolder @DoNotStrip private constructor() : HybridClassBase() 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 8bd7ef473c80..2a6dd0da595c 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 @@ -23,6 +23,7 @@ include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-ut add_library( reactnativejni_common OBJECT + JCallback.cpp JDynamicNative.cpp JReactMarker.cpp NativeArray.cpp diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JCallback.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JCallback.cpp new file mode 100644 index 000000000000..90a02af31923 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JCallback.cpp @@ -0,0 +1,107 @@ +/* + * 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 "JCallback.h" + +#include + +#include +#include +#include + +#include "JMutableDataBuffer.h" +#include "TurboModuleJSError.h" + +namespace facebook::react { + +namespace { + +jsi::Value byteBufferToJSIArrayBuffer( + jsi::Runtime& rt, + jni::global_ref buffer) { + auto mutableBuf = std::make_shared(std::move(buffer)); + return jsi::Value(rt, jsi::ArrayBuffer(rt, std::move(mutableBuf))); +} + +} // namespace + +void JCxxCallbackImpl::registerNatives() { + registerHybrid({ + makeNativeMethod("nativeInvoke", JCxxCallbackImpl::invoke), + makeNativeMethod( + "nativeInvokeWithByteBuffer", + JCxxCallbackImpl::invokeWithByteBuffer), + }); +} + +JCxxCallbackImpl::JCxxCallbackImpl(AsyncCallback<> callback) + : asyncCallback_(std::move(callback)) {} + +JCxxCallbackImpl::JCxxCallbackImpl(std::function callback) + : directCallback_(std::move(callback)) {} + +void JCxxCallbackImpl::invoke(NativeArray* arguments) { + auto args = arguments->consume(); + if (directCallback_) { + directCallback_(std::move(args)); + return; + } + if (!asyncCallback_) { + LOG(FATAL) << "CxxCallbackImpl::invoke called more than once"; + return; + } + asyncCallback_->call( + [args = std::move(args)](jsi::Runtime& rt, jsi::Function& fn) { + std::vector jsArgs; + jsArgs.reserve(args.size()); + for (const auto& val : args) { + jsArgs.emplace_back(jsi::valueFromDynamic(rt, val)); + } + fn.call(rt, (const jsi::Value*)jsArgs.data(), jsArgs.size()); + }); + asyncCallback_ = std::nullopt; +} + +void JCxxCallbackImpl::invokeWithByteBuffer( + jni::alias_ref buffer) { + if (directCallback_) { + LOG(FATAL) << "ByteBuffer arguments are not supported for this callback"; + return; + } + if (!asyncCallback_) { + LOG(FATAL) << "CxxCallbackImpl::invokeWithByteBuffer called more than once"; + return; + } + asyncCallback_->call([buffer = jni::make_global(buffer)]( + jsi::Runtime& rt, jsi::Function& fn) mutable { + fn.call(rt, byteBufferToJSIArrayBuffer(rt, std::move(buffer))); + }); + asyncCallback_ = std::nullopt; +} + +void JCxxCallbackRejectImpl::registerNatives() { + registerHybrid( + {makeNativeMethod("nativeInvoke", JCxxCallbackRejectImpl::invoke)}); +} + +JCxxCallbackRejectImpl::JCxxCallbackRejectImpl(AsyncCallback<> callback) + : asyncCallback_(std::move(callback)) {} + +void JCxxCallbackRejectImpl::invoke(NativeArray* arguments) { + auto args = arguments->consume(); + if (!asyncCallback_) { + LOG(FATAL) << "CxxCallbackRejectImpl::invoke called more than once"; + return; + } + asyncCallback_->call([args = std::move(args)]( + jsi::Runtime& rt, jsi::Function& fn) { + fn.call(rt, createRejectionError(rt, args)); + }); + asyncCallback_ = std::nullopt; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JCallback.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JCallback.h index bde467d4a7e8..455472659553 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JCallback.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JCallback.h @@ -7,44 +7,67 @@ #pragma once -#include +#include +#include +#include #include #include +#include #include "NativeArray.h" namespace facebook::react { -class Instance; - struct JCallback : public jni::JavaClass { - constexpr static auto kJavaDescriptor = "Lcom/facebook/react/bridge/Callback;"; + constexpr static auto kJavaDescriptor = + "Lcom/facebook/react/bridge/Callback;"; }; +// Callback from JS into Java TurboModules (resolve, Callback args, event +// emitter). The Kotlin peer routes direct ByteBuffers around folly::dynamic for +// zero-copy jsi::ArrayBuffer on the JS thread. class JCxxCallbackImpl : public jni::HybridClass { public: - constexpr static auto kJavaDescriptor = "Lcom/facebook/react/bridge/CxxCallbackImpl;"; + constexpr static auto kJavaDescriptor = + "Lcom/facebook/react/bridge/CxxCallbackImpl;"; + + static void registerNatives(); + + private: + friend HybridBase; + + // AsyncCallback<> constructor: Promise resolve / Callback. Callable once. + explicit JCxxCallbackImpl(AsyncCallback<> callback); + + // std::function constructor: event emitter. Callable multiple times. + explicit JCxxCallbackImpl(std::function callback); + + void invoke(NativeArray* arguments); + void invokeWithByteBuffer( + jni::alias_ref buffer); + + std::optional> asyncCallback_; + std::function directCallback_; +}; + +// Promise reject and other error-only callbacks (folly::dynamic only). +class JCxxCallbackRejectImpl + : public jni::HybridClass { + public: + static constexpr auto kJavaDescriptor = + "Lcom/facebook/react/bridge/CxxCallbackRejectImpl;"; - static void registerNatives() - { - registerHybrid({ - makeNativeMethod("nativeInvoke", JCxxCallbackImpl::invoke), - }); - } + static void registerNatives(); private: friend HybridBase; - using Callback = std::function; - JCxxCallbackImpl(Callback callback) : callback_(std::move(callback)) {} + explicit JCxxCallbackRejectImpl(AsyncCallback<> callback); - void invoke(NativeArray *arguments) - { - callback_(arguments->consume()); - } + void invoke(NativeArray* arguments); - Callback callback_; + std::optional> asyncCallback_; }; } // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JMutableDataBuffer.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JMutableDataBuffer.h new file mode 100644 index 000000000000..7c747927699e --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JMutableDataBuffer.h @@ -0,0 +1,50 @@ +/* + * 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 +#include +#include + +namespace facebook::react { + +// Wraps a direct Java ByteBuffer as a jsi::MutableBuffer for zero-copy JS +// ArrayBuffer access. The global ref keeps the Java object alive for as long as +// the JS ArrayBuffer is reachable. +// +// IMPORTANT: jsi::MutableBuffer finalizers run on the Hermes GC background +// thread, which is not attached to JNI. Destroying a jni::global_ref on an +// unattached thread calls abort(). jni::ThreadScope in the destructor attaches +// the GC thread temporarily, making the global_ref teardown safe. +class JMutableDataBuffer final : public jsi::MutableBuffer { + public: + explicit JMutableDataBuffer( + jni::global_ref byteBuffer) noexcept + : byteBuffer_(std::move(byteBuffer)) {} + + ~JMutableDataBuffer() { + jni::ThreadScope threadScope; + byteBuffer_.reset(); + } + + jni::local_ref getJavaByteBuffer() const { + return jni::make_local(byteBuffer_); + } + + size_t size() const override { + return byteBuffer_->getDirectSize(); + } + uint8_t* data() override { + return byteBuffer_->getDirectBytes(); + } + + private: + jni::global_ref byteBuffer_; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JZeroCopyByteBuffer.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JZeroCopyByteBuffer.h new file mode 100644 index 000000000000..36cad831824e --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JZeroCopyByteBuffer.h @@ -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. + */ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace facebook::react { + +// Retains a jsi::MutableBuffer and exposes its memory as a direct ByteBuffer +// view. +class JZeroCopyByteBufferHolder + : public jni::HybridClass { + public: + static constexpr auto kJavaDescriptor = + "Lcom/facebook/react/bridge/ZeroCopyByteBufferHolder;"; + + struct Wrapped { + jni::local_ref holder; + jni::local_ref byteBuffer; + }; + + static void registerNatives() { + registerHybrid({}); + } + + static Wrapped wrapMutableBuffer(std::shared_ptr buffer) { + auto byteBuffer = [&]() { + if (auto* javaBacked = dynamic_cast(buffer.get())) { + return javaBacked->getJavaByteBuffer(); + } + auto* mutableBuffer = holder->cthis()->buffer_.get(); + return jni::JByteBuffer::wrapBytes( + mutableBuffer->data(), mutableBuffer->size()); + }(); + auto holder = newObjectCxxArgs(std::move(buffer)); + return { + std::move(holder), + std::move(byteBuffer), + }; + } + + private: + friend HybridBase; + + explicit JZeroCopyByteBufferHolder( + std::shared_ptr buffer) noexcept + : buffer_(std::move(buffer)) {} + + std::shared_ptr buffer_; +}; + +} // namespace facebook::react 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 293d2ffc8abf..5c34b1e2bcf9 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 @@ -8,6 +8,7 @@ #include #include "JCallback.h" #include "JDynamicNative.h" +#include "JZeroCopyByteBuffer.h" #include "JReactMarker.h" #include "NativeArray.h" #include "NativeMap.h" @@ -19,7 +20,9 @@ namespace facebook::react { extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { return facebook::jni::initialize(vm, [] { JCxxCallbackImpl::registerNatives(); + JCxxCallbackRejectImpl::registerNatives(); JDynamicNative::registerNatives(); + JZeroCopyByteBufferHolder::registerNatives(); JReactMarker::registerNatives(); NativeArray::registerNatives(); NativeMap::registerNatives(); diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/TurboModuleJSError.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TurboModuleJSError.h new file mode 100644 index 000000000000..1dd57284415e --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TurboModuleJSError.h @@ -0,0 +1,56 @@ +/* + * 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 +#include +#include +#include + +namespace facebook::react { + +inline jsi::Value createJSRuntimeError( + jsi::Runtime& runtime, + jsi::Value&& message) { + return runtime.global() + .getPropertyAsFunction(runtime, "Error") + .call(runtime, std::move(message)); +} + +inline jsi::Value createJSRuntimeError( + jsi::Runtime& runtime, + const std::string& message) { + return createJSRuntimeError( + runtime, jsi::String::createFromUtf8(runtime, message)); +} + +inline jsi::Value createRejectionError( + jsi::Runtime& rt, + const folly::dynamic& args) { + react_native_assert( + args.size() == 1 && "promise reject should have only one argument"); + + auto value = jsi::valueFromDynamic(rt, args[0]); + react_native_assert(value.isObject() && "promise reject should return a map"); + + const jsi::Object& valueAsObject = value.asObject(rt); + auto jsError = createJSRuntimeError( + rt, valueAsObject.getProperty(rt, "message")); + + auto jsErrorAsObject = jsError.asObject(rt); + auto propertyNames = valueAsObject.getPropertyNames(rt); + for (size_t i = 0; i < propertyNames.size(rt); ++i) { + auto propertyName = jsi::PropNameID::forString( + rt, propertyNames.getValueAtIndex(rt, i).asString(rt)); + jsErrorAsObject.setProperty( + rt, propertyName, valueAsObject.getProperty(rt, propertyName)); + } + return jsError; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h index f1d51678bf54..6774209c3a47 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h @@ -31,6 +31,7 @@ enum TurboModuleMethodValueKind { ArrayKind, FunctionKind, PromiseKind, + ArrayBufferKind }; /** diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp index 5c9b464a8c3a..355bb94b7146 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp @@ -5,11 +5,13 @@ * LICENSE file in the root directory of this source tree. */ +#include #include #include #include #include +#include #include #include #include @@ -20,9 +22,13 @@ #include #include #include +#include #include +#include +#include #include #include +#include #include #include "JavaTurboModule.h" @@ -78,84 +84,20 @@ struct JNIArgs { } }; -jsi::Value createJSRuntimeError(jsi::Runtime& runtime, jsi::Value&& message) { - return runtime.global() - .getPropertyAsFunction(runtime, "Error") - .call(runtime, std::move(message)); -} - -jsi::Value createJSRuntimeError( - jsi::Runtime& runtime, - const std::string& message) { - return createJSRuntimeError( - runtime, jsi::String::createFromUtf8(runtime, message)); -} - -jsi::Value createRejectionError(jsi::Runtime& rt, const folly::dynamic& args) { - react_native_assert( - args.size() == 1 && "promise reject should has only one argument"); - - auto value = jsi::valueFromDynamic(rt, args[0]); - react_native_assert(value.isObject() && "promise reject should return a map"); - - const jsi::Object& valueAsObject = value.asObject(rt); - auto jsError = - createJSRuntimeError(rt, valueAsObject.getProperty(rt, "message")); - - auto jsErrorAsObject = jsError.asObject(rt); - auto propertyNames = valueAsObject.getPropertyNames(rt); - for (size_t i = 0; i < propertyNames.size(rt); ++i) { - auto propertyName = jsi::PropNameID::forString( - rt, propertyNames.getValueAtIndex(rt, i).asString(rt)); - jsErrorAsObject.setProperty( - rt, propertyName, valueAsObject.getProperty(rt, propertyName)); - } - return jsError; -} - auto createJavaCallback( jsi::Runtime& rt, jsi::Function&& function, std::shared_ptr jsInvoker) { - std::optional> callback( - {rt, std::move(function), std::move(jsInvoker)}); return JCxxCallbackImpl::newObjectCxxArgs( - [callback = std::move(callback)](folly::dynamic args) mutable { - if (!callback) { - LOG(FATAL) << "Callback arg cannot be called more than once"; - return; - } - callback->call([args = std::move(args)]( - jsi::Runtime& rt, jsi::Function& jsFunction) { - std::vector jsArgs; - jsArgs.reserve(args.size()); - for (const auto& val : args) { - jsArgs.emplace_back(jsi::valueFromDynamic(rt, val)); - } - jsFunction.call(rt, (const jsi::Value*)jsArgs.data(), jsArgs.size()); - }); - callback = std::nullopt; - }); + AsyncCallback<>(rt, std::move(function), std::move(jsInvoker))); } auto createJavaRejectCallback( jsi::Runtime& rt, jsi::Function&& function, std::shared_ptr jsInvoker) { - std::optional> callback( - {rt, std::move(function), std::move(jsInvoker)}); - return JCxxCallbackImpl::newObjectCxxArgs( - [callback = std::move(callback)](folly::dynamic args) mutable { - if (!callback) { - LOG(FATAL) << "Callback arg cannot be called more than once"; - return; - } - callback->call([args = std::move(args)]( - jsi::Runtime& rt, jsi::Function& jsFunction) { - jsFunction.call(rt, createRejectionError(rt, args)); - }); - callback = std::nullopt; - }); + return JCxxCallbackRejectImpl::newObjectCxxArgs( + AsyncCallback<>(rt, std::move(function), std::move(jsInvoker))); } struct JPromiseImpl : public jni::JavaClass { @@ -311,12 +253,16 @@ JNIArgs convertJSIArgsToJNIArgs( auto& jargs = jniArgs.args; auto& globalRefs = jniArgs.globalRefs; + auto makeGlobal = [&](jobject obj) { + jobject globalObj = env->NewGlobalRef(obj); + globalRefs.push_back(globalObj); + env->DeleteLocalRef(obj); + return globalObj; + }; + auto makeGlobalIfNecessary = [&](jobject obj) { if (valueKind == VoidKind || valueKind == PromiseKind) { - jobject globalObj = env->NewGlobalRef(obj); - globalRefs.push_back(globalObj); - env->DeleteLocalRef(obj); - return globalObj; + return makeGlobal(obj); } return obj; @@ -434,6 +380,46 @@ JNIArgs convertJSIArgsToJNIArgs( auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg); auto jParams = JDynamicNative::newObjectCxxArgs(dynamicFromValue); jarg->l = makeGlobalIfNecessary(jParams.release()); + } else if (type == "Ljava/nio/ByteBuffer;") { + if (!(arg->isObject() && arg->getObject(rt).isArrayBuffer(rt))) { + throw JavaTurboModuleArgumentConversionException( + "ArrayBuffer", argIndex, methodName, arg, &rt); + } + + auto isSync = valueKind != VoidKind && valueKind != PromiseKind; + auto arrayBuffer = arg->getObject(rt).getArrayBuffer(rt); + + if (arrayBuffer.detached(rt)) { + throw jsi::JSError( + rt, + "JavaTurboModule::convertJSIArgsToJNIArgs: ArrayBuffer is detached."); + } + + auto size = arrayBuffer.size(rt); + auto data = arrayBuffer.data(rt); + auto buffer = [&]() { + // Native-backed buffers keep zero-copy semantics by wrapping the + // MutableBuffer in a holder and pinning that holder as a global JNI + // ref. + if (auto mutableBuffer = arrayBuffer.tryGetMutableBuffer(rt)) { + auto wrapped = JZeroCopyByteBufferHolder::wrapMutableBuffer( + std::move(mutableBuffer)); + makeGlobal(wrapped.holder.release()); + return wrapped.byteBuffer; + } + // Synchronous invocations can borrow JS-backed bytes for the duration + // of the call. + if (isSync) { + return jni::JByteBuffer::wrapBytes( + arrayBuffer.data(rt), arrayBuffer.size(rt)); + } + // Asynchronous JS-backed buffers need an owned copy before they can + // escape the JS thread. + auto copy = jni::JByteBuffer::allocateDirect(size); + std::memcpy(copy->getDirectAddress(), data, size); + return copy; + }(); + jarg->l = makeGlobalIfNecessary(buffer.release()); } else { throw JavaTurboModuleInvalidArgumentTypeException( type, argIndex, methodName); @@ -874,15 +860,16 @@ jsi::Value JavaTurboModule::invokeJavaMethod( throw jsi::JSError(runtime, "Incorrect number of arguments"); } - nativeRejectCallback = AsyncCallback( + nativeRejectCallback = AsyncCallback<>( runtime, args[1].getObject(runtime).getFunction(runtime), jsInvoker_); - auto resolve = createJavaCallback( - runtime, - args[0].getObject(runtime).getFunction(runtime), - jsInvoker_); + auto resolve = JCxxCallbackImpl::newObjectCxxArgs( + AsyncCallback<>( + runtime, + args[0].getObject(runtime).getFunction(runtime), + jsInvoker_)); auto reject = createJavaRejectCallback( runtime, args[1].getObject(runtime).getFunction(runtime), @@ -963,6 +950,34 @@ jsi::Value JavaTurboModule::invokeJavaMethod( TMPL::asyncMethodCallEnd(moduleName, methodName); return jsPromise; } + case ArrayBufferKind: { + auto returnObject = + (jobject)env->CallObjectMethodA(instance, methodID, jargs.data()); + checkJNIErrorForMethodCall(); + + TMPL::syncMethodCallExecutionEnd(moduleName, methodName); + TMPL::syncMethodCallReturnConversionStart(moduleName, methodName); + + jsi::Value returnValue = jsi::Value::null(); + if (returnObject != nullptr) { + auto jByteBuffer = jni::adopt_local( + static_cast(returnObject)); + + if (!jByteBuffer->isDirect()) { + throw jsi::JSError( + runtime, + "Only direct ByteBuffers (ByteBuffer.allocateDirect) can be returned from a TurboModule."); + } + auto nativeBuffer = + std::make_shared(jni::make_global(jByteBuffer)); + returnValue = jsi::Value( + runtime, jsi::ArrayBuffer(runtime, std::move(nativeBuffer))); + } + + TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName); + TMPL::syncMethodCallEnd(moduleName, methodName); + return returnValue; + } default: throw std::runtime_error( "Unable to find method module: " + methodNameStr + "(" + diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.kt b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.kt index e6ae5f5060f1..25570053161d 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.kt +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.kt @@ -27,6 +27,7 @@ import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.module.annotations.ReactModule import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings +import java.nio.ByteBuffer import java.util.UUID @DoNotStrip @@ -264,6 +265,53 @@ public class SampleTurboModule(private val context: ReactApplicationContext) : override fun invalidate(): Unit = Unit + @DoNotStrip + @Suppress("unused") + override fun getArrayBuffer(buffer: ByteBuffer) : ByteBuffer { + val size = buffer.capacity() + for (i in 0 until size) { + buffer.put(i, (10 * i).toByte()) + } + return buffer + } + + @DoNotStrip + @Suppress("unused") + override fun createNativeBuffer(size: Double) : ByteBuffer { + val capacity = size.toInt() + val buffer = ByteBuffer.allocateDirect(capacity) + for (i in 0 until capacity) { + buffer.put(i, (i + 1).toByte()) + } + return buffer + } + + @DoNotStrip + @Suppress("unused") + override fun processAsyncBuffer(payload: ByteBuffer, promise: Promise) { + Thread { + var sum = 0.0 + val size = payload.capacity() + for (i in 0 until size) { + sum += payload.get(i).toInt() + } + promise.resolve(sum) + }.start() + } + + @DoNotStrip + @Suppress("unused") + override fun getAsyncBuffer(size: Double, promise: Promise) { + Thread { + val capacity = size.toInt() + val buffer = ByteBuffer.allocateDirect(capacity) + for (i in 0 until capacity) { + buffer.put(i, (i + 1).toByte()) + } + promise.resolve(buffer) + }.start() + } + override fun getName(): String { return NAME } diff --git a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js index 65ee3fe605c1..3f4b77c4801c 100644 --- a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js +++ b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js @@ -51,6 +51,10 @@ export interface Spec extends TurboModule { readonly getUnsafeObject: (arg: UnsafeObject) => UnsafeObject; readonly getRootTag: (arg: RootTag) => RootTag; readonly getValue: (x: number, y: string, z: Object) => Object; + readonly getArrayBuffer: (buffer: ArrayBuffer) => ArrayBuffer; + readonly createNativeBuffer: (size: number) => ArrayBuffer; + readonly processAsyncBuffer: (payload: ArrayBuffer) => Promise; + readonly getAsyncBuffer: (size: number) => Promise; readonly getValueWithCallback: (callback: (value: string) => void) => void; readonly getValueWithPromise: (error: boolean) => Promise; readonly voidFuncThrows?: () => void; diff --git a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js index c1c5803c06c1..aa5a073d7f97 100644 --- a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js +++ b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js @@ -46,6 +46,10 @@ type Examples = | 'getString' | 'getUnion' | 'getValue' + | 'getArrayBuffer' + | 'createNativeBuffer' + | 'processAsyncBuffer' + | 'getAsyncBuffer' | 'promise' | 'rejectPromise' | 'voidFunc' @@ -108,6 +112,39 @@ class SampleTurboModuleExample extends React.Component<{}, State> { getRootTag: () => NativeSampleTurboModule.getRootTag(this.context), getValue: () => NativeSampleTurboModule.getValue(5, 'test', {a: 1, b: 'foo'}), + getArrayBuffer: () => { + const view = new Uint8Array([1, 2, 3, 4, 5]); + const buffer = NativeSampleTurboModule?.getArrayBuffer(view.buffer); + return new Uint8Array(buffer || []).toString(); + }, + createNativeBuffer: () => { + const buffer = NativeSampleTurboModule?.createNativeBuffer(8); + return new Uint8Array(buffer || []).toString(); + }, + processAsyncBuffer: async () => { + const view = new Uint8Array([10, 20, 30, 40, 50]); + const nativeBuffer = NativeSampleTurboModule?.createNativeBuffer(10); + + const [byteSum, zeroCopyByteSum] = await Promise.all([ + NativeSampleTurboModule?.processAsyncBuffer(view.buffer), + nativeBuffer != null + ? NativeSampleTurboModule?.processAsyncBuffer(nativeBuffer) + : Promise.resolve(0), + ]); + + this._setResult( + 'processAsyncBuffer', + `sum=${(byteSum ?? 0) + (zeroCopyByteSum ?? 0)}`, + ); + }, + getAsyncBuffer: async () => { + return NativeSampleTurboModule?.getAsyncBuffer(10).then(buffer => { + this._setResult( + 'getAsyncBuffer', + new Uint8Array(buffer || []).toString(), + ); + }); + }, }; // $FlowFixMe[missing-local-annot] diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 5bbaffe84b69..a4ad2d4c4009 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -2784,6 +2784,11 @@ class facebook::react::JCxxCallbackImpl : public jni::HybridClass { + public static constexpr auto kJavaDescriptor; + public static void registerNatives(); +} + class facebook::react::JDynamicNative : public jni::HybridClass { public JDynamicNative(folly::dynamic payload); public static constexpr auto kJavaDescriptor; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index f41430b9759f..a3c682e1e71b 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -2740,6 +2740,11 @@ class facebook::react::JCxxCallbackImpl : public jni::HybridClass { + public static constexpr auto kJavaDescriptor; + public static void registerNatives(); +} + class facebook::react::JDynamicNative : public jni::HybridClass { public JDynamicNative(folly::dynamic payload); public static constexpr auto kJavaDescriptor; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index b70b7f31f3e5..1f86ed2cb4f8 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -2781,6 +2781,11 @@ class facebook::react::JCxxCallbackImpl : public jni::HybridClass { + public static constexpr auto kJavaDescriptor; + public static void registerNatives(); +} + class facebook::react::JDynamicNative : public jni::HybridClass { public JDynamicNative(folly::dynamic payload); public static constexpr auto kJavaDescriptor; From 9f2757c1fb6eea7df89af28b2ef5081186993989 Mon Sep 17 00:00:00 2001 From: Kamil Paradowski Date: Wed, 3 Jun 2026 16:04:55 +0200 Subject: [PATCH 2/5] feat: get iOS compiling --- .../GenerateModuleObjCpp/serializeMethod.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js index ce1fdfef0c6c..9f789ade20a0 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js @@ -51,7 +51,8 @@ type ReturnJSType = | 'ObjectKind' | 'ArrayKind' | 'NumberKind' - | 'StringKind'; + | 'StringKind' + | 'ArrayBufferKind'; export type MethodSerializationOutput = Readonly<{ methodName: string, @@ -219,6 +220,9 @@ function getParamObjCType( */ return notStruct(wrapOptional('NSArray *', !nullable)); } + case 'ArrayBufferTypeAnnotation': { + return notStruct(wrapOptional('NSMutableData *', !nullable)); + } } const [structTypeAnnotation] = unwrapNullable( @@ -388,9 +392,7 @@ function getReturnObjCType( case 'GenericObjectTypeAnnotation': return wrapOptional('NSDictionary *', isRequired); case 'ArrayBufferTypeAnnotation': - throw new Error( - `Unsupported return type for ${methodName}: ArrayBuffer is only supported for C++ TurboModules.`, - ); + return wrapOptional('NSMutableData *', isRequired); default: typeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( @@ -464,9 +466,7 @@ function getReturnJSType( throw new Error(`Unsupported union member types`); } case 'ArrayBufferTypeAnnotation': - throw new Error( - `Unsupported return type for ${methodName}: ArrayBuffer is only supported for C++ TurboModules.`, - ); + return 'ArrayBufferKind'; default: typeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( From ed6642506fa624c9bd13ea919913edcc605f61cd Mon Sep 17 00:00:00 2001 From: Kamil Paradowski Date: Wed, 3 Jun 2026 17:08:56 +0200 Subject: [PATCH 3/5] fix: add missing type --- .../src/generators/modules/GenerateModuleJniCpp.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js index 0ee063f20cd7..977cd1e29c70 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js @@ -35,7 +35,8 @@ type JSReturnType = | 'NumberKind' | 'PromiseKind' | 'ObjectKind' - | 'ArrayKind'; + | 'ArrayKind' + | 'ArrayBufferKind'; const HostFunctionTemplate = ({ hasteModuleName, From 54f0c98499b727ea4737c2f637469e35b6e04ed5 Mon Sep 17 00:00:00 2001 From: Kamil Paradowski Date: Wed, 3 Jun 2026 17:14:50 +0200 Subject: [PATCH 4/5] feat: add C++ API snapshots --- .../api-snapshots/ReactAndroidDebugCxx.api | 23 +++++++++++++++++++ .../api-snapshots/ReactAndroidNewarchCxx.api | 23 +++++++++++++++++++ .../api-snapshots/ReactAndroidReleaseCxx.api | 23 +++++++++++++++++++ .../api-snapshots/ReactAppleDebugCxx.api | 5 ++++ .../api-snapshots/ReactAppleNewarchCxx.api | 5 ++++ .../api-snapshots/ReactAppleReleaseCxx.api | 5 ++++ .../api-snapshots/ReactCommonDebugCxx.api | 1 + .../api-snapshots/ReactCommonNewarchCxx.api | 1 + .../api-snapshots/ReactCommonReleaseCxx.api | 1 + 9 files changed, 87 insertions(+) diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index a4ad2d4c4009..3bc8f53fc128 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -833,7 +833,10 @@ double facebook::react::floor(double value) noexcept; double facebook::react::getCPUTimeNanos(); double facebook::react::interpolate(double inputValue, double inputMin, double inputMax, double outputMin, double outputMax, std::string_view extrapolateLeft, std::string_view extrapolateRight); double facebook::react::round(double value) noexcept; +facebook::jsi::Value facebook::react::createJSRuntimeError(facebook::jsi::Runtime& runtime, const std::string& message); +facebook::jsi::Value facebook::react::createJSRuntimeError(facebook::jsi::Runtime& runtime, facebook::jsi::Value&& message); facebook::jsi::Value facebook::react::createPromiseAsJSIValue(facebook::jsi::Runtime& rt, facebook::react::PromiseSetupFunctionType&& func); +facebook::jsi::Value facebook::react::createRejectionError(facebook::jsi::Runtime& rt, const folly::dynamic& args); facebook::react::Color facebook::react::hostPlatformColorFromComponents(facebook::react::ColorComponents components); facebook::react::Color facebook::react::hostPlatformColorFromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); facebook::react::ColorComponents facebook::react::colorComponentsFromColor(facebook::react::SharedColor color); @@ -2828,6 +2831,14 @@ class facebook::react::JMessageQueueThread : public facebook::react::MessageQueu public virtual void runOnQueueSync(std::function&& runnable) override; } +class facebook::react::JMutableDataBuffer : public facebook::jsi::MutableBuffer { + public JMutableDataBuffer(jni::global_ref byteBuffer) noexcept; + public jni::local_ref getJavaByteBuffer() const; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~JMutableDataBuffer(); +} + class facebook::react::JNativeModulePerfLogger : public jni::HybridClass { public static constexpr auto kJavaDescriptor; public virtual std::unique_ptr get() = 0; @@ -3023,6 +3034,17 @@ class facebook::react::JWritableMapBuffer : public facebook::jni::JavaClass { + public static constexpr auto kJavaDescriptor; + public static facebook::react::JZeroCopyByteBufferHolder::Wrapped wrapMutableBuffer(std::shared_ptr buffer); + public static void registerNatives(); +} + +struct facebook::react::JZeroCopyByteBufferHolder::Wrapped { + public jni::local_ref holder; + public jni::local_ref byteBuffer; +} + class facebook::react::JavaInteropTurboModule : public facebook::react::JavaTurboModule { protected virtual facebook::jsi::Value create(facebook::jsi::Runtime& runtime, const facebook::jsi::PropNameID& propName) override; public JavaInteropTurboModule(const facebook::react::JavaTurboModule::InitParams& params, const std::vector& methodDescriptors); @@ -6591,6 +6613,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index a3c682e1e71b..4bc00c95de88 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -832,7 +832,10 @@ double facebook::react::floor(double value) noexcept; double facebook::react::getCPUTimeNanos(); double facebook::react::interpolate(double inputValue, double inputMin, double inputMax, double outputMin, double outputMax, std::string_view extrapolateLeft, std::string_view extrapolateRight); double facebook::react::round(double value) noexcept; +facebook::jsi::Value facebook::react::createJSRuntimeError(facebook::jsi::Runtime& runtime, const std::string& message); +facebook::jsi::Value facebook::react::createJSRuntimeError(facebook::jsi::Runtime& runtime, facebook::jsi::Value&& message); facebook::jsi::Value facebook::react::createPromiseAsJSIValue(facebook::jsi::Runtime& rt, facebook::react::PromiseSetupFunctionType&& func); +facebook::jsi::Value facebook::react::createRejectionError(facebook::jsi::Runtime& rt, const folly::dynamic& args); facebook::react::Color facebook::react::hostPlatformColorFromComponents(facebook::react::ColorComponents components); facebook::react::Color facebook::react::hostPlatformColorFromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); facebook::react::ColorComponents facebook::react::colorComponentsFromColor(facebook::react::SharedColor color); @@ -2784,6 +2787,14 @@ class facebook::react::JMessageQueueThread : public facebook::react::MessageQueu public virtual void runOnQueueSync(std::function&& runnable) override; } +class facebook::react::JMutableDataBuffer : public facebook::jsi::MutableBuffer { + public JMutableDataBuffer(jni::global_ref byteBuffer) noexcept; + public jni::local_ref getJavaByteBuffer() const; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~JMutableDataBuffer(); +} + class facebook::react::JNativeModulePerfLogger : public jni::HybridClass { public static constexpr auto kJavaDescriptor; public virtual std::unique_ptr get() = 0; @@ -2948,6 +2959,17 @@ class facebook::react::JWritableMapBuffer : public facebook::jni::JavaClass { + public static constexpr auto kJavaDescriptor; + public static facebook::react::JZeroCopyByteBufferHolder::Wrapped wrapMutableBuffer(std::shared_ptr buffer); + public static void registerNatives(); +} + +struct facebook::react::JZeroCopyByteBufferHolder::Wrapped { + public jni::local_ref holder; + public jni::local_ref byteBuffer; +} + class facebook::react::JavaInteropTurboModule : public facebook::react::JavaTurboModule { protected virtual facebook::jsi::Value create(facebook::jsi::Runtime& runtime, const facebook::jsi::PropNameID& propName) override; public JavaInteropTurboModule(const facebook::react::JavaTurboModule::InitParams& params, const std::vector& methodDescriptors); @@ -6417,6 +6439,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 1f86ed2cb4f8..6fadb58a5329 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -833,7 +833,10 @@ double facebook::react::floor(double value) noexcept; double facebook::react::getCPUTimeNanos(); double facebook::react::interpolate(double inputValue, double inputMin, double inputMax, double outputMin, double outputMax, std::string_view extrapolateLeft, std::string_view extrapolateRight); double facebook::react::round(double value) noexcept; +facebook::jsi::Value facebook::react::createJSRuntimeError(facebook::jsi::Runtime& runtime, const std::string& message); +facebook::jsi::Value facebook::react::createJSRuntimeError(facebook::jsi::Runtime& runtime, facebook::jsi::Value&& message); facebook::jsi::Value facebook::react::createPromiseAsJSIValue(facebook::jsi::Runtime& rt, facebook::react::PromiseSetupFunctionType&& func); +facebook::jsi::Value facebook::react::createRejectionError(facebook::jsi::Runtime& rt, const folly::dynamic& args); facebook::react::Color facebook::react::hostPlatformColorFromComponents(facebook::react::ColorComponents components); facebook::react::Color facebook::react::hostPlatformColorFromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); facebook::react::ColorComponents facebook::react::colorComponentsFromColor(facebook::react::SharedColor color); @@ -2825,6 +2828,14 @@ class facebook::react::JMessageQueueThread : public facebook::react::MessageQueu public virtual void runOnQueueSync(std::function&& runnable) override; } +class facebook::react::JMutableDataBuffer : public facebook::jsi::MutableBuffer { + public JMutableDataBuffer(jni::global_ref byteBuffer) noexcept; + public jni::local_ref getJavaByteBuffer() const; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~JMutableDataBuffer(); +} + class facebook::react::JNativeModulePerfLogger : public jni::HybridClass { public static constexpr auto kJavaDescriptor; public virtual std::unique_ptr get() = 0; @@ -3020,6 +3031,17 @@ class facebook::react::JWritableMapBuffer : public facebook::jni::JavaClass { + public static constexpr auto kJavaDescriptor; + public static facebook::react::JZeroCopyByteBufferHolder::Wrapped wrapMutableBuffer(std::shared_ptr buffer); + public static void registerNatives(); +} + +struct facebook::react::JZeroCopyByteBufferHolder::Wrapped { + public jni::local_ref holder; + public jni::local_ref byteBuffer; +} + class facebook::react::JavaInteropTurboModule : public facebook::react::JavaTurboModule { protected virtual facebook::jsi::Value create(facebook::jsi::Runtime& runtime, const facebook::jsi::PropNameID& propName) override; public JavaInteropTurboModule(const facebook::react::JavaTurboModule::InitParams& params, const std::vector& methodDescriptors); @@ -6582,6 +6604,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index b687cb8ed556..a7ff7861b74c 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -2511,6 +2511,8 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual NSDictionary* getObjectThrows:(NSDictionary* arg); public virtual NSDictionary* getUnsafeObject:(NSDictionary* arg); public virtual NSDictionary* getValue:y:z:(double x, NSString* y, NSDictionary* z); + public virtual NSMutableData* createNativeBuffer:(double size); + public virtual NSMutableData* getArrayBuffer:(NSMutableData* buffer); public virtual NSNumber* getBool:(BOOL arg); public virtual NSNumber* getEnum:(double arg); public virtual NSNumber* getNumber:(double arg); @@ -2518,9 +2520,11 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual NSString* getString:(NSString* arg); public virtual facebook::react::ModuleConstants constantsToExport(); public virtual facebook::react::ModuleConstants getConstants(); + public virtual void getAsyncBuffer:resolve:reject:(double size, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void getImageUrl:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void getValueWithCallback:(RCTResponseSenderBlock callback); public virtual void getValueWithPromise:resolve:reject:(BOOL error, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); + public virtual void processAsyncBuffer:resolve:reject:(NSMutableData* payload, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void promiseAssert:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void promiseThrows:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void voidFunc(); @@ -8800,6 +8804,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index c0dd7c1584c6..a66aa5227cb0 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -2499,6 +2499,8 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual NSDictionary* getObjectThrows:(NSDictionary* arg); public virtual NSDictionary* getUnsafeObject:(NSDictionary* arg); public virtual NSDictionary* getValue:y:z:(double x, NSString* y, NSDictionary* z); + public virtual NSMutableData* createNativeBuffer:(double size); + public virtual NSMutableData* getArrayBuffer:(NSMutableData* buffer); public virtual NSNumber* getBool:(BOOL arg); public virtual NSNumber* getEnum:(double arg); public virtual NSNumber* getNumber:(double arg); @@ -2506,9 +2508,11 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual NSString* getString:(NSString* arg); public virtual facebook::react::ModuleConstants constantsToExport(); public virtual facebook::react::ModuleConstants getConstants(); + public virtual void getAsyncBuffer:resolve:reject:(double size, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void getImageUrl:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void getValueWithCallback:(RCTResponseSenderBlock callback); public virtual void getValueWithPromise:resolve:reject:(BOOL error, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); + public virtual void processAsyncBuffer:resolve:reject:(NSMutableData* payload, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void promiseAssert:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void promiseThrows:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void voidFunc(); @@ -8653,6 +8657,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 1a2f6c30e468..c2f41320db3b 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -2511,6 +2511,8 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual NSDictionary* getObjectThrows:(NSDictionary* arg); public virtual NSDictionary* getUnsafeObject:(NSDictionary* arg); public virtual NSDictionary* getValue:y:z:(double x, NSString* y, NSDictionary* z); + public virtual NSMutableData* createNativeBuffer:(double size); + public virtual NSMutableData* getArrayBuffer:(NSMutableData* buffer); public virtual NSNumber* getBool:(BOOL arg); public virtual NSNumber* getEnum:(double arg); public virtual NSNumber* getNumber:(double arg); @@ -2518,9 +2520,11 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual NSString* getString:(NSString* arg); public virtual facebook::react::ModuleConstants constantsToExport(); public virtual facebook::react::ModuleConstants getConstants(); + public virtual void getAsyncBuffer:resolve:reject:(double size, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void getImageUrl:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void getValueWithCallback:(RCTResponseSenderBlock callback); public virtual void getValueWithPromise:resolve:reject:(BOOL error, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); + public virtual void processAsyncBuffer:resolve:reject:(NSMutableData* payload, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void promiseAssert:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void promiseThrows:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void voidFunc(); @@ -8791,6 +8795,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 2e43b23d2fc1..21eec33ba2e0 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -4922,6 +4922,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index e9700e2725df..f9ff4d72ccbe 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -4788,6 +4788,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index bf1acdc6eb1a..94b0edf61d8d 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -4913,6 +4913,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, From 8d3df50b8f4343f05dd6d1ca30987a5b10d94d4f Mon Sep 17 00:00:00 2001 From: Kamil Paradowski Date: Wed, 3 Jun 2026 17:15:44 +0200 Subject: [PATCH 5/5] refactor: simplify byteBuffer creation --- .../src/main/jni/react/jni/JZeroCopyByteBuffer.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JZeroCopyByteBuffer.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JZeroCopyByteBuffer.h index 36cad831824e..1119948c22e0 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JZeroCopyByteBuffer.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JZeroCopyByteBuffer.h @@ -34,14 +34,10 @@ class JZeroCopyByteBufferHolder } static Wrapped wrapMutableBuffer(std::shared_ptr buffer) { - auto byteBuffer = [&]() { - if (auto* javaBacked = dynamic_cast(buffer.get())) { - return javaBacked->getJavaByteBuffer(); - } - auto* mutableBuffer = holder->cthis()->buffer_.get(); - return jni::JByteBuffer::wrapBytes( - mutableBuffer->data(), mutableBuffer->size()); - }(); + auto* javaBacked = dynamic_cast(buffer.get()); + auto byteBuffer = javaBacked + ? javaBacked->getJavaByteBuffer() + : jni::JByteBuffer::wrapBytes(buffer->data(), buffer->size()); auto holder = newObjectCxxArgs(std::move(buffer)); return { std::move(holder),