From dd478cfab9e6a0545f7c7dc4fd08487346e17f60 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Thu, 7 May 2026 08:56:22 -0700 Subject: [PATCH] Add ArrayBuffer support for React Native TurboModules - shares data between JavaScript and native (#56689) 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/56689 ## 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 | 108 ++++++++++++++++++ .../ReactCommon/react/bridging/Bridging.h | 1 + .../ReactCommon/react/bridging/Convert.h | 12 ++ .../react/bridging/tests/BridgingTest.cpp | 86 ++++++++++++++ .../core/ReactCommon/TurboModule.h | 1 + .../android/ReactCommon/JavaTurboModule.cpp | 94 +++++++++++++++ .../ios/ReactCommon/RCTInteropTurboModule.h | 3 +- .../ios/ReactCommon/RCTInteropTurboModule.mm | 15 +-- .../platform/ios/ReactCommon/RCTTurboModule.h | 6 +- .../ios/ReactCommon/RCTTurboModule.mm | 90 +++++++++++++-- .../api-snapshots/ReactAndroidDebugCxx.api | 21 ++++ .../api-snapshots/ReactAndroidReleaseCxx.api | 21 ++++ .../api-snapshots/ReactAppleDebugCxx.api | 27 ++++- .../api-snapshots/ReactAppleReleaseCxx.api | 27 ++++- .../api-snapshots/ReactCommonDebugCxx.api | 21 ++++ .../api-snapshots/ReactCommonReleaseCxx.api | 21 ++++ 39 files changed, 1231 insertions(+), 30 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..5f40da132cd4 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js @@ -51,7 +51,8 @@ type ReturnJSType = | 'ObjectKind' | 'ArrayKind' | 'NumberKind' - | 'StringKind'; + | 'StringKind' + | 'ArrayBufferKind'; export type MethodSerializationOutput = Readonly<{ methodName: string, @@ -219,6 +220,9 @@ function getParamObjCType( */ return notStruct(wrapOptional('NSArray *', !nullable)); } + case 'ArrayBufferTypeAnnotation': { + return notStruct(wrapOptional('NSMutableData *', !nullable)); + } } const [structTypeAnnotation] = unwrapNullable( @@ -387,6 +391,8 @@ function getReturnObjCType( } case 'GenericObjectTypeAnnotation': return wrapOptional('NSDictionary *', isRequired); + case 'ArrayBufferTypeAnnotation': + return wrapOptional('NSMutableData *', 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..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 @@ -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 + +- (NSMutableData *)getArrayBuffer:(NSMutableData *)buffer; +- (void)saveData:(NSMutableData *)data; +- (NSMutableData * _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..d28fc59b3896 --- /dev/null +++ b/packages/react-native/ReactCommon/react/bridging/ArrayBuffer.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +/** + * A non-owning implementation of jsi::MutableBuffer that shares 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 SharedMutableBuffer 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. + * + * Prefer a concrete platform-specific subclass of jsi::MutableBuffer when one + * exists (see JByteBufferMutableBuffer on Android, NSMutableDataMutableBuffer + * on iOS). SharedMutableBuffer is an escape hatch for callers that cannot + * model their ownership with a dedicated subclass. + */ +class SharedMutableBuffer : public jsi::MutableBuffer { + public: + SharedMutableBuffer(uint8_t *data, size_t size, std::function release = nullptr) + : data_(data), size_(size), release_(std::move(release)) + { + } + + ~SharedMutableBuffer() override + { + if (release_) { + release_(); + } + } + + SharedMutableBuffer(const SharedMutableBuffer &) = delete; + SharedMutableBuffer &operator=(const SharedMutableBuffer &) = delete; + SharedMutableBuffer(SharedMutableBuffer &&) = delete; + SharedMutableBuffer &operator=(SharedMutableBuffer &&) = delete; + + size_t size() const override + { + return size_; + } + + uint8_t *data() override + { + return data_; + } + + private: + uint8_t *data_; + size_t size_; + std::function release_; +}; + +/** + * An owning implementation of jsi::MutableBuffer backed by a std::vector. + * Use this when the source of the bytes cannot be guaranteed to outlive the + * buffer (for example, returning data to JS from an asynchronous native call, + * or copying a JS ArrayBuffer argument before storing it). + */ +class VectorMutableBuffer final : public jsi::MutableBuffer { + public: + explicit VectorMutableBuffer(std::vector data) : data_(std::move(data)) {} + + VectorMutableBuffer(const uint8_t *source, size_t size) : data_(size) + { + std::memcpy(data_.data(), source, size); + } + + size_t size() const override + { + return data_.size(); + } + + uint8_t *data() override + { + return data_.data(); + } + + private: + std::vector data_; +}; + +} // 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 a2f8374ae173..e155b86b5a2f 100644 --- a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp +++ b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp @@ -775,6 +775,92 @@ TEST_F(BridgingTest, dynamicTest) { EXPECT_TRUE(undefinedFromJsResult.isNull()); } +TEST_F(BridgingTest, sharedMutableBufferTest) { + // Basic construction and accessors + std::vector source = {1, 2, 3, 4, 5}; + auto shared = SharedMutableBuffer(source.data(), source.size()); + EXPECT_EQ(source.size(), shared.size()); + EXPECT_EQ(source.data(), shared.data()); // pointer identity, no copy + + // Mutability through the shared pointer is visible at the source + *shared.data() = 99; + EXPECT_EQ(99, source[0]); + + // Release callback fires on destruction + bool released = false; + { + auto buffer = SharedMutableBuffer( + source.data(), source.size(), [&released]() { released = true; }); + EXPECT_FALSE(released); + } + EXPECT_TRUE(released); + + // No-op release callback does not crash + { + auto buffer = SharedMutableBuffer(source.data(), source.size()); + } +} + +// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) +TEST_F(BridgingTest, vectorMutableBufferOwnsCopy) { + std::vector source = {1, 2, 3, 4, 5}; + auto buffer = std::make_shared(source); + EXPECT_EQ(5, buffer->size()); + EXPECT_EQ(1, buffer->data()[0]); + + // Mutating the source after construction does NOT affect the owned copy + source[0] = 99; + EXPECT_EQ(1, buffer->data()[0]); + + // Construction from raw pointer + size also takes a copy + std::vector source2 = {10, 20, 30}; + auto buffer2 = + std::make_shared(source2.data(), source2.size()); + source2[0] = 0xFF; + EXPECT_EQ(10, buffer2->data()[0]); +} + +TEST_F(BridgingTest, arrayBufferBidirectionalMutation) { + // Modifications through jsi::ArrayBuffer are visible via the underlying + // MutableBuffer, and vice versa. + std::vector original = {10, 20, 30, 40, 50}; + auto buffer = std::make_shared(original); + auto* originalPtr = buffer->data(); + + auto arrayBuffer = jsi::ArrayBuffer(rt, buffer); + EXPECT_EQ(5, arrayBuffer.size(rt)); + EXPECT_EQ(originalPtr, arrayBuffer.data(rt)); // pointer identity preserved + + // Write through the ArrayBuffer + arrayBuffer.data(rt)[0] = 0xDE; + arrayBuffer.data(rt)[4] = 0xAD; + EXPECT_EQ(0xDE, buffer->data()[0]); + EXPECT_EQ(0xAD, buffer->data()[4]); + EXPECT_EQ(20, buffer->data()[1]); // middle bytes unchanged + EXPECT_EQ(30, buffer->data()[2]); + EXPECT_EQ(40, buffer->data()[3]); + + // Write through the MutableBuffer + buffer->data()[2] = 0xBE; + EXPECT_EQ(0xBE, arrayBuffer.data(rt)[2]); +} + +TEST_F(BridgingTest, arrayBufferSharedBufferRoundTrip) { + // SharedMutableBuffer over a stack vector, handed to jsi::ArrayBuffer. + // Mutations flow back to the original source (zero-copy semantics). + std::vector source = {1, 2, 3, 4, 5}; + auto sourcePtr = source.data(); + auto buffer = + std::make_shared(source.data(), source.size()); + auto arrayBuffer = jsi::ArrayBuffer(rt, std::move(buffer)); + + EXPECT_EQ(source.size(), arrayBuffer.size(rt)); + EXPECT_EQ(sourcePtr, arrayBuffer.data(rt)); + arrayBuffer.data(rt)[0] = 0x42; + EXPECT_EQ(0x42, source[0]); // mutation flows back to the borrowed source +} +// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + 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..898af5f250e5 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 @@ -31,6 +32,46 @@ namespace facebook::react { namespace TMPL = TurboModulePerfLogger; +namespace { +// MutableBuffer backed by a JNI global reference to a direct +// java.nio.ByteBuffer. The direct ByteBuffer's address is stable for the +// lifetime of the global ref, so we cache it in the constructor. +// +// The destructor may run on a non-JVM thread when JS GC fires, so it attaches +// the current thread before releasing the global ref. +class JByteBufferMutableBuffer final : public jsi::MutableBuffer { + public: + explicit JByteBufferMutableBuffer( + jni::global_ref byteBuffer) + : byteBuffer_(std::move(byteBuffer)), + data_(byteBuffer_->getDirectBytes()), + size_(static_cast(byteBuffer_->getDirectSize())) {} + + ~JByteBufferMutableBuffer() override { + jni::ThreadScope threadScope; + byteBuffer_.reset(); + } + + JByteBufferMutableBuffer(const JByteBufferMutableBuffer&) = delete; + JByteBufferMutableBuffer& operator=(const JByteBufferMutableBuffer&) = delete; + JByteBufferMutableBuffer(JByteBufferMutableBuffer&&) = delete; + JByteBufferMutableBuffer& operator=(JByteBufferMutableBuffer&&) = delete; + + size_t size() const override { + return size_; + } + + uint8_t* data() override { + return data_; + } + + private: + jni::global_ref byteBuffer_; + uint8_t* data_; + size_t size_; +}; +} // namespace + JavaTurboModule::JavaTurboModule(const InitParams& params) : TurboModule(params.moduleName, params.jsInvoker), instance_(jni::make_global(params.instance)), @@ -434,6 +475,28 @@ JNIArgs convertJSIArgsToJNIArgs( auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg); auto jParams = JDynamicNative::newObjectCxxArgs(dynamicFromValue); jarg->l = makeGlobalIfNecessary(jParams.release()); + } else if (type == "Ljava/nio/ByteBuffer;") { + if (!arg->isObject() || !arg->getObject(rt).isArrayBuffer(rt)) { + throw JavaTurboModuleArgumentConversionException( + "ArrayBuffer", static_cast(argIndex), methodName, arg, &rt); + } + auto arrayBuffer = arg->getObject(rt).getArrayBuffer(rt); + auto data = arrayBuffer.data(rt); + auto size = arrayBuffer.size(rt); + jni::local_ref byteBuffer; + if (valueKind == VoidKind || valueKind == PromiseKind) { + // Async: the JS ArrayBuffer may be GC'd before Java reads the buffer. + // Allocate a Java-owned direct ByteBuffer and copy the bytes in. + byteBuffer = jni::JByteBuffer::allocateDirect(static_cast(size)); + // Destination was just allocated with exactly `size` bytes above. + // NOLINTNEXTLINE(facebook-security-vulnerable-memcpy) + std::memcpy(byteBuffer->getDirectBytes(), data, size); + } else { + // Sync: the JS ArrayBuffer outlives the call. Wrap its memory directly + // for zero-copy. + byteBuffer = jni::JByteBuffer::wrapBytes(data, size); + } + jarg->l = makeGlobalIfNecessary(byteBuffer.release()); } else { throw JavaTurboModuleInvalidArgumentTypeException( type, argIndex, methodName); @@ -963,6 +1026,37 @@ 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) { + // Zero-copy: a JNI global reference pins the ByteBuffer's memory for + // the lifetime of the JS ArrayBuffer. The destructor of + // JByteBufferMutableBuffer attaches the current thread before + // releasing the global ref, so it is safe to be invoked on any + // thread by the JS GC. + auto buffer = std::make_shared( + jni::make_global(jByteBuffer)); + 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/RCTInteropTurboModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.h index b3e98589eb14..0b05012cd77a 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.h @@ -73,7 +73,8 @@ class JSI_EXPORT ObjCInteropTurboModule : public ObjCTurboModule { const jsi::Value &arg, size_t i, NSInvocation *inv, - NSMutableArray *retainedObjectsForInvocation) override; + NSMutableArray *retainedObjectsForInvocation, + bool isSync) override; private: std::vector methodDescriptors_; diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm index 1e3738bcbc6e..e2e3b3e42d85 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm @@ -361,7 +361,8 @@ T RCTConvertTo(SEL selector, id json) const jsi::Value &jsiArg, size_t index, NSInvocation *inv, - NSMutableArray *retainedObjectsForInvocation) + NSMutableArray *retainedObjectsForInvocation, + bool isSync) { NSString *methodName = @(methodNameCStr); std::string methodJsSignature = name_ + "." + methodNameCStr + "()"; @@ -373,7 +374,7 @@ T RCTConvertTo(SEL selector, id json) SEL selector = selectorForType(argumentType); if ([RCTConvert respondsToSelector:selector]) { - id objCArg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES); + id objCArg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES, isSync); if (objCArgType == @encode(char)) { char arg = RCTConvertTo(selector, objCArg); @@ -536,8 +537,8 @@ T RCTConvertTo(SEL selector, id json) runtime, errorPrefix + "JavaScript argument must be a function. Got " + getType(runtime, jsiArg)); } - RCTResponseSenderBlock arg = - (RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES); + RCTResponseSenderBlock arg = (RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject( + runtime, jsiArg, jsInvoker_, YES, isSync); if (arg != nullptr) { [retainedObjectsForInvocation addObject:arg]; } @@ -551,8 +552,8 @@ T RCTConvertTo(SEL selector, id json) runtime, errorPrefix + "JavaScript argument must be a function. Got " + getType(runtime, jsiArg)); } - RCTResponseSenderBlock senderBlock = - (RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES); + RCTResponseSenderBlock senderBlock = (RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject( + runtime, jsiArg, jsInvoker_, YES, isSync); RCTResponseErrorBlock arg = ^(NSError *error) { senderBlock(@[ RCTJSErrorFromNSError(error) ]); }; @@ -582,7 +583,7 @@ T RCTConvertTo(SEL selector, id json) runtime, errorPrefix + "JavaScript argument must be a plain object. Got " + getType(runtime, jsiArg)); } - id arg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES); + id arg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES, isSync); RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend; RCTManagedPointer *box = convert([RCTCxxConvert class], selector, arg); diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h index d3fc87e6f148..fe62803e29e6 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h @@ -35,7 +35,8 @@ id convertJSIValueToObjCObject( jsi::Runtime &runtime, const jsi::Value &value, const std::shared_ptr &jsInvoker, - BOOL useNSNull = NO); + BOOL useNSNull = NO, + BOOL isSync = NO); } // namespace TurboModuleConvertUtils template <> @@ -121,7 +122,8 @@ class JSI_EXPORT ObjCTurboModule : public TurboModule { const jsi::Value &arg, size_t i, NSInvocation *inv, - NSMutableArray *retainedObjectsForInvocation); + NSMutableArray *retainedObjectsForInvocation, + bool isSync); private: // Does the NativeModule dispatch async methods to the JS thread? 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..acda062e592d 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 @@ -43,6 +43,29 @@ static int32_t getUniqueId() namespace facebook::react { +namespace { +// MutableBuffer backed by an NSMutableData. ARC retains the NSMutableData via +// the strong member, so the underlying bytes stay alive for the buffer's +// lifetime. +class NSMutableDataMutableBuffer final : public jsi::MutableBuffer { + public: + explicit NSMutableDataMutableBuffer(NSMutableData *data) : data_(data) {} + + size_t size() const override + { + return data_.length; + } + + uint8_t *data() override + { + return static_cast(data_.mutableBytes); + } + + private: + NSMutableData *data_; // strong (ARC) — keeps memory alive +}; +} // namespace + namespace TurboModuleConvertUtils { /** * All static helper functions are ObjC++ specific. @@ -102,6 +125,13 @@ 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]]) { + // Async path: the NSData lifetime cannot be guaranteed (it may be a + // temporary built inside the module). Copy into an owning + // VectorMutableBuffer so the JS ArrayBuffer is independent of the source. + auto data = (NSData *)value; + auto buffer = std::make_shared(static_cast(data.bytes), data.length); + return {runtime, jsi::ArrayBuffer(runtime, std::move(buffer))}; } else if (value == (id)kCFNull) { return jsi::Value::null(); } @@ -117,13 +147,15 @@ static int32_t getUniqueId() jsi::Runtime &runtime, const jsi::Array &value, const std::shared_ptr &jsInvoker, - BOOL useNSNull) + BOOL useNSNull, + BOOL isSync) { size_t size = value.size(runtime); NSMutableArray *result = [NSMutableArray new]; for (size_t i = 0; i < size; i++) { // Insert kCFNull when it's `undefined` value to preserve the indices. - id convertedObject = convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker, useNSNull); + id convertedObject = + convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker, useNSNull, isSync); [result addObject:(convertedObject != nullptr) ? convertedObject : (id)kCFNull]; } return result; @@ -133,7 +165,8 @@ static int32_t getUniqueId() jsi::Runtime &runtime, const jsi::Object &value, const std::shared_ptr &jsInvoker, - BOOL useNSNull) + BOOL useNSNull, + BOOL isSync) { jsi::Array propertyNames = value.getPropertyNames(runtime); size_t size = propertyNames.size(runtime); @@ -141,7 +174,7 @@ static int32_t getUniqueId() for (size_t i = 0; i < size; i++) { jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime); NSString *k = convertJSIStringToNSString(runtime, name); - id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker, useNSNull); + id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker, useNSNull, isSync); if (v != nullptr) { result[k] = v; } @@ -170,7 +203,8 @@ id convertJSIValueToObjCObject( jsi::Runtime &runtime, const jsi::Value &value, const std::shared_ptr &jsInvoker, - BOOL useNSNull) + BOOL useNSNull, + BOOL isSync) { if (value.isUndefined() || (value.isNull() && !useNSNull)) { return nil; @@ -190,12 +224,26 @@ id convertJSIValueToObjCObject( if (value.isObject()) { jsi::Object o = value.getObject(runtime); if (o.isArray(runtime)) { - return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker, useNSNull); + return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker, useNSNull, isSync); } if (o.isFunction(runtime)) { return convertJSIFunctionToCallback(runtime, o.getFunction(runtime), jsInvoker); } - return convertJSIObjectToNSDictionary(runtime, o, jsInvoker, useNSNull); + if (o.isArrayBuffer(runtime)) { + auto ab = o.getArrayBuffer(runtime); + if (isSync) { + // Sync: the JS ArrayBuffer outlives this call, so the NSMutableData + // can safely wrap its memory directly. freeWhenDone:NO ensures the + // JS-owned memory is not freed by NSMutableData. + return [NSMutableData dataWithBytesNoCopy:(void *)ab.data(runtime) length:ab.size(runtime) freeWhenDone:NO]; + } + // Async: the JS ArrayBuffer may be GC'd before the native module reads + // the buffer (e.g. dispatched to another queue, stored on a property). + // Copy into ObjC-owned NSMutableData to give the module independent + // ownership of the bytes. + return [NSMutableData dataWithBytes:ab.data(runtime) length:ab.size(runtime)]; + } + return convertJSIObjectToNSDictionary(runtime, o, jsInvoker, useNSNull, isSync); } throw jsi::JSError(runtime, "Unsupported jsi::Value kind"); @@ -526,6 +574,24 @@ 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: { + // Codegen guarantees the return type is NSMutableData *, but guard + // against modules that return a plain NSData (or anything else) and + // would otherwise UB on -mutableBytes. + if (result != nil && ![result isKindOfClass:[NSMutableData class]]) { + RCTLogError(@"TurboModule returned non-NSMutableData for ArrayBuffer return value."); + break; + } + auto mutableData = (NSMutableData *)result; + if (mutableData.length > 0) { + // Zero-copy: NSMutableDataMutableBuffer holds a strong (ARC) reference + // to the NSMutableData, keeping the bytes alive for the lifetime of + // the JS ArrayBuffer. + auto buffer = std::make_shared(mutableData); + returnValue = jsi::ArrayBuffer(runtime, std::move(buffer)); + } + break; + } } return returnValue; @@ -596,7 +662,8 @@ TraceSection s( const jsi::Value &arg, size_t i, NSInvocation *inv, - NSMutableArray *retainedObjectsForInvocation) + NSMutableArray *retainedObjectsForInvocation, + bool isSync) { if (arg.isBool()) { bool v = arg.getBool(); @@ -639,7 +706,7 @@ TraceSection s( * Convert arg to ObjC objects. */ BOOL enableModuleArgumentNSNullConversionIOS = ReactNativeFeatureFlags::enableModuleArgumentNSNullConversionIOS(); - id objCArg = convertJSIValueToObjCObject(runtime, arg, jsInvoker_, enableModuleArgumentNSNullConversionIOS); + id objCArg = convertJSIValueToObjCObject(runtime, arg, jsInvoker_, enableModuleArgumentNSNullConversionIOS, isSync); if (objCArg != nullptr) { NSString *methodNameNSString = @(methodName); @@ -728,7 +795,7 @@ TraceSection s( for (size_t i = 0; i < count; i++) { const jsi::Value &arg = args[i]; const std::string objCArgType = [methodSignature getArgumentTypeAtIndex:i + 2]; - setInvocationArg(runtime, methodName, objCArgType, arg, i, inv, retainedObjectsForInvocation); + setInvocationArg(runtime, methodName, objCArgType, arg, i, inv, retainedObjectsForInvocation, isSync); } if (isSync) { @@ -813,7 +880,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..32ff2d346a86 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -4742,6 +4742,17 @@ class facebook::react::SharedColor { public std::string toString() const noexcept; } +class facebook::react::SharedMutableBuffer : public facebook::jsi::MutableBuffer { + public SharedMutableBuffer(const facebook::react::SharedMutableBuffer&) = delete; + public SharedMutableBuffer(facebook::react::SharedMutableBuffer&&) = delete; + public SharedMutableBuffer(uint8_t* data, size_t size, std::function release = nullptr); + public facebook::react::SharedMutableBuffer& operator=(const facebook::react::SharedMutableBuffer&) = delete; + public facebook::react::SharedMutableBuffer& operator=(facebook::react::SharedMutableBuffer&&) = delete; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~SharedMutableBuffer() override; +} + class facebook::react::SpringAnimationDriver : public facebook::react::AnimationDriver { protected virtual bool update(double timeDeltaMs, bool restarting) override; public SpringAnimationDriver(int id, facebook::react::Tag animatedValueTag, std::optional endCallback, folly::dynamic config, facebook::react::NativeAnimatedNodesManager* manager); @@ -5372,6 +5383,13 @@ class facebook::react::ValueFactoryEventPayload : public facebook::react::EventP public virtual facebook::react::EventPayloadType getType() const override; } +class facebook::react::VectorMutableBuffer : public facebook::jsi::MutableBuffer { + public VectorMutableBuffer(const uint8_t* source, size_t size); + public VectorMutableBuffer(std::vector data); + public virtual size_t size() const override; + public virtual uint8_t* data() override; +} + class facebook::react::ViewComponentDescriptor : public facebook::react::ConcreteComponentDescriptor { public ViewComponentDescriptor(const facebook::react::ComponentDescriptorParameters& parameters); } @@ -6534,6 +6552,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -11547,11 +11566,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..3521a0cbbe69 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -4733,6 +4733,17 @@ class facebook::react::SharedColor { public std::string toString() const noexcept; } +class facebook::react::SharedMutableBuffer : public facebook::jsi::MutableBuffer { + public SharedMutableBuffer(const facebook::react::SharedMutableBuffer&) = delete; + public SharedMutableBuffer(facebook::react::SharedMutableBuffer&&) = delete; + public SharedMutableBuffer(uint8_t* data, size_t size, std::function release = nullptr); + public facebook::react::SharedMutableBuffer& operator=(const facebook::react::SharedMutableBuffer&) = delete; + public facebook::react::SharedMutableBuffer& operator=(facebook::react::SharedMutableBuffer&&) = delete; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~SharedMutableBuffer() override; +} + class facebook::react::SpringAnimationDriver : public facebook::react::AnimationDriver { protected virtual bool update(double timeDeltaMs, bool restarting) override; public SpringAnimationDriver(int id, facebook::react::Tag animatedValueTag, std::optional endCallback, folly::dynamic config, facebook::react::NativeAnimatedNodesManager* manager); @@ -5363,6 +5374,13 @@ class facebook::react::ValueFactoryEventPayload : public facebook::react::EventP public virtual facebook::react::EventPayloadType getType() const override; } +class facebook::react::VectorMutableBuffer : public facebook::jsi::MutableBuffer { + public VectorMutableBuffer(const uint8_t* source, size_t size); + public VectorMutableBuffer(std::vector data); + public virtual size_t size() const override; + public virtual uint8_t* data() override; +} + class facebook::react::ViewComponentDescriptor : public facebook::react::ConcreteComponentDescriptor { public ViewComponentDescriptor(const facebook::react::ComponentDescriptorParameters& parameters); } @@ -6525,6 +6543,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -11403,11 +11422,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 a32188996a13..31a1dddfc321 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -6441,7 +6441,7 @@ class facebook::react::ObjCInteropTurboModule : public facebook::react::ObjCTurb protected virtual NSString* getArgumentTypeName(facebook::jsi::Runtime& runtime, NSString* methodName, int argIndex) override; protected virtual facebook::jsi::Value convertReturnIdToJSIValue(facebook::jsi::Runtime& runtime, const char* methodName, facebook::react::TurboModuleMethodValueKind returnType, id result) override; protected virtual facebook::jsi::Value create(facebook::jsi::Runtime& runtime, const facebook::jsi::PropNameID& propName) override; - protected virtual void setInvocationArg(facebook::jsi::Runtime& runtime, const char* methodName, const std::string& objCArgType, const facebook::jsi::Value& arg, size_t i, NSInvocation* inv, NSMutableArray* retainedObjectsForInvocation) override; + protected virtual void setInvocationArg(facebook::jsi::Runtime& runtime, const char* methodName, const std::string& objCArgType, const facebook::jsi::Value& arg, size_t i, NSInvocation* inv, NSMutableArray* retainedObjectsForInvocation, bool isSync) override; public ObjCInteropTurboModule(const facebook::react::ObjCTurboModule::InitParams& params); public virtual std::vector getPropertyNames(facebook::jsi::Runtime& runtime) override; } @@ -6456,7 +6456,7 @@ struct facebook::react::ObjCInteropTurboModule::MethodDescriptor { class facebook::react::ObjCTurboModule : public facebook::react::TurboModule { protected virtual NSString* getArgumentTypeName(facebook::jsi::Runtime& runtime, NSString* methodName, int argIndex); protected virtual facebook::jsi::Value convertReturnIdToJSIValue(facebook::jsi::Runtime& runtime, const char* methodName, facebook::react::TurboModuleMethodValueKind returnType, id result); - protected virtual void setInvocationArg(facebook::jsi::Runtime& runtime, const char* methodName, const std::string& objCArgType, const facebook::jsi::Value& arg, size_t i, NSInvocation* inv, NSMutableArray* retainedObjectsForInvocation); + protected virtual void setInvocationArg(facebook::jsi::Runtime& runtime, const char* methodName, const std::string& objCArgType, const facebook::jsi::Value& arg, size_t i, NSInvocation* inv, NSMutableArray* retainedObjectsForInvocation, bool isSync); protected void setEventEmitterCallback(facebook::react::EventEmitterCallback eventEmitterCallback); protected void setMethodArgConversionSelector(NSString* methodName, size_t argIndex, NSString* fnName); public ObjCTurboModule(const facebook::react::ObjCTurboModule::InitParams& params); @@ -7326,6 +7326,17 @@ class facebook::react::SharedColor { public std::string toString() const noexcept; } +class facebook::react::SharedMutableBuffer : public facebook::jsi::MutableBuffer { + public SharedMutableBuffer(const facebook::react::SharedMutableBuffer&) = delete; + public SharedMutableBuffer(facebook::react::SharedMutableBuffer&&) = delete; + public SharedMutableBuffer(uint8_t* data, size_t size, std::function release = nullptr); + public facebook::react::SharedMutableBuffer& operator=(const facebook::react::SharedMutableBuffer&) = delete; + public facebook::react::SharedMutableBuffer& operator=(facebook::react::SharedMutableBuffer&&) = delete; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~SharedMutableBuffer() override; +} + class facebook::react::SpringAnimationDriver : public facebook::react::AnimationDriver { protected virtual bool update(double timeDeltaMs, bool restarting) override; public SpringAnimationDriver(int id, facebook::react::Tag animatedValueTag, std::optional endCallback, folly::dynamic config, facebook::react::NativeAnimatedNodesManager* manager); @@ -7933,6 +7944,13 @@ class facebook::react::ValueFactoryEventPayload : public facebook::react::EventP public virtual facebook::react::EventPayloadType getType() const override; } +class facebook::react::VectorMutableBuffer : public facebook::jsi::MutableBuffer { + public VectorMutableBuffer(const uint8_t* source, size_t size); + public VectorMutableBuffer(std::vector data); + public virtual size_t size() const override; + public virtual uint8_t* data() override; +} + class facebook::react::ViewComponentDescriptor : public facebook::react::ConcreteComponentDescriptor { public ViewComponentDescriptor(const facebook::react::ComponentDescriptorParameters& parameters); } @@ -9109,6 +9127,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -13672,7 +13691,7 @@ struct facebook::react::dom::RNMeasureRect { facebook::jsi::Value facebook::react::TurboModuleConvertUtils::convertObjCObjectToJSIValue(facebook::jsi::Runtime& runtime, id value); -id facebook::react::TurboModuleConvertUtils::convertJSIValueToObjCObject(facebook::jsi::Runtime& runtime, const facebook::jsi::Value& value, const std::shared_ptr& jsInvoker, BOOL useNSNull = NO); +id facebook::react::TurboModuleConvertUtils::convertJSIValueToObjCObject(facebook::jsi::Runtime& runtime, const facebook::jsi::Value& value, const std::shared_ptr& jsInvoker, BOOL useNSNull = NO, BOOL isSync = NO); static const facebook::react::Color facebook::react::HostPlatformColor::UndefinedColor; @@ -13765,11 +13784,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 d7818d3fe138..7e6502050a62 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -6438,7 +6438,7 @@ class facebook::react::ObjCInteropTurboModule : public facebook::react::ObjCTurb protected virtual NSString* getArgumentTypeName(facebook::jsi::Runtime& runtime, NSString* methodName, int argIndex) override; protected virtual facebook::jsi::Value convertReturnIdToJSIValue(facebook::jsi::Runtime& runtime, const char* methodName, facebook::react::TurboModuleMethodValueKind returnType, id result) override; protected virtual facebook::jsi::Value create(facebook::jsi::Runtime& runtime, const facebook::jsi::PropNameID& propName) override; - protected virtual void setInvocationArg(facebook::jsi::Runtime& runtime, const char* methodName, const std::string& objCArgType, const facebook::jsi::Value& arg, size_t i, NSInvocation* inv, NSMutableArray* retainedObjectsForInvocation) override; + protected virtual void setInvocationArg(facebook::jsi::Runtime& runtime, const char* methodName, const std::string& objCArgType, const facebook::jsi::Value& arg, size_t i, NSInvocation* inv, NSMutableArray* retainedObjectsForInvocation, bool isSync) override; public ObjCInteropTurboModule(const facebook::react::ObjCTurboModule::InitParams& params); public virtual std::vector getPropertyNames(facebook::jsi::Runtime& runtime) override; } @@ -6453,7 +6453,7 @@ struct facebook::react::ObjCInteropTurboModule::MethodDescriptor { class facebook::react::ObjCTurboModule : public facebook::react::TurboModule { protected virtual NSString* getArgumentTypeName(facebook::jsi::Runtime& runtime, NSString* methodName, int argIndex); protected virtual facebook::jsi::Value convertReturnIdToJSIValue(facebook::jsi::Runtime& runtime, const char* methodName, facebook::react::TurboModuleMethodValueKind returnType, id result); - protected virtual void setInvocationArg(facebook::jsi::Runtime& runtime, const char* methodName, const std::string& objCArgType, const facebook::jsi::Value& arg, size_t i, NSInvocation* inv, NSMutableArray* retainedObjectsForInvocation); + protected virtual void setInvocationArg(facebook::jsi::Runtime& runtime, const char* methodName, const std::string& objCArgType, const facebook::jsi::Value& arg, size_t i, NSInvocation* inv, NSMutableArray* retainedObjectsForInvocation, bool isSync); protected void setEventEmitterCallback(facebook::react::EventEmitterCallback eventEmitterCallback); protected void setMethodArgConversionSelector(NSString* methodName, size_t argIndex, NSString* fnName); public ObjCTurboModule(const facebook::react::ObjCTurboModule::InitParams& params); @@ -7317,6 +7317,17 @@ class facebook::react::SharedColor { public std::string toString() const noexcept; } +class facebook::react::SharedMutableBuffer : public facebook::jsi::MutableBuffer { + public SharedMutableBuffer(const facebook::react::SharedMutableBuffer&) = delete; + public SharedMutableBuffer(facebook::react::SharedMutableBuffer&&) = delete; + public SharedMutableBuffer(uint8_t* data, size_t size, std::function release = nullptr); + public facebook::react::SharedMutableBuffer& operator=(const facebook::react::SharedMutableBuffer&) = delete; + public facebook::react::SharedMutableBuffer& operator=(facebook::react::SharedMutableBuffer&&) = delete; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~SharedMutableBuffer() override; +} + class facebook::react::SpringAnimationDriver : public facebook::react::AnimationDriver { protected virtual bool update(double timeDeltaMs, bool restarting) override; public SpringAnimationDriver(int id, facebook::react::Tag animatedValueTag, std::optional endCallback, folly::dynamic config, facebook::react::NativeAnimatedNodesManager* manager); @@ -7924,6 +7935,13 @@ class facebook::react::ValueFactoryEventPayload : public facebook::react::EventP public virtual facebook::react::EventPayloadType getType() const override; } +class facebook::react::VectorMutableBuffer : public facebook::jsi::MutableBuffer { + public VectorMutableBuffer(const uint8_t* source, size_t size); + public VectorMutableBuffer(std::vector data); + public virtual size_t size() const override; + public virtual uint8_t* data() override; +} + class facebook::react::ViewComponentDescriptor : public facebook::react::ConcreteComponentDescriptor { public ViewComponentDescriptor(const facebook::react::ComponentDescriptorParameters& parameters); } @@ -9100,6 +9118,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -13538,7 +13557,7 @@ struct facebook::react::dom::RNMeasureRect { facebook::jsi::Value facebook::react::TurboModuleConvertUtils::convertObjCObjectToJSIValue(facebook::jsi::Runtime& runtime, id value); -id facebook::react::TurboModuleConvertUtils::convertJSIValueToObjCObject(facebook::jsi::Runtime& runtime, const facebook::jsi::Value& value, const std::shared_ptr& jsInvoker, BOOL useNSNull = NO); +id facebook::react::TurboModuleConvertUtils::convertJSIValueToObjCObject(facebook::jsi::Runtime& runtime, const facebook::jsi::Value& value, const std::shared_ptr& jsInvoker, BOOL useNSNull = NO, BOOL isSync = NO); static const facebook::react::Color facebook::react::HostPlatformColor::UndefinedColor; @@ -13631,11 +13650,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..0976319072ab 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -3302,6 +3302,17 @@ class facebook::react::SharedColor { public std::string toString() const noexcept; } +class facebook::react::SharedMutableBuffer : public facebook::jsi::MutableBuffer { + public SharedMutableBuffer(const facebook::react::SharedMutableBuffer&) = delete; + public SharedMutableBuffer(facebook::react::SharedMutableBuffer&&) = delete; + public SharedMutableBuffer(uint8_t* data, size_t size, std::function release = nullptr); + public facebook::react::SharedMutableBuffer& operator=(const facebook::react::SharedMutableBuffer&) = delete; + public facebook::react::SharedMutableBuffer& operator=(facebook::react::SharedMutableBuffer&&) = delete; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~SharedMutableBuffer() override; +} + class facebook::react::SpringAnimationDriver : public facebook::react::AnimationDriver { protected virtual bool update(double timeDeltaMs, bool restarting) override; public SpringAnimationDriver(int id, facebook::react::Tag animatedValueTag, std::optional endCallback, folly::dynamic config, facebook::react::NativeAnimatedNodesManager* manager); @@ -3821,6 +3832,13 @@ class facebook::react::ValueFactoryEventPayload : public facebook::react::EventP public virtual facebook::react::EventPayloadType getType() const override; } +class facebook::react::VectorMutableBuffer : public facebook::jsi::MutableBuffer { + public VectorMutableBuffer(const uint8_t* source, size_t size); + public VectorMutableBuffer(std::vector data); + public virtual size_t size() const override; + public virtual uint8_t* data() override; +} + class facebook::react::ViewComponentDescriptor : public facebook::react::ConcreteComponentDescriptor { public ViewComponentDescriptor(const facebook::react::ComponentDescriptorParameters& parameters); } @@ -4885,6 +4903,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -8585,11 +8604,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..4b688d96bcde 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -3293,6 +3293,17 @@ class facebook::react::SharedColor { public std::string toString() const noexcept; } +class facebook::react::SharedMutableBuffer : public facebook::jsi::MutableBuffer { + public SharedMutableBuffer(const facebook::react::SharedMutableBuffer&) = delete; + public SharedMutableBuffer(facebook::react::SharedMutableBuffer&&) = delete; + public SharedMutableBuffer(uint8_t* data, size_t size, std::function release = nullptr); + public facebook::react::SharedMutableBuffer& operator=(const facebook::react::SharedMutableBuffer&) = delete; + public facebook::react::SharedMutableBuffer& operator=(facebook::react::SharedMutableBuffer&&) = delete; + public virtual size_t size() const override; + public virtual uint8_t* data() override; + public ~SharedMutableBuffer() override; +} + class facebook::react::SpringAnimationDriver : public facebook::react::AnimationDriver { protected virtual bool update(double timeDeltaMs, bool restarting) override; public SpringAnimationDriver(int id, facebook::react::Tag animatedValueTag, std::optional endCallback, folly::dynamic config, facebook::react::NativeAnimatedNodesManager* manager); @@ -3812,6 +3823,13 @@ class facebook::react::ValueFactoryEventPayload : public facebook::react::EventP public virtual facebook::react::EventPayloadType getType() const override; } +class facebook::react::VectorMutableBuffer : public facebook::jsi::MutableBuffer { + public VectorMutableBuffer(const uint8_t* source, size_t size); + public VectorMutableBuffer(std::vector data); + public virtual size_t size() const override; + public virtual uint8_t* data() override; +} + class facebook::react::ViewComponentDescriptor : public facebook::react::ConcreteComponentDescriptor { public ViewComponentDescriptor(const facebook::react::ComponentDescriptorParameters& parameters); } @@ -4876,6 +4894,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -8576,11 +8595,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();