// Copyright 2004-present Facebook. All Rights Reserved. #include "JSCExecutor.h" #include #include #include #include #include #include #include #include #include "Value.h" #include "jni/OnLoad.h" #ifdef WITH_JSC_EXTRA_TRACING #include #include #include #endif #ifdef WITH_JSC_MEMORY_PRESSURE #include #endif #ifdef WITH_FBSYSTRACE #include using fbsystrace::FbSystraceSection; #endif // Add native performance markers support #include #include #ifdef WITH_FB_JSC_TUNING #include #endif static const int64_t NANOSECONDS_IN_SECOND = 1000000000LL; static const int64_t NANOSECONDS_IN_MILLISECOND = 1000000LL; using namespace facebook::jni; namespace facebook { namespace react { static std::unordered_map s_globalContextRefToJSCExecutor; static JSValueRef nativeFlushQueueImmediate( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception); static JSValueRef nativeLoggingHook( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception); static JSValueRef nativePerformanceNow( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception); static JSValueRef evaluateScriptWithJSC( JSGlobalContextRef ctx, JSStringRef script, JSStringRef sourceURL) { JSValueRef exn; auto result = JSEvaluateScript(ctx, script, nullptr, sourceURL, 0, &exn); if (result == nullptr) { JSValueProtect(ctx, exn); std::string exceptionText = Value(ctx, exn).toString().str(); FBLOGE("Got JS Exception: %s", exceptionText.c_str()); auto line = Value(ctx, JSObjectGetProperty(ctx, JSValueToObject(ctx, exn, nullptr), JSStringCreateWithUTF8CString("line"), nullptr )); std::ostringstream lineInfo; if (line != nullptr && line.isNumber()) { lineInfo << " (line " << line.asInteger() << " in the generated bundle)"; } else { lineInfo << " (no line info)"; } throwNewJavaException("com/facebook/react/bridge/JSExecutionException", (exceptionText + lineInfo.str()).c_str()); } return result; } static std::string executeJSCallWithJSC( JSGlobalContextRef ctx, const std::string& methodName, const std::vector& arguments) { #ifdef WITH_FBSYSTRACE FbSystraceSection s( TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.executeJSCall", "method", methodName); #endif // Evaluate script with JSC folly::dynamic jsonArgs(arguments.begin(), arguments.end()); auto js = folly::to( "__fbBatchedBridge.", methodName, ".apply(null, ", folly::toJson(jsonArgs), ")"); auto result = evaluateScriptWithJSC(ctx, String(js.c_str()), nullptr); JSValueProtect(ctx, result); return Value(ctx, result).toJSONString(); } std::unique_ptr JSCExecutorFactory::createJSExecutor(FlushImmediateCallback cb) { return std::unique_ptr(new JSCExecutor(cb)); } JSCExecutor::JSCExecutor(FlushImmediateCallback cb) : m_flushImmediateCallback(cb) { m_context = JSGlobalContextCreateInGroup(nullptr, nullptr); s_globalContextRefToJSCExecutor[m_context] = this; installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate); installGlobalFunction(m_context, "nativeLoggingHook", nativeLoggingHook); installGlobalFunction(m_context, "nativePerformanceNow", nativePerformanceNow); #ifdef WITH_FB_JSC_TUNING configureJSCForAndroid(); #endif #ifdef WITH_JSC_EXTRA_TRACING addNativeTracingHooks(m_context); addNativeProfilingHooks(m_context); addNativePerfLoggingHooks(m_context); #endif addNativeMemoryHooks(m_context); } JSCExecutor::~JSCExecutor() { s_globalContextRefToJSCExecutor.erase(m_context); JSGlobalContextRelease(m_context); } void JSCExecutor::executeApplicationScript( const std::string& script, const std::string& sourceURL) { JNIEnv* env = Environment::current(); jclass markerClass = env->FindClass("com/facebook/react/bridge/ReactMarker"); jmethodID logMarkerMethod = facebook::react::getLogMarkerMethod(); jstring startStringMarker = env->NewStringUTF("executeApplicationScript_startStringConvert"); jstring endStringMarker = env->NewStringUTF("executeApplicationScript_endStringConvert"); env->CallStaticVoidMethod(markerClass, logMarkerMethod, startStringMarker); String jsScript = String::createExpectingAscii(script); env->CallStaticVoidMethod(markerClass, logMarkerMethod, endStringMarker); env->DeleteLocalRef(startStringMarker); env->DeleteLocalRef(endStringMarker); String jsSourceURL(sourceURL.c_str()); #ifdef WITH_FBSYSTRACE FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor::executeApplicationScript", "sourceURL", sourceURL); #endif evaluateScriptWithJSC(m_context, jsScript, jsSourceURL); } std::string JSCExecutor::flush() { // TODO: Make this a first class function instead of evaling. #9317773 return executeJSCallWithJSC(m_context, "flushedQueue", std::vector()); } std::string JSCExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) { // TODO: Make this a first class function instead of evaling. #9317773 std::vector call{ (double) moduleId, (double) methodId, std::move(arguments), }; return executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call)); } std::string JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) { // TODO: Make this a first class function instead of evaling. #9317773 std::vector call{ (double) callbackId, std::move(arguments) }; return executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call)); } void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) { auto globalObject = JSContextGetGlobalObject(m_context); String jsPropertyName(propName.c_str()); String jsValueJSON(jsonValue.c_str()); auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON); JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL); } bool JSCExecutor::supportsProfiling() { #ifdef WITH_FBSYSTRACE return true; #else return false; #endif } void JSCExecutor::startProfiler(const std::string &titleString) { #ifdef WITH_JSC_EXTRA_TRACING JSStringRef title = JSStringCreateWithUTF8CString(titleString.c_str()); #if WITH_JSC_INTERNAL JSStartProfiling(m_context, title, false); #else JSStartProfiling(m_context, title); #endif JSStringRelease(title); #endif } void JSCExecutor::stopProfiler(const std::string &titleString, const std::string& filename) { #ifdef WITH_JSC_EXTRA_TRACING JSStringRef title = JSStringCreateWithUTF8CString(titleString.c_str()); facebook::react::stopAndOutputProfilingFile(m_context, title, filename.c_str()); JSStringRelease(title); #endif } void JSCExecutor::handleMemoryPressureModerate() { #ifdef WITH_JSC_MEMORY_PRESSURE JSHandleMemoryPressure(this, m_context, JSMemoryPressure::MODERATE); #endif } void JSCExecutor::handleMemoryPressureCritical() { #ifdef WITH_JSC_MEMORY_PRESSURE JSHandleMemoryPressure(this, m_context, JSMemoryPressure::CRITICAL); #endif } void JSCExecutor::flushQueueImmediate(std::string queueJSON) { m_flushImmediateCallback(queueJSON); } static JSValueRef createErrorString(JSContextRef ctx, const char *msg) { return JSValueMakeString(ctx, String(msg)); } static JSValueRef nativeFlushQueueImmediate( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { if (argumentCount != 1) { *exception = createErrorString(ctx, "Got wrong number of args"); return JSValueMakeUndefined(ctx); } JSCExecutor *executor; try { executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx)); } catch (std::out_of_range& e) { *exception = createErrorString(ctx, "Global JS context didn't map to a valid executor"); return JSValueMakeUndefined(ctx); } JSValueProtect(ctx, arguments[0]); std::string resStr = Value(ctx, arguments[0]).toJSONString(); executor->flushQueueImmediate(resStr); return JSValueMakeUndefined(ctx); } static JSValueRef nativeLoggingHook( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { android_LogPriority logLevel = ANDROID_LOG_DEBUG; if (argumentCount > 1) { int level = (int) JSValueToNumber(ctx, arguments[1], NULL); // The lowest log level we get from JS is 0. We shift and cap it to be // in the range the Android logging method expects. logLevel = std::min( static_cast(level + ANDROID_LOG_DEBUG), ANDROID_LOG_FATAL); } if (argumentCount > 0) { JSStringRef jsString = JSValueToStringCopy(ctx, arguments[0], NULL); String message = String::adopt(jsString); FBLOG_PRI(logLevel, "ReactNativeJS", "%s", message.str().c_str()); } return JSValueMakeUndefined(ctx); } static JSValueRef nativePerformanceNow( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { // This is equivalent to android.os.SystemClock.elapsedRealtime() in native struct timespec now; clock_gettime(CLOCK_MONOTONIC_RAW, &now); int64_t nano = now.tv_sec * NANOSECONDS_IN_SECOND + now.tv_nsec; return JSValueMakeNumber(ctx, (nano / (double)NANOSECONDS_IN_MILLISECOND)); } } }