Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ type JSReturnType =
| 'NumberKind'
| 'PromiseKind'
| 'ObjectKind'
| 'ArrayKind';
| 'ArrayKind'
| 'ArrayBufferKind';

const HostFunctionTemplate = ({
hasteModuleName,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ type ReturnJSType =
| 'ObjectKind'
| 'ArrayKind'
| 'NumberKind'
| 'StringKind';
| 'StringKind'
| 'ArrayBufferKind';

export type MethodSerializationOutput = Readonly<{
methodName: string,
Expand Down Expand Up @@ -219,6 +220,9 @@ function getParamObjCType(
*/
return notStruct(wrapOptional('NSArray *', !nullable));
}
case 'ArrayBufferTypeAnnotation': {
return notStruct(wrapOptional('NSMutableData *', !nullable));
}
}

const [structTypeAnnotation] = unwrapNullable(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2678,7 +2678,7 @@ const ARRAY_BUFFER_NATIVE_MODULE: SchemaType = {
],
},
moduleName: 'SampleTurboModule',
excludedPlatforms: ['iOS', 'android'],
excludedPlatforms: ['iOS'],
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,63 @@ public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaMo
}
`;

exports[`GenerateModuleJavaSpec can generate fixture array_buffer_native_module 1`] = `Map {}`;
exports[`GenerateModuleJavaSpec can generate fixture array_buffer_native_module 1`] = `
Map {
"java/com/facebook/fbreact/specs/NativeSampleTurboModuleSpec.java" => "
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GenerateModuleJavaSpec.js
*
* @nolint
*/

package com.facebook.fbreact.specs;

import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaModule implements TurboModule {
public static final String NAME = \\"SampleTurboModule\\";

public NativeSampleTurboModuleSpec(ReactApplicationContext reactContext) {
super(reactContext);
}

@Override
public @Nonnull String getName() {
return NAME;
}

@ReactMethod(isBlockingSynchronousMethod = true)
@DoNotStrip
public abstract ByteBuffer getArrayBuffer();

@ReactMethod
@DoNotStrip
public abstract void voidArrayBuffer(ByteBuffer arg);

@ReactMethod
@DoNotStrip
public abstract void voidNullableArrayBuffer(@Nullable ByteBuffer arg);

@ReactMethod
@DoNotStrip
public abstract void promiseArrayBuffer(Promise promise);
}
",
}
`;

exports[`GenerateModuleJavaSpec can generate fixture complex_objects 1`] = `
Map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,38 @@ Map {
namespace facebook::react {
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ArrayBufferKind, \\"getArrayBuffer\\", \\"()Ljava/nio/ByteBuffer;\\", args, count, cachedMethodId);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidArrayBuffer\\", \\"(Ljava/nio/ByteBuffer;)V\\", args, count, cachedMethodId);
}
std::shared_ptr<TurboModule> array_buffer_native_module_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
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<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidNullableArrayBuffer\\", \\"(Ljava/nio/ByteBuffer;)V\\", args, count, cachedMethodId);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, \\"promiseArrayBuffer\\", \\"(Lcom/facebook/react/bridge/Promise;)V\\", args, count, cachedMethodId);
}
NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams &params)
: JavaTurboModule(params) {
methodMap_[\\"getArrayBuffer\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer};
methodMap_[\\"voidArrayBuffer\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_voidArrayBuffer};
methodMap_[\\"voidNullableArrayBuffer\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_voidNullableArrayBuffer};
methodMap_[\\"promiseArrayBuffer\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseArrayBuffer};
}
std::shared_ptr<TurboModule> array_buffer_native_module_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
if (moduleName == \\"SampleTurboModule\\") {
return std::make_shared<NativeSampleTurboModuleSpecJSI>(params);
}
return nullptr;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ Map {

namespace facebook::react {

/**
* JNI C++ class for module 'NativeSampleTurboModule'
*/
class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public JavaTurboModule {
public:
NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams &params);
};


JSI_EXPORT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,54 @@ package com.facebook.react.bridge

import com.facebook.jni.HybridClassBase
import com.facebook.proguard.annotations.DoNotStrip
import java.nio.ByteBuffer

/** Callback impl that calls directly into the cxx bridge. Created from C++. */
/**
* Callback impl that calls directly into the cxx bridge. Created from C++.
* A single direct [ByteBuffer] bypasses folly::dynamic for zero-copy `jsi::ArrayBuffer`
* on the JS thread (see [JCxxCallbackImpl]).
*/
@DoNotStrip
public class CxxCallbackImpl @DoNotStrip private constructor() : HybridClassBase(), Callback {

override fun invoke(vararg args: Any?) {
val singleArg = args.singleOrNull()
if (singleArg is ByteBuffer) {
requireDirectByteBuffer(singleArg)
nativeInvokeWithByteBuffer(singleArg)
return
}

val hasByteBuffer = args.any { arg ->
if (arg is ByteBuffer) {
requireDirectByteBuffer(arg)
true
} else {
false
}
}

if (hasByteBuffer) {
throw IllegalArgumentException(
"Callbacks support at most one direct ByteBuffer argument passed alone. " +
"Use promise.resolve(buffer) or callback.invoke(buffer)."
)
}

@Suppress("UNCHECKED_CAST") nativeInvoke(Arguments.fromJavaArgs(args as Array<Any?>))
}

private external fun nativeInvoke(arguments: NativeArray)

private external fun nativeInvokeWithByteBuffer(buffer: ByteBuffer)

private companion object {
private fun requireDirectByteBuffer(buffer: ByteBuffer) {
check(buffer.isDirect) {
"Only direct ByteBuffers (ByteBuffer.allocateDirect) can be passed to a native " +
"callback or promise. Use ByteBuffer.allocateDirect() instead of ByteBuffer.wrap() " +
"or ByteBuffer.allocate()."
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.bridge

import com.facebook.jni.HybridClassBase
import com.facebook.proguard.annotations.DoNotStrip

/** C++-backed promise reject callback (error maps only; folly::dynamic path). */
@DoNotStrip
internal class CxxCallbackRejectImpl @DoNotStrip private constructor() :
HybridClassBase(), Callback {

override fun invoke(vararg args: Any?) {
@Suppress("UNCHECKED_CAST")
nativeInvoke(Arguments.fromJavaArgs(args as Array<Any?>))
}

private external fun nativeInvoke(arguments: NativeArray)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ constructor(private var resolve: Callback?, private var reject: Callback?) : Pro
*/
override fun resolve(value: Any?) {
resolve?.let { callback ->
callback.invoke(value)
try {
callback.invoke(value)
} catch (e: Exception) {
reject(e)
}
resolve = null
reject = null
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.bridge

import com.facebook.jni.HybridClassBase
import com.facebook.proguard.annotations.DoNotStrip

/**
* Retains a native [jsi::MutableBuffer] owner while TurboModule JNI passes a zero-copy
* [java.nio.ByteBuffer] view to Java.
*/
@DoNotStrip
internal class ZeroCopyByteBufferHolder @DoNotStrip private constructor() : HybridClassBase()
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-ut
add_library(
reactnativejni_common
OBJECT
JCallback.cpp
JDynamicNative.cpp
JReactMarker.cpp
NativeArray.cpp
Expand Down
Loading
Loading