diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/BindingsInstallerHolder.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/BindingsInstallerHolder.kt new file mode 100644 index 000000000000..6d0a835887a6 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/BindingsInstallerHolder.kt @@ -0,0 +1,17 @@ +/* + * 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.turbomodule.core + +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip + +/** + * A Java holder for a C++ BindingsInstallerHolder. + */ +@DoNotStrip +public class BindingsInstallerHolder(@field:DoNotStrip private val mHybridData: HybridData) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/interfaces/TurboModuleWithJSIBindings.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/interfaces/TurboModuleWithJSIBindings.kt new file mode 100644 index 000000000000..b4bb4f1ec52e --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/interfaces/TurboModuleWithJSIBindings.kt @@ -0,0 +1,25 @@ +/* + * 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.turbomodule.core.interfaces + +import com.facebook.proguard.annotations.DoNotStrip +import com.facebook.react.turbomodule.core.BindingsInstallerHolder + +/** + * Implements this interface if a TurboModule needs to install its own JSI bindings. + */ +@DoNotStrip +public interface TurboModuleWithJSIBindings { + /** + * Returns the [BindingsInstallerHolder] that the core will later invoke with + * an `facebook::jsi::Runtime` instance. + * The implementation will typically mix with JNI and C++. + */ + @DoNotStrip + public fun getBindingsInstaller(): BindingsInstallerHolder +} diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/CMakeLists.txt index 2a937e547ca9..4c7aeb1f8964 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/CMakeLists.txt @@ -44,6 +44,7 @@ target_link_libraries(callinvokerholder add_library( turbomodulejsijni SHARED + ReactCommon/BindingsInstallerHolder.cpp ReactCommon/CompositeTurboModuleManagerDelegate.cpp ReactCommon/OnLoad.cpp ReactCommon/TurboModuleManager.cpp diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/BindingsInstallerHolder.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/BindingsInstallerHolder.cpp new file mode 100644 index 000000000000..5239fdf99e41 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/BindingsInstallerHolder.cpp @@ -0,0 +1,20 @@ +/* + * 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 "BindingsInstallerHolder.h" + +namespace facebook::react { + +BindingsInstallerHolder::BindingsInstallerHolder( + TurboModule::BindingsInstaller bindingsInstaller) + : bindingsInstaller_(bindingsInstaller) {} + +TurboModule::BindingsInstaller BindingsInstallerHolder::get() { + return bindingsInstaller_; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/BindingsInstallerHolder.h b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/BindingsInstallerHolder.h new file mode 100644 index 000000000000..b04252c9bb98 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/BindingsInstallerHolder.h @@ -0,0 +1,29 @@ +/* + * 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 + +namespace facebook::react { + +class BindingsInstallerHolder + : public jni::HybridClass { + public: + static auto constexpr kJavaDescriptor = + "Lcom/facebook/react/turbomodule/core/BindingsInstallerHolder;"; + + TurboModule::BindingsInstaller get(); + + private: + friend HybridBase; + BindingsInstallerHolder(TurboModule::BindingsInstaller bindingsInstaller); + TurboModule::BindingsInstaller bindingsInstaller_; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.cpp index 58230cdc97a0..4505e4fdff33 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -128,8 +129,10 @@ void TurboModuleManager::registerNatives() { TurboModuleProviderFunctionType TurboModuleManager::createTurboModuleProvider( jni::alias_ref javaPart, + jsi::Runtime* runtime, bool enableSyncVoidMethods) { return [turboModuleCache_ = std::weak_ptr(turboModuleCache_), + runtime, jsCallInvoker_ = std::weak_ptr(jsCallInvoker_), nativeMethodCallInvoker_ = std::weak_ptr(nativeMethodCallInvoker_), @@ -208,6 +211,18 @@ TurboModuleProviderFunctionType TurboModuleManager::createTurboModuleProvider( .shouldVoidMethodsExecuteSync = enableSyncVoidMethods}; auto turboModule = delegate->cthis()->getTurboModule(name, params); + if (moduleInstance->isInstanceOf( + JTurboModuleWithJSIBindings::javaClassStatic())) { + auto getBindingsInstaller = + moduleInstance->getClass() + ->getMethod( + "getBindingsInstaller"); + auto installer = getBindingsInstaller(moduleInstance); + if (installer) { + installer->cthis()->get()(*runtime); + } + } + turboModuleCache->insert({name, turboModule}); TurboModulePerfLogger::moduleJSRequireEndingEnd(moduleName); return turboModule; @@ -325,7 +340,8 @@ void TurboModuleManager::installJSIBindings( enableSyncVoidMethods](jsi::Runtime& runtime) { TurboModuleBinding::install( runtime, - cxxPart->createTurboModuleProvider(javaPart, enableSyncVoidMethods), + cxxPart->createTurboModuleProvider( + javaPart, &runtime, enableSyncVoidMethods), shouldCreateLegacyModules ? cxxPart->createLegacyModuleProvider(javaPart) : nullptr); diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.h b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.h index 9cc27b76a44f..4e44a59edc3e 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.h @@ -67,6 +67,7 @@ class TurboModuleManager : public jni::HybridClass { TurboModuleProviderFunctionType createTurboModuleProvider( jni::alias_ref javaPart, + jsi::Runtime* runtime, bool enableSyncVoidMethods); TurboModuleProviderFunctionType createLegacyModuleProvider( jni::alias_ref javaPart); diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h index aad026af97b7..6de478d9a7ce 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h @@ -48,6 +48,8 @@ class JSI_EXPORT TurboModule : public facebook::jsi::HostObject { public: TurboModule(std::string name, std::shared_ptr jsInvoker); + using BindingsInstaller = std::function; + // Note: keep this method declared inline to avoid conflicts // between RTTI and non-RTTI compilation units facebook::jsi::Value get( 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 cb020a852e6a..15a8265645df 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 @@ -22,6 +22,12 @@ struct JTurboModule : jni::JavaClass { "Lcom/facebook/react/turbomodule/core/interfaces/TurboModule;"; }; +struct JTurboModuleWithJSIBindings + : jni::JavaClass { + static auto constexpr kJavaDescriptor = + "Lcom/facebook/react/turbomodule/core/interfaces/TurboModuleWithJSIBindings;"; +}; + class JSI_EXPORT JavaTurboModule : public TurboModule { public: // TODO(T65603471): Should we unify this with a Fabric abstraction? diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/CMakeLists.txt index b405fc7c7883..9652f38fa711 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/CMakeLists.txt @@ -23,4 +23,5 @@ target_include_directories(sampleturbomodule PUBLIC .) target_link_libraries(sampleturbomodule fbjni jsi - react_nativemodule_core) + react_nativemodule_core + turbomodulejsijni) diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleJSIBindings.cpp b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleJSIBindings.cpp new file mode 100644 index 000000000000..8f94e822a318 --- /dev/null +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleJSIBindings.cpp @@ -0,0 +1,32 @@ +/* + * 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 + +namespace facebook::react { + +// static +void SampleTurboModuleJSIBindings::registerNatives() { + javaClassLocal()->registerNatives({ + makeNativeMethod( + "getBindingsInstallerCxx", + SampleTurboModuleJSIBindings::getBindingsInstallerCxx), + }); +} + +// static +jni::local_ref +SampleTurboModuleJSIBindings::getBindingsInstallerCxx( + jni::alias_ref clazz) { + return jni::make_local( + BindingsInstallerHolder::newObjectCxxArgs([](jsi::Runtime& runtime) { + runtime.global().setProperty( + runtime, "__SampleTurboModuleJSIBindings", "Hello JSI!"); + })); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleJSIBindings.h b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleJSIBindings.h new file mode 100644 index 000000000000..26c50746c7c1 --- /dev/null +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleJSIBindings.h @@ -0,0 +1,32 @@ +/* + * 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 + +namespace facebook::react { + +class SampleTurboModuleJSIBindings + : public jni::JavaClass { + public: + static constexpr const char* kJavaDescriptor = + "Lcom/facebook/fbreact/specs/SampleTurboModule;"; + + SampleTurboModuleJSIBindings() = default; + + static void registerNatives(); + + private: + // Using static function as a simple demonstration + static jni::local_ref + getBindingsInstallerCxx(jni::alias_ref clazz); +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.java b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.java index 866f7c2e6ab6..9a29e062654c 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.java +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.java @@ -23,11 +23,14 @@ import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.common.MapBuilder; import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.turbomodule.core.BindingsInstallerHolder; +import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings; import java.util.HashMap; import java.util.Map; @ReactModule(name = SampleTurboModule.NAME) -public class SampleTurboModule extends NativeSampleTurboModuleSpec { +public class SampleTurboModule extends NativeSampleTurboModuleSpec + implements TurboModuleWithJSIBindings { public static final String NAME = "SampleTurboModule"; @@ -251,4 +254,12 @@ public void invalidate() {} public String getName() { return NAME; } + + @Override + @DoNotStrip + public BindingsInstallerHolder getBindingsInstaller() { + return getBindingsInstallerCxx(); + } + + private static native BindingsInstallerHolder getBindingsInstallerCxx(); } diff --git a/packages/rn-tester/android/app/src/main/jni/OnLoad.cpp b/packages/rn-tester/android/app/src/main/jni/OnLoad.cpp index 927ab744ca76..4c488722ca85 100644 --- a/packages/rn-tester/android/app/src/main/jni/OnLoad.cpp +++ b/packages/rn-tester/android/app/src/main/jni/OnLoad.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -73,5 +74,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { facebook::react::DefaultComponentsRegistry:: registerComponentDescriptorsFromEntryPoint = &facebook::react::registerComponents; + facebook::react::SampleTurboModuleJSIBindings::registerNatives(); }); } diff --git a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js index 7e3a979ec5b0..ac6f10662d64 100644 --- a/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js +++ b/packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js @@ -14,6 +14,7 @@ import styles from './TurboModuleExampleCommon'; import * as React from 'react'; import { FlatList, + Platform, RootTagContext, Text, TouchableOpacity, @@ -128,6 +129,9 @@ class SampleTurboModuleExample extends React.Component<{||}, State> { this._setResult('promiseAssert', e.message); }); }, + installJSIBindings: () => { + return global.__SampleTurboModuleJSIBindings; + }, }; _setResult( @@ -194,6 +198,14 @@ class SampleTurboModuleExample extends React.Component<{||}, State> { 'Cannot load this example because TurboModule is not configured.', ); } + if ( + global.__SampleTurboModuleJSIBindings == null && + Platform.OS === 'android' // TODO: Add iOS support + ) { + throw new Error( + 'The JSI bindings for SampleTurboModule are not installed.', + ); + } } render(): React.Node {