diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index 1516c841e1d..b960aaed3fa 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -116,4 +116,15 @@ public class ReactFeatureFlags { /** Feature Flag to control RN Android scrollEventThrottle prop. */ public static boolean enableScrollEventThrottle = false; + + /** + * Feature flag that controls how turbo modules are exposed to JS + * + * + */ + public static int turboModuleBindingMode = 0; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp index 04238f8eb8f..a4d1c1289d9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp @@ -20,6 +20,17 @@ namespace facebook { namespace react { +constexpr static auto ReactFeatureFlagsJavaDescriptor = + "com/facebook/react/config/ReactFeatureFlags"; + +static int getFeatureFlagValue(const char *name) { + static const auto reactFeatureFlagsJavaDescriptor = + jni::findClassStatic(ReactFeatureFlagsJavaDescriptor); + const auto field = + reactFeatureFlagsJavaDescriptor->getStaticField(name); + return reactFeatureFlagsJavaDescriptor->getStaticFieldValue(field); +} + TurboModuleManager::TurboModuleManager( jni::alias_ref jThis, RuntimeExecutor runtimeExecutor, @@ -169,8 +180,13 @@ void TurboModuleManager::installJSIBindings() { return nullptr; }; + TurboModuleBindingMode bindingMode = static_cast( + getFeatureFlagValue("turboModuleBindingMode")); TurboModuleBinding::install( - runtime, std::move(turboModuleProvider), longLivedObjectCollection_); + runtime, + std::move(turboModuleProvider), + bindingMode, + longLivedObjectCollection_); }); } diff --git a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp index 3e27164946a..89ac90bcd83 100644 --- a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp +++ b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp @@ -15,5 +15,26 @@ TurboModule::TurboModule( std::shared_ptr jsInvoker) : name_(std::move(name)), jsInvoker_(std::move(jsInvoker)) {} +jsi::Value TurboModule::get( + jsi::Runtime &runtime, + const jsi::PropNameID &propName, + const MethodMetadata &meta) { + auto result = jsi::Function::createFromHostFunction( + runtime, + propName, + static_cast(meta.argCount), + [this, meta]( + jsi::Runtime &rt, + const jsi::Value &thisVal, + const jsi::Value *args, + size_t count) { return meta.invoker(rt, *this, args, count); }); + // If we have a JS wrapper, cache the result of this lookup + // We don't cache misses, to allow for methodMap_ to dynamically be extended + if (jsRepresentation_) { + jsRepresentation_->setProperty(runtime, propName, result); + } + return result; +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h index a55b8bec9ff..e0788dcbf73 100644 --- a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h +++ b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h @@ -53,16 +53,7 @@ class JSI_EXPORT TurboModule : public facebook::jsi::HostObject { // Method was not found, let JS decide what to do. return facebook::jsi::Value::undefined(); } else { - MethodMetadata meta = p->second; - return facebook::jsi::Function::createFromHostFunction( - runtime, - propName, - static_cast(meta.argCount), - [this, meta]( - jsi::Runtime &rt, - const jsi::Value &thisVal, - const jsi::Value *args, - size_t count) { return meta.invoker(rt, *this, args, count); }); + return get(runtime, propName, p->second); } } } @@ -80,7 +71,16 @@ class JSI_EXPORT TurboModule : public facebook::jsi::HostObject { size_t count); }; + facebook::jsi::Value get( + facebook::jsi::Runtime &runtime, + const facebook::jsi::PropNameID &propName, + const MethodMetadata &meta); + std::unordered_map methodMap_; + + private: + friend class TurboModuleBinding; + std::unique_ptr jsRepresentation_; }; /** diff --git a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp index d24ef17707e..77c8d222318 100644 --- a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp +++ b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp @@ -24,13 +24,16 @@ namespace react { TurboModuleBinding::TurboModuleBinding( const TurboModuleProviderFunctionType &&moduleProvider, + TurboModuleBindingMode bindingMode, std::shared_ptr longLivedObjectCollection) : moduleProvider_(std::move(moduleProvider)), - longLivedObjectCollection_(std::move(longLivedObjectCollection)) {} + longLivedObjectCollection_(std::move(longLivedObjectCollection)), + bindingMode_(bindingMode) {} void TurboModuleBinding::install( jsi::Runtime &runtime, const TurboModuleProviderFunctionType &&moduleProvider, + TurboModuleBindingMode bindingMode, std::shared_ptr longLivedObjectCollection) { runtime.global().setProperty( runtime, @@ -41,6 +44,7 @@ void TurboModuleBinding::install( 1, [binding = TurboModuleBinding( std::move(moduleProvider), + bindingMode, std::move(longLivedObjectCollection))]( jsi::Runtime &rt, const jsi::Value &thisVal, @@ -76,7 +80,33 @@ jsi::Value TurboModuleBinding::getModule( module = moduleProvider_(moduleName); } if (module) { - return jsi::Object::createFromHostObject(runtime, std::move(module)); + // Default behaviour + if (bindingMode_ == TurboModuleBindingMode::HostObject) { + return jsi::Object::createFromHostObject(runtime, std::move(module)); + } + + auto &jsRepresentation = module->jsRepresentation_; + if (!jsRepresentation) { + jsRepresentation = std::make_unique(runtime); + if (bindingMode_ == TurboModuleBindingMode::Prototype) { + // Option 1: create plain object, with it's prototype mapped back to the + // hostobject. Any properties accessed are stored on the plain object + auto hostObject = + jsi::Object::createFromHostObject(runtime, std::move(module)); + jsRepresentation->setProperty( + runtime, "__proto__", std::move(hostObject)); + } else { + // Option 2: eagerly install all hostfunctions at this point, avoids + // prototype + for (auto it = module->methodMap_.cbegin(); + it != module->methodMap_.cend(); + ++it) { + auto propName = jsi::PropNameID::forUtf8(runtime, it->first); + module->get(runtime, propName, it->second); + } + } + } + return jsi::Value(runtime, *jsRepresentation); } else { return jsi::Value::null(); } diff --git a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.h b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.h index 8d7742f3ff0..1791d4e272e 100644 --- a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.h +++ b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.h @@ -18,6 +18,12 @@ namespace react { class JSCallInvoker; +enum class TurboModuleBindingMode : uint8_t { + HostObject = 0, + Prototype = 1, + Eager = 2, +}; + /** * Represents the JavaScript binding for the TurboModule system. */ @@ -30,11 +36,13 @@ class TurboModuleBinding { static void install( jsi::Runtime &runtime, const TurboModuleProviderFunctionType &&moduleProvider, + TurboModuleBindingMode bindingMode, std::shared_ptr longLivedObjectCollection); private: TurboModuleBinding( const TurboModuleProviderFunctionType &&moduleProvider, + TurboModuleBindingMode bindingMode, std::shared_ptr longLivedObjectCollection); virtual ~TurboModuleBinding(); @@ -50,6 +58,7 @@ class TurboModuleBinding { TurboModuleProviderFunctionType moduleProvider_; std::shared_ptr longLivedObjectCollection_; + TurboModuleBindingMode bindingMode_; }; } // namespace react diff --git a/ReactCommon/react/nativemodule/core/platform/ios/RCTTurboModuleManager.mm b/ReactCommon/react/nativemodule/core/platform/ios/RCTTurboModuleManager.mm index e58ce0dc126..a6689b4e7ff 100644 --- a/ReactCommon/react/nativemodule/core/platform/ios/RCTTurboModuleManager.mm +++ b/ReactCommon/react/nativemodule/core/platform/ios/RCTTurboModuleManager.mm @@ -776,12 +776,14 @@ static Class getFallbackClassFromName(const char *name) if (RCTGetTurboModuleCleanupMode() == kRCTGlobalScope || RCTGetTurboModuleCleanupMode() == kRCTGlobalScopeUsingRetainJSCallback) { runtimeExecutor([turboModuleProvider = std::move(turboModuleProvider)](jsi::Runtime &runtime) { - react::TurboModuleBinding::install(runtime, std::move(turboModuleProvider), nullptr); + react::TurboModuleBinding::install( + runtime, std::move(turboModuleProvider), TurboModuleBindingMode::HostObject, nullptr); }); } else if (RCTGetTurboModuleCleanupMode() == kRCTTurboModuleManagerScope) { runtimeExecutor([turboModuleProvider = std::move(turboModuleProvider), longLivedObjectCollection = _longLivedObjectCollection](jsi::Runtime &runtime) { - react::TurboModuleBinding::install(runtime, std::move(turboModuleProvider), longLivedObjectCollection); + react::TurboModuleBinding::install( + runtime, std::move(turboModuleProvider), TurboModuleBindingMode::HostObject, longLivedObjectCollection); }); } }