// Copyright 2004-present Facebook. All Rights Reserved. #include "JSCExecutor.h" #include #include #include #include #include #include #include #include "JSCHelpers.h" #include "Value.h" #include "Platform.h" #ifdef WITH_JSC_EXTRA_TRACING #include "JSCTracing.h" #include "JSCLegacyProfiler.h" #include #endif #ifdef WITH_JSC_MEMORY_PRESSURE #include #endif #ifdef WITH_FBSYSTRACE #include using fbsystrace::FbSystraceSection; #endif #ifdef WITH_FB_MEMORY_PROFILING #include "JSCMemory.h" #endif #ifdef WITH_FB_JSC_TUNING #include #endif static const int64_t NANOSECONDS_IN_SECOND = 1000000000LL; static const int64_t NANOSECONDS_IN_MILLISECOND = 1000000LL; 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 nativePerformanceNow( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception); static JSValueRef nativeInjectHMRUpdate( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception); 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 = evaluateScript(ctx, String(js.c_str()), nullptr); return Value(ctx, result).toJSONString(); } std::unique_ptr JSCExecutorFactory::createJSExecutor(FlushImmediateCallback cb) { return std::unique_ptr(new JSCExecutor(cb, cacheDir_)); } JSCExecutor::JSCExecutor(FlushImmediateCallback cb, const std::string& cacheDir) : m_flushImmediateCallback(cb), m_deviceCacheDir(cacheDir) { m_context = JSGlobalContextCreateInGroup(nullptr, nullptr); m_messageQueueThread = MessageQueues::getCurrentMessageQueueThread(); s_globalContextRefToJSCExecutor[m_context] = this; installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate); installGlobalFunction(m_context, "nativePerformanceNow", nativePerformanceNow); installGlobalFunction(m_context, "nativeStartWorker", nativeStartWorker); installGlobalFunction(m_context, "nativePostMessageToWorker", nativePostMessageToWorker); installGlobalFunction(m_context, "nativeTerminateWorker", nativeTerminateWorker); installGlobalFunction(m_context, "nativeInjectHMRUpdate", nativeInjectHMRUpdate); installGlobalFunction(m_context, "nativeLoggingHook", JSLogging::nativeHook); #ifdef WITH_FB_JSC_TUNING configureJSCForAndroid(); #endif #ifdef WITH_JSC_EXTRA_TRACING addNativeTracingHooks(m_context); addNativeProfilingHooks(m_context); PerfLogging::installNativeHooks(m_context); #endif #ifdef WITH_FB_MEMORY_PROFILING addNativeMemoryHooks(m_context); #endif } JSCExecutor::~JSCExecutor() { // terminateWebWorker mutates m_webWorkers so collect all the workers to terminate first std::vector workerIds; for (auto it = m_webWorkers.begin(); it != m_webWorkers.end(); it++) { workerIds.push_back(it->first); } for (int workerId : workerIds) { terminateWebWorker(workerId); } s_globalContextRefToJSCExecutor.erase(m_context); JSGlobalContextRelease(m_context); } void JSCExecutor::executeApplicationScript( const std::string& script, const std::string& sourceURL) { ReactMarker::logMarker("executeApplicationScript_startStringConvert"); String jsScript = String::createExpectingAscii(script); ReactMarker::logMarker("executeApplicationScript_endStringConvert"); String jsSourceURL(sourceURL.c_str()); #ifdef WITH_FBSYSTRACE FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor::executeApplicationScript", "sourceURL", sourceURL); #endif if (!jsSourceURL) { evaluateScript(m_context, jsScript, jsSourceURL); } else { // If we're evaluating a script, get the device's cache dir // in which a cache file for that script will be stored. evaluateScript(m_context, jsScript, jsSourceURL, m_deviceCacheDir.c_str()); } } void JSCExecutor::loadApplicationUnbundle( std::unique_ptr unbundle, const std::string& startupCode, const std::string& sourceURL) { if (!m_unbundle) { installGlobalFunction(m_context, "nativeRequire", nativeRequire); } m_unbundle = std::move(unbundle); executeApplicationScript(startupCode, sourceURL); } 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, false); } void JSCExecutor::loadModule(uint32_t moduleId) { auto module = m_unbundle->getModule(moduleId); auto sourceUrl = String::createExpectingAscii(module.name); auto source = String::createExpectingAscii(module.code); evaluateScript(m_context, source, sourceUrl); } // WebWorker impl JSGlobalContextRef JSCExecutor::getContext() { return m_context; } std::shared_ptr JSCExecutor::getMessageQueueThread() { return m_messageQueueThread; } void JSCExecutor::onMessageReceived(int workerId, const std::string& json) { Object& worker = m_webWorkerJSObjs.at(workerId); Value onmessageValue = worker.getProperty("onmessage"); if (onmessageValue.isUndefined()) { return; } JSValueRef args[] = { JSCWebWorker::createMessageObject(m_context, json) }; onmessageValue.asObject().callAsFunction(1, args); m_flushImmediateCallback(flush(), true); } int JSCExecutor::addWebWorker(const std::string& script, JSValueRef workerRef) { static std::atomic_int nextWorkerId(0); int workerId = nextWorkerId++; m_webWorkers.emplace(std::piecewise_construct, std::forward_as_tuple(workerId), std::forward_as_tuple(workerId, this, script)); Object workerObj = Value(m_context, workerRef).asObject(); workerObj.makeProtected(); m_webWorkerJSObjs.emplace(workerId, std::move(workerObj)); return workerId; } void JSCExecutor::postMessageToWebWorker(int workerId, JSValueRef message, JSValueRef *exn) { JSCWebWorker& worker = m_webWorkers.at(workerId); worker.postMessage(message); } void JSCExecutor::terminateWebWorker(int workerId) { JSCWebWorker& worker = m_webWorkers.at(workerId); worker.terminate(); m_webWorkers.erase(workerId); m_webWorkerJSObjs.erase(workerId); } // Native JS hooks static JSValueRef makeInvalidModuleIdJSCException( JSContextRef ctx, const JSValueRef id, JSValueRef *exception) { std::string message = "Received invalid module ID: "; message += String::adopt(JSValueToStringCopy(ctx, id, exception)).str(); return makeJSCException(ctx, message.c_str()); } JSValueRef JSCExecutor::nativeRequire( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { if (argumentCount != 1) { *exception = makeJSCException(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 = makeJSCException(ctx, "Global JS context didn't map to a valid executor"); return JSValueMakeUndefined(ctx); } double moduleId = JSValueToNumber(ctx, arguments[0], exception); if (moduleId <= (double) std::numeric_limits::max() && moduleId >= 0.0) { try { executor->loadModule(moduleId); } catch (JSModulesUnbundle::ModuleNotFound&) { *exception = makeInvalidModuleIdJSCException(ctx, arguments[0], exception); } } else { *exception = makeInvalidModuleIdJSCException(ctx, arguments[0], exception); } return JSValueMakeUndefined(ctx); } 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); } std::string resStr = Value(ctx, arguments[0]).toJSONString(); executor->flushQueueImmediate(resStr); return JSValueMakeUndefined(ctx); } JSValueRef JSCExecutor::nativeStartWorker( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { if (argumentCount != 2) { *exception = createErrorString(ctx, "Got wrong number of args"); return JSValueMakeUndefined(ctx); } std::string scriptFile = Value(ctx, arguments[0]).toString().str(); JSValueRef worker = arguments[1]; 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); } int workerId = executor->addWebWorker(scriptFile, worker); return JSValueMakeNumber(ctx, workerId); } JSValueRef JSCExecutor::nativePostMessageToWorker( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { if (argumentCount != 2) { *exception = createErrorString(ctx, "Got wrong number of args"); return JSValueMakeUndefined(ctx); } double workerDouble = JSValueToNumber(ctx, arguments[0], exception); if (workerDouble != workerDouble) { *exception = createErrorString(ctx, "Got invalid worker id"); 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); } executor->postMessageToWebWorker((int) workerDouble, arguments[1], exception); return JSValueMakeUndefined(ctx); } JSValueRef JSCExecutor::nativeTerminateWorker( 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); } double workerDouble = JSValueToNumber(ctx, arguments[0], exception); if (workerDouble != workerDouble) { *exception = createErrorString(ctx, "Got invalid worker id"); 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); } executor->terminateWebWorker((int) workerDouble); 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)); } static JSValueRef nativeInjectHMRUpdate( JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { String execJSString = Value(ctx, arguments[0]).toString(); String jsURL = Value(ctx, arguments[1]).toString(); evaluateScript(ctx, execJSString, jsURL); return JSValueMakeUndefined(ctx); } } }