Permalink
Browse files

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
  • Loading branch information...
javache authored and Facebook Github Bot committed Oct 11, 2016
1 parent 606fc11 commit 9ed9bca0bf2cc4131221bc9be05ad5d458a5750d
@@ -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');
-
-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;
- }
-
- 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)
- });
- }
-});
+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');
+
+ (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)
+ });
+ }
+ });
+}
module.exports = NativeModules;
@@ -42,12 +42,13 @@ ProxyExecutor::ProxyExecutor(jni::global_ref<jobject>&& 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);
}
}
@@ -13,6 +13,7 @@ LOCAL_SRC_FILES := \
JSCLegacyProfiler.cpp \
JSCLegacyTracing.cpp \
JSCMemory.cpp \
+ JSCNativeModules.cpp \
JSCPerfStats.cpp \
JSCTracing.cpp \
JSCWebWorker.cpp \
@@ -116,6 +116,7 @@ CXXREACT_PUBLIC_HEADERS = [
'Instance.h',
'JSCExecutor.h',
'JSCHelpers.h',
+ 'JSCNativeModules.h',
'JSCWebWorker.h',
'JSModulesUnbundle.h',
'MessageQueueThread.h',
@@ -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<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
@@ -109,28 +132,15 @@ JSCExecutor::JSCExecutor(std::shared_ptr<ExecutorDelegate> 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<JSBigStdString>(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<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(
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[]) {
@@ -15,6 +15,7 @@
#include "ExecutorToken.h"
#include "JSCHelpers.h"
#include "Value.h"
+#include "JSCNativeModules.h"
namespace facebook {
namespace react {
@@ -102,6 +103,7 @@ class JSCExecutor : public JSExecutor {
std::string m_deviceCacheDir;
std::shared_ptr<MessageQueueThread> m_messageQueueThread;
std::unique_ptr<JSModulesUnbundle> m_unbundle;
+ JSCNativeModules m_nativeModules;
folly::dynamic m_jscConfig;
folly::Optional<Object> m_invokeCallbackAndReturnFlushedQueueJS;
@@ -142,10 +144,8 @@ class JSCExecutor : public JSExecutor {
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[]);
@@ -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) {
@@ -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);
@@ -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();
+}
+
+} }
@@ -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);
+};
+
+}
+}
Oops, something went wrong.

0 comments on commit 9ed9bca

Please sign in to comment.