mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-22 11:16:06 +08:00
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:
committed by
Facebook Github Bot 5
parent
1d802da7d2
commit
24fe8b7e92
85
ReactCommon/bridge/BUCK
Normal file
85
ReactCommon/bridge/BUCK
Normal 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' ],
|
||||
)
|
||||
269
ReactCommon/bridge/Bridge.cpp
Normal file
269
ReactCommon/bridge/Bridge.cpp
Normal 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
173
ReactCommon/bridge/Bridge.h
Normal 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);
|
||||
};
|
||||
|
||||
} }
|
||||
80
ReactCommon/bridge/Executor.h
Normal file
80
ReactCommon/bridge/Executor.h
Normal 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() {};
|
||||
};
|
||||
|
||||
} }
|
||||
54
ReactCommon/bridge/ExecutorToken.h
Normal file
54
ReactCommon/bridge/ExecutorToken.h
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
25
ReactCommon/bridge/ExecutorTokenFactory.h
Normal file
25
ReactCommon/bridge/ExecutorTokenFactory.h
Normal 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;
|
||||
};
|
||||
|
||||
} }
|
||||
226
ReactCommon/bridge/Instance.cpp
Normal file
226
ReactCommon/bridge/Instance.cpp
Normal 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
|
||||
68
ReactCommon/bridge/Instance.h
Normal file
68
ReactCommon/bridge/Instance.h
Normal 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_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
653
ReactCommon/bridge/JSCExecutor.cpp
Normal file
653
ReactCommon/bridge/JSCExecutor.cpp
Normal 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);
|
||||
}
|
||||
|
||||
} }
|
||||
171
ReactCommon/bridge/JSCExecutor.h
Normal file
171
ReactCommon/bridge/JSCExecutor.h
Normal 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);
|
||||
};
|
||||
|
||||
} }
|
||||
55
ReactCommon/bridge/JSCHelpers.cpp
Normal file
55
ReactCommon/bridge/JSCHelpers.cpp
Normal 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;
|
||||
}
|
||||
|
||||
} }
|
||||
46
ReactCommon/bridge/JSCHelpers.h
Normal file
46
ReactCommon/bridge/JSCHelpers.h
Normal 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);
|
||||
|
||||
} }
|
||||
88
ReactCommon/bridge/JSCLegacyProfiler.cpp
Normal file
88
ReactCommon/bridge/JSCLegacyProfiler.cpp
Normal 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
|
||||
19
ReactCommon/bridge/JSCLegacyProfiler.h
Normal file
19
ReactCommon/bridge/JSCLegacyProfiler.h
Normal 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
|
||||
48
ReactCommon/bridge/JSCMemory.cpp
Normal file
48
ReactCommon/bridge/JSCMemory.cpp
Normal 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
|
||||
|
||||
}
|
||||
|
||||
} }
|
||||
11
ReactCommon/bridge/JSCMemory.h
Normal file
11
ReactCommon/bridge/JSCMemory.h
Normal 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);
|
||||
|
||||
} }
|
||||
65
ReactCommon/bridge/JSCPerfStats.cpp
Normal file
65
ReactCommon/bridge/JSCPerfStats.cpp
Normal 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
|
||||
}
|
||||
|
||||
} }
|
||||
12
ReactCommon/bridge/JSCPerfStats.h
Normal file
12
ReactCommon/bridge/JSCPerfStats.h
Normal 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);
|
||||
|
||||
} }
|
||||
482
ReactCommon/bridge/JSCTracing.cpp
Normal file
482
ReactCommon/bridge/JSCTracing.cpp
Normal 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
|
||||
15
ReactCommon/bridge/JSCTracing.h
Normal file
15
ReactCommon/bridge/JSCTracing.h
Normal 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
|
||||
137
ReactCommon/bridge/JSCWebWorker.cpp
Normal file
137
ReactCommon/bridge/JSCWebWorker.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
94
ReactCommon/bridge/JSCWebWorker.h
Normal file
94
ReactCommon/bridge/JSCWebWorker.h
Normal 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);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
35
ReactCommon/bridge/JSModulesUnbundle.h
Normal file
35
ReactCommon/bridge/JSModulesUnbundle.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
23
ReactCommon/bridge/MessageQueueThread.h
Normal file
23
ReactCommon/bridge/MessageQueueThread.h
Normal 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;
|
||||
};
|
||||
|
||||
}}
|
||||
74
ReactCommon/bridge/MethodCall.cpp
Normal file
74
ReactCommon/bridge/MethodCall.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
29
ReactCommon/bridge/MethodCall.h
Normal file
29
ReactCommon/bridge/MethodCall.h
Normal 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);
|
||||
|
||||
} }
|
||||
114
ReactCommon/bridge/ModuleRegistry.cpp
Normal file
114
ReactCommon/bridge/ModuleRegistry.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}}
|
||||
38
ReactCommon/bridge/ModuleRegistry.h
Normal file
38
ReactCommon/bridge/ModuleRegistry.h
Normal 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_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
44
ReactCommon/bridge/NativeModule.h
Normal file
44
ReactCommon/bridge/NativeModule.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
27
ReactCommon/bridge/Platform.cpp
Normal file
27
ReactCommon/bridge/Platform.cpp
Normal 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;
|
||||
}
|
||||
|
||||
} }
|
||||
49
ReactCommon/bridge/Platform.h
Normal file
49
ReactCommon/bridge/Platform.h
Normal 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;
|
||||
}
|
||||
|
||||
} }
|
||||
142
ReactCommon/bridge/Value.cpp
Normal file
142
ReactCommon/bridge/Value.cpp
Normal 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
238
ReactCommon/bridge/Value.h
Normal 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;
|
||||
};
|
||||
|
||||
} }
|
||||
12
ReactCommon/bridge/noncopyable.h
Normal file
12
ReactCommon/bridge/noncopyable.h
Normal 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;
|
||||
};
|
||||
}}
|
||||
Reference in New Issue
Block a user