mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-23 20:01:01 +08:00
Drop support for webworkers
Reviewed By: AaaChiuuu Differential Revision: D4916449 fbshipit-source-id: a447233d3b7cfee98db2ce00f1c0505d513e2429
This commit is contained in:
committed by
Facebook Github Bot
parent
a20882f62e
commit
34bc6bd2ae
@@ -16,7 +16,6 @@ LOCAL_SRC_FILES := \
|
||||
JSCNativeModules.cpp \
|
||||
JSCPerfStats.cpp \
|
||||
JSCTracing.cpp \
|
||||
JSCWebWorker.cpp \
|
||||
JSIndexedRAMBundle.cpp \
|
||||
MethodCall.cpp \
|
||||
ModuleRegistry.cpp \
|
||||
|
||||
@@ -129,14 +129,11 @@ cxx_library(
|
||||
CXXREACT_PUBLIC_HEADERS = [
|
||||
"CxxNativeModule.h",
|
||||
"Executor.h",
|
||||
"ExecutorToken.h",
|
||||
"ExecutorTokenFactory.h",
|
||||
"Instance.h",
|
||||
"JSBigString.h",
|
||||
"JSBundleType.h",
|
||||
"JSCExecutor.h",
|
||||
"JSCNativeModules.h",
|
||||
"JSCWebWorker.h",
|
||||
"JSIndexedRAMBundle.h",
|
||||
"JSModulesUnbundle.h",
|
||||
"MessageQueueThread.h",
|
||||
|
||||
@@ -15,15 +15,15 @@ namespace facebook {
|
||||
namespace react {
|
||||
|
||||
std::function<void(folly::dynamic)> makeCallback(
|
||||
std::weak_ptr<Instance> instance, ExecutorToken token, const folly::dynamic& callbackId) {
|
||||
std::weak_ptr<Instance> instance, const folly::dynamic& callbackId) {
|
||||
if (!callbackId.isInt()) {
|
||||
throw std::invalid_argument("Expected callback(s) as final argument");
|
||||
}
|
||||
|
||||
auto id = callbackId.getInt();
|
||||
return [winstance = std::move(instance), token, id](folly::dynamic args) {
|
||||
return [winstance = std::move(instance), id](folly::dynamic args) {
|
||||
if (auto instance = winstance.lock()) {
|
||||
instance->callJSCallback(token, id, std::move(args));
|
||||
instance->callJSCallback(id, std::move(args));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -75,12 +75,7 @@ folly::dynamic CxxNativeModule::getConstants() {
|
||||
return constants;
|
||||
}
|
||||
|
||||
bool CxxNativeModule::supportsWebWorkers() {
|
||||
// TODO(andrews): web worker support in cxxmodules
|
||||
return false;
|
||||
}
|
||||
|
||||
void CxxNativeModule::invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) {
|
||||
void CxxNativeModule::invoke(unsigned int reactMethodId, folly::dynamic&& params) {
|
||||
if (reactMethodId >= methods_.size()) {
|
||||
throw std::invalid_argument(folly::to<std::string>("methodId ", reactMethodId,
|
||||
" out of range [0..", methods_.size(), "]"));
|
||||
@@ -106,10 +101,10 @@ void CxxNativeModule::invoke(ExecutorToken token, unsigned int reactMethodId, fo
|
||||
}
|
||||
|
||||
if (method.callbacks == 1) {
|
||||
first = convertCallback(makeCallback(instance_, token, params[params.size() - 1]));
|
||||
first = convertCallback(makeCallback(instance_, params[params.size() - 1]));
|
||||
} else if (method.callbacks == 2) {
|
||||
first = convertCallback(makeCallback(instance_, token, params[params.size() - 2]));
|
||||
second = convertCallback(makeCallback(instance_, token, params[params.size() - 1]));
|
||||
first = convertCallback(makeCallback(instance_, params[params.size() - 2]));
|
||||
second = convertCallback(makeCallback(instance_, params[params.size() - 1]));
|
||||
}
|
||||
|
||||
params.resize(params.size() - method.callbacks);
|
||||
@@ -146,8 +141,7 @@ void CxxNativeModule::invoke(ExecutorToken token, unsigned int reactMethodId, fo
|
||||
});
|
||||
}
|
||||
|
||||
MethodCallResult CxxNativeModule::callSerializableNativeHook(
|
||||
ExecutorToken token, unsigned int hookId, folly::dynamic&& args) {
|
||||
MethodCallResult CxxNativeModule::callSerializableNativeHook(unsigned int hookId, folly::dynamic&& args) {
|
||||
if (hookId >= methods_.size()) {
|
||||
throw std::invalid_argument(
|
||||
folly::to<std::string>("methodId ", hookId, " out of range [0..", methods_.size(), "]"));
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace react {
|
||||
class Instance;
|
||||
|
||||
std::function<void(folly::dynamic)> makeCallback(
|
||||
std::weak_ptr<Instance> instance, ExecutorToken token, const folly::dynamic& callbackId);
|
||||
std::weak_ptr<Instance> instance, const folly::dynamic& callbackId);
|
||||
|
||||
class CxxNativeModule : public NativeModule {
|
||||
public:
|
||||
@@ -27,10 +27,8 @@ public:
|
||||
std::string getName() override;
|
||||
std::vector<MethodDescriptor> getMethods() override;
|
||||
folly::dynamic getConstants() override;
|
||||
bool supportsWebWorkers() override;
|
||||
void invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) override;
|
||||
MethodCallResult callSerializableNativeHook(
|
||||
ExecutorToken token, unsigned int hookId, folly::dynamic&& args) override;
|
||||
void invoke(unsigned int reactMethodId, folly::dynamic&& params) override;
|
||||
MethodCallResult callSerializableNativeHook(unsigned int hookId, folly::dynamic&& args) override;
|
||||
|
||||
private:
|
||||
void lazyInit();
|
||||
|
||||
@@ -38,10 +38,6 @@ class ExecutorDelegate {
|
||||
public:
|
||||
virtual ~ExecutorDelegate() {}
|
||||
|
||||
virtual void registerExecutor(std::unique_ptr<JSExecutor> executor,
|
||||
std::shared_ptr<MessageQueueThread> queue) = 0;
|
||||
virtual std::unique_ptr<JSExecutor> unregisterExecutor(JSExecutor& executor) = 0;
|
||||
|
||||
virtual std::shared_ptr<ModuleRegistry> getModuleRegistry() = 0;
|
||||
|
||||
virtual void callNativeModules(
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cxxreact/Executor.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/**
|
||||
* This class exists so that we have a type for the shared_ptr on ExecutorToken
|
||||
* that implements a virtual destructor.
|
||||
*/
|
||||
class PlatformExecutorToken {
|
||||
public:
|
||||
virtual ~PlatformExecutorToken() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class corresponding to a JS VM that can call into native modules. This is
|
||||
* passed to native modules to allow their JS module calls/callbacks to be
|
||||
* routed back to the proper JS VM on the proper thread.
|
||||
*/
|
||||
class ExecutorToken {
|
||||
public:
|
||||
/**
|
||||
* This should only be used by the implementation of the platform ExecutorToken.
|
||||
* Do not use as a client of ExecutorToken.
|
||||
*/
|
||||
explicit ExecutorToken(std::shared_ptr<PlatformExecutorToken> platformToken) :
|
||||
platformToken_(platformToken) {}
|
||||
|
||||
std::shared_ptr<PlatformExecutorToken> getPlatformExecutorToken() const {
|
||||
return platformToken_;
|
||||
}
|
||||
|
||||
bool operator==(const ExecutorToken& other) const {
|
||||
return platformToken_.get() == other.platformToken_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<PlatformExecutorToken> platformToken_;
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
namespace std {
|
||||
template<>
|
||||
struct hash<facebook::react::ExecutorToken> {
|
||||
size_t operator()(const facebook::react::ExecutorToken& token) const {
|
||||
return (size_t) token.getPlatformExecutorToken().get();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cxxreact/Executor.h>
|
||||
#include <cxxreact/ExecutorToken.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/**
|
||||
* Class that knows how to create the platform-specific implementation
|
||||
* of ExecutorToken.
|
||||
*/
|
||||
class ExecutorTokenFactory {
|
||||
public:
|
||||
virtual ~ExecutorTokenFactory() {}
|
||||
|
||||
/**
|
||||
* Creates a new ExecutorToken.
|
||||
*/
|
||||
virtual ExecutorToken createExecutorToken() const = 0;
|
||||
};
|
||||
|
||||
} }
|
||||
@@ -128,20 +128,15 @@ void *Instance::getJavaScriptContext() {
|
||||
return nativeToJsBridge_->getJavaScriptContext();
|
||||
}
|
||||
|
||||
void Instance::callJSFunction(ExecutorToken token, std::string&& module, std::string&& method,
|
||||
folly::dynamic&& params) {
|
||||
void Instance::callJSFunction(std::string&& module, std::string&& method, folly::dynamic&& params) {
|
||||
callback_->incrementPendingJSCalls();
|
||||
nativeToJsBridge_->callFunction(token, std::move(module), std::move(method), std::move(params));
|
||||
nativeToJsBridge_->callFunction(std::move(module), std::move(method), std::move(params));
|
||||
}
|
||||
|
||||
void Instance::callJSCallback(ExecutorToken token, uint64_t callbackId, folly::dynamic&& params) {
|
||||
void Instance::callJSCallback(uint64_t callbackId, folly::dynamic&& params) {
|
||||
SystraceSection s("<callback>");
|
||||
callback_->incrementPendingJSCalls();
|
||||
nativeToJsBridge_->invokeCallback(token, (double) callbackId, std::move(params));
|
||||
}
|
||||
|
||||
ExecutorToken Instance::getMainExecutorToken() {
|
||||
return nativeToJsBridge_->getMainExecutorToken();
|
||||
nativeToJsBridge_->invokeCallback((double) callbackId, std::move(params));
|
||||
}
|
||||
|
||||
void Instance::handleMemoryPressureUiHidden() {
|
||||
|
||||
@@ -19,8 +19,6 @@ struct InstanceCallback {
|
||||
virtual void onBatchComplete() = 0;
|
||||
virtual void incrementPendingJSCalls() = 0;
|
||||
virtual void decrementPendingJSCalls() = 0;
|
||||
virtual ExecutorToken createExecutorToken() = 0;
|
||||
virtual void onExecutorStopped(ExecutorToken) = 0;
|
||||
};
|
||||
|
||||
class Instance {
|
||||
@@ -50,11 +48,9 @@ class Instance {
|
||||
void stopProfiler(const std::string& title, const std::string& filename);
|
||||
void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue);
|
||||
void *getJavaScriptContext();
|
||||
void callJSFunction(ExecutorToken token, std::string&& module, std::string&& method,
|
||||
folly::dynamic&& params);
|
||||
void callJSCallback(ExecutorToken token, uint64_t callbackId, folly::dynamic&& params);
|
||||
MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId,
|
||||
unsigned int methodId, folly::dynamic&& args);
|
||||
void callJSFunction(std::string&& module, std::string&& method, folly::dynamic&& params);
|
||||
void callJSCallback(uint64_t callbackId, folly::dynamic&& params);
|
||||
MethodCallResult callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, folly::dynamic&& args);
|
||||
// This method is experimental, and may be modified or removed.
|
||||
template <typename T>
|
||||
Value callFunctionSync(const std::string& module, const std::string& method, T&& args) {
|
||||
@@ -62,13 +58,12 @@ class Instance {
|
||||
return nativeToJsBridge_->callFunctionSync(module, method, std::forward<T>(args));
|
||||
}
|
||||
|
||||
ExecutorToken getMainExecutorToken();
|
||||
void handleMemoryPressureUiHidden();
|
||||
void handleMemoryPressureModerate();
|
||||
void handleMemoryPressureCritical();
|
||||
|
||||
private:
|
||||
void callNativeModules(ExecutorToken token, folly::dynamic&& calls, bool isEndOfBatch);
|
||||
void callNativeModules(folly::dynamic&& calls, bool isEndOfBatch);
|
||||
|
||||
std::shared_ptr<InstanceCallback> callback_;
|
||||
std::unique_ptr<NativeToJsBridge> nativeToJsBridge_;
|
||||
|
||||
@@ -132,16 +132,13 @@ static JSValueRef nativeInjectHMRUpdate(
|
||||
|
||||
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate, std::shared_ptr<MessageQueueThread> jsQueue) {
|
||||
return std::unique_ptr<JSExecutor>(
|
||||
new JSCExecutor(delegate, jsQueue, m_cacheDir, m_jscConfig));
|
||||
return folly::make_unique<JSCExecutor>(delegate, jsQueue, m_jscConfig);
|
||||
}
|
||||
|
||||
JSCExecutor::JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> messageQueueThread,
|
||||
const std::string& cacheDir,
|
||||
const folly::dynamic& jscConfig) throw(JSException) :
|
||||
m_delegate(delegate),
|
||||
m_deviceCacheDir(cacheDir),
|
||||
m_messageQueueThread(messageQueueThread),
|
||||
m_nativeModules(delegate ? delegate->getModuleRegistry() : nullptr),
|
||||
m_jscConfig(jscConfig) {
|
||||
@@ -154,52 +151,6 @@ JSCExecutor::JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate,
|
||||
}
|
||||
}
|
||||
|
||||
JSCExecutor::JSCExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> messageQueueThread,
|
||||
int workerId,
|
||||
JSCExecutor *owner,
|
||||
std::string scriptURL,
|
||||
std::unordered_map<std::string, std::string> globalObjAsJSON,
|
||||
const folly::dynamic& jscConfig) :
|
||||
m_delegate(delegate),
|
||||
m_workerId(workerId),
|
||||
m_owner(owner),
|
||||
m_deviceCacheDir(owner->m_deviceCacheDir),
|
||||
m_messageQueueThread(messageQueueThread),
|
||||
m_nativeModules(delegate->getModuleRegistry()),
|
||||
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, scriptURL,
|
||||
globalObjAsJSON=std::move(globalObjAsJSON)] () {
|
||||
initOnJSVMThread();
|
||||
|
||||
installNativeHook<&JSCExecutor::nativePostMessage>("postMessage");
|
||||
|
||||
for (auto& it : globalObjAsJSON) {
|
||||
setGlobalVariable(std::move(it.first),
|
||||
folly::make_unique<JSBigStdString>(std::move(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::unique_ptr<const JSBigString> script;
|
||||
if (scriptURL.find("http://") == 0 || scriptURL.find("https://") == 0) {
|
||||
std::stringstream outfileBuilder;
|
||||
outfileBuilder << m_deviceCacheDir << "/workerScript" << m_workerId << ".js";
|
||||
script = folly::make_unique<JSBigStdString>(
|
||||
WebWorkerUtil::loadScriptFromNetworkSync(scriptURL, outfileBuilder.str()));
|
||||
} else {
|
||||
// TODO(9604438): Protect against script does not exist
|
||||
script = WebWorkerUtil::loadScriptFromAssets(scriptURL);
|
||||
}
|
||||
|
||||
// TODO(9994180): Throw on error
|
||||
loadApplicationScript(std::move(script), std::move(scriptURL));
|
||||
});
|
||||
}
|
||||
|
||||
JSCExecutor::~JSCExecutor() {
|
||||
CHECK(*m_isDestroyed) << "JSCExecutor::destroy() must be called before its destructor!";
|
||||
}
|
||||
@@ -260,11 +211,6 @@ void JSCExecutor::initOnJSVMThread() throw(JSException) {
|
||||
installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
|
||||
installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook");
|
||||
|
||||
// Webworker support
|
||||
installNativeHook<&JSCExecutor::nativeStartWorker>("nativeStartWorker");
|
||||
installNativeHook<&JSCExecutor::nativePostMessageToWorker>("nativePostMessageToWorker");
|
||||
installNativeHook<&JSCExecutor::nativeTerminateWorker>("nativeTerminateWorker");
|
||||
|
||||
installGlobalFunction(m_context, "nativeLoggingHook", JSNativeHooks::loggingHook);
|
||||
installGlobalFunction(m_context, "nativePerformanceNow", JSNativeHooks::nowHook);
|
||||
|
||||
@@ -299,16 +245,6 @@ void JSCExecutor::initOnJSVMThread() throw(JSException) {
|
||||
}
|
||||
|
||||
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_nativeModules.reset();
|
||||
|
||||
#ifdef WITH_INSPECTOR
|
||||
@@ -635,115 +571,6 @@ void JSCExecutor::loadModule(uint32_t moduleId) {
|
||||
evaluateScript(m_context, source, sourceUrl);
|
||||
}
|
||||
|
||||
int JSCExecutor::addWebWorker(
|
||||
std::string scriptURL,
|
||||
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;
|
||||
|
||||
std::shared_ptr<MessageQueueThread> workerMQT =
|
||||
WebWorkerUtil::createWebWorkerThread(workerId, m_messageQueueThread.get());
|
||||
std::unique_ptr<JSCExecutor> worker;
|
||||
workerMQT->runOnQueueSync([this, &worker, &workerMQT, &scriptURL, &globalObj, workerId, &workerJscConfig] () {
|
||||
worker.reset(new JSCExecutor(m_delegate, workerMQT, workerId, this, std::move(scriptURL),
|
||||
globalObj.toJSONMap(), workerJscConfig));
|
||||
});
|
||||
|
||||
Object workerObj = Value(m_context, workerRef).asObject();
|
||||
workerObj.makeProtected();
|
||||
|
||||
JSCExecutor *workerPtr = worker.get();
|
||||
std::shared_ptr<MessageQueueThread> sharedMessageQueueThread = worker->m_messageQueueThread;
|
||||
m_delegate->registerExecutor(
|
||||
std::move(worker),
|
||||
std::move(sharedMessageQueueThread));
|
||||
|
||||
m_ownedWorkers.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(workerId),
|
||||
std::forward_as_tuple(workerPtr, std::move(workerObj)));
|
||||
|
||||
return workerId;
|
||||
}
|
||||
|
||||
void JSCExecutor::postMessageToOwnedWebWorker(int workerId, JSValueRef message) {
|
||||
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;
|
||||
m_ownedWorkers.erase(workerId);
|
||||
|
||||
workerMQT->runOnQueueSync([this, &workerMQT] {
|
||||
workerMQT->quitSynchronous();
|
||||
std::unique_ptr<JSExecutor> worker = m_delegate->unregisterExecutor(*this);
|
||||
worker->destroy();
|
||||
worker.reset();
|
||||
});
|
||||
}
|
||||
|
||||
Object JSCExecutor::createMessageObject(const std::string& msgJson) {
|
||||
Value rebornJSMsg = Value::fromJSON(m_context, String(m_context, msgJson.c_str()));
|
||||
Object messageObject = Object::create(m_context);
|
||||
messageObject.setProperty("data", rebornJSMsg);
|
||||
return messageObject;
|
||||
}
|
||||
|
||||
// Native JS hooks
|
||||
template<JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])>
|
||||
void JSCExecutor::installNativeHook(const char* name) {
|
||||
@@ -758,18 +585,6 @@ JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef property
|
||||
return m_nativeModules.getModule(m_context, propertyName);
|
||||
}
|
||||
|
||||
JSValueRef JSCExecutor::nativePostMessage(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]) {
|
||||
if (argumentCount != 1) {
|
||||
throw std::invalid_argument("Got wrong number of args");
|
||||
}
|
||||
JSValueRef msg = arguments[0];
|
||||
postMessageToOwner(msg);
|
||||
|
||||
return Value::makeUndefined(m_context);
|
||||
}
|
||||
|
||||
JSValueRef JSCExecutor::nativeRequire(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]) {
|
||||
@@ -801,57 +616,6 @@ JSValueRef JSCExecutor::nativeFlushQueueImmediate(
|
||||
return Value::makeUndefined(m_context);
|
||||
}
|
||||
|
||||
JSValueRef JSCExecutor::nativeStartWorker(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]) {
|
||||
if (argumentCount != 3) {
|
||||
throw std::invalid_argument("Got wrong number of args");
|
||||
}
|
||||
|
||||
std::string scriptFile = Value(m_context, arguments[0]).toString().str();
|
||||
|
||||
JSValueRef worker = arguments[1];
|
||||
JSValueRef globalObj = arguments[2];
|
||||
|
||||
int workerId = addWebWorker(scriptFile, worker, globalObj);
|
||||
|
||||
return Value::makeNumber(m_context, workerId);
|
||||
}
|
||||
|
||||
JSValueRef JSCExecutor::nativePostMessageToWorker(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]) {
|
||||
if (argumentCount != 2) {
|
||||
throw std::invalid_argument("Got wrong number of args");
|
||||
}
|
||||
|
||||
double workerDouble = Value(m_context, arguments[0]).asNumber();
|
||||
if (workerDouble != workerDouble) {
|
||||
throw std::invalid_argument("Got invalid worker id");
|
||||
}
|
||||
|
||||
postMessageToOwnedWebWorker((int) workerDouble, arguments[1]);
|
||||
|
||||
return Value::makeUndefined(m_context);
|
||||
}
|
||||
|
||||
JSValueRef JSCExecutor::nativeTerminateWorker(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]) {
|
||||
if (argumentCount != 1) {
|
||||
throw std::invalid_argument("Got wrong number of args");
|
||||
}
|
||||
|
||||
double workerDouble = Value(m_context, arguments[0]).asNumber();
|
||||
if (workerDouble != workerDouble) {
|
||||
std::invalid_argument("Got invalid worker id");
|
||||
}
|
||||
|
||||
terminateOwnedWebWorker((int) workerDouble);
|
||||
|
||||
return Value::makeUndefined(m_context);
|
||||
}
|
||||
|
||||
JSValueRef JSCExecutor::nativeCallSyncHook(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]) {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include <cxxreact/Executor.h>
|
||||
#include <cxxreact/ExecutorToken.h>
|
||||
#include <cxxreact/JSCNativeModules.h>
|
||||
#include <folly/Optional.h>
|
||||
#include <folly/json.h>
|
||||
@@ -23,10 +22,9 @@ class MessageQueueThread;
|
||||
|
||||
class RN_EXPORT JSCExecutorFactory : public JSExecutorFactory {
|
||||
public:
|
||||
JSCExecutorFactory(const std::string& cacheDir, const folly::dynamic& jscConfig) :
|
||||
m_cacheDir(cacheDir),
|
||||
m_jscConfig(jscConfig) {}
|
||||
virtual std::unique_ptr<JSExecutor> createJSExecutor(
|
||||
JSCExecutorFactory(const folly::dynamic& jscConfig) :
|
||||
m_jscConfig(jscConfig) {}
|
||||
std::unique_ptr<JSExecutor> createJSExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue) override;
|
||||
private:
|
||||
@@ -34,17 +32,6 @@ private:
|
||||
folly::dynamic m_jscConfig;
|
||||
};
|
||||
|
||||
class JSCExecutor;
|
||||
class WorkerRegistration : public noncopyable {
|
||||
public:
|
||||
explicit WorkerRegistration(JSCExecutor* executor_, Object jsObj_) :
|
||||
executor(executor_),
|
||||
jsObj(std::move(jsObj_)) {}
|
||||
|
||||
JSCExecutor *executor;
|
||||
Object jsObj;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ValueEncoder;
|
||||
|
||||
@@ -55,7 +42,6 @@ public:
|
||||
*/
|
||||
explicit JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> messageQueueThread,
|
||||
const std::string& cacheDir,
|
||||
const folly::dynamic& jscConfig) throw(JSException);
|
||||
~JSCExecutor() override;
|
||||
|
||||
@@ -104,11 +90,7 @@ public:
|
||||
private:
|
||||
JSGlobalContextRef m_context;
|
||||
std::shared_ptr<ExecutorDelegate> m_delegate;
|
||||
int m_workerId = 0; // if this is a worker executor, this is non-zero
|
||||
JSCExecutor *m_owner = nullptr; // if this is a worker executor, this is non-null
|
||||
std::shared_ptr<bool> m_isDestroyed = std::shared_ptr<bool>(new bool(false));
|
||||
std::unordered_map<int, WorkerRegistration> m_ownedWorkers;
|
||||
std::string m_deviceCacheDir;
|
||||
std::shared_ptr<MessageQueueThread> m_messageQueueThread;
|
||||
std::unique_ptr<JSModulesUnbundle> m_unbundle;
|
||||
JSCNativeModules m_nativeModules;
|
||||
@@ -120,18 +102,6 @@ private:
|
||||
folly::Optional<Object> m_flushedQueueJS;
|
||||
folly::Optional<Object> m_callFunctionReturnResultAndFlushedQueueJS;
|
||||
|
||||
/**
|
||||
* WebWorker constructor. Must be invoked from thread this Executor will run on.
|
||||
*/
|
||||
JSCExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> messageQueueThread,
|
||||
int workerId,
|
||||
JSCExecutor *owner,
|
||||
std::string scriptURL,
|
||||
std::unordered_map<std::string, std::string> globalObjAsJSON,
|
||||
const folly::dynamic& jscConfig);
|
||||
|
||||
void initOnJSVMThread() throw(JSException);
|
||||
// This method is experimental, and may be modified or removed.
|
||||
Value callFunctionSyncWithValue(
|
||||
@@ -143,30 +113,10 @@ private:
|
||||
void flushQueueImmediate(Value&&);
|
||||
void loadModule(uint32_t moduleId);
|
||||
|
||||
int addWebWorker(std::string scriptURL, JSValueRef workerRef, JSValueRef globalObjRef);
|
||||
void postMessageToOwnedWebWorker(int worker, JSValueRef message);
|
||||
void postMessageToOwner(JSValueRef result);
|
||||
void receiveMessageFromOwnedWebWorker(int workerId, const std::string& message);
|
||||
void receiveMessageFromOwner(const std::string &msgString);
|
||||
void terminateOwnedWebWorker(int worker);
|
||||
Object createMessageObject(const std::string& msgData);
|
||||
|
||||
template<JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])>
|
||||
void installNativeHook(const char* name);
|
||||
JSValueRef getNativeModule(JSObjectRef object, JSStringRef propertyName);
|
||||
|
||||
JSValueRef nativeStartWorker(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]);
|
||||
JSValueRef nativePostMessageToWorker(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]);
|
||||
JSValueRef nativeTerminateWorker(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]);
|
||||
JSValueRef nativePostMessage(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]);
|
||||
JSValueRef nativeRequire(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]);
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "JSCWebWorker.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <folly/Memory.h>
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include <jschelpers/JSCHelpers.h>
|
||||
#include <jschelpers/Value.h>
|
||||
|
||||
#include "MessageQueueThread.h"
|
||||
#include "Platform.h"
|
||||
#include "JSCUtils.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
// TODO(9604425): thread safety
|
||||
static std::unordered_map<JSContextRef, JSCWebWorker*> s_globalContextRefToJSCWebWorker;
|
||||
|
||||
JSCWebWorker::JSCWebWorker(int id, JSCWebWorkerOwner *owner, std::string scriptSrc) :
|
||||
id_(id),
|
||||
scriptName_(std::move(scriptSrc)),
|
||||
owner_(owner) {
|
||||
ownerMessageQueueThread_ = owner->getMessageQueueThread();
|
||||
CHECK(ownerMessageQueueThread_) << "Owner MessageQueue must not be null";
|
||||
workerMessageQueueThread_ = WebWorkerUtil::createWebWorkerThread(id, ownerMessageQueueThread_.get());
|
||||
CHECK(workerMessageQueueThread_) << "Failed to create worker thread";
|
||||
|
||||
workerMessageQueueThread_->runOnQueue([this] () {
|
||||
initJSVMAndLoadScript();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
JSCWebWorker::~JSCWebWorker() {
|
||||
CHECK(isTerminated()) << "Didn't terminate the web worker before releasing it!";;
|
||||
}
|
||||
|
||||
void JSCWebWorker::postMessage(JSValueRef msg) {
|
||||
std::string msgString = Value(owner_->getContext(), msg).toJSONString();
|
||||
|
||||
workerMessageQueueThread_->runOnQueue([this, msgString] () {
|
||||
if (isTerminated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSValueRef args[] = { createMessageObject(context_, msgString) };
|
||||
Value onmessageValue = Object::getGlobalObject(context_).getProperty("onmessage");
|
||||
onmessageValue.asObject().callAsFunction(1, args);
|
||||
});
|
||||
}
|
||||
|
||||
void JSCWebWorker::terminate() {
|
||||
if (isTerminated()) {
|
||||
return;
|
||||
}
|
||||
isTerminated_.store(true, std::memory_order_release);
|
||||
|
||||
workerMessageQueueThread_->runOnQueueSync([this] {
|
||||
terminateOnWorkerThread();
|
||||
});
|
||||
}
|
||||
|
||||
void JSCWebWorker::terminateOnWorkerThread() {
|
||||
s_globalContextRefToJSCWebWorker.erase(context_);
|
||||
JSC_JSGlobalContextRelease(context_);
|
||||
context_ = nullptr;
|
||||
workerMessageQueueThread_->quitSynchronous();
|
||||
}
|
||||
|
||||
bool JSCWebWorker::isTerminated() {
|
||||
return isTerminated_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void JSCWebWorker::initJSVMAndLoadScript() {
|
||||
CHECK(!isTerminated()) << "Worker was already finished!";
|
||||
CHECK(!context_) << "Worker JS VM was already created!";
|
||||
|
||||
context_ = JSC_JSGlobalContextCreateInGroup(
|
||||
false, // no support required for custom JSC
|
||||
NULL, // use default JS 'global' object
|
||||
NULL // create new group (i.e. new VM)
|
||||
);
|
||||
s_globalContextRefToJSCWebWorker[context_] = this;
|
||||
|
||||
// TODO(9604438): Protect against script does not exist
|
||||
std::unique_ptr<const JSBigString> script = WebWorkerUtil::loadScriptFromAssets(scriptName_);
|
||||
evaluateScript(context_, jsStringFromBigString(context_, *script), String(context_, scriptName_.c_str()));
|
||||
|
||||
installGlobalFunction(context_, "postMessage", nativePostMessage);
|
||||
}
|
||||
|
||||
void JSCWebWorker::postMessageToOwner(JSValueRef msg) {
|
||||
std::string msgString = Value(context_, msg).toJSONString();
|
||||
ownerMessageQueueThread_->runOnQueue([this, msgString] () {
|
||||
owner_->onMessageReceived(id_, msgString);
|
||||
});
|
||||
}
|
||||
|
||||
JSValueRef JSCWebWorker::nativePostMessage(
|
||||
JSContextRef ctx,
|
||||
JSObjectRef function,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[],
|
||||
JSValueRef *exception) {
|
||||
if (argumentCount != 1) {
|
||||
*exception = Value::makeError(ctx, "postMessage got wrong number of arguments");
|
||||
return Value::makeUndefined(ctx);
|
||||
}
|
||||
JSValueRef msg = arguments[0];
|
||||
JSCWebWorker *webWorker = s_globalContextRefToJSCWebWorker.at(JSC_JSContextGetGlobalContext(ctx));
|
||||
|
||||
if (!webWorker->isTerminated()) {
|
||||
webWorker->postMessageToOwner(msg);
|
||||
}
|
||||
|
||||
return Value::makeUndefined(ctx);
|
||||
}
|
||||
|
||||
/*static*/
|
||||
Object JSCWebWorker::createMessageObject(JSContextRef context, const std::string& msgJson) {
|
||||
Value rebornJSMsg = Value::fromJSON(context, String(context, msgJson.c_str()));
|
||||
Object messageObject = Object::create(context);
|
||||
messageObject.setProperty("data", rebornJSMsg);
|
||||
return messageObject;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
|
||||
#include <jschelpers/JavaScriptCore.h>
|
||||
#include <jschelpers/Value.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class MessageQueueThread;
|
||||
|
||||
/**
|
||||
* A class that can own the lifecycle, receive messages from, and dispatch messages
|
||||
* to JSCWebWorkers.
|
||||
*/
|
||||
class JSCWebWorkerOwner {
|
||||
public:
|
||||
/**
|
||||
* Called when a worker has posted a message with `postMessage`.
|
||||
*/
|
||||
virtual void onMessageReceived(int workerId, const std::string& message) = 0;
|
||||
virtual JSGlobalContextRef getContext() = 0;
|
||||
|
||||
/**
|
||||
* Should return the owner's MessageQueueThread. Calls to onMessageReceived will be enqueued
|
||||
* on this thread.
|
||||
*/
|
||||
virtual std::shared_ptr<MessageQueueThread> getMessageQueueThread() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of a web worker for JSC. The web worker should be created from the owner's
|
||||
* (e.g., owning JSCExecutor instance) JS MessageQueueThread. The worker is responsible for
|
||||
* creating its own MessageQueueThread.
|
||||
*
|
||||
* During operation, the JSCExecutor should call postMessage **from its own MessageQueueThread**
|
||||
* to send messages to the worker. The worker will handle enqueueing those messages on its own
|
||||
* MessageQueueThread as appropriate. When the worker has a message to post to the owner, it will
|
||||
* enqueue a call to owner->onMessageReceived on the owner's MessageQueueThread.
|
||||
*/
|
||||
class JSCWebWorker {
|
||||
public:
|
||||
explicit JSCWebWorker(int id, JSCWebWorkerOwner *owner, std::string script);
|
||||
~JSCWebWorker();
|
||||
|
||||
/**
|
||||
* Post a message to be received by the worker on its thread. This must be called from
|
||||
* ownerMessageQueueThread_.
|
||||
*/
|
||||
void postMessage(JSValueRef msg);
|
||||
|
||||
/**
|
||||
* Synchronously quits the current worker and cleans up its VM.
|
||||
*/
|
||||
void terminate();
|
||||
|
||||
/**
|
||||
* Whether terminate() has been called on this worker.
|
||||
*/
|
||||
bool isTerminated();
|
||||
|
||||
static Object createMessageObject(JSContextRef context, const std::string& msgData);
|
||||
private:
|
||||
void initJSVMAndLoadScript();
|
||||
void postRunnableToEventLoop(std::function<void()>&& runnable);
|
||||
void postMessageToOwner(JSValueRef result);
|
||||
void terminateOnWorkerThread();
|
||||
|
||||
int id_;
|
||||
std::atomic_bool isTerminated_ = ATOMIC_VAR_INIT(false);
|
||||
std::string scriptName_;
|
||||
JSCWebWorkerOwner *owner_ = nullptr;
|
||||
std::shared_ptr<MessageQueueThread> ownerMessageQueueThread_;
|
||||
std::unique_ptr<MessageQueueThread> workerMessageQueueThread_;
|
||||
JSGlobalContextRef context_ = nullptr;
|
||||
|
||||
static JSValueRef nativePostMessage(
|
||||
JSContextRef ctx,
|
||||
JSObjectRef function,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[],
|
||||
JSValueRef *exception);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -113,8 +113,7 @@ folly::Optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name)
|
||||
}
|
||||
}
|
||||
|
||||
void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId,
|
||||
folly::dynamic&& params, int callId) {
|
||||
void ModuleRegistry::callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId) {
|
||||
if (moduleId >= modules_.size()) {
|
||||
throw std::runtime_error(
|
||||
folly::to<std::string>("moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
|
||||
@@ -126,15 +125,15 @@ void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId
|
||||
}
|
||||
#endif
|
||||
|
||||
modules_[moduleId]->invoke(token, methodId, std::move(params));
|
||||
modules_[moduleId]->invoke(methodId, std::move(params));
|
||||
}
|
||||
|
||||
MethodCallResult ModuleRegistry::callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& params) {
|
||||
MethodCallResult ModuleRegistry::callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params) {
|
||||
if (moduleId >= modules_.size()) {
|
||||
throw std::runtime_error(
|
||||
folly::to<std::string>("moduleId ", moduleId, "out of range [0..", modules_.size(), ")"));
|
||||
}
|
||||
return modules_[moduleId]->callSerializableNativeHook(token, methodId, std::move(params));
|
||||
return modules_[moduleId]->callSerializableNativeHook(methodId, std::move(params));
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <cxxreact/ExecutorToken.h>
|
||||
#include <cxxreact/NativeModule.h>
|
||||
#include <folly/Optional.h>
|
||||
#include <folly/dynamic.h>
|
||||
@@ -36,9 +35,8 @@ class ModuleRegistry {
|
||||
|
||||
folly::Optional<ModuleConfig> getConfig(const std::string& name);
|
||||
|
||||
void callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId,
|
||||
folly::dynamic&& params, int callId);
|
||||
MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args);
|
||||
void callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId);
|
||||
MethodCallResult callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, folly::dynamic&& args);
|
||||
|
||||
private:
|
||||
// This is always populated
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cxxreact/ExecutorToken.h>
|
||||
#include <folly/dynamic.h>
|
||||
#include <cxxreact/Executor.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
@@ -27,11 +27,10 @@ class NativeModule {
|
||||
virtual std::string getName() = 0;
|
||||
virtual std::vector<MethodDescriptor> getMethods() = 0;
|
||||
virtual folly::dynamic getConstants() = 0;
|
||||
virtual bool supportsWebWorkers() = 0;
|
||||
// TODO mhorowitz: do we need initialize()/onCatalystInstanceDestroy() in C++
|
||||
// or only Java?
|
||||
virtual void invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) = 0;
|
||||
virtual MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& args) = 0;
|
||||
virtual void invoke(unsigned int reactMethodId, folly::dynamic&& params) = 0;
|
||||
virtual MethodCallResult callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic&& args) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -22,23 +22,11 @@ namespace react {
|
||||
// This class manages calls from JS to native code.
|
||||
class JsToNativeBridge : public react::ExecutorDelegate {
|
||||
public:
|
||||
JsToNativeBridge(NativeToJsBridge* nativeToJs,
|
||||
std::shared_ptr<ModuleRegistry> registry,
|
||||
JsToNativeBridge(std::shared_ptr<ModuleRegistry> registry,
|
||||
std::shared_ptr<InstanceCallback> callback)
|
||||
: m_nativeToJs(nativeToJs)
|
||||
, m_registry(registry)
|
||||
: m_registry(registry)
|
||||
, m_callback(callback) {}
|
||||
|
||||
void registerExecutor(std::unique_ptr<JSExecutor> executor,
|
||||
std::shared_ptr<MessageQueueThread> queue) override {
|
||||
m_nativeToJs->registerExecutor(m_callback->createExecutorToken(), std::move(executor), queue);
|
||||
}
|
||||
|
||||
std::unique_ptr<JSExecutor> unregisterExecutor(JSExecutor& executor) override {
|
||||
m_callback->onExecutorStopped(m_nativeToJs->getTokenForExecutor(executor));
|
||||
return m_nativeToJs->unregisterExecutor(executor);
|
||||
}
|
||||
|
||||
std::shared_ptr<ModuleRegistry> getModuleRegistry() override {
|
||||
return m_registry;
|
||||
}
|
||||
@@ -48,15 +36,13 @@ public:
|
||||
|
||||
CHECK(m_registry || calls.empty()) <<
|
||||
"native module calls cannot be completed with no native modules";
|
||||
ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
|
||||
m_batchHadNativeModuleCalls = m_batchHadNativeModuleCalls || !calls.empty();
|
||||
|
||||
// An exception anywhere in here stops processing of the batch. This
|
||||
// was the behavior of the Android bridge, and since exception handling
|
||||
// terminates the whole bridge, there's not much point in continuing.
|
||||
for (auto& call : parseMethodCalls(std::move(calls))) {
|
||||
m_registry->callNativeMethod(
|
||||
token, call.moduleId, call.methodId, std::move(call.arguments), call.callId);
|
||||
m_registry->callNativeMethod(call.moduleId, call.methodId, std::move(call.arguments), call.callId);
|
||||
}
|
||||
if (isEndOfBatch) {
|
||||
// onBatchComplete will be called on the native (module) queue, but
|
||||
@@ -73,18 +59,14 @@ public:
|
||||
MethodCallResult callSerializableNativeHook(
|
||||
JSExecutor& executor, unsigned int moduleId, unsigned int methodId,
|
||||
folly::dynamic&& args) override {
|
||||
ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
|
||||
return m_registry->callSerializableNativeHook(token, moduleId, methodId, std::move(args));
|
||||
return m_registry->callSerializableNativeHook(moduleId, methodId, std::move(args));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// These methods are always invoked from an Executor. The NativeToJsBridge
|
||||
// keeps a reference to the root executor, and when destroy() is
|
||||
// called, the Executors are all destroyed synchronously on their
|
||||
// bridges. So, the bridge pointer will will always point to a
|
||||
// valid object during a call to a delegate method from an exectuto.
|
||||
NativeToJsBridge* m_nativeToJs;
|
||||
// keeps a reference to the executor, and when destroy() is called, the
|
||||
// executor is destroyed synchronously on its queue.
|
||||
std::shared_ptr<ModuleRegistry> m_registry;
|
||||
std::shared_ptr<InstanceCallback> m_callback;
|
||||
bool m_batchHadNativeModuleCalls = false;
|
||||
@@ -96,14 +78,9 @@ NativeToJsBridge::NativeToJsBridge(
|
||||
std::shared_ptr<MessageQueueThread> jsQueue,
|
||||
std::shared_ptr<InstanceCallback> callback)
|
||||
: m_destroyed(std::make_shared<bool>(false))
|
||||
, m_mainExecutorToken(callback->createExecutorToken())
|
||||
, m_delegate(std::make_shared<JsToNativeBridge>(this, registry, callback)) {
|
||||
std::unique_ptr<JSExecutor> mainExecutor =
|
||||
jsExecutorFactory->createJSExecutor(m_delegate, jsQueue);
|
||||
// cached to avoid locked map lookup in the common case
|
||||
m_mainExecutor = mainExecutor.get();
|
||||
registerExecutor(m_mainExecutorToken, std::move(mainExecutor), jsQueue);
|
||||
}
|
||||
, m_delegate(std::make_shared<JsToNativeBridge>(registry, callback))
|
||||
, m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue))
|
||||
, m_executorMessageQueueThread(std::move(jsQueue)) {}
|
||||
|
||||
// This must be called on the same thread on which the constructor was called.
|
||||
NativeToJsBridge::~NativeToJsBridge() {
|
||||
@@ -116,7 +93,6 @@ void NativeToJsBridge::loadApplication(
|
||||
std::unique_ptr<const JSBigString> startupScript,
|
||||
std::string startupScriptSourceURL) {
|
||||
runOnExecutorQueue(
|
||||
m_mainExecutorToken,
|
||||
[unbundleWrap=folly::makeMoveWrapper(std::move(unbundle)),
|
||||
startupScript=folly::makeMoveWrapper(std::move(startupScript)),
|
||||
startupScriptSourceURL=std::move(startupScriptSourceURL)]
|
||||
@@ -135,14 +111,13 @@ void NativeToJsBridge::loadApplicationSync(
|
||||
std::unique_ptr<const JSBigString> startupScript,
|
||||
std::string startupScriptSourceURL) {
|
||||
if (unbundle) {
|
||||
m_mainExecutor->setJSModulesUnbundle(std::move(unbundle));
|
||||
m_executor->setJSModulesUnbundle(std::move(unbundle));
|
||||
}
|
||||
m_mainExecutor->loadApplicationScript(std::move(startupScript),
|
||||
m_executor->loadApplicationScript(std::move(startupScript),
|
||||
std::move(startupScriptSourceURL));
|
||||
}
|
||||
|
||||
void NativeToJsBridge::callFunction(
|
||||
ExecutorToken executorToken,
|
||||
std::string&& module,
|
||||
std::string&& method,
|
||||
folly::dynamic&& arguments) {
|
||||
@@ -160,23 +135,24 @@ void NativeToJsBridge::callFunction(
|
||||
std::string tracingName;
|
||||
#endif
|
||||
|
||||
runOnExecutorQueue(executorToken, [module = std::move(module), method = std::move(method), arguments = std::move(arguments), tracingName = std::move(tracingName), systraceCookie] (JSExecutor* executor) {
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
FbSystraceAsyncFlow::end(
|
||||
TRACE_TAG_REACT_CXX_BRIDGE,
|
||||
tracingName.c_str(),
|
||||
systraceCookie);
|
||||
SystraceSection s(tracingName.c_str());
|
||||
#endif
|
||||
runOnExecutorQueue([module = std::move(module), method = std::move(method), arguments = std::move(arguments), tracingName = std::move(tracingName), systraceCookie]
|
||||
(JSExecutor* executor) {
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
FbSystraceAsyncFlow::end(
|
||||
TRACE_TAG_REACT_CXX_BRIDGE,
|
||||
tracingName.c_str(),
|
||||
systraceCookie);
|
||||
SystraceSection s(tracingName.c_str());
|
||||
#endif
|
||||
|
||||
// This is safe because we are running on the executor's thread: it won't
|
||||
// destruct until after it's been unregistered (which we check above) and
|
||||
// that will happen on this thread
|
||||
executor->callFunction(module, method, arguments);
|
||||
});
|
||||
// This is safe because we are running on the executor's thread: it won't
|
||||
// destruct until after it's been unregistered (which we check above) and
|
||||
// that will happen on this thread
|
||||
executor->callFunction(module, method, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::invokeCallback(ExecutorToken executorToken, double callbackId, folly::dynamic&& arguments) {
|
||||
void NativeToJsBridge::invokeCallback(double callbackId, folly::dynamic&& arguments) {
|
||||
int systraceCookie = -1;
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
systraceCookie = m_systraceCookie++;
|
||||
@@ -186,24 +162,23 @@ void NativeToJsBridge::invokeCallback(ExecutorToken executorToken, double callba
|
||||
systraceCookie);
|
||||
#endif
|
||||
|
||||
runOnExecutorQueue(executorToken, [callbackId, arguments = std::move(arguments), systraceCookie] (JSExecutor* executor) {
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
FbSystraceAsyncFlow::end(
|
||||
TRACE_TAG_REACT_CXX_BRIDGE,
|
||||
"<callback>",
|
||||
systraceCookie);
|
||||
SystraceSection s("NativeToJsBridge.invokeCallback");
|
||||
#endif
|
||||
runOnExecutorQueue([callbackId, arguments = std::move(arguments), systraceCookie]
|
||||
(JSExecutor* executor) {
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
FbSystraceAsyncFlow::end(
|
||||
TRACE_TAG_REACT_CXX_BRIDGE,
|
||||
"<callback>",
|
||||
systraceCookie);
|
||||
SystraceSection s("NativeToJsBridge.invokeCallback");
|
||||
#endif
|
||||
|
||||
executor->invokeCallback(callbackId, arguments);
|
||||
});
|
||||
executor->invokeCallback(callbackId, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::setGlobalVariable(std::string propName,
|
||||
std::unique_ptr<const JSBigString> jsonValue) {
|
||||
runOnExecutorQueue(
|
||||
m_mainExecutorToken,
|
||||
[propName=std::move(propName), jsonValue=folly::makeMoveWrapper(std::move(jsonValue))]
|
||||
runOnExecutorQueue([propName=std::move(propName), jsonValue=folly::makeMoveWrapper(std::move(jsonValue))]
|
||||
(JSExecutor* executor) mutable {
|
||||
executor->setGlobalVariable(propName, jsonValue.move());
|
||||
});
|
||||
@@ -211,149 +186,72 @@ void NativeToJsBridge::setGlobalVariable(std::string propName,
|
||||
|
||||
void* NativeToJsBridge::getJavaScriptContext() {
|
||||
// TODO(cjhopman): this seems unsafe unless we require that it is only called on the main js queue.
|
||||
return m_mainExecutor->getJavaScriptContext();
|
||||
return m_executor->getJavaScriptContext();
|
||||
}
|
||||
|
||||
bool NativeToJsBridge::supportsProfiling() {
|
||||
// Intentionally doesn't post to jsqueue. supportsProfiling() can be called from any thread.
|
||||
return m_mainExecutor->supportsProfiling();
|
||||
return m_executor->supportsProfiling();
|
||||
}
|
||||
|
||||
void NativeToJsBridge::startProfiler(const std::string& title) {
|
||||
runOnExecutorQueue(m_mainExecutorToken, [=] (JSExecutor* executor) {
|
||||
runOnExecutorQueue([=] (JSExecutor* executor) {
|
||||
executor->startProfiler(title);
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::stopProfiler(const std::string& title, const std::string& filename) {
|
||||
runOnExecutorQueue(m_mainExecutorToken, [=] (JSExecutor* executor) {
|
||||
runOnExecutorQueue([=] (JSExecutor* executor) {
|
||||
executor->stopProfiler(title, filename);
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::handleMemoryPressureUiHidden() {
|
||||
runOnExecutorQueue(m_mainExecutorToken, [=] (JSExecutor* executor) {
|
||||
runOnExecutorQueue([=] (JSExecutor* executor) {
|
||||
executor->handleMemoryPressureUiHidden();
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::handleMemoryPressureModerate() {
|
||||
runOnExecutorQueue(m_mainExecutorToken, [=] (JSExecutor* executor) {
|
||||
runOnExecutorQueue([=] (JSExecutor* executor) {
|
||||
executor->handleMemoryPressureModerate();
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::handleMemoryPressureCritical() {
|
||||
runOnExecutorQueue(m_mainExecutorToken, [=] (JSExecutor* executor) {
|
||||
runOnExecutorQueue([=] (JSExecutor* executor) {
|
||||
executor->handleMemoryPressureCritical();
|
||||
});
|
||||
}
|
||||
|
||||
ExecutorToken NativeToJsBridge::getMainExecutorToken() const {
|
||||
return m_mainExecutorToken;
|
||||
}
|
||||
|
||||
ExecutorToken NativeToJsBridge::registerExecutor(
|
||||
ExecutorToken token,
|
||||
std::unique_ptr<JSExecutor> executor,
|
||||
std::shared_ptr<MessageQueueThread> messageQueueThread) {
|
||||
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
|
||||
|
||||
CHECK(m_executorTokenMap.find(executor.get()) == m_executorTokenMap.end())
|
||||
<< "Trying to register an already registered executor!";
|
||||
|
||||
m_executorTokenMap.emplace(executor.get(), token);
|
||||
m_executorMap.emplace(
|
||||
token,
|
||||
ExecutorRegistration(std::move(executor), messageQueueThread));
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
std::unique_ptr<JSExecutor> NativeToJsBridge::unregisterExecutor(JSExecutor& executor) {
|
||||
std::unique_ptr<JSExecutor> ret;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
|
||||
|
||||
auto it = m_executorTokenMap.find(&executor);
|
||||
CHECK(it != m_executorTokenMap.end())
|
||||
<< "Trying to unregister an executor that was never registered!";
|
||||
auto it2 = m_executorMap.find(it->second);
|
||||
ret = std::move(it2->second.executor_);
|
||||
|
||||
m_executorTokenMap.erase(it);
|
||||
m_executorMap.erase(it2);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MessageQueueThread* NativeToJsBridge::getMessageQueueThread(const ExecutorToken& executorToken) {
|
||||
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
|
||||
auto it = m_executorMap.find(executorToken);
|
||||
if (it == m_executorMap.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->second.messageQueueThread_.get();
|
||||
}
|
||||
|
||||
JSExecutor* NativeToJsBridge::getExecutor(const ExecutorToken& executorToken) {
|
||||
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
|
||||
auto it = m_executorMap.find(executorToken);
|
||||
if (it == m_executorMap.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->second.executor_.get();
|
||||
}
|
||||
|
||||
ExecutorToken NativeToJsBridge::getTokenForExecutor(JSExecutor& executor) {
|
||||
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
|
||||
return m_executorTokenMap.at(&executor);
|
||||
}
|
||||
|
||||
void NativeToJsBridge::destroy() {
|
||||
auto* executorMessageQueueThread = getMessageQueueThread(m_mainExecutorToken);
|
||||
// All calls made through runOnExecutorQueue have an early exit if
|
||||
// m_destroyed is true. Setting this before the runOnQueueSync will cause
|
||||
// pending work to be cancelled and we won't have to wait for it.
|
||||
*m_destroyed = true;
|
||||
executorMessageQueueThread->runOnQueueSync([this, executorMessageQueueThread] {
|
||||
m_mainExecutor->destroy();
|
||||
executorMessageQueueThread->quitSynchronous();
|
||||
m_delegate->unregisterExecutor(*m_mainExecutor);
|
||||
m_mainExecutor = nullptr;
|
||||
m_executorMessageQueueThread->runOnQueueSync([this] {
|
||||
m_executor->destroy();
|
||||
m_executorMessageQueueThread->quitSynchronous();
|
||||
m_executor = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::runOnExecutorQueue(ExecutorToken executorToken, std::function<void(JSExecutor*)> task) {
|
||||
void NativeToJsBridge::runOnExecutorQueue(std::function<void(JSExecutor*)> task) {
|
||||
if (*m_destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto executorMessageQueueThread = getMessageQueueThread(executorToken);
|
||||
if (executorMessageQueueThread == nullptr) {
|
||||
LOG(WARNING) << "Dropping JS action for executor that has been unregistered...";
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<bool> isDestroyed = m_destroyed;
|
||||
executorMessageQueueThread->runOnQueue([this, isDestroyed, executorToken, task=std::move(task)] {
|
||||
m_executorMessageQueueThread->runOnQueue([this, isDestroyed, task=std::move(task)] {
|
||||
if (*isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSExecutor *executor = getExecutor(executorToken);
|
||||
if (executor == nullptr) {
|
||||
LOG(WARNING) << "Dropping JS call for executor that has been unregistered...";
|
||||
return;
|
||||
}
|
||||
|
||||
// The executor is guaranteed to be valid for the duration of the task because:
|
||||
// 1. the executor is only destroyed after it is unregistered
|
||||
// 2. the executor is unregistered on this queue
|
||||
// 3. we just confirmed that the executor hasn't been unregistered above
|
||||
task(executor);
|
||||
task(m_executor.get());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include <cxxreact/Executor.h>
|
||||
#include <cxxreact/ExecutorToken.h>
|
||||
#include <cxxreact/JSCExecutor.h>
|
||||
#include <cxxreact/JSModulesUnbundle.h>
|
||||
#include <cxxreact/MessageQueueThread.h>
|
||||
@@ -26,22 +25,9 @@ struct dynamic;
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
struct InstanceCallback;
|
||||
class ModuleRegistry;
|
||||
|
||||
class ExecutorRegistration {
|
||||
public:
|
||||
ExecutorRegistration(
|
||||
std::unique_ptr<JSExecutor> executor,
|
||||
std::shared_ptr<MessageQueueThread> executorMessageQueueThread) :
|
||||
executor_(std::move(executor)),
|
||||
messageQueueThread_(executorMessageQueueThread) {}
|
||||
|
||||
std::unique_ptr<JSExecutor> executor_;
|
||||
std::shared_ptr<MessageQueueThread> messageQueueThread_;
|
||||
};
|
||||
|
||||
class JsToNativeBridge;
|
||||
struct InstanceCallback;
|
||||
|
||||
// This class manages calls from native code to JS. It also manages
|
||||
// executors and their threads. All functions here can be called from
|
||||
@@ -68,16 +54,12 @@ public:
|
||||
* Executes a function with the module ID and method ID and any additional
|
||||
* arguments in JS.
|
||||
*/
|
||||
void callFunction(
|
||||
ExecutorToken executorToken,
|
||||
std::string&& module,
|
||||
std::string&& method,
|
||||
folly::dynamic&& args);
|
||||
void callFunction(std::string&& module, std::string&& method, folly::dynamic&& args);
|
||||
|
||||
/**
|
||||
* Invokes a callback with the cbID, and optional additional arguments in JS.
|
||||
*/
|
||||
void invokeCallback(ExecutorToken executorToken, double callbackId, folly::dynamic&& args);
|
||||
void invokeCallback(double callbackId, folly::dynamic&& args);
|
||||
|
||||
/**
|
||||
* Executes a JS method on the given executor synchronously, returning its
|
||||
@@ -98,10 +80,10 @@ public:
|
||||
" after bridge is destroyed"));
|
||||
}
|
||||
|
||||
JSCExecutor *jscExecutor = dynamic_cast<JSCExecutor*>(m_mainExecutor);
|
||||
JSCExecutor *jscExecutor = dynamic_cast<JSCExecutor*>(m_executor.get());
|
||||
if (!jscExecutor) {
|
||||
throw std::invalid_argument(
|
||||
folly::to<std::string>("Executor type ", typeid(*m_mainExecutor).name(),
|
||||
folly::to<std::string>("Executor type ", typeid(m_executor.get()).name(),
|
||||
" does not support synchronous calls"));
|
||||
}
|
||||
|
||||
@@ -131,56 +113,25 @@ public:
|
||||
void handleMemoryPressureModerate();
|
||||
void handleMemoryPressureCritical();
|
||||
|
||||
/**
|
||||
* Returns the ExecutorToken corresponding to the main JSExecutor.
|
||||
*/
|
||||
ExecutorToken getMainExecutorToken() const;
|
||||
|
||||
/**
|
||||
* Synchronously tears down the bridge and the main executor.
|
||||
*/
|
||||
void destroy();
|
||||
private:
|
||||
/**
|
||||
* Registers the given JSExecutor which runs on the given MessageQueueThread
|
||||
* with the NativeToJsBridge. Part of this registration is transfering
|
||||
* ownership of this JSExecutor to the NativeToJsBridge for the duration of
|
||||
* the registration.
|
||||
*
|
||||
* Returns a ExecutorToken which can be used to refer to this JSExecutor
|
||||
* in the NativeToJsBridge.
|
||||
*/
|
||||
ExecutorToken registerExecutor(
|
||||
ExecutorToken token,
|
||||
std::unique_ptr<JSExecutor> executor,
|
||||
std::shared_ptr<MessageQueueThread> executorMessageQueueThread);
|
||||
|
||||
/**
|
||||
* Unregisters a JSExecutor that was previously registered with this NativeToJsBridge
|
||||
* using registerExecutor.
|
||||
*/
|
||||
std::unique_ptr<JSExecutor> unregisterExecutor(JSExecutor& executorToken);
|
||||
|
||||
void runOnExecutorQueue(ExecutorToken token, std::function<void(JSExecutor*)> task);
|
||||
void runOnExecutorQueue(std::function<void(JSExecutor*)> task);
|
||||
|
||||
// This is used to avoid a race condition where a proxyCallback gets queued
|
||||
// after ~NativeToJsBridge(), on the same thread. In that case, the callback
|
||||
// will try to run the task on m_callback which will have been destroyed
|
||||
// within ~NativeToJsBridge(), thus causing a SIGSEGV.
|
||||
std::shared_ptr<bool> m_destroyed;
|
||||
JSExecutor* m_mainExecutor;
|
||||
ExecutorToken m_mainExecutorToken;
|
||||
std::shared_ptr<JsToNativeBridge> m_delegate;
|
||||
std::unordered_map<JSExecutor*, ExecutorToken> m_executorTokenMap;
|
||||
std::unordered_map<ExecutorToken, ExecutorRegistration> m_executorMap;
|
||||
std::mutex m_registrationMutex;
|
||||
std::unique_ptr<JSExecutor> m_executor;
|
||||
std::shared_ptr<MessageQueueThread> m_executorMessageQueueThread;
|
||||
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
std::atomic_uint_least32_t m_systraceCookie = ATOMIC_VAR_INIT();
|
||||
#endif
|
||||
|
||||
MessageQueueThread* getMessageQueueThread(const ExecutorToken& executorToken);
|
||||
JSExecutor* getExecutor(const ExecutorToken& executorToken);
|
||||
ExecutorToken getTokenForExecutor(JSExecutor& executor);
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
@@ -9,12 +9,6 @@ namespace ReactMarker {
|
||||
LogMarker logMarker;
|
||||
};
|
||||
|
||||
namespace WebWorkerUtil {
|
||||
WebWorkerQueueFactory createWebWorkerThread;
|
||||
LoadScriptFromAssets loadScriptFromAssets;
|
||||
LoadScriptFromNetworkSync loadScriptFromNetworkSync;
|
||||
};
|
||||
|
||||
namespace PerfLogging {
|
||||
InstallNativeHooks installNativeHooks;
|
||||
};
|
||||
|
||||
@@ -27,17 +27,6 @@ using LogMarker = std::function<void(const ReactMarkerId)>;
|
||||
extern LogMarker logMarker;
|
||||
};
|
||||
|
||||
namespace WebWorkerUtil {
|
||||
using WebWorkerQueueFactory = std::function<std::unique_ptr<MessageQueueThread>(int id, MessageQueueThread* ownerMessageQueue)>;
|
||||
extern WebWorkerQueueFactory createWebWorkerThread;
|
||||
|
||||
using LoadScriptFromAssets = std::function<std::unique_ptr<const JSBigString>(const std::string& assetName)>;
|
||||
extern LoadScriptFromAssets loadScriptFromAssets;
|
||||
|
||||
using LoadScriptFromNetworkSync = std::function<std::string(const std::string& url, const std::string& tempfileName)>;
|
||||
extern LoadScriptFromNetworkSync loadScriptFromNetworkSync;
|
||||
};
|
||||
|
||||
namespace PerfLogging {
|
||||
using InstallNativeHooks = std::function<void(JSGlobalContextRef)>;
|
||||
extern InstallNativeHooks installNativeHooks;
|
||||
|
||||
Reference in New Issue
Block a user