From 4ecbe04819f9d0daab320b4932b25b5533b00b62 Mon Sep 17 00:00:00 2001 From: Kamil Paradowski Date: Thu, 13 Aug 2026 14:04:02 +0200 Subject: [PATCH] feat: support Promise in Java and ObjC TurboModules --- .../modules/GenerateModuleJavaSpec.js | 11 +- .../modules/GenerateModuleJniCpp.js | 34 ++- .../GenerateModuleObjCpp/serializeMethod.js | 6 - .../src/generators/modules/Utils.js | 42 ---- .../modules/__test_fixtures__/fixtures.js | 38 ++-- .../__tests__/GenerateModuleHObjCpp-test.js | 40 ---- .../__tests__/GenerateModuleJavaSpec-test.js | 35 ---- .../__tests__/GenerateModuleJniCpp-test.js | 39 ---- .../GenerateModuleH-test.js.snap | 45 +--- .../GenerateModuleHObjCpp-test.js.snap | 46 +---- .../GenerateModuleJavaSpec-test.js.snap | 11 +- .../GenerateModuleJniCpp-test.js.snap | 116 +++++------ .../GenerateModuleJniH-test.js.snap | 59 ------ .../GenerateModuleMm-test.js.snap | 36 ++-- .../bridge/CxxArrayBufferCallbackImpl.kt | 57 +++++ .../main/jni/react/jni/JArrayBufferCallback.h | 59 ++++++ .../src/main/jni/react/jni/OnLoad-common.cpp | 2 + .../android/ReactCommon/JavaTurboModule.cpp | 195 +++++++++++++++--- .../android/ReactCommon/JavaTurboModule.h | 3 +- .../platform/android/SampleTurboModule.kt | 21 ++ .../ios/ReactCommon/RCTSampleTurboModule.mm | 19 ++ .../modules/NativeSampleTurboModule.js | 1 + .../TurboModule/SampleTurboModuleExample.js | 5 + .../api-snapshots/ReactAndroidDebugCxx.api | 7 +- .../api-snapshots/ReactAndroidNewarchCxx.api | 7 +- .../api-snapshots/ReactAndroidReleaseCxx.api | 7 +- .../api-snapshots/ReactAppleDebugCxx.api | 1 + .../api-snapshots/ReactAppleNewarchCxx.api | 1 + .../api-snapshots/ReactAppleReleaseCxx.api | 1 + 29 files changed, 481 insertions(+), 463 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxArrayBufferCallbackImpl.kt create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/jni/JArrayBufferCallback.h diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js index 9dc0b42ce8f6..c038f166e319 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js @@ -25,11 +25,7 @@ import type {AliasResolver} from './Utils'; const {unwrapNullable} = require('../../parsers/parsers-commons'); const {wrapOptional} = require('../TypeUtils/Java'); const {parseValidUnionType, toPascalCase} = require('../Utils'); -const { - createAliasResolver, - getModules, - throwIfUnsupportedPromiseArrayBuffer, -} = require('./Utils'); +const {createAliasResolver, getModules} = require('./Utils'); type FilesOutput = Map; @@ -599,11 +595,6 @@ module.exports = { method.typeAnnotation, ); - throwIfUnsupportedPromiseArrayBuffer( - method.name, - methodTypeAnnotation.returnTypeAnnotation, - ); - // Handle return type const translatedReturnType = translateFunctionReturnTypeToJavaType( methodTypeAnnotation.returnTypeAnnotation, diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js index 7c088461107c..5085004a46dc 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js @@ -24,11 +24,7 @@ import type {AliasResolver} from './Utils'; const {unwrapNullable} = require('../../parsers/parsers-commons'); const {parseValidUnionType} = require('../Utils'); -const { - createAliasResolver, - getModules, - throwIfUnsupportedPromiseArrayBuffer, -} = require('./Utils'); +const {createAliasResolver, getModules} = require('./Utils'); type FilesOutput = Map; @@ -47,15 +43,20 @@ const HostFunctionTemplate = ({ propertyName, jniSignature, jsReturnType, + promiseResolveSupportsArrayBuffer, }: Readonly<{ hasteModuleName: string, propertyName: string, jniSignature: string, jsReturnType: JSReturnType, + promiseResolveSupportsArrayBuffer: boolean, }>) => { + const promiseResolveSupportsArrayBufferArg = `, ${ + promiseResolveSupportsArrayBuffer ? 'true' : 'false' + }`; return `static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${propertyName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId${promiseResolveSupportsArrayBufferArg}); }`; }; @@ -406,6 +407,23 @@ function translateReturnTypeToJniType( } } +function doesPromiseResolveSupportArrayBuffer( + nullableReturnTypeAnnotation: Nullable, +): boolean { + const [returnTypeAnnotation] = + unwrapNullable( + nullableReturnTypeAnnotation, + ); + if (returnTypeAnnotation.type !== 'PromiseTypeAnnotation') { + return false; + } + + let elementType = returnTypeAnnotation.elementType; + [elementType] = unwrapNullable(elementType); + + return elementType.type === 'ArrayBufferTypeAnnotation'; +} + function translateMethodTypeToJniSignature( property: NativeModulePropertyShape, resolveAlias: AliasResolver, @@ -453,8 +471,6 @@ function translateMethodForImplementation( unwrapNullable(property.typeAnnotation); const {returnTypeAnnotation} = propertyTypeAnnotation; - throwIfUnsupportedPromiseArrayBuffer(property.name, returnTypeAnnotation); - if ( property.name === 'getConstants' && returnTypeAnnotation.type === 'ObjectTypeAnnotation' && @@ -468,6 +484,8 @@ function translateMethodForImplementation( propertyName: property.name, jniSignature: translateMethodTypeToJniSignature(property, resolveAlias), jsReturnType: translateReturnTypeToKind(returnTypeAnnotation, resolveAlias), + promiseResolveSupportsArrayBuffer: + doesPromiseResolveSupportArrayBuffer(returnTypeAnnotation), }); } 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 397516dc5c22..c22c45b9f4cc 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js @@ -26,7 +26,6 @@ const { } = require('../../../parsers/parsers-commons'); const {wrapOptional} = require('../../TypeUtils/Objective-C'); const {capitalize, parseValidUnionType} = require('../../Utils'); -const {throwIfUnsupportedPromiseArrayBuffer} = require('../Utils'); const {getNamespacedStructName} = require('./Utils'); const invariant = require('invariant'); @@ -104,11 +103,6 @@ function serializeMethod( } }); - throwIfUnsupportedPromiseArrayBuffer( - methodName, - propertyTypeAnnotation.returnTypeAnnotation, - ); - // Unwrap returnTypeAnnotation, so we check if the return type is Promise // TODO(T76719514): Disallow nullable PromiseTypeAnnotations const [returnTypeAnnotation] = unwrapNullable( diff --git a/packages/react-native-codegen/src/generators/modules/Utils.js b/packages/react-native-codegen/src/generators/modules/Utils.js index 8cd8d37ff096..ce6b63398417 100644 --- a/packages/react-native-codegen/src/generators/modules/Utils.js +++ b/packages/react-native-codegen/src/generators/modules/Utils.js @@ -13,7 +13,6 @@ import type { NativeModuleAliasMap, NativeModuleObjectTypeAnnotation, - NativeModuleReturnTypeAnnotation, NativeModuleSchema, NativeModuleTypeAnnotation, Nullable, @@ -78,50 +77,9 @@ function isArrayRecursiveMember( ); } -// Platform-native (Java/Kotlin and ObjC) TurboModules copy ArrayBuffer -// arguments and return ArrayBuffers zero-copy from synchronous methods, but -// `Promise` is not part of their contract. -// -// On Android it cannot work: the resolve path serializes through -// folly::dynamic, which cannot carry raw bytes. On iOS the resolve path is a -// direct ObjC->jsi conversion that would in fact produce an ArrayBuffer for an -// NSMutableData, so the limitation there is not technical — the guard is -// applied to ObjC as well to keep one cross-platform contract, so a spec that -// compiles for iOS cannot fail to build for Android. -// -// Reject `Promise` at codegen time for both native platforms so -// the unsupported case surfaces as a build error rather than a runtime failure -// or a silent iOS/Android divergence. -function throwIfUnsupportedPromiseArrayBuffer( - methodName: string, - nullableReturnTypeAnnotation: Nullable, -): void { - const [returnTypeAnnotation] = - unwrapNullable( - nullableReturnTypeAnnotation, - ); - if (returnTypeAnnotation.type !== 'PromiseTypeAnnotation') { - return; - } - let elementType = returnTypeAnnotation.elementType; - if (elementType.type === 'NullableTypeAnnotation') { - elementType = elementType.typeAnnotation; - } - if (elementType.type === 'ArrayBufferTypeAnnotation') { - throw new Error( - `Unsupported return type for method "${methodName}": Promise is not ` + - 'supported for Android (Java/Kotlin) or iOS (ObjC) TurboModules. Use a C++ ' + - '(Cxx) TurboModule, return the ArrayBuffer from a synchronous method, or resolve ' + - 'the Promise with a different type. ArrayBuffer is still supported as a method ' + - 'argument and as a synchronous return value on all platforms.', - ); - } -} - module.exports = { createAliasResolver, getModules, isDirectRecursiveMember, isArrayRecursiveMember, - throwIfUnsupportedPromiseArrayBuffer, }; 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 83cc98bef054..9ad6453a0d69 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 @@ -2661,25 +2661,6 @@ const ARRAY_BUFFER_NATIVE_MODULE: SchemaType = { ], }, }, - ], - }, - moduleName: 'SampleTurboModule', - }, - }, -}; - -// Promise is only supported by C++ (Cxx) TurboModules (see -// throwIfUnsupportedPromiseArrayBuffer), so this fixture is excluded on both -// Android and iOS. It keeps C++ codegen coverage for the async-return case. -const ARRAY_BUFFER_PROMISE_NATIVE_MODULE: SchemaType = { - modules: { - NativeSampleTurboModule: { - type: 'NativeModule', - aliasMap: {}, - enumMap: {}, - spec: { - eventEmitters: [], - methods: [ { name: 'promiseArrayBuffer', optional: false, @@ -2694,10 +2675,26 @@ const ARRAY_BUFFER_PROMISE_NATIVE_MODULE: SchemaType = { params: [], }, }, + { + name: 'promiseNullableArrayBuffer', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'PromiseTypeAnnotation', + elementType: { + type: 'NullableTypeAnnotation', + typeAnnotation: { + type: 'ArrayBufferTypeAnnotation', + }, + }, + }, + params: [], + }, + }, ], }, moduleName: 'SampleTurboModule', - excludedPlatforms: ['android', 'iOS'], }, }, }; @@ -2896,7 +2893,6 @@ const STRING_LITERALS: SchemaType = { module.exports = { array_buffer_native_module: ARRAY_BUFFER_NATIVE_MODULE, - array_buffer_promise_native_module: ARRAY_BUFFER_PROMISE_NATIVE_MODULE, complex_objects: COMPLEX_OBJECTS, two_modules_different_files: TWO_MODULES_DIFFERENT_FILES, empty_native_modules: EMPTY_NATIVE_MODULES, diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleHObjCpp-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleHObjCpp-test.js index c151a4aa1cc1..bb50ae55a1ea 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleHObjCpp-test.js +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleHObjCpp-test.js @@ -10,8 +10,6 @@ 'use strict'; -import type {SchemaType} from '../../../CodegenSchema'; - const fixtures = require('../__test_fixtures__/fixtures.js'); const generator = require('../GenerateModuleObjCpp'); @@ -33,42 +31,4 @@ describe('GenerateModuleHObjCpp', () => { ).toMatchSnapshot(); }); }); - - it('throws for a method returning Promise (unsupported on iOS)', () => { - const schema: SchemaType = { - modules: { - NativeSampleTurboModule: { - type: 'NativeModule', - aliasMap: {}, - enumMap: {}, - spec: { - eventEmitters: [], - methods: [ - { - name: 'getAsyncBuffer', - optional: false, - typeAnnotation: { - type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { - type: 'PromiseTypeAnnotation', - elementType: {type: 'ArrayBufferTypeAnnotation'}, - }, - params: [], - }, - }, - ], - }, - moduleName: 'SampleTurboModule', - }, - }, - }; - expect(() => - generator.generate( - 'array_buffer_promise_throws', - schema, - 'com.facebook.fbreact.specs', - false, - ), - ).toThrow(/Promise is not supported/); - }); }); diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJavaSpec-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJavaSpec-test.js index 3cbcf9747179..45d7b0e58796 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJavaSpec-test.js +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJavaSpec-test.js @@ -10,8 +10,6 @@ 'use strict'; -import type {SchemaType} from '../../../CodegenSchema'; - const fixtures = require('../__test_fixtures__/fixtures.js'); const generator = require('../GenerateModuleJavaSpec.js'); @@ -31,37 +29,4 @@ describe('GenerateModuleJavaSpec', () => { ).toMatchSnapshot(); }); }); - - it('throws for a method returning Promise (unsupported on Android)', () => { - const schema: SchemaType = { - modules: { - NativeSampleTurboModule: { - type: 'NativeModule', - aliasMap: {}, - enumMap: {}, - spec: { - eventEmitters: [], - methods: [ - { - name: 'getAsyncBuffer', - optional: false, - typeAnnotation: { - type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { - type: 'PromiseTypeAnnotation', - elementType: {type: 'ArrayBufferTypeAnnotation'}, - }, - params: [], - }, - }, - ], - }, - moduleName: 'SampleTurboModule', - }, - }, - }; - expect(() => - generator.generate('array_buffer_promise_throws', schema), - ).toThrow(/Promise is not supported/); - }); }); diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJniCpp-test.js b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJniCpp-test.js index 72e173904c6a..0e1fae7402eb 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJniCpp-test.js +++ b/packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleJniCpp-test.js @@ -10,8 +10,6 @@ 'use strict'; -import type {SchemaType} from '../../../CodegenSchema'; - const fixtures = require('../__test_fixtures__/fixtures.js'); const generator = require('../GenerateModuleJniCpp.js'); @@ -31,41 +29,4 @@ describe('GenerateModuleJniCpp', () => { ).toMatchSnapshot(); }); }); - - it('throws for a method returning Promise (unsupported on Android)', () => { - const schema: SchemaType = { - modules: { - NativeSampleTurboModule: { - type: 'NativeModule', - aliasMap: {}, - enumMap: {}, - spec: { - eventEmitters: [], - methods: [ - { - name: 'getAsyncBuffer', - optional: false, - typeAnnotation: { - type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { - type: 'PromiseTypeAnnotation', - elementType: {type: 'ArrayBufferTypeAnnotation'}, - }, - params: [], - }, - }, - ], - }, - moduleName: 'SampleTurboModule', - }, - }, - }; - expect(() => - generator.generate( - 'array_buffer_promise_throws', - schema, - 'com.facebook.fbreact.specs', - ), - ).toThrow(/Promise is not supported/); - }); }); 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 f2b1d6ad49ce..2e35e312030a 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 @@ -67,6 +67,8 @@ protected: methodMap_[\\"getArrayBuffer\\"] = MethodMetadata {.argCount = 0, .invoker = __getArrayBuffer}; methodMap_[\\"voidArrayBuffer\\"] = MethodMetadata {.argCount = 1, .invoker = __voidArrayBuffer}; methodMap_[\\"voidNullableArrayBuffer\\"] = MethodMetadata {.argCount = 1, .invoker = __voidNullableArrayBuffer}; + methodMap_[\\"promiseArrayBuffer\\"] = MethodMetadata {.argCount = 0, .invoker = __promiseArrayBuffer}; + methodMap_[\\"promiseNullableArrayBuffer\\"] = MethodMetadata {.argCount = 0, .invoker = __promiseNullableArrayBuffer}; } private: @@ -92,49 +94,20 @@ private: bridging::callFromJs(rt, &T::voidNullableArrayBuffer, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule), count <= 0 || args[0].isNull() || args[0].isUndefined() ? std::nullopt : std::make_optional(args[0].asObject(rt).getArrayBuffer(rt)));return jsi::Value::undefined(); } -}; - -} // namespace facebook::react -", -} -`; - -exports[`GenerateModuleH can generate fixture array_buffer_promise_native_module 1`] = ` -Map { - "array_buffer_promise_native_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_[\\"promiseArrayBuffer\\"] = MethodMetadata {.argCount = 0, .invoker = __promiseArrayBuffer}; - } - -private: static jsi::Value __promiseArrayBuffer(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* /*args*/, size_t /*count*/) { static_assert( bridging::getParameterCount(&T::promiseArrayBuffer) == 1, \\"Expected promiseArrayBuffer(...) to have 1 parameters\\"); return bridging::callFromJs(rt, &T::promiseArrayBuffer, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule)); } + + static jsi::Value __promiseNullableArrayBuffer(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* /*args*/, size_t /*count*/) { + static_assert( + bridging::getParameterCount(&T::promiseNullableArrayBuffer) == 1, + \\"Expected promiseNullableArrayBuffer(...) to have 1 parameters\\"); + return bridging::callFromJs(rt, &T::promiseNullableArrayBuffer, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule)); + } }; } // namespace facebook::react 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 4726cdcd0d96..327bf53723f1 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 @@ -107,6 +107,10 @@ Map { - (RCTArrayBuffer *)getArrayBuffer; - (void)voidArrayBuffer:(RCTArrayBuffer *)arg; - (void)voidNullableArrayBuffer:(RCTArrayBuffer * _Nullable)arg; +- (void)promiseArrayBuffer:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject; +- (void)promiseNullableArrayBuffer:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject; @end @@ -134,48 +138,6 @@ namespace facebook::react { } `; -exports[`GenerateModuleHObjCpp can generate fixture array_buffer_promise_native_module 1`] = ` -Map { - "array_buffer_promise_native_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 array_buffer_promise_native_module symbols -#ifndef array_buffer_promise_native_module_H -#define array_buffer_promise_native_module_H - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - - - -#endif // array_buffer_promise_native_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 14722e2179f5..0c74d5657710 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 @@ -59,6 +59,7 @@ package com.facebook.fbreact.specs; import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.ArrayBuffer; +import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; @@ -89,13 +90,19 @@ public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaMo @ReactMethod @DoNotStrip public abstract void voidNullableArrayBuffer(@Nullable ArrayBuffer arg); + + @ReactMethod + @DoNotStrip + public abstract void promiseArrayBuffer(Promise promise); + + @ReactMethod + @DoNotStrip + public abstract void promiseNullableArrayBuffer(Promise promise); } ", } `; -exports[`GenerateModuleJavaSpec can generate fixture array_buffer_promise_native_module 1`] = `Map {}`; - 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 547344fda403..5b429ff94580 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 @@ -53,17 +53,27 @@ 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\\", \\"()Lcom/facebook/react/bridge/ArrayBuffer;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, ArrayBufferKind, \\"getArrayBuffer\\", \\"()Lcom/facebook/react/bridge/ArrayBuffer;\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidArrayBuffer\\", \\"(Lcom/facebook/react/bridge/ArrayBuffer;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidArrayBuffer\\", \\"(Lcom/facebook/react/bridge/ArrayBuffer;)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidNullableArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidNullableArrayBuffer\\", \\"(Lcom/facebook/react/bridge/ArrayBuffer;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidNullableArrayBuffer\\", \\"(Lcom/facebook/react/bridge/ArrayBuffer;)V\\", args, count, cachedMethodId, false); +} + +static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"promiseArrayBuffer\\", \\"(Lcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId, true); +} + +static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseNullableArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"promiseNullableArrayBuffer\\", \\"(Lcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId, true); } NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms) @@ -71,6 +81,8 @@ NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboMo 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}; + methodMap_[\\"promiseNullableArrayBuffer\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseNullableArrayBuffer}; } std::shared_ptr array_buffer_native_module_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) { @@ -85,34 +97,6 @@ std::shared_ptr array_buffer_native_module_ModuleProvider(const std } `; -exports[`GenerateModuleJniCpp can generate fixture array_buffer_promise_native_module 1`] = ` -Map { - "jni/array_buffer_promise_native_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 \\"array_buffer_promise_native_module.h\\" - -namespace facebook::react { - - - -std::shared_ptr array_buffer_promise_native_module_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) { - - return nullptr; -} - -} // namespace facebook::react -", -} -`; - exports[`GenerateModuleJniCpp can generate fixture complex_objects 1`] = ` Map { "jni/complex_objects-generated.cpp" => " @@ -131,37 +115,37 @@ namespace facebook::react { static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_difficult(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"difficult\\", \\"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"difficult\\", \\"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_optionals(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, \\"optionals\\", \\"(Lcom/facebook/react/bridge/ReadableMap;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"optionals\\", \\"(Lcom/facebook/react/bridge/ReadableMap;)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_optionalMethod(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, \\"optionalMethod\\", \\"(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/ReadableArray;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"optionalMethod\\", \\"(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/ReadableArray;)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArrays(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, \\"getArrays\\", \\"(Lcom/facebook/react/bridge/ReadableMap;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"getArrays\\", \\"(Lcom/facebook/react/bridge/ReadableMap;)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getNullableObject(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getNullableObject\\", \\"()Lcom/facebook/react/bridge/WritableMap;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getNullableObject\\", \\"()Lcom/facebook/react/bridge/WritableMap;\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getNullableGenericObject(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getNullableGenericObject\\", \\"()Lcom/facebook/react/bridge/WritableMap;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getNullableGenericObject\\", \\"()Lcom/facebook/react/bridge/WritableMap;\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getNullableArray(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, ArrayKind, \\"getNullableArray\\", \\"()Lcom/facebook/react/bridge/WritableArray;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, ArrayKind, \\"getNullableArray\\", \\"()Lcom/facebook/react/bridge/WritableArray;\\", args, count, cachedMethodId, false); } NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms) @@ -268,7 +252,7 @@ namespace facebook::react { static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc(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, \\"voidFunc\\", \\"()V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidFunc\\", \\"()V\\", args, count, cachedMethodId, false); } NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms) @@ -315,7 +299,7 @@ namespace facebook::react { static facebook::jsi::Value __hostFunction_AliasTurboModuleSpecJSI_cropImage(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, \\"cropImage\\", \\"(Lcom/facebook/react/bridge/ReadableMap;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"cropImage\\", \\"(Lcom/facebook/react/bridge/ReadableMap;)V\\", args, count, cachedMethodId, false); } AliasTurboModuleSpecJSI::AliasTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms) @@ -355,17 +339,17 @@ namespace facebook::react { static facebook::jsi::Value __hostFunction_NativeCameraRollManagerSpecJSI_getPhotos(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"getPhotos\\", \\"(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"getPhotos\\", \\"(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeCameraRollManagerSpecJSI_saveToCameraRoll(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"saveToCameraRoll\\", \\"(Ljava/lang/String;Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"saveToCameraRoll\\", \\"(Ljava/lang/String;Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeCameraRollManagerSpecJSI_deletePhotos(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"deletePhotos\\", \\"(Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"deletePhotos\\", \\"(Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId, false); } NativeCameraRollManagerSpecJSI::NativeCameraRollManagerSpecJSI(const JavaTurboModule::InitParams ¶ms) @@ -376,27 +360,27 @@ NativeCameraRollManagerSpecJSI::NativeCameraRollManagerSpecJSI(const JavaTurboMo } static facebook::jsi::Value __hostFunction_NativeExceptionsManagerSpecJSI_reportFatalException(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, \\"reportFatalException\\", \\"(Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;D)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"reportFatalException\\", \\"(Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;D)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeExceptionsManagerSpecJSI_reportSoftException(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, \\"reportSoftException\\", \\"(Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;D)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"reportSoftException\\", \\"(Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;D)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeExceptionsManagerSpecJSI_reportException(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, \\"reportException\\", \\"(Lcom/facebook/react/bridge/ReadableMap;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"reportException\\", \\"(Lcom/facebook/react/bridge/ReadableMap;)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeExceptionsManagerSpecJSI_updateExceptionMessage(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, \\"updateExceptionMessage\\", \\"(Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;D)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"updateExceptionMessage\\", \\"(Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;D)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeExceptionsManagerSpecJSI_dismissRedbox(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, \\"dismissRedbox\\", \\"()V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"dismissRedbox\\", \\"()V\\", args, count, cachedMethodId, false); } NativeExceptionsManagerSpecJSI::NativeExceptionsManagerSpecJSI(const JavaTurboModule::InitParams ¶ms) @@ -441,72 +425,72 @@ namespace facebook::react { static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getConstants\\", \\"()Ljava/util/Map;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getConstants\\", \\"()Ljava/util/Map;\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc(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, \\"voidFunc\\", \\"()V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidFunc\\", \\"()V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getBool(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, BooleanKind, \\"getBool\\", \\"(Z)Z\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, BooleanKind, \\"getBool\\", \\"(Z)Z\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getNumber(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, NumberKind, \\"getNumber\\", \\"(D)D\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, NumberKind, \\"getNumber\\", \\"(D)D\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getString(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, StringKind, \\"getString\\", \\"(Ljava/lang/String;)Ljava/lang/String;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, StringKind, \\"getString\\", \\"(Ljava/lang/String;)Ljava/lang/String;\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArray(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, ArrayKind, \\"getArray\\", \\"(Lcom/facebook/react/bridge/ReadableArray;)Lcom/facebook/react/bridge/WritableArray;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, ArrayKind, \\"getArray\\", \\"(Lcom/facebook/react/bridge/ReadableArray;)Lcom/facebook/react/bridge/WritableArray;\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getObject(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getObject\\", \\"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getObject\\", \\"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, NumberKind, \\"getRootTag\\", \\"(D)D\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, NumberKind, \\"getRootTag\\", \\"(D)D\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValue(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getValue\\", \\"(DLjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getValue\\", \\"(DLjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getEnumReturn(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, NumberKind, \\"getEnumReturn\\", \\"()D\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, NumberKind, \\"getEnumReturn\\", \\"()D\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback(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, \\"getValueWithCallback\\", \\"(Lcom/facebook/react/bridge/Callback;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"getValueWithCallback\\", \\"(Lcom/facebook/react/bridge/Callback;)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"getValueWithPromise\\", \\"(ZLcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"getValueWithPromise\\", \\"(ZLcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithOptionalArg(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"getValueWithOptionalArg\\", \\"(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, \\"getValueWithOptionalArg\\", \\"(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId, false); } static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getEnums(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, StringKind, \\"getEnums\\", \\"(DLjava/lang/String;)Ljava/lang/String;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, StringKind, \\"getEnums\\", \\"(DLjava/lang/String;)Ljava/lang/String;\\", args, count, cachedMethodId, false); } NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms) @@ -557,7 +541,7 @@ namespace facebook::react { static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getStringLiteral(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, StringKind, \\"getStringLiteral\\", \\"(Ljava/lang/String;)Ljava/lang/String;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, StringKind, \\"getStringLiteral\\", \\"(Ljava/lang/String;)Ljava/lang/String;\\", args, count, cachedMethodId, false); } NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms) @@ -597,7 +581,7 @@ namespace facebook::react { static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc(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, \\"voidFunc\\", \\"()V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidFunc\\", \\"()V\\", args, count, cachedMethodId, false); } NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms) @@ -608,7 +592,7 @@ NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboMo static facebook::jsi::Value __hostFunction_NativeSampleTurboModule2SpecJSI_voidFunc(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, \\"voidFunc\\", \\"()V\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidFunc\\", \\"()V\\", args, count, cachedMethodId, false); } NativeSampleTurboModule2SpecJSI::NativeSampleTurboModule2SpecJSI(const JavaTurboModule::InitParams ¶ms) @@ -649,7 +633,7 @@ namespace facebook::react { static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getUnion(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { static jmethodID cachedMethodId = nullptr; - return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getUnion\\", \\"(DDLcom/facebook/react/bridge/ReadableMap;Ljava/lang/String;Ljava/lang/String;)Lcom/facebook/react/bridge/ReadableMap;\\", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getUnion\\", \\"(DDLcom/facebook/react/bridge/ReadableMap;Ljava/lang/String;Ljava/lang/String;)Lcom/facebook/react/bridge/ReadableMap;\\", args, count, cachedMethodId, false); } NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams ¶ms) 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 fed0ac2033fd..daa41686b1c4 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniH-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniH-test.js.snap @@ -132,65 +132,6 @@ target_compile_reactnative_options(react_codegen_array_buffer_native_module PRIV } `; -exports[`GenerateModuleJniH can generate fixture array_buffer_promise_native_module 1`] = ` -Map { - "jni/array_buffer_promise_native_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 { - - - -JSI_EXPORT -std::shared_ptr array_buffer_promise_native_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/array_buffer_promise_native_module/*.cpp) - -add_library( - react_codegen_array_buffer_promise_native_module - OBJECT - \${react_codegen_SRCS} -) - -target_include_directories(react_codegen_array_buffer_promise_native_module PUBLIC . react/renderer/components/array_buffer_promise_native_module) - -target_link_libraries( - react_codegen_array_buffer_promise_native_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_array_buffer_promise_native_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 f70403ac89b6..096243c34bcd 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 @@ -82,6 +82,14 @@ namespace facebook::react { 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); + } + + static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseNullableArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, PromiseKind, \\"promiseNullableArrayBuffer\\", @selector(promiseNullableArrayBuffer:reject:), args, count); + } + NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms) : ObjCTurboModule(params) { @@ -93,34 +101,18 @@ namespace facebook::react { methodMap_[\\"voidNullableArrayBuffer\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_voidNullableArrayBuffer}; + + methodMap_[\\"promiseArrayBuffer\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseArrayBuffer}; + + + methodMap_[\\"promiseNullableArrayBuffer\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseNullableArrayBuffer}; + } } // namespace facebook::react ", } `; -exports[`GenerateModuleMm can generate fixture array_buffer_promise_native_module 1`] = ` -Map { - "array_buffer_promise_native_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 \\"array_buffer_promise_native_module.h\\" - - -", -} -`; - exports[`GenerateModuleMm can generate fixture complex_objects 1`] = ` Map { "complex_objects-generated.mm" => "/** diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxArrayBufferCallbackImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxArrayBufferCallbackImpl.kt new file mode 100644 index 000000000000..cbfe73143c1c --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxArrayBufferCallbackImpl.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.bridge + +import com.facebook.jni.HybridClassBase +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Resolve callback for a Promise that may be fulfilled with an [ArrayBuffer] or null. Created from + * C++ only where the JavaScript spec permits `Promise` or `Promise`. + * + * Unlike [CxxCallbackImpl], this does not serialize through folly::dynamic: an owning [ArrayBuffer] + * reaches JavaScript aliasing the same bytes, and null is forwarded explicitly. + * + * The buffer must own its bytes. A non-owning one borrows from the JS `ArrayBuffer` passed to some + * earlier synchronous call, and that borrow is revoked once the call returns - long before a + * Promise resolved here reaches JavaScript. + * + * A module that resolves with anything else is misusing its spec. Rather than throwing on whichever + * thread called `Promise.resolve`, the problem is described to C++, which rejects the Promise with + * it. + */ +@DoNotStrip +internal class CxxArrayBufferCallbackImpl @DoNotStrip private constructor() : + HybridClassBase(), Callback { + + override fun invoke(vararg args: Any?) { + if (args.size > 1) { + nativeInvoke(null, "expected at most one argument, got ${args.size}") + return + } + when (val arg = args.firstOrNull()) { + null -> nativeInvoke(null, null) + is ArrayBuffer -> + if (arg.isOwningBytes) { + nativeInvoke(arg, null) + } else { + nativeInvoke( + null, + "expected an ArrayBuffer that owns its bytes; the bytes of a non-owning one are " + + "no longer valid by the time the Promise resolves. Copy them with " + + "ArrayBuffer.arrayBufferWithCopiedBytes().") + } + else -> nativeInvoke(null, "expected an ArrayBuffer or null, got ${arg.javaClass.name}") + } + } + + /** + * At most one of [arrayBuffer] and [error] is non-null. Both null resolves with JavaScript null. + */ + private external fun nativeInvoke(arrayBuffer: ArrayBuffer?, error: String?) +} diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JArrayBufferCallback.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JArrayBufferCallback.h new file mode 100644 index 000000000000..f1edaf4b464a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JArrayBufferCallback.h @@ -0,0 +1,59 @@ +/* + * 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 "JArrayBuffer.h" +#include "JCallback.h" + +namespace facebook::react { + +// Resolve callback for a Promise that may be fulfilled with an ArrayBuffer or +// null. +// +// Created only where the JavaScript spec permits Promise or +// Promise. Does not use folly::dynamic; the bytes of an owning +// com.facebook.react.bridge.ArrayBuffer reach JavaScript without a copy, and +// null is forwarded explicitly. +// +// The Java side validates what the module resolved with and reports a +// description of the problem through `error` instead of throwing, so that +// misuse rejects the Promise rather than escaping on the resolving thread. +class JCxxArrayBufferCallbackImpl : public jni::HybridClass { + public: + constexpr static auto kJavaDescriptor = "Lcom/facebook/react/bridge/CxxArrayBufferCallbackImpl;"; + + static void registerNatives() + { + registerHybrid({ + makeNativeMethod("nativeInvoke", JCxxArrayBufferCallbackImpl::invoke), + }); + } + + private: + friend HybridBase; + + // At most one of `arrayBuffer` and `error` is non-null. Both null resolves + // the Promise with JavaScript null. + using Callback = std::function< + void(jni::alias_ref arrayBuffer, jni::alias_ref error)>; + + explicit JCxxArrayBufferCallbackImpl(Callback callback) : callback_(std::move(callback)) {} + + void invoke(jni::alias_ref arrayBuffer, jni::alias_ref error) + { + callback_(arrayBuffer, error); + } + + Callback callback_; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp index 8c6787bdde65..cb8729cd9d11 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp @@ -7,6 +7,7 @@ #include #include "JArrayBuffer.h" +#include "JArrayBufferCallback.h" #include "JCallback.h" #include "JDynamicNative.h" #include "JReactMarker.h" @@ -20,6 +21,7 @@ namespace facebook::react { extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { return facebook::jni::initialize(vm, [] { JArrayBuffer::registerNatives(); + JCxxArrayBufferCallbackImpl::registerNatives(); JCxxCallbackImpl::registerNatives(); JDynamicNative::registerNatives(); JReactMarker::registerNatives(); 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 0db4927a6338..a8d8e8a44c06 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 @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -24,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -134,20 +136,37 @@ jsi::Value createRejectionError(jsi::Runtime& rt, const folly::dynamic& args) { return jsError; } -auto createJavaCallback( - jsi::Runtime& rt, - jsi::Function&& function, - std::shared_ptr jsInvoker) { - std::optional> callback( - {rt, std::move(function), std::move(jsInvoker)}); - return JCxxCallbackImpl::newObjectCxxArgs( - [callback = std::move(callback)](folly::dynamic args) mutable { - if (!callback) { - LOG(FATAL) << "Callback arg cannot be called more than once"; - return; - } - callback->call([args = std::move(args)]( - jsi::Runtime& rt, jsi::Function& jsFunction) { +class OnceCallback { + std::optional> callback_; + + public: + OnceCallback( + jsi::Runtime& rt, + jsi::Function function, + std::shared_ptr jsInvoker) + : callback_( + AsyncCallback<>(rt, std::move(function), std::move(jsInvoker))) {} + + OnceCallback(const OnceCallback&) = delete; + OnceCallback& operator=(const OnceCallback&) = delete; + OnceCallback(OnceCallback&&) = default; + OnceCallback& operator=(OnceCallback&&) = default; + + template + void call(const char* what, F&& invoke) { + if (!callback_) { + LOG(FATAL) << what << " cannot be called more than once"; + return; + } + callback_->call(std::forward(invoke)); + callback_ = std::nullopt; + } + + void callWithArgs(const char* what, folly::dynamic&& args) noexcept { + call( + what, + [args = std::move(args)]( + jsi::Runtime& rt, jsi::Function& jsFunction) mutable { std::vector jsArgs; jsArgs.reserve(args.size()); for (const auto& val : args) { @@ -155,27 +174,135 @@ auto createJavaCallback( } jsFunction.call(rt, (const jsi::Value*)jsArgs.data(), jsArgs.size()); }); - callback = std::nullopt; - }); + } +}; + +template +jni::local_ref makeJavaOnceCallback( + jsi::Runtime& rt, + jsi::Function function, + std::shared_ptr jsInvoker, + Handler handler) { + auto once = std::make_shared( + rt, std::move(function), std::move(jsInvoker)); + return jni::static_ref_cast( + JavaCallbackImpl::newObjectCxxArgs( + [once = std::move(once), + handler = std::move(handler)](auto&&... args) mutable { + handler(*once, std::forward(args)...); + })); } -auto createJavaRejectCallback( +jni::local_ref createJavaCallback( jsi::Runtime& rt, jsi::Function&& function, std::shared_ptr jsInvoker) { - std::optional> callback( - {rt, std::move(function), std::move(jsInvoker)}); - return JCxxCallbackImpl::newObjectCxxArgs( - [callback = std::move(callback)](folly::dynamic args) mutable { - if (!callback) { - LOG(FATAL) << "Callback arg cannot be called more than once"; + return makeJavaOnceCallback( + rt, + std::move(function), + std::move(jsInvoker), + [](OnceCallback& once, folly::dynamic args) { + once.callWithArgs("Callback arg", std::move(args)); + }); +} + +jni::local_ref createJavaArrayBufferCallback( + jsi::Runtime& rt, + jsi::Function&& resolveFunction, + jsi::Function&& rejectFunction, + std::shared_ptr jsInvoker) { + auto rejectMisuse = + std::make_shared(rt, std::move(rejectFunction), jsInvoker); + return makeJavaOnceCallback( + rt, + std::move(resolveFunction), + std::move(jsInvoker), + [rejectMisuse = std::move(rejectMisuse)]( + OnceCallback& once, + jni::alias_ref arrayBuffer, + jni::alias_ref error) { + auto reject = [&rejectMisuse](std::string message) { + rejectMisuse->call( + "Promise reject", + [message = "Invalid Promise resolution: " + + std::move(message)]( + jsi::Runtime& rt, jsi::Function& jsFunction) { + jsFunction.call(rt, createJSRuntimeError(rt, message)); + }); + }; + + if (error) { + reject(error->toStdString()); return; } - callback->call([args = std::move(args)]( - jsi::Runtime& rt, jsi::Function& jsFunction) { - jsFunction.call(rt, createRejectionError(rt, args)); - }); - callback = std::nullopt; + + // Kotlin has already rejected anything but null or an owning + // ArrayBuffer, and invalidate() never revokes an owning buffer, so the + // peer's bytes are still there and can go to JS unchanged. hasBytes() + // is re-checked anyway: mutableBuffer() throws without it, and letting + // a std::runtime_error escape a JNI frame is a poor way to find out + // that the two sides ever disagreed about ownership. + std::shared_ptr buffer; + if (arrayBuffer) { + auto* peer = arrayBuffer->cthis(); + if (peer == nullptr) { + reject("ArrayBuffer has no native peer."); + return; + } + if (!peer->hasBytes()) { + reject( + "the bytes of this ArrayBuffer are no longer valid. Copy them " + "with ArrayBuffer.arrayBufferWithCopiedBytes() to resolve with " + "them later."); + return; + } + buffer = peer->mutableBuffer(); + } + + once.call( + "Promise resolve", + [buffer = std::move(buffer)]( + jsi::Runtime& rt, jsi::Function& jsFunction) { + if (!buffer) { + jsFunction.call(rt, jsi::Value::null()); + return; + } + jsFunction.call(rt, jsi::Value(jsi::ArrayBuffer(rt, buffer))); + }); + }); +} + +jni::local_ref createJavaResolveCallback( + jsi::Runtime& rt, + jsi::Function&& resolveFunction, + jsi::Function&& rejectFunction, + std::shared_ptr jsInvoker, + bool promiseResolveSupportsArrayBuffer) { + return promiseResolveSupportsArrayBuffer + ? createJavaArrayBufferCallback( + rt, + std::move(resolveFunction), + std::move(rejectFunction), + std::move(jsInvoker)) + : createJavaCallback( + rt, std::move(resolveFunction), std::move(jsInvoker)); +} + +jni::local_ref createJavaRejectCallback( + jsi::Runtime& rt, + jsi::Function&& function, + std::shared_ptr jsInvoker) { + return makeJavaOnceCallback( + rt, + std::move(function), + std::move(jsInvoker), + [](OnceCallback& once, folly::dynamic args) { + once.call( + "Promise reject", + [args = std::move(args)]( + jsi::Runtime& rt, jsi::Function& jsFunction) { + jsFunction.call(rt, createRejectionError(rt, args)); + }); }); } @@ -595,7 +722,11 @@ jsi::Value JavaTurboModule::invokeJavaMethod( const std::string& methodSignature, const jsi::Value* args, size_t argCount, - jmethodID& methodID) { + jmethodID& methodID, + bool promiseResolveSupportsArrayBuffer) { + react_native_assert( + !promiseResolveSupportsArrayBuffer || valueKind == PromiseKind); + const char* methodName = methodNameStr.c_str(); const char* moduleName = name_.c_str(); @@ -959,10 +1090,12 @@ jsi::Value JavaTurboModule::invokeJavaMethod( args[1].getObject(runtime).getFunction(runtime), jsInvoker_); - auto resolve = createJavaCallback( + auto resolve = createJavaResolveCallback( runtime, args[0].getObject(runtime).getFunction(runtime), - jsInvoker_); + args[1].getObject(runtime).getFunction(runtime), + jsInvoker_, + promiseResolveSupportsArrayBuffer); auto reject = createJavaRejectCallback( runtime, args[1].getObject(runtime).getFunction(runtime), diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.h index c42f1c7dbac0..e2c8dd777917 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.h @@ -45,7 +45,8 @@ class JSI_EXPORT JavaTurboModule : public TurboModule { const std::string &methodSignature, const jsi::Value *args, size_t argCount, - jmethodID &cachedMethodID); + jmethodID &cachedMethodID, + bool promiseResolveSupportsArrayBuffer = false); protected: void configureEventEmitterCallback(); 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 4c97e1140156..1f35cc70b760 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 @@ -186,6 +186,27 @@ public class SampleTurboModule(private val context: ReactApplicationContext) : promise.resolve((payload?.size ?: 0).toDouble()) } + // Resolving with an owning ArrayBuffer hands JS the same bytes without + // copying them; the ArrayBuffer keeps them alive for as long as JS can reach + // them. + @DoNotStrip + @Suppress("unused") + override fun getAsyncBuffer(size: Double, promise: Promise) { + if (!size.isFinite() || size < 0.0 || size > Int.MAX_VALUE.toDouble()) { + promise.reject( + "invalid_size", + "getAsyncBuffer: size must be a finite value in [0, ${Int.MAX_VALUE}], got $size") + return + } + val buffer = ArrayBuffer(size.toInt()) + val bytes = buffer.bytes + for (i in 0 until bytes.capacity()) { + bytes.put(i, (i + 1).toByte()) + } + log("getAsyncBuffer", size, buffer) + promise.resolve(buffer) + } + @DoNotStrip @Suppress("unused") override fun getValueWithCallback(callback: Callback?) { 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 6ec73228f76e..a167efca0d77 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,7 @@ #import #import +#include #include using namespace facebook::react; @@ -176,6 +177,24 @@ - (void)processAsyncBuffer:(RCTArrayBuffer *)payload resolve(@(payload.length)); } +// Resolving a Promise with an owning RCTArrayBuffer hands JS the same bytes +// without copying them; the buffer keeps them alive for as long as JS holds the +// ArrayBuffer. +- (void)getAsyncBuffer:(double)size resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject +{ + if (!std::isfinite(size) || size < 0 || size > (double)NSUIntegerMax) { + reject(@"invalid_size", [NSString stringWithFormat:@"getAsyncBuffer: invalid size %g", size], nil); + return; + } + + RCTArrayBuffer *buffer = [RCTArrayBuffer arrayBufferWithLength:(NSUInteger)size]; + std::span byteSpan(static_cast(buffer.mutableBytes), static_cast(buffer.length)); + for (size_t i = 0; i < byteSpan.size(); i++) { + byteSpan[i] = static_cast(i + 1); + } + resolve(buffer); +} + - (void)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 c458c91a2204..3f4b77c4801c 100644 --- a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js +++ b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js @@ -54,6 +54,7 @@ export interface Spec extends TurboModule { readonly getArrayBuffer: (buffer: ArrayBuffer) => ArrayBuffer; readonly createNativeBuffer: (size: number) => ArrayBuffer; readonly processAsyncBuffer: (payload: ArrayBuffer) => Promise; + readonly getAsyncBuffer: (size: number) => Promise; readonly getValueWithCallback: (callback: (value: string) => void) => void; readonly getValueWithPromise: (error: boolean) => Promise; readonly voidFuncThrows?: () => void; diff --git a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js index aab25aacd46b..4b63a4cb073a 100644 --- a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js +++ b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js @@ -49,6 +49,7 @@ type Examples = | 'getArrayBuffer' | 'createNativeBuffer' | 'processAsyncBuffer' + | 'getAsyncBuffer' | 'promise' | 'rejectPromise' | 'voidFunc' @@ -130,6 +131,10 @@ class SampleTurboModuleExample extends React.Component<{}, State> { NativeSampleTurboModule.processAsyncBuffer( new Uint8Array([1, 2, 3]).buffer, ).then(length => this._setResult('processAsyncBuffer', length)), + getAsyncBuffer: () => + NativeSampleTurboModule.getAsyncBuffer(4).then(buffer => + this._setResult('getAsyncBuffer', Array.from(new Uint8Array(buffer))), + ), }; // $FlowFixMe[missing-local-annot] diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 828513e24e82..3cb293643c74 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -2823,6 +2823,11 @@ class facebook::react::JByteBufferMutableBuffer : public facebook::jsi::MutableB public ~JByteBufferMutableBuffer() override; } +class facebook::react::JCxxArrayBufferCallbackImpl : public jni::HybridClass { + public static constexpr auto kJavaDescriptor; + public static void registerNatives(); +} + class facebook::react::JCxxCallbackImpl : public jni::HybridClass { public static constexpr auto kJavaDescriptor; public static void registerNatives(); @@ -3098,7 +3103,7 @@ class facebook::react::JavaTurboModule : public facebook::react::TurboModule { protected void configureEventEmitterCallback(); protected void setEventEmitterCallback(jni::alias_ref); public JavaTurboModule(const facebook::react::JavaTurboModule::InitParams& params); - public facebook::jsi::Value invokeJavaMethod(facebook::jsi::Runtime& runtime, facebook::react::TurboModuleMethodValueKind valueKind, const std::string& methodName, const std::string& methodSignature, const facebook::jsi::Value* args, size_t argCount, jmethodID& cachedMethodID); + public facebook::jsi::Value invokeJavaMethod(facebook::jsi::Runtime& runtime, facebook::react::TurboModuleMethodValueKind valueKind, const std::string& methodName, const std::string& methodSignature, const facebook::jsi::Value* args, size_t argCount, jmethodID& cachedMethodID, bool promiseResolveSupportsArrayBuffer = false); public virtual ~JavaTurboModule(); } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 39e6d87b0d02..78223f46d30a 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -2781,6 +2781,11 @@ class facebook::react::JByteBufferMutableBuffer : public facebook::jsi::MutableB public ~JByteBufferMutableBuffer() override; } +class facebook::react::JCxxArrayBufferCallbackImpl : public jni::HybridClass { + public static constexpr auto kJavaDescriptor; + public static void registerNatives(); +} + class facebook::react::JCxxCallbackImpl : public jni::HybridClass { public static constexpr auto kJavaDescriptor; public static void registerNatives(); @@ -3017,7 +3022,7 @@ class facebook::react::JavaTurboModule : public facebook::react::TurboModule { protected void configureEventEmitterCallback(); protected void setEventEmitterCallback(jni::alias_ref); public JavaTurboModule(const facebook::react::JavaTurboModule::InitParams& params); - public facebook::jsi::Value invokeJavaMethod(facebook::jsi::Runtime& runtime, facebook::react::TurboModuleMethodValueKind valueKind, const std::string& methodName, const std::string& methodSignature, const facebook::jsi::Value* args, size_t argCount, jmethodID& cachedMethodID); + public facebook::jsi::Value invokeJavaMethod(facebook::jsi::Runtime& runtime, facebook::react::TurboModuleMethodValueKind valueKind, const std::string& methodName, const std::string& methodSignature, const facebook::jsi::Value* args, size_t argCount, jmethodID& cachedMethodID, bool promiseResolveSupportsArrayBuffer = false); public virtual ~JavaTurboModule(); } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index f5dcbb21b548..23b4859bfb60 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -2820,6 +2820,11 @@ class facebook::react::JByteBufferMutableBuffer : public facebook::jsi::MutableB public ~JByteBufferMutableBuffer() override; } +class facebook::react::JCxxArrayBufferCallbackImpl : public jni::HybridClass { + public static constexpr auto kJavaDescriptor; + public static void registerNatives(); +} + class facebook::react::JCxxCallbackImpl : public jni::HybridClass { public static constexpr auto kJavaDescriptor; public static void registerNatives(); @@ -3095,7 +3100,7 @@ class facebook::react::JavaTurboModule : public facebook::react::TurboModule { protected void configureEventEmitterCallback(); protected void setEventEmitterCallback(jni::alias_ref); public JavaTurboModule(const facebook::react::JavaTurboModule::InitParams& params); - public facebook::jsi::Value invokeJavaMethod(facebook::jsi::Runtime& runtime, facebook::react::TurboModuleMethodValueKind valueKind, const std::string& methodName, const std::string& methodSignature, const facebook::jsi::Value* args, size_t argCount, jmethodID& cachedMethodID); + public facebook::jsi::Value invokeJavaMethod(facebook::jsi::Runtime& runtime, facebook::react::TurboModuleMethodValueKind valueKind, const std::string& methodName, const std::string& methodSignature, const facebook::jsi::Value* args, size_t argCount, jmethodID& cachedMethodID, bool promiseResolveSupportsArrayBuffer = false); public virtual ~JavaTurboModule(); } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index bfbe9280ffce..9c00190b234c 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -2521,6 +2521,7 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual RCTArrayBuffer* getArrayBuffer:(RCTArrayBuffer* buffer); 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); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 5a097a3af563..603aee2a0821 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -2514,6 +2514,7 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual RCTArrayBuffer* getArrayBuffer:(RCTArrayBuffer* buffer); 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); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 8a50f499d137..03571ca1ebce 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -2521,6 +2521,7 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC public virtual RCTArrayBuffer* getArrayBuffer:(RCTArrayBuffer* buffer); 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);