mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: This is caused by receiving \u2028/2029 in callbacks/function calls. The correct solution is to not evaluate these strings as scripts but instead parse them as json and pass them through the JSC API. Reviewed By: lexs Differential Revision: D3543098 fbshipit-source-id: 4d8acce1d510bb17361d32103d4738fc0208b0a8
710 lines
23 KiB
C++
710 lines
23 KiB
C++
// Copyright 2004-present Facebook. All Rights Reserved.
|
|
|
|
#include "JSCExecutor.h"
|
|
|
|
#include <algorithm>
|
|
#include <condition_variable>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <glog/logging.h>
|
|
#include <folly/json.h>
|
|
#include <folly/Memory.h>
|
|
#include <folly/String.h>
|
|
#include <sys/time.h>
|
|
|
|
#include "Bridge.h"
|
|
#include "JSCHelpers.h"
|
|
#include "Platform.h"
|
|
#include "Value.h"
|
|
|
|
#ifdef WITH_JSC_EXTRA_TRACING
|
|
#include "JSCTracing.h"
|
|
#include "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
|
|
|
|
#ifdef WITH_FB_MEMORY_PROFILING
|
|
#include "JSCMemory.h"
|
|
#endif
|
|
|
|
#ifdef WITH_FB_JSC_TUNING
|
|
#include <jsc_config_android.h>
|
|
#endif
|
|
|
|
#ifdef JSC_HAS_PERF_STATS_API
|
|
#include "JSCPerfStats.h"
|
|
#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<JSContextRef, JSCExecutor*> s_globalContextRefToJSCExecutor;
|
|
|
|
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);
|
|
|
|
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(Bridge *bridge) {
|
|
return std::unique_ptr<JSExecutor>(new JSCExecutor(bridge, cacheDir_, m_jscConfig));
|
|
}
|
|
|
|
JSCExecutor::JSCExecutor(Bridge *bridge, const std::string& cacheDir, const folly::dynamic& jscConfig) :
|
|
m_bridge(bridge),
|
|
m_deviceCacheDir(cacheDir),
|
|
m_messageQueueThread(MessageQueues::getCurrentMessageQueueThread()),
|
|
m_jscConfig(jscConfig) {
|
|
initOnJSVMThread();
|
|
}
|
|
|
|
JSCExecutor::JSCExecutor(
|
|
Bridge *bridge,
|
|
int workerId,
|
|
JSCExecutor *owner,
|
|
const std::string& script,
|
|
const std::unordered_map<std::string, std::string>& globalObjAsJSON,
|
|
const folly::dynamic& jscConfig) :
|
|
m_bridge(bridge),
|
|
m_workerId(workerId),
|
|
m_owner(owner),
|
|
m_deviceCacheDir(owner->m_deviceCacheDir),
|
|
m_messageQueueThread(MessageQueues::getCurrentMessageQueueThread()),
|
|
m_jscConfig(jscConfig) {
|
|
// We post initOnJSVMThread here so that the owner doesn't have to wait for
|
|
// initialization on its own thread
|
|
m_messageQueueThread->runOnQueue([this, script, globalObjAsJSON] () {
|
|
initOnJSVMThread();
|
|
|
|
installGlobalFunction(m_context, "postMessage", nativePostMessage);
|
|
|
|
for (auto& it : globalObjAsJSON) {
|
|
setGlobalVariable(it.first, it.second);
|
|
}
|
|
|
|
// Try to load the script from the network if script is a URL
|
|
// NB: For security, this will only work in debug builds
|
|
std::string scriptSrc;
|
|
if (script.find("http://") == 0 || script.find("https://") == 0) {
|
|
std::stringstream outfileBuilder;
|
|
outfileBuilder << m_deviceCacheDir << "/workerScript" << m_workerId << ".js";
|
|
scriptSrc = WebWorkerUtil::loadScriptFromNetworkSync(script, outfileBuilder.str());
|
|
} else {
|
|
// TODO(9604438): Protect against script does not exist
|
|
scriptSrc = WebWorkerUtil::loadScriptFromAssets(script);
|
|
}
|
|
|
|
// TODO(9994180): Throw on error
|
|
loadApplicationScript(scriptSrc, script);
|
|
});
|
|
}
|
|
|
|
JSCExecutor::~JSCExecutor() {
|
|
CHECK(*m_isDestroyed) << "JSCExecutor::destroy() must be called before its destructor!";
|
|
}
|
|
|
|
void JSCExecutor::destroy() {
|
|
*m_isDestroyed = true;
|
|
if (m_messageQueueThread->isOnThread()) {
|
|
terminateOnJSVMThread();
|
|
} else {
|
|
m_messageQueueThread->runOnQueueSync([this] () {
|
|
terminateOnJSVMThread();
|
|
});
|
|
}
|
|
}
|
|
|
|
void JSCExecutor::initOnJSVMThread() {
|
|
#if defined(WITH_FB_JSC_TUNING)
|
|
configureJSCForAndroid(m_jscConfig);
|
|
#endif
|
|
m_context = JSGlobalContextCreateInGroup(nullptr, nullptr);
|
|
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_JSC_EXTRA_TRACING
|
|
addNativeTracingHooks(m_context);
|
|
addNativeProfilingHooks(m_context);
|
|
PerfLogging::installNativeHooks(m_context);
|
|
#endif
|
|
|
|
#ifdef WITH_FB_MEMORY_PROFILING
|
|
addNativeMemoryHooks(m_context);
|
|
#endif
|
|
|
|
#ifdef JSC_HAS_PERF_STATS_API
|
|
addJSCPerfStatsHooks(m_context);
|
|
#endif
|
|
|
|
#if defined(WITH_FB_JSC_TUNING)
|
|
configureJSContextForAndroid(m_context, m_jscConfig, m_deviceCacheDir);
|
|
#endif
|
|
}
|
|
|
|
void JSCExecutor::terminateOnJSVMThread() {
|
|
// terminateOwnedWebWorker mutates m_ownedWorkers so collect all the workers
|
|
// to terminate first
|
|
std::vector<int> workerIds;
|
|
for (auto& it : m_ownedWorkers) {
|
|
workerIds.push_back(it.first);
|
|
}
|
|
for (int workerId : workerIds) {
|
|
terminateOwnedWebWorker(workerId);
|
|
}
|
|
|
|
m_batchedBridge.reset();
|
|
m_flushedQueueObj.reset();
|
|
m_callFunctionObj.reset();
|
|
m_invokeCallbackObj.reset();
|
|
|
|
s_globalContextRefToJSCExecutor.erase(m_context);
|
|
JSGlobalContextRelease(m_context);
|
|
m_context = nullptr;
|
|
}
|
|
|
|
// Checks if the user is in the pre-parsing cache & StringRef QE.
|
|
// Should be removed when these features are no longer gated.
|
|
bool JSCExecutor::usePreparsingAndStringRef(){
|
|
return m_jscConfig.getDefault("PreparsingStringRef", true).getBool();
|
|
}
|
|
|
|
void JSCExecutor::loadApplicationScript(
|
|
const std::string& script,
|
|
const std::string& sourceURL) {
|
|
ReactMarker::logMarker("loadApplicationScript_startStringConvert");
|
|
#if WITH_FBJSCEXTENSIONS
|
|
JSStringRef jsScriptRef;
|
|
if (usePreparsingAndStringRef()){
|
|
jsScriptRef = JSStringCreateWithUTF8CStringExpectAscii(script.c_str(), script.size());
|
|
} else {
|
|
jsScriptRef = JSStringCreateWithUTF8CString(script.c_str());
|
|
}
|
|
|
|
String jsScript = String::adopt(jsScriptRef);
|
|
#else
|
|
String jsScript = String::createExpectingAscii(script);
|
|
#endif
|
|
|
|
ReactMarker::logMarker("loadApplicationScript_endStringConvert");
|
|
|
|
String jsSourceURL(sourceURL.c_str());
|
|
#ifdef WITH_FBSYSTRACE
|
|
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor::loadApplicationScript",
|
|
"sourceURL", sourceURL);
|
|
#endif
|
|
evaluateScript(m_context, jsScript, jsSourceURL);
|
|
flush();
|
|
ReactMarker::logMarker("RUN_JS_BUNDLE_END");
|
|
ReactMarker::logMarker("CREATE_REACT_CONTEXT_END");
|
|
}
|
|
|
|
void JSCExecutor::loadApplicationUnbundle(
|
|
std::unique_ptr<JSModulesUnbundle> unbundle,
|
|
const std::string& startupCode,
|
|
const std::string& sourceURL) {
|
|
if (!m_unbundle) {
|
|
installGlobalFunction(m_context, "nativeRequire", nativeRequire);
|
|
}
|
|
m_unbundle = std::move(unbundle);
|
|
loadApplicationScript(startupCode, sourceURL);
|
|
}
|
|
|
|
bool JSCExecutor::ensureBatchedBridgeObject() {
|
|
if (m_batchedBridge) {
|
|
return true;
|
|
}
|
|
|
|
Value batchedBridgeValue = Object::getGlobalObject(m_context).getProperty("__fbBatchedBridge");
|
|
if (batchedBridgeValue.isUndefined()) {
|
|
return false;
|
|
}
|
|
m_batchedBridge = folly::make_unique<Object>(batchedBridgeValue.asObject());
|
|
m_flushedQueueObj = folly::make_unique<Object>(m_batchedBridge->getProperty("flushedQueue").asObject());
|
|
m_callFunctionObj = folly::make_unique<Object>(m_batchedBridge->getProperty("callFunctionReturnFlushedQueue").asObject());
|
|
m_invokeCallbackObj = folly::make_unique<Object>(m_batchedBridge->getProperty("invokeCallbackAndReturnFlushedQueue").asObject());
|
|
return true;
|
|
}
|
|
|
|
void JSCExecutor::flush() {
|
|
#ifdef WITH_FBSYSTRACE
|
|
FbSystraceSection s(
|
|
TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.flush");
|
|
#endif
|
|
|
|
if (!ensureBatchedBridgeObject()) {
|
|
throwJSExecutionException(
|
|
"Could not connect to development server.\n"
|
|
"Try the following to fix the issue:\n"
|
|
"Ensure that the packager server is running\n"
|
|
"Ensure that your device/emulator is connected to your machine and has USB debugging enabled - run 'adb devices' to see a list of connected devices\n"
|
|
"If you're on a physical device connected to the same machine, run 'adb reverse tcp:8081 tcp:8081' to forward requests from your device\n"
|
|
"If your device is on the same Wi-Fi network, set 'Debug server host & port for device' in 'Dev settings' to your machine's IP address and the port of the local dev server - e.g. 10.0.1.1:8081");
|
|
}
|
|
|
|
std::string calls = m_flushedQueueObj->callAsFunction().toJSONString();
|
|
m_bridge->callNativeModules(*this, calls, true);
|
|
}
|
|
|
|
void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
|
|
#ifdef WITH_FBSYSTRACE
|
|
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.callFunction");
|
|
#endif
|
|
|
|
if (!ensureBatchedBridgeObject()) {
|
|
throwJSExecutionException(
|
|
"Couldn't call JS module %s, method %s: bridge configuration isn't available. This "
|
|
"probably means you're calling a JS module method before bridge setup has completed or without a JS bundle loaded.",
|
|
moduleId.c_str(),
|
|
methodId.c_str());
|
|
}
|
|
|
|
String argsString = String(folly::toJson(std::move(arguments)).c_str());
|
|
String moduleIdStr(moduleId.c_str());
|
|
String methodIdStr(methodId.c_str());
|
|
JSValueRef args[] = {
|
|
JSValueMakeString(m_context, moduleIdStr),
|
|
JSValueMakeString(m_context, methodIdStr),
|
|
Value::fromJSON(m_context, argsString)
|
|
};
|
|
auto result = m_callFunctionObj->callAsFunction(3, args);
|
|
m_bridge->callNativeModules(*this, result.toJSONString(), true);
|
|
}
|
|
|
|
void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
|
|
#ifdef WITH_FBSYSTRACE
|
|
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.invokeCallback");
|
|
#endif
|
|
|
|
if (!ensureBatchedBridgeObject()) {
|
|
throwJSExecutionException(
|
|
"Couldn't invoke JS callback %d: bridge configuration isn't available. This shouldn't be possible. Congratulations.", (int) callbackId);
|
|
}
|
|
|
|
String argsString = String(folly::toJson(std::move(arguments)).c_str());
|
|
JSValueRef args[] = {
|
|
JSValueMakeNumber(m_context, callbackId),
|
|
Value::fromJSON(m_context, argsString)
|
|
};
|
|
auto result = m_invokeCallbackObj->callAsFunction(2, args);
|
|
m_bridge->callNativeModules(*this, result.toJSONString(), true);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void* JSCExecutor::getJavaScriptContext() {
|
|
return m_context;
|
|
}
|
|
|
|
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_REACT_INTERNAL_SETTINGS
|
|
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::handleMemoryPressureUiHidden() {
|
|
#ifdef WITH_JSC_MEMORY_PRESSURE
|
|
JSHandleMemoryPressure(this, m_context, JSMemoryPressure::UI_HIDDEN);
|
|
#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_bridge->callNativeModules(*this, 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);
|
|
}
|
|
|
|
int JSCExecutor::addWebWorker(
|
|
const std::string& script,
|
|
JSValueRef workerRef,
|
|
JSValueRef globalObjRef) {
|
|
static std::atomic_int nextWorkerId(1);
|
|
int workerId = nextWorkerId++;
|
|
|
|
Object globalObj = Value(m_context, globalObjRef).asObject();
|
|
|
|
auto workerJscConfig = m_jscConfig;
|
|
workerJscConfig["isWebWorker"] = true;
|
|
|
|
auto workerMQT = WebWorkerUtil::createWebWorkerThread(workerId, m_messageQueueThread.get());
|
|
std::unique_ptr<JSCExecutor> worker;
|
|
workerMQT->runOnQueueSync([this, &worker, &script, &globalObj, workerId, &workerJscConfig] () {
|
|
worker.reset(new JSCExecutor(m_bridge, workerId, this, script, globalObj.toJSONMap(), workerJscConfig));
|
|
});
|
|
|
|
Object workerObj = Value(m_context, workerRef).asObject();
|
|
workerObj.makeProtected();
|
|
|
|
JSCExecutor *workerPtr = worker.get();
|
|
std::shared_ptr<MessageQueueThread> sharedMessageQueueThread = worker->m_messageQueueThread;
|
|
ExecutorToken token = m_bridge->registerExecutor(
|
|
std::move(worker),
|
|
std::move(sharedMessageQueueThread));
|
|
|
|
m_ownedWorkers.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(workerId),
|
|
std::forward_as_tuple(workerPtr, token, std::move(workerObj)));
|
|
|
|
return workerId;
|
|
}
|
|
|
|
void JSCExecutor::postMessageToOwnedWebWorker(int workerId, JSValueRef message, JSValueRef *exn) {
|
|
auto worker = m_ownedWorkers.at(workerId).executor;
|
|
std::string msgString = Value(m_context, message).toJSONString();
|
|
|
|
std::shared_ptr<bool> isWorkerDestroyed = worker->m_isDestroyed;
|
|
worker->m_messageQueueThread->runOnQueue([isWorkerDestroyed, worker, msgString] () {
|
|
if (*isWorkerDestroyed) {
|
|
return;
|
|
}
|
|
worker->receiveMessageFromOwner(msgString);
|
|
});
|
|
}
|
|
|
|
void JSCExecutor::postMessageToOwner(JSValueRef msg) {
|
|
std::string msgString = Value(m_context, msg).toJSONString();
|
|
std::shared_ptr<bool> ownerIsDestroyed = m_owner->m_isDestroyed;
|
|
m_owner->m_messageQueueThread->runOnQueue([workerId=m_workerId, owner=m_owner, ownerIsDestroyed, msgString] () {
|
|
if (*ownerIsDestroyed) {
|
|
return;
|
|
}
|
|
owner->receiveMessageFromOwnedWebWorker(workerId, msgString);
|
|
});
|
|
}
|
|
|
|
void JSCExecutor::receiveMessageFromOwnedWebWorker(int workerId, const std::string& json) {
|
|
Object* workerObj;
|
|
try {
|
|
workerObj = &m_ownedWorkers.at(workerId).jsObj;
|
|
} catch (std::out_of_range& e) {
|
|
// Worker was already terminated
|
|
return;
|
|
}
|
|
|
|
Value onmessageValue = workerObj->getProperty("onmessage");
|
|
if (onmessageValue.isUndefined()) {
|
|
return;
|
|
}
|
|
|
|
JSValueRef args[] = { createMessageObject(json) };
|
|
onmessageValue.asObject().callAsFunction(1, args);
|
|
|
|
flush();
|
|
}
|
|
|
|
void JSCExecutor::receiveMessageFromOwner(const std::string& msgString) {
|
|
CHECK(m_owner) << "Received message in a Executor that doesn't have an owner!";
|
|
|
|
JSValueRef args[] = { createMessageObject(msgString) };
|
|
Value onmessageValue = Object::getGlobalObject(m_context).getProperty("onmessage");
|
|
onmessageValue.asObject().callAsFunction(1, args);
|
|
}
|
|
|
|
void JSCExecutor::terminateOwnedWebWorker(int workerId) {
|
|
auto& workerRegistration = m_ownedWorkers.at(workerId);
|
|
std::shared_ptr<MessageQueueThread> workerMQT = workerRegistration.executor->m_messageQueueThread;
|
|
ExecutorToken workerExecutorToken = workerRegistration.executorToken;
|
|
m_ownedWorkers.erase(workerId);
|
|
|
|
std::unique_ptr<JSExecutor> worker = m_bridge->unregisterExecutor(workerExecutorToken);
|
|
worker->destroy();
|
|
worker.reset();
|
|
workerMQT->quitSynchronous();
|
|
}
|
|
|
|
Object JSCExecutor::createMessageObject(const std::string& msgJson) {
|
|
Value rebornJSMsg = Value::fromJSON(m_context, String(msgJson.c_str()));
|
|
Object messageObject = Object::create(m_context);
|
|
messageObject.setProperty("data", rebornJSMsg);
|
|
return messageObject;
|
|
}
|
|
|
|
// Native JS hooks
|
|
JSValueRef JSCExecutor::nativePostMessage(
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef *exception) {
|
|
if (argumentCount != 1) {
|
|
*exception = makeJSCException(ctx, "postMessage got wrong number of arguments");
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
JSValueRef msg = arguments[0];
|
|
JSCExecutor *webWorker = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
|
|
|
|
webWorker->postMessageToOwner(msg);
|
|
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
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<uint32_t>::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));
|
|
}
|
|
|
|
JSValueRef JSCExecutor::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 != 3) {
|
|
*exception = createErrorString(ctx, "Got wrong number of args");
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
std::string scriptFile = Value(ctx, arguments[0]).toString().str();
|
|
|
|
JSValueRef worker = arguments[1];
|
|
JSValueRef globalObj = arguments[2];
|
|
|
|
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, globalObj);
|
|
|
|
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->postMessageToOwnedWebWorker((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->terminateOwnedWebWorker((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);
|
|
}
|
|
|
|
} }
|