JArrayBuffer zero-copy class for Java TM (#57897)

Summary:
Android TurboModules mapped a JS `ArrayBuffer` to `java.nio.ByteBuffer`, copying every argument into a direct buffer — and `ByteBuffer` carries no ownership contract, so there was no way to express aliased or borrowed bytes for synchronous in-place access.

This adds `ArrayBuffer` (`packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ArrayBuffer.kt`) as the Java representation of an `ArrayBuffer`. It carries an `isOwningBytes` flag: an owning buffer can be stored and returned to JS, a non-owning one aliases bytes valid only for the synchronous call that produced it.

Codegen now emits `ArrayBuffer` for `ArrayBufferTypeAnnotation` params (was `ByteBuffer`) and returns (was `ByteBuffer`).

## Changelog:

[ANDROID] [ADDED] - Add `ArrayBuffer`, the Java representation of a JS `ArrayBuffer` for TurboModules, with an explicit byte-ownership contract

Pull Request resolved: https://github.com/react/react-native/pull/57897

Test Plan:
- Codegen Java spec and JNI C++ snapshot tests updated for `ArrayBuffer` param/return signatures.
- `SampleTurboModule` doubles its sync argument in place and returns the same buffer, covering the zero-copy path end to end; `createNativeBuffer` allocates via `ArrayBuffer`.
- C++ API snapshots regenerated.

Reviewed By: javache

Differential Revision: D115755247

Pulled By: christophpurrer

fbshipit-source-id: de067789ad145b7202da721a02f838358c82f4d8
This commit is contained in:
Kamil Paradowski
2026-08-17 15:48:23 -07:00
committed by meta-codesync[bot]
parent 4bf5575490
commit 5bb9639594
15 changed files with 409 additions and 48 deletions
@@ -282,8 +282,8 @@ function translateFunctionParamToJavaType(
imports.add('com.facebook.react.bridge.Callback');
return wrapOptional('Callback', isRequired);
case 'ArrayBufferTypeAnnotation':
imports.add('java.nio.ByteBuffer');
return wrapOptional('ByteBuffer', isRequired);
imports.add('com.facebook.react.bridge.ArrayBuffer');
return wrapOptional('ArrayBuffer', isRequired);
default:
realTypeAnnotation.type as 'MixedTypeAnnotation';
throw new Error(createErrorMessage(realTypeAnnotation.type));
@@ -379,8 +379,8 @@ function translateFunctionReturnTypeToJavaType(
imports.add('com.facebook.react.bridge.WritableArray');
return wrapOptional('WritableArray', isRequired);
case 'ArrayBufferTypeAnnotation':
imports.add('java.nio.ByteBuffer');
return wrapOptional('ByteBuffer', isRequired);
imports.add('com.facebook.react.bridge.ArrayBuffer');
return wrapOptional('ArrayBuffer', isRequired);
default:
realTypeAnnotation.type as 'MixedTypeAnnotation';
throw new Error(createErrorMessage(realTypeAnnotation.type));
@@ -311,7 +311,7 @@ function translateParamTypeToJniType(
case 'FunctionTypeAnnotation':
return 'Lcom/facebook/react/bridge/Callback;';
case 'ArrayBufferTypeAnnotation':
return 'Ljava/nio/ByteBuffer;';
return 'Lcom/facebook/react/bridge/ArrayBuffer;';
default:
realTypeAnnotation.type as 'MixedTypeAnnotation';
throw new Error(
@@ -397,7 +397,7 @@ function translateReturnTypeToJniType(
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableArray;';
case 'ArrayBufferTypeAnnotation':
return 'Ljava/nio/ByteBuffer;';
return 'Lcom/facebook/react/bridge/ArrayBuffer;';
default:
realTypeAnnotation.type as 'MixedTypeAnnotation';
throw new Error(
@@ -58,11 +58,11 @@ Map {
package com.facebook.fbreact.specs;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.ArrayBuffer;
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;
@@ -80,15 +80,15 @@ public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaMo
@ReactMethod(isBlockingSynchronousMethod = true)
@DoNotStrip
public abstract ByteBuffer getArrayBuffer();
public abstract ArrayBuffer getArrayBuffer();
@ReactMethod
@DoNotStrip
public abstract void voidArrayBuffer(ByteBuffer arg);
public abstract void voidArrayBuffer(ArrayBuffer arg);
@ReactMethod
@DoNotStrip
public abstract void voidNullableArrayBuffer(@Nullable ByteBuffer arg);
public abstract void voidNullableArrayBuffer(@Nullable ArrayBuffer arg);
}
",
}
@@ -53,17 +53,17 @@ 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);
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ArrayBufferKind, \\"getArrayBuffer\\", \\"()Lcom/facebook/react/bridge/ArrayBuffer;\\", 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);
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidArrayBuffer\\", \\"(Lcom/facebook/react/bridge/ArrayBuffer;)V\\", args, count, cachedMethodId);
}
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);
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, VoidKind, \\"voidNullableArrayBuffer\\", \\"(Lcom/facebook/react/bridge/ArrayBuffer;)V\\", args, count, cachedMethodId);
}
NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams &params)
@@ -537,6 +537,28 @@ public final class com/facebook/react/bridge/Arguments {
public static final fun toList (Lcom/facebook/react/bridge/ReadableArray;)Ljava/util/ArrayList;
}
public final class com/facebook/react/bridge/ArrayBuffer : com/facebook/jni/HybridClassBase {
public static final field Companion Lcom/facebook/react/bridge/ArrayBuffer$Companion;
public fun <init> (I)V
public synthetic fun <init> (Ljava/nio/ByteBuffer;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V
public static final fun arrayBufferWithCopiedBytes (Lcom/facebook/react/bridge/ArrayBuffer;)Lcom/facebook/react/bridge/ArrayBuffer;
public static final fun arrayBufferWithCopiedBytes (Ljava/nio/ByteBuffer;)Lcom/facebook/react/bridge/ArrayBuffer;
public static final fun arrayBufferWithCopiedBytes ([B)Lcom/facebook/react/bridge/ArrayBuffer;
public static final fun arrayBufferWithLength (I)Lcom/facebook/react/bridge/ArrayBuffer;
public static final fun arrayBufferWithOwnedBytes (Ljava/nio/ByteBuffer;)Lcom/facebook/react/bridge/ArrayBuffer;
public final fun getBytes ()Ljava/nio/ByteBuffer;
public final fun getSize ()I
public final fun isOwningBytes ()Z
}
public final class com/facebook/react/bridge/ArrayBuffer$Companion {
public final fun arrayBufferWithCopiedBytes (Lcom/facebook/react/bridge/ArrayBuffer;)Lcom/facebook/react/bridge/ArrayBuffer;
public final fun arrayBufferWithCopiedBytes (Ljava/nio/ByteBuffer;)Lcom/facebook/react/bridge/ArrayBuffer;
public final fun arrayBufferWithCopiedBytes ([B)Lcom/facebook/react/bridge/ArrayBuffer;
public final fun arrayBufferWithLength (I)Lcom/facebook/react/bridge/ArrayBuffer;
public final fun arrayBufferWithOwnedBytes (Ljava/nio/ByteBuffer;)Lcom/facebook/react/bridge/ArrayBuffer;
}
public final class com/facebook/react/bridge/AssertionException : java/lang/RuntimeException {
public fun <init> (Ljava/lang/String;)V
}
@@ -0,0 +1,120 @@
/*
* 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
import java.nio.ByteBuffer
/**
* A fixed-length byte buffer for TurboModule `ArrayBuffer` arguments and return values.
*
* @property isOwningBytes:
* - `true` — safe to retain and return to JS. Synchronize externally if JS may touch the same
* memory concurrently.
* - `false` — bytes are borrowed from a JS `ArrayBuffer` for the current synchronous call only.
* Copy with [arrayBufferWithCopiedBytes] to keep them.
*/
@DoNotStrip
public class ArrayBuffer : HybridClassBase {
private val buffer: ByteBuffer
/** Whether this buffer owns its bytes. See the class documentation. */
public val isOwningBytes: Boolean
@DoNotStrip
private constructor(buffer: ByteBuffer, isOwningBytes: Boolean) : super() {
this.buffer = buffer
this.isOwningBytes = isOwningBytes
}
/** @param size number of zero-filled bytes to allocate */
public constructor(size: Int) : this(allocateDirect(size), true) {
initHybrid(buffer, isOwningBytes)
}
public val bytes: ByteBuffer
get() = buffer
public val size: Int
get() = buffer.capacity()
private external fun initHybrid(buffer: ByteBuffer, isOwningBytes: Boolean)
public companion object {
init {
ReactNativeJniCommonSoLoader.staticInit()
}
/** @param size number of zero-filled bytes to allocate. Same as `ArrayBuffer(size)`. */
@JvmStatic
@DoNotStrip
public fun arrayBufferWithLength(size: Int): ArrayBuffer = ArrayBuffer(size)
/** @param bytes copied into a new owning buffer */
@JvmStatic
@DoNotStrip
public fun arrayBufferWithCopiedBytes(bytes: ByteArray): ArrayBuffer {
val buffer = ArrayBuffer(bytes.size)
if (bytes.isNotEmpty()) {
buffer.bytes.put(bytes)
buffer.bytes.rewind()
}
return buffer
}
/** @param source remaining bytes are copied into a new owning buffer */
@JvmStatic
@DoNotStrip
public fun arrayBufferWithCopiedBytes(source: ByteBuffer): ArrayBuffer {
val length = source.remaining()
val buffer = ArrayBuffer(length)
if (length > 0) {
buffer.bytes.put(source.duplicate())
buffer.bytes.rewind()
}
return buffer
}
/**
* @param source copied into a new owning buffer. Use to keep bytes from a non-owning argument
* after the call returns.
*/
@JvmStatic
@DoNotStrip
public fun arrayBufferWithCopiedBytes(source: ArrayBuffer): ArrayBuffer {
val length = source.size
val buffer = ArrayBuffer(length)
if (length > 0) {
val src = source.bytes.duplicate()
src.position(0)
src.limit(length)
buffer.bytes.put(src)
buffer.bytes.rewind()
}
return buffer
}
/**
* @param buffer direct [ByteBuffer] to alias without copying. The caller must keep it valid for
* as long as this [ArrayBuffer] lives.
*/
@JvmStatic
@DoNotStrip
public fun arrayBufferWithOwnedBytes(buffer: ByteBuffer): ArrayBuffer {
require(buffer.isDirect) { "arrayBufferWithOwnedBytes requires a direct ByteBuffer" }
return ArrayBuffer(buffer, true).apply { initHybrid(buffer, isOwningBytes) }
}
private fun allocateDirect(size: Int): ByteBuffer {
require(size >= 0) { "ArrayBuffer size must not be negative, got $size" }
return ByteBuffer.allocateDirect(size)
}
}
}
@@ -23,6 +23,7 @@ include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-ut
add_library(
reactnativejni_common
OBJECT
JArrayBuffer.cpp
JDynamicNative.cpp
JReactMarker.cpp
NativeArray.cpp
@@ -0,0 +1,118 @@
/*
* 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.
*/
#include "JArrayBuffer.h"
#include <cstring>
#include <span>
#include <utility>
#include <vector>
#include "JByteBufferMutableBuffer.h"
namespace facebook::react {
namespace {
// Holds a copy of bytes borrowed from a JS ArrayBuffer.
class OwnedBytesBuffer final : public jsi::MutableBuffer {
public:
explicit OwnedBytesBuffer(std::vector<uint8_t> bytes) noexcept
: bytes_(std::move(bytes)) {}
size_t size() const override {
return bytes_.size();
}
uint8_t* data() override {
return bytes_.data();
}
private:
std::vector<uint8_t> bytes_;
};
} // namespace
void JArrayBuffer::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", JArrayBuffer::initHybrid),
});
}
void JArrayBuffer::initHybrid(
jni::alias_ref<jhybridobject> jobj,
jni::alias_ref<jni::JByteBuffer> buffer,
jboolean owningBytes) {
setCxxInstance(
jobj,
std::make_shared<JByteBufferMutableBuffer>(buffer),
owningBytes != JNI_FALSE);
}
jni::local_ref<JArrayBuffer::javaobject> JArrayBuffer::create(
jni::local_ref<jni::JByteBuffer> byteBuffer,
std::shared_ptr<jsi::MutableBuffer> buffer,
bool owningBytes) {
auto cxxPart = std::make_unique<JArrayBuffer>(std::move(buffer), owningBytes);
auto javaPart = newObjectJavaArgs(byteBuffer, owningBytes);
setNativePointer(javaPart, std::move(cxxPart));
return javaPart;
}
jni::local_ref<JArrayBuffer::javaobject> JArrayBuffer::createOwning(
std::shared_ptr<jsi::MutableBuffer> buffer) {
// NewDirectByteBuffer rejects a null address, which is what an empty
// jsi::ArrayBuffer reports, so empty buffers get an allocation of their own.
if (buffer->size() == 0) {
return create(jni::JByteBuffer::allocateDirect(0), std::move(buffer), true);
}
auto byteBuffer = jni::JByteBuffer::wrapBytes(buffer->data(), buffer->size());
return create(std::move(byteBuffer), std::move(buffer), true);
}
jni::local_ref<JArrayBuffer::javaobject> JArrayBuffer::createUnowned(
void* bytes,
size_t size) {
// NewDirectByteBuffer rejects a null address, which is what an empty
// jsi::ArrayBuffer reports, so empty buffers get an allocation of their own.
auto byteBuffer = size == 0
? jni::JByteBuffer::allocateDirect(0)
: jni::JByteBuffer::wrapBytes(static_cast<uint8_t*>(bytes), size);
auto buffer = std::make_shared<JByteBufferMutableBuffer>(byteBuffer);
return create(std::move(byteBuffer), std::move(buffer), false);
}
jni::local_ref<JArrayBuffer::javaobject> JArrayBuffer::createOwned(
const void* bytes,
size_t size) {
auto byteBuffer = jni::JByteBuffer::allocateDirect(static_cast<jint>(size));
if (size > 0 && bytes != nullptr) {
// @lint-ignore CLANGSECURITY facebook-security-vulnerable-memcpy
std::memcpy(byteBuffer->getDirectBytes(), bytes, size);
}
auto buffer = std::make_shared<JByteBufferMutableBuffer>(byteBuffer);
return create(std::move(byteBuffer), std::move(buffer), true);
}
std::shared_ptr<jsi::MutableBuffer> JArrayBuffer::toJSBuffer(
jni::alias_ref<javaobject> arrayBuffer) {
auto* self = arrayBuffer->cthis();
if (self->owningBytes_) {
return self->buffer_;
}
// Borrowed bytes still belong to the inbound JS ArrayBuffer; copy them before
// handing a new buffer back to JS.
auto bytes = std::span<uint8_t>(self->buffer_->data(), self->buffer_->size());
return std::make_shared<OwnedBytesBuffer>(
std::vector<uint8_t>(bytes.begin(), bytes.end()));
}
} // namespace facebook::react
@@ -0,0 +1,64 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <fbjni/ByteBuffer.h>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
namespace facebook::react {
// JNI side of com.facebook.react.bridge.ArrayBuffer.
//
// When owningBytes_ is true, the module may retain the buffer and return it to
// JS without copying. When false, the bytes were borrowed from a JS-heap
// ArrayBuffer for a synchronous call only.
class JArrayBuffer : public jni::HybridClass<JArrayBuffer> {
public:
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/ArrayBuffer;";
static void registerNatives();
// JS ArrayBuffer with a native MutableBuffer (tryGetMutableBuffer). Retain
// the owner so the bytes stay valid after the call.
static jni::local_ref<javaobject> createOwning(std::shared_ptr<jsi::MutableBuffer> buffer);
// JS-heap bytes passed to a synchronous call. Zero-copy for the call only;
// do not retain the result.
static jni::local_ref<javaobject> createUnowned(void *bytes, size_t size);
// Copy JS-heap bytes into a new owned buffer. Used for async/promise calls
// and anywhere the module needs its own copy of the data.
static jni::local_ref<javaobject> createOwned(const void *bytes, size_t size);
// Convert a module return value for rt.createArrayBuffer. Owning buffers pass
// through; borrowed ones are copied because createArrayBuffer needs its own
// backing store.
static std::shared_ptr<jsi::MutableBuffer> toJSBuffer(jni::alias_ref<javaobject> arrayBuffer);
JArrayBuffer(std::shared_ptr<jsi::MutableBuffer> buffer, bool owningBytes) noexcept
: buffer_(std::move(buffer)), owningBytes_(owningBytes)
{
}
private:
friend HybridBase;
static void
initHybrid(jni::alias_ref<jhybridobject> jobj, jni::alias_ref<jni::JByteBuffer> buffer, jboolean owningBytes);
static jni::local_ref<javaobject>
create(jni::local_ref<jni::JByteBuffer> byteBuffer, std::shared_ptr<jsi::MutableBuffer> buffer, bool owningBytes);
std::shared_ptr<jsi::MutableBuffer> buffer_;
bool owningBytes_;
};
} // namespace facebook::react
@@ -6,6 +6,7 @@
*/
#include <fbjni/fbjni.h>
#include "JArrayBuffer.h"
#include "JCallback.h"
#include "JDynamicNative.h"
#include "JReactMarker.h"
@@ -18,6 +19,7 @@ namespace facebook::react {
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
return facebook::jni::initialize(vm, [] {
JArrayBuffer::registerNatives();
JCxxCallbackImpl::registerNatives();
JDynamicNative::registerNatives();
JReactMarker::registerNatives();
@@ -23,7 +23,7 @@
#include <react/bridging/Bridging.h>
#include <react/debug/react_native_assert.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/jni/JByteBufferMutableBuffer.h>
#include <react/jni/JArrayBuffer.h>
#include <react/jni/JDynamicNative.h>
#include <react/jni/NativeMap.h>
#include <react/jni/ReadableNativeMap.h>
@@ -315,6 +315,8 @@ JNIArgs convertJSIArgsToJNIArgs(
auto& jargs = jniArgs.args;
auto& globalRefs = jniArgs.globalRefs;
auto isSyncInvocation = valueKind != VoidKind && valueKind != PromiseKind;
auto makeGlobalIfNecessary = [&](jobject obj) {
if (valueKind == VoidKind || valueKind == PromiseKind) {
jobject globalObj = env->NewGlobalRef(obj);
@@ -440,7 +442,7 @@ JNIArgs convertJSIArgsToJNIArgs(
auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg);
auto jParams = JDynamicNative::newObjectCxxArgs(dynamicFromValue);
jarg->l = makeGlobalIfNecessary(jParams.release());
} else if (type == "Ljava/nio/ByteBuffer;") {
} else if (type == "Lcom/facebook/react/bridge/ArrayBuffer;") {
if (!(arg->isObject() && arg->getObject(rt).isArrayBuffer(rt))) {
throw JavaTurboModuleArgumentConversionException(
"ArrayBuffer", argIndex, methodName, arg, &rt);
@@ -459,18 +461,24 @@ JNIArgs convertJSIArgsToJNIArgs(
rt,
"JavaTurboModule::convertJSIArgsToJNIArgs: ArrayBuffer exceeds maximum size.");
}
auto data = arrayBuffer.data(rt);
// ArrayBuffer arguments are always copied into a Java-owned direct
// ByteBuffer, so Java fully owns the bytes. Borrowing the JS bytes is
// never safe — even on a synchronous call the module may retain the
// buffer or hand it to an async method, and JS may garbage-collect the
// source ArrayBuffer, leaving Java with a dangling view.
auto buffer = jni::JByteBuffer::allocateDirect(static_cast<jint>(size));
if (size > 0) {
// @lint-ignore CLANGSECURITY facebook-security-vulnerable-memcpy
std::memcpy(buffer->getDirectBytes(), data, size);
}
jarg->l = makeGlobalIfNecessary(buffer.release());
auto jArrayBuffer = [&]() {
// Backed by a native buffer: alias it and retain its owner, so the
// bytes stay valid for as long as the module holds the ArrayBuffer.
if (auto mutableBuffer = arrayBuffer.tryGetMutableBuffer(rt)) {
return JArrayBuffer::createOwning(std::move(mutableBuffer));
}
// JS heap bytes on a synchronous call: lend them for the duration of
// the call.
if (isSyncInvocation) {
return JArrayBuffer::createUnowned(arrayBuffer.data(rt), size);
}
// JS heap bytes that outlive the call: copy.
return JArrayBuffer::createOwned(arrayBuffer.data(rt), size);
}();
jarg->l = makeGlobalIfNecessary(jArrayBuffer.release());
} else {
throw JavaTurboModuleInvalidArgumentTypeException(
type, argIndex, methodName);
@@ -1013,22 +1021,11 @@ jsi::Value JavaTurboModule::invokeJavaMethod(
jsi::Value returnValue = jsi::Value::null();
if (returnObject != nullptr) {
auto jByteBuffer = jni::adopt_local(
static_cast<jni::JByteBuffer::javaobject>(returnObject));
if (!jByteBuffer->isDirect()) {
throw jsi::JSError(
runtime,
"Only direct ByteBuffers (ByteBuffer.allocateDirect) can be returned from a TurboModule.");
}
// Zero-copy: JByteBufferMutableBuffer takes a global reference that
// pins the ByteBuffer's memory for the lifetime of the JS ArrayBuffer,
// and its destructor attaches the current thread before releasing that
// ref, so JS GC finalization on any thread is safe.
auto nativeBuffer =
std::make_shared<JByteBufferMutableBuffer>(jByteBuffer);
auto jArrayBuffer = jni::adopt_local(
static_cast<JArrayBuffer::javaobject>(returnObject));
returnValue = {
runtime, jsi::ArrayBuffer{runtime, std::move(nativeBuffer)}};
runtime,
jsi::ArrayBuffer{runtime, JArrayBuffer::toJSBuffer(jArrayBuffer)}};
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
@@ -15,6 +15,7 @@ import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ArrayBuffer
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
@@ -27,7 +28,6 @@ import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder
import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings
import java.nio.ByteBuffer
import java.util.UUID
@DoNotStrip
@@ -155,28 +155,35 @@ public class SampleTurboModule(private val context: ReactApplicationContext) :
return map
}
// Mutating the argument updates the JS ArrayBuffer in place.
@DoNotStrip
@Suppress("unused")
override fun getArrayBuffer(buffer: ByteBuffer?): ByteBuffer? {
override fun getArrayBuffer(buffer: ArrayBuffer?): ArrayBuffer? {
if (buffer != null) {
val bytes = buffer.bytes
for (i in 0 until bytes.capacity()) {
bytes.put(i, (bytes.get(i) * 2).toByte())
}
}
log("getArrayBuffer", buffer, buffer)
return buffer
}
@DoNotStrip
@Suppress("unused")
override fun createNativeBuffer(size: Double): ByteBuffer {
override fun createNativeBuffer(size: Double): ArrayBuffer {
require(size.isFinite() && size >= 0.0 && size <= Int.MAX_VALUE.toDouble()) {
"createNativeBuffer: size must be a finite value in [0, ${Int.MAX_VALUE}], got $size"
}
val buffer = ByteBuffer.allocateDirect(size.toInt())
val buffer = ArrayBuffer(size.toInt())
log("createNativeBuffer", size, buffer)
return buffer
}
@DoNotStrip
@Suppress("unused")
override fun processAsyncBuffer(payload: ByteBuffer?, promise: Promise) {
promise.resolve((payload?.capacity() ?: 0).toDouble())
override fun processAsyncBuffer(payload: ArrayBuffer?, promise: Promise) {
promise.resolve((payload?.size ?: 0).toDouble())
}
@DoNotStrip
@@ -2792,6 +2792,16 @@ class facebook::react::IntersectionObserverState {
public static facebook::react::IntersectionObserverState NotIntersecting();
}
class facebook::react::JArrayBuffer : public jni::HybridClass<facebook::react::JArrayBuffer> {
public JArrayBuffer(std::shared_ptr<facebook::jsi::MutableBuffer> buffer, bool owningBytes) noexcept;
public static constexpr auto kJavaDescriptor;
public static jni::local_ref<javaobject> createOwned(const void* bytes, size_t size);
public static jni::local_ref<javaobject> createOwning(std::shared_ptr<facebook::jsi::MutableBuffer> buffer);
public static jni::local_ref<javaobject> createUnowned(void* bytes, size_t size);
public static std::shared_ptr<facebook::jsi::MutableBuffer> toJSBuffer(jni::alias_ref<javaobject> arrayBuffer);
public static void registerNatives();
}
class facebook::react::JBindingsInstaller : public jni::HybridClass<facebook::react::JBindingsInstaller>, public facebook::react::BindingsInstaller {
public static constexpr auto kJavaDescriptor;
public ~JBindingsInstaller();
@@ -2750,6 +2750,16 @@ class facebook::react::IntersectionObserverState {
public static facebook::react::IntersectionObserverState NotIntersecting();
}
class facebook::react::JArrayBuffer : public jni::HybridClass<facebook::react::JArrayBuffer> {
public JArrayBuffer(std::shared_ptr<facebook::jsi::MutableBuffer> buffer, bool owningBytes) noexcept;
public static constexpr auto kJavaDescriptor;
public static jni::local_ref<javaobject> createOwned(const void* bytes, size_t size);
public static jni::local_ref<javaobject> createOwning(std::shared_ptr<facebook::jsi::MutableBuffer> buffer);
public static jni::local_ref<javaobject> createUnowned(void* bytes, size_t size);
public static std::shared_ptr<facebook::jsi::MutableBuffer> toJSBuffer(jni::alias_ref<javaobject> arrayBuffer);
public static void registerNatives();
}
class facebook::react::JBindingsInstaller : public jni::HybridClass<facebook::react::JBindingsInstaller>, public facebook::react::BindingsInstaller {
public static constexpr auto kJavaDescriptor;
public ~JBindingsInstaller();
@@ -2789,6 +2789,16 @@ class facebook::react::IntersectionObserverState {
public static facebook::react::IntersectionObserverState NotIntersecting();
}
class facebook::react::JArrayBuffer : public jni::HybridClass<facebook::react::JArrayBuffer> {
public JArrayBuffer(std::shared_ptr<facebook::jsi::MutableBuffer> buffer, bool owningBytes) noexcept;
public static constexpr auto kJavaDescriptor;
public static jni::local_ref<javaobject> createOwned(const void* bytes, size_t size);
public static jni::local_ref<javaobject> createOwning(std::shared_ptr<facebook::jsi::MutableBuffer> buffer);
public static jni::local_ref<javaobject> createUnowned(void* bytes, size_t size);
public static std::shared_ptr<facebook::jsi::MutableBuffer> toJSBuffer(jni::alias_ref<javaobject> arrayBuffer);
public static void registerNatives();
}
class facebook::react::JBindingsInstaller : public jni::HybridClass<facebook::react::JBindingsInstaller>, public facebook::react::BindingsInstaller {
public static constexpr auto kJavaDescriptor;
public ~JBindingsInstaller();