Lazily instantiate native modules

Summary: Instead of sending a list of modules over to JS on startup (and actually blocking script execution) instead provide a proxy object that constructs each of these lazily.

Reviewed By: lexs

Differential Revision: D3936979

fbshipit-source-id: 71bde822f01eb17a29f56c5e60e95e98e207d74d
This commit is contained in:
Pieter De Baets 2016-10-11 07:19:31 -07:00 committed by Facebook Github Bot
parent 606fc11487
commit 9ed9bca0bf
12 changed files with 208 additions and 60 deletions

View File

@ -58,6 +58,9 @@ function genModule(config: ?ModuleConfig, moduleID: number): ?{name: string, mod
return { name: moduleName, module }; 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 { function loadModule(name: string, moduleID: number): ?Object {
invariant(global.nativeRequireModuleConfig, invariant(global.nativeRequireModuleConfig,
'Can\'t lazily create module without nativeRequireModuleConfig'); 'Can\'t lazily create module without nativeRequireModuleConfig');
@ -115,27 +118,31 @@ function createErrorFromErrorData(errorData: {message: string}): Error {
return Object.assign(error, extraErrorInfo); return Object.assign(error, extraErrorInfo);
} }
const bridgeConfig = global.__fbBatchedBridgeConfig; let NativeModules : {[moduleName: string]: Object} = {};
invariant(bridgeConfig, '__fbBatchedBridgeConfig is not set, cannot invoke native modules'); 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) => {
(bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => { // Initially this config will only contain the module name when running in JSC. The actual
// Initially this config will only contain the module name when running in JSC. The actual // configuration of the module will be lazily loaded.
// configuration of the module will be lazily loaded. const info = genModule(config, moduleID);
const info = genModule(config, moduleID); if (!info) {
if (!info) { return;
return; }
}
if (info.module) { if (info.module) {
NativeModules[info.name] = info.module; NativeModules[info.name] = info.module;
} }
// If there's no module config, define a lazy getter // If there's no module config, define a lazy getter
else { else {
defineLazyObjectProperty(NativeModules, info.name, { defineLazyObjectProperty(NativeModules, info.name, {
get: () => loadModule(info.name, moduleID) get: () => loadModule(info.name, moduleID)
}); });
} }
}); });
}
module.exports = NativeModules; module.exports = NativeModules;

View File

@ -42,12 +42,13 @@ ProxyExecutor::ProxyExecutor(jni::global_ref<jobject>&& executorInstance,
, m_delegate(delegate) { , m_delegate(delegate) {
folly::dynamic nativeModuleConfig = folly::dynamic::array; folly::dynamic nativeModuleConfig = folly::dynamic::array;
auto moduleRegistry = delegate->getModuleRegistry();
{ {
SystraceSection s("collectNativeModuleDescriptions"); SystraceSection s("collectNativeModuleDescriptions");
auto moduleRegistry = delegate->getModuleRegistry();
for (const auto& name : moduleRegistry->moduleNames()) { for (const auto& name : moduleRegistry->moduleNames()) {
nativeModuleConfig.push_back(moduleRegistry->getConfig(name)); auto config = moduleRegistry->getConfig(name);
nativeModuleConfig.push_back(config ? config->config : nullptr);
} }
} }

View File

@ -13,6 +13,7 @@ LOCAL_SRC_FILES := \
JSCLegacyProfiler.cpp \ JSCLegacyProfiler.cpp \
JSCLegacyTracing.cpp \ JSCLegacyTracing.cpp \
JSCMemory.cpp \ JSCMemory.cpp \
JSCNativeModules.cpp \
JSCPerfStats.cpp \ JSCPerfStats.cpp \
JSCTracing.cpp \ JSCTracing.cpp \
JSCWebWorker.cpp \ JSCWebWorker.cpp \

View File

@ -116,6 +116,7 @@ CXXREACT_PUBLIC_HEADERS = [
'Instance.h', 'Instance.h',
'JSCExecutor.h', 'JSCExecutor.h',
'JSCHelpers.h', 'JSCHelpers.h',
'JSCNativeModules.h',
'JSCWebWorker.h', 'JSCWebWorker.h',
'JSModulesUnbundle.h', 'JSModulesUnbundle.h',
'MessageQueueThread.h', 'MessageQueueThread.h',

View File

@ -20,6 +20,7 @@
#include "Platform.h" #include "Platform.h"
#include "SystraceSection.h" #include "SystraceSection.h"
#include "Value.h" #include "Value.h"
#include "JSCNativeModules.h"
#include "JSCSamplingProfiler.h" #include "JSCSamplingProfiler.h"
#include "JSModulesUnbundle.h" #include "JSModulesUnbundle.h"
#include "ModuleRegistry.h" #include "ModuleRegistry.h"
@ -79,6 +80,28 @@ inline JSObjectCallAsFunctionCallback exceptionWrapMethod() {
return &funcWrapper::call; return &funcWrapper::call;
} }
template<JSValueRef (JSCExecutor::*method)(JSObjectRef object, JSStringRef propertyName)>
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<JSCExecutor*>(JSObjectGetPrivate(globalObj));
return (executor->*method)(object, propertyName);
} catch (...) {
*exception = translatePendingCppExceptionToJSError(ctx, object);
return JSValueMakeUndefined(ctx);
}
}
};
return &funcWrapper::call;
}
} }
#if DEBUG #if DEBUG
@ -109,28 +132,15 @@ JSCExecutor::JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate,
m_delegate(delegate), m_delegate(delegate),
m_deviceCacheDir(cacheDir), m_deviceCacheDir(cacheDir),
m_messageQueueThread(messageQueueThread), m_messageQueueThread(messageQueueThread),
m_nativeModules(delegate->getModuleRegistry()),
m_jscConfig(jscConfig) { m_jscConfig(jscConfig) {
initOnJSVMThread(); initOnJSVMThread();
SystraceSection s("setBatchedBridgeConfig");
folly::dynamic nativeModuleConfig = folly::dynamic::array();
{ {
SystraceSection s("collectNativeModuleNames"); SystraceSection s("nativeModuleProxy object");
for (auto& name : delegate->getModuleRegistry()->moduleNames()) { installGlobalProxy(m_context, "nativeModuleProxy",
nativeModuleConfig.push_back(folly::dynamic::array(std::move(name))); exceptionWrapMethod<&JSCExecutor::getNativeModule>());
}
} }
folly::dynamic config =
folly::dynamic::object
("remoteModuleConfig", std::move(nativeModuleConfig));
SystraceSection t("setGlobalVariable");
setGlobalVariable(
"__fbBatchedBridgeConfig",
folly::make_unique<JSBigStdString>(folly::toJson(config)));
} }
JSCExecutor::JSCExecutor( JSCExecutor::JSCExecutor(
@ -146,6 +156,7 @@ JSCExecutor::JSCExecutor(
m_owner(owner), m_owner(owner),
m_deviceCacheDir(owner->m_deviceCacheDir), m_deviceCacheDir(owner->m_deviceCacheDir),
m_messageQueueThread(messageQueueThread), m_messageQueueThread(messageQueueThread),
m_nativeModules(delegate->getModuleRegistry()),
m_jscConfig(jscConfig) { m_jscConfig(jscConfig) {
// We post initOnJSVMThread here so that the owner doesn't have to wait for // We post initOnJSVMThread here so that the owner doesn't have to wait for
// initialization on its own thread // 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 // Add a pointer to ourselves so we can retrieve it later in our hooks
JSObjectSetPrivate(JSContextGetGlobalObject(m_context), this); JSObjectSetPrivate(JSContextGetGlobalObject(m_context), this);
installNativeHook<&JSCExecutor::nativeRequireModuleConfig>("nativeRequireModuleConfig");
installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate"); installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook"); installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook");
@ -264,6 +274,8 @@ void JSCExecutor::terminateOnJSVMThread() {
terminateOwnedWebWorker(workerId); terminateOwnedWebWorker(workerId);
} }
m_nativeModules.reset();
JSGlobalContextRelease(m_context); JSGlobalContextRelease(m_context);
m_context = nullptr; m_context = nullptr;
} }
@ -647,6 +659,14 @@ void JSCExecutor::installNativeHook(const char* name) {
installGlobalFunction(m_context, name, exceptionWrapMethod<method>()); installGlobalFunction(m_context, name, exceptionWrapMethod<method>());
} }
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( JSValueRef JSCExecutor::nativePostMessage(
size_t argumentCount, size_t argumentCount,
const JSValueRef arguments[]) { const JSValueRef arguments[]) {
@ -680,18 +700,6 @@ JSValueRef JSCExecutor::nativeRequire(
return JSValueMakeUndefined(m_context); 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( JSValueRef JSCExecutor::nativeFlushQueueImmediate(
size_t argumentCount, size_t argumentCount,
const JSValueRef arguments[]) { const JSValueRef arguments[]) {

View File

@ -15,6 +15,7 @@
#include "ExecutorToken.h" #include "ExecutorToken.h"
#include "JSCHelpers.h" #include "JSCHelpers.h"
#include "Value.h" #include "Value.h"
#include "JSCNativeModules.h"
namespace facebook { namespace facebook {
namespace react { namespace react {
@ -102,6 +103,7 @@ private:
std::string m_deviceCacheDir; std::string m_deviceCacheDir;
std::shared_ptr<MessageQueueThread> m_messageQueueThread; std::shared_ptr<MessageQueueThread> m_messageQueueThread;
std::unique_ptr<JSModulesUnbundle> m_unbundle; std::unique_ptr<JSModulesUnbundle> m_unbundle;
JSCNativeModules m_nativeModules;
folly::dynamic m_jscConfig; folly::dynamic m_jscConfig;
folly::Optional<Object> m_invokeCallbackAndReturnFlushedQueueJS; folly::Optional<Object> m_invokeCallbackAndReturnFlushedQueueJS;
@ -142,10 +144,8 @@ private:
template< JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])> template< JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])>
void installNativeHook(const char* name); void installNativeHook(const char* name);
JSValueRef getNativeModule(JSObjectRef object, JSStringRef propertyName);
JSValueRef nativeRequireModuleConfig(
size_t argumentCount,
const JSValueRef arguments[]);
JSValueRef nativeStartWorker( JSValueRef nativeStartWorker(
size_t argumentCount, size_t argumentCount,
const JSValueRef arguments[]); const JSValueRef arguments[]);

View File

@ -24,6 +24,25 @@ void installGlobalFunction(
JSStringRelease(jsName); 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( JSValueRef makeJSCException(
JSContextRef ctx, JSContextRef ctx,
const char* exception_text) { const char* exception_text) {

View File

@ -38,6 +38,11 @@ void installGlobalFunction(
const char* name, const char* name,
JSObjectCallAsFunctionCallback callback); JSObjectCallAsFunctionCallback callback);
void installGlobalProxy(
JSGlobalContextRef ctx,
const char* name,
JSObjectGetPropertyCallback callback);
JSValueRef makeJSCException( JSValueRef makeJSCException(
JSContextRef ctx, JSContextRef ctx,
const char* exception_text); const char* exception_text);

View File

@ -0,0 +1,63 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "JSCNativeModules.h"
#include <string>
namespace facebook {
namespace react {
JSCNativeModules::JSCNativeModules(std::shared_ptr<ModuleRegistry> 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<JSObjectRef>(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<JSObjectRef>(result->second);
}
void JSCNativeModules::reset() {
m_genNativeModuleJS = nullptr;
m_objects.clear();
}
folly::Optional<Object> 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();
}
} }

View File

@ -0,0 +1,35 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <folly/Optional.h>
#include <memory>
#include <string>
#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> moduleRegistry);
JSValueRef getModule(JSContextRef context, JSStringRef name);
void reset();
private:
folly::Optional<Object> m_genNativeModuleJS;
std::shared_ptr<ModuleRegistry> m_moduleRegistry;
std::unordered_map<std::string, Object> m_objects;
folly::Optional<Object> createModule(const std::string& name, JSContextRef context);
};
}
}

View File

@ -38,17 +38,17 @@ std::vector<std::string> ModuleRegistry::moduleNames() {
return names; return names;
} }
folly::dynamic ModuleRegistry::getConfig(const std::string& name) { folly::Optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name) {
SystraceSection s("getConfig", "module", name); SystraceSection s("getConfig", "module", name);
auto it = modulesByName_.find(name); auto it = modulesByName_.find(name);
if (it == modulesByName_.end()) { if (it == modulesByName_.end()) {
return nullptr; return nullptr;
} }
CHECK(it->second < modules_.size());
CHECK(it->second < modules_.size());
NativeModule* module = modules_[it->second].get(); 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); folly::dynamic config = folly::dynamic::array(name);
{ {
@ -89,7 +89,7 @@ folly::dynamic ModuleRegistry::getConfig(const std::string& name) {
// no constants or methods // no constants or methods
return nullptr; return nullptr;
} else { } else {
return config; return ModuleConfig({it->second, config});
} }
} }

View File

@ -6,6 +6,7 @@
#include <vector> #include <vector>
#include <folly/dynamic.h> #include <folly/dynamic.h>
#include <folly/Optional.h>
#include "ExecutorToken.h" #include "ExecutorToken.h"
#include "NativeModule.h" #include "NativeModule.h"
@ -15,6 +16,11 @@ namespace react {
class NativeModule; class NativeModule;
struct ModuleConfig {
size_t index;
folly::dynamic config;
};
class ModuleRegistry { class ModuleRegistry {
public: public:
// not implemented: // not implemented:
@ -26,7 +32,9 @@ class ModuleRegistry {
ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules); ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules);
std::vector<std::string> moduleNames(); std::vector<std::string> moduleNames();
folly::dynamic getConfig(const std::string& name);
folly::Optional<ModuleConfig> getConfig(const std::string& name);
void callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId, void callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId,
folly::dynamic&& params, int callId); folly::dynamic&& params, int callId);
MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args); MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args);