diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js index dfbd9edd1a3554..f1a4da1d4c2412 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js @@ -46,8 +46,11 @@ Object.keys(RemoteModules).forEach((moduleName) => { get: () => { let module = RemoteModules[moduleName]; if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) { - const json = global.nativeRequireModuleConfig(moduleName); - const config = json && JSON.parse(json); + // The old bridge (still used by iOS) will send the config as + // a JSON string that needs parsing, so we set config according + // to the type of response we got. + const rawConfig = global.nativeRequireModuleConfig(moduleName); + const config = typeof rawConfig === 'string' ? JSON.parse(rawConfig) : rawConfig; module = config && BatchedBridge.processModuleConfig(config, module.moduleID); RemoteModules[moduleName] = module; } diff --git a/ReactCommon/cxxreact/JSCExecutor.cpp b/ReactCommon/cxxreact/JSCExecutor.cpp index 25efa9f3ecb120..42967be0c879d2 100644 --- a/ReactCommon/cxxreact/JSCExecutor.cpp +++ b/ReactCommon/cxxreact/JSCExecutor.cpp @@ -596,7 +596,7 @@ JSValueRef JSCExecutor::nativeRequireModuleConfig( std::string moduleName = Value(m_context, arguments[0]).toString().str(); folly::dynamic config = m_delegate->getModuleConfig(moduleName); - return JSValueMakeString(m_context, String(folly::toJson(config).c_str())); + return Value::fromDynamic(m_context, config); } JSValueRef JSCExecutor::nativeFlushQueueImmediate( @@ -681,7 +681,7 @@ JSValueRef JSCExecutor::nativeCallSyncHook( if (result.isUndefined) { return JSValueMakeUndefined(m_context); } - return Value::fromJSON(m_context, String(folly::toJson(result.result).c_str())); + return Value::fromDynamic(m_context, result.result); } static JSValueRef nativeInjectHMRUpdate( diff --git a/ReactCommon/cxxreact/Value.cpp b/ReactCommon/cxxreact/Value.cpp index c328701ec362d3..7bc40b20fd6e45 100644 --- a/ReactCommon/cxxreact/Value.cpp +++ b/ReactCommon/cxxreact/Value.cpp @@ -6,6 +6,9 @@ #include "JSCHelpers.h" +// See the comment under Value::fromDynamic() +#define USE_FAST_FOLLY_DYNAMIC_CONVERSION !defined(__APPLE__) && defined(WITH_FB_JSC_TUNING) + namespace facebook { namespace react { @@ -51,9 +54,78 @@ Value Value::fromJSON(JSContextRef ctx, const String& json) throw(JSException) { return Value(ctx, result); } -Value Value::fromDynamic(JSContextRef ctx, folly::dynamic value) throw(JSException) { +JSValueRef Value::fromDynamic(JSContextRef ctx, const folly::dynamic& value) { +// JavaScriptCore's iOS APIs have their own version of this direct conversion. +// In addition, using this requires exposing some of JSC's private APIs, +// so it's limited to non-apple platforms and to builds that use the custom JSC. +// Otherwise, we use the old way of converting through JSON. +#if USE_FAST_FOLLY_DYNAMIC_CONVERSION + // Defer GC during the creation of the JSValue, as we don't want + // intermediate objects to be collected. + // We could use JSValueProtect(), but it will make the process much slower. + JSDeferredGCRef deferGC = JSDeferGarbageCollection(ctx); + // Set a global lock for the whole process, + // instead of re-acquiring the lock for each operation. + JSLock(ctx); + JSValueRef jsVal = Value::fromDynamicInner(ctx, value); + JSUnlock(ctx); + JSResumeGarbageCollection(ctx, deferGC); + return jsVal; +#else auto json = folly::toJson(value); return fromJSON(ctx, String(json.c_str())); +#endif +} + +JSValueRef Value::fromDynamicInner(JSContextRef ctx, const folly::dynamic& obj) { + switch (obj.type()) { + // For premitive types (and strings), just create and return an equivalent JSValue + case folly::dynamic::Type::NULLT: + return JSValueMakeNull(ctx); + + case folly::dynamic::Type::BOOL: + return JSValueMakeBoolean(ctx, obj.getBool()); + + case folly::dynamic::Type::DOUBLE: + return JSValueMakeNumber(ctx, obj.getDouble()); + + case folly::dynamic::Type::INT64: + return JSValueMakeNumber(ctx, obj.asDouble()); + + case folly::dynamic::Type::STRING: + return JSValueMakeString(ctx, String(obj.getString().c_str())); + + case folly::dynamic::Type::ARRAY: { + // Collect JSValue for every element in the array + JSValueRef vals[obj.size()]; + for (size_t i = 0; i < obj.size(); ++i) { + vals[i] = fromDynamicInner(ctx, obj[i]); + } + // Create a JSArray with the values + JSValueRef arr = JSObjectMakeArray(ctx, obj.size(), vals, nullptr); + return arr; + } + + case folly::dynamic::Type::OBJECT: { + // Create an empty object + JSObjectRef jsObj = JSObjectMake(ctx, nullptr, nullptr); + // Create a JSValue for each of the object's children and set them in the object + for (auto it = obj.items().begin(); it != obj.items().end(); ++it) { + JSObjectSetProperty( + ctx, + jsObj, + String(it->first.asString().c_str()), + fromDynamicInner(ctx, it->second), + kJSPropertyAttributeNone, + nullptr); + } + return jsObj; + } + default: + // Assert not reached + LOG(FATAL) << "Trying to convert a folly object of unsupported type."; + return JSValueMakeNull(ctx); + } } Object Value::asObject() { diff --git a/ReactCommon/cxxreact/Value.h b/ReactCommon/cxxreact/Value.h index 1aa965e9c31415..044f3a52256f8a 100644 --- a/ReactCommon/cxxreact/Value.h +++ b/ReactCommon/cxxreact/Value.h @@ -284,11 +284,12 @@ class Value : public noncopyable { std::string toJSONString(unsigned indent = 0) const throw(JSException); static Value fromJSON(JSContextRef ctx, const String& json) throw(JSException); - static Value fromDynamic(JSContextRef ctx, folly::dynamic value) throw(JSException); + static JSValueRef fromDynamic(JSContextRef ctx, const folly::dynamic& value); JSContextRef context() const; protected: JSContextRef m_context; JSValueRef m_value; + static JSValueRef fromDynamicInner(JSContextRef ctx, const folly::dynamic& obj); }; } }