Implement incremental module requires
Differential Revision: D3234844 fbshipit-source-id: 24615528ad6a049aad7c2dbb7ce55e8b034c79e7
This commit is contained in:
parent
7f1346bfdd
commit
779314a413
|
@ -1,277 +0,0 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "Bridge.h"
|
||||
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
#include <fbsystrace.h>
|
||||
using fbsystrace::FbSystraceAsyncFlow;
|
||||
#endif
|
||||
|
||||
#include <folly/json.h>
|
||||
#include <folly/Memory.h>
|
||||
#include <folly/MoveWrapper.h>
|
||||
|
||||
#include "Platform.h"
|
||||
#include "SystraceSection.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
Bridge::Bridge(
|
||||
JSExecutorFactory* jsExecutorFactory,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue,
|
||||
std::unique_ptr<ExecutorTokenFactory> executorTokenFactory,
|
||||
std::unique_ptr<BridgeCallback> callback) :
|
||||
m_callback(std::move(callback)),
|
||||
m_destroyed(std::make_shared<bool>(false)),
|
||||
m_executorTokenFactory(std::move(executorTokenFactory)) {
|
||||
std::unique_ptr<JSExecutor> mainExecutor = jsExecutorFactory->createJSExecutor(this, jsQueue);
|
||||
// cached to avoid locked map lookup in the common case
|
||||
m_mainExecutor = mainExecutor.get();
|
||||
m_mainExecutorToken = folly::make_unique<ExecutorToken>(registerExecutor(
|
||||
std::move(mainExecutor), jsQueue));
|
||||
}
|
||||
|
||||
// This must be called on the same thread on which the constructor was called.
|
||||
Bridge::~Bridge() {
|
||||
CHECK(*m_destroyed) << "Bridge::destroy() must be called before deallocating the Bridge!";
|
||||
}
|
||||
|
||||
void Bridge::loadApplicationScript(std::unique_ptr<const JSBigString> script,
|
||||
std::string sourceURL) {
|
||||
// TODO(t11144533): Add assert that we are on the correct thread
|
||||
m_mainExecutor->loadApplicationScript(std::move(script), std::move(sourceURL));
|
||||
}
|
||||
|
||||
void Bridge::loadApplicationUnbundle(
|
||||
std::unique_ptr<JSModulesUnbundle> unbundle,
|
||||
std::unique_ptr<const JSBigString> startupScript,
|
||||
std::string startupScriptSourceURL) {
|
||||
runOnExecutorQueue(
|
||||
*m_mainExecutorToken,
|
||||
[unbundle=folly::makeMoveWrapper(std::move(unbundle)),
|
||||
startupScript=folly::makeMoveWrapper(std::move(startupScript)),
|
||||
startupScriptSourceURL=std::move(startupScriptSourceURL)]
|
||||
(JSExecutor* executor) mutable {
|
||||
|
||||
executor->setJSModulesUnbundle(unbundle.move());
|
||||
executor->loadApplicationScript(std::move(*startupScript),
|
||||
std::move(startupScriptSourceURL));
|
||||
});
|
||||
}
|
||||
|
||||
void Bridge::callFunction(
|
||||
ExecutorToken executorToken,
|
||||
const std::string& moduleId,
|
||||
const std::string& methodId,
|
||||
const folly::dynamic& arguments,
|
||||
const std::string& tracingName) {
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
int systraceCookie = m_systraceCookie++;
|
||||
FbSystraceAsyncFlow::begin(
|
||||
TRACE_TAG_REACT_CXX_BRIDGE,
|
||||
tracingName.c_str(),
|
||||
systraceCookie);
|
||||
#endif
|
||||
|
||||
runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, 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(moduleId, methodId, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
void Bridge::invokeCallback(ExecutorToken executorToken, const double callbackId, const folly::dynamic& arguments) {
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
int systraceCookie = m_systraceCookie++;
|
||||
FbSystraceAsyncFlow::begin(
|
||||
TRACE_TAG_REACT_CXX_BRIDGE,
|
||||
"<callback>",
|
||||
systraceCookie);
|
||||
#endif
|
||||
|
||||
runOnExecutorQueue(executorToken, [callbackId, arguments, systraceCookie] (JSExecutor* executor) {
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
FbSystraceAsyncFlow::end(
|
||||
TRACE_TAG_REACT_CXX_BRIDGE,
|
||||
"<callback>",
|
||||
systraceCookie);
|
||||
SystraceSection s("Bridge.invokeCallback");
|
||||
#endif
|
||||
|
||||
executor->invokeCallback(callbackId, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
void Bridge::setGlobalVariable(std::string propName,
|
||||
std::unique_ptr<const JSBigString> jsonValue) {
|
||||
runOnExecutorQueue(
|
||||
*m_mainExecutorToken,
|
||||
[propName=std::move(propName), jsonValue=folly::makeMoveWrapper(std::move(jsonValue))]
|
||||
(JSExecutor* executor) mutable {
|
||||
executor->setGlobalVariable(propName, jsonValue.move());
|
||||
});
|
||||
}
|
||||
|
||||
void* Bridge::getJavaScriptContext() {
|
||||
// TODO(cjhopman): this seems unsafe unless we require that it is only called on the main js queue.
|
||||
return m_mainExecutor->getJavaScriptContext();
|
||||
}
|
||||
|
||||
bool Bridge::supportsProfiling() {
|
||||
// Intentionally doesn't post to jsqueue. supportsProfiling() can be called from any thread.
|
||||
return m_mainExecutor->supportsProfiling();
|
||||
}
|
||||
|
||||
void Bridge::startProfiler(const std::string& title) {
|
||||
runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
|
||||
executor->startProfiler(title);
|
||||
});
|
||||
}
|
||||
|
||||
void Bridge::stopProfiler(const std::string& title, const std::string& filename) {
|
||||
runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
|
||||
executor->stopProfiler(title, filename);
|
||||
});
|
||||
}
|
||||
|
||||
void Bridge::handleMemoryPressureModerate() {
|
||||
runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
|
||||
executor->handleMemoryPressureModerate();
|
||||
});
|
||||
}
|
||||
|
||||
void Bridge::handleMemoryPressureCritical() {
|
||||
runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
|
||||
executor->handleMemoryPressureCritical();
|
||||
});
|
||||
}
|
||||
|
||||
void Bridge::callNativeModules(JSExecutor& executor, const std::string& callJSON, bool isEndOfBatch) {
|
||||
// This is called by the executor and thus runs on the executor's own queue.
|
||||
// This means that the executor has not yet been unregistered (and we are
|
||||
// guaranteed to be able to get the token).
|
||||
m_callback->onCallNativeModules(getTokenForExecutor(executor), callJSON, isEndOfBatch);
|
||||
}
|
||||
|
||||
MethodCallResult Bridge::callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, const std::string& argsJSON) {
|
||||
return m_callback->callSerializableNativeHook(*m_mainExecutorToken, moduleId, methodId, folly::parseJson(argsJSON));
|
||||
}
|
||||
|
||||
ExecutorToken Bridge::getMainExecutorToken() const {
|
||||
return *m_mainExecutorToken.get();
|
||||
}
|
||||
|
||||
ExecutorToken Bridge::registerExecutor(
|
||||
std::unique_ptr<JSExecutor> executor,
|
||||
std::shared_ptr<MessageQueueThread> messageQueueThread) {
|
||||
auto token = m_executorTokenFactory->createExecutorToken();
|
||||
|
||||
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,
|
||||
folly::make_unique<ExecutorRegistration>(std::move(executor), std::move(messageQueueThread)));
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
std::unique_ptr<JSExecutor> Bridge::unregisterExecutor(ExecutorToken executorToken) {
|
||||
std::unique_ptr<JSExecutor> executor;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
|
||||
|
||||
auto it = m_executorMap.find(executorToken);
|
||||
CHECK(it != m_executorMap.end())
|
||||
<< "Trying to unregister an executor that was never registered!";
|
||||
|
||||
executor = std::move(it->second->executor_);
|
||||
m_executorMap.erase(it);
|
||||
m_executorTokenMap.erase(executor.get());
|
||||
}
|
||||
|
||||
m_callback->onExecutorUnregistered(executorToken);
|
||||
|
||||
return executor;
|
||||
}
|
||||
|
||||
MessageQueueThread* Bridge::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* Bridge::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 Bridge::getTokenForExecutor(JSExecutor& executor) {
|
||||
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
|
||||
return m_executorTokenMap.at(&executor);
|
||||
}
|
||||
|
||||
void Bridge::destroy() {
|
||||
auto executorMessageQueueThread = getMessageQueueThread(*m_mainExecutorToken);
|
||||
executorMessageQueueThread->runOnQueueSync([this, &executorMessageQueueThread] {
|
||||
executorMessageQueueThread->quitSynchronous();
|
||||
*m_destroyed = true;
|
||||
m_mainExecutor = nullptr;
|
||||
std::unique_ptr<JSExecutor> mainExecutor = unregisterExecutor(*m_mainExecutorToken);
|
||||
mainExecutor->destroy();
|
||||
});
|
||||
}
|
||||
|
||||
void Bridge::runOnExecutorQueue(ExecutorToken executorToken, 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)] {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
} }
|
|
@ -7,26 +7,45 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <folly/dynamic.h>
|
||||
|
||||
#include "JSModulesUnbundle.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
struct dynamic;
|
||||
|
||||
}
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class Bridge;
|
||||
class JSExecutor;
|
||||
class MessageQueueThread;
|
||||
|
||||
struct MethodCallResult {
|
||||
folly::dynamic result;
|
||||
bool isUndefined;
|
||||
};
|
||||
|
||||
// This interface describes the delegate interface required by
|
||||
// Executor implementations to call from JS into native code.
|
||||
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::vector<std::string> moduleNames() = 0;
|
||||
virtual folly::dynamic getModuleConfig(const std::string& name) = 0;
|
||||
virtual void callNativeModules(
|
||||
JSExecutor& executor, std::string callJSON, bool isEndOfBatch) = 0;
|
||||
virtual MethodCallResult callSerializableNativeHook(
|
||||
JSExecutor& executor, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args) = 0;
|
||||
};
|
||||
|
||||
class JSExecutorFactory {
|
||||
public:
|
||||
virtual std::unique_ptr<JSExecutor> createJSExecutor(
|
||||
Bridge *bridge, std::shared_ptr<MessageQueueThread> jsQueue) = 0;
|
||||
virtual ~JSExecutorFactory() {};
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue) = 0;
|
||||
virtual ~JSExecutorFactory() {}
|
||||
};
|
||||
|
||||
// JSExecutor functions sometimes take large strings, on the order of
|
||||
|
@ -143,18 +162,18 @@ public:
|
|||
std::unique_ptr<const JSBigString> jsonValue) = 0;
|
||||
virtual void* getJavaScriptContext() {
|
||||
return nullptr;
|
||||
};
|
||||
}
|
||||
virtual bool supportsProfiling() {
|
||||
return false;
|
||||
};
|
||||
virtual void startProfiler(const std::string &titleString) {};
|
||||
virtual void stopProfiler(const std::string &titleString, const std::string &filename) {};
|
||||
virtual void handleMemoryPressureModerate() {};
|
||||
}
|
||||
virtual void startProfiler(const std::string &titleString) {}
|
||||
virtual void stopProfiler(const std::string &titleString, const std::string &filename) {}
|
||||
virtual void handleMemoryPressureModerate() {}
|
||||
virtual void handleMemoryPressureCritical() {
|
||||
handleMemoryPressureModerate();
|
||||
};
|
||||
virtual void destroy() {};
|
||||
virtual ~JSExecutor() {};
|
||||
}
|
||||
virtual void destroy() {}
|
||||
virtual ~JSExecutor() {}
|
||||
};
|
||||
|
||||
} }
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <folly/json.h>
|
||||
#include <folly/Memory.h>
|
||||
#include <folly/MoveWrapper.h>
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
|
@ -19,42 +20,9 @@
|
|||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
namespace {
|
||||
struct ExecutorTokenFactoryImpl : ExecutorTokenFactory {
|
||||
ExecutorTokenFactoryImpl(InstanceCallback* callback): callback_(callback) {}
|
||||
virtual ExecutorToken createExecutorToken() const {
|
||||
return callback_->createExecutorToken();
|
||||
}
|
||||
private:
|
||||
InstanceCallback* callback_;
|
||||
};
|
||||
}
|
||||
|
||||
class Instance::BridgeCallbackImpl : public BridgeCallback {
|
||||
public:
|
||||
explicit BridgeCallbackImpl(Instance* instance) : instance_(instance) {}
|
||||
virtual void onCallNativeModules(
|
||||
ExecutorToken executorToken,
|
||||
const std::string& calls,
|
||||
bool isEndOfBatch) override {
|
||||
instance_->callNativeModules(executorToken, calls, isEndOfBatch);
|
||||
}
|
||||
|
||||
virtual void onExecutorUnregistered(ExecutorToken executorToken) override {
|
||||
// TODO(cjhopman): implement this.
|
||||
}
|
||||
|
||||
virtual MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int hookId, folly::dynamic&& params) override {
|
||||
return instance_->callSerializableNativeHook(token, moduleId, hookId, std::move(params));
|
||||
}
|
||||
private:
|
||||
Instance* instance_;
|
||||
};
|
||||
|
||||
Instance::~Instance() {
|
||||
if (nativeQueue_) {
|
||||
nativeQueue_->quitSynchronous();
|
||||
bridge_->destroy();
|
||||
if (nativeToJsBridge_) {
|
||||
nativeToJsBridge_->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,32 +33,15 @@ void Instance::initializeBridge(
|
|||
std::unique_ptr<MessageQueueThread> nativeQueue,
|
||||
std::shared_ptr<ModuleRegistry> moduleRegistry) {
|
||||
callback_ = std::move(callback);
|
||||
nativeQueue_ = std::move(nativeQueue);
|
||||
jsQueue_ = jsQueue;
|
||||
moduleRegistry_ = moduleRegistry;
|
||||
|
||||
jsQueue_->runOnQueueSync([this, &jsef] {
|
||||
bridge_ = folly::make_unique<Bridge>(
|
||||
jsef.get(), jsQueue_, folly::make_unique<ExecutorTokenFactoryImpl>(callback_.get()), folly::make_unique<BridgeCallbackImpl>(this));
|
||||
jsQueue->runOnQueueSync(
|
||||
[this, &jsef, moduleRegistry, jsQueue,
|
||||
nativeQueue=folly::makeMoveWrapper(std::move(nativeQueue))] () mutable {
|
||||
nativeToJsBridge_ = folly::make_unique<NativeToJsBridge>(
|
||||
jsef.get(), moduleRegistry, jsQueue, nativeQueue.move(), callback_);
|
||||
});
|
||||
SystraceSection s("setBatchedBridgeConfig");
|
||||
|
||||
CHECK(bridge_);
|
||||
|
||||
folly::dynamic nativeModuleDescriptions = folly::dynamic::array();
|
||||
{
|
||||
SystraceSection s("collectNativeModuleDescriptions");
|
||||
nativeModuleDescriptions = moduleRegistry_->moduleDescriptions();
|
||||
}
|
||||
|
||||
folly::dynamic config =
|
||||
folly::dynamic::object
|
||||
("remoteModuleConfig", std::move(nativeModuleDescriptions));
|
||||
|
||||
SystraceSection t("setGlobalVariable");
|
||||
setGlobalVariable(
|
||||
"__fbBatchedBridgeConfig",
|
||||
folly::make_unique<JSBigStdString>(folly::toJson(config)));
|
||||
CHECK(nativeToJsBridge_);
|
||||
}
|
||||
|
||||
void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
|
||||
|
@ -99,7 +50,7 @@ void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
|
|||
SystraceSection s("reactbridge_xplat_loadScriptFromString",
|
||||
"sourceURL", sourceURL);
|
||||
// TODO mhorowitz: ReactMarker around loadApplicationScript
|
||||
bridge_->loadApplicationScript(std::move(string), std::move(sourceURL));
|
||||
nativeToJsBridge_->loadApplicationScript(std::move(string), std::move(sourceURL));
|
||||
}
|
||||
|
||||
void Instance::loadScriptFromFile(const std::string& filename,
|
||||
|
@ -129,71 +80,42 @@ void Instance::loadUnbundle(std::unique_ptr<JSModulesUnbundle> unbundle,
|
|||
std::string startupScriptSourceURL) {
|
||||
callback_->incrementPendingJSCalls();
|
||||
SystraceSection s("reactbridge_xplat_setJSModulesUnbundle");
|
||||
bridge_->loadApplicationUnbundle(std::move(unbundle), std::move(startupScript),
|
||||
nativeToJsBridge_->loadApplicationUnbundle(std::move(unbundle), std::move(startupScript),
|
||||
std::move(startupScriptSourceURL));
|
||||
}
|
||||
|
||||
bool Instance::supportsProfiling() {
|
||||
return bridge_->supportsProfiling();
|
||||
return nativeToJsBridge_->supportsProfiling();
|
||||
}
|
||||
|
||||
void Instance::startProfiler(const std::string& title) {
|
||||
return bridge_->startProfiler(title);
|
||||
return nativeToJsBridge_->startProfiler(title);
|
||||
}
|
||||
|
||||
void Instance::stopProfiler(const std::string& title, const std::string& filename) {
|
||||
return bridge_->stopProfiler(title, filename);
|
||||
return nativeToJsBridge_->stopProfiler(title, filename);
|
||||
}
|
||||
|
||||
void Instance::setGlobalVariable(std::string propName,
|
||||
std::unique_ptr<const JSBigString> jsonValue) {
|
||||
bridge_->setGlobalVariable(std::move(propName), std::move(jsonValue));
|
||||
nativeToJsBridge_->setGlobalVariable(std::move(propName), std::move(jsonValue));
|
||||
}
|
||||
|
||||
void Instance::callJSFunction(ExecutorToken token, const std::string& module, const std::string& method,
|
||||
folly::dynamic&& params, const std::string& tracingName) {
|
||||
SystraceSection s(tracingName.c_str());
|
||||
callback_->incrementPendingJSCalls();
|
||||
bridge_->callFunction(token, module, method, std::move(params), tracingName);
|
||||
nativeToJsBridge_->callFunction(token, module, method, std::move(params), tracingName);
|
||||
}
|
||||
|
||||
void Instance::callJSCallback(ExecutorToken token, uint64_t callbackId, folly::dynamic&& params) {
|
||||
SystraceSection s("<callback>");
|
||||
callback_->incrementPendingJSCalls();
|
||||
bridge_->invokeCallback(token, (double) callbackId, std::move(params));
|
||||
nativeToJsBridge_->invokeCallback(token, (double) callbackId, std::move(params));
|
||||
}
|
||||
|
||||
ExecutorToken Instance::getMainExecutorToken() {
|
||||
return bridge_->getMainExecutorToken();
|
||||
}
|
||||
|
||||
void Instance::callNativeModules(ExecutorToken token, const std::string& calls, bool isEndOfBatch) {
|
||||
// TODO mhorowitz: avoid copying calls here.
|
||||
nativeQueue_->runOnQueue([this, token, calls, isEndOfBatch] {
|
||||
try {
|
||||
// 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 : react::parseMethodCalls(calls)) {
|
||||
moduleRegistry_->callNativeMethod(
|
||||
token, call.moduleId, call.methodId, std::move(call.arguments), call.callId);
|
||||
}
|
||||
if (isEndOfBatch) {
|
||||
callback_->onBatchComplete();
|
||||
callback_->decrementPendingJSCalls();
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG(ERROR) << folly::exceptionStr(e).toStdString();
|
||||
callback_->onNativeException(folly::exceptionStr(e).toStdString());
|
||||
} catch (...) {
|
||||
LOG(ERROR) << "Unknown exception";
|
||||
callback_->onNativeException("Unknown exception");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MethodCallResult Instance::callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& params) {
|
||||
return moduleRegistry_->callSerializableNativeHook(token, moduleId, methodId, std::move(params));
|
||||
return nativeToJsBridge_->getMainExecutorToken();
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#include <folly/dynamic.h>
|
||||
|
||||
#include "Bridge.h"
|
||||
#include "NativeToJsBridge.h"
|
||||
#include "ModuleRegistry.h"
|
||||
#include "NativeModule.h"
|
||||
|
||||
|
@ -22,6 +22,7 @@ struct InstanceCallback {
|
|||
virtual void decrementPendingJSCalls() = 0;
|
||||
virtual void onNativeException(const std::string& what) = 0;
|
||||
virtual ExecutorToken createExecutorToken() = 0;
|
||||
virtual void onExecutorStopped(ExecutorToken) = 0;
|
||||
};
|
||||
|
||||
class Instance {
|
||||
|
@ -46,21 +47,15 @@ class Instance {
|
|||
void callJSFunction(ExecutorToken token, const std::string& module, const std::string& method,
|
||||
folly::dynamic&& params, const std::string& tracingName);
|
||||
void callJSCallback(ExecutorToken token, uint64_t callbackId, folly::dynamic&& params);
|
||||
MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args);
|
||||
MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId,
|
||||
unsigned int methodId, folly::dynamic&& args);
|
||||
ExecutorToken getMainExecutorToken();
|
||||
|
||||
private:
|
||||
class BridgeCallbackImpl;
|
||||
|
||||
void callNativeModules(ExecutorToken token, const std::string& calls, bool isEndOfBatch);
|
||||
|
||||
std::unique_ptr<InstanceCallback> callback_;
|
||||
std::shared_ptr<ModuleRegistry> moduleRegistry_;
|
||||
// TODO #10487027: clean up the ownership of this.
|
||||
std::shared_ptr<MessageQueueThread> jsQueue_;
|
||||
std::unique_ptr<MessageQueueThread> nativeQueue_;
|
||||
|
||||
std::unique_ptr<Bridge> bridge_;
|
||||
std::shared_ptr<InstanceCallback> callback_;
|
||||
std::unique_ptr<NativeToJsBridge> nativeToJsBridge_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
#include <folly/Conv.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "Bridge.h"
|
||||
#include "JSCHelpers.h"
|
||||
#include "Platform.h"
|
||||
#include "SystraceSection.h"
|
||||
|
@ -107,29 +106,51 @@ static std::string executeJSCallWithJSC(
|
|||
}
|
||||
|
||||
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
|
||||
Bridge *bridge, std::shared_ptr<MessageQueueThread> jsQueue) {
|
||||
std::shared_ptr<ExecutorDelegate> delegate, std::shared_ptr<MessageQueueThread> jsQueue) {
|
||||
return std::unique_ptr<JSExecutor>(
|
||||
new JSCExecutor(bridge, jsQueue, cacheDir_, m_jscConfig));
|
||||
new JSCExecutor(delegate, jsQueue, m_cacheDir, m_jscConfig));
|
||||
}
|
||||
|
||||
JSCExecutor::JSCExecutor(Bridge *bridge, std::shared_ptr<MessageQueueThread> messageQueueThread,
|
||||
const std::string& cacheDir, const folly::dynamic& jscConfig) :
|
||||
m_bridge(bridge),
|
||||
JSCExecutor::JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> messageQueueThread,
|
||||
const std::string& cacheDir,
|
||||
const folly::dynamic& jscConfig) :
|
||||
m_delegate(delegate),
|
||||
m_deviceCacheDir(cacheDir),
|
||||
m_messageQueueThread(messageQueueThread),
|
||||
m_jscConfig(jscConfig) {
|
||||
initOnJSVMThread();
|
||||
|
||||
SystraceSection s("setBatchedBridgeConfig");
|
||||
|
||||
folly::dynamic nativeModuleConfig = folly::dynamic::array();
|
||||
|
||||
{
|
||||
SystraceSection s("collectNativeModuleNames");
|
||||
std::vector<std::string> names = delegate->moduleNames();
|
||||
for (auto& name : delegate->moduleNames()) {
|
||||
nativeModuleConfig.push_back(folly::dynamic::array(std::move(name)));
|
||||
}
|
||||
}
|
||||
|
||||
folly::dynamic config =
|
||||
folly::dynamic::object("remoteModuleConfig", std::move(nativeModuleConfig));
|
||||
|
||||
SystraceSection t("setGlobalVariable");
|
||||
setGlobalVariable(
|
||||
"__fbBatchedBridgeConfig",
|
||||
folly::make_unique<JSBigStdString>(folly::toJson(config)));
|
||||
}
|
||||
|
||||
JSCExecutor::JSCExecutor(
|
||||
Bridge *bridge,
|
||||
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_bridge(bridge),
|
||||
m_delegate(delegate),
|
||||
m_workerId(workerId),
|
||||
m_owner(owner),
|
||||
m_deviceCacheDir(owner->m_deviceCacheDir),
|
||||
|
@ -198,6 +219,7 @@ void JSCExecutor::initOnJSVMThread() {
|
|||
// Add a pointer to ourselves so we can retrieve it later in our hooks
|
||||
JSObjectSetPrivate(JSContextGetGlobalObject(m_context), this);
|
||||
|
||||
installNativeHook<&JSCExecutor::nativeRequireModuleConfig>("nativeRequireModuleConfig");
|
||||
installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
|
||||
installNativeHook<&JSCExecutor::nativeStartWorker>("nativeStartWorker");
|
||||
installNativeHook<&JSCExecutor::nativePostMessageToWorker>("nativePostMessageToWorker");
|
||||
|
@ -280,7 +302,7 @@ void JSCExecutor::setJSModulesUnbundle(std::unique_ptr<JSModulesUnbundle> unbund
|
|||
void JSCExecutor::flush() {
|
||||
// TODO: Make this a first class function instead of evaling. #9317773
|
||||
std::string calls = executeJSCallWithJSC(m_context, "flushedQueue", std::vector<folly::dynamic>());
|
||||
m_bridge->callNativeModules(*this, calls, true);
|
||||
m_delegate->callNativeModules(*this, std::move(calls), true);
|
||||
}
|
||||
|
||||
void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
|
||||
|
@ -291,7 +313,7 @@ void JSCExecutor::callFunction(const std::string& moduleId, const std::string& m
|
|||
std::move(arguments),
|
||||
};
|
||||
std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
|
||||
m_bridge->callNativeModules(*this, calls, true);
|
||||
m_delegate->callNativeModules(*this, std::move(calls), true);
|
||||
}
|
||||
|
||||
void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
|
||||
|
@ -301,7 +323,7 @@ void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic&
|
|||
std::move(arguments)
|
||||
};
|
||||
std::string calls = executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call));
|
||||
m_bridge->callNativeModules(*this, calls, true);
|
||||
m_delegate->callNativeModules(*this, std::move(calls), true);
|
||||
}
|
||||
|
||||
void JSCExecutor::setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue) {
|
||||
|
@ -362,7 +384,7 @@ void JSCExecutor::handleMemoryPressureCritical() {
|
|||
}
|
||||
|
||||
void JSCExecutor::flushQueueImmediate(std::string queueJSON) {
|
||||
m_bridge->callNativeModules(*this, queueJSON, false);
|
||||
m_delegate->callNativeModules(*this, std::move(queueJSON), false);
|
||||
}
|
||||
|
||||
void JSCExecutor::loadModule(uint32_t moduleId) {
|
||||
|
@ -388,7 +410,7 @@ int JSCExecutor::addWebWorker(
|
|||
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_bridge, workerMQT, workerId, this, scriptURL,
|
||||
worker.reset(new JSCExecutor(m_delegate, workerMQT, workerId, this, std::move(scriptURL),
|
||||
globalObj.toJSONMap(), workerJscConfig));
|
||||
});
|
||||
|
||||
|
@ -397,14 +419,14 @@ int JSCExecutor::addWebWorker(
|
|||
|
||||
JSCExecutor *workerPtr = worker.get();
|
||||
std::shared_ptr<MessageQueueThread> sharedMessageQueueThread = worker->m_messageQueueThread;
|
||||
ExecutorToken token = m_bridge->registerExecutor(
|
||||
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, token, std::move(workerObj)));
|
||||
std::forward_as_tuple(workerPtr, std::move(workerObj)));
|
||||
|
||||
return workerId;
|
||||
}
|
||||
|
@ -464,12 +486,11 @@ void JSCExecutor::receiveMessageFromOwner(const std::string& msgString) {
|
|||
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);
|
||||
|
||||
workerMQT->runOnQueueSync([this, workerExecutorToken, &workerMQT] {
|
||||
workerMQT->runOnQueueSync([this, &workerMQT] {
|
||||
workerMQT->quitSynchronous();
|
||||
std::unique_ptr<JSExecutor> worker = m_bridge->unregisterExecutor(workerExecutorToken);
|
||||
std::unique_ptr<JSExecutor> worker = m_delegate->unregisterExecutor(*this);
|
||||
worker->destroy();
|
||||
worker.reset();
|
||||
});
|
||||
|
@ -521,6 +542,18 @@ JSValueRef JSCExecutor::nativeRequire(
|
|||
return JSValueMakeUndefined(m_context);
|
||||
}
|
||||
|
||||
JSValueRef JSCExecutor::nativeRequireModuleConfig(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]) {
|
||||
if (argumentCount != 1) {
|
||||
throw std::invalid_argument("Got wrong number of args");
|
||||
}
|
||||
|
||||
std::string moduleName = Value(m_context, arguments[0]).toString().str();
|
||||
folly::dynamic config = m_delegate->getModuleConfig(moduleName);
|
||||
return JSValueMakeString(m_context, String(folly::toJson(config).c_str()));
|
||||
}
|
||||
|
||||
JSValueRef JSCExecutor::nativeFlushQueueImmediate(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]) {
|
||||
|
@ -529,7 +562,7 @@ JSValueRef JSCExecutor::nativeFlushQueueImmediate(
|
|||
}
|
||||
|
||||
std::string resStr = Value(m_context, arguments[0]).toJSONString();
|
||||
flushQueueImmediate(resStr);
|
||||
flushQueueImmediate(std::move(resStr));
|
||||
return JSValueMakeUndefined(m_context);
|
||||
}
|
||||
|
||||
|
@ -595,7 +628,8 @@ JSValueRef JSCExecutor::nativeCallSyncHook(
|
|||
unsigned int methodId = Value(m_context, arguments[1]).asUnsignedInteger();
|
||||
std::string argsJson = Value(m_context, arguments[2]).toJSONString();
|
||||
|
||||
MethodCallResult result = m_bridge->callSerializableNativeHook(
|
||||
MethodCallResult result = m_delegate->callSerializableNativeHook(
|
||||
*this,
|
||||
moduleId,
|
||||
methodId,
|
||||
argsJson);
|
||||
|
|
|
@ -23,25 +23,24 @@ class MessageQueueThread;
|
|||
class JSCExecutorFactory : public JSExecutorFactory {
|
||||
public:
|
||||
JSCExecutorFactory(const std::string& cacheDir, const folly::dynamic& jscConfig) :
|
||||
cacheDir_(cacheDir),
|
||||
m_cacheDir(cacheDir),
|
||||
m_jscConfig(jscConfig) {}
|
||||
virtual std::unique_ptr<JSExecutor> createJSExecutor(
|
||||
Bridge *bridge, std::shared_ptr<MessageQueueThread> jsQueue) override;
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue) override;
|
||||
private:
|
||||
std::string cacheDir_;
|
||||
std::string m_cacheDir;
|
||||
folly::dynamic m_jscConfig;
|
||||
};
|
||||
|
||||
class JSCExecutor;
|
||||
class WorkerRegistration : public noncopyable {
|
||||
public:
|
||||
explicit WorkerRegistration(JSCExecutor* executor_, ExecutorToken executorToken_, Object jsObj_) :
|
||||
explicit WorkerRegistration(JSCExecutor* executor_, Object jsObj_) :
|
||||
executor(executor_),
|
||||
executorToken(executorToken_),
|
||||
jsObj(std::move(jsObj_)) {}
|
||||
|
||||
JSCExecutor *executor;
|
||||
ExecutorToken executorToken;
|
||||
Object jsObj;
|
||||
};
|
||||
|
||||
|
@ -50,8 +49,10 @@ public:
|
|||
/**
|
||||
* Must be invoked from thread this Executor will run on.
|
||||
*/
|
||||
explicit JSCExecutor(Bridge *bridge, std::shared_ptr<MessageQueueThread> messageQueueThread,
|
||||
const std::string& cacheDir, const folly::dynamic& jscConfig);
|
||||
explicit JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> messageQueueThread,
|
||||
const std::string& cacheDir,
|
||||
const folly::dynamic& jscConfig);
|
||||
~JSCExecutor() override;
|
||||
|
||||
virtual void loadApplicationScript(
|
||||
|
@ -79,7 +80,7 @@ public:
|
|||
|
||||
private:
|
||||
JSGlobalContextRef m_context;
|
||||
Bridge *m_bridge;
|
||||
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));
|
||||
|
@ -93,7 +94,7 @@ private:
|
|||
* WebWorker constructor. Must be invoked from thread this Executor will run on.
|
||||
*/
|
||||
JSCExecutor(
|
||||
Bridge *bridge,
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> messageQueueThread,
|
||||
int workerId,
|
||||
JSCExecutor *owner,
|
||||
|
@ -118,6 +119,9 @@ private:
|
|||
template< JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])>
|
||||
void installNativeHook(const char* name);
|
||||
|
||||
JSValueRef nativeRequireModuleConfig(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]);
|
||||
JSValueRef nativeStartWorker(
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[]);
|
||||
|
|
|
@ -8,45 +8,86 @@
|
|||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string normalizeName(std::string name) {
|
||||
// TODO mhorowitz #10487027: This is super ugly. We should just
|
||||
// change iOS to emit normalized names, drop the "RK..." from
|
||||
// names hardcoded in Android, and then delete this and the
|
||||
// similar hacks in js.
|
||||
if (name.compare(0, 3, "RCT") == 0) {
|
||||
return name.substr(3);
|
||||
} else if (name.compare(0, 2, "RK") == 0) {
|
||||
return name.substr(2);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ModuleRegistry::ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules)
|
||||
: modules_(std::move(modules)) {}
|
||||
|
||||
folly::dynamic ModuleRegistry::moduleDescriptions() {
|
||||
folly::dynamic modDescs = folly::dynamic::object;
|
||||
std::vector<std::string> ModuleRegistry::moduleNames() {
|
||||
std::vector<std::string> names;
|
||||
for (size_t i = 0; i < modules_.size(); i++) {
|
||||
std::string name = normalizeName(modules_[i]->getName());
|
||||
modulesByName_[name] = i;
|
||||
names.push_back(std::move(name));
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
for (size_t moduleId = 0; moduleId < modules_.size(); ++moduleId) {
|
||||
const auto& module = modules_[moduleId];
|
||||
folly::dynamic ModuleRegistry::getConfig(const std::string& name) {
|
||||
auto it = modulesByName_.find(name);
|
||||
if (it == modulesByName_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
CHECK(it->second < modules_.size());
|
||||
|
||||
NativeModule* module = modules_[it->second].get();
|
||||
|
||||
// string name, [object constants,] array methodNames (methodId is index), [array asyncMethodIds]
|
||||
folly::dynamic config = folly::dynamic::array(name);
|
||||
|
||||
folly::dynamic methodDescs = folly::dynamic::object;
|
||||
std::vector<MethodDescriptor> methods;
|
||||
{
|
||||
SystraceSection s("getMethods",
|
||||
"module", module->getName());
|
||||
methods = module->getMethods();
|
||||
SystraceSection s("getConfig constants",
|
||||
"module", name);
|
||||
folly::dynamic constants = module->getConstants();
|
||||
if (constants.isObject() && constants.size() > 0) {
|
||||
config.push_back(std::move(constants));
|
||||
}
|
||||
for (size_t methodId = 0; methodId < methods.size(); ++methodId) {
|
||||
methodDescs.insert(std::move(methods[methodId].name),
|
||||
folly::dynamic::object
|
||||
("methodID", methodId)
|
||||
("type", std::move(methods[methodId].type)));
|
||||
}
|
||||
|
||||
folly::dynamic constants = folly::dynamic::array();
|
||||
{
|
||||
SystraceSection s("getConstants",
|
||||
"module", module->getName());
|
||||
constants = module->getConstants();
|
||||
SystraceSection s("getConfig methods",
|
||||
"module", name);
|
||||
std::vector<MethodDescriptor> methods = module->getMethods();
|
||||
|
||||
folly::dynamic methodNames = folly::dynamic::array;
|
||||
folly::dynamic asyncMethodIds = folly::dynamic::array;
|
||||
|
||||
for (auto& descriptor : methods) {
|
||||
methodNames.push_back(std::move(descriptor.name));
|
||||
if (descriptor.type == "remoteAsync") {
|
||||
asyncMethodIds.push_back(methodNames.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
modDescs.insert(module->getName(),
|
||||
folly::dynamic::object
|
||||
("supportsWebWorkers", module->supportsWebWorkers())
|
||||
("moduleID", moduleId)
|
||||
("methods", std::move(methodDescs))
|
||||
("constants", std::move(constants)));
|
||||
|
||||
if (!methodNames.empty()) {
|
||||
config.push_back(std::move(methodNames));
|
||||
if (!asyncMethodIds.empty()) {
|
||||
config.push_back(std::move(asyncMethodIds));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.size() == 1) {
|
||||
// no constants or methods
|
||||
return nullptr;
|
||||
} else {
|
||||
return config;
|
||||
}
|
||||
return modDescs;
|
||||
}
|
||||
|
||||
void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId,
|
||||
|
@ -75,7 +116,7 @@ void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId
|
|||
// fall through;
|
||||
}
|
||||
|
||||
std::string moduleName = modules_[moduleId]->getName();
|
||||
std::string moduleName = normalizeName(modules_[moduleId]->getName());
|
||||
auto descs = modules_[moduleId]->getMethods();
|
||||
std::string methodName;
|
||||
if (methodId < descs.size()) {
|
||||
|
|
|
@ -25,13 +25,18 @@ class ModuleRegistry {
|
|||
// notifyCatalystInstanceDestroy: use RAII instead
|
||||
|
||||
ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules);
|
||||
folly::dynamic moduleDescriptions();
|
||||
std::vector<std::string> moduleNames();
|
||||
folly::dynamic 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);
|
||||
|
||||
private:
|
||||
// This is always populated
|
||||
std::vector<std::unique_ptr<NativeModule>> modules_;
|
||||
|
||||
// This is only populated if moduleNames() is called. Values are indices into modules_.
|
||||
std::unordered_map<std::string, size_t> modulesByName_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -12,11 +12,6 @@
|
|||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
struct MethodCallResult {
|
||||
folly::dynamic result;
|
||||
bool isUndefined;
|
||||
};
|
||||
|
||||
struct MethodDescriptor {
|
||||
std::string name;
|
||||
// type is one of js MessageQueue.MethodTypes
|
||||
|
|
|
@ -0,0 +1,347 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "NativeToJsBridge.h"
|
||||
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
#include <fbsystrace.h>
|
||||
using fbsystrace::FbSystraceAsyncFlow;
|
||||
#endif
|
||||
|
||||
#include <folly/json.h>
|
||||
#include <folly/Memory.h>
|
||||
#include <folly/MoveWrapper.h>
|
||||
|
||||
#include "Instance.h"
|
||||
#include "ModuleRegistry.h"
|
||||
#include "Platform.h"
|
||||
#include "SystraceSection.h"
|
||||
|
||||
namespace facebook {
|
||||
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,
|
||||
std::unique_ptr<MessageQueueThread> nativeQueue,
|
||||
std::shared_ptr<InstanceCallback> callback)
|
||||
: m_nativeToJs(nativeToJs)
|
||||
, m_registry(registry)
|
||||
, m_nativeQueue(std::move(nativeQueue))
|
||||
, 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::vector<std::string> moduleNames() override {
|
||||
// If this turns out to be too expensive to run on the js thread,
|
||||
// we can compute it in the ctor, and just return std::move() it
|
||||
// here.
|
||||
return m_registry->moduleNames();
|
||||
}
|
||||
|
||||
folly::dynamic getModuleConfig(const std::string& name) override {
|
||||
return m_registry->getConfig(name);
|
||||
}
|
||||
|
||||
void callNativeModules(
|
||||
JSExecutor& executor, std::string callJSON, bool isEndOfBatch) override {
|
||||
ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
|
||||
m_nativeQueue->runOnQueue([this, token, callJSON=std::move(callJSON), isEndOfBatch] {
|
||||
// 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 : react::parseMethodCalls(callJSON)) {
|
||||
m_registry->callNativeMethod(
|
||||
token, call.moduleId, call.methodId, std::move(call.arguments), call.callId);
|
||||
}
|
||||
if (isEndOfBatch) {
|
||||
m_callback->onBatchComplete();
|
||||
m_callback->decrementPendingJSCalls();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void quitQueueSynchronous() {
|
||||
m_nativeQueue->quitSynchronous();
|
||||
}
|
||||
|
||||
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;
|
||||
std::shared_ptr<ModuleRegistry> m_registry;
|
||||
std::unique_ptr<MessageQueueThread> m_nativeQueue;
|
||||
std::shared_ptr<InstanceCallback> m_callback;
|
||||
};
|
||||
|
||||
NativeToJsBridge::NativeToJsBridge(
|
||||
JSExecutorFactory* jsExecutorFactory,
|
||||
std::shared_ptr<ModuleRegistry> registry,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue,
|
||||
std::unique_ptr<MessageQueueThread> nativeQueue,
|
||||
std::shared_ptr<InstanceCallback> callback)
|
||||
: m_destroyed(std::make_shared<bool>(false))
|
||||
, m_mainExecutorToken(callback->createExecutorToken())
|
||||
, m_delegate(
|
||||
std::make_shared<JsToNativeBridge>(
|
||||
this, registry, std::move(nativeQueue), 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);
|
||||
}
|
||||
|
||||
// This must be called on the same thread on which the constructor was called.
|
||||
NativeToJsBridge::~NativeToJsBridge() {
|
||||
CHECK(*m_destroyed) <<
|
||||
"NativeToJsBridge::destroy() must be called before deallocating the NativeToJsBridge!";
|
||||
}
|
||||
|
||||
void NativeToJsBridge::loadApplicationScript(std::unique_ptr<const JSBigString> script,
|
||||
std::string sourceURL) {
|
||||
// TODO(t11144533): Add assert that we are on the correct thread
|
||||
m_mainExecutor->loadApplicationScript(std::move(script), std::move(sourceURL));
|
||||
}
|
||||
|
||||
void NativeToJsBridge::loadApplicationUnbundle(
|
||||
std::unique_ptr<JSModulesUnbundle> unbundle,
|
||||
std::unique_ptr<const JSBigString> startupScript,
|
||||
std::string startupScriptSourceURL) {
|
||||
runOnExecutorQueue(
|
||||
m_mainExecutorToken,
|
||||
[unbundle=folly::makeMoveWrapper(std::move(unbundle)),
|
||||
startupScript=folly::makeMoveWrapper(std::move(startupScript)),
|
||||
startupScriptSourceURL=std::move(startupScriptSourceURL)]
|
||||
(JSExecutor* executor) mutable {
|
||||
|
||||
executor->setJSModulesUnbundle(unbundle.move());
|
||||
executor->loadApplicationScript(std::move(*startupScript),
|
||||
std::move(startupScriptSourceURL));
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::callFunction(
|
||||
ExecutorToken executorToken,
|
||||
const std::string& moduleId,
|
||||
const std::string& methodId,
|
||||
const folly::dynamic& arguments,
|
||||
const std::string& tracingName) {
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
int systraceCookie = m_systraceCookie++;
|
||||
FbSystraceAsyncFlow::begin(
|
||||
TRACE_TAG_REACT_CXX_BRIDGE,
|
||||
tracingName.c_str(),
|
||||
systraceCookie);
|
||||
#endif
|
||||
|
||||
runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, 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(moduleId, methodId, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::invokeCallback(ExecutorToken executorToken, const double callbackId,
|
||||
const folly::dynamic& arguments) {
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
int systraceCookie = m_systraceCookie++;
|
||||
FbSystraceAsyncFlow::begin(
|
||||
TRACE_TAG_REACT_CXX_BRIDGE,
|
||||
"<callback>",
|
||||
systraceCookie);
|
||||
#endif
|
||||
|
||||
runOnExecutorQueue(executorToken, [callbackId, 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);
|
||||
});
|
||||
}
|
||||
|
||||
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))]
|
||||
(JSExecutor* executor) mutable {
|
||||
executor->setGlobalVariable(propName, jsonValue.move());
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
bool NativeToJsBridge::supportsProfiling() {
|
||||
// Intentionally doesn't post to jsqueue. supportsProfiling() can be called from any thread.
|
||||
return m_mainExecutor->supportsProfiling();
|
||||
}
|
||||
|
||||
void NativeToJsBridge::startProfiler(const std::string& title) {
|
||||
runOnExecutorQueue(m_mainExecutorToken, [=] (JSExecutor* executor) {
|
||||
executor->startProfiler(title);
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::stopProfiler(const std::string& title, const std::string& filename) {
|
||||
runOnExecutorQueue(m_mainExecutorToken, [=] (JSExecutor* executor) {
|
||||
executor->stopProfiler(title, filename);
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::handleMemoryPressureModerate() {
|
||||
runOnExecutorQueue(m_mainExecutorToken, [=] (JSExecutor* executor) {
|
||||
executor->handleMemoryPressureModerate();
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::handleMemoryPressureCritical() {
|
||||
runOnExecutorQueue(m_mainExecutorToken, [=] (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() {
|
||||
m_delegate->quitQueueSynchronous();
|
||||
auto executorMessageQueueThread = getMessageQueueThread(m_mainExecutorToken);
|
||||
executorMessageQueueThread->runOnQueueSync([this, &executorMessageQueueThread] {
|
||||
executorMessageQueueThread->quitSynchronous();
|
||||
*m_destroyed = true;
|
||||
std::unique_ptr<JSExecutor> mainExecutor = unregisterExecutor(*m_mainExecutor);
|
||||
m_mainExecutor = nullptr;
|
||||
mainExecutor->destroy();
|
||||
});
|
||||
}
|
||||
|
||||
void NativeToJsBridge::runOnExecutorQueue(ExecutorToken executorToken, 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)] {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
} }
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
#include "Executor.h"
|
||||
#include "ExecutorToken.h"
|
||||
#include "ExecutorTokenFactory.h"
|
||||
#include "JSModulesUnbundle.h"
|
||||
#include "MessageQueueThread.h"
|
||||
#include "MethodCall.h"
|
||||
|
@ -27,21 +26,9 @@ struct dynamic;
|
|||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class BridgeCallback {
|
||||
public:
|
||||
virtual ~BridgeCallback() {};
|
||||
struct InstanceCallback;
|
||||
class ModuleRegistry;
|
||||
|
||||
virtual void onCallNativeModules(
|
||||
ExecutorToken executorToken,
|
||||
const std::string& callJSON,
|
||||
bool isEndOfBatch) = 0;
|
||||
|
||||
virtual void onExecutorUnregistered(ExecutorToken executorToken) = 0;
|
||||
|
||||
virtual MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args) = 0;
|
||||
};
|
||||
|
||||
class Bridge;
|
||||
class ExecutorRegistration {
|
||||
public:
|
||||
ExecutorRegistration(
|
||||
|
@ -54,17 +41,26 @@ public:
|
|||
std::shared_ptr<MessageQueueThread> messageQueueThread_;
|
||||
};
|
||||
|
||||
class Bridge {
|
||||
class JsToNativeBridge;
|
||||
|
||||
// This class manages calls from native code to JS. It also manages
|
||||
// executors and their threads. This part is used by both bridges for
|
||||
// now, but further refactorings should separate the bridges more
|
||||
// fully #11247981.
|
||||
class NativeToJsBridge {
|
||||
public:
|
||||
friend class JsToNativeBridge;
|
||||
|
||||
/**
|
||||
* This must be called on the main JS thread.
|
||||
*/
|
||||
Bridge(
|
||||
NativeToJsBridge(
|
||||
JSExecutorFactory* jsExecutorFactory,
|
||||
std::shared_ptr<ModuleRegistry> registry,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue,
|
||||
std::unique_ptr<ExecutorTokenFactory> executorTokenFactory,
|
||||
std::unique_ptr<BridgeCallback> callback);
|
||||
virtual ~Bridge();
|
||||
std::unique_ptr<MessageQueueThread> nativeQueue,
|
||||
std::shared_ptr<InstanceCallback> callback);
|
||||
virtual ~NativeToJsBridge();
|
||||
|
||||
/**
|
||||
* Executes a function with the module ID and method ID and any additional
|
||||
|
@ -108,58 +104,48 @@ public:
|
|||
void handleMemoryPressureModerate();
|
||||
void handleMemoryPressureCritical();
|
||||
|
||||
/**
|
||||
* Invokes a set of native module calls on behalf of the given executor.
|
||||
*
|
||||
* TODO: get rid of isEndOfBatch
|
||||
*/
|
||||
void callNativeModules(JSExecutor& executor, const std::string& callJSON, bool isEndOfBatch);
|
||||
|
||||
MethodCallResult callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, const std::string& argsJSON);
|
||||
|
||||
/**
|
||||
* Returns the ExecutorToken corresponding to the main JSExecutor.
|
||||
*/
|
||||
ExecutorToken getMainExecutorToken() const;
|
||||
|
||||
/**
|
||||
* Registers the given JSExecutor which runs on the given MessageQueueThread
|
||||
* with the Bridge. Part of this registration is transfering ownership of this
|
||||
* JSExecutor to the Bridge for the duration of the registration.
|
||||
*
|
||||
* Returns a ExecutorToken which can be used to refer to this JSExecutor
|
||||
* in the Bridge.
|
||||
*/
|
||||
ExecutorToken registerExecutor(
|
||||
std::unique_ptr<JSExecutor> executor,
|
||||
std::shared_ptr<MessageQueueThread> executorMessageQueueThread);
|
||||
|
||||
/**
|
||||
* Unregisters a JSExecutor that was previously registered with this Bridge
|
||||
* using registerExecutor. Use the ExecutorToken returned from this
|
||||
* registerExecutor call. This method will return ownership of the unregistered
|
||||
* executor to the caller for it to retain or tear down.
|
||||
*
|
||||
* Returns ownership of the unregistered executor.
|
||||
*/
|
||||
std::unique_ptr<JSExecutor> unregisterExecutor(ExecutorToken executorToken);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
std::unique_ptr<BridgeCallback> m_callback;
|
||||
// This is used to avoid a race condition where a proxyCallback gets queued after ~Bridge(),
|
||||
// on the same thread. In that case, the callback will try to run the task on m_callback which
|
||||
// will have been destroyed within ~Bridge(), thus causing a SIGSEGV.
|
||||
|
||||
// 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;
|
||||
std::unique_ptr<ExecutorToken> m_mainExecutorToken;
|
||||
std::unique_ptr<ExecutorTokenFactory> m_executorTokenFactory;
|
||||
ExecutorToken m_mainExecutorToken;
|
||||
std::shared_ptr<JsToNativeBridge> m_delegate;
|
||||
std::unordered_map<JSExecutor*, ExecutorToken> m_executorTokenMap;
|
||||
std::unordered_map<ExecutorToken, std::unique_ptr<ExecutorRegistration>> m_executorMap;
|
||||
std::unordered_map<ExecutorToken, ExecutorRegistration> m_executorMap;
|
||||
std::mutex m_registrationMutex;
|
||||
#ifdef WITH_FBSYSTRACE
|
||||
std::atomic_uint_least32_t m_systraceCookie = ATOMIC_VAR_INIT();
|
||||
|
@ -167,7 +153,7 @@ private:
|
|||
|
||||
MessageQueueThread* getMessageQueueThread(const ExecutorToken& executorToken);
|
||||
JSExecutor* getExecutor(const ExecutorToken& executorToken);
|
||||
inline ExecutorToken getTokenForExecutor(JSExecutor& executor);
|
||||
ExecutorToken getTokenForExecutor(JSExecutor& executor);
|
||||
};
|
||||
|
||||
} }
|
Loading…
Reference in New Issue