diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js index 472a2cc0f..071f04f74 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js @@ -58,6 +58,9 @@ function genModule(config: ?ModuleConfig, moduleID: number): ?{name: string, mod return { name: moduleName, module }; } +// export this method as a global so we can call it from native +global.__fbGenNativeModule = genModule; + function loadModule(name: string, moduleID: number): ?Object { invariant(global.nativeRequireModuleConfig, 'Can\'t lazily create module without nativeRequireModuleConfig'); @@ -115,27 +118,31 @@ function createErrorFromErrorData(errorData: {message: string}): Error { return Object.assign(error, extraErrorInfo); } -const bridgeConfig = global.__fbBatchedBridgeConfig; -invariant(bridgeConfig, '__fbBatchedBridgeConfig is not set, cannot invoke native modules'); +let NativeModules : {[moduleName: string]: Object} = {}; +if (global.nativeModuleProxy) { + NativeModules = global.nativeModuleProxy; +} else { + const bridgeConfig = global.__fbBatchedBridgeConfig; + invariant(bridgeConfig, '__fbBatchedBridgeConfig is not set, cannot invoke native modules'); -const NativeModules : {[moduleName: string]: Object} = {}; -(bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => { - // Initially this config will only contain the module name when running in JSC. The actual - // configuration of the module will be lazily loaded. - const info = genModule(config, moduleID); - if (!info) { - return; - } + (bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => { + // Initially this config will only contain the module name when running in JSC. The actual + // configuration of the module will be lazily loaded. + const info = genModule(config, moduleID); + if (!info) { + return; + } - if (info.module) { - NativeModules[info.name] = info.module; - } - // If there's no module config, define a lazy getter - else { - defineLazyObjectProperty(NativeModules, info.name, { - get: () => loadModule(info.name, moduleID) - }); - } -}); + if (info.module) { + NativeModules[info.name] = info.module; + } + // If there's no module config, define a lazy getter + else { + defineLazyObjectProperty(NativeModules, info.name, { + get: () => loadModule(info.name, moduleID) + }); + } + }); +} module.exports = NativeModules; diff --git a/ReactAndroid/src/main/jni/xreact/jni/ProxyExecutor.cpp b/ReactAndroid/src/main/jni/xreact/jni/ProxyExecutor.cpp index 20d8d6ee2..9287f6c85 100644 --- a/ReactAndroid/src/main/jni/xreact/jni/ProxyExecutor.cpp +++ b/ReactAndroid/src/main/jni/xreact/jni/ProxyExecutor.cpp @@ -42,12 +42,13 @@ ProxyExecutor::ProxyExecutor(jni::global_ref&& executorInstance, , m_delegate(delegate) { folly::dynamic nativeModuleConfig = folly::dynamic::array; - auto moduleRegistry = delegate->getModuleRegistry(); { SystraceSection s("collectNativeModuleDescriptions"); + auto moduleRegistry = delegate->getModuleRegistry(); for (const auto& name : moduleRegistry->moduleNames()) { - nativeModuleConfig.push_back(moduleRegistry->getConfig(name)); + auto config = moduleRegistry->getConfig(name); + nativeModuleConfig.push_back(config ? config->config : nullptr); } } diff --git a/ReactCommon/cxxreact/Android.mk b/ReactCommon/cxxreact/Android.mk index 3be689a2c..0cd876001 100644 --- a/ReactCommon/cxxreact/Android.mk +++ b/ReactCommon/cxxreact/Android.mk @@ -13,6 +13,7 @@ LOCAL_SRC_FILES := \ JSCLegacyProfiler.cpp \ JSCLegacyTracing.cpp \ JSCMemory.cpp \ + JSCNativeModules.cpp \ JSCPerfStats.cpp \ JSCTracing.cpp \ JSCWebWorker.cpp \ diff --git a/ReactCommon/cxxreact/BUCK b/ReactCommon/cxxreact/BUCK index a16b65212..5ea4d388a 100644 --- a/ReactCommon/cxxreact/BUCK +++ b/ReactCommon/cxxreact/BUCK @@ -116,6 +116,7 @@ CXXREACT_PUBLIC_HEADERS = [ 'Instance.h', 'JSCExecutor.h', 'JSCHelpers.h', + 'JSCNativeModules.h', 'JSCWebWorker.h', 'JSModulesUnbundle.h', 'MessageQueueThread.h', diff --git a/ReactCommon/cxxreact/JSCExecutor.cpp b/ReactCommon/cxxreact/JSCExecutor.cpp index 7d7046ae7..210ee64e8 100644 --- a/ReactCommon/cxxreact/JSCExecutor.cpp +++ b/ReactCommon/cxxreact/JSCExecutor.cpp @@ -20,6 +20,7 @@ #include "Platform.h" #include "SystraceSection.h" #include "Value.h" +#include "JSCNativeModules.h" #include "JSCSamplingProfiler.h" #include "JSModulesUnbundle.h" #include "ModuleRegistry.h" @@ -79,6 +80,28 @@ inline JSObjectCallAsFunctionCallback exceptionWrapMethod() { return &funcWrapper::call; } +template +inline JSObjectGetPropertyCallback exceptionWrapMethod() { + struct funcWrapper { + static JSValueRef call( + JSContextRef ctx, + JSObjectRef object, + JSStringRef propertyName, + JSValueRef *exception) { + try { + auto globalObj = JSContextGetGlobalObject(ctx); + auto executor = static_cast(JSObjectGetPrivate(globalObj)); + return (executor->*method)(object, propertyName); + } catch (...) { + *exception = translatePendingCppExceptionToJSError(ctx, object); + return JSValueMakeUndefined(ctx); + } + } + }; + + return &funcWrapper::call; +} + } #if DEBUG @@ -109,28 +132,15 @@ JSCExecutor::JSCExecutor(std::shared_ptr delegate, m_delegate(delegate), m_deviceCacheDir(cacheDir), m_messageQueueThread(messageQueueThread), + m_nativeModules(delegate->getModuleRegistry()), m_jscConfig(jscConfig) { initOnJSVMThread(); - SystraceSection s("setBatchedBridgeConfig"); - - folly::dynamic nativeModuleConfig = folly::dynamic::array(); - { - SystraceSection s("collectNativeModuleNames"); - for (auto& name : delegate->getModuleRegistry()->moduleNames()) { - nativeModuleConfig.push_back(folly::dynamic::array(std::move(name))); - } + SystraceSection s("nativeModuleProxy object"); + installGlobalProxy(m_context, "nativeModuleProxy", + exceptionWrapMethod<&JSCExecutor::getNativeModule>()); } - - folly::dynamic config = - folly::dynamic::object - ("remoteModuleConfig", std::move(nativeModuleConfig)); - - SystraceSection t("setGlobalVariable"); - setGlobalVariable( - "__fbBatchedBridgeConfig", - folly::make_unique(folly::toJson(config))); } JSCExecutor::JSCExecutor( @@ -146,6 +156,7 @@ JSCExecutor::JSCExecutor( m_owner(owner), m_deviceCacheDir(owner->m_deviceCacheDir), m_messageQueueThread(messageQueueThread), + m_nativeModules(delegate->getModuleRegistry()), m_jscConfig(jscConfig) { // We post initOnJSVMThread here so that the owner doesn't have to wait for // initialization on its own thread @@ -211,7 +222,6 @@ void JSCExecutor::initOnJSVMThread() throw(JSException) { // Add a pointer to ourselves so we can retrieve it later in our hooks JSObjectSetPrivate(JSContextGetGlobalObject(m_context), this); - installNativeHook<&JSCExecutor::nativeRequireModuleConfig>("nativeRequireModuleConfig"); installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate"); installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook"); @@ -264,6 +274,8 @@ void JSCExecutor::terminateOnJSVMThread() { terminateOwnedWebWorker(workerId); } + m_nativeModules.reset(); + JSGlobalContextRelease(m_context); m_context = nullptr; } @@ -647,6 +659,14 @@ void JSCExecutor::installNativeHook(const char* name) { installGlobalFunction(m_context, name, exceptionWrapMethod()); } +JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName) { + if (JSStringIsEqualToUTF8CString(propertyName, "name")) { + return Value(m_context, String("NativeModules")); + } + + return m_nativeModules.getModule(m_context, propertyName); +} + JSValueRef JSCExecutor::nativePostMessage( size_t argumentCount, const JSValueRef arguments[]) { @@ -680,18 +700,6 @@ JSValueRef JSCExecutor::nativeRequire( return JSValueMakeUndefined(m_context); } -JSValueRef JSCExecutor::nativeRequireModuleConfig( - size_t argumentCount, - const JSValueRef arguments[]) { - if (argumentCount != 1) { - throw std::invalid_argument("Got wrong number of args"); - } - - std::string moduleName = Value(m_context, arguments[0]).toString().str(); - folly::dynamic config = m_delegate->getModuleRegistry()->getConfig(moduleName); - return Value::fromDynamic(m_context, config); -} - JSValueRef JSCExecutor::nativeFlushQueueImmediate( size_t argumentCount, const JSValueRef arguments[]) { diff --git a/ReactCommon/cxxreact/JSCExecutor.h b/ReactCommon/cxxreact/JSCExecutor.h index 63ea21b63..ce7d4992f 100644 --- a/ReactCommon/cxxreact/JSCExecutor.h +++ b/ReactCommon/cxxreact/JSCExecutor.h @@ -15,6 +15,7 @@ #include "ExecutorToken.h" #include "JSCHelpers.h" #include "Value.h" +#include "JSCNativeModules.h" namespace facebook { namespace react { @@ -102,6 +103,7 @@ private: std::string m_deviceCacheDir; std::shared_ptr m_messageQueueThread; std::unique_ptr m_unbundle; + JSCNativeModules m_nativeModules; folly::dynamic m_jscConfig; folly::Optional m_invokeCallbackAndReturnFlushedQueueJS; @@ -142,10 +144,8 @@ private: template< JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])> void installNativeHook(const char* name); + JSValueRef getNativeModule(JSObjectRef object, JSStringRef propertyName); - JSValueRef nativeRequireModuleConfig( - size_t argumentCount, - const JSValueRef arguments[]); JSValueRef nativeStartWorker( size_t argumentCount, const JSValueRef arguments[]); diff --git a/ReactCommon/cxxreact/JSCHelpers.cpp b/ReactCommon/cxxreact/JSCHelpers.cpp index 366feb39e..896262981 100644 --- a/ReactCommon/cxxreact/JSCHelpers.cpp +++ b/ReactCommon/cxxreact/JSCHelpers.cpp @@ -24,6 +24,25 @@ void installGlobalFunction( JSStringRelease(jsName); } +void installGlobalProxy( + JSGlobalContextRef ctx, + const char* name, + JSObjectGetPropertyCallback callback) { + JSClassDefinition proxyClassDefintion = kJSClassDefinitionEmpty; + proxyClassDefintion.className = "_FBProxyClass"; + proxyClassDefintion.getProperty = callback; + JSClassRef proxyClass = JSClassCreate(&proxyClassDefintion); + + JSObjectRef proxyObj = JSObjectMake(ctx, proxyClass, nullptr); + + JSObjectRef globalObject = JSContextGetGlobalObject(ctx); + JSStringRef jsName = JSStringCreateWithUTF8CString(name); + JSObjectSetProperty(ctx, globalObject, jsName, proxyObj, 0, NULL); + + JSStringRelease(jsName); + JSClassRelease(proxyClass); +} + JSValueRef makeJSCException( JSContextRef ctx, const char* exception_text) { diff --git a/ReactCommon/cxxreact/JSCHelpers.h b/ReactCommon/cxxreact/JSCHelpers.h index a55906d56..d9814c46c 100644 --- a/ReactCommon/cxxreact/JSCHelpers.h +++ b/ReactCommon/cxxreact/JSCHelpers.h @@ -38,6 +38,11 @@ void installGlobalFunction( const char* name, JSObjectCallAsFunctionCallback callback); +void installGlobalProxy( + JSGlobalContextRef ctx, + const char* name, + JSObjectGetPropertyCallback callback); + JSValueRef makeJSCException( JSContextRef ctx, const char* exception_text); diff --git a/ReactCommon/cxxreact/JSCNativeModules.cpp b/ReactCommon/cxxreact/JSCNativeModules.cpp new file mode 100644 index 000000000..dc400deec --- /dev/null +++ b/ReactCommon/cxxreact/JSCNativeModules.cpp @@ -0,0 +1,63 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "JSCNativeModules.h" + +#include + +namespace facebook { +namespace react { + +JSCNativeModules::JSCNativeModules(std::shared_ptr moduleRegistry) : + m_moduleRegistry(std::move(moduleRegistry)) {} + +JSValueRef JSCNativeModules::getModule(JSContextRef context, JSStringRef jsName) { + std::string moduleName = String::ref(jsName).str(); + + const auto it = m_objects.find(moduleName); + if (it != m_objects.end()) { + return static_cast(it->second); + } + + auto module = createModule(moduleName, context); + if (!module.hasValue()) { + return JSValueMakeUndefined(context); + } + + // Protect since we'll be holding on to this value, even though JS may not + module->makeProtected(); + + auto result = m_objects.emplace(std::move(moduleName), std::move(*module)).first; + return static_cast(result->second); +} + +void JSCNativeModules::reset() { + m_genNativeModuleJS = nullptr; + m_objects.clear(); +} + +folly::Optional JSCNativeModules::createModule(const std::string& name, JSContextRef context) { + if (!m_genNativeModuleJS) { + auto global = Object::getGlobalObject(context); + m_genNativeModuleJS = global.getProperty("__fbGenNativeModule").asObject(); + m_genNativeModuleJS->makeProtected(); + + // Initialize the module name list, otherwise getModuleConfig won't work + // TODO (pieterdb): fix this in ModuleRegistry + m_moduleRegistry->moduleNames(); + } + + auto result = m_moduleRegistry->getConfig(name); + if (!result.hasValue()) { + return nullptr; + } + + Value moduleInfo = m_genNativeModuleJS->callAsFunction({ + Value::fromDynamic(context, result->config), + JSValueMakeNumber(context, result->index) + }); + CHECK(!moduleInfo.isNull()) << "Module returned from genNativeModule is null"; + + return moduleInfo.asObject().getProperty("module").asObject(); +} + +} } diff --git a/ReactCommon/cxxreact/JSCNativeModules.h b/ReactCommon/cxxreact/JSCNativeModules.h new file mode 100644 index 000000000..249f25a1c --- /dev/null +++ b/ReactCommon/cxxreact/JSCNativeModules.h @@ -0,0 +1,35 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include + +#include +#include + +#include "Value.h" +#include "ModuleRegistry.h" + +namespace facebook { +namespace react { + +/** + * Holds and creates JS representations of the modules in ModuleRegistry + */ +class JSCNativeModules { + +public: + explicit JSCNativeModules(std::shared_ptr moduleRegistry); + JSValueRef getModule(JSContextRef context, JSStringRef name); + void reset(); + +private: + folly::Optional m_genNativeModuleJS; + std::shared_ptr m_moduleRegistry; + std::unordered_map m_objects; + + folly::Optional createModule(const std::string& name, JSContextRef context); +}; + +} +} diff --git a/ReactCommon/cxxreact/ModuleRegistry.cpp b/ReactCommon/cxxreact/ModuleRegistry.cpp index 0b13e4109..39ef2e0d2 100644 --- a/ReactCommon/cxxreact/ModuleRegistry.cpp +++ b/ReactCommon/cxxreact/ModuleRegistry.cpp @@ -38,17 +38,17 @@ std::vector ModuleRegistry::moduleNames() { return names; } -folly::dynamic ModuleRegistry::getConfig(const std::string& name) { +folly::Optional ModuleRegistry::getConfig(const std::string& name) { SystraceSection s("getConfig", "module", name); auto it = modulesByName_.find(name); if (it == modulesByName_.end()) { return nullptr; } - CHECK(it->second < modules_.size()); + CHECK(it->second < modules_.size()); NativeModule* module = modules_[it->second].get(); - // string name, [object constants,] array methodNames (methodId is index), [array promiseMethodIds], [array syncMethodIds] + // string name, object constants, array methodNames (methodId is index), [array promiseMethodIds], [array syncMethodIds] folly::dynamic config = folly::dynamic::array(name); { @@ -89,7 +89,7 @@ folly::dynamic ModuleRegistry::getConfig(const std::string& name) { // no constants or methods return nullptr; } else { - return config; + return ModuleConfig({it->second, config}); } } diff --git a/ReactCommon/cxxreact/ModuleRegistry.h b/ReactCommon/cxxreact/ModuleRegistry.h index a96a20bcf..bac11edf4 100644 --- a/ReactCommon/cxxreact/ModuleRegistry.h +++ b/ReactCommon/cxxreact/ModuleRegistry.h @@ -6,6 +6,7 @@ #include #include +#include #include "ExecutorToken.h" #include "NativeModule.h" @@ -15,6 +16,11 @@ namespace react { class NativeModule; +struct ModuleConfig { + size_t index; + folly::dynamic config; +}; + class ModuleRegistry { public: // not implemented: @@ -26,7 +32,9 @@ class ModuleRegistry { ModuleRegistry(std::vector> modules); std::vector moduleNames(); - folly::dynamic getConfig(const std::string& name); + + folly::Optional getConfig(const std::string& name); + void callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId); MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args);