Move xplat bridge core to OSS

Reviewed By: mhorowitz

Differential Revision: D3237829

fb-gh-sync-id: 348c7d1e2810400c1259e3d99c2d026da4a39816
fbshipit-source-id: 348c7d1e2810400c1259e3d99c2d026da4a39816
This commit is contained in:
Chris Hopman
2016-05-03 19:29:58 -07:00
committed by Facebook Github Bot 5
parent 1d802da7d2
commit 24fe8b7e92
34 changed files with 3701 additions and 0 deletions

85
ReactCommon/bridge/BUCK Normal file
View File

@@ -0,0 +1,85 @@
def kwargs_add(base_kwargs, **new_kwargs):
ret_kwargs = dict(base_kwargs)
for name, add_value in new_kwargs.iteritems():
if name in ret_kwargs:
# Don't use +=, it will modify base_kwargs
ret_kwargs[name] = ret_kwargs[name] + add_value
else:
ret_kwargs[name] = add_value
return ret_kwargs
if THIS_IS_FBANDROID:
include_defs('//ReactAndroid/DEFS')
def react_library(**kwargs):
kwargs = kwargs_add(
kwargs,
# We depend on JSC, support the same platforms
supported_platforms_regex = '^android-(armv7|x86)$',
compiler_flags = [
'-Wno-pessimizing-move',
],
deps = [
'//xplat/folly:molly',
])
cxx_library(
name = 'bridge',
**kwargs_add(
kwargs,
preprocessor_flags = [
'-DWITH_JSC_EXTRA_TRACING=1',
'-DWITH_JSC_MEMORY_PRESSURE=1',
'-DWITH_REACT_INTERNAL_SETTINGS=1',
'-DWITH_FB_MEMORY_PROFILING=1',
],
deps = JSC_DEPS
)
)
elif THIS_IS_FBOBJC:
def react_library(**kwargs):
ios_library(
name = 'bridge',
header_path_prefix = "cxxreact",
frameworks = [
'$SDKROOT/System/Library/Frameworks/JavaScriptCore.framework',
],
**kwargs_add(
kwargs,
deps = [
'//xplat/folly:molly',
]
)
)
LOCAL_HEADERS = [
'JSCTracing.h',
'JSCLegacyProfiler.h',
'JSCMemory.h',
'JSCPerfStats.h',
]
react_library(
soname = 'libreactnativefb.so',
header_namespace = 'cxxreact',
force_static = True,
srcs = glob(['*.cpp']),
headers = LOCAL_HEADERS,
preprocessor_flags = [
'-DLOG_TAG="ReactNative"',
'-DWITH_FBSYSTRACE=1',
],
compiler_flags = [
'-Wall',
'-fexceptions',
'-fvisibility=hidden',
'-frtti',
],
exported_headers = glob(['*.h'], excludes=LOCAL_HEADERS),
deps = [
'//xplat/fbsystrace:fbsystrace',
],
visibility = [ 'PUBLIC' ],
)

View File

@@ -0,0 +1,269 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "Bridge.h"
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
using fbsystrace::FbSystraceSection;
using fbsystrace::FbSystraceAsyncFlow;
#endif
#include <folly/json.h>
#include <folly/Memory.h>
#include <folly/MoveWrapper.h>
#include "Platform.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(const std::string& script, const std::string& sourceURL) {
runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
executor->loadApplicationScript(script, sourceURL);
});
}
void Bridge::loadApplicationUnbundle(
std::unique_ptr<JSModulesUnbundle> unbundle,
const std::string& startupScript,
const std::string& startupScriptSourceURL) {
runOnExecutorQueue(
*m_mainExecutorToken,
[unbundle=folly::makeMoveWrapper(std::move(unbundle)), startupScript, startupScriptSourceURL]
(JSExecutor* executor) mutable {
executor->setJSModulesUnbundle(unbundle.move());
executor->loadApplicationScript(startupScript, 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);
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, 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);
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "Bridge.invokeCallback");
#endif
executor->invokeCallback(callbackId, arguments);
});
}
void Bridge::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
executor->setGlobalVariable(propName, jsonValue);
});
}
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);
});
}
} }

173
ReactCommon/bridge/Bridge.h Normal file
View File

@@ -0,0 +1,173 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <atomic>
#include <functional>
#include <map>
#include <vector>
#include <folly/dynamic.h>
#include "Executor.h"
#include "ExecutorToken.h"
#include "ExecutorTokenFactory.h"
#include "JSModulesUnbundle.h"
#include "MessageQueueThread.h"
#include "MethodCall.h"
#include "NativeModule.h"
#include "Value.h"
namespace folly {
struct dynamic;
}
namespace facebook {
namespace react {
class BridgeCallback {
public:
virtual ~BridgeCallback() {};
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(
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 Bridge {
public:
/**
* This must be called on the main JS thread.
*/
Bridge(
JSExecutorFactory* jsExecutorFactory,
std::shared_ptr<MessageQueueThread> jsQueue,
std::unique_ptr<ExecutorTokenFactory> executorTokenFactory,
std::unique_ptr<BridgeCallback> callback);
virtual ~Bridge();
/**
* Executes a function with the module ID and method ID and any additional
* arguments in JS.
*/
void callFunction(
ExecutorToken executorToken,
const std::string& moduleId,
const std::string& methodId,
const folly::dynamic& args,
const std::string& tracingName);
/**
* Invokes a callback with the cbID, and optional additional arguments in JS.
*/
void invokeCallback(ExecutorToken executorToken, const double callbackId, const folly::dynamic& args);
/**
* Starts the JS application from an "bundle", i.e. a JavaScript file that
* contains code for all modules and a runtime that resolves and
* executes modules.
*/
void loadApplicationScript(const std::string& script, const std::string& sourceURL);
/**
* An "unbundle" is a backend that stores and injects JavaScript modules as
* individual scripts, rather than bundling all of them into a single scrupt.
*
* Loading an unbundle means setting the storage backend and executing the
* startup script.
*/
void loadApplicationUnbundle(
std::unique_ptr<JSModulesUnbundle> unbundle,
const std::string& startupCode,
const std::string& sourceURL);
void setGlobalVariable(const std::string& propName, const std::string& jsonValue);
void* getJavaScriptContext();
bool supportsProfiling();
void startProfiler(const std::string& title);
void stopProfiler(const std::string& title, const std::string& filename);
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:
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.
std::shared_ptr<bool> m_destroyed;
JSExecutor* m_mainExecutor;
std::unique_ptr<ExecutorToken> m_mainExecutorToken;
std::unique_ptr<ExecutorTokenFactory> m_executorTokenFactory;
std::unordered_map<JSExecutor*, ExecutorToken> m_executorTokenMap;
std::unordered_map<ExecutorToken, std::unique_ptr<ExecutorRegistration>> m_executorMap;
std::mutex m_registrationMutex;
#ifdef WITH_FBSYSTRACE
std::atomic_uint_least32_t m_systraceCookie = ATOMIC_VAR_INIT();
#endif
MessageQueueThread* getMessageQueueThread(const ExecutorToken& executorToken);
JSExecutor* getExecutor(const ExecutorToken& executorToken);
inline ExecutorToken getTokenForExecutor(JSExecutor& executor);
};
} }

View File

@@ -0,0 +1,80 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "JSModulesUnbundle.h"
namespace folly {
struct dynamic;
}
namespace facebook {
namespace react {
class Bridge;
class JSExecutor;
class MessageQueueThread;
class JSExecutorFactory {
public:
virtual std::unique_ptr<JSExecutor> createJSExecutor(
Bridge *bridge, std::shared_ptr<MessageQueueThread> jsQueue) = 0;
virtual ~JSExecutorFactory() {};
};
class JSExecutor {
public:
/**
* Execute an application script bundle in the JS context.
*/
virtual void loadApplicationScript(
const std::string& script,
const std::string& sourceURL) = 0;
/**
* Add an application "unbundle" file
*/
virtual void setJSModulesUnbundle(std::unique_ptr<JSModulesUnbundle> bundle) = 0;
/**
* Executes BatchedBridge.callFunctionReturnFlushedQueue with the module ID,
* method ID and optional additional arguments in JS. The executor is responsible
* for using Bridge->callNativeModules to invoke any necessary native modules methods.
*/
virtual void callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) = 0;
/**
* Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID,
* and optional additional arguments in JS and returns the next queue. The executor
* is responsible for using Bridge->callNativeModules to invoke any necessary
* native modules methods.
*/
virtual void invokeCallback(const double callbackId, const folly::dynamic& arguments) = 0;
virtual void setGlobalVariable(
const std::string& propName,
const std::string& 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 handleMemoryPressureCritical() {
handleMemoryPressureModerate();
};
virtual void destroy() {};
virtual ~JSExecutor() {};
};
} }

View File

@@ -0,0 +1,54 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include "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> {
const size_t operator()(const facebook::react::ExecutorToken& token) const {
return (size_t) token.getPlatformExecutorToken().get();
}
};
}

View File

@@ -0,0 +1,25 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include "ExecutorToken.h"
#include "Executor.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;
};
} }

View File

@@ -0,0 +1,226 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "Instance.h"
#include "Executor.h"
#include "MethodCall.h"
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
using fbsystrace::FbSystraceSection;
#endif
#include <folly/json.h>
#include <folly/Memory.h>
#include <glog/logging.h>
#include <condition_variable>
#include <fstream>
#include <mutex>
#include <string>
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();
}
}
void Instance::initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::unique_ptr<MessageQueueThread> nativeQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry,
folly::dynamic jsModuleDescriptions) {
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));
});
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "setBatchedBridgeConfig");
#endif
CHECK(bridge_);
folly::dynamic nativeModuleDescriptions = folly::dynamic::array();
{
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "collectNativeModuleDescriptions");
#endif
nativeModuleDescriptions = moduleRegistry_->moduleDescriptions();
}
folly::dynamic config =
folly::dynamic::object
("remoteModuleConfig", std::move(nativeModuleDescriptions))
("localModulesConfig", std::move(jsModuleDescriptions));
#ifdef WITH_FBSYSTRACE
FbSystraceSection t(TRACE_TAG_REACT_CXX_BRIDGE, "setGlobalVariable");
#endif
setGlobalVariable(
"__fbBatchedBridgeConfig",
folly::toJson(config));
}
void Instance::loadScriptFromString(const std::string& string,
const std::string& sourceURL) {
callback_->incrementPendingJSCalls();
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE,
"reactbridge_xplat_loadScriptFromString",
"sourceURL", sourceURL);
#endif
// TODO mhorowitz: ReactMarker around loadApplicationScript
bridge_->loadApplicationScript(string, sourceURL);
}
void Instance::loadScriptFromFile(const std::string& filename,
const std::string& sourceURL) {
// TODO mhorowitz: ReactMarker around file read
std::string script;
{
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE,
"reactbridge_xplat_loadScriptFromFile",
"fileName", filename);
#endif
std::ifstream jsfile(filename);
if (!jsfile) {
LOG(ERROR) << "Unable to load script from file" << filename;
} else {
jsfile.seekg(0, std::ios::end);
script.reserve(jsfile.tellg());
jsfile.seekg(0, std::ios::beg);
script.assign(
std::istreambuf_iterator<char>(jsfile),
std::istreambuf_iterator<char>());
}
}
loadScriptFromString(script, sourceURL);
}
void Instance::loadUnbundle(std::unique_ptr<JSModulesUnbundle> unbundle,
const std::string& startupScript,
const std::string& startupScriptSourceURL) {
callback_->incrementPendingJSCalls();
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE,
"reactbridge_xplat_setJSModulesUnbundle");
#endif
bridge_->loadApplicationUnbundle(std::move(unbundle), startupScript, startupScriptSourceURL);
}
bool Instance::supportsProfiling() {
return bridge_->supportsProfiling();
}
void Instance::startProfiler(const std::string& title) {
return bridge_->startProfiler(title);
}
void Instance::stopProfiler(const std::string& title, const std::string& filename) {
return bridge_->stopProfiler(title, filename);
}
void Instance::setGlobalVariable(const std::string& propName,
const std::string& jsonValue) {
bridge_->setGlobalVariable(propName, jsonValue);
}
void Instance::callJSFunction(ExecutorToken token, const std::string& module, const std::string& method,
folly::dynamic&& params, const std::string& tracingName) {
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, tracingName.c_str());
#endif
callback_->incrementPendingJSCalls();
bridge_->callFunction(token, module, method, std::move(params), tracingName);
}
void Instance::callJSCallback(ExecutorToken token, uint64_t callbackId, folly::dynamic&& params) {
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "<callback>");
#endif
callback_->incrementPendingJSCalls();
bridge_->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));
}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,68 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <memory>
#include <folly/dynamic.h>
#include "Bridge.h"
#include "ModuleRegistry.h"
#include "NativeModule.h"
namespace facebook {
namespace react {
class JSExecutorFactory;
struct InstanceCallback {
virtual ~InstanceCallback() {}
virtual void onBatchComplete() = 0;
virtual void incrementPendingJSCalls() = 0;
virtual void decrementPendingJSCalls() = 0;
virtual void onNativeException(const std::string& what) = 0;
virtual ExecutorToken createExecutorToken() = 0;
};
class Instance {
public:
~Instance();
void initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::unique_ptr<MessageQueueThread> nativeQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry,
folly::dynamic jsModuleDescriptions);
void loadScriptFromString(const std::string& string, const std::string& sourceURL);
void loadScriptFromFile(const std::string& filename, const std::string& sourceURL);
void loadUnbundle(
std::unique_ptr<JSModulesUnbundle> unbundle,
const std::string& startupScript,
const std::string& startupScriptSourceURL);
bool supportsProfiling();
void startProfiler(const std::string& title);
void stopProfiler(const std::string& title, const std::string& filename);
void setGlobalVariable(const std::string& propName, const std::string& jsonValue);
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);
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_;
};
}
}

View File

@@ -0,0 +1,653 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "JSCExecutor.h"
#include <algorithm>
#include <condition_variable>
#include <mutex>
#include <sstream>
#include <string>
#include <glog/logging.h>
#include <folly/json.h>
#include <folly/String.h>
#include <sys/time.h>
#include "Bridge.h"
#include "JSCHelpers.h"
#include "Platform.h"
#include "Value.h"
#ifdef WITH_JSC_EXTRA_TRACING
#include "JSCTracing.h"
#include "JSCLegacyProfiler.h"
#include <JavaScriptCore/API/JSProfilerPrivate.h>
#endif
#ifdef WITH_JSC_MEMORY_PRESSURE
#include <jsc_memory.h>
#endif
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
using fbsystrace::FbSystraceSection;
#endif
#ifdef WITH_FB_MEMORY_PROFILING
#include "JSCMemory.h"
#endif
#ifdef WITH_FB_JSC_TUNING
#include <jsc_config_android.h>
#endif
#ifdef JSC_HAS_PERF_STATS_API
#include "JSCPerfStats.h"
#endif
namespace facebook {
namespace react {
static std::unordered_map<JSContextRef, JSCExecutor*> s_globalContextRefToJSCExecutor;
static JSValueRef nativeInjectHMRUpdate(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception);
static std::string executeJSCallWithJSC(
JSGlobalContextRef ctx,
const std::string& methodName,
const std::vector<folly::dynamic>& arguments) {
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(
TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.executeJSCall",
"method", methodName);
#endif
// Evaluate script with JSC
folly::dynamic jsonArgs(arguments.begin(), arguments.end());
auto js = folly::to<folly::fbstring>(
"__fbBatchedBridge.", methodName, ".apply(null, ",
folly::toJson(jsonArgs), ")");
auto result = evaluateScript(ctx, String(js.c_str()), nullptr);
return Value(ctx, result).toJSONString();
}
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
Bridge *bridge, std::shared_ptr<MessageQueueThread> jsQueue) {
return std::unique_ptr<JSExecutor>(
new JSCExecutor(bridge, jsQueue, cacheDir_, m_jscConfig));
}
JSCExecutor::JSCExecutor(Bridge *bridge, std::shared_ptr<MessageQueueThread> messageQueueThread,
const std::string& cacheDir, const folly::dynamic& jscConfig) :
m_bridge(bridge),
m_deviceCacheDir(cacheDir),
m_messageQueueThread(messageQueueThread),
m_jscConfig(jscConfig) {
initOnJSVMThread();
}
JSCExecutor::JSCExecutor(
Bridge *bridge,
std::shared_ptr<MessageQueueThread> messageQueueThread,
int workerId,
JSCExecutor *owner,
const std::string& script,
const std::unordered_map<std::string, std::string>& globalObjAsJSON,
const folly::dynamic& jscConfig) :
m_bridge(bridge),
m_workerId(workerId),
m_owner(owner),
m_deviceCacheDir(owner->m_deviceCacheDir),
m_messageQueueThread(messageQueueThread),
m_jscConfig(jscConfig) {
// We post initOnJSVMThread here so that the owner doesn't have to wait for
// initialization on its own thread
m_messageQueueThread->runOnQueue([this, script, globalObjAsJSON] () {
initOnJSVMThread();
installGlobalFunction(m_context, "postMessage", nativePostMessage);
for (auto& it : globalObjAsJSON) {
setGlobalVariable(it.first, it.second);
}
// Try to load the script from the network if script is a URL
// NB: For security, this will only work in debug builds
std::string scriptSrc;
if (script.find("http://") == 0 || script.find("https://") == 0) {
std::stringstream outfileBuilder;
outfileBuilder << m_deviceCacheDir << "/workerScript" << m_workerId << ".js";
scriptSrc = WebWorkerUtil::loadScriptFromNetworkSync(script, outfileBuilder.str());
} else {
// TODO(9604438): Protect against script does not exist
scriptSrc = WebWorkerUtil::loadScriptFromAssets(script);
}
// TODO(9994180): Throw on error
loadApplicationScript(scriptSrc, script);
});
}
JSCExecutor::~JSCExecutor() {
CHECK(*m_isDestroyed) << "JSCExecutor::destroy() must be called before its destructor!";
}
void JSCExecutor::destroy() {
*m_isDestroyed = true;
m_messageQueueThread->runOnQueueSync([this] () {
terminateOnJSVMThread();
});
}
void JSCExecutor::initOnJSVMThread() {
#if defined(WITH_FB_JSC_TUNING)
configureJSCForAndroid(m_jscConfig);
#endif
m_context = JSGlobalContextCreateInGroup(nullptr, nullptr);
s_globalContextRefToJSCExecutor[m_context] = this;
installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate);
installGlobalFunction(m_context, "nativeStartWorker", nativeStartWorker);
installGlobalFunction(m_context, "nativePostMessageToWorker", nativePostMessageToWorker);
installGlobalFunction(m_context, "nativeTerminateWorker", nativeTerminateWorker);
installGlobalFunction(m_context, "nativeInjectHMRUpdate", nativeInjectHMRUpdate);
installGlobalFunction(m_context, "nativeCallSyncHook", nativeCallSyncHook);
installGlobalFunction(m_context, "nativeLoggingHook", JSNativeHooks::loggingHook);
installGlobalFunction(m_context, "nativePerformanceNow", JSNativeHooks::nowHook);
#ifdef WITH_JSC_EXTRA_TRACING
addNativeTracingHooks(m_context);
addNativeProfilingHooks(m_context);
PerfLogging::installNativeHooks(m_context);
#endif
#ifdef WITH_FB_MEMORY_PROFILING
addNativeMemoryHooks(m_context);
#endif
#ifdef JSC_HAS_PERF_STATS_API
addJSCPerfStatsHooks(m_context);
#endif
#if defined(WITH_FB_JSC_TUNING)
configureJSContextForAndroid(m_context, m_jscConfig, m_deviceCacheDir);
#endif
}
void JSCExecutor::terminateOnJSVMThread() {
// terminateOwnedWebWorker mutates m_ownedWorkers so collect all the workers
// to terminate first
std::vector<int> workerIds;
for (auto& it : m_ownedWorkers) {
workerIds.push_back(it.first);
}
for (int workerId : workerIds) {
terminateOwnedWebWorker(workerId);
}
s_globalContextRefToJSCExecutor.erase(m_context);
JSGlobalContextRelease(m_context);
m_context = nullptr;
}
void JSCExecutor::loadApplicationScript(
const std::string& script,
const std::string& sourceURL) {
ReactMarker::logMarker("loadApplicationScript_startStringConvert");
String jsScript = String::createExpectingAscii(script);
ReactMarker::logMarker("loadApplicationScript_endStringConvert");
String jsSourceURL(sourceURL.c_str());
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor::loadApplicationScript",
"sourceURL", sourceURL);
#endif
evaluateScript(m_context, jsScript, jsSourceURL);
flush();
ReactMarker::logMarker("CREATE_REACT_CONTEXT_END");
}
void JSCExecutor::setJSModulesUnbundle(std::unique_ptr<JSModulesUnbundle> unbundle) {
if (!m_unbundle) {
installGlobalFunction(m_context, "nativeRequire", nativeRequire);
}
m_unbundle = std::move(unbundle);
}
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);
}
void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
// TODO: Make this a first class function instead of evaling. #9317773
std::vector<folly::dynamic> call{
moduleId,
methodId,
std::move(arguments),
};
std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
m_bridge->callNativeModules(*this, calls, true);
}
void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
// TODO: Make this a first class function instead of evaling. #9317773
std::vector<folly::dynamic> call{
(double) callbackId,
std::move(arguments)
};
std::string calls = executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call));
m_bridge->callNativeModules(*this, calls, true);
}
void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
auto globalObject = JSContextGetGlobalObject(m_context);
String jsPropertyName(propName.c_str());
String jsValueJSON(jsonValue.c_str());
auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON);
JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);
}
void* JSCExecutor::getJavaScriptContext() {
return m_context;
}
bool JSCExecutor::supportsProfiling() {
#ifdef WITH_FBSYSTRACE
return true;
#else
return false;
#endif
}
void JSCExecutor::startProfiler(const std::string &titleString) {
#ifdef WITH_JSC_EXTRA_TRACING
JSStringRef title = JSStringCreateWithUTF8CString(titleString.c_str());
#if WITH_REACT_INTERNAL_SETTINGS
JSStartProfiling(m_context, title, false);
#else
JSStartProfiling(m_context, title);
#endif
JSStringRelease(title);
#endif
}
void JSCExecutor::stopProfiler(const std::string &titleString, const std::string& filename) {
#ifdef WITH_JSC_EXTRA_TRACING
JSStringRef title = JSStringCreateWithUTF8CString(titleString.c_str());
facebook::react::stopAndOutputProfilingFile(m_context, title, filename.c_str());
JSStringRelease(title);
#endif
}
void JSCExecutor::handleMemoryPressureModerate() {
#ifdef WITH_JSC_MEMORY_PRESSURE
JSHandleMemoryPressure(this, m_context, JSMemoryPressure::MODERATE);
#endif
}
void JSCExecutor::handleMemoryPressureCritical() {
#ifdef WITH_JSC_MEMORY_PRESSURE
JSHandleMemoryPressure(this, m_context, JSMemoryPressure::CRITICAL);
#endif
}
void JSCExecutor::flushQueueImmediate(std::string queueJSON) {
m_bridge->callNativeModules(*this, queueJSON, false);
}
void JSCExecutor::loadModule(uint32_t moduleId) {
auto module = m_unbundle->getModule(moduleId);
auto sourceUrl = String::createExpectingAscii(module.name);
auto source = String::createExpectingAscii(module.code);
evaluateScript(m_context, source, sourceUrl);
}
int JSCExecutor::addWebWorker(
const std::string& script,
JSValueRef workerRef,
JSValueRef globalObjRef) {
static std::atomic_int nextWorkerId(1);
int workerId = nextWorkerId++;
Object globalObj = Value(m_context, globalObjRef).asObject();
std::shared_ptr<MessageQueueThread> workerMQT =
WebWorkerUtil::createWebWorkerThread(workerId, m_messageQueueThread.get());
std::unique_ptr<JSCExecutor> worker;
workerMQT->runOnQueueSync([this, &worker, &workerMQT, &script, &globalObj, workerId] () {
worker.reset(new JSCExecutor(m_bridge, workerMQT, workerId, this, script,
globalObj.toJSONMap(), m_jscConfig));
});
Object workerObj = Value(m_context, workerRef).asObject();
workerObj.makeProtected();
JSCExecutor *workerPtr = worker.get();
std::shared_ptr<MessageQueueThread> sharedMessageQueueThread = worker->m_messageQueueThread;
ExecutorToken token = m_bridge->registerExecutor(
std::move(worker),
std::move(sharedMessageQueueThread));
m_ownedWorkers.emplace(
std::piecewise_construct,
std::forward_as_tuple(workerId),
std::forward_as_tuple(workerPtr, token, std::move(workerObj)));
return workerId;
}
void JSCExecutor::postMessageToOwnedWebWorker(int workerId, JSValueRef message, JSValueRef *exn) {
auto worker = m_ownedWorkers.at(workerId).executor;
std::string msgString = Value(m_context, message).toJSONString();
std::shared_ptr<bool> isWorkerDestroyed = worker->m_isDestroyed;
worker->m_messageQueueThread->runOnQueue([isWorkerDestroyed, worker, msgString] () {
if (*isWorkerDestroyed) {
return;
}
worker->receiveMessageFromOwner(msgString);
});
}
void JSCExecutor::postMessageToOwner(JSValueRef msg) {
std::string msgString = Value(m_context, msg).toJSONString();
std::shared_ptr<bool> ownerIsDestroyed = m_owner->m_isDestroyed;
m_owner->m_messageQueueThread->runOnQueue([workerId=m_workerId, owner=m_owner, ownerIsDestroyed, msgString] () {
if (*ownerIsDestroyed) {
return;
}
owner->receiveMessageFromOwnedWebWorker(workerId, msgString);
});
}
void JSCExecutor::receiveMessageFromOwnedWebWorker(int workerId, const std::string& json) {
Object* workerObj;
try {
workerObj = &m_ownedWorkers.at(workerId).jsObj;
} catch (std::out_of_range& e) {
// Worker was already terminated
return;
}
Value onmessageValue = workerObj->getProperty("onmessage");
if (onmessageValue.isUndefined()) {
return;
}
JSValueRef args[] = { createMessageObject(json) };
onmessageValue.asObject().callAsFunction(1, args);
flush();
}
void JSCExecutor::receiveMessageFromOwner(const std::string& msgString) {
CHECK(m_owner) << "Received message in a Executor that doesn't have an owner!";
JSValueRef args[] = { createMessageObject(msgString) };
Value onmessageValue = Object::getGlobalObject(m_context).getProperty("onmessage");
onmessageValue.asObject().callAsFunction(1, args);
}
void JSCExecutor::terminateOwnedWebWorker(int workerId) {
auto& workerRegistration = m_ownedWorkers.at(workerId);
std::shared_ptr<MessageQueueThread> workerMQT = workerRegistration.executor->m_messageQueueThread;
ExecutorToken workerExecutorToken = workerRegistration.executorToken;
m_ownedWorkers.erase(workerId);
workerMQT->runOnQueueSync([this, workerExecutorToken, &workerMQT] {
workerMQT->quitSynchronous();
std::unique_ptr<JSExecutor> worker = m_bridge->unregisterExecutor(workerExecutorToken);
worker->destroy();
worker.reset();
});
}
Object JSCExecutor::createMessageObject(const std::string& msgJson) {
Value rebornJSMsg = Value::fromJSON(m_context, String(msgJson.c_str()));
Object messageObject = Object::create(m_context);
messageObject.setProperty("data", rebornJSMsg);
return messageObject;
}
// Native JS hooks
JSValueRef JSCExecutor::nativePostMessage(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception) {
if (argumentCount != 1) {
*exception = makeJSCException(ctx, "postMessage got wrong number of arguments");
return JSValueMakeUndefined(ctx);
}
JSValueRef msg = arguments[0];
JSCExecutor *webWorker = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
webWorker->postMessageToOwner(msg);
return JSValueMakeUndefined(ctx);
}
static JSValueRef makeInvalidModuleIdJSCException(
JSContextRef ctx,
const JSValueRef id,
JSValueRef *exception) {
std::string message = "Received invalid module ID: ";
message += String::adopt(JSValueToStringCopy(ctx, id, exception)).str();
return makeJSCException(ctx, message.c_str());
}
JSValueRef JSCExecutor::nativeRequire(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception) {
if (argumentCount != 1) {
*exception = makeJSCException(ctx, "Got wrong number of args");
return JSValueMakeUndefined(ctx);
}
JSCExecutor *executor;
try {
executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
} catch (std::out_of_range& e) {
*exception = makeJSCException(ctx, "Global JS context didn't map to a valid executor");
return JSValueMakeUndefined(ctx);
}
double moduleId = JSValueToNumber(ctx, arguments[0], exception);
if (moduleId <= (double) std::numeric_limits<uint32_t>::max() && moduleId >= 0.0) {
try {
executor->loadModule(moduleId);
} catch (JSModulesUnbundle::ModuleNotFound&) {
*exception = makeInvalidModuleIdJSCException(ctx, arguments[0], exception);
}
} else {
*exception = makeInvalidModuleIdJSCException(ctx, arguments[0], exception);
}
return JSValueMakeUndefined(ctx);
}
static JSValueRef createErrorString(JSContextRef ctx, const char *msg) {
return JSValueMakeString(ctx, String(msg));
}
JSValueRef JSCExecutor::nativeFlushQueueImmediate(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception) {
if (argumentCount != 1) {
*exception = createErrorString(ctx, "Got wrong number of args");
return JSValueMakeUndefined(ctx);
}
JSCExecutor *executor;
try {
executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
} catch (std::out_of_range& e) {
*exception = createErrorString(ctx, "Global JS context didn't map to a valid executor");
return JSValueMakeUndefined(ctx);
}
std::string resStr = Value(ctx, arguments[0]).toJSONString();
executor->flushQueueImmediate(resStr);
return JSValueMakeUndefined(ctx);
}
JSValueRef JSCExecutor::nativeStartWorker(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception) {
if (argumentCount != 3) {
*exception = createErrorString(ctx, "Got wrong number of args");
return JSValueMakeUndefined(ctx);
}
std::string scriptFile = Value(ctx, arguments[0]).toString().str();
JSValueRef worker = arguments[1];
JSValueRef globalObj = arguments[2];
JSCExecutor *executor;
try {
executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
} catch (std::out_of_range& e) {
*exception = createErrorString(ctx, "Global JS context didn't map to a valid executor");
return JSValueMakeUndefined(ctx);
}
int workerId = executor->addWebWorker(scriptFile, worker, globalObj);
return JSValueMakeNumber(ctx, workerId);
}
JSValueRef JSCExecutor::nativePostMessageToWorker(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception) {
if (argumentCount != 2) {
*exception = createErrorString(ctx, "Got wrong number of args");
return JSValueMakeUndefined(ctx);
}
double workerDouble = JSValueToNumber(ctx, arguments[0], exception);
if (workerDouble != workerDouble) {
*exception = createErrorString(ctx, "Got invalid worker id");
return JSValueMakeUndefined(ctx);
}
JSCExecutor *executor;
try {
executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
} catch (std::out_of_range& e) {
*exception = createErrorString(ctx, "Global JS context didn't map to a valid executor");
return JSValueMakeUndefined(ctx);
}
executor->postMessageToOwnedWebWorker((int) workerDouble, arguments[1], exception);
return JSValueMakeUndefined(ctx);
}
JSValueRef JSCExecutor::nativeTerminateWorker(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception) {
if (argumentCount != 1) {
*exception = createErrorString(ctx, "Got wrong number of args");
return JSValueMakeUndefined(ctx);
}
double workerDouble = JSValueToNumber(ctx, arguments[0], exception);
if (workerDouble != workerDouble) {
*exception = createErrorString(ctx, "Got invalid worker id");
return JSValueMakeUndefined(ctx);
}
JSCExecutor *executor;
try {
executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
} catch (std::out_of_range& e) {
*exception = createErrorString(ctx, "Global JS context didn't map to a valid executor");
return JSValueMakeUndefined(ctx);
}
executor->terminateOwnedWebWorker((int) workerDouble);
return JSValueMakeUndefined(ctx);
}
JSValueRef JSCExecutor::nativeCallSyncHook(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception) {
if (argumentCount != 3) {
*exception = createErrorString(ctx, "Got wrong number of args for callSyncHook");
return JSValueMakeUndefined(ctx);
}
unsigned int moduleId = Value(ctx, arguments[0]).asUnsignedInteger();
unsigned int methodId = Value(ctx, arguments[1]).asUnsignedInteger();
std::string argsJson = Value(ctx, arguments[2]).toJSONString();
JSCExecutor *executor;
try {
executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
} catch (std::out_of_range& e) {
*exception = createErrorString(ctx, "Global JS context didn't map to a valid executor");
return JSValueMakeUndefined(ctx);
}
MethodCallResult result = executor->m_bridge->callSerializableNativeHook(
moduleId,
methodId,
argsJson);
if (result.isUndefined) {
return JSValueMakeUndefined(ctx);
}
return Value::fromJSON(ctx, String(folly::toJson(result.result).c_str()));
}
static JSValueRef nativeInjectHMRUpdate(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[], JSValueRef *exception) {
String execJSString = Value(ctx, arguments[0]).toString();
String jsURL = Value(ctx, arguments[1]).toString();
evaluateScript(ctx, execJSString, jsURL);
return JSValueMakeUndefined(ctx);
}
} }

View File

@@ -0,0 +1,171 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <cstdint>
#include <memory>
#include <unordered_map>
#include <JavaScriptCore/JSContextRef.h>
#include <folly/json.h>
#include "Executor.h"
#include "ExecutorToken.h"
#include "JSCHelpers.h"
#include "Value.h"
namespace facebook {
namespace react {
class MessageQueueThread;
class JSCExecutorFactory : public JSExecutorFactory {
public:
JSCExecutorFactory(const std::string& cacheDir, const folly::dynamic& jscConfig) :
cacheDir_(cacheDir),
m_jscConfig(jscConfig) {}
virtual std::unique_ptr<JSExecutor> createJSExecutor(
Bridge *bridge, std::shared_ptr<MessageQueueThread> jsQueue) override;
private:
std::string cacheDir_;
folly::dynamic m_jscConfig;
};
class JSCExecutor;
class WorkerRegistration : public noncopyable {
public:
explicit WorkerRegistration(JSCExecutor* executor_, ExecutorToken executorToken_, Object jsObj_) :
executor(executor_),
executorToken(executorToken_),
jsObj(std::move(jsObj_)) {}
JSCExecutor *executor;
ExecutorToken executorToken;
Object jsObj;
};
class JSCExecutor : public JSExecutor {
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);
~JSCExecutor() override;
virtual void loadApplicationScript(
const std::string& script,
const std::string& sourceURL) override;
virtual void setJSModulesUnbundle(
std::unique_ptr<JSModulesUnbundle> unbundle) override;
virtual void callFunction(
const std::string& moduleId,
const std::string& methodId,
const folly::dynamic& arguments) override;
virtual void invokeCallback(
const double callbackId,
const folly::dynamic& arguments) override;
virtual void setGlobalVariable(
const std::string& propName,
const std::string& jsonValue) override;
virtual void* getJavaScriptContext() override;
virtual bool supportsProfiling() override;
virtual void startProfiler(const std::string &titleString) override;
virtual void stopProfiler(const std::string &titleString, const std::string &filename) override;
virtual void handleMemoryPressureModerate() override;
virtual void handleMemoryPressureCritical() override;
virtual void destroy() override;
void installNativeHook(const char *name, JSObjectCallAsFunctionCallback callback);
private:
JSGlobalContextRef m_context;
Bridge *m_bridge;
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;
folly::dynamic m_jscConfig;
/**
* WebWorker constructor. Must be invoked from thread this Executor will run on.
*/
JSCExecutor(
Bridge *bridge,
std::shared_ptr<MessageQueueThread> messageQueueThread,
int workerId,
JSCExecutor *owner,
const std::string& script,
const std::unordered_map<std::string, std::string>& globalObjAsJSON,
const folly::dynamic& jscConfig);
void initOnJSVMThread();
void terminateOnJSVMThread();
void flush();
void flushQueueImmediate(std::string queueJSON);
void loadModule(uint32_t moduleId);
int addWebWorker(const std::string& script, JSValueRef workerRef, JSValueRef globalObjRef);
void postMessageToOwnedWebWorker(int worker, JSValueRef message, JSValueRef *exn);
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);
static JSValueRef nativeStartWorker(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception);
static JSValueRef nativePostMessageToWorker(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception);
static JSValueRef nativeTerminateWorker(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception);
static JSValueRef nativePostMessage(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception);
static JSValueRef nativeRequire(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception);
static JSValueRef nativeFlushQueueImmediate(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception);
static JSValueRef nativeCallSyncHook(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception);
};
} }

View File

@@ -0,0 +1,55 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "JSCHelpers.h"
#include <JavaScriptCore/JSStringRef.h>
#include <glog/logging.h>
#include "Value.h"
namespace facebook {
namespace react {
void installGlobalFunction(
JSGlobalContextRef ctx,
const char* name,
JSObjectCallAsFunctionCallback callback) {
JSStringRef jsName = JSStringCreateWithUTF8CString(name);
JSObjectRef functionObj = JSObjectMakeFunctionWithCallback(
ctx, jsName, callback);
JSObjectRef globalObject = JSContextGetGlobalObject(ctx);
JSObjectSetProperty(ctx, globalObject, jsName, functionObj, 0, NULL);
JSStringRelease(jsName);
}
JSValueRef makeJSCException(
JSContextRef ctx,
const char* exception_text) {
JSStringRef message = JSStringCreateWithUTF8CString(exception_text);
JSValueRef exceptionString = JSValueMakeString(ctx, message);
JSStringRelease(message);
return JSValueToObject(ctx, exceptionString, NULL);
}
JSValueRef evaluateScript(JSContextRef context, JSStringRef script, JSStringRef source) {
JSValueRef exn, result;
result = JSEvaluateScript(context, script, NULL, source, 0, &exn);
if (result == nullptr) {
Value exception = Value(context, exn);
std::string exceptionText = exception.toString().str();
LOG(ERROR) << "Got JS Exception: " << exceptionText;
auto line = exception.asObject().getProperty("line");
std::ostringstream locationInfo;
std::string file = source != nullptr ? String::ref(source).str() : "";
locationInfo << "(" << (file.length() ? file : "<unknown file>");
if (line != nullptr && line.isNumber()) {
locationInfo << ":" << line.asInteger();
}
locationInfo << ")";
throwJSExecutionException("%s %s", exceptionText.c_str(), locationInfo.str().c_str());
}
return result;
}
} }

View File

@@ -0,0 +1,46 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <JavaScriptCore/JSContextRef.h>
#include <JavaScriptCore/JSObjectRef.h>
#include <JavaScriptCore/JSValueRef.h>
#include <stdexcept>
#include <algorithm>
namespace facebook {
namespace react {
struct JsException : std::runtime_error {
using std::runtime_error::runtime_error;
};
inline void throwJSExecutionException(const char* msg) {
throw JsException(msg);
}
template <typename... Args>
inline void throwJSExecutionException(const char* fmt, Args... args) {
int msgSize = snprintf(nullptr, 0, fmt, args...);
msgSize = std::min(512, msgSize + 1);
char *msg = (char*) alloca(msgSize);
snprintf(msg, msgSize, fmt, args...);
throw JsException(msg);
}
void installGlobalFunction(
JSGlobalContextRef ctx,
const char* name,
JSObjectCallAsFunctionCallback callback);
JSValueRef makeJSCException(
JSContextRef ctx,
const char* exception_text);
JSValueRef evaluateScript(
JSContextRef ctx,
JSStringRef script,
JSStringRef sourceURL);
} }

View File

@@ -0,0 +1,88 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#ifdef WITH_JSC_EXTRA_TRACING
#include <stdio.h>
#include <string.h>
#include <JavaScriptCore/JavaScript.h>
#include <JavaScriptCore/API/JSProfilerPrivate.h>
#include <jsc_legacy_profiler.h>
#include "JSCHelpers.h"
#include "JSCLegacyProfiler.h"
#include "Value.h"
static JSValueRef nativeProfilerStart(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
if (argumentCount < 1) {
if (exception) {
*exception = facebook::react::makeJSCException(
ctx,
"nativeProfilerStart: requires at least 1 argument");
}
return JSValueMakeUndefined(ctx);
}
JSStringRef title = JSValueToStringCopy(ctx, arguments[0], exception);
#if WITH_REACT_INTERNAL_SETTINGS
JSStartProfiling(ctx, title, false);
#else
JSStartProfiling(ctx, title);
#endif
JSStringRelease(title);
return JSValueMakeUndefined(ctx);
}
static JSValueRef nativeProfilerEnd(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
if (argumentCount < 1) {
if (exception) {
*exception = facebook::react::makeJSCException(
ctx,
"nativeProfilerEnd: requires at least 1 argument");
}
return JSValueMakeUndefined(ctx);
}
std::string writeLocation("/sdcard/");
if (argumentCount > 1) {
JSStringRef fileName = JSValueToStringCopy(ctx, arguments[1], exception);
writeLocation += facebook::react::String::ref(fileName).str();
JSStringRelease(fileName);
} else {
writeLocation += "profile.json";
}
JSStringRef title = JSValueToStringCopy(ctx, arguments[0], exception);
JSEndProfilingAndRender(ctx, title, writeLocation.c_str());
JSStringRelease(title);
return JSValueMakeUndefined(ctx);
}
namespace facebook {
namespace react {
void stopAndOutputProfilingFile(
JSContextRef ctx,
JSStringRef title,
const char *filename) {
JSEndProfilingAndRender(ctx, title, filename);
}
void addNativeProfilingHooks(JSGlobalContextRef ctx) {
JSEnableByteCodeProfiling();
installGlobalFunction(ctx, "nativeProfilerStart", nativeProfilerStart);
installGlobalFunction(ctx, "nativeProfilerEnd", nativeProfilerEnd);
}
} }
#endif

View File

@@ -0,0 +1,19 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#ifdef WITH_JSC_EXTRA_TRACING
#include <JavaScriptCore/JSContextRef.h>
namespace facebook {
namespace react {
void addNativeProfilingHooks(JSGlobalContextRef ctx);
void stopAndOutputProfilingFile(
JSContextRef ctx,
JSStringRef title,
const char *filename);
} }
#endif

View File

@@ -0,0 +1,48 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include <stdio.h>
#include <string.h>
#include <JavaScriptCore/JavaScript.h>
#include <JavaScriptCore/API/JSProfilerPrivate.h>
#include "JSCHelpers.h"
#include "Value.h"
#ifdef WITH_FB_MEMORY_PROFILING
static JSValueRef nativeCaptureHeap(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
if (argumentCount < 1) {
if (exception) {
*exception = facebook::react::makeJSCException(
ctx,
"nativeCaptureHeap requires the path to save the capture");
}
return JSValueMakeUndefined(ctx);
}
JSStringRef outputFilename = JSValueToStringCopy(ctx, arguments[0], exception);
std::string finalFilename = facebook::react::String::ref(outputFilename).str();
JSCaptureHeap(ctx, finalFilename.c_str(), exception);
JSStringRelease(outputFilename);
return JSValueMakeUndefined(ctx);
}
#endif // WITH_FB_MEMORY_PROFILING
namespace facebook {
namespace react {
void addNativeMemoryHooks(JSGlobalContextRef ctx) {
#ifdef WITH_FB_MEMORY_PROFILING
installGlobalFunction(ctx, "nativeCaptureHeap", nativeCaptureHeap);
#endif // WITH_FB_MEMORY_PROFILING
}
} }

View File

@@ -0,0 +1,11 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <JavaScriptCore/JSContextRef.h>
namespace facebook {
namespace react {
void addNativeMemoryHooks(JSGlobalContextRef ctx);
} }

View File

@@ -0,0 +1,65 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "JSCPerfStats.h"
#ifdef JSC_HAS_PERF_STATS_API
#include <JavaScriptCore/JSPerfStats.h>
#include <JavaScriptCore/JSValueRef.h>
#include "JSCHelpers.h"
#include "Value.h"
static JSValueRef nativeGetHeapStats(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
JSHeapStats heapStats = {0};
JSGetHeapStats(ctx, &heapStats);
auto result = facebook::react::Object::create(ctx);
result.setProperty("size", {ctx, JSValueMakeNumber(ctx, heapStats.size)});
result.setProperty("extra_size", {ctx, JSValueMakeNumber(ctx, heapStats.extraSize)});
result.setProperty("capacity", {ctx, JSValueMakeNumber(ctx, heapStats.capacity)});
result.setProperty("object_count", {ctx, JSValueMakeNumber(ctx, heapStats.objectCount)});
return (JSObjectRef) result;
}
static JSValueRef nativeGetGCStats(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
JSGCStats gcStats = {0};
JSGetGCStats(ctx, &gcStats);
auto result = facebook::react::Object::create(ctx);
result.setProperty(
"last_full_gc_length",
{ctx, JSValueMakeNumber(ctx, gcStats.lastFullGCLength)});
result.setProperty(
"last_eden_gc_length",
{ctx, JSValueMakeNumber(ctx, gcStats.lastEdenGCLength)});
return (JSObjectRef) result;
}
#endif
namespace facebook {
namespace react {
void addJSCPerfStatsHooks(JSGlobalContextRef ctx) {
#ifdef JSC_HAS_PERF_STATS_API
installGlobalFunction(ctx, "nativeGetHeapStats", nativeGetHeapStats);
installGlobalFunction(ctx, "nativeGetGCStats", nativeGetGCStats);
#endif
}
} }

View File

@@ -0,0 +1,12 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <JavaScriptCore/JSContextRef.h>
namespace facebook {
namespace react {
void addJSCPerfStatsHooks(JSGlobalContextRef ctx);
} }

View File

@@ -0,0 +1,482 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#ifdef WITH_JSC_EXTRA_TRACING
#include <algorithm>
#include <JavaScriptCore/JavaScript.h>
#include <JavaScriptCore/API/JSProfilerPrivate.h>
#include <fbsystrace.h>
#include <sys/types.h>
#include <unistd.h>
#include "JSCHelpers.h"
using std::min;
static const char *ENABLED_FBSYSTRACE_PROFILE_NAME = "__fbsystrace__";
static uint64_t tagFromJSValue(
JSContextRef ctx,
JSValueRef value,
JSValueRef* exception) {
// XXX validate that this is a lossless conversion.
// XXX should we just have separate functions for bridge, infra, and apps,
// then drop this argument to save time?
(void)exception;
uint64_t tag = (uint64_t) JSValueToNumber(ctx, value, NULL);
return tag;
}
static int64_t int64FromJSValue(
JSContextRef ctx,
JSValueRef value,
JSValueRef* exception) {
(void)exception;
int64_t num = (int64_t) JSValueToNumber(ctx, value, NULL);
return num;
}
static size_t copyTruncatedAsciiChars(
char* buf,
size_t bufLen,
JSContextRef ctx,
JSValueRef value,
size_t maxLen) {
JSStringRef jsString = JSValueToStringCopy(ctx, value, NULL);
size_t stringLen = JSStringGetLength(jsString);
// Unlike the Java version, we truncate from the end of the string,
// rather than the beginning.
size_t toWrite = min(stringLen, min(bufLen, maxLen));
const char *startBuf = buf;
const JSChar* chars = JSStringGetCharactersPtr(jsString);
while (toWrite-- > 0) {
*(buf++) = (char)*(chars++);
}
JSStringRelease(jsString);
// Return the number of bytes written
return buf - startBuf;
}
static size_t copyArgsToBuffer(
char* buf,
size_t bufLen,
size_t pos,
JSContextRef ctx,
size_t argumentCount,
const JSValueRef arguments[]) {
char separator = '|';
for (
size_t idx = 0;
idx + 1 < argumentCount; // Make sure key and value are present.
idx += 2) {
JSValueRef key = arguments[idx];
JSValueRef value = arguments[idx+1];
buf[pos++] = separator;
separator = ';';
if (FBSYSTRACE_UNLIKELY(pos >= bufLen)) { break; }
pos += copyTruncatedAsciiChars(
buf + pos, bufLen - pos, ctx, key, FBSYSTRACE_MAX_MESSAGE_LENGTH);
if (FBSYSTRACE_UNLIKELY(pos >= bufLen)) { break; }
buf[pos++] = '=';
if (FBSYSTRACE_UNLIKELY(pos >= bufLen)) { break; }
pos += copyTruncatedAsciiChars(
buf + pos, bufLen - pos, ctx, value, FBSYSTRACE_MAX_MESSAGE_LENGTH);
if (FBSYSTRACE_UNLIKELY(pos >= bufLen)) { break; }
}
return pos;
}
static JSValueRef nativeTraceBeginSection(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
if (FBSYSTRACE_UNLIKELY(argumentCount < 2)) {
if (exception) {
*exception = facebook::react::makeJSCException(
ctx,
"nativeTraceBeginSection: requires at least 2 arguments");
}
return JSValueMakeUndefined(ctx);
}
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
if (!fbsystrace_is_tracing(tag)) {
return JSValueMakeUndefined(ctx);
}
char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH];
size_t pos = 0;
pos += snprintf(buf + pos, sizeof(buf) - pos, "B|%d|", getpid());
// Skip the overflow check here because the int will be small.
pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[1], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
// Skip the overflow check here because the section name will be small-ish.
pos = copyArgsToBuffer(buf, sizeof(buf), pos, ctx, argumentCount - 2, arguments + 2);
if (FBSYSTRACE_UNLIKELY(pos >= sizeof(buf))) {
goto flush;
}
flush:
fbsystrace_trace_raw(buf, min(pos, sizeof(buf)-1));
return JSValueMakeUndefined(ctx);
}
static JSValueRef nativeTraceEndSection(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
if (FBSYSTRACE_UNLIKELY(argumentCount < 1)) {
if (exception) {
*exception = facebook::react::makeJSCException(
ctx,
"nativeTraceEndSection: requires at least 1 argument");
}
return JSValueMakeUndefined(ctx);
}
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
if (!fbsystrace_is_tracing(tag)) {
return JSValueMakeUndefined(ctx);
}
if (FBSYSTRACE_LIKELY(argumentCount == 1)) {
fbsystrace_end_section(tag);
} else {
char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH];
size_t pos = 0;
buf[pos++] = 'E';
buf[pos++] = '|';
buf[pos++] = '|';
pos = copyArgsToBuffer(buf, sizeof(buf), pos, ctx, argumentCount - 1, arguments + 1);
if (FBSYSTRACE_UNLIKELY(pos >= sizeof(buf))) {
goto flush;
}
flush:
fbsystrace_trace_raw(buf, min(pos, sizeof(buf)-1));
}
return JSValueMakeUndefined(ctx);
}
static JSValueRef beginOrEndAsync(
bool isEnd,
bool isFlow,
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
if (FBSYSTRACE_UNLIKELY(argumentCount < 3)) {
if (exception) {
*exception = facebook::react::makeJSCException(
ctx,
"beginOrEndAsync: requires at least 3 arguments");
}
return JSValueMakeUndefined(ctx);
}
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
if (!fbsystrace_is_tracing(tag)) {
return JSValueMakeUndefined(ctx);
}
char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH];
size_t pos = 0;
// This uses an if-then-else instruction in ARMv7, which should be cheaper
// than a full branch.
buf[pos++] = ((isFlow) ? (isEnd ? 'f' : 's') : (isEnd ? 'F' : 'S'));
pos += snprintf(buf + pos, sizeof(buf) - pos, "|%d|", getpid());
// Skip the overflow check here because the int will be small.
pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[1], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
// Skip the overflow check here because the section name will be small-ish.
// I tried some trickery to avoid a branch here, but gcc did not cooperate.
// We could consider changing the implementation to be lest branchy in the
// future.
// This is not required for flow use an or to avoid introducing another branch
if (!(isEnd | isFlow)) {
buf[pos++] = '<';
buf[pos++] = '0';
buf[pos++] = '>';
}
buf[pos++] = '|';
// Append the cookie. It should be an integer, but copyTruncatedAsciiChars
// will automatically convert it to a string. We might be able to get more
// performance by just getting the number and doing to string
// conversion ourselves. We truncate to FBSYSTRACE_MAX_SECTION_NAME_LENGTH
// just to make sure we can avoid the overflow check even if the caller
// passes in something bad.
pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[2], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
pos = copyArgsToBuffer(buf, sizeof(buf), pos, ctx, argumentCount - 3, arguments + 3);
if (FBSYSTRACE_UNLIKELY(pos >= sizeof(buf))) {
goto flush;
}
flush:
fbsystrace_trace_raw(buf, min(pos, sizeof(buf)-1));
return JSValueMakeUndefined(ctx);
}
static JSValueRef stageAsync(
bool isFlow,
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
if (FBSYSTRACE_UNLIKELY(argumentCount < 4)) {
if (exception) {
*exception = facebook::react::makeJSCException(
ctx,
"stageAsync: requires at least 4 arguments");
}
return JSValueMakeUndefined(ctx);
}
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
if (!fbsystrace_is_tracing(tag)) {
return JSValueMakeUndefined(ctx);
}
char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH];
size_t pos = 0;
buf[pos++] = (isFlow ? 't' : 'T');
pos += snprintf(buf + pos, sizeof(buf) - pos, "|%d", getpid());
// Skip the overflow check here because the int will be small.
// Arguments are section name, cookie, and stage name.
// All added together, they still cannot cause an overflow.
for (int i = 1; i < 4; i++) {
buf[pos++] = '|';
pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[i], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
}
fbsystrace_trace_raw(buf, min(pos, sizeof(buf)-1));
return JSValueMakeUndefined(ctx);
}
static JSValueRef nativeTraceBeginAsyncSection(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
return beginOrEndAsync(
false /* isEnd */,
false /* isFlow */,
ctx,
function,
thisObject,
argumentCount,
arguments,
exception);
}
static JSValueRef nativeTraceEndAsyncSection(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
return beginOrEndAsync(
true /* isEnd */,
false /* isFlow */,
ctx,
function,
thisObject,
argumentCount,
arguments,
exception);
}
static JSValueRef nativeTraceAsyncSectionStage(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
return stageAsync(
false /* isFlow */,
ctx,
function,
thisObject,
argumentCount,
arguments,
exception);
}
static JSValueRef nativeTraceBeginAsyncFlow(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
return beginOrEndAsync(
false /* isEnd */,
true /* isFlow */,
ctx,
function,
thisObject,
argumentCount,
arguments,
exception);
}
static JSValueRef nativeTraceEndAsyncFlow(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
return beginOrEndAsync(
true /* isEnd */,
true /* isFlow */,
ctx,
function,
thisObject,
argumentCount,
arguments,
exception);
}
static JSValueRef nativeTraceAsyncFlowStage(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
return stageAsync(
true /* isFlow */,
ctx,
function,
thisObject,
argumentCount,
arguments,
exception);
}
static JSValueRef nativeTraceCounter(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
if (FBSYSTRACE_UNLIKELY(argumentCount < 3)) {
if (exception) {
*exception = facebook::react::makeJSCException(
ctx,
"nativeTraceCounter: requires at least 3 arguments");
}
return JSValueMakeUndefined(ctx);
}
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
if (!fbsystrace_is_tracing(tag)) {
return JSValueMakeUndefined(ctx);
}
char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH];
size_t len = copyTruncatedAsciiChars(buf, sizeof(buf), ctx,
arguments[1], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
buf[min(len,(FBSYSTRACE_MAX_MESSAGE_LENGTH-1))] = 0;
int64_t value = int64FromJSValue(ctx, arguments[2], exception);
fbsystrace_counter(tag, buf, value);
return JSValueMakeUndefined(ctx);
}
static JSValueRef nativeTraceBeginLegacy(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
if (FBSYSTRACE_LIKELY(argumentCount >= 1)) {
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
if (!fbsystrace_is_tracing(tag)) {
return JSValueMakeUndefined(ctx);
}
}
JSStringRef title = JSStringCreateWithUTF8CString(ENABLED_FBSYSTRACE_PROFILE_NAME);
#if WITH_REACT_INTERNAL_SETTINGS
JSStartProfiling(ctx, title, true);
#else
JSStartProfiling(ctx, title);
#endif
JSStringRelease(title);
return JSValueMakeUndefined(ctx);
}
static JSValueRef nativeTraceEndLegacy(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef* exception) {
if (FBSYSTRACE_LIKELY(argumentCount >= 1)) {
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
if (!fbsystrace_is_tracing(tag)) {
return JSValueMakeUndefined(ctx);
}
}
JSStringRef title = JSStringCreateWithUTF8CString(ENABLED_FBSYSTRACE_PROFILE_NAME);
JSEndProfiling(ctx, title);
JSStringRelease(title);
return JSValueMakeUndefined(ctx);
}
namespace facebook {
namespace react {
void addNativeTracingHooks(JSGlobalContextRef ctx) {
installGlobalFunction(ctx, "nativeTraceBeginSection", nativeTraceBeginSection);
installGlobalFunction(ctx, "nativeTraceEndSection", nativeTraceEndSection);
installGlobalFunction(ctx, "nativeTraceBeginLegacy", nativeTraceBeginLegacy);
installGlobalFunction(ctx, "nativeTraceEndLegacy", nativeTraceEndLegacy);
installGlobalFunction(ctx, "nativeTraceBeginAsyncSection", nativeTraceBeginAsyncSection);
installGlobalFunction(ctx, "nativeTraceEndAsyncSection", nativeTraceEndAsyncSection);
installGlobalFunction(ctx, "nativeTraceAsyncSectionStage", nativeTraceAsyncSectionStage);
installGlobalFunction(ctx, "nativeTraceBeginAsyncFlow", nativeTraceBeginAsyncFlow);
installGlobalFunction(ctx, "nativeTraceEndAsyncFlow", nativeTraceEndAsyncFlow);
installGlobalFunction(ctx, "nativeTraceAsyncFlowStage", nativeTraceAsyncFlowStage);
installGlobalFunction(ctx, "nativeTraceCounter", nativeTraceCounter);
}
} }
#endif

View File

@@ -0,0 +1,15 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#ifdef WITH_JSC_EXTRA_TRACING
#include <JavaScriptCore/JSContextRef.h>
namespace facebook {
namespace react {
void addNativeTracingHooks(JSGlobalContextRef ctx);
} }
#endif

View File

@@ -0,0 +1,137 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "JSCWebWorker.h"
#include <unistd.h>
#include <condition_variable>
#include <mutex>
#include <unordered_map>
#include <glog/logging.h>
#include <folly/Memory.h>
#include "JSCHelpers.h"
#include "MessageQueueThread.h"
#include "Platform.h"
#include "Value.h"
#include <JavaScriptCore/JSValueRef.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_);
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_ = JSGlobalContextCreateInGroup(
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::string script = WebWorkerUtil::loadScriptFromAssets(scriptName_);
evaluateScript(context_, String(script.c_str()), String(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 = makeJSCException(ctx, "postMessage got wrong number of arguments");
return JSValueMakeUndefined(ctx);
}
JSValueRef msg = arguments[0];
JSCWebWorker *webWorker = s_globalContextRefToJSCWebWorker.at(JSContextGetGlobalContext(ctx));
if (webWorker->isTerminated()) {
return JSValueMakeUndefined(ctx);
}
webWorker->postMessageToOwner(msg);
return JSValueMakeUndefined(ctx);
}
/*static*/
Object JSCWebWorker::createMessageObject(JSContextRef context, const std::string& msgJson) {
Value rebornJSMsg = Value::fromJSON(context, String(msgJson.c_str()));
Object messageObject = Object::create(context);
messageObject.setProperty("data", rebornJSMsg);
return messageObject;
}
}
}

View File

@@ -0,0 +1,94 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include <atomic>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <queue>
#include <JavaScriptCore/JSValueRef.h>
#include "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);
};
}
}

View File

@@ -0,0 +1,35 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <cstdint>
#include <string>
#include <stdexcept>
#include "noncopyable.h"
namespace facebook {
namespace react {
class JSModulesUnbundle : noncopyable {
/**
* Represents the set of JavaScript modules that the application consists of.
* The source code of each module can be retrieved by module ID.
*
* The class is non-copyable because copying instances might involve copying
* several megabytes of memory.
*/
public:
class ModuleNotFound : public std::out_of_range {
using std::out_of_range::out_of_range;
};
struct Module {
std::string name;
std::string code;
};
virtual ~JSModulesUnbundle() {}
virtual Module getModule(uint32_t moduleId) const = 0;
};
}
}

View File

@@ -0,0 +1,23 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <condition_variable>
#include <functional>
#include <mutex>
namespace facebook {
namespace react {
class MessageQueueThread {
public:
virtual ~MessageQueueThread() {}
virtual void runOnQueue(std::function<void()>&&) = 0;
// runOnQueueSync and quitSynchronous are dangerous. They should only be
// used for initialization and cleanup.
virtual void runOnQueueSync(std::function<void()>&&) = 0;
// Once quitSynchronous() returns, no further work should run on the queue.
virtual void quitSynchronous() = 0;
};
}}

View File

@@ -0,0 +1,74 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "MethodCall.h"
#include <folly/json.h>
#include <stdexcept>
namespace facebook {
namespace react {
#define REQUEST_MODULE_IDS 0
#define REQUEST_METHOD_IDS 1
#define REQUEST_PARAMSS 2
#define REQUEST_CALLID 3
std::vector<MethodCall> parseMethodCalls(const std::string& json) {
folly::dynamic jsonData = folly::parseJson(json);
if (jsonData.isNull()) {
return {};
}
if (!jsonData.isArray()) {
throw std::invalid_argument(
folly::to<std::string>("Did not get valid calls back from JS: ", jsonData.typeName()));
}
if (jsonData.size() < REQUEST_PARAMSS + 1) {
throw std::invalid_argument(
folly::to<std::string>("Did not get valid calls back from JS: size == ", jsonData.size()));
}
auto moduleIds = jsonData[REQUEST_MODULE_IDS];
auto methodIds = jsonData[REQUEST_METHOD_IDS];
auto params = jsonData[REQUEST_PARAMSS];
int callId = -1;
if (!moduleIds.isArray() || !methodIds.isArray() || !params.isArray()) {
throw std::invalid_argument(
folly::to<std::string>("Did not get valid calls back from JS: ", json.c_str()));
}
if (jsonData.size() > REQUEST_CALLID) {
if (!jsonData[REQUEST_CALLID].isInt()) {
throw std::invalid_argument(
folly::to<std::string>("Did not get valid calls back from JS: %s", json.c_str()));
} else {
callId = jsonData[REQUEST_CALLID].getInt();
}
}
std::vector<MethodCall> methodCalls;
for (size_t i = 0; i < moduleIds.size(); i++) {
auto paramsValue = params[i];
if (!paramsValue.isArray()) {
throw std::invalid_argument(
folly::to<std::string>("Call argument isn't an array"));
}
methodCalls.emplace_back(
moduleIds[i].getInt(),
methodIds[i].getInt(),
std::move(params[i]),
callId);
// only incremement callid if contains valid callid as callid is optional
callId += (callId != -1) ? 1 : 0;
}
return methodCalls;
}
}}

View File

@@ -0,0 +1,29 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <string>
#include <vector>
#include <map>
#include <folly/dynamic.h>
namespace facebook {
namespace react {
struct MethodCall {
int moduleId;
int methodId;
folly::dynamic arguments;
int callId;
MethodCall(int mod, int meth, folly::dynamic args, int cid)
: moduleId(mod)
, methodId(meth)
, arguments(std::move(args))
, callId(cid) {}
};
std::vector<MethodCall> parseMethodCalls(const std::string& json);
} }

View File

@@ -0,0 +1,114 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "ModuleRegistry.h"
#include "NativeModule.h"
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
using fbsystrace::FbSystraceSection;
#endif
namespace facebook {
namespace react {
ModuleRegistry::ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules)
: modules_(std::move(modules)) {}
folly::dynamic ModuleRegistry::moduleDescriptions() {
folly::dynamic modDescs = folly::dynamic::object;
for (size_t moduleId = 0; moduleId < modules_.size(); ++moduleId) {
const auto& module = modules_[moduleId];
folly::dynamic methodDescs = folly::dynamic::object;
std::vector<MethodDescriptor> methods;
{
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "getMethods", "module", module->getName());
#endif
methods = module->getMethods();
}
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();
{
#ifdef WITH_FBSYSTRACE
FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "getConstants", "module", module->getName());
#endif
constants = module->getConstants();
}
modDescs.insert(module->getName(),
folly::dynamic::object
("supportsWebWorkers", module->supportsWebWorkers())
("moduleID", moduleId)
("methods", std::move(methodDescs))
("constants", std::move(constants)));
}
return modDescs;
}
void ModuleRegistry::callNativeMethod(ExecutorToken token, 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(), ")"));
}
#ifdef WITH_FBSYSTRACE
if (callId != -1) {
fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
}
#endif
// TODO mhorowitz: systrace
std::string what;
try {
modules_[moduleId]->invoke(token, methodId, std::move(params));
return;
} catch (const std::exception& e) {
what = e.what();
// fall through;
} catch (...) {
// fall through;
}
std::string moduleName = modules_[moduleId]->getName();
auto descs = modules_[moduleId]->getMethods();
std::string methodName;
if (methodId < descs.size()) {
methodName = descs[methodId].name;
} else {
methodName = folly::to<std::string>("id ", methodId, " (out of range [0..",
descs.size(), "))");
}
if (what.empty()) {
throw std::runtime_error(
folly::to<std::string>("Unknown native exception in module '", moduleName,
"' method '", methodName, "'"));
} else {
throw std::runtime_error(
folly::to<std::string>("Native exception in module '", moduleName,
"' method '", methodName, "': ", what));
}
}
MethodCallResult ModuleRegistry::callSerializableNativeHook(ExecutorToken token, 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));
}
}}

View File

@@ -0,0 +1,38 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <memory>
#include <vector>
#include <folly/dynamic.h>
#include "ExecutorToken.h"
#include "NativeModule.h"
namespace facebook {
namespace react {
class NativeModule;
class ModuleRegistry {
public:
// not implemented:
// onBatchComplete: see https://our.intern.facebook.com/intern/tasks/?t=5279396
// getModule: only used by views
// getAllModules: only used for cleanup; use RAII instead
// notifyCatalystInstanceInitialized: this is really only used by view-related code
// notifyCatalystInstanceDestroy: use RAII instead
ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules);
folly::dynamic moduleDescriptions();
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:
std::vector<std::unique_ptr<NativeModule>> modules_;
};
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <string>
#include <vector>
#include <folly/dynamic.h>
#include "ExecutorToken.h"
namespace facebook {
namespace react {
struct MethodCallResult {
folly::dynamic result;
bool isUndefined;
};
struct MethodDescriptor {
std::string name;
// type is one of js MessageQueue.MethodTypes
std::string type;
MethodDescriptor(std::string n, std::string t)
: name(std::move(n))
, type(std::move(t)) {}
};
class NativeModule {
public:
virtual ~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;
};
}
}

View File

@@ -0,0 +1,27 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "Platform.h"
namespace facebook {
namespace react {
namespace ReactMarker {
LogMarker logMarker;
};
namespace WebWorkerUtil {
WebWorkerQueueFactory createWebWorkerThread;
LoadScriptFromAssets loadScriptFromAssets;
LoadScriptFromNetworkSync loadScriptFromNetworkSync;
};
namespace PerfLogging {
InstallNativeHooks installNativeHooks;
};
namespace JSNativeHooks {
Hook loggingHook = nullptr;
Hook nowHook = nullptr;
}
} }

View File

@@ -0,0 +1,49 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <JavaScriptCore/JSContextRef.h>
#include "MessageQueueThread.h"
namespace facebook {
namespace react {
namespace ReactMarker {
using LogMarker = std::function<void(const std::string&)>;
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::string(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;
};
namespace JSNativeHooks {
using Hook = JSValueRef (*) (
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception);
extern Hook loggingHook;
extern Hook nowHook;
}
} }

View File

@@ -0,0 +1,142 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "Value.h"
#include "JSCHelpers.h"
namespace facebook {
namespace react {
Value::Value(JSContextRef context, JSValueRef value) :
m_context(context),
m_value(value)
{
}
Value::Value(Value&& other) :
m_context(other.m_context),
m_value(other.m_value)
{
other.m_value = nullptr;
}
JSContextRef Value::context() const {
return m_context;
}
std::string Value::toJSONString(unsigned indent) const {
JSValueRef exn;
auto stringToAdopt = JSValueCreateJSONString(m_context, m_value, indent, &exn);
if (stringToAdopt == nullptr) {
std::string exceptionText = Value(m_context, exn).toString().str();
throwJSExecutionException("Exception creating JSON string: %s", exceptionText.c_str());
}
return String::adopt(stringToAdopt).str();
}
/* static */
Value Value::fromJSON(JSContextRef ctx, const String& json) {
auto result = JSValueMakeFromJSONString(ctx, json);
if (!result) {
throwJSExecutionException("Failed to create String from JSON");
}
return Value(ctx, result);
}
Object Value::asObject() {
JSValueRef exn;
JSObjectRef jsObj = JSValueToObject(context(), m_value, &exn);
if (!jsObj) {
std::string exceptionText = Value(m_context, exn).toString().str();
throwJSExecutionException("Failed to convert to object: %s", exceptionText.c_str());
}
Object ret = Object(context(), jsObj);
m_value = nullptr;
return ret;
}
Object::operator Value() const {
return Value(m_context, m_obj);
}
Value Object::callAsFunction(int nArgs, JSValueRef args[]) {
JSValueRef exn;
JSValueRef result = JSObjectCallAsFunction(m_context, m_obj, NULL, nArgs, args, &exn);
if (!result) {
std::string exceptionText = Value(m_context, exn).toString().str();
throwJSExecutionException("Exception calling JS function: %s", exceptionText.c_str());
}
return Value(m_context, result);
}
Value Object::getProperty(const String& propName) const {
JSValueRef exn;
JSValueRef property = JSObjectGetProperty(m_context, m_obj, propName, &exn);
if (!property) {
std::string exceptionText = Value(m_context, exn).toString().str();
throwJSExecutionException("Failed to get property: %s", exceptionText.c_str());
}
return Value(m_context, property);
}
Value Object::getPropertyAtIndex(unsigned index) const {
JSValueRef exn;
JSValueRef property = JSObjectGetPropertyAtIndex(m_context, m_obj, index, &exn);
if (!property) {
std::string exceptionText = Value(m_context, exn).toString().str();
throwJSExecutionException("Failed to get property at index %u: %s", index, exceptionText.c_str());
}
return Value(m_context, property);
}
Value Object::getProperty(const char *propName) const {
return getProperty(String(propName));
}
void Object::setProperty(const String& propName, const Value& value) const {
JSValueRef exn = NULL;
JSObjectSetProperty(m_context, m_obj, propName, value, kJSPropertyAttributeNone, &exn);
if (exn) {
std::string exceptionText = Value(m_context, exn).toString().str();
throwJSExecutionException("Failed to set property: %s", exceptionText.c_str());
}
}
void Object::setProperty(const char *propName, const Value& value) const {
setProperty(String(propName), value);
}
std::vector<std::string> Object::getPropertyNames() const {
std::vector<std::string> names;
auto namesRef = JSObjectCopyPropertyNames(m_context, m_obj);
size_t count = JSPropertyNameArrayGetCount(namesRef);
for (size_t i = 0; i < count; i++) {
auto string = String::ref(JSPropertyNameArrayGetNameAtIndex(namesRef, i));
names.emplace_back(string.str());
}
JSPropertyNameArrayRelease(namesRef);
return names;
}
std::unordered_map<std::string, std::string> Object::toJSONMap() const {
std::unordered_map<std::string, std::string> map;
auto namesRef = JSObjectCopyPropertyNames(m_context, m_obj);
size_t count = JSPropertyNameArrayGetCount(namesRef);
for (size_t i = 0; i < count; i++) {
auto key = String::ref(JSPropertyNameArrayGetNameAtIndex(namesRef, i));
map.emplace(key.str(), getProperty(key).toJSONString());
}
JSPropertyNameArrayRelease(namesRef);
return map;
}
/* static */
Object Object::create(JSContextRef ctx) {
JSObjectRef newObj = JSObjectMake(
ctx,
NULL, // create instance of default object class
NULL); // no private data
return Object(ctx, newObj);
}
} }

238
ReactCommon/bridge/Value.h Normal file
View File

@@ -0,0 +1,238 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
#include <memory>
#include <sstream>
#include <unordered_map>
#include <vector>
#include <JavaScriptCore/JSContextRef.h>
#include <JavaScriptCore/JSObjectRef.h>
#include <JavaScriptCore/JSStringRef.h>
#include <JavaScriptCore/JSValueRef.h>
#include "noncopyable.h"
#if WITH_FBJSCEXTENSIONS
#include <jsc_stringref.h>
#endif
namespace facebook {
namespace react {
class Value;
class Context;
class String : public noncopyable {
public:
explicit String(const char* utf8) :
m_string(JSStringCreateWithUTF8CString(utf8))
{}
String(String&& other) :
m_string(other.m_string)
{}
String(const String& other) :
m_string(other.m_string)
{
if (m_string) {
JSStringRetain(m_string);
}
}
~String() {
if (m_string) {
JSStringRelease(m_string);
}
}
operator JSStringRef() const {
return m_string;
}
// Length in characters
size_t length() const {
return JSStringGetLength(m_string);
}
// Length in bytes of a null-terminated utf8 encoded value
size_t utf8Size() const {
return JSStringGetMaximumUTF8CStringSize(m_string);
}
std::string str() const {
size_t reserved = utf8Size();
char* bytes = new char[reserved];
size_t length = JSStringGetUTF8CString(m_string, bytes, reserved) - 1;
std::unique_ptr<char[]> retainedBytes(bytes);
return std::string(bytes, length);
}
// Assumes that utf8 is null terminated
bool equals(const char* utf8) {
return JSStringIsEqualToUTF8CString(m_string, utf8);
}
static String createExpectingAscii(std::string const &utf8) {
#if WITH_FBJSCEXTENSIONS
return String(
JSStringCreateWithUTF8CStringExpectAscii(utf8.c_str(), utf8.size()), true);
#else
return String(JSStringCreateWithUTF8CString(utf8.c_str()), true);
#endif
}
static String ref(JSStringRef string) {
return String(string, false);
}
static String adopt(JSStringRef string) {
return String(string, true);
}
private:
explicit String(JSStringRef string, bool adopt) :
m_string(string)
{
if (!adopt && string) {
JSStringRetain(string);
}
}
JSStringRef m_string;
};
class Object : public noncopyable {
public:
Object(JSContextRef context, JSObjectRef obj) :
m_context(context),
m_obj(obj)
{}
Object(Object&& other) :
m_context(other.m_context),
m_obj(other.m_obj),
m_isProtected(other.m_isProtected) {
other.m_obj = nullptr;
other.m_isProtected = false;
}
~Object() {
if (m_isProtected && m_obj) {
JSValueUnprotect(m_context, m_obj);
}
}
operator JSObjectRef() const {
return m_obj;
}
operator Value() const;
bool isFunction() const {
return JSObjectIsFunction(m_context, m_obj);
}
Value callAsFunction(int nArgs, JSValueRef args[]);
Value getProperty(const String& propName) const;
Value getProperty(const char *propName) const;
Value getPropertyAtIndex(unsigned index) const;
void setProperty(const String& propName, const Value& value) const;
void setProperty(const char *propName, const Value& value) const;
std::vector<std::string> getPropertyNames() const;
std::unordered_map<std::string, std::string> toJSONMap() const;
void makeProtected() {
if (!m_isProtected && m_obj) {
JSValueProtect(m_context, m_obj);
m_isProtected = true;
}
}
static Object getGlobalObject(JSContextRef ctx) {
auto globalObj = JSContextGetGlobalObject(ctx);
return Object(ctx, globalObj);
}
/**
* Creates an instance of the default object class.
*/
static Object create(JSContextRef ctx);
private:
JSContextRef m_context;
JSObjectRef m_obj;
bool m_isProtected = false;
};
class Value : public noncopyable {
public:
Value(JSContextRef context, JSValueRef value);
Value(Value&&);
operator JSValueRef() const {
return m_value;
}
bool isBoolean() const {
return JSValueIsBoolean(context(), m_value);
}
bool asBoolean() const {
return JSValueToBoolean(context(), m_value);
}
bool isNumber() const {
return JSValueIsNumber(context(), m_value);
}
bool isNull() const {
return JSValueIsNull(context(), m_value);
}
bool isUndefined() const {
return JSValueIsUndefined(context(), m_value);
}
double asNumber() const {
if (isNumber()) {
return JSValueToNumber(context(), m_value, nullptr);
} else {
return 0.0f;
}
}
int32_t asInteger() const {
return static_cast<int32_t>(asNumber());
}
uint32_t asUnsignedInteger() const {
return static_cast<uint32_t>(asNumber());
}
bool isObject() const {
return JSValueIsObject(context(), m_value);
}
Object asObject();
bool isString() const {
return JSValueIsString(context(), m_value);
}
String toString() {
return String::adopt(JSValueToStringCopy(context(), m_value, nullptr));
}
std::string toJSONString(unsigned indent = 0) const;
static Value fromJSON(JSContextRef ctx, const String& json);
protected:
JSContextRef context() const;
JSContextRef m_context;
JSValueRef m_value;
};
} }

View File

@@ -0,0 +1,12 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#pragma once
namespace facebook {
namespace react {
struct noncopyable {
noncopyable(const noncopyable&) = delete;
noncopyable& operator=(const noncopyable&) = delete;
protected:
noncopyable() = default;
};
}}