mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Reviewed By: astreet Differential Revision: D2699681 fb-gh-sync-id: 81617187abec6d9bc1ffe3da02513c6875d0b93b
315 lines
10 KiB
C++
315 lines
10 KiB
C++
// Copyright 2004-present Facebook. All Rights Reserved.
|
|
|
|
#include "JSCExecutor.h"
|
|
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <fb/log.h>
|
|
#include <folly/json.h>
|
|
#include <folly/String.h>
|
|
#include <jni/fbjni/Exceptions.h>
|
|
#include <sys/time.h>
|
|
#include "Value.h"
|
|
#include "jni/OnLoad.h"
|
|
|
|
#ifdef WITH_JSC_EXTRA_TRACING
|
|
#include <react/JSCTracing.h>
|
|
#include <react/JSCLegacyProfiler.h>
|
|
#include <JavaScriptCore/API/JSProfilerPrivate.h>
|
|
#endif
|
|
|
|
#ifdef WITH_JSC_MEMORY_PRESSURE
|
|
#include <jsc_memory.h>
|
|
#endif
|
|
|
|
#ifdef WITH_FBSYSTRACE
|
|
#include <fbsystrace.h>
|
|
using fbsystrace::FbSystraceSection;
|
|
#endif
|
|
|
|
// Add native performance markers support
|
|
#include <react/JSCPerfLogging.h>
|
|
#include <react/JSCMemory.h>
|
|
|
|
#ifdef WITH_FB_JSC_TUNING
|
|
#include <jsc_config_android.h>
|
|
#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<JSContextRef, JSCExecutor*> 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<folly::dynamic>& 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<folly::fbstring>(
|
|
"__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<JSExecutor> JSCExecutorFactory::createJSExecutor(FlushImmediateCallback cb) {
|
|
return std::unique_ptr<JSExecutor>(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<folly::dynamic>());
|
|
}
|
|
|
|
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<folly::dynamic> 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<folly::dynamic> 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<android_LogPriority>(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));
|
|
}
|
|
|
|
} }
|