diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js index 58331fb2651f..d59c3d6b1890 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js @@ -268,9 +268,8 @@ function translateFunctionParamToJavaType( imports.add('com.facebook.react.bridge.Callback'); return wrapOptional('Callback', isRequired); case 'ArrayBufferTypeAnnotation': - throw new Error( - `${createErrorMessage(realTypeAnnotation.type)} ArrayBuffer is only supported for C++ TurboModules.`, - ); + imports.add('java.nio.ByteBuffer'); + return wrapOptional('ByteBuffer', isRequired); default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error(createErrorMessage(realTypeAnnotation.type)); @@ -366,9 +365,8 @@ function translateFunctionReturnTypeToJavaType( imports.add('com.facebook.react.bridge.WritableArray'); return wrapOptional('WritableArray', isRequired); case 'ArrayBufferTypeAnnotation': - throw new Error( - `${createErrorMessage(realTypeAnnotation.type)} ArrayBuffer is only supported for C++ TurboModules.`, - ); + imports.add('java.nio.ByteBuffer'); + return wrapOptional('ByteBuffer', isRequired); default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error(createErrorMessage(realTypeAnnotation.type)); @@ -452,9 +450,7 @@ function getFalsyReturnStatementFromReturnType( case 'ArrayTypeAnnotation': return 'return null;'; case 'ArrayBufferTypeAnnotation': - throw new Error( - `${createErrorMessage(realTypeAnnotation.type)} ArrayBuffer is only supported for C++ TurboModules.`, - ); + return 'return null;'; default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error(createErrorMessage(realTypeAnnotation.type)); diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js index c05b97f0b2d6..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, @@ -217,7 +218,7 @@ function translateReturnTypeToKind( case 'ArrayTypeAnnotation': return 'ArrayKind'; case 'ArrayBufferTypeAnnotation': - throw new Error('ArrayBuffer is only supported for C++ TurboModules.'); + return 'ArrayBufferKind'; default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( @@ -306,7 +307,7 @@ function translateParamTypeToJniType( case 'FunctionTypeAnnotation': return 'Lcom/facebook/react/bridge/Callback;'; case 'ArrayBufferTypeAnnotation': - throw new Error('ArrayBuffer is only supported for C++ TurboModules.'); + return 'Ljava/nio/ByteBuffer;'; default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( @@ -392,7 +393,7 @@ function translateReturnTypeToJniType( case 'ArrayTypeAnnotation': return 'Lcom/facebook/react/bridge/WritableArray;'; case 'ArrayBufferTypeAnnotation': - throw new Error('ArrayBuffer is only supported for C++ TurboModules.'); + return 'Ljava/nio/ByteBuffer;'; default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js index ce1fdfef0c6c..9f789ade20a0 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js @@ -51,7 +51,8 @@ type ReturnJSType = | 'ObjectKind' | 'ArrayKind' | 'NumberKind' - | 'StringKind'; + | 'StringKind' + | 'ArrayBufferKind'; export type MethodSerializationOutput = Readonly<{ methodName: string, @@ -219,6 +220,9 @@ function getParamObjCType( */ return notStruct(wrapOptional('NSArray *', !nullable)); } + case 'ArrayBufferTypeAnnotation': { + return notStruct(wrapOptional('NSMutableData *', !nullable)); + } } const [structTypeAnnotation] = unwrapNullable( @@ -388,9 +392,7 @@ function getReturnObjCType( case 'GenericObjectTypeAnnotation': return wrapOptional('NSDictionary *', isRequired); case 'ArrayBufferTypeAnnotation': - throw new Error( - `Unsupported return type for ${methodName}: ArrayBuffer is only supported for C++ TurboModules.`, - ); + return wrapOptional('NSMutableData *', isRequired); default: typeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( @@ -464,9 +466,7 @@ function getReturnJSType( throw new Error(`Unsupported union member types`); } case 'ArrayBufferTypeAnnotation': - throw new Error( - `Unsupported return type for ${methodName}: ArrayBuffer is only supported for C++ TurboModules.`, - ); + return 'ArrayBufferKind'; default: typeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( diff --git a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js index 2b1db52e4ba9..b3d3345244db 100644 --- a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js @@ -2678,7 +2678,7 @@ const ARRAY_BUFFER_NATIVE_MODULE: SchemaType = { ], }, moduleName: 'SampleTurboModule', - excludedPlatforms: ['iOS', 'android'], + excludedPlatforms: ['android'], }, }, }; 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 229db766e450..7612ea01b0f8 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 @@ -100,6 +100,34 @@ Map { #import +@protocol NativeSampleTurboModuleSpec + +- (NSMutableData *)getArrayBuffer; +- (void)voidArrayBuffer:(NSMutableData *)arg; +- (void)voidNullableArrayBuffer:(NSMutableData * _Nullable)arg; +- (void)promiseArrayBuffer:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject; + +@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 // array_buffer_native_module_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 e8ff005d5866..c15e20ab0bf1 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 @@ -58,6 +58,50 @@ Map { #import \\"array_buffer_native_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_voidArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, \\"voidArrayBuffer\\", @selector(voidArrayBuffer:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidNullableArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, \\"voidNullableArrayBuffer\\", @selector(voidNullableArrayBuffer:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, PromiseKind, \\"promiseArrayBuffer\\", @selector(promiseArrayBuffer:reject:), args, count); + } + + NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms) + : ObjCTurboModule(params) { + + methodMap_[\\"getArrayBuffer\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer}; + + + methodMap_[\\"voidArrayBuffer\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_voidArrayBuffer}; + + + methodMap_[\\"voidNullableArrayBuffer\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_voidNullableArrayBuffer}; + + + methodMap_[\\"promiseArrayBuffer\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseArrayBuffer}; + + } +} // namespace facebook::react ", } `; diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h index f1d51678bf54..6774209c3a47 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h @@ -31,6 +31,7 @@ enum TurboModuleMethodValueKind { ArrayKind, FunctionKind, PromiseKind, + ArrayBufferKind }; /** diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.h index b3e98589eb14..2341b82a11ab 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..13ce974846ea 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); @@ -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..b20c5ebdb95f 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..e7dcbb091ba2 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 @@ -35,6 +35,77 @@ using namespace facebook::react; using namespace facebook::react::TurboModuleConvertUtils; +// RCTZeroCopyData wraps externally owned memory as an NSMutableData +// without copying the underlying bytes. This is necessary because +// [NSMutableData (init|data)WithBytesNoCopy:length:] may still copy the data +// internally. +@interface RCTZeroCopyData : NSMutableData ++ (instancetype)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length; ++ (instancetype)dataWithMutableBuffer:(std::shared_ptr)mutableBuffer; +@end + +@interface RCTZeroCopyData () +- (instancetype)initWithExternalBytes:(void *)bytes + length:(NSUInteger)length + mutableBuffer:(std::shared_ptr)mutableBuffer; +@end + +@implementation RCTZeroCopyData { + void *_bytes; + NSUInteger _length; + std::shared_ptr _mutableBuffer; +} + +- (instancetype)initWithExternalBytes:(void *)bytes + length:(NSUInteger)length + mutableBuffer:(std::shared_ptr)mutableBuffer +{ + self = [super init]; + if (self) { + _bytes = bytes; + _length = length; + _mutableBuffer = std::move(mutableBuffer); + } + return self; +} + ++ (instancetype)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length +{ + return [[self alloc] initWithExternalBytes:bytes length:length mutableBuffer:nullptr]; +} + ++ (instancetype)dataWithMutableBuffer:(std::shared_ptr)mutableBuffer +{ + RCTAssert(mutableBuffer, @"Failed to create RCTZeroCopyData: mutableBuffer cannot be null"); + return [[self alloc] initWithExternalBytes:mutableBuffer->data() + length:mutableBuffer->size() + mutableBuffer:std::move(mutableBuffer)]; +} + +- (NSUInteger)length +{ + return _length; +} + +- (const void *)bytes +{ + return _bytes; +} + +- (void *)mutableBytes +{ + return _bytes; +} + +- (void)setLength:(NSUInteger)length +{ + if (length != _length) { + [NSException raise:NSRangeException format:@"RCTZeroCopyData's length is immutable."]; + } +} + +@end + static int32_t getUniqueId() { static int32_t counter = 0; @@ -89,6 +160,33 @@ static int32_t getUniqueId() return result; } +static jsi::ArrayBuffer convertNSMutableDataToJSIArrayBuffer(jsi::Runtime &runtime, NSMutableData *value) +{ + class MutableDataBuffer final : public jsi::MutableBuffer { + public: + explicit MutableDataBuffer(NSMutableData *data) : data_(data) + { + RCTAssert(data, @"Failed to create MutableDataBuffer: data cannot be null"); + } + + size_t size() const override + { + return data_.length; + } + + uint8_t *data() override + { + return static_cast(data_.mutableBytes); + } + + private: + NSMutableData *data_; + }; + auto buffer = std::make_shared(value); + auto arrayBuffer = jsi::ArrayBuffer(runtime, std::move(buffer)); + return arrayBuffer; +} + jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value) { if ([value isKindOfClass:[NSString class]]) { @@ -102,6 +200,8 @@ static int32_t getUniqueId() return convertNSDictionaryToJSIObject(runtime, (NSDictionary *)value); } else if ([value isKindOfClass:[NSArray class]]) { return convertNSArrayToJSIArray(runtime, (NSArray *)value); + } else if ([value isKindOfClass:[NSMutableData class]]) { + return convertNSMutableDataToJSIArrayBuffer(runtime, (NSMutableData *)value); } else if (value == (id)kCFNull) { return jsi::Value::null(); } @@ -117,13 +217,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 +235,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 +244,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; } @@ -166,11 +269,27 @@ static int32_t getUniqueId() }; } +static NSMutableData *convertJSIArrayBufferToNSMutableData(jsi::Runtime &rt, const jsi::ArrayBuffer &value, BOOL isSync) +{ + // Native-backed buffers can retain their MutableBuffer owner for safe + // zero-copy access, even when the NSMutableData escapes the JS thread. + if (auto mutableBuffer = value.tryGetMutableBuffer(rt)) { + return [RCTZeroCopyData dataWithMutableBuffer:std::move(mutableBuffer)]; + } + // Synchronous invocations can borrow JS-backed bytes for the duration of the call. + if (isSync) { + return [RCTZeroCopyData dataWithBytesNoCopy:value.data(rt) length:value.size(rt)]; + } + // Asynchronous JS-backed buffers need an owned copy before they can escape the JS thread. + return [NSMutableData dataWithBytes:value.data(rt) length:value.size(rt)]; +} + 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 +309,15 @@ 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)) { + return convertJSIArrayBufferToNSMutableData(runtime, o.getArrayBuffer(runtime), isSync); + } + return convertJSIObjectToNSDictionary(runtime, o, jsInvoker, useNSNull, isSync); } throw jsi::JSError(runtime, "Unsupported jsi::Value kind"); @@ -522,6 +644,10 @@ TraceSection s( returnValue = convertNSArrayToJSIArray(runtime, (NSArray *)result); break; } + case ArrayBufferKind: { + returnValue = convertNSMutableDataToJSIArrayBuffer(runtime, (NSMutableData *)result); + break; + } case FunctionKind: throw jsi::JSError(runtime, "convertReturnIdToJSIValue: FunctionKind is not supported yet."); case PromiseKind: @@ -596,7 +722,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 +766,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 +855,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 +940,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/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.kt b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.kt index e6ae5f5060f1..935a209da094 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.kt +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.kt @@ -27,6 +27,7 @@ import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.module.annotations.ReactModule import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings +import java.nio.ByteBuffer import java.util.UUID @DoNotStrip @@ -264,6 +265,30 @@ public class SampleTurboModule(private val context: ReactApplicationContext) : override fun invalidate(): Unit = Unit + @DoNotStrip + @Suppress("unused") + override fun getArrayBuffer(buffer: ByteBuffer) : ByteBuffer { + error("ArrayBuffer is not yet supported in Java TurboModules") + } + + @DoNotStrip + @Suppress("unused") + override fun createNativeBuffer(size: Double) : ByteBuffer { + error("ArrayBuffer is not yet supported in Java TurboModules") + } + + @DoNotStrip + @Suppress("unused") + override fun processAsyncBuffer(payload: ByteBuffer, promise: Promise) { + promise.reject("UNSUPPORTED", "ArrayBuffer is not yet supported in Java TurboModules") + } + + @DoNotStrip + @Suppress("unused") + override fun getAsyncBuffer(size: Double, promise: Promise) { + promise.reject("UNSUPPORTED", "ArrayBuffer is not yet supported in Java TurboModules") + } + override fun getName(): String { return NAME } diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm index a412dc955b43..c2016f7dc5c8 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm @@ -16,6 +16,16 @@ using namespace facebook::react; +static NSMutableData *RCTCreateIntegerSequenceBuffer(NSUInteger size) +{ + NSMutableData *buffer = [NSMutableData dataWithLength:size]; + auto *bytes = static_cast(buffer.mutableBytes); + for (NSUInteger i = 0; i < size; i++) { + bytes[i] = i + 1; + } + return buffer; +} + @interface RCTSampleTurboModule () @end @@ -145,6 +155,49 @@ - (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime }; } +RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSMutableData *, getArrayBuffer : (NSMutableData *)buffer) +{ + auto data = static_cast(buffer.mutableBytes); + auto size = buffer.length; + for (size_t i = 0; i < size; ++i) { + data[i] = 10 * i; + } + return buffer; +} + +RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSMutableData *, createNativeBuffer : (double)size) +{ + return RCTCreateIntegerSequenceBuffer(static_cast(size)); +} + +RCT_EXPORT_METHOD( + processAsyncBuffer : (NSMutableData *)payload resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) +{ + if ((resolve == nullptr) || (reject == nullptr)) { + return; + } + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + uint8_t *bytes = static_cast(payload.mutableBytes); + double sum = 0; + for (NSInteger i = 0; i < payload.length; ++i) { + sum += bytes[i]; + } + resolve(@(sum)); + }); +} + +RCT_EXPORT_METHOD(getAsyncBuffer : (double)size resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) +{ + if ((resolve == nullptr) || (reject == nullptr)) { + return; + } + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + resolve(RCTCreateIntegerSequenceBuffer(static_cast(size))); + }); +} + RCT_EXPORT_METHOD(getValueWithCallback : (RCTResponseSenderBlock)callback) { if (callback == nullptr) { diff --git a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js index 65ee3fe605c1..3f4b77c4801c 100644 --- a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js +++ b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js @@ -51,6 +51,10 @@ export interface Spec extends TurboModule { readonly getUnsafeObject: (arg: UnsafeObject) => UnsafeObject; readonly getRootTag: (arg: RootTag) => RootTag; readonly getValue: (x: number, y: string, z: Object) => Object; + readonly getArrayBuffer: (buffer: ArrayBuffer) => ArrayBuffer; + readonly createNativeBuffer: (size: number) => ArrayBuffer; + readonly processAsyncBuffer: (payload: ArrayBuffer) => Promise; + readonly getAsyncBuffer: (size: number) => Promise; readonly getValueWithCallback: (callback: (value: string) => void) => void; readonly getValueWithPromise: (error: boolean) => Promise; readonly voidFuncThrows?: () => void; diff --git a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj index 898cd14e1ca5..2f6e9c5ea8d2 100644 --- a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj +++ b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ E7DB216722B2F69F005AC45F /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7DB213022B2C649005AC45F /* JavaScriptCore.framework */; }; E7DB218C22B41FCD005AC45F /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7DB218B22B41FCD005AC45F /* XCTest.framework */; }; F0D621C32BBB9E38005960AC /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = F0D621C22BBB9E38005960AC /* PrivacyInfo.xcprivacy */; }; + F1A0B1C23D4E5F6071829302 /* RCTTurboModuleArrayBufferTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F1A0B1C23D4E5F6071829301 /* RCTTurboModuleArrayBufferTests.mm */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -156,6 +157,7 @@ E7DB215D22B2F3EC005AC45F /* RNTesterTestModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNTesterTestModule.m; sourceTree = ""; }; E7DB218B22B41FCD005AC45F /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = XCTest.framework; sourceTree = DEVELOPER_DIR; }; F0D621C22BBB9E38005960AC /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + F1A0B1C23D4E5F6071829301 /* RCTTurboModuleArrayBufferTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTTurboModuleArrayBufferTests.mm; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -332,6 +334,7 @@ E7DB20AA22B2BAA3005AC45F /* RCTModuleInitNotificationRaceTests.m */, E7DB20B022B2BAA4005AC45F /* RCTModuleInitTests.m */, E7DB20CB22B2BAA5005AC45F /* RCTModuleMethodTests.mm */, + F1A0B1C23D4E5F6071829301 /* RCTTurboModuleArrayBufferTests.mm */, E7DB20CF22B2BAA5005AC45F /* RCTMultipartStreamReaderTests.m */, A975CA6B2C05EADE0043F72A /* RCTNetworkTaskTests.m */, E7DB20BE22B2BAA4005AC45F /* RCTNativeAnimatedNodesManagerTests.m */, @@ -746,6 +749,7 @@ E7DB20EC22B2BAA6005AC45F /* RCTMultipartStreamReaderTests.m in Sources */, E7DB20E022B2BAA6005AC45F /* RCTMethodArgumentTests.m in Sources */, E7DB20E822B2BAA6005AC45F /* RCTModuleMethodTests.mm in Sources */, + F1A0B1C23D4E5F6071829302 /* RCTTurboModuleArrayBufferTests.mm in Sources */, E7DB20E222B2BAA6005AC45F /* RCTGzipTests.m in Sources */, E7DB20ED22B2BAA6005AC45F /* RCTURLUtilsTests.m in Sources */, E7DB20D322B2BAA6005AC45F /* RCTBlobManagerTests.m in Sources */, diff --git a/packages/rn-tester/RNTesterUnitTests/RCTTurboModuleArrayBufferTests.mm b/packages/rn-tester/RNTesterUnitTests/RCTTurboModuleArrayBufferTests.mm new file mode 100644 index 000000000000..b1b1c2b6d324 --- /dev/null +++ b/packages/rn-tester/RNTesterUnitTests/RCTTurboModuleArrayBufferTests.mm @@ -0,0 +1,361 @@ +#import + +#import +#import +#import +#import + +#import +#import + +using namespace facebook::react; + +namespace { + +std::shared_ptr createHermesRuntime(bool enableMicrotaskQueue = false) +{ + if (!enableMicrotaskQueue) { + return facebook::hermes::makeHermesRuntime(); + } + + return facebook::hermes::makeHermesRuntime(::hermes::vm::RuntimeConfig::Builder().withMicrotaskQueue(true).build()); +} + +NSMutableData *createIntegerSequenceData(NSUInteger size) +{ + NSMutableData *data = [NSMutableData dataWithLength:size]; + auto *bytes = static_cast(data.mutableBytes); + for (NSUInteger i = 0; i < size; ++i) { + bytes[i] = static_cast(i); + } + return data; +} + +std::vector bytesFromData(NSData *data) +{ + if (data == nil) { + return {}; + } + + auto *bytes = static_cast(data.bytes); + return std::vector(bytes, bytes + data.length); +} + +std::vector bytesFromArrayBuffer(facebook::jsi::Runtime &runtime, const facebook::jsi::ArrayBuffer &buffer) +{ + auto *bytes = buffer.data(runtime); + return std::vector(bytes, bytes + buffer.size(runtime)); +} + +class ImmediateNativeMethodCallInvoker final : public NativeMethodCallInvoker { + public: + void invokeAsync(const std::string &, NativeMethodCallFunc &&func) noexcept override + { + func(); + } + + void invokeSync(const std::string &, NativeMethodCallFunc &&func) noexcept override + { + func(); + } +}; + +class QueueingNativeMethodCallInvoker final : public NativeMethodCallInvoker { + public: + void invokeAsync(const std::string &, NativeMethodCallFunc &&func) noexcept override + { + queue_.push_back(std::move(func)); + } + + void invokeSync(const std::string &, NativeMethodCallFunc &&func) noexcept override + { + func(); + } + + void flushQueue() + { + while (!queue_.empty()) { + auto func = std::move(queue_.front()); + queue_.pop_front(); + func(); + } + } + + private: + std::list queue_; +}; + +} // namespace + +@interface RCTTestArrayBufferTurboModule : NSObject + +@property (nonatomic, copy) NSData *lastReceivedPayload; + +@end + +@implementation RCTTestArrayBufferTurboModule + +RCT_EXPORT_MODULE() + +RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSMutableData *, testMethodWhichMutatesArrayBuffer : (NSMutableData *)buffer) +{ + auto *bytes = static_cast(buffer.mutableBytes); + for (NSUInteger i = 0; i < buffer.length; ++i) { + bytes[i] = static_cast((i + 1) * 10); + } + return buffer; +} + +RCT_EXPORT_METHOD(testMethodWhichStoresArrayBuffer : (NSMutableData *)payload) +{ + self.lastReceivedPayload = [payload copy]; +} + +RCT_EXPORT_METHOD(testMethodWhichStoresNestedArrayBuffer : (NSDictionary *)payload) +{ + NSDictionary *nestedPayload = payload[@"nested"]; + self.lastReceivedPayload = [nestedPayload[@"buffer"] copy]; +} + +RCT_EXPORT_METHOD(testMethodWhichReturnsArrayBuffer + : (double)size resolve + : (RCTPromiseResolveBlock)resolve reject + : (RCTPromiseRejectBlock)reject) +{ + if (resolve == nil || reject == nil) { + return; + } + + resolve(createIntegerSequenceData(static_cast(size))); +} + +@end + +@interface RCTTurboModuleArrayBufferTests : XCTestCase + +@end + +@implementation RCTTurboModuleArrayBufferTests + +- (void)testSyncArrayBufferRoundTrip +{ + auto hermesRuntime = createHermesRuntime(); + facebook::jsi::Runtime *rt = hermesRuntime.get(); + auto *instance = [RCTTestArrayBufferTurboModule new]; + + ObjCTurboModule::InitParams params = { + .moduleName = "TestModule", + .instance = instance, + .jsInvoker = nullptr, + .nativeMethodCallInvoker = std::make_shared(), + .isSyncModule = false, + }; + ObjCTurboModule module(params); + + auto sourceBuffer = rt->global() + .getPropertyAsFunction(*rt, "eval") + .call(*rt, "new Uint8Array([1, 2, 3]).buffer") + .asObject(*rt) + .getArrayBuffer(*rt); + facebook::jsi::Value args[1] = {facebook::jsi::Value(*rt, sourceBuffer)}; + + auto result = module.invokeObjCMethod( + *rt, + ArrayBufferKind, + "testMethodWhichMutatesArrayBuffer", + @selector(testMethodWhichMutatesArrayBuffer:), + args, + 1); + + XCTAssertTrue(result.isObject()); + XCTAssertTrue(result.asObject(*rt).isArrayBuffer(*rt)); + + auto returnedBuffer = result.asObject(*rt).getArrayBuffer(*rt); + auto returnedBytes = bytesFromArrayBuffer(*rt, returnedBuffer); + XCTAssertEqual(returnedBytes.size(), 3u); + XCTAssertEqual(returnedBytes[0], 10); + XCTAssertEqual(returnedBytes[1], 20); + XCTAssertEqual(returnedBytes[2], 30); +} + +- (void)testAsyncJSBackedArrayBufferIsCopied +{ + auto hermesRuntime = createHermesRuntime(); + facebook::jsi::Runtime *rt = hermesRuntime.get(); + auto nativeInvoker = std::make_shared(); + auto *instance = [RCTTestArrayBufferTurboModule new]; + + ObjCTurboModule::InitParams params = { + .moduleName = "TestModule", + .instance = instance, + .jsInvoker = nullptr, + .nativeMethodCallInvoker = nativeInvoker, + .isSyncModule = false, + }; + ObjCTurboModule module(params); + + auto sourceBuffer = rt->global() + .getPropertyAsFunction(*rt, "eval") + .call(*rt, "new Uint8Array([1, 2, 3]).buffer") + .asObject(*rt) + .getArrayBuffer(*rt); + facebook::jsi::Value args[1] = {facebook::jsi::Value(*rt, sourceBuffer)}; + + module.invokeObjCMethod( + *rt, VoidKind, "testMethodWhichStoresArrayBuffer", @selector(testMethodWhichStoresArrayBuffer:), args, 1); + + auto *sourceBytes = sourceBuffer.data(*rt); + sourceBytes[0] = 9; + sourceBytes[1] = 8; + sourceBytes[2] = 7; + + nativeInvoker->flushQueue(); + + auto receivedBytes = bytesFromData(instance.lastReceivedPayload); + XCTAssertEqual(receivedBytes.size(), 3u); + XCTAssertEqual(receivedBytes[0], 1); + XCTAssertEqual(receivedBytes[1], 2); + XCTAssertEqual(receivedBytes[2], 3); +} + +- (void)testAsyncNestedJSBackedArrayBufferIsCopied +{ + auto hermesRuntime = createHermesRuntime(); + facebook::jsi::Runtime *rt = hermesRuntime.get(); + auto nativeInvoker = std::make_shared(); + auto *instance = [RCTTestArrayBufferTurboModule new]; + + ObjCTurboModule::InitParams params = { + .moduleName = "TestModule", + .instance = instance, + .jsInvoker = nullptr, + .nativeMethodCallInvoker = nativeInvoker, + .isSyncModule = false, + }; + ObjCTurboModule module(params); + + auto sourceObject = rt->global() + .getPropertyAsFunction(*rt, "eval") + .call(*rt, "({nested: {buffer: new Uint8Array([1, 2, 3]).buffer}})") + .asObject(*rt); + auto sourceBuffer = sourceObject.getProperty(*rt, "nested") + .asObject(*rt) + .getProperty(*rt, "buffer") + .asObject(*rt) + .getArrayBuffer(*rt); + facebook::jsi::Value args[1] = {facebook::jsi::Value(*rt, sourceObject)}; + + module.invokeObjCMethod( + *rt, + VoidKind, + "testMethodWhichStoresNestedArrayBuffer", + @selector(testMethodWhichStoresNestedArrayBuffer:), + args, + 1); + + auto *sourceBytes = sourceBuffer.data(*rt); + sourceBytes[0] = 9; + sourceBytes[1] = 8; + sourceBytes[2] = 7; + + nativeInvoker->flushQueue(); + + auto receivedBytes = bytesFromData(instance.lastReceivedPayload); + XCTAssertEqual(receivedBytes.size(), 3u); + XCTAssertEqual(receivedBytes[0], 1); + XCTAssertEqual(receivedBytes[1], 2); + XCTAssertEqual(receivedBytes[2], 3); +} + +- (void)testAsyncNativeBackedArrayBufferRetainsBackingStore +{ + auto hermesRuntime = createHermesRuntime(); + facebook::jsi::Runtime *rt = hermesRuntime.get(); + auto nativeInvoker = std::make_shared(); + auto *instance = [RCTTestArrayBufferTurboModule new]; + + ObjCTurboModule::InitParams params = { + .moduleName = "TestModule", + .instance = instance, + .jsInvoker = nullptr, + .nativeMethodCallInvoker = nativeInvoker, + .isSyncModule = false, + }; + ObjCTurboModule module(params); + + facebook::jsi::Value args[1] = {facebook::jsi::Value::undefined()}; + { + auto nativeBuffer = std::make_shared(std::vector{4, 5, 6, 7}); + auto sourceBuffer = facebook::jsi::ArrayBuffer(*rt, nativeBuffer); + args[0] = facebook::jsi::Value(*rt, sourceBuffer); + } + + module.invokeObjCMethod( + *rt, VoidKind, "testMethodWhichStoresArrayBuffer", @selector(testMethodWhichStoresArrayBuffer:), args, 1); + args[0] = facebook::jsi::Value::undefined(); + + nativeInvoker->flushQueue(); + + auto receivedBytes = bytesFromData(instance.lastReceivedPayload); + XCTAssertEqual(receivedBytes.size(), 4u); + XCTAssertEqual(receivedBytes[0], 4); + XCTAssertEqual(receivedBytes[1], 5); + XCTAssertEqual(receivedBytes[2], 6); + XCTAssertEqual(receivedBytes[3], 7); +} + +- (void)testPromiseResolvesArrayBuffer +{ + auto hermesRuntime = createHermesRuntime(true); + facebook::jsi::Runtime *rt = hermesRuntime.get(); + auto jsInvoker = std::make_shared(*rt); + auto *instance = [RCTTestArrayBufferTurboModule new]; + + ObjCTurboModule::InitParams params = { + .moduleName = "TestModule", + .instance = instance, + .jsInvoker = jsInvoker, + .nativeMethodCallInvoker = std::make_shared(), + .isSyncModule = false, + }; + ObjCTurboModule module(params); + + facebook::jsi::Value args[1] = {facebook::jsi::Value(4.0)}; + auto promiseValue = module.invokeObjCMethod( + *rt, + PromiseKind, + "testMethodWhichReturnsArrayBuffer", + @selector(testMethodWhichReturnsArrayBuffer:resolve:reject:), + args, + 1); + + auto promise = promiseValue.asObject(*rt); + auto then = promise.getPropertyAsFunction(*rt, "then"); + + std::vector resolvedBytes; + auto onResolved = facebook::jsi::Function::createFromHostFunction( + *rt, + facebook::jsi::PropNameID::forAscii(*rt, "onResolved"), + 1, + [&resolvedBytes]( + facebook::jsi::Runtime &runtime, + const facebook::jsi::Value &, + const facebook::jsi::Value *callbackArgs, + size_t count) -> facebook::jsi::Value { + if (count == 1 && callbackArgs[0].isObject() && callbackArgs[0].asObject(runtime).isArrayBuffer(runtime)) { + resolvedBytes = bytesFromArrayBuffer(runtime, callbackArgs[0].asObject(runtime).getArrayBuffer(runtime)); + } + return facebook::jsi::Value::undefined(); + }); + then.callWithThis(*rt, promise, onResolved); + + jsInvoker->flushQueue(); + + XCTAssertEqual(resolvedBytes.size(), 4u); + XCTAssertEqual(resolvedBytes[0], 0); + XCTAssertEqual(resolvedBytes[1], 1); + XCTAssertEqual(resolvedBytes[2], 2); + XCTAssertEqual(resolvedBytes[3], 3); +} + +@end diff --git a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js index c1c5803c06c1..aa5a073d7f97 100644 --- a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js +++ b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js @@ -46,6 +46,10 @@ type Examples = | 'getString' | 'getUnion' | 'getValue' + | 'getArrayBuffer' + | 'createNativeBuffer' + | 'processAsyncBuffer' + | 'getAsyncBuffer' | 'promise' | 'rejectPromise' | 'voidFunc' @@ -108,6 +112,39 @@ class SampleTurboModuleExample extends React.Component<{}, State> { getRootTag: () => NativeSampleTurboModule.getRootTag(this.context), getValue: () => NativeSampleTurboModule.getValue(5, 'test', {a: 1, b: 'foo'}), + getArrayBuffer: () => { + const view = new Uint8Array([1, 2, 3, 4, 5]); + const buffer = NativeSampleTurboModule?.getArrayBuffer(view.buffer); + return new Uint8Array(buffer || []).toString(); + }, + createNativeBuffer: () => { + const buffer = NativeSampleTurboModule?.createNativeBuffer(8); + return new Uint8Array(buffer || []).toString(); + }, + processAsyncBuffer: async () => { + const view = new Uint8Array([10, 20, 30, 40, 50]); + const nativeBuffer = NativeSampleTurboModule?.createNativeBuffer(10); + + const [byteSum, zeroCopyByteSum] = await Promise.all([ + NativeSampleTurboModule?.processAsyncBuffer(view.buffer), + nativeBuffer != null + ? NativeSampleTurboModule?.processAsyncBuffer(nativeBuffer) + : Promise.resolve(0), + ]); + + this._setResult( + 'processAsyncBuffer', + `sum=${(byteSum ?? 0) + (zeroCopyByteSum ?? 0)}`, + ); + }, + getAsyncBuffer: async () => { + return NativeSampleTurboModule?.getAsyncBuffer(10).then(buffer => { + this._setResult( + 'getAsyncBuffer', + new Uint8Array(buffer || []).toString(), + ); + }); + }, }; // $FlowFixMe[missing-local-annot] diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 2b4fbb3a4986..56a89e6922a8 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -6585,6 +6585,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 06d8694b4b82..09ec3f15df12 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -6411,6 +6411,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 1f7567f39773..e4130cb13f8d 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -6576,6 +6576,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index d4f7c124a3cb..abefd801ca31 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -2850,6 +2850,8 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual NSDictionary* getObjectThrows:(NSDictionary* arg); public virtual NSDictionary* getUnsafeObject:(NSDictionary* arg); public virtual NSDictionary* getValue:y:z:(double x, NSString* y, NSDictionary* z); + public virtual NSMutableData* createNativeBuffer:(double size); + public virtual NSMutableData* getArrayBuffer:(NSMutableData* buffer); public virtual NSNumber* getBool:(BOOL arg); public virtual NSNumber* getEnum:(double arg); public virtual NSNumber* getNumber:(double arg); @@ -2857,9 +2859,11 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual NSString* getString:(NSString* arg); public virtual facebook::react::ModuleConstants constantsToExport(); public virtual facebook::react::ModuleConstants getConstants(); + public virtual void getAsyncBuffer:resolve:reject:(double size, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void getImageUrl:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void getValueWithCallback:(RCTResponseSenderBlock callback); public virtual void getValueWithPromise:resolve:reject:(BOOL error, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); + public virtual void processAsyncBuffer:resolve:reject:(NSMutableData* payload, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void promiseAssert:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void promiseThrows:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void voidFunc(); @@ -6488,7 +6492,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; } @@ -6503,7 +6507,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); @@ -9177,6 +9181,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -13777,7 +13782,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; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index cd89d347ccfa..8241d7338c10 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -2499,6 +2499,8 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual NSDictionary* getObjectThrows:(NSDictionary* arg); public virtual NSDictionary* getUnsafeObject:(NSDictionary* arg); public virtual NSDictionary* getValue:y:z:(double x, NSString* y, NSDictionary* z); + public virtual NSMutableData* createNativeBuffer:(double size); + public virtual NSMutableData* getArrayBuffer:(NSMutableData* buffer); public virtual NSNumber* getBool:(BOOL arg); public virtual NSNumber* getEnum:(double arg); public virtual NSNumber* getNumber:(double arg); @@ -2506,9 +2508,11 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual NSString* getString:(NSString* arg); public virtual facebook::react::ModuleConstants constantsToExport(); public virtual facebook::react::ModuleConstants getConstants(); + public virtual void getAsyncBuffer:resolve:reject:(double size, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void getImageUrl:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void getValueWithCallback:(RCTResponseSenderBlock callback); public virtual void getValueWithPromise:resolve:reject:(BOOL error, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); + public virtual void processAsyncBuffer:resolve:reject:(NSMutableData* payload, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void promiseAssert:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void promiseThrows:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void voidFunc(); @@ -5996,7 +6000,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; } @@ -6011,7 +6015,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); @@ -8652,6 +8656,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -13098,7 +13103,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; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 8219222a7c4d..0adcebd99ea5 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -2850,6 +2850,8 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual NSDictionary* getObjectThrows:(NSDictionary* arg); public virtual NSDictionary* getUnsafeObject:(NSDictionary* arg); public virtual NSDictionary* getValue:y:z:(double x, NSString* y, NSDictionary* z); + public virtual NSMutableData* createNativeBuffer:(double size); + public virtual NSMutableData* getArrayBuffer:(NSMutableData* buffer); public virtual NSNumber* getBool:(BOOL arg); public virtual NSNumber* getEnum:(double arg); public virtual NSNumber* getNumber:(double arg); @@ -2857,9 +2859,11 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual NSString* getString:(NSString* arg); public virtual facebook::react::ModuleConstants constantsToExport(); public virtual facebook::react::ModuleConstants getConstants(); + public virtual void getAsyncBuffer:resolve:reject:(double size, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void getImageUrl:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void getValueWithCallback:(RCTResponseSenderBlock callback); public virtual void getValueWithPromise:resolve:reject:(BOOL error, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); + public virtual void processAsyncBuffer:resolve:reject:(NSMutableData* payload, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void promiseAssert:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void promiseThrows:reject:(RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject); public virtual void voidFunc(); @@ -6485,7 +6489,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; } @@ -6500,7 +6504,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); @@ -9168,6 +9172,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, @@ -13640,7 +13645,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; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 5f68b22fb882..657973ef6e46 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -4921,6 +4921,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 81705abe51bd..587f0131ad8d 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -4787,6 +4787,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind, diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 29810fb16f09..e96cbecbb5d1 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -4912,6 +4912,7 @@ enum facebook::react::TransformOperationType : uint8_t { } enum facebook::react::TurboModuleMethodValueKind { + ArrayBufferKind, ArrayKind, BooleanKind, FunctionKind,