From 954933bc3e5db0a4adcaa75ffb1fac4bfdd0280f Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Tue, 5 May 2026 16:27:37 -0700 Subject: [PATCH 1/3] Add ArrayBuffer support for React Native TurboModules - copies data between JavaScript and native MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: ## Changelog: [General] [Added] - Add ArrayBuffer support for React Native Turbo Modules https://github.com/react-native-community/discussions-and-proposals/pull/947 Adds first-class `ArrayBuffer` support across the entire TurboModule pipeline, enabling efficient binary data transfer between JavaScript and native code without base64 encoding overhead. **Schema & Parser:** - Added `NativeModuleArrayBufferTypeAnnotation` to `CodegenSchema.js` and `.d.ts` - Added `emitArrayBuffer` to `parsers-primitives.js` type map **C++ (Bridging & Generator):** - Added `ArrayBufferKind` to `TurboModuleMethodValueKind` enum - Created `ArrayBuffer.h` bridging header with `OwnedMutableBuffer` and `Bridging>` - Added `jsi::ArrayBuffer` conversion operators to `Convert.h` - Mapped `ArrayBufferTypeAnnotation` → `jsi::ArrayBuffer` in `GenerateModuleH.js` **Android (Java/JNI):** - Mapped `ArrayBufferTypeAnnotation` → `java.nio.ByteBuffer` in Java spec generator - Mapped to `Ljava/nio/ByteBuffer;` JNI signature in JNI generator - Added `ByteBuffer` arg/return handling in `JavaTurboModule.cpp` via `NewDirectByteBuffer`/`GetDirectBufferAddress` **iOS (ObjC):** - Mapped `ArrayBufferTypeAnnotation` → `NSData *` in ObjC spec generator - Added ArrayBuffer→NSData conversion in `convertJSIValueToObjCObject()` - Added NSData→ArrayBuffer conversion in `convertReturnIdToJSIValue()` **Flow-Schema:** - Added `ArrayBuffer` to `JAVASCRIPT_BUILTINS` in `BoundaryTypes.js` **Tests:** - Added Flow and TypeScript parser fixtures (`NATIVE_MODULE_WITH_ARRAYBUFFER`) - Added generator schema fixture (`ARRAYBUFFER_MODULE`) - Added `emitArrayBuffer` unit tests Differential Revision: D95649873 --- .../src/CodegenSchema.d.ts | 5 ++ .../react-native-codegen/src/CodegenSchema.js | 7 +- .../src/generators/modules/GenerateModuleH.js | 4 ++ .../modules/GenerateModuleJavaSpec.js | 9 +++ .../modules/GenerateModuleJniCpp.js | 9 ++- .../GenerateModuleObjCpp/StructCollector.js | 2 + .../GenerateModuleObjCpp/serializeMethod.js | 10 ++- .../modules/__test_fixtures__/fixtures.js | 69 ++++++++++++++++++ .../GenerateModuleH-test.js.snap | 61 ++++++++++++++++ .../GenerateModuleHObjCpp-test.js.snap | 67 ++++++++++++++++++ .../GenerateModuleJavaSpec-test.js.snap | 53 ++++++++++++++ .../GenerateModuleJniCpp-test.js.snap | 50 +++++++++++++ .../GenerateModuleJniH-test.js.snap | 66 +++++++++++++++++ .../GenerateModuleMm-test.js.snap | 59 ++++++++++++++++ .../__tests__/parsers-primitives-test.js | 27 +++++++ .../modules/__test_fixtures__/fixtures.js | 27 +++++++ .../module-parser-snapshot-test.js.snap | 70 +++++++++++++++++++ .../src/parsers/parsers-primitives.js | 11 +++ .../modules/__test_fixtures__/fixtures.js | 24 +++++++ ...script-module-parser-snapshot-test.js.snap | 70 +++++++++++++++++++ .../src/ErrorFormatting.js | 2 + .../src/SortTypeAnnotations.js | 4 ++ .../src/TypeDiffing.js | 1 + .../ReactCommon/react/bridging/ArrayBuffer.h | 64 +++++++++++++++++ .../ReactCommon/react/bridging/Bridging.h | 1 + .../ReactCommon/react/bridging/Convert.h | 12 ++++ .../react/bridging/tests/BridgingTest.cpp | 29 ++++++++ .../core/ReactCommon/TurboModule.h | 1 + .../android/ReactCommon/JavaTurboModule.cpp | 42 +++++++++++ .../ios/ReactCommon/RCTTurboModule.mm | 13 +++- .../api-snapshots/ReactAndroidDebugCxx.api | 16 +++++ .../api-snapshots/ReactAndroidReleaseCxx.api | 16 +++++ .../api-snapshots/ReactAppleDebugCxx.api | 16 +++++ .../api-snapshots/ReactAppleReleaseCxx.api | 16 +++++ .../api-snapshots/ReactCommonDebugCxx.api | 16 +++++ .../api-snapshots/ReactCommonReleaseCxx.api | 16 +++++ 36 files changed, 961 insertions(+), 4 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h diff --git a/packages/react-native-codegen/src/CodegenSchema.d.ts b/packages/react-native-codegen/src/CodegenSchema.d.ts index a1d7115f2bef..062ac44ad527 100644 --- a/packages/react-native-codegen/src/CodegenSchema.d.ts +++ b/packages/react-native-codegen/src/CodegenSchema.d.ts @@ -397,6 +397,10 @@ export interface NativeModuleMixedTypeAnnotation { readonly type: 'MixedTypeAnnotation'; } +export interface NativeModuleArrayBufferTypeAnnotation { + readonly type: 'ArrayBufferTypeAnnotation'; +} + export type NativeModuleEventEmitterBaseTypeAnnotation = | NativeModuleBooleanTypeAnnotation | NativeModuleDoubleTypeAnnotation @@ -434,6 +438,7 @@ export type NativeModuleBaseTypeAnnotation = | NativeModuleObjectTypeAnnotation | NativeModuleUnionTypeAnnotation | NativeModuleMixedTypeAnnotation + | NativeModuleArrayBufferTypeAnnotation | NativeModuleArrayTypeAnnotation; export type NativeModuleParamTypeAnnotation = diff --git a/packages/react-native-codegen/src/CodegenSchema.js b/packages/react-native-codegen/src/CodegenSchema.js index 90043816eeb5..1afb87c833c3 100644 --- a/packages/react-native-codegen/src/CodegenSchema.js +++ b/packages/react-native-codegen/src/CodegenSchema.js @@ -391,6 +391,10 @@ export type NativeModuleMixedTypeAnnotation = Readonly<{ type: 'MixedTypeAnnotation', }>; +export type NativeModuleArrayBufferTypeAnnotation = Readonly<{ + type: 'ArrayBufferTypeAnnotation', +}>; + type NativeModuleEventEmitterBaseTypeAnnotation = | BooleanTypeAnnotation | DoubleTypeAnnotation @@ -428,7 +432,8 @@ export type NativeModuleBaseTypeAnnotation = | NativeModuleArrayTypeAnnotation> | NativeModuleObjectTypeAnnotation | NativeModuleUnionTypeAnnotation - | NativeModuleMixedTypeAnnotation; + | NativeModuleMixedTypeAnnotation + | NativeModuleArrayBufferTypeAnnotation; export type NativeModuleParamTypeAnnotation = | NativeModuleBaseTypeAnnotation diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js index 388e00e1a27f..af7f00aa3008 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js @@ -146,6 +146,8 @@ function serializeArg( return wrap(val => `${val}.asObject(rt)`); case 'MixedTypeAnnotation': return wrap(val => `jsi::Value(rt, ${val})`); + case 'ArrayBufferTypeAnnotation': + return wrap(val => `${val}.asObject(rt).getArrayBuffer(rt)`); default: realTypeAnnotation.type as empty; throw new Error( @@ -307,6 +309,8 @@ function translatePrimitiveJSTypeToCpp( return wrapOptional('jsi::Value', isRequired); case 'MixedTypeAnnotation': return wrapOptional('jsi::Value', isRequired); + case 'ArrayBufferTypeAnnotation': + return wrapOptional('jsi::ArrayBuffer', isRequired); default: realTypeAnnotation.type as empty; throw new Error(createErrorMessage(realTypeAnnotation.type)); diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js index 98ed68318f83..3f0b4f8f24dc 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js @@ -170,6 +170,7 @@ function translateEventEmitterTypeToJavaType( case 'FloatTypeAnnotation': case 'Int32TypeAnnotation': case 'VoidTypeAnnotation': + case 'ArrayBufferTypeAnnotation': // TODO: Add support for these types throw new Error( `Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`, @@ -267,6 +268,9 @@ function translateFunctionParamToJavaType( case 'FunctionTypeAnnotation': imports.add('com.facebook.react.bridge.Callback'); return wrapOptional('Callback', isRequired); + case 'ArrayBufferTypeAnnotation': + imports.add('java.nio.ByteBuffer'); + return wrapOptional('ByteBuffer', isRequired); default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error(createErrorMessage(realTypeAnnotation.type)); @@ -361,6 +365,9 @@ function translateFunctionReturnTypeToJavaType( case 'ArrayTypeAnnotation': imports.add('com.facebook.react.bridge.WritableArray'); return wrapOptional('WritableArray', isRequired); + case 'ArrayBufferTypeAnnotation': + imports.add('java.nio.ByteBuffer'); + return wrapOptional('ByteBuffer', isRequired); default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error(createErrorMessage(realTypeAnnotation.type)); @@ -443,6 +450,8 @@ function getFalsyReturnStatementFromReturnType( return 'return null;'; case 'ArrayTypeAnnotation': return 'return null;'; + case 'ArrayBufferTypeAnnotation': + 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 55be945773f4..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, @@ -216,6 +217,8 @@ function translateReturnTypeToKind( return 'ObjectKind'; case 'ArrayTypeAnnotation': return 'ArrayKind'; + case 'ArrayBufferTypeAnnotation': + return 'ArrayBufferKind'; default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( @@ -303,6 +306,8 @@ function translateParamTypeToJniType( return 'Lcom/facebook/react/bridge/ReadableArray;'; case 'FunctionTypeAnnotation': return 'Lcom/facebook/react/bridge/Callback;'; + case 'ArrayBufferTypeAnnotation': + return 'Ljava/nio/ByteBuffer;'; default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( @@ -387,6 +392,8 @@ function translateReturnTypeToJniType( return 'Lcom/facebook/react/bridge/WritableMap;'; case 'ArrayTypeAnnotation': return 'Lcom/facebook/react/bridge/WritableArray;'; + case 'ArrayBufferTypeAnnotation': + return 'Ljava/nio/ByteBuffer;'; default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js index 2cc39108680b..21d9cbf7034e 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js @@ -132,6 +132,8 @@ class StructCollector { return wrapNullable(nullable, typeAnnotation); case 'MixedTypeAnnotation': throw new Error('Mixed types are unsupported in structs'); + case 'ArrayBufferTypeAnnotation': + throw new Error('ArrayBuffer types are unsupported in structs'); case 'UnionTypeAnnotation': try { const validUnionType = parseValidUnionType(typeAnnotation); 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 c618b0f78c54..6a28c994244f 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('NSData *', !nullable)); + } } const [structTypeAnnotation] = unwrapNullable( @@ -387,6 +391,8 @@ function getReturnObjCType( } case 'GenericObjectTypeAnnotation': return wrapOptional('NSDictionary *', isRequired); + case 'ArrayBufferTypeAnnotation': + return wrapOptional('NSData *', isRequired); default: typeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( @@ -433,6 +439,8 @@ function getReturnJSType( return 'BooleanKind'; case 'GenericObjectTypeAnnotation': return 'ObjectKind'; + case 'ArrayBufferTypeAnnotation': + return 'ArrayBufferKind'; case 'EnumDeclaration': switch (typeAnnotation.memberType) { case 'NumberTypeAnnotation': 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 6b13eeb9c254..776ec005f787 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 @@ -2792,6 +2792,74 @@ const STRING_LITERALS: SchemaType = { }, }; +const ARRAYBUFFER_MODULE: SchemaType = { + modules: { + NativeSampleTurboModule: { + type: 'NativeModule', + aliasMap: {}, + enumMap: {}, + spec: { + eventEmitters: [], + methods: [ + { + name: 'getArrayBuffer', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'ArrayBufferTypeAnnotation', + }, + params: [ + { + name: 'buffer', + optional: false, + typeAnnotation: { + type: 'ArrayBufferTypeAnnotation', + }, + }, + ], + }, + }, + { + name: 'saveData', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'VoidTypeAnnotation', + }, + params: [ + { + name: 'data', + optional: false, + typeAnnotation: { + type: 'ArrayBufferTypeAnnotation', + }, + }, + ], + }, + }, + { + name: 'loadData', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'NullableTypeAnnotation', + typeAnnotation: { + type: 'ArrayBufferTypeAnnotation', + }, + }, + params: [], + }, + }, + ], + }, + moduleName: 'SampleTurboModule', + }, + }, +}; + module.exports = { complex_objects: COMPLEX_OBJECTS, two_modules_different_files: TWO_MODULES_DIFFERENT_FILES, @@ -2804,4 +2872,5 @@ module.exports = { SampleWithUppercaseName: SAMPLE_WITH_UPPERCASE_NAME, union_module: UNION_MODULE, string_literals: STRING_LITERALS, + arraybuffer_module: ARRAYBUFFER_MODULE, }; diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap index 2feb4a244ae5..8c63891eaaac 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap @@ -38,6 +38,67 @@ private: } `; +exports[`GenerateModuleH can generate fixture arraybuffer_module 1`] = ` +Map { + "arraybuffer_moduleJSI.h" => "/** + * 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: GenerateModuleH.js + */ + +#pragma once + +#include +#include + +namespace facebook::react { + + +template +class JSI_EXPORT NativeSampleTurboModuleCxxSpec : public TurboModule { +public: + static constexpr std::string_view kModuleName = \\"SampleTurboModule\\"; + +protected: + NativeSampleTurboModuleCxxSpec(std::shared_ptr jsInvoker) : TurboModule(std::string{NativeSampleTurboModuleCxxSpec::kModuleName}, jsInvoker) { + methodMap_[\\"getArrayBuffer\\"] = MethodMetadata {.argCount = 1, .invoker = __getArrayBuffer}; + methodMap_[\\"saveData\\"] = MethodMetadata {.argCount = 1, .invoker = __saveData}; + methodMap_[\\"loadData\\"] = MethodMetadata {.argCount = 0, .invoker = __loadData}; + } + +private: + static jsi::Value __getArrayBuffer(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_assert( + bridging::getParameterCount(&T::getArrayBuffer) == 2, + \\"Expected getArrayBuffer(...) to have 2 parameters\\"); + return bridging::callFromJs(rt, &T::getArrayBuffer, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule), + count <= 0 ? throw jsi::JSError(rt, \\"Expected argument in position 0 to be passed\\") : args[0].asObject(rt).getArrayBuffer(rt)); + } + + static jsi::Value __saveData(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_assert( + bridging::getParameterCount(&T::saveData) == 2, + \\"Expected saveData(...) to have 2 parameters\\"); + bridging::callFromJs(rt, &T::saveData, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule), + count <= 0 ? throw jsi::JSError(rt, \\"Expected argument in position 0 to be passed\\") : args[0].asObject(rt).getArrayBuffer(rt));return jsi::Value::undefined(); + } + + static jsi::Value __loadData(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* /*args*/, size_t /*count*/) { + static_assert( + bridging::getParameterCount(&T::loadData) == 1, + \\"Expected loadData(...) to have 1 parameters\\"); + auto result = bridging::callFromJs>(rt, &T::loadData, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule));return result ? jsi::Value(std::move(*result)) : jsi::Value::null(); + } +}; + +} // namespace facebook::react +", +} +`; + exports[`GenerateModuleH can generate fixture complex_objects 1`] = ` Map { "complex_objectsJSI.h" => "/** diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap index c67b27268cf9..de4e61035167 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap @@ -65,6 +65,73 @@ namespace facebook::react { } `; +exports[`GenerateModuleHObjCpp can generate fixture arraybuffer_module 1`] = ` +Map { + "arraybuffer_module.h" => "/** + * 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: GenerateModuleObjCpp + * + * We create an umbrella header (and corresponding implementation) here since + * Cxx compilation in BUCK has a limitation: source-code producing genrule()s + * must have a single output. More files => more genrule()s => slower builds. + */ + +#ifndef __cplusplus +#error This file must be compiled as Obj-C++. If you are importing it, you must change your file extension to .mm. +#endif + +// Avoid multiple includes of arraybuffer_module symbols +#ifndef arraybuffer_module_H +#define arraybuffer_module_H + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + + +@protocol NativeSampleTurboModuleSpec + +- (NSData *)getArrayBuffer:(NSData *)buffer; +- (void)saveData:(NSData *)data; +- (NSData * _Nullable)loadData; + +@end + +@interface NativeSampleTurboModuleSpecBase : NSObject { +@protected +facebook::react::EventEmitterCallback _eventEmitterCallback; +} +- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper; + + +@end + +namespace facebook::react { + /** + * ObjC++ class for module 'NativeSampleTurboModule' + */ + class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public ObjCTurboModule { + public: + NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms); + }; +} // namespace facebook::react + +#endif // arraybuffer_module_H +", +} +`; + exports[`GenerateModuleHObjCpp can generate fixture complex_objects 1`] = ` Map { "complex_objects.h" => "/** 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 4bf92d2761df..fe88c6010176 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,6 +41,59 @@ public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaMo } `; +exports[`GenerateModuleJavaSpec can generate fixture arraybuffer_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.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(ByteBuffer buffer); + + @ReactMethod + @DoNotStrip + public abstract void saveData(ByteBuffer data); + + @ReactMethod(isBlockingSynchronousMethod = true) + @DoNotStrip + public abstract @Nullable ByteBuffer loadData(); +} +", +} +`; + exports[`GenerateModuleJavaSpec can generate fixture complex_objects 1`] = ` Map { "java/com/facebook/fbreact/specs/NativeSampleTurboModuleSpec.java" => " 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 38fbd875e56b..0430ff00552e 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 @@ -35,6 +35,56 @@ std::shared_ptr SampleWithUppercaseName_ModuleProvider(const std::s } `; +exports[`GenerateModuleJniCpp can generate fixture arraybuffer_module 1`] = ` +Map { + "jni/arraybuffer_module-generated.cpp" => " +/** + * 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: GenerateModuleJniCpp.js + */ + +#include \\"arraybuffer_module.h\\" + +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;)Ljava/nio/ByteBuffer;\\", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_saveData(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, \\"saveData\\", \\"(Ljava/nio/ByteBuffer;)V\\", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_loadData(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, \\"loadData\\", \\"()Ljava/nio/ByteBuffer;\\", args, count, cachedMethodId); +} + +NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms) + : JavaTurboModule(params) { + methodMap_[\\"getArrayBuffer\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer}; + methodMap_[\\"saveData\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_saveData}; + methodMap_[\\"loadData\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_loadData}; +} + +std::shared_ptr arraybuffer_module_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) { + if (moduleName == \\"SampleTurboModule\\") { + return std::make_shared(params); + } + return nullptr; +} + +} // namespace facebook::react +", +} +`; + exports[`GenerateModuleJniCpp can generate fixture complex_objects 1`] = ` Map { "jni/complex_objects-generated.cpp" => " 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 a45028176b14..07d715e39157 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 @@ -66,6 +66,72 @@ target_compile_reactnative_options(react_codegen_SampleWithUppercaseName PRIVATE } `; +exports[`GenerateModuleJniH can generate fixture arraybuffer_module 1`] = ` +Map { + "jni/arraybuffer_module.h" => " +/** + * 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: GenerateModuleJniH.js + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +/** + * JNI C++ class for module 'NativeSampleTurboModule' + */ +class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public JavaTurboModule { +public: + NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms); +}; + + +JSI_EXPORT +std::shared_ptr arraybuffer_module_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms); + +} // namespace facebook::react +", + "jni/CMakeLists.txt" => "# 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. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/arraybuffer_module/*.cpp) + +add_library( + react_codegen_arraybuffer_module + OBJECT + \${react_codegen_SRCS} +) + +target_include_directories(react_codegen_arraybuffer_module PUBLIC . react/renderer/components/arraybuffer_module) + +target_link_libraries( + react_codegen_arraybuffer_module + fbjni + jsi + # We need to link different libraries based on whether we are building rncore or not, that's necessary + # because we want to break a circular dependency between react_codegen_rncore and reactnative + reactnative +) + +target_compile_reactnative_options(react_codegen_arraybuffer_module PRIVATE) +", +} +`; + exports[`GenerateModuleJniH can generate fixture complex_objects 1`] = ` Map { "jni/complex_objects.h" => " diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap index fb6f5d7fbb35..f7f5af9e58bb 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap @@ -40,6 +40,65 @@ namespace facebook::react { } `; +exports[`GenerateModuleMm can generate fixture arraybuffer_module 1`] = ` +Map { + "arraybuffer_module-generated.mm" => "/** + * 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: GenerateModuleObjCpp + * + * We create an umbrella header (and corresponding implementation) here since + * Cxx compilation in BUCK has a limitation: source-code producing genrule()s + * must have a single output. More files => more genrule()s => slower builds. + */ + +#import \\"arraybuffer_module.h\\" + + +@implementation NativeSampleTurboModuleSpecBase + + +- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper +{ + _eventEmitterCallback = std::move(eventEmitterCallbackWrapper->_eventEmitterCallback); +} +@end + + +namespace facebook::react { + + static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, ArrayBufferKind, \\"getArrayBuffer\\", @selector(getArrayBuffer:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_saveData(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, \\"saveData\\", @selector(saveData:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_loadData(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, ArrayBufferKind, \\"loadData\\", @selector(loadData), args, count); + } + + NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms) + : ObjCTurboModule(params) { + + methodMap_[\\"getArrayBuffer\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer}; + + + methodMap_[\\"saveData\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_saveData}; + + + methodMap_[\\"loadData\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_loadData}; + + } +} // namespace facebook::react +", +} +`; + exports[`GenerateModuleMm can generate fixture complex_objects 1`] = ` Map { "complex_objects-generated.mm" => "/** diff --git a/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js b/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js index 0b73b6058ee2..9d712f73ceb1 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js @@ -18,6 +18,7 @@ const {MockedParser} = require('../parserMock'); const {emitUnion} = require('../parsers-primitives'); const { Visitor, + emitArrayBuffer, emitArrayType, emitBoolean, emitBoolProp, @@ -828,6 +829,32 @@ describe('emitMixed', () => { }); }); +describe('emitArrayBuffer', () => { + describe('when nullable is true', () => { + it('returns nullable type annotation', () => { + const result = emitArrayBuffer(true); + const expected = { + type: 'NullableTypeAnnotation', + typeAnnotation: { + type: 'ArrayBufferTypeAnnotation', + }, + }; + + expect(result).toEqual(expected); + }); + }); + describe('when nullable is false', () => { + it('returns non nullable type annotation', () => { + const result = emitArrayBuffer(false); + const expected = { + type: 'ArrayBufferTypeAnnotation', + }; + + expect(result).toEqual(expected); + }); + }); +}); + describe('emitUnion', () => { const hasteModuleName = 'SampleTurboModule'; diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js index 9edf171d7b73..4067194a0fd0 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js @@ -985,6 +985,32 @@ export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); `; +const NATIVE_MODULE_WITH_ARRAYBUFFER = ` +/** + * 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. + * + * @flow strict-local + * @format + */ + +'use strict'; + +import type {TurboModule} from '../RCTExport'; +import * as TurboModuleRegistry from '../TurboModuleRegistry'; + +export interface Spec extends TurboModule { + +getArrayBuffer: (buffer: ArrayBuffer) => ArrayBuffer; + +saveData: (data: ArrayBuffer) => void; + +loadData: () => ?ArrayBuffer; +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); + +`; + module.exports = { NATIVE_MODULE_WITH_OBJECT_WITH_OBJECT_DEFINED_IN_FILE_AS_PROPERTY, NATIVE_MODULE_WITH_ARRAY_WITH_UNION_AND_TOUPLE, @@ -1018,4 +1044,5 @@ module.exports = { NAMESPACED_NATIVE_MODULE_WITH_FLOAT_AND_INT32, NAMESPACED_NATIVE_MODULE_WITH_UNSAFE_OBJECT, NAMESPACED_NATIVE_MODULE_WITH_EVENT_EMITTERS, + NATIVE_MODULE_WITH_ARRAYBUFFER, }; diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap index a8e8707e8865..2aaf46b158ab 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap +++ b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap @@ -1207,6 +1207,76 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ARRAY_WI }" `; +exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ARRAYBUFFER 1`] = ` +"{ + 'modules': { + 'NativeSampleTurboModule': { + 'type': 'NativeModule', + 'aliasMap': {}, + 'enumMap': {}, + 'spec': { + 'eventEmitters': [], + 'methods': [ + { + 'name': 'getArrayBuffer', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + }, + 'params': [ + { + 'name': 'buffer', + 'optional': false, + 'typeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + } + } + ] + } + }, + { + 'name': 'saveData', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'VoidTypeAnnotation' + }, + 'params': [ + { + 'name': 'data', + 'optional': false, + 'typeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + } + } + ] + } + }, + { + 'name': 'loadData', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'NullableTypeAnnotation', + 'typeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + } + }, + 'params': [] + } + } + ] + }, + 'moduleName': 'SampleTurboModule' + } + } +}" +`; + exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_BASIC_ARRAY 1`] = ` "{ 'modules': { diff --git a/packages/react-native-codegen/src/parsers/parsers-primitives.js b/packages/react-native-codegen/src/parsers/parsers-primitives.js index 3243a9a95cfd..d6bfadb03c35 100644 --- a/packages/react-native-codegen/src/parsers/parsers-primitives.js +++ b/packages/react-native-codegen/src/parsers/parsers-primitives.js @@ -19,6 +19,7 @@ import type { Int32TypeAnnotation, NamedShape, NativeModuleAliasMap, + NativeModuleArrayBufferTypeAnnotation, NativeModuleBaseTypeAnnotation, NativeModuleEnumDeclaration, NativeModuleEnumMap, @@ -168,6 +169,14 @@ function emitMixed( }); } +function emitArrayBuffer( + nullable: boolean, +): Nullable { + return wrapNullable(nullable, { + type: 'ArrayBufferTypeAnnotation', + }); +} + function emitNumberLiteral( nullable: boolean, value: number, @@ -666,6 +675,7 @@ function emitCommonTypes( UnsafeMixed: cxxOnly ? emitMixed : emitGenericObject, unknown: cxxOnly ? emitMixed : emitGenericObject, UnknownTypeAnnotation: cxxOnly ? emitMixed : emitGenericObject, + ArrayBuffer: emitArrayBuffer, }; const typeAnnotationName = parser.convertKeywordToTypeAnnotation( @@ -789,6 +799,7 @@ module.exports = { emitStringProp, emitStringLiteral, emitMixed, + emitArrayBuffer, emitUnion, emitPartial, emitCommonTypes, diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js index 9f10cc5b5aa6..0f0f92afe2f0 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js @@ -995,6 +995,29 @@ export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); `; +const NATIVE_MODULE_WITH_ARRAYBUFFER = ` +/** + * 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. + * + * @format + */ + +import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; +import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; + +export interface Spec extends TurboModule { + readonly getArrayBuffer: (buffer: ArrayBuffer) => ArrayBuffer; + readonly saveData: (data: ArrayBuffer) => void; + readonly loadData: () => ArrayBuffer | null | undefined; +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); + +`; + module.exports = { NATIVE_MODULE_WITH_OBJECT_WITH_OBJECT_DEFINED_IN_FILE_AS_PROPERTY, NATIVE_MODULE_WITH_ARRAY_WITH_UNION_AND_TOUPLE, @@ -1032,4 +1055,5 @@ module.exports = { NAMESPACED_NATIVE_MODULE_WITH_FLOAT_AND_INT32, NAMESPACED_NATIVE_MODULE_WITH_UNSAFE_OBJECT, NAMESPACED_NATIVE_MODULE_WITH_EVENT_EMITTERS, + NATIVE_MODULE_WITH_ARRAYBUFFER, }; diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap b/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap index 3f85279cfa61..57b681212763 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap +++ b/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap @@ -1291,6 +1291,76 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_AR }" `; +exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_ARRAYBUFFER 1`] = ` +"{ + 'modules': { + 'NativeSampleTurboModule': { + 'type': 'NativeModule', + 'aliasMap': {}, + 'enumMap': {}, + 'spec': { + 'eventEmitters': [], + 'methods': [ + { + 'name': 'getArrayBuffer', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + }, + 'params': [ + { + 'name': 'buffer', + 'optional': false, + 'typeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + } + } + ] + } + }, + { + 'name': 'saveData', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'VoidTypeAnnotation' + }, + 'params': [ + { + 'name': 'data', + 'optional': false, + 'typeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + } + } + ] + } + }, + { + 'name': 'loadData', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'NullableTypeAnnotation', + 'typeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + } + }, + 'params': [] + } + } + ] + }, + 'moduleName': 'SampleTurboModule' + } + } +}" +`; + exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_BASIC_ARRAY 1`] = ` "{ 'modules': { diff --git a/packages/react-native-compatibility-check/src/ErrorFormatting.js b/packages/react-native-compatibility-check/src/ErrorFormatting.js index 8f7fb95770d3..640aa26a83e7 100644 --- a/packages/react-native-compatibility-check/src/ErrorFormatting.js +++ b/packages/react-native-compatibility-check/src/ErrorFormatting.js @@ -100,6 +100,8 @@ function formatTypeAnnotation(annotation: CompleteTypeAnnotation): string { switch (annotation.type) { case 'AnyTypeAnnotation': return 'any'; + case 'ArrayBufferTypeAnnotation': + return 'ArrayBuffer'; case 'ArrayTypeAnnotation': return 'Array<' + formatTypeAnnotation(annotation.elementType) + '>'; case 'BooleanTypeAnnotation': diff --git a/packages/react-native-compatibility-check/src/SortTypeAnnotations.js b/packages/react-native-compatibility-check/src/SortTypeAnnotations.js index 81b2e425f861..635b31586e00 100644 --- a/packages/react-native-compatibility-check/src/SortTypeAnnotations.js +++ b/packages/react-native-compatibility-check/src/SortTypeAnnotations.js @@ -157,6 +157,8 @@ export function compareTypeAnnotationForSorting( return typeA.name.localeCompare(typeB.name); case 'MixedTypeAnnotation': return 0; + case 'ArrayBufferTypeAnnotation': + return 0; default: typeA.type as empty; return -1; @@ -281,6 +283,8 @@ function typeAnnotationArbitraryOrder(annotation: CompleteTypeAnnotation) { return 28; case 'UnionTypeAnnotation': return 30; + case 'ArrayBufferTypeAnnotation': + return 31; default: annotation.type as empty; return -1; diff --git a/packages/react-native-compatibility-check/src/TypeDiffing.js b/packages/react-native-compatibility-check/src/TypeDiffing.js index bdc081aeeef7..1aae84552996 100644 --- a/packages/react-native-compatibility-check/src/TypeDiffing.js +++ b/packages/react-native-compatibility-check/src/TypeDiffing.js @@ -183,6 +183,7 @@ export function compareTypeAnnotation( case 'NumberTypeAnnotation': case 'StringTypeAnnotation': case 'VoidTypeAnnotation': + case 'ArrayBufferTypeAnnotation': return {status: 'matching'}; case 'ArrayTypeAnnotation': invariant(olderAnnotation.type === 'ArrayTypeAnnotation', EQUALITY_MSG); diff --git a/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h b/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h new file mode 100644 index 000000000000..8c67a7e42233 --- /dev/null +++ b/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h @@ -0,0 +1,64 @@ +/* + * 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 { + +/** + * A concrete implementation of jsi::MutableBuffer that owns a copy of the data. + * Used when creating jsi::ArrayBuffer from native data (e.g., NSData, + * ByteBuffer) where we need to copy the data to ensure proper memory + * ownership. + */ +class OwnedMutableBuffer : public jsi::MutableBuffer { + public: + OwnedMutableBuffer(const uint8_t *data, size_t size) : data_(data, std::next(data, static_cast(size))) + { + } + + explicit OwnedMutableBuffer(size_t size) : data_(size, 0) {} + + explicit OwnedMutableBuffer(std::vector data) : data_(std::move(data)) {} + + size_t size() const override + { + return data_.size(); + } + + uint8_t *data() override + { + return data_.data(); + } + + private: + std::vector data_; +}; + +template <> +struct Bridging> { + static std::vector fromJs(jsi::Runtime &rt, const jsi::ArrayBuffer &buffer) + { + auto size = buffer.size(rt); + auto data = buffer.data(rt); + return {data, std::next(data, static_cast(size))}; + } + + static jsi::ArrayBuffer toJs(jsi::Runtime &rt, const std::vector &data) + { + auto buffer = std::make_shared(data.data(), data.size()); + return {rt, std::move(buffer)}; + } +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/bridging/Bridging.h b/packages/react-native/ReactCommon/react/bridging/Bridging.h index 558d91335f6c..7507c8ae3048 100644 --- a/packages/react-native/ReactCommon/react/bridging/Bridging.h +++ b/packages/react-native/ReactCommon/react/bridging/Bridging.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include diff --git a/packages/react-native/ReactCommon/react/bridging/Convert.h b/packages/react-native/ReactCommon/react/bridging/Convert.h index 51622c555ca2..1b85d40385b5 100644 --- a/packages/react-native/ReactCommon/react/bridging/Convert.h +++ b/packages/react-native/ReactCommon/react/bridging/Convert.h @@ -63,6 +63,8 @@ struct ConverterBase { return std::move(value).getObject(rt_).getArray(rt_); } else if constexpr (std::is_same_v) { return std::move(value).getObject(rt_).getFunction(rt_); + } else if constexpr (std::is_same_v) { + return std::move(value).getObject(rt_).getArrayBuffer(rt_); } } else { return std::move(value_); @@ -117,6 +119,11 @@ struct Converter : public ConverterBase { { return std::move(value_).asObject(rt_).asFunction(rt_); } + + operator jsi::ArrayBuffer() && + { + return std::move(value_).asObject(rt_).getArrayBuffer(rt_); + } }; template <> @@ -132,6 +139,11 @@ struct Converter : public ConverterBase { { return std::move(value_).asFunction(rt_); } + + operator jsi::ArrayBuffer() && + { + return std::move(value_).getArrayBuffer(rt_); + } }; template diff --git a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp index 8faa39791316..bb9bf344aa19 100644 --- a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp +++ b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp @@ -780,6 +780,35 @@ TEST_F(BridgingTest, dynamicTest) { EXPECT_TRUE(undefinedFromJsResult.isNull()); } +TEST_F(BridgingTest, arrayBufferTest) { + // Test round-trip: vector -> ArrayBuffer -> vector + auto vec = std::vector({1, 2, 3, 4, 5}); + auto arrayBuffer = bridging::toJs(rt, vec, invoker); + auto result = + bridging::fromJs>(rt, arrayBuffer, invoker); + EXPECT_EQ(vec, result); + + // Test empty vector round-trip + auto emptyVec = std::vector(); + auto emptyArrayBuffer = bridging::toJs(rt, emptyVec, invoker); + auto emptyResult = + bridging::fromJs>(rt, emptyArrayBuffer, invoker); + EXPECT_TRUE(emptyResult.empty()); + + // Test OwnedMutableBuffer constructed from size + auto sizedBuffer = OwnedMutableBuffer(4); + EXPECT_EQ(4, sizedBuffer.size()); + // Should be zero-initialized + auto expected_zeros = std::vector(4, 0); + EXPECT_EQ(0, std::memcmp(sizedBuffer.data(), expected_zeros.data(), 4)); + + // Test OwnedMutableBuffer constructed from vector + auto inputVec = std::vector({10, 20, 30}); + auto vecBuffer = OwnedMutableBuffer(inputVec); + EXPECT_EQ(3, vecBuffer.size()); + EXPECT_EQ(0, std::memcmp(vecBuffer.data(), inputVec.data(), inputVec.size())); +} + TEST_F(BridgingTest, highResTimeStampTest) { HighResTimeStamp timestamp = HighResTimeStamp::now(); EXPECT_EQ( 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..4626a550c17e 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..dcc065969fe6 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 @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -434,6 +435,19 @@ 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()) { + throw JavaTurboModuleArgumentConversionException( + "ArrayBuffer", static_cast(argIndex), methodName, arg, &rt); + } + auto arrayBuffer = arg->getObject(rt).getArrayBuffer(rt); + auto data = arrayBuffer.data(rt); + auto size = arrayBuffer.size(rt); + // Create a direct ByteBuffer wrapping the ArrayBuffer's memory. + // Safe because the JS ArrayBuffer is alive for the duration of + // this synchronous call. + auto byteBuffer = jni::JByteBuffer::wrapBytes(data, size); + jarg->l = makeGlobalIfNecessary(byteBuffer.release()); } else { throw JavaTurboModuleInvalidArgumentTypeException( type, argIndex, methodName); @@ -963,6 +977,34 @@ jsi::Value JavaTurboModule::invokeJavaMethod( TMPL::asyncMethodCallEnd(moduleName, methodName); return jsPromise; } + case ArrayBufferKind: { + auto returnObject = + 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)); + auto size = jByteBuffer->getDirectSize(); + + if (size > 0) { + // Copy the data from the ByteBuffer into a new ArrayBuffer. + // We must copy because the ByteBuffer's lifetime is managed by Java. + auto buffer = std::make_shared( + jByteBuffer->getDirectBytes(), static_cast(size)); + auto arrayBuffer = jsi::ArrayBuffer(runtime, std::move(buffer)); + returnValue = jsi::Value(runtime, arrayBuffer); + } + } + + 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/core/platform/ios/ReactCommon/RCTTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm index a1111f9c8565..80d94b2e789b 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm @@ -195,6 +195,10 @@ id convertJSIValueToObjCObject( if (o.isFunction(runtime)) { return convertJSIFunctionToCallback(runtime, o.getFunction(runtime), jsInvoker); } + if (o.isArrayBuffer(runtime)) { + auto ab = o.getArrayBuffer(runtime); + return [NSData dataWithBytes:ab.data(runtime) length:ab.size(runtime)]; + } return convertJSIObjectToNSDictionary(runtime, o, jsInvoker, useNSNull); } @@ -526,6 +530,12 @@ TraceSection s( throw jsi::JSError(runtime, "convertReturnIdToJSIValue: FunctionKind is not supported yet."); case PromiseKind: throw jsi::JSError(runtime, "convertReturnIdToJSIValue: PromiseKind wasn't handled properly."); + case ArrayBufferKind: { + auto data = (NSData *)result; + auto buffer = std::make_shared(static_cast(data.bytes), data.length); + returnValue = jsi::ArrayBuffer(runtime, std::move(buffer)); + break; + } } return returnValue; @@ -813,7 +823,8 @@ TraceSection s( case StringKind: case ObjectKind: case ArrayKind: - case FunctionKind: { + case FunctionKind: + case ArrayBufferKind: { id result = performMethodInvocation(runtime, true, methodName, inv, retainedObjectsForInvocation); TurboModulePerfLogger::syncMethodCallReturnConversionStart(moduleName, methodName); returnValue = convertReturnIdToJSIValue(runtime, methodName, returnType, result); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 8895f9a22552..be1c79d2dfe0 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -3909,6 +3909,14 @@ class facebook::react::OperatorAnimatedNode : public facebook::react::ValueAnima public OperatorAnimatedNode(facebook::react::Tag tag, const folly::dynamic& config, facebook::react::NativeAnimatedNodesManager& manager); } +class facebook::react::OwnedMutableBuffer : public facebook::jsi::MutableBuffer { + public OwnedMutableBuffer(const uint8_t* data, size_t size); + public OwnedMutableBuffer(size_t size); + public OwnedMutableBuffer(std::vector data); + public virtual size_t size() const override; + public virtual uint8_t* data() override; +} + class facebook::react::ParagraphAttributes : public facebook::react::DebugStringConvertible { public bool adjustsFontSizeToFit; public bool includeFontPadding; @@ -6534,6 +6542,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -9458,6 +9467,11 @@ struct facebook::react::Bridging { public static facebook::jsi::String toJs(facebook::jsi::Runtime& rt, std::string_view value); } +struct facebook::react::Bridging> { + public static facebook::jsi::ArrayBuffer toJs(facebook::jsi::Runtime& rt, const std::vector& data); + public static std::vector fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); +} + template struct facebook::react::Bridging> { public static constexpr size_t kArgumentCount; @@ -11547,11 +11561,13 @@ struct facebook::react::bridging::is_optional> : public std::tr struct facebook::react::bridging::Converter : public facebook::react::bridging::ConverterBase { public operator facebook::jsi::Array(); + public operator facebook::jsi::ArrayBuffer(); public operator facebook::jsi::Function(); } struct facebook::react::bridging::Converter : public facebook::react::bridging::ConverterBase { public operator facebook::jsi::Array(); + public operator facebook::jsi::ArrayBuffer(); public operator facebook::jsi::Function(); public operator facebook::jsi::Object(); public operator facebook::jsi::String(); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index e82d7dfbbeea..e919a48fd8a9 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -3906,6 +3906,14 @@ class facebook::react::OperatorAnimatedNode : public facebook::react::ValueAnima public OperatorAnimatedNode(facebook::react::Tag tag, const folly::dynamic& config, facebook::react::NativeAnimatedNodesManager& manager); } +class facebook::react::OwnedMutableBuffer : public facebook::jsi::MutableBuffer { + public OwnedMutableBuffer(const uint8_t* data, size_t size); + public OwnedMutableBuffer(size_t size); + public OwnedMutableBuffer(std::vector data); + public virtual size_t size() const override; + public virtual uint8_t* data() override; +} + class facebook::react::ParagraphAttributes : public facebook::react::DebugStringConvertible { public bool adjustsFontSizeToFit; public bool includeFontPadding; @@ -6525,6 +6533,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -9314,6 +9323,11 @@ struct facebook::react::Bridging { public static facebook::jsi::String toJs(facebook::jsi::Runtime& rt, std::string_view value); } +struct facebook::react::Bridging> { + public static facebook::jsi::ArrayBuffer toJs(facebook::jsi::Runtime& rt, const std::vector& data); + public static std::vector fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); +} + template struct facebook::react::Bridging> { public static constexpr size_t kArgumentCount; @@ -11403,11 +11417,13 @@ struct facebook::react::bridging::is_optional> : public std::tr struct facebook::react::bridging::Converter : public facebook::react::bridging::ConverterBase { public operator facebook::jsi::Array(); + public operator facebook::jsi::ArrayBuffer(); public operator facebook::jsi::Function(); } struct facebook::react::bridging::Converter : public facebook::react::bridging::ConverterBase { public operator facebook::jsi::Array(); + public operator facebook::jsi::ArrayBuffer(); public operator facebook::jsi::Function(); public operator facebook::jsi::Object(); public operator facebook::jsi::String(); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 62d88ff75464..7cb05c0ab06a 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -6483,6 +6483,14 @@ class facebook::react::OperatorAnimatedNode : public facebook::react::ValueAnima public OperatorAnimatedNode(facebook::react::Tag tag, const folly::dynamic& config, facebook::react::NativeAnimatedNodesManager& manager); } +class facebook::react::OwnedMutableBuffer : public facebook::jsi::MutableBuffer { + public OwnedMutableBuffer(const uint8_t* data, size_t size); + public OwnedMutableBuffer(size_t size); + public OwnedMutableBuffer(std::vector data); + public virtual size_t size() const override; + public virtual uint8_t* data() override; +} + class facebook::react::ParagraphAttributes : public facebook::react::DebugStringConvertible { public bool adjustsFontSizeToFit; public bool includeFontPadding; @@ -9109,6 +9117,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -11747,6 +11756,11 @@ struct facebook::react::Bridging { public static facebook::jsi::String toJs(facebook::jsi::Runtime& rt, std::string_view value); } +struct facebook::react::Bridging> { + public static facebook::jsi::ArrayBuffer toJs(facebook::jsi::Runtime& rt, const std::vector& data); + public static std::vector fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); +} + template struct facebook::react::Bridging> { public static constexpr size_t kArgumentCount; @@ -13765,11 +13779,13 @@ struct facebook::react::bridging::is_optional> : public std::tr struct facebook::react::bridging::Converter : public facebook::react::bridging::ConverterBase { public operator facebook::jsi::Array(); + public operator facebook::jsi::ArrayBuffer(); public operator facebook::jsi::Function(); } struct facebook::react::bridging::Converter : public facebook::react::bridging::ConverterBase { public operator facebook::jsi::Array(); + public operator facebook::jsi::ArrayBuffer(); public operator facebook::jsi::Function(); public operator facebook::jsi::Object(); public operator facebook::jsi::String(); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index b8feb8d7d212..ae5960a41d98 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -6480,6 +6480,14 @@ class facebook::react::OperatorAnimatedNode : public facebook::react::ValueAnima public OperatorAnimatedNode(facebook::react::Tag tag, const folly::dynamic& config, facebook::react::NativeAnimatedNodesManager& manager); } +class facebook::react::OwnedMutableBuffer : public facebook::jsi::MutableBuffer { + public OwnedMutableBuffer(const uint8_t* data, size_t size); + public OwnedMutableBuffer(size_t size); + public OwnedMutableBuffer(std::vector data); + public virtual size_t size() const override; + public virtual uint8_t* data() override; +} + class facebook::react::ParagraphAttributes : public facebook::react::DebugStringConvertible { public bool adjustsFontSizeToFit; public bool includeFontPadding; @@ -9100,6 +9108,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -11613,6 +11622,11 @@ struct facebook::react::Bridging { public static facebook::jsi::String toJs(facebook::jsi::Runtime& rt, std::string_view value); } +struct facebook::react::Bridging> { + public static facebook::jsi::ArrayBuffer toJs(facebook::jsi::Runtime& rt, const std::vector& data); + public static std::vector fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); +} + template struct facebook::react::Bridging> { public static constexpr size_t kArgumentCount; @@ -13631,11 +13645,13 @@ struct facebook::react::bridging::is_optional> : public std::tr struct facebook::react::bridging::Converter : public facebook::react::bridging::ConverterBase { public operator facebook::jsi::Array(); + public operator facebook::jsi::ArrayBuffer(); public operator facebook::jsi::Function(); } struct facebook::react::bridging::Converter : public facebook::react::bridging::ConverterBase { public operator facebook::jsi::Array(); + public operator facebook::jsi::ArrayBuffer(); public operator facebook::jsi::Function(); public operator facebook::jsi::Object(); public operator facebook::jsi::String(); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 5f8fc0c410be..e9c60a53157c 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -2546,6 +2546,14 @@ class facebook::react::OperatorAnimatedNode : public facebook::react::ValueAnima public OperatorAnimatedNode(facebook::react::Tag tag, const folly::dynamic& config, facebook::react::NativeAnimatedNodesManager& manager); } +class facebook::react::OwnedMutableBuffer : public facebook::jsi::MutableBuffer { + public OwnedMutableBuffer(const uint8_t* data, size_t size); + public OwnedMutableBuffer(size_t size); + public OwnedMutableBuffer(std::vector data); + public virtual size_t size() const override; + public virtual uint8_t* data() override; +} + class facebook::react::ParagraphAttributes : public facebook::react::DebugStringConvertible { public Float maximumFontSize; public Float minimumFontScale; @@ -4885,6 +4893,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -6574,6 +6583,11 @@ struct facebook::react::Bridging { public static facebook::jsi::String toJs(facebook::jsi::Runtime& rt, std::string_view value); } +struct facebook::react::Bridging> { + public static facebook::jsi::ArrayBuffer toJs(facebook::jsi::Runtime& rt, const std::vector& data); + public static std::vector fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); +} + template struct facebook::react::Bridging> { public static constexpr size_t kArgumentCount; @@ -8585,11 +8599,13 @@ struct facebook::react::bridging::is_optional> : public std::tr struct facebook::react::bridging::Converter : public facebook::react::bridging::ConverterBase { public operator facebook::jsi::Array(); + public operator facebook::jsi::ArrayBuffer(); public operator facebook::jsi::Function(); } struct facebook::react::bridging::Converter : public facebook::react::bridging::ConverterBase { public operator facebook::jsi::Array(); + public operator facebook::jsi::ArrayBuffer(); public operator facebook::jsi::Function(); public operator facebook::jsi::Object(); public operator facebook::jsi::String(); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 727e60d3be74..7659dbe2a2cf 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -2543,6 +2543,14 @@ class facebook::react::OperatorAnimatedNode : public facebook::react::ValueAnima public OperatorAnimatedNode(facebook::react::Tag tag, const folly::dynamic& config, facebook::react::NativeAnimatedNodesManager& manager); } +class facebook::react::OwnedMutableBuffer : public facebook::jsi::MutableBuffer { + public OwnedMutableBuffer(const uint8_t* data, size_t size); + public OwnedMutableBuffer(size_t size); + public OwnedMutableBuffer(std::vector data); + public virtual size_t size() const override; + public virtual uint8_t* data() override; +} + class facebook::react::ParagraphAttributes : public facebook::react::DebugStringConvertible { public Float maximumFontSize; public Float minimumFontScale; @@ -4876,6 +4884,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -6565,6 +6574,11 @@ struct facebook::react::Bridging { public static facebook::jsi::String toJs(facebook::jsi::Runtime& rt, std::string_view value); } +struct facebook::react::Bridging> { + public static facebook::jsi::ArrayBuffer toJs(facebook::jsi::Runtime& rt, const std::vector& data); + public static std::vector fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); +} + template struct facebook::react::Bridging> { public static constexpr size_t kArgumentCount; @@ -8576,11 +8590,13 @@ struct facebook::react::bridging::is_optional> : public std::tr struct facebook::react::bridging::Converter : public facebook::react::bridging::ConverterBase { public operator facebook::jsi::Array(); + public operator facebook::jsi::ArrayBuffer(); public operator facebook::jsi::Function(); } struct facebook::react::bridging::Converter : public facebook::react::bridging::ConverterBase { public operator facebook::jsi::Array(); + public operator facebook::jsi::ArrayBuffer(); public operator facebook::jsi::Function(); public operator facebook::jsi::Object(); public operator facebook::jsi::String(); From cbec9de309af6447165239ec66e8c97f979f53c3 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Tue, 5 May 2026 16:27:37 -0700 Subject: [PATCH 2/3] Add ArrayBuffer sample methods to TurboModule examples Summary: ## Changelog: [General] [Added] - Add ArrayBuffer sample methods to TurboModule examples https://github.com/react-native-community/discussions-and-proposals/pull/947 Add `getArrayBuffer` method to NativeCxxModuleExample and NativeSampleTurboModule specs to demonstrate and validate ArrayBuffer support across all platforms. - **JS Specs:** Added `getArrayBuffer: (arg: ArrayBuffer) => ArrayBuffer` to both NativeCxxModuleExample.js and NativeSampleTurboModule.js - **C++:** Implemented in NativeCxxModuleExample.cpp using `OwnedMutableBuffer` to copy and return the ArrayBuffer - **Java/Kotlin:** Implemented in SampleTurboModule.kt with `ByteBuffer` param/return - **ObjC:** Implemented in RCTSampleTurboModule.mm (iOS + macOS) with `NSData *` param/return Differential Revision: D95652569 --- .../react-native/React/Base/RCTConvert.mm | 19 ++++++++++++++- .../android/NativeSampleTurboModuleSpec.java | 7 ++++++ .../ReactCommon/SampleTurboModuleSpec.cpp | 21 +++++++++++++++++ .../platform/android/SampleTurboModule.kt | 8 +++++++ .../RCTNativeSampleTurboModuleSpec.h | 1 + .../RCTNativeSampleTurboModuleSpec.mm | 13 +++++++++++ .../ios/ReactCommon/RCTSampleTurboModule.mm | 5 ++++ .../modules/NativeSampleTurboModule.js | 1 + .../NativeCxxModuleExample.cpp | 11 +++++++++ .../NativeCxxModuleExample.h | 2 ++ .../NativeCxxModuleExample.js | 1 + .../tests/NativeCxxModuleExampleTests.cpp | 23 +++++++++++++++++++ .../NativeCxxModuleExampleExample.js | 14 +++++++++++ .../TurboModule/SampleTurboModuleExample.js | 17 ++++++++++++++ 14 files changed, 142 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Base/RCTConvert.mm b/packages/react-native/React/Base/RCTConvert.mm index 0aacbd856107..c042e5065b2e 100644 --- a/packages/react-native/React/Base/RCTConvert.mm +++ b/packages/react-native/React/Base/RCTConvert.mm @@ -60,7 +60,24 @@ +(type *)type : (id)json \ RCT_JSON_CONVERTER(NSNumber) RCT_CUSTOM_CONVERTER(NSSet *, NSSet, [NSSet setWithArray:json]) -RCT_CUSTOM_CONVERTER(NSData *, NSData, [json dataUsingEncoding:NSUTF8StringEncoding]) + ++ (NSData *)NSData:(id)json RCT_DYNAMIC +{ + // Return NSData as-is (e.g., when bridging ArrayBuffer from JS). + if ([json isKindOfClass:[NSData class]]) { + return json; + } + if (!RCT_DEBUG) { + return [json dataUsingEncoding:NSUTF8StringEncoding]; + } else { + @try { + return [json dataUsingEncoding:NSUTF8StringEncoding]; + } @catch (__unused NSException *e) { + RCTLogConvertError(json, @"NSData"); + return nil; + } + } +} + (NSIndexSet *)NSIndexSet:(id)json { diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/NativeSampleTurboModuleSpec.java b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/NativeSampleTurboModuleSpec.java index 4fa8d6862175..d1dee7a001da 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/NativeSampleTurboModuleSpec.java +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/NativeSampleTurboModuleSpec.java @@ -21,6 +21,7 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.build.ReactBuildConfig; import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashSet; import java.util.Map; @@ -165,4 +166,10 @@ public void promiseAssert(Promise promise) {} @ReactMethod @DoNotStrip public void getImageUrl(Promise promise) {} + + @ReactMethod(isBlockingSynchronousMethod = true) + @DoNotStrip + public ByteBuffer getArrayBuffer(ByteBuffer arg) { + return null; + } } diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleSpec.cpp b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleSpec.cpp index f004350f4383..2970815ffefb 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleSpec.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleSpec.cpp @@ -328,6 +328,24 @@ __hostFunction_NativeSampleTurboModuleSpecJSI_getImageUrl( cachedMethodId); } +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;)Ljava/nio/ByteBuffer;", + args, + count, + cachedMethodId); +} + NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI( const JavaTurboModule::InitParams& params) : JavaTurboModule(params) { @@ -393,6 +411,9 @@ NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI( methodMap_["getImageUrl"] = MethodMetadata{ .argCount = 0, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getImageUrl}; + methodMap_["getArrayBuffer"] = MethodMetadata{ + .argCount = 1, + .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer}; eventEmitterMap_["onPress"] = std::make_shared>(); eventEmitterMap_["onClick"] = 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..b5d95e2acd6b 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 @@ -225,6 +226,13 @@ public class SampleTurboModule(private val context: ReactApplicationContext) : assert(false) { "Intentional assert from JVM promiseAssert" } } + @DoNotStrip + @Suppress("unused") + override fun getArrayBuffer(arg: ByteBuffer): ByteBuffer { + log("getArrayBuffer", arg, arg) + return arg + } + @DoNotStrip @Suppress("unused") override fun getImageUrl(promise: Promise) { diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.h b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.h index 142cdd2f4748..01e8bc7f1f5b 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.h +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.h @@ -104,6 +104,7 @@ inline JS::NativeSampleTurboModule::Constants::Builder::Builder(Constants i) - (NSDictionary *)getObjectAssert:(NSDictionary *)arg; - (void)promiseAssert:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject; - (void)getImageUrl:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject; +- (NSData *)getArrayBuffer:(NSData *)arg; - (facebook::react::ModuleConstants)constantsToExport; - (facebook::react::ModuleConstants)getConstants; diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.mm b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.mm index 1f5dd37ad138..8eda33e6267f 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTNativeSampleTurboModuleSpec.mm @@ -224,6 +224,16 @@ - (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallb .invokeObjCMethod(rt, PromiseKind, "getImageUrl", @selector(getImageUrl:reject:), args, count); } +static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer( + facebook::jsi::Runtime &rt, + TurboModule &turboModule, + const facebook::jsi::Value *args, + size_t count) +{ + return static_cast(turboModule) + .invokeObjCMethod(rt, ArrayBufferKind, "getArrayBuffer", @selector(getArrayBuffer:), args, count); +} + static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants( facebook::jsi::Runtime &rt, TurboModule &turboModule, @@ -277,6 +287,9 @@ - (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallb methodMap_["getImageUrl"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_getImageUrl}; + methodMap_["getArrayBuffer"] = + MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer}; + methodMap_["getConstants"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants}; eventEmitterMap_["onPress"] = std::make_shared>(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm index a412dc955b43..3bcc315211cf 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm @@ -210,6 +210,11 @@ - (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime RCTAssert(false, @"Intentional assert from ObjC promiseAssert"); } +RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSData *, getArrayBuffer : (NSData *)arg) +{ + return arg; +} + @end Class _Nonnull RCTSampleTurboModuleCls(void) 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 2cfb9ccb8184..9001f29b8fe3 100644 --- a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js +++ b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js @@ -47,6 +47,7 @@ export interface Spec extends TurboModule { +getNumber: (arg: number) => number; +getString: (arg: string) => string; +getArray: (arg: Array) => Array; + +getArrayBuffer: (arg: ArrayBuffer) => ArrayBuffer; +getObject: (arg: Object) => Object; +getUnsafeObject: (arg: UnsafeObject) => UnsafeObject; +getRootTag: (arg: RootTag) => RootTag; diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp index 492c29a33b66..55c263253096 100644 --- a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp @@ -6,6 +6,7 @@ */ #include "NativeCxxModuleExample.h" +#include #include #include #include @@ -265,4 +266,14 @@ AsyncPromise<> NativeCxxModuleExample::promiseAssert(jsi::Runtime& rt) { return promise; }; +jsi::ArrayBuffer NativeCxxModuleExample::getArrayBuffer( + jsi::Runtime& rt, + jsi::ArrayBuffer arg) { + // Return a copy of the input ArrayBuffer + auto size = arg.size(rt); + auto data = arg.data(rt); + auto buffer = std::make_shared(data, size); + return {rt, std::move(buffer)}; +} + } // namespace facebook::react diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h index 9c2ce01677b1..7a7c8b193f38 100644 --- a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h @@ -182,6 +182,8 @@ class NativeCxxModuleExample : public NativeCxxModuleExampleCxxSpec promiseAssert(jsi::Runtime &rt); + jsi::ArrayBuffer getArrayBuffer(jsi::Runtime &rt, jsi::ArrayBuffer arg); + private: std::optional> valueCallback_; }; diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js index 589360363a32..220a9d526b08 100644 --- a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js @@ -82,6 +82,7 @@ export interface Spec extends TurboModule { +onSubmit: CodegenTypes.EventEmitter; +onEvent: CodegenTypes.EventEmitter; +getArray: (arg: Array) => Array; + +getArrayBuffer: (arg: ArrayBuffer) => ArrayBuffer; +getBool: (arg: boolean) => boolean; +getConstants: () => ConstantsStruct; +getCustomEnum: (arg: EnumInt) => EnumInt; diff --git a/packages/rn-tester/NativeCxxModuleExample/tests/NativeCxxModuleExampleTests.cpp b/packages/rn-tester/NativeCxxModuleExample/tests/NativeCxxModuleExampleTests.cpp index 1de45d805f87..116149151d17 100644 --- a/packages/rn-tester/NativeCxxModuleExample/tests/NativeCxxModuleExampleTests.cpp +++ b/packages/rn-tester/NativeCxxModuleExample/tests/NativeCxxModuleExampleTests.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,28 @@ TEST_F(NativeCxxModuleExampleTests, GetArrayReturnsCorrectValues) { EXPECT_EQ(result[0]->b, "2"); } +TEST_F(NativeCxxModuleExampleTests, GetArrayBufferReturnsCorrectValues) { + // Create an ArrayBuffer with known data + std::vector inputData = {1, 2, 3, 4, 5}; + auto inputBuffer = + std::make_shared(inputData.data(), inputData.size()); + jsi::ArrayBuffer arg(*runtime_, std::move(inputBuffer)); + + auto result = module_->getArrayBuffer(*runtime_, std::move(arg)); + ASSERT_EQ(result.size(*runtime_), inputData.size()); + EXPECT_EQ( + std::memcmp(result.data(*runtime_), inputData.data(), inputData.size()), + 0); +} + +TEST_F(NativeCxxModuleExampleTests, GetArrayBufferEmptyReturnsEmpty) { + auto inputBuffer = std::make_shared(nullptr, 0); + jsi::ArrayBuffer arg(*runtime_, std::move(inputBuffer)); + + auto result = module_->getArrayBuffer(*runtime_, std::move(arg)); + EXPECT_EQ(result.size(*runtime_), 0); +} + TEST_F(NativeCxxModuleExampleTests, GetBoolReturnsCorrectValues) { EXPECT_FALSE(module_->getBool(*runtime_, false)); EXPECT_TRUE(module_->getBool(*runtime_, true)); diff --git a/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js b/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js index dcee38ad6560..7d40c720c5f0 100644 --- a/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js +++ b/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js @@ -40,6 +40,7 @@ type Examples = | 'callback' | 'callbackWithSubscription' | 'getArray' + | 'getArrayBuffer' | 'getBool' | 'getConstants' | 'getCustomEnum' @@ -102,6 +103,19 @@ class NativeCxxModuleExampleExample extends React.Component<{}, State> { {a: 2, b: 'bar'}, null, ]), + getArrayBuffer: () => { + const buffer = new ArrayBuffer(4); + const view = new Uint8Array(buffer); + view[0] = 1; + view[1] = 2; + view[2] = 3; + view[3] = 4; + const result = NativeCxxModuleExample?.getArrayBuffer(buffer); + if (result) { + return Array.from(new Uint8Array(result)); + } + return null; + }, getBool: () => NativeCxxModuleExample?.getBool(true), getConstants: () => NativeCxxModuleExample?.getConstants(), getCustomEnum: () => NativeCxxModuleExample?.getCustomEnum(EnumInt.IB), diff --git a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js index c1c5803c06c1..75ac13810f95 100644 --- a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js +++ b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js @@ -31,6 +31,7 @@ type State = { type Examples = | 'callback' | 'getArray' + | 'getArrayBuffer' | 'getBool' | 'getConstants' | 'getCustomEnum' @@ -101,6 +102,22 @@ class SampleTurboModuleExample extends React.Component<{}, State> { {a: 2, b: 'bar'}, null, ]), + getArrayBuffer: () => { + if (!NativeSampleTurboModule.getArrayBuffer) { + return null; + } + const buffer = new ArrayBuffer(4); + const view = new Uint8Array(buffer); + view[0] = 1; + view[1] = 2; + view[2] = 3; + view[3] = 4; + const result = NativeSampleTurboModule.getArrayBuffer(buffer); + if (result) { + return Array.from(new Uint8Array(result)); + } + return null; + }, getObject: () => NativeSampleTurboModule.getObject({a: 1, b: 'foo', c: null}), getUnsafeObject: () => From 5b43793a2e00e50b0351a8db2a3e313aaa8336f0 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Tue, 5 May 2026 16:42:57 -0700 Subject: [PATCH 3/3] Change ArrayBuffer implementation from copyBuffer to moveBuffer (#56691) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/56691 Switches the `jsi::ArrayBuffer ⇄ native byte buffer` bridge in TurboModules from a copy-based implementation (`OwnedMutableBuffer`) to a zero-copy borrow implementation (`BorrowedMutableBuffer`) on the paths where the caller can guarantee the source memory's lifetime, eliminating an O(N) copy on every ArrayBuffer that crosses the JS/native boundary. **`ReactCommon/react/bridging/ArrayBuffer.h`** — adds `BorrowedMutableBuffer`: holds a raw `uint8_t*` + size, plus an optional `std::function` release callback invoked from the destructor. Caller is responsible for keeping the underlying memory alive — typically by capturing a JNI `global_ref` or ARC-retained ObjC object in the release lambda. Non-copyable; documents thread-safety requirements (release may fire on the JS GC thread). `OwnedMutableBuffer` and the `Bridging>` specialization are preserved for paths where the source lifetime cannot be guaranteed. **Android — `JavaTurboModule.cpp`** — wraps the Java `ByteBuffer`'s direct memory in a `BorrowedMutableBuffer`. Pins the buffer with `jni::make_global(jByteBuffer)` and captures the global ref in the release lambda so Java GC cannot collect it while the JS ArrayBuffer is alive. Tightens the `Ljava/nio/ByteBuffer;` argument check from `isObject()` to `isObject() && isArrayBuffer()`. **iOS — `RCTTurboModule.mm`** — `convertJSIObjectToObjCObject` (sync arg path) returns `NSMutableData dataWithBytesNoCopy:length:freeWhenDone:NO` instead of copying via `[NSData dataWithBytes:length:]`. Safe because the JS ArrayBuffer outlives the synchronous call. `convertReturnIdToJSIValue` `ArrayBufferKind` (sync return path) wraps `NSMutableData.mutableBytes` with `BorrowedMutableBuffer`, retaining the `NSMutableData` via the block capture for the lifetime of the JS ArrayBuffer. `convertObjCObjectToJSIValue` (async/promise path) adds an `NSData → ArrayBuffer` branch that still copies via `OwnedMutableBuffer`, since the source NSData lifetime cannot be guaranteed past the async callback. **Codegen — `serializeMethod.js` + snapshot** — ObjC++ spec generator now emits `NSMutableData *` (instead of `NSData *`) for `ArrayBufferTypeAnnotation`, both for parameters and return types. Module authors must update their ObjC implementations. Changelog: [General][Changed] - TurboModule ArrayBuffer transfers between JS and native are now zero-copy where caller lifetime can be guaranteed; ObjC TurboModule specs now use `NSMutableData *` for `ArrayBufferTypeAnnotation` parameters and return types Differential Revision: D96034456 --- .../GenerateModuleObjCpp/serializeMethod.js | 4 +- .../GenerateModuleHObjCpp-test.js.snap | 6 +- .../ReactCommon/react/bridging/ArrayBuffer.h | 63 +++++++++--------- .../react/bridging/tests/BridgingTest.cpp | 64 +++++++++++-------- .../android/ReactCommon/JavaTurboModule.cpp | 16 +++-- .../ios/ReactCommon/RCTTurboModule.mm | 32 ++++++++-- .../NativeCxxModuleExample.cpp | 9 ++- .../tests/NativeCxxModuleExampleTests.cpp | 6 +- .../api-snapshots/ReactAndroidDebugCxx.api | 22 +++---- .../api-snapshots/ReactAndroidReleaseCxx.api | 22 +++---- .../api-snapshots/ReactAppleDebugCxx.api | 22 +++---- .../api-snapshots/ReactAppleReleaseCxx.api | 22 +++---- .../api-snapshots/ReactCommonDebugCxx.api | 22 +++---- .../api-snapshots/ReactCommonReleaseCxx.api | 22 +++---- 14 files changed, 179 insertions(+), 153 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 6a28c994244f..5f40da132cd4 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js @@ -221,7 +221,7 @@ function getParamObjCType( return notStruct(wrapOptional('NSArray *', !nullable)); } case 'ArrayBufferTypeAnnotation': { - return notStruct(wrapOptional('NSData *', !nullable)); + return notStruct(wrapOptional('NSMutableData *', !nullable)); } } @@ -392,7 +392,7 @@ function getReturnObjCType( case 'GenericObjectTypeAnnotation': return wrapOptional('NSDictionary *', isRequired); case 'ArrayBufferTypeAnnotation': - return wrapOptional('NSData *', isRequired); + return wrapOptional('NSMutableData *', isRequired); default: typeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap index de4e61035167..3abd6400dda0 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap @@ -102,9 +102,9 @@ Map { @protocol NativeSampleTurboModuleSpec -- (NSData *)getArrayBuffer:(NSData *)buffer; -- (void)saveData:(NSData *)data; -- (NSData * _Nullable)loadData; +- (NSMutableData *)getArrayBuffer:(NSMutableData *)buffer; +- (void)saveData:(NSMutableData *)data; +- (NSMutableData * _Nullable)loadData; @end diff --git a/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h b/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h index 8c67a7e42233..0bb4e5c209ca 100644 --- a/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h +++ b/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h @@ -10,55 +10,60 @@ #include #include -#include -#include +#include namespace facebook::react { /** - * A concrete implementation of jsi::MutableBuffer that owns a copy of the data. - * Used when creating jsi::ArrayBuffer from native data (e.g., NSData, - * ByteBuffer) where we need to copy the data to ensure proper memory - * ownership. + * A non-owning implementation of jsi::MutableBuffer that borrows a pointer to + * externally-managed memory. The caller MUST ensure the underlying data outlives + * the buffer, or provide a release callback that prevents the source from being + * deallocated (e.g., a JNI global_ref or an ARC-retained ObjC object). + * + * When the BorrowedMutableBuffer is destroyed, the optional release callback is + * invoked, allowing the prevent-GC reference to be dropped. + * + * Thread safety: The release callback may be invoked on any thread (typically + * the JS thread during garbage collection). Callers must ensure the release + * callback is safe to invoke from any thread. JNI DeleteGlobalRef and ARC + * release are both thread-safe. + * + * The release callback must not throw exceptions. Throwing from the destructor + * will terminate the process. */ -class OwnedMutableBuffer : public jsi::MutableBuffer { +class BorrowedMutableBuffer : public jsi::MutableBuffer { public: - OwnedMutableBuffer(const uint8_t *data, size_t size) : data_(data, std::next(data, static_cast(size))) + BorrowedMutableBuffer(uint8_t *data, size_t size, std::function release = nullptr) + : data_(data), size_(size), release_(std::move(release)) { } - explicit OwnedMutableBuffer(size_t size) : data_(size, 0) {} + ~BorrowedMutableBuffer() override + { + if (release_) { + release_(); + } + } - explicit OwnedMutableBuffer(std::vector data) : data_(std::move(data)) {} + BorrowedMutableBuffer(const BorrowedMutableBuffer &) = delete; + BorrowedMutableBuffer &operator=(const BorrowedMutableBuffer &) = delete; + BorrowedMutableBuffer(BorrowedMutableBuffer &&) = delete; + BorrowedMutableBuffer &operator=(BorrowedMutableBuffer &&) = delete; size_t size() const override { - return data_.size(); + return size_; } uint8_t *data() override { - return data_.data(); + return data_; } private: - std::vector data_; -}; - -template <> -struct Bridging> { - static std::vector fromJs(jsi::Runtime &rt, const jsi::ArrayBuffer &buffer) - { - auto size = buffer.size(rt); - auto data = buffer.data(rt); - return {data, std::next(data, static_cast(size))}; - } - - static jsi::ArrayBuffer toJs(jsi::Runtime &rt, const std::vector &data) - { - auto buffer = std::make_shared(data.data(), data.size()); - return {rt, std::move(buffer)}; - } + uint8_t *data_; + size_t size_; + std::function release_; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp index bb9bf344aa19..337f2825c46a 100644 --- a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp +++ b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp @@ -780,33 +780,43 @@ TEST_F(BridgingTest, dynamicTest) { EXPECT_TRUE(undefinedFromJsResult.isNull()); } -TEST_F(BridgingTest, arrayBufferTest) { - // Test round-trip: vector -> ArrayBuffer -> vector - auto vec = std::vector({1, 2, 3, 4, 5}); - auto arrayBuffer = bridging::toJs(rt, vec, invoker); - auto result = - bridging::fromJs>(rt, arrayBuffer, invoker); - EXPECT_EQ(vec, result); - - // Test empty vector round-trip - auto emptyVec = std::vector(); - auto emptyArrayBuffer = bridging::toJs(rt, emptyVec, invoker); - auto emptyResult = - bridging::fromJs>(rt, emptyArrayBuffer, invoker); - EXPECT_TRUE(emptyResult.empty()); - - // Test OwnedMutableBuffer constructed from size - auto sizedBuffer = OwnedMutableBuffer(4); - EXPECT_EQ(4, sizedBuffer.size()); - // Should be zero-initialized - auto expected_zeros = std::vector(4, 0); - EXPECT_EQ(0, std::memcmp(sizedBuffer.data(), expected_zeros.data(), 4)); - - // Test OwnedMutableBuffer constructed from vector - auto inputVec = std::vector({10, 20, 30}); - auto vecBuffer = OwnedMutableBuffer(inputVec); - EXPECT_EQ(3, vecBuffer.size()); - EXPECT_EQ(0, std::memcmp(vecBuffer.data(), inputVec.data(), inputVec.size())); +TEST_F(BridgingTest, borrowedMutableBufferTest) { + // Test basic construction and accessors + std::vector source = {1, 2, 3, 4, 5}; + auto borrowed = BorrowedMutableBuffer(source.data(), source.size()); + EXPECT_EQ(source.size(), borrowed.size()); + EXPECT_EQ(source.data(), borrowed.data()); // pointer identity, no copy + + // Test that data is readable through the borrowed pointer + EXPECT_EQ(0, std::memcmp(borrowed.data(), source.data(), source.size())); + + // Test mutability through borrowed pointer + *borrowed.data() = 99; + EXPECT_EQ(99, source[0]); // mutation visible through original + + // Test release callback is invoked on destruction + bool released = false; + { + auto buffer = BorrowedMutableBuffer( + source.data(), source.size(), [&released]() { released = true; }); + EXPECT_FALSE(released); + } + EXPECT_TRUE(released); + + // Test with nullptr release callback (no-op) + { + auto buffer = BorrowedMutableBuffer(source.data(), source.size()); + // Should not crash on destruction + } + + // Test zero-copy round-trip through jsi::ArrayBuffer + source[0] = 1; // restore + auto mutableBuffer = + std::make_shared(source.data(), source.size()); + auto originalPtr = mutableBuffer->data(); + auto arrayBuffer = jsi::ArrayBuffer(rt, std::move(mutableBuffer)); + EXPECT_EQ(source.size(), arrayBuffer.size(rt)); + EXPECT_EQ(originalPtr, arrayBuffer.data(rt)); // pointer identity preserved } TEST_F(BridgingTest, highResTimeStampTest) { 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 dcc065969fe6..78271967c04a 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 @@ -436,7 +436,7 @@ JNIArgs convertJSIArgsToJNIArgs( auto jParams = JDynamicNative::newObjectCxxArgs(dynamicFromValue); jarg->l = makeGlobalIfNecessary(jParams.release()); } else if (type == "Ljava/nio/ByteBuffer;") { - if (!arg->isObject()) { + if (!arg->isObject() || !arg->getObject(rt).isArrayBuffer(rt)) { throw JavaTurboModuleArgumentConversionException( "ArrayBuffer", static_cast(argIndex), methodName, arg, &rt); } @@ -992,10 +992,16 @@ jsi::Value JavaTurboModule::invokeJavaMethod( auto size = jByteBuffer->getDirectSize(); if (size > 0) { - // Copy the data from the ByteBuffer into a new ArrayBuffer. - // We must copy because the ByteBuffer's lifetime is managed by Java. - auto buffer = std::make_shared( - jByteBuffer->getDirectBytes(), static_cast(size)); + // Zero-copy: wrap the ByteBuffer's memory directly. A JNI global + // reference prevents Java GC from collecting the ByteBuffer while + // the JS ArrayBuffer is alive. The release callback drops the + // global ref when the JS GC collects the ArrayBuffer. + auto globalRef = jni::make_global(jByteBuffer); + auto data = globalRef->getDirectBytes(); + auto buffer = std::make_shared( + data, + static_cast(size), + [prevent_gc = std::move(globalRef)]() { (void)prevent_gc; }); auto arrayBuffer = jsi::ArrayBuffer(runtime, std::move(buffer)); returnValue = jsi::Value(runtime, arrayBuffer); } diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm index 80d94b2e789b..97ca692ed407 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm @@ -102,6 +102,17 @@ static int32_t getUniqueId() return convertNSDictionaryToJSIObject(runtime, (NSDictionary *)value); } else if ([value isKindOfClass:[NSArray class]]) { return convertNSArrayToJSIArray(runtime, (NSArray *)value); + } else if ([value isKindOfClass:[NSData class]]) { + // Zero-copy: wrap the NSData's memory directly. ARC retains the NSData via + // the release callback's block capture, preventing deallocation while the + // JS ArrayBuffer is alive. The release callback fires when the JS GC + // collects the ArrayBuffer. + auto data = (NSData *)value; + auto buffer = std::make_shared( + const_cast(static_cast(data.bytes)), data.length, [retainedData = data]() { + (void)retainedData; + }); + return {runtime, jsi::ArrayBuffer(runtime, std::move(buffer))}; } else if (value == (id)kCFNull) { return jsi::Value::null(); } @@ -197,7 +208,11 @@ id convertJSIValueToObjCObject( } if (o.isArrayBuffer(runtime)) { auto ab = o.getArrayBuffer(runtime); - return [NSData dataWithBytes:ab.data(runtime) length:ab.size(runtime)]; + // Zero-copy: wrap the JS ArrayBuffer's memory without copying. + // Safe because this is only called during synchronous argument setup, + // and the JS ArrayBuffer is alive for the duration of the call. + // freeWhenDone:NO ensures NSMutableData does not free the JS-owned memory. + return [NSMutableData dataWithBytesNoCopy:(void *)ab.data(runtime) length:ab.size(runtime) freeWhenDone:NO]; } return convertJSIObjectToNSDictionary(runtime, o, jsInvoker, useNSNull); } @@ -531,9 +546,18 @@ TraceSection s( case PromiseKind: throw jsi::JSError(runtime, "convertReturnIdToJSIValue: PromiseKind wasn't handled properly."); case ArrayBufferKind: { - auto data = (NSData *)result; - auto buffer = std::make_shared(static_cast(data.bytes), data.length); - returnValue = jsi::ArrayBuffer(runtime, std::move(buffer)); + auto mutableData = (NSMutableData *)result; + if (mutableData != nil && mutableData.length > 0) { + // Zero-copy: wrap the NSMutableData's memory directly. ARC retains the + // NSMutableData via the release callback's block capture, preventing + // deallocation while the JS ArrayBuffer is alive. The release callback + // fires when the JS GC collects the ArrayBuffer. + auto buffer = std::make_shared( + static_cast(mutableData.mutableBytes), mutableData.length, [retainedData = mutableData]() { + (void)retainedData; + }); + returnValue = jsi::ArrayBuffer(runtime, std::move(buffer)); + } break; } } diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp index 55c263253096..7130e8f7752e 100644 --- a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp @@ -8,6 +8,7 @@ #include "NativeCxxModuleExample.h" #include #include +#include #include #include #include @@ -269,10 +270,14 @@ AsyncPromise<> NativeCxxModuleExample::promiseAssert(jsi::Runtime& rt) { jsi::ArrayBuffer NativeCxxModuleExample::getArrayBuffer( jsi::Runtime& rt, jsi::ArrayBuffer arg) { - // Return a copy of the input ArrayBuffer + // Return a copy of the input ArrayBuffer. We copy into a heap-allocated + // vector that is kept alive by the BorrowedMutableBuffer release callback. auto size = arg.size(rt); auto data = arg.data(rt); - auto buffer = std::make_shared(data, size); + auto owned = std::make_shared>(size); + std::memcpy(owned->data(), data, size); + auto buffer = std::make_shared( + owned->data(), owned->size(), [owned]() { (void)owned; }); return {rt, std::move(buffer)}; } diff --git a/packages/rn-tester/NativeCxxModuleExample/tests/NativeCxxModuleExampleTests.cpp b/packages/rn-tester/NativeCxxModuleExample/tests/NativeCxxModuleExampleTests.cpp index 116149151d17..3ecf50ec7515 100644 --- a/packages/rn-tester/NativeCxxModuleExample/tests/NativeCxxModuleExampleTests.cpp +++ b/packages/rn-tester/NativeCxxModuleExample/tests/NativeCxxModuleExampleTests.cpp @@ -35,8 +35,8 @@ TEST_F(NativeCxxModuleExampleTests, GetArrayReturnsCorrectValues) { TEST_F(NativeCxxModuleExampleTests, GetArrayBufferReturnsCorrectValues) { // Create an ArrayBuffer with known data std::vector inputData = {1, 2, 3, 4, 5}; - auto inputBuffer = - std::make_shared(inputData.data(), inputData.size()); + auto inputBuffer = std::make_shared( + inputData.data(), inputData.size()); jsi::ArrayBuffer arg(*runtime_, std::move(inputBuffer)); auto result = module_->getArrayBuffer(*runtime_, std::move(arg)); @@ -47,7 +47,7 @@ TEST_F(NativeCxxModuleExampleTests, GetArrayBufferReturnsCorrectValues) { } TEST_F(NativeCxxModuleExampleTests, GetArrayBufferEmptyReturnsEmpty) { - auto inputBuffer = std::make_shared(nullptr, 0); + auto inputBuffer = std::make_shared(nullptr, 0); jsi::ArrayBuffer arg(*runtime_, std::move(inputBuffer)); auto result = module_->getArrayBuffer(*runtime_, std::move(arg)); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index be1c79d2dfe0..ba1ddd788b1c 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -1838,6 +1838,15 @@ class facebook::react::BlobModuleJSIBindings : public jni::JavaClass release = nullptr); + public facebook::react::BorrowedMutableBuffer& operator=(const facebook::react::BorrowedMutableBuffer&) = delete; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~BorrowedMutableBuffer() override; +} + class facebook::react::BridgelessNativeMethodCallInvoker : public facebook::react::NativeMethodCallInvoker { public BridgelessNativeMethodCallInvoker(std::shared_ptr messageQueueThread); public virtual void invokeAsync(const std::string& methodName, facebook::react::NativeMethodCallFunc&& func) noexcept override; @@ -3909,14 +3918,6 @@ class facebook::react::OperatorAnimatedNode : public facebook::react::ValueAnima public OperatorAnimatedNode(facebook::react::Tag tag, const folly::dynamic& config, facebook::react::NativeAnimatedNodesManager& manager); } -class facebook::react::OwnedMutableBuffer : public facebook::jsi::MutableBuffer { - public OwnedMutableBuffer(const uint8_t* data, size_t size); - public OwnedMutableBuffer(size_t size); - public OwnedMutableBuffer(std::vector data); - public virtual size_t size() const override; - public virtual uint8_t* data() override; -} - class facebook::react::ParagraphAttributes : public facebook::react::DebugStringConvertible { public bool adjustsFontSizeToFit; public bool includeFontPadding; @@ -9467,11 +9468,6 @@ struct facebook::react::Bridging { public static facebook::jsi::String toJs(facebook::jsi::Runtime& rt, std::string_view value); } -struct facebook::react::Bridging> { - public static facebook::jsi::ArrayBuffer toJs(facebook::jsi::Runtime& rt, const std::vector& data); - public static std::vector fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); -} - template struct facebook::react::Bridging> { public static constexpr size_t kArgumentCount; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index e919a48fd8a9..c45e084f9575 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -1836,6 +1836,15 @@ class facebook::react::BlobModuleJSIBindings : public jni::JavaClass release = nullptr); + public facebook::react::BorrowedMutableBuffer& operator=(const facebook::react::BorrowedMutableBuffer&) = delete; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~BorrowedMutableBuffer() override; +} + class facebook::react::BridgelessNativeMethodCallInvoker : public facebook::react::NativeMethodCallInvoker { public BridgelessNativeMethodCallInvoker(std::shared_ptr messageQueueThread); public virtual void invokeAsync(const std::string& methodName, facebook::react::NativeMethodCallFunc&& func) noexcept override; @@ -3906,14 +3915,6 @@ class facebook::react::OperatorAnimatedNode : public facebook::react::ValueAnima public OperatorAnimatedNode(facebook::react::Tag tag, const folly::dynamic& config, facebook::react::NativeAnimatedNodesManager& manager); } -class facebook::react::OwnedMutableBuffer : public facebook::jsi::MutableBuffer { - public OwnedMutableBuffer(const uint8_t* data, size_t size); - public OwnedMutableBuffer(size_t size); - public OwnedMutableBuffer(std::vector data); - public virtual size_t size() const override; - public virtual uint8_t* data() override; -} - class facebook::react::ParagraphAttributes : public facebook::react::DebugStringConvertible { public bool adjustsFontSizeToFit; public bool includeFontPadding; @@ -9323,11 +9324,6 @@ struct facebook::react::Bridging { public static facebook::jsi::String toJs(facebook::jsi::Runtime& rt, std::string_view value); } -struct facebook::react::Bridging> { - public static facebook::jsi::ArrayBuffer toJs(facebook::jsi::Runtime& rt, const std::vector& data); - public static std::vector fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); -} - template struct facebook::react::Bridging> { public static constexpr size_t kArgumentCount; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 7cb05c0ab06a..c46053a2c275 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -4769,6 +4769,15 @@ class facebook::react::BindingsInstaller { public virtual facebook::react::ReactInstance::BindingsInstallFunc getBindingsInstallFunc(); } +class facebook::react::BorrowedMutableBuffer : public facebook::jsi::MutableBuffer { + public BorrowedMutableBuffer(const facebook::react::BorrowedMutableBuffer&) = delete; + public BorrowedMutableBuffer(uint8_t* data, size_t size, std::function release = nullptr); + public facebook::react::BorrowedMutableBuffer& operator=(const facebook::react::BorrowedMutableBuffer&) = delete; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~BorrowedMutableBuffer() override; +} + class facebook::react::BridgelessNativeMethodCallInvoker : public facebook::react::NativeMethodCallInvoker { public BridgelessNativeMethodCallInvoker(std::shared_ptr messageQueueThread); public virtual void invokeAsync(const std::string& methodName, facebook::react::NativeMethodCallFunc&& func) noexcept override; @@ -6483,14 +6492,6 @@ class facebook::react::OperatorAnimatedNode : public facebook::react::ValueAnima public OperatorAnimatedNode(facebook::react::Tag tag, const folly::dynamic& config, facebook::react::NativeAnimatedNodesManager& manager); } -class facebook::react::OwnedMutableBuffer : public facebook::jsi::MutableBuffer { - public OwnedMutableBuffer(const uint8_t* data, size_t size); - public OwnedMutableBuffer(size_t size); - public OwnedMutableBuffer(std::vector data); - public virtual size_t size() const override; - public virtual uint8_t* data() override; -} - class facebook::react::ParagraphAttributes : public facebook::react::DebugStringConvertible { public bool adjustsFontSizeToFit; public bool includeFontPadding; @@ -11756,11 +11757,6 @@ struct facebook::react::Bridging { public static facebook::jsi::String toJs(facebook::jsi::Runtime& rt, std::string_view value); } -struct facebook::react::Bridging> { - public static facebook::jsi::ArrayBuffer toJs(facebook::jsi::Runtime& rt, const std::vector& data); - public static std::vector fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); -} - template struct facebook::react::Bridging> { public static constexpr size_t kArgumentCount; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index ae5960a41d98..f408de267763 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -4767,6 +4767,15 @@ class facebook::react::BindingsInstaller { public virtual facebook::react::ReactInstance::BindingsInstallFunc getBindingsInstallFunc(); } +class facebook::react::BorrowedMutableBuffer : public facebook::jsi::MutableBuffer { + public BorrowedMutableBuffer(const facebook::react::BorrowedMutableBuffer&) = delete; + public BorrowedMutableBuffer(uint8_t* data, size_t size, std::function release = nullptr); + public facebook::react::BorrowedMutableBuffer& operator=(const facebook::react::BorrowedMutableBuffer&) = delete; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~BorrowedMutableBuffer() override; +} + class facebook::react::BridgelessNativeMethodCallInvoker : public facebook::react::NativeMethodCallInvoker { public BridgelessNativeMethodCallInvoker(std::shared_ptr messageQueueThread); public virtual void invokeAsync(const std::string& methodName, facebook::react::NativeMethodCallFunc&& func) noexcept override; @@ -6480,14 +6489,6 @@ class facebook::react::OperatorAnimatedNode : public facebook::react::ValueAnima public OperatorAnimatedNode(facebook::react::Tag tag, const folly::dynamic& config, facebook::react::NativeAnimatedNodesManager& manager); } -class facebook::react::OwnedMutableBuffer : public facebook::jsi::MutableBuffer { - public OwnedMutableBuffer(const uint8_t* data, size_t size); - public OwnedMutableBuffer(size_t size); - public OwnedMutableBuffer(std::vector data); - public virtual size_t size() const override; - public virtual uint8_t* data() override; -} - class facebook::react::ParagraphAttributes : public facebook::react::DebugStringConvertible { public bool adjustsFontSizeToFit; public bool includeFontPadding; @@ -11622,11 +11623,6 @@ struct facebook::react::Bridging { public static facebook::jsi::String toJs(facebook::jsi::Runtime& rt, std::string_view value); } -struct facebook::react::Bridging> { - public static facebook::jsi::ArrayBuffer toJs(facebook::jsi::Runtime& rt, const std::vector& data); - public static std::vector fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); -} - template struct facebook::react::Bridging> { public static constexpr size_t kArgumentCount; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index e9c60a53157c..cd94c187397e 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -1148,6 +1148,15 @@ class facebook::react::BindingsInstaller { public virtual facebook::react::ReactInstance::BindingsInstallFunc getBindingsInstallFunc(); } +class facebook::react::BorrowedMutableBuffer : public facebook::jsi::MutableBuffer { + public BorrowedMutableBuffer(const facebook::react::BorrowedMutableBuffer&) = delete; + public BorrowedMutableBuffer(uint8_t* data, size_t size, std::function release = nullptr); + public facebook::react::BorrowedMutableBuffer& operator=(const facebook::react::BorrowedMutableBuffer&) = delete; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~BorrowedMutableBuffer() override; +} + class facebook::react::BridgelessNativeMethodCallInvoker : public facebook::react::NativeMethodCallInvoker { public BridgelessNativeMethodCallInvoker(std::shared_ptr messageQueueThread); public virtual void invokeAsync(const std::string& methodName, facebook::react::NativeMethodCallFunc&& func) noexcept override; @@ -2546,14 +2555,6 @@ class facebook::react::OperatorAnimatedNode : public facebook::react::ValueAnima public OperatorAnimatedNode(facebook::react::Tag tag, const folly::dynamic& config, facebook::react::NativeAnimatedNodesManager& manager); } -class facebook::react::OwnedMutableBuffer : public facebook::jsi::MutableBuffer { - public OwnedMutableBuffer(const uint8_t* data, size_t size); - public OwnedMutableBuffer(size_t size); - public OwnedMutableBuffer(std::vector data); - public virtual size_t size() const override; - public virtual uint8_t* data() override; -} - class facebook::react::ParagraphAttributes : public facebook::react::DebugStringConvertible { public Float maximumFontSize; public Float minimumFontScale; @@ -6583,11 +6584,6 @@ struct facebook::react::Bridging { public static facebook::jsi::String toJs(facebook::jsi::Runtime& rt, std::string_view value); } -struct facebook::react::Bridging> { - public static facebook::jsi::ArrayBuffer toJs(facebook::jsi::Runtime& rt, const std::vector& data); - public static std::vector fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); -} - template struct facebook::react::Bridging> { public static constexpr size_t kArgumentCount; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 7659dbe2a2cf..08790527f4e5 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -1146,6 +1146,15 @@ class facebook::react::BindingsInstaller { public virtual facebook::react::ReactInstance::BindingsInstallFunc getBindingsInstallFunc(); } +class facebook::react::BorrowedMutableBuffer : public facebook::jsi::MutableBuffer { + public BorrowedMutableBuffer(const facebook::react::BorrowedMutableBuffer&) = delete; + public BorrowedMutableBuffer(uint8_t* data, size_t size, std::function release = nullptr); + public facebook::react::BorrowedMutableBuffer& operator=(const facebook::react::BorrowedMutableBuffer&) = delete; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~BorrowedMutableBuffer() override; +} + class facebook::react::BridgelessNativeMethodCallInvoker : public facebook::react::NativeMethodCallInvoker { public BridgelessNativeMethodCallInvoker(std::shared_ptr messageQueueThread); public virtual void invokeAsync(const std::string& methodName, facebook::react::NativeMethodCallFunc&& func) noexcept override; @@ -2543,14 +2552,6 @@ class facebook::react::OperatorAnimatedNode : public facebook::react::ValueAnima public OperatorAnimatedNode(facebook::react::Tag tag, const folly::dynamic& config, facebook::react::NativeAnimatedNodesManager& manager); } -class facebook::react::OwnedMutableBuffer : public facebook::jsi::MutableBuffer { - public OwnedMutableBuffer(const uint8_t* data, size_t size); - public OwnedMutableBuffer(size_t size); - public OwnedMutableBuffer(std::vector data); - public virtual size_t size() const override; - public virtual uint8_t* data() override; -} - class facebook::react::ParagraphAttributes : public facebook::react::DebugStringConvertible { public Float maximumFontSize; public Float minimumFontScale; @@ -6574,11 +6575,6 @@ struct facebook::react::Bridging { public static facebook::jsi::String toJs(facebook::jsi::Runtime& rt, std::string_view value); } -struct facebook::react::Bridging> { - public static facebook::jsi::ArrayBuffer toJs(facebook::jsi::Runtime& rt, const std::vector& data); - public static std::vector fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::ArrayBuffer& buffer); -} - template struct facebook::react::Bridging> { public static constexpr size_t kArgumentCount;