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 52d9861de96..81c17fabb95 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 @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "TurboModuleManager.h" @@ -76,7 +77,8 @@ void TurboModuleManager::installJSIBindings() { jsCallInvoker_ = std::weak_ptr(jsCallInvoker_), nativeCallInvoker_ = std::weak_ptr(nativeCallInvoker_), delegate_ = jni::make_weak(delegate_), - javaPart_ = jni::make_weak(javaPart_)]( + javaPart_ = jni::make_weak(javaPart_), + runtime_ = runtime_]( const std::string &name, const jsi::Value *schema) -> std::shared_ptr { auto turboModuleCache = turboModuleCache_.lock(); @@ -139,6 +141,13 @@ void TurboModuleManager::installJSIBindings() { .jsInvoker = jsCallInvoker, .nativeInvoker = nativeCallInvoker}; + if (schema->isObject() && !schema->isNull()) { + auto turboModule = std::make_shared( + params, TurboModuleSchema::parse(*runtime_, name, *schema)); + TurboModulePerfLogger::moduleJSRequireEndingEnd(moduleName); + return turboModule; + } + auto turboModule = delegate->cthis()->getTurboModule(name, params); turboModuleCache->insert({name, turboModule}); TurboModulePerfLogger::moduleJSRequireEndingEnd(moduleName); diff --git a/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp b/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp index f4b3f967e91..f7af1f517f5 100644 --- a/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp +++ b/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp @@ -56,6 +56,68 @@ void JavaTurboModule::enablePromiseAsyncDispatch(bool enable) { isPromiseAsyncDispatchEnabled_ = enable; } +JavaTurboModule::JavaTurboModule( + const InitParams ¶ms, + TurboModuleSchema &&schema) + : TurboModule(params.moduleName, params.jsInvoker), + instance_(jni::make_global(params.instance)), + nativeInvoker_(params.nativeInvoker), + turboModuleSchema_(std::move(schema)) {} + +jsi::Value JavaTurboModule::get( + jsi::Runtime &runtime, + const jsi::PropNameID &propName) { + if (!turboModuleSchema_) { + return TurboModule::get(runtime, propName); + } + + std::string methodName = propName.utf8(runtime); + if (!turboModuleSchema_->hasMethod(methodName)) { + return jsi::Value::undefined(); + } + + using MethodImplStatus = TurboModuleSchema::Method::ImplStatus; + TurboModuleSchema::Method &method = turboModuleSchema_->getMethod(methodName); + + if (method.isOptional) { + if (method.implStatus == MethodImplStatus::Unknown) { + auto instance = instance_.get(); + JNIEnv *env = jni::Environment::current(); + jclass cls = env->GetObjectClass(instance); + jmethodID methodID = env->GetMethodID( + cls, methodName.c_str(), method.jniSignature.c_str()); + + // If the method signature doesn't match, show a redbox here instead of + // crashing later. + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + method.implStatus = methodID != nullptr ? MethodImplStatus::Implemented + : MethodImplStatus::Unimplemented; + } + + if (method.implStatus == MethodImplStatus::Unimplemented) { + return jsi::Value::undefined(); + } + } + + return jsi::Function::createFromHostFunction( + runtime, + propName, + method.jsParamCount, + [this, method]( + facebook::jsi::Runtime &runtime, + const facebook::jsi::Value &thisVal, + const facebook::jsi::Value *args, + size_t count) { + return invokeJavaMethod( + runtime, + method.jsReturnType, + method.name, + method.jniSignature, + args, + count); + }); +} + namespace { jni::local_ref createJavaCallbackFromJSIFunction( jsi::Function &&function, diff --git a/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.h b/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.h index 93acda48fcf..2e15fea7176 100644 --- a/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.h +++ b/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.h @@ -13,9 +13,12 @@ #include #include #include +#include #include #include +#include "TurboModuleSchema.h" + namespace facebook { namespace react { @@ -41,7 +44,9 @@ class JSI_EXPORT JavaTurboModule : public TurboModule { }; JavaTurboModule(const InitParams ¶ms); + JavaTurboModule(const InitParams ¶ms, TurboModuleSchema &&schema); virtual ~JavaTurboModule(); + jsi::Value invokeJavaMethod( jsi::Runtime &runtime, TurboModuleMethodValueKind valueKind, @@ -51,10 +56,13 @@ class JSI_EXPORT JavaTurboModule : public TurboModule { size_t argCount); static void enablePromiseAsyncDispatch(bool enable); + jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &propName) + override; private: jni::global_ref instance_; std::shared_ptr nativeInvoker_; + folly::Optional turboModuleSchema_; /** * Experiments diff --git a/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/TurboModuleSchema.cpp b/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/TurboModuleSchema.cpp index dfa51d287cd..484a3e98e4d 100644 --- a/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/TurboModuleSchema.cpp +++ b/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/TurboModuleSchema.cpp @@ -44,6 +44,7 @@ std::ostream &operator<<(std::ostream &os, const TurboModuleSchema &schema) { << std::endl; os << " .isOptional = " << (method.isOptional ? "true" : "false") << std::endl; + os << " .jsParamCount = " << method.jsParamCount << "," << std::endl; os << " }," << std::endl; } @@ -96,9 +97,9 @@ bool TurboModuleSchema::hasMethod(const std::string &methodName) const { return false; } -const TurboModuleSchema::Method &TurboModuleSchema::getMethod( - const std::string &methodName) const { - for (const Method &method : methods_) { +TurboModuleSchema::Method &TurboModuleSchema::getMethod( + const std::string &methodName) { + for (Method &method : methods_) { if (method.name == methodName) { return method; } @@ -377,6 +378,8 @@ TurboModuleSchema::Method parseMethod( .name = methodName, .jniSignature = "()Ljava/util/Map;", .isOptional = !isMethodRequired, + .jsParamCount = 0, + .implStatus = TurboModuleSchema::Method::ImplStatus::Unknown, }; } @@ -460,6 +463,8 @@ TurboModuleSchema::Method parseMethod( .name = methodName, .jniSignature = jniSignature, .isOptional = !isMethodRequired, + .jsParamCount = numFunctionTypeAnnotationParams, + .implStatus = TurboModuleSchema::Method::ImplStatus::Unknown, }; } diff --git a/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/TurboModuleSchema.h b/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/TurboModuleSchema.h index 76e0839c2ce..585739de76c 100644 --- a/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/TurboModuleSchema.h +++ b/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/TurboModuleSchema.h @@ -18,10 +18,24 @@ namespace react { class TurboModuleSchema { public: struct Method { + /** + * Optional methods might not be implemented on the Java NativeModule class. + * - Unknown: We must check if the method exists using JNI + * - Implemented: Using JNI, we verified that the method exists + * - Unimplemented: Using JNI, we verified that the method doesn't exist + */ + enum class ImplStatus { + Unknown, + Implemented, + Unimplemented, + }; + const TurboModuleMethodValueKind jsReturnType; const std::string name; const std::string jniSignature; const bool isOptional; + const size_t jsParamCount; + ImplStatus implStatus; }; class ParseException : public jsi::JSIException { @@ -31,7 +45,7 @@ class TurboModuleSchema { private: const std::string moduleName_; - const std::vector methods_; + std::vector methods_; TurboModuleSchema( const std::string &moduleName, @@ -40,7 +54,7 @@ class TurboModuleSchema { public: TurboModuleSchema() = delete; bool hasMethod(const std::string &methodName) const; - const Method &getMethod(const std::string &methodName) const; + Method &getMethod(const std::string &methodName); static TurboModuleSchema parse( jsi::Runtime &runtime,