first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_cxxreact_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_cxxreact OBJECT ${react_cxxreact_SRC})
target_include_directories(react_cxxreact PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_cxxreact
boost
callinvoker
folly_runtime
glog
jsi
jsinspector
logger
reactperflogger
runtimeexecutor
react_debug)
target_compile_reactnative_options(react_cxxreact PRIVATE)
target_compile_options(react_cxxreact PRIVATE -Wno-unused-lambda-capture)

View File

@@ -0,0 +1,221 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <functional>
#include <map>
#include <tuple>
#include <vector>
#include <folly/dynamic.h>
namespace facebook::react {
class Instance;
} // namespace facebook::react
namespace facebook::xplat::module {
/**
* Base class for Catalyst native modules whose implementations are
* written in C++. Native methods are represented by instances of the
* Method struct. Generally, a derived class will manage an instance
* which represents the data for the module, and non-Catalyst-specific
* methods can be wrapped in lambdas which convert between
* folly::dynamic and native C++ objects. The Callback arguments will
* pass through to js functions passed to the analogous javascript
* methods. At most two callbacks will be converted. Results should
* be passed to the first callback, and errors to the second callback.
* Exceptions thrown by a method will be converted to platform
* exceptions, and handled however they are handled on that platform.
* (TODO mhorowitz #7128529: this exception behavior is not yet
* implemented.)
*
* There are two sets of constructors here. The first set initializes
* a Method using a name and anything convertible to a std::function.
* This is most useful for registering a lambda as a RN method. There
* are overloads to support functions which take no arguments,
* arguments only, and zero, one, or two callbacks.
*
* The second set of methods is similar, but instead of taking a
* function, takes the method name, an object, and a pointer to a
* method on that object.
*/
class CxxModule {
class AsyncTagType {};
class SyncTagType {};
public:
using Provider = std::function<std::unique_ptr<CxxModule>()>;
using Callback = std::function<void(std::vector<folly::dynamic>)>;
constexpr static AsyncTagType AsyncTag = AsyncTagType();
constexpr static SyncTagType SyncTag = SyncTagType();
struct Method {
std::string name;
size_t callbacks;
bool isPromise;
std::function<void(folly::dynamic, Callback, Callback)> func;
std::function<folly::dynamic(folly::dynamic)> syncFunc;
const char *getType()
{
assert(func || syncFunc);
return func ? (isPromise ? "promise" : "async") : "sync";
}
// std::function/lambda ctors
Method(std::string aname, std::function<void()> &&afunc)
: name(std::move(aname)), callbacks(0), isPromise(false), func(std::bind(std::move(afunc)))
{
}
Method(std::string aname, std::function<void(folly::dynamic)> &&afunc)
: name(std::move(aname)),
callbacks(0),
isPromise(false),
func(std::bind(std::move(afunc), std::placeholders::_1))
{
}
Method(std::string aname, std::function<void(folly::dynamic, Callback)> &&afunc)
: name(std::move(aname)),
callbacks(1),
isPromise(false),
func(std::bind(std::move(afunc), std::placeholders::_1, std::placeholders::_2))
{
}
Method(std::string aname, std::function<void(folly::dynamic, Callback, Callback)> &&afunc)
: name(std::move(aname)), callbacks(2), isPromise(true), func(std::move(afunc))
{
}
Method(std::string aname, std::function<void(folly::dynamic, Callback, Callback)> &&afunc, AsyncTagType /*unused*/)
: name(std::move(aname)), callbacks(2), isPromise(false), func(std::move(afunc))
{
}
// method pointer ctors
template <typename T>
Method(std::string aname, T *t, void (T::*method)())
: name(std::move(aname)), callbacks(0), isPromise(false), func(std::bind(method, t))
{
}
template <typename T>
Method(std::string aname, T *t, void (T::*method)(folly::dynamic))
: name(std::move(aname)), callbacks(0), isPromise(false), func(std::bind(method, t, std::placeholders::_1))
{
}
template <typename T>
Method(std::string aname, T *t, void (T::*method)(folly::dynamic, Callback))
: name(std::move(aname)),
callbacks(1),
isPromise(false),
func(std::bind(method, t, std::placeholders::_1, std::placeholders::_2))
{
}
template <typename T>
Method(std::string aname, T *t, void (T::*method)(folly::dynamic, Callback, Callback))
: name(std::move(aname)),
callbacks(2),
isPromise(true),
func(std::bind(method, t, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))
{
}
template <typename T>
Method(std::string aname, T *t, void (T::*method)(folly::dynamic, Callback, Callback), AsyncTagType /*unused*/)
: name(std::move(aname)),
callbacks(2),
isPromise(false),
func(std::bind(method, t, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))
{
}
// sync std::function/lambda ctors
// Overloads for functions returning void give ambiguity errors.
// I am not sure if this is a runtime/compiler bug, or a
// limitation I do not understand.
Method(std::string aname, std::function<folly::dynamic()> &&afunc, SyncTagType /*unused*/)
: name(std::move(aname)),
callbacks(0),
isPromise(false),
syncFunc([afunc = std::move(afunc)](const folly::dynamic &) { return afunc(); })
{
}
Method(std::string aname, std::function<folly::dynamic(folly::dynamic)> &&afunc, SyncTagType /*unused*/)
: name(std::move(aname)), callbacks(0), isPromise(false), syncFunc(std::move(afunc))
{
}
};
/**
* This may block, if necessary to complete cleanup before the
* object is destroyed.
*/
virtual ~CxxModule() = default;
/**
* @return the name of this module. This will be the name used to {@code
* require()} this module from javascript.
*/
virtual std::string getName() = 0;
/**
* Each entry in the map will be exported as a property to JS. The
* key is the property name, and the value can be anything.
*/
virtual auto getConstants() -> std::map<std::string, folly::dynamic>
{
return {};
};
/**
* @return a list of methods this module exports to JS.
*/
virtual auto getMethods() -> std::vector<Method> = 0;
/**
* Called during the construction of CxxNativeModule.
*/
void setInstance(std::weak_ptr<react::Instance> instance)
{
instance_ = instance;
}
/**
* @return a weak_ptr to the current instance of the bridge.
* When used with CxxNativeModule, this gives Cxx modules access to functions
* such as `callJSFunction`, allowing them to communicate back to JS outside
* of the regular callbacks.
*/
std::weak_ptr<react::Instance> getInstance()
{
return instance_;
}
private:
std::weak_ptr<react::Instance> instance_;
};
} // namespace facebook::xplat::module

View File

@@ -0,0 +1,258 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "CxxNativeModule.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include "Instance.h"
#include <folly/json.h>
#include <glog/logging.h>
#include <iterator>
#include "JsArgumentHelpers.h"
#include "MessageQueueThread.h"
#include "TraceSection.h"
#include <logger/react_native_log.h>
using facebook::xplat::module::CxxModule;
namespace facebook::react {
std::function<void(folly::dynamic)> makeCallback(
std::weak_ptr<Instance> instance,
const folly::dynamic& callbackId) {
if (!callbackId.isNumber()) {
throw std::invalid_argument("Expected callback(s) as final argument");
}
auto id = callbackId.asInt();
return [winstance = std::move(instance), id](folly::dynamic args) {
if (auto instance = winstance.lock()) {
instance->callJSCallback(id, std::move(args));
}
};
}
namespace {
/**
* CxxModule::Callback accepts a vector<dynamic>, makeCallback returns
* a callback that accepts a dynamic, adapt the second into the first.
* TODO: Callback types should be made equal (preferably
* function<void(dynamic)>) to avoid the extra copy and indirect call.
*/
CxxModule::Callback convertCallback(
std::function<void(folly::dynamic)> callback) {
return [callback = std::move(callback)](std::vector<folly::dynamic> args) {
// after unpinning folly, can use folly::dynamic::array_range
folly::dynamic obj = folly::dynamic::array;
for (auto& arg : args) {
obj.push_back(std::move(arg));
}
callback(std::move(obj));
};
}
} // namespace
bool CxxNativeModule::shouldWarnOnUse_ = false;
void CxxNativeModule::setShouldWarnOnUse(bool value) {
shouldWarnOnUse_ = value;
}
void CxxNativeModule::emitWarnIfWarnOnUsage(
const std::string& method_name,
const std::string& module_name) {
if (shouldWarnOnUse_) {
std::string message = "Calling " + method_name +
" on Cxx NativeModule (name = \"" + module_name + "\").";
react_native_log_warn(message.c_str());
}
}
std::string CxxNativeModule::getName() {
return name_;
}
std::string CxxNativeModule::getSyncMethodName(unsigned int reactMethodId) {
if (reactMethodId >= methods_.size()) {
throw std::invalid_argument(
"methodId " + std::to_string(reactMethodId) + " out of range [0.." +
std::to_string(methods_.size()) + "]");
}
return methods_[reactMethodId].name;
}
std::vector<MethodDescriptor> CxxNativeModule::getMethods() {
lazyInit();
std::vector<MethodDescriptor> descs;
descs.reserve(methods_.size());
for (auto& method : methods_) {
descs.emplace_back(method.name, method.getType());
}
return descs;
}
folly::dynamic CxxNativeModule::getConstants() {
lazyInit();
if (!module_) {
return nullptr;
}
emitWarnIfWarnOnUsage("getConstants()", getName());
folly::dynamic constants = folly::dynamic::object();
for (auto& pair : module_->getConstants()) {
constants.insert(std::move(pair.first), std::move(pair.second));
}
return constants;
}
void CxxNativeModule::invoke(
unsigned int reactMethodId,
folly::dynamic&& params,
int callId) {
if (reactMethodId >= methods_.size()) {
throw std::invalid_argument(
"methodId " + std::to_string(reactMethodId) + " out of range [0.." +
std::to_string(methods_.size()) + "]");
}
if (!params.isArray()) {
throw std::invalid_argument(
std::string("Method parameters should be array, but are ") +
params.typeName());
}
CxxModule::Callback first;
CxxModule::Callback second;
const auto& method = methods_[reactMethodId];
if (!method.func) {
throw std::runtime_error(
"Method " + method.name + " is synchronous but invoked asynchronously");
}
emitWarnIfWarnOnUsage(method.name, getName());
if (params.size() < method.callbacks) {
throw std::invalid_argument(
"Expected " + std::to_string(method.callbacks) +
" callbacks, but only " + std::to_string(params.size()) +
" parameters provided");
}
if (method.callbacks == 1) {
first = convertCallback(makeCallback(instance_, params[params.size() - 1]));
} else if (method.callbacks == 2) {
first = convertCallback(makeCallback(instance_, params[params.size() - 2]));
second =
convertCallback(makeCallback(instance_, params[params.size() - 1]));
}
params.resize(params.size() - method.callbacks);
// I've got a few flawed options here. I can let the C++ exception
// propagate, and the registry will log/convert them to java exceptions.
// This lets all the java and red box handling work ok, but the only info I
// can capture about the C++ exception is the what() string, not the stack.
// I can std::terminate() the app. This causes the full, accurate C++
// stack trace to be added to logcat by debuggerd. The java state is lost,
// but in practice, the java stack is always the same in this case since
// the javascript stack is not visible, and the crash is unfriendly to js
// developers, but crucial to C++ developers. The what() value is also
// lost. Finally, I can catch, log the java stack, then rethrow the C++
// exception. In this case I get java and C++ stack data, but the C++
// stack is as of the rethrow, not the original throw, both the C++ and
// java stacks always look the same.
//
// I am going with option 2, since that seems like the most useful
// choice. It would be nice to be able to get what() and the C++
// stack. I'm told that will be possible in the future. TODO
// mhorowitz #7128529: convert C++ exceptions to Java
const auto& moduleName = name_;
TraceSection s(
"CxxMethodCallQueue", "module", moduleName, "method", method.name);
messageQueueThread_->runOnQueue([method,
moduleName,
params = std::move(params),
first,
second,
callId]() {
#ifdef WITH_FBSYSTRACE
if (callId != -1) {
fbsystrace_end_async_flow(TRACE_TAG_REACT, "native", callId);
}
#else
(void)(callId);
#endif
TraceSection s(
"CxxMethodCallDispatch", "module", moduleName, "method", method.name);
try {
method.func(params, first, second);
} catch (const facebook::xplat::JsArgumentException&) {
throw;
} catch (std::exception& e) {
LOG(ERROR) << "std::exception. Method call " << method.name.c_str()
<< " failed: " << e.what();
std::terminate();
} catch (std::string& error) {
LOG(ERROR) << "std::string. Method call " << method.name.c_str()
<< " failed: " << error.c_str();
std::terminate();
} catch (...) {
LOG(ERROR) << "Method call " << method.name.c_str()
<< " failed. unknown error";
std::terminate();
}
});
}
MethodCallResult CxxNativeModule::callSerializableNativeHook(
unsigned int hookId,
folly::dynamic&& args) {
if (hookId >= methods_.size()) {
throw std::invalid_argument(
"methodId " + std::to_string(hookId) + " out of range [0.." +
std::to_string(methods_.size()) + "]");
}
const auto& method = methods_[hookId];
if (!method.syncFunc) {
throw std::runtime_error(
"Method " + method.name + " is asynchronous but invoked synchronously");
}
emitWarnIfWarnOnUsage(method.name, getName());
return method.syncFunc(std::move(args));
}
void CxxNativeModule::lazyInit() {
if (module_ || !provider_) {
return;
}
// TODO 17216751: providers should never return null modules
module_ = provider_();
provider_ = nullptr;
if (module_) {
module_->setInstance(instance_);
methods_ = module_->getMethods();
}
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <cxxreact/CxxModule.h>
#include <cxxreact/NativeModule.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class Instance;
class MessageQueueThread;
typedef void (*WarnOnUsageLogger)(std::string message);
[[deprecated("This API will be removed along with the legacy architecture.")]]
std::function<void(folly::dynamic)> makeCallback(std::weak_ptr<Instance> instance, const folly::dynamic &callbackId);
class RN_EXPORT [[deprecated("This API will be removed along with the legacy architecture.")]] CxxNativeModule
: public NativeModule {
public:
CxxNativeModule(
std::weak_ptr<Instance> instance,
std::string name,
xplat::module::CxxModule::Provider provider,
std::shared_ptr<MessageQueueThread> messageQueueThread)
: instance_(instance), name_(std::move(name)), provider_(provider), messageQueueThread_(messageQueueThread)
{
}
std::string getName() override;
std::string getSyncMethodName(unsigned int methodId) override;
std::vector<MethodDescriptor> getMethods() override;
folly::dynamic getConstants() override;
void invoke(unsigned int reactMethodId, folly::dynamic &&params, int callId) override;
MethodCallResult callSerializableNativeHook(unsigned int hookId, folly::dynamic &&args) override;
static void setShouldWarnOnUse(bool value);
private:
void lazyInit();
std::weak_ptr<Instance> instance_;
std::string name_;
xplat::module::CxxModule::Provider provider_;
std::shared_ptr<MessageQueueThread> messageQueueThread_;
std::unique_ptr<xplat::module::CxxModule> module_;
std::vector<xplat::module::CxxModule::Method> methods_;
void emitWarnIfWarnOnUsage(const std::string &method_name, const std::string &module_name);
static bool shouldWarnOnUse_;
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <jsi/jsi.h>
namespace facebook::react {
inline static void handleJSError(jsi::Runtime &runtime, const jsi::JSError &error, bool isFatal)
{
auto errorUtils = runtime.global().getProperty(runtime, "ErrorUtils");
if (errorUtils.isUndefined() || !errorUtils.isObject() ||
!errorUtils.getObject(runtime).hasProperty(runtime, "reportFatalError") ||
!errorUtils.getObject(runtime).hasProperty(runtime, "reportError")) {
// ErrorUtils was not set up. This probably means the bundle didn't
// load properly.
throw jsi::JSError(
runtime,
"ErrorUtils is not set up properly. Something probably went wrong trying to load the JS bundle. Trying to report error " +
error.getMessage(),
error.getStack());
}
// TODO(janzer): Rewrite this function to return the processed error
// instead of just reporting it through the native module
if (isFatal) {
auto func = errorUtils.asObject(runtime).getPropertyAsFunction(runtime, "reportFatalError");
func.call(runtime, error.value());
} else {
auto func = errorUtils.asObject(runtime).getPropertyAsFunction(runtime, "reportError");
func.call(runtime, error.value());
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,372 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "Instance.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include "ErrorUtils.h"
#include "JSBigString.h"
#include "JSBundleType.h"
#include "JSExecutor.h"
#include "MessageQueueThread.h"
#include "MethodCall.h"
#include "NativeToJsBridge.h"
#include "RAMBundleRegistry.h"
#include "RecoverableError.h"
#include "TraceSection.h"
#include <cxxreact/JSIndexedRAMBundle.h>
#include <folly/json.h>
#include <react/debug/react_native_assert.h>
#include <glog/logging.h>
#include <condition_variable>
#include <exception>
#include <memory>
#include <mutex>
#include <string>
namespace facebook::react {
Instance::~Instance() {
if (nativeToJsBridge_) {
nativeToJsBridge_->destroy();
}
}
void Instance::unregisterFromInspector() {
if (inspectorTarget_ != nullptr) {
assert(runtimeInspectorTarget_);
inspectorTarget_->unregisterRuntime(*runtimeInspectorTarget_);
assert(parentInspectorTarget_);
parentInspectorTarget_->unregisterInstance(*inspectorTarget_);
parentInspectorTarget_ = nullptr;
inspectorTarget_ = nullptr;
}
}
void Instance::initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry,
jsinspector_modern::HostTarget* parentInspectorTarget) {
callback_ = std::move(callback);
moduleRegistry_ = std::move(moduleRegistry);
parentInspectorTarget_ = parentInspectorTarget;
jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
nativeToJsBridge_ = std::make_shared<NativeToJsBridge>(
jsef.get(), moduleRegistry_, jsQueue, callback_);
// If a parent inspector HostTarget is provided, perform inspector
// initialization synchronously.
if (parentInspectorTarget_ != nullptr) {
auto inspectorExecutor = parentInspectorTarget_->executorFromThis();
std::mutex inspectorInitializedMutex;
std::condition_variable inspectorInitializedCv;
bool inspectorInitialized = false;
// Schedule work on the inspector thread. NOTE: We expect this callback
// to always execute, given the invariant that `initializeBridge` (this
// method) completes before `unregisterFromInspector` is called.
// - On iOS, instance creation and invalidation both run on the main
// queue (`RCTCxxBridge::start,invalidate` use `RCTAssertMainQueue`).
// - On Android, `ReactContext` must be initialized with a constructed
// `CatalystInstance` (in which `Instance::initializeBridge` has
// completed) before `destroy` can be called.
inspectorExecutor([this,
&inspectorInitialized,
&inspectorInitializedMutex,
&inspectorInitializedCv](
jsinspector_modern::HostTarget& hostTarget) {
// NOTE: By passing *this, we strongly assume the Instance will still
// be alive by the time this executes.
// - On iOS, instance creation is done synchronously
// (`RCTCxxBridge::_initializeBridgeLocked`).
// - On Android, we explicitly wait for instance creation before
// destruction (`ReactInstanceManager::mReactContextLock`).
inspectorTarget_ = &hostTarget.registerInstance(*this);
RuntimeExecutor runtimeExecutorIfJsi = getRuntimeExecutor();
runtimeInspectorTarget_ = &inspectorTarget_->registerRuntime(
nativeToJsBridge_->getInspectorTargetDelegate(),
runtimeExecutorIfJsi ? runtimeExecutorIfJsi : [](auto) {});
// Signal that initialization is complete
{
std::lock_guard lock(inspectorInitializedMutex);
inspectorInitialized = true;
}
inspectorInitializedCv.notify_one();
});
// Wait for the initialization work to complete
{
std::unique_lock lock(inspectorInitializedMutex);
inspectorInitializedCv.wait(
lock, [&inspectorInitialized] { return inspectorInitialized; });
}
}
// Initialize the JavaScript runtime after we've initialized the inspector
nativeToJsBridge_->initializeRuntime();
// After NativeToJsBridge is created, the jsi::Runtime should exist. Also,
// the JS message queue thread exists. So, it's safe to schedule all queued
// up JS calls.
jsCallInvoker_->setNativeToJsBridgeAndFlushCalls(nativeToJsBridge_);
std::scoped_lock lock(m_syncMutex);
m_syncReady = true;
m_syncCV.notify_all();
});
CHECK(nativeToJsBridge_);
}
void Instance::loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string sourceURL) {
callback_->incrementPendingJSCalls();
TraceSection s("Instance::loadBundle", "sourceURL", sourceURL);
nativeToJsBridge_->loadBundle(
std::move(bundleRegistry),
std::move(startupScript),
std::move(sourceURL));
}
void Instance::loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string sourceURL) {
std::unique_lock<std::mutex> lock(m_syncMutex);
m_syncCV.wait(lock, [this] { return m_syncReady; });
TraceSection s("Instance::loadBundleSync", "sourceURL", sourceURL);
nativeToJsBridge_->loadBundleSync(
std::move(bundleRegistry),
std::move(startupScript),
std::move(sourceURL));
}
void Instance::setSourceURL(std::string sourceURL) {
callback_->incrementPendingJSCalls();
TraceSection s("Instance::setSourceURL", "sourceURL", sourceURL);
nativeToJsBridge_->loadBundle(nullptr, nullptr, std::move(sourceURL));
}
void Instance::loadScriptFromString(
std::unique_ptr<const JSBigString> string,
std::string sourceURL,
bool loadSynchronously) {
TraceSection s("Instance::loadScriptFromString", "sourceURL", sourceURL);
if (loadSynchronously) {
loadBundleSync(nullptr, std::move(string), std::move(sourceURL));
} else {
loadBundle(nullptr, std::move(string), std::move(sourceURL));
}
}
void Instance::loadRAMBundleFromString(
std::unique_ptr<const JSBigString> script,
const std::string& sourceURL) {
auto bundle = std::make_unique<JSIndexedRAMBundle>(std::move(script));
auto startupScript = bundle->getStartupCode();
auto registry = RAMBundleRegistry::singleBundleRegistry(std::move(bundle));
loadRAMBundle(std::move(registry), std::move(startupScript), sourceURL, true);
}
void Instance::loadRAMBundleFromFile(
const std::string& sourcePath,
const std::string& sourceURL,
bool loadSynchronously) {
auto bundle = std::make_unique<JSIndexedRAMBundle>(sourcePath.c_str());
auto startupScript = bundle->getStartupCode();
auto registry = RAMBundleRegistry::multipleBundlesRegistry(
std::move(bundle), JSIndexedRAMBundle::buildFactory());
loadRAMBundle(
std::move(registry),
std::move(startupScript),
sourceURL,
loadSynchronously);
}
void Instance::loadRAMBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL,
bool loadSynchronously) {
if (loadSynchronously) {
loadBundleSync(
std::move(bundleRegistry),
std::move(startupScript),
std::move(startupScriptSourceURL));
} else {
loadBundle(
std::move(bundleRegistry),
std::move(startupScript),
std::move(startupScriptSourceURL));
}
}
void Instance::setGlobalVariable(
std::string propName,
std::unique_ptr<const JSBigString> jsonValue) {
nativeToJsBridge_->setGlobalVariable(
std::move(propName), std::move(jsonValue));
}
void* Instance::getJavaScriptContext() {
return nativeToJsBridge_ ? nativeToJsBridge_->getJavaScriptContext()
: nullptr;
}
bool Instance::isInspectable() {
return nativeToJsBridge_ ? nativeToJsBridge_->isInspectable() : false;
}
bool Instance::isBatchActive() {
return nativeToJsBridge_ ? nativeToJsBridge_->isBatchActive() : false;
}
void Instance::callJSFunction(
std::string&& module,
std::string&& method,
folly::dynamic&& params) {
callback_->incrementPendingJSCalls();
nativeToJsBridge_->callFunction(
std::move(module), std::move(method), std::move(params));
}
void Instance::callJSCallback(uint64_t callbackId, folly::dynamic&& params) {
TraceSection s("Instance::callJSCallback");
callback_->incrementPendingJSCalls();
nativeToJsBridge_->invokeCallback((double)callbackId, std::move(params));
}
void Instance::registerBundle(
uint32_t bundleId,
const std::string& bundlePath) {
nativeToJsBridge_->registerBundle(bundleId, bundlePath);
}
const ModuleRegistry& Instance::getModuleRegistry() const {
return *moduleRegistry_;
}
ModuleRegistry& Instance::getModuleRegistry() {
return *moduleRegistry_;
}
void Instance::handleMemoryPressure(int pressureLevel) {
if (nativeToJsBridge_) {
// This class resets `nativeToJsBridge_` only in the destructor,
// hence a race is not possible there.
nativeToJsBridge_->handleMemoryPressure(pressureLevel);
}
}
std::shared_ptr<CallInvoker> Instance::getJSCallInvoker() {
return std::static_pointer_cast<CallInvoker>(jsCallInvoker_);
}
RuntimeExecutor Instance::getRuntimeExecutor() {
// HACK: RuntimeExecutor is not compatible with non-JSIExecutor, we return
// a null callback, which the caller should handle.
if (getJavaScriptContext() == nullptr) {
return nullptr;
}
std::weak_ptr<NativeToJsBridge> weakNativeToJsBridge = nativeToJsBridge_;
return [weakNativeToJsBridge](
std::function<void(jsi::Runtime & runtime)>&& callback) {
if (auto strongNativeToJsBridge = weakNativeToJsBridge.lock()) {
strongNativeToJsBridge->runOnExecutorQueue(
[callback = std::move(callback)](JSExecutor* executor) {
// Assumes the underlying executor is a JSIExecutor
auto* runtime =
static_cast<jsi::Runtime*>(executor->getJavaScriptContext());
try {
react_native_assert(runtime != nullptr);
callback(*runtime);
executor->flush();
} catch (jsi::JSError& originalError) {
handleJSError(*runtime, originalError, true);
}
});
}
};
}
std::shared_ptr<NativeMethodCallInvoker>
Instance::getDecoratedNativeMethodCallInvoker(
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker) {
return nativeToJsBridge_->getDecoratedNativeMethodCallInvoker(
nativeMethodCallInvoker);
}
void Instance::JSCallInvoker::setNativeToJsBridgeAndFlushCalls(
std::weak_ptr<NativeToJsBridge> nativeToJsBridge) {
std::scoped_lock guard(m_mutex);
m_shouldBuffer = false;
m_nativeToJsBridge = nativeToJsBridge;
while (!m_workBuffer.empty()) {
scheduleAsync(std::move(m_workBuffer.front()));
m_workBuffer.pop_front();
}
}
void Instance::JSCallInvoker::invokeSync(CallFunc&& /*work*/) {
// TODO: Replace JS Callinvoker with RuntimeExecutor.
throw std::runtime_error(
"Synchronous native -> JS calls are currently not supported.");
}
void Instance::JSCallInvoker::invokeAsync(CallFunc&& work) noexcept {
std::scoped_lock guard(m_mutex);
/**
* Why is is necessary to queue up async work?
*
* 1. TurboModuleManager must be created synchronously after the Instance,
* before we load the source code. This is when the NativeModule system
* is initialized. RCTDevLoadingView shows bundle download progress.
* 2. TurboModuleManager requires a JS CallInvoker.
* 3. The JS CallInvoker requires the NativeToJsBridge, which is created on
* the JS thread in Instance::initializeBridge.
*
* Therefore, although we don't call invokeAsync before the JS bundle is
* executed, this buffering is implemented anyways to ensure that work
* isn't discarded.
*/
if (m_shouldBuffer) {
m_workBuffer.push_back(std::move(work));
return;
}
scheduleAsync(std::move(work));
}
void Instance::JSCallInvoker::scheduleAsync(CallFunc&& work) noexcept {
if (auto strongNativeToJsBridge = m_nativeToJsBridge.lock()) {
strongNativeToJsBridge->runOnExecutorQueue(
[work = std::move(work)](JSExecutor* executor) {
auto* runtime = (jsi::Runtime*)executor->getJavaScriptContext();
work(*runtime);
executor->flush();
});
}
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,176 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <condition_variable>
#include <list>
#include <memory>
#include <mutex>
#include <ReactCommon/RuntimeExecutor.h>
#include <cxxreact/NativeToJsBridge.h>
#include <jsinspector-modern/ReactCdp.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace folly {
struct dynamic;
}
namespace facebook::react {
class JSBigString;
class JSExecutorFactory;
class MessageQueueThread;
class ModuleRegistry;
class RAMBundleRegistry;
struct [[deprecated("This API will be removed along with the legacy architecture.")]] InstanceCallback {
virtual ~InstanceCallback() = default;
virtual void onBatchComplete() {}
virtual void incrementPendingJSCalls() {}
virtual void decrementPendingJSCalls() {}
};
class RN_EXPORT [[deprecated("This API will be removed along with the legacy architecture.")]] Instance
: private jsinspector_modern::InstanceTargetDelegate {
public:
~Instance() override;
void initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry,
jsinspector_modern::HostTarget *inspectorTarget = nullptr);
void initializeRuntime();
void setSourceURL(std::string sourceURL);
void loadScriptFromString(std::unique_ptr<const JSBigString> string, std::string sourceURL, bool loadSynchronously);
void loadRAMBundleFromString(std::unique_ptr<const JSBigString> script, const std::string &sourceURL);
void loadRAMBundleFromFile(const std::string &sourcePath, const std::string &sourceURL, bool loadSynchronously);
void loadRAMBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL,
bool loadSynchronously);
bool supportsProfiling();
void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue);
void *getJavaScriptContext();
bool isInspectable();
bool isBatchActive();
void callJSFunction(std::string &&module, std::string &&method, folly::dynamic &&params);
void callJSCallback(uint64_t callbackId, folly::dynamic &&params);
// This method is experimental, and may be modified or removed.
void registerBundle(uint32_t bundleId, const std::string &bundlePath);
const ModuleRegistry &getModuleRegistry() const;
ModuleRegistry &getModuleRegistry();
void handleMemoryPressure(int pressureLevel);
/**
* JS CallInvoker is used by TurboModules to schedule work on the JS thread.
*
* Why is the bridge creating JS CallInvoker?
*
* - After every Native -> JS call in the TurboModule system, the bridge
* needs to flush all queued NativeModule method calls. The bridge must
* also dispatch onBatchComplete if the queue of NativeModule method calls
* was not empty.
*/
std::shared_ptr<CallInvoker> getJSCallInvoker();
/**
* NativeMethodCallInvoker is used by TurboModules to schedule work on the
* NativeModule thread(s).
*
* Why is the bridge decorating NativeMethodCallInvoker?
*
* - The bridge must be informed of all TurboModule async method calls. Why?
* When all queued NativeModule method calls are flushed by a call from
* Native -> JS, if that queue was non-zero in size, JsToNativeBridge
* dispatches onBatchComplete. When we turn our NativeModules to
* TurboModules, there will be less and less pending NativeModule method
* calls, so onBatchComplete will not fire as often. Therefore, the bridge
* needs to know how many TurboModule async method calls have been completed
* since the last time the bridge was flushed. If this number is non-zero,
* we fire onBatchComplete.
*
* Why can't we just create and return a new NativeMethodCallInvoker?
*
* - On Android, we have one NativeModule thread. That thread is created and
* managed outside of NativeToJsBridge. On iOS, we have one MethodQueue per
* module. Those MethodQueues are also created and managed outside of
* NativeToJsBridge. Therefore, we need to pass in a
* NativeMethodCallInvoker that schedules work on the respective thread.
*/
std::shared_ptr<NativeMethodCallInvoker> getDecoratedNativeMethodCallInvoker(
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker);
/**
* RuntimeExecutor is used by Fabric to access the jsi::Runtime.
*/
RuntimeExecutor getRuntimeExecutor();
/**
* Unregisters the instance from the inspector. This method must be called
* on the main (non-JS) thread, AFTER \c initializeBridge has completed.
*/
void unregisterFromInspector();
private:
void callNativeModules(folly::dynamic &&calls, bool isEndOfBatch);
void loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL);
void loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL);
std::shared_ptr<InstanceCallback> callback_;
std::shared_ptr<NativeToJsBridge> nativeToJsBridge_;
std::shared_ptr<ModuleRegistry> moduleRegistry_;
std::mutex m_syncMutex;
std::condition_variable m_syncCV;
bool m_syncReady = false;
class JSCallInvoker : public CallInvoker {
private:
std::weak_ptr<NativeToJsBridge> m_nativeToJsBridge;
std::mutex m_mutex;
bool m_shouldBuffer = true;
std::list<CallFunc> m_workBuffer;
void scheduleAsync(CallFunc &&work) noexcept;
public:
void setNativeToJsBridgeAndFlushCalls(std::weak_ptr<NativeToJsBridge> nativeToJsBridge);
void invokeAsync(CallFunc &&work) noexcept override;
void invokeSync(CallFunc &&work) override;
};
std::shared_ptr<JSCallInvoker> jsCallInvoker_ = std::make_shared<JSCallInvoker>();
jsinspector_modern::HostTarget *parentInspectorTarget_{nullptr};
jsinspector_modern::InstanceTarget *inspectorTarget_{nullptr};
jsinspector_modern::RuntimeTarget *runtimeInspectorTarget_{nullptr};
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,117 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "JSBigString.h"
#include <glog/logging.h>
#include <folly/portability/Fcntl.h>
#include <folly/portability/SysMman.h>
#include <folly/portability/SysStat.h>
#include <folly/portability/Unistd.h>
#include <cstring>
#include <memory>
namespace facebook::react {
JSBigFileString::JSBigFileString(int fd, size_t size, off_t offset /*= 0*/)
: m_fd{-1}, m_data{nullptr} {
m_fd = dup(fd);
if (m_fd == -1) {
const char* message =
"JSBigFileString::JSBigFileString - Could not duplicate file descriptor";
LOG(ERROR) << message;
throw std::runtime_error(message);
}
// Offsets given to mmap must be page aligned. We abstract away that
// restriction by sending a page aligned offset to mmap, and keeping track
// of the offset within the page that we must alter the mmap pointer by to
// get the final desired offset.
if (offset != 0) {
const static auto ps = sysconf(_SC_PAGESIZE);
auto d = lldiv(offset, ps);
m_mapOff = static_cast<off_t>(d.quot) * ps;
m_pageOff = static_cast<off_t>(d.rem);
m_size = size + m_pageOff;
} else {
m_mapOff = 0;
m_pageOff = 0;
m_size = size;
}
}
JSBigFileString::~JSBigFileString() {
if (m_data != nullptr) {
munmap((void*)m_data, m_size);
}
folly::fileops::close(m_fd);
}
const char* JSBigFileString::c_str() const {
if (m_size == 0) {
return "";
}
if (m_data == nullptr) {
m_data = (const char*)mmap(
nullptr, m_size, PROT_READ, MAP_PRIVATE, m_fd, m_mapOff);
CHECK(m_data != MAP_FAILED)
<< " fd: " << m_fd << " size: " << m_size << " offset: " << m_mapOff
<< " error: " << std::strerror(errno);
}
static const size_t kMinPageSize = 4096;
CHECK(!(reinterpret_cast<uintptr_t>(m_data) & (kMinPageSize - 1)))
<< "mmap address misaligned, likely corrupted"
<< " m_data: " << (const void*)m_data;
CHECK(m_pageOff <= m_size)
<< "offset impossibly large, likely corrupted"
<< " m_pageOff: " << m_pageOff << " m_size: " << m_size;
return m_data + m_pageOff;
}
size_t JSBigFileString::size() const {
// Ensure mapping has been initialized.
c_str();
return m_size - m_pageOff;
}
int JSBigFileString::fd() const {
return m_fd;
}
std::unique_ptr<const JSBigFileString> JSBigFileString::fromPath(
const std::string& sourceURL) {
int fd = folly::fileops::open(sourceURL.c_str(), O_RDONLY);
if (fd == -1) {
const std::string message =
std::string("JSBigFileString::fromPath - Could not open file: ") +
sourceURL;
LOG(ERROR) << message;
throw std::runtime_error(message.c_str());
}
struct stat fileInfo{};
int res = ::fstat(fd, &fileInfo);
if (res == -1) {
const std::string message =
"JSBigFileString::fromPath - fstat on bundle failed: " + sourceURL;
LOG(ERROR) << message;
folly::fileops::close(fd);
throw std::runtime_error(message.c_str());
}
auto ptr = std::make_unique<const JSBigFileString>(fd, fileInfo.st_size);
CHECK(folly::fileops::close(fd) == 0);
return ptr;
}
} // namespace facebook::react

View File

@@ -0,0 +1,144 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <string>
#ifndef RN_EXPORT
#ifdef _MSC_VER
#define RN_EXPORT
#else
#define RN_EXPORT __attribute__((visibility("default")))
#endif
#endif
namespace facebook::react {
// JSExecutor functions sometimes take large strings, on the order of
// megabytes. Copying these can be expensive. Introducing a
// move-only, non-CopyConstructible type will let the compiler ensure
// that no copies occur. folly::MoveWrapper should be used when a
// large string needs to be curried into a std::function<>, which must
// by CopyConstructible.
class JSBigString {
public:
JSBigString() = default;
// Not copyable
JSBigString(const JSBigString &) = delete;
JSBigString &operator=(const JSBigString &) = delete;
virtual ~JSBigString() = default;
virtual bool isAscii() const = 0;
// This needs to be a \0 terminated string
virtual const char *c_str() const = 0;
// Length of the c_str without the NULL byte.
virtual size_t size() const = 0;
};
// Concrete JSBigString implementation which holds a std::string
// instance.
class JSBigStdString : public JSBigString {
public:
JSBigStdString(std::string str, bool isAscii = false) : m_isAscii(isAscii), m_str(std::move(str)) {}
bool isAscii() const override
{
return m_isAscii;
}
const char *c_str() const override
{
return m_str.c_str();
}
size_t size() const override
{
return m_str.size();
}
private:
bool m_isAscii;
std::string m_str;
};
// Concrete JSBigString implementation which holds a heap-allocated
// buffer, and provides an accessor for writing to it. This can be
// used to construct a JSBigString in place, such as by reading from a
// file.
class RN_EXPORT JSBigBufferString : public JSBigString {
public:
JSBigBufferString(size_t size) : m_data(new char[size + 1]), m_size(size)
{
// Guarantee nul-termination. The caller is responsible for
// filling in the rest of m_data.
m_data[m_size] = '\0';
}
~JSBigBufferString() override
{
delete[] m_data;
}
bool isAscii() const override
{
return true;
}
const char *c_str() const override
{
return m_data;
}
size_t size() const override
{
return m_size;
}
char *data()
{
return m_data;
}
private:
char *m_data;
size_t m_size;
};
// JSBigString interface implemented by a file-backed mmap region.
class RN_EXPORT JSBigFileString : public JSBigString {
public:
JSBigFileString(int fd, size_t size, off_t offset = 0);
~JSBigFileString() override;
bool isAscii() const override
{
return true;
}
const char *c_str() const override;
size_t size() const override;
int fd() const;
static std::unique_ptr<const JSBigFileString> fromPath(const std::string &sourceURL);
private:
int m_fd; // The file descriptor being mmapped
size_t m_size; // The size of the mmapped region
mutable off_t m_pageOff; // The offset in the mmapped region to the data.
off_t m_mapOff; // The offset in the file to the mmapped region.
mutable const char *m_data; // Pointer to the mmapped region.
};
} // namespace facebook::react

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "JSBundleType.h"
namespace facebook::react {
static uint32_t constexpr RAMBundleMagicNumber = 0xFB0BD1E5;
// "Hermes" in ancient Greek encoded in UTF-16BE and truncated to 8 bytes.
static uint64_t constexpr HermesBCBundleMagicNumber = 0x1F1903C103BC1FC6;
ScriptTag parseTypeFromHeader(const BundleHeader& header) {
switch (header.magic32.value) {
case RAMBundleMagicNumber:
return ScriptTag::RAMBundle;
default:
return ScriptTag::String;
}
}
const char* stringForScriptTag(const ScriptTag& tag) {
switch (tag) {
case ScriptTag::String:
return "String";
case ScriptTag::RAMBundle:
return "RAM Bundle";
}
return "";
}
bool isHermesBytecodeBundle(const BundleHeader& header) {
return header.magic64 == HermesBCBundleMagicNumber;
}
} // namespace facebook::react

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <folly/Portability.h>
#include <cstdint>
#include <cstring>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
/*
* Scripts given to the JS Executors to run could be in any of the following
* formats. They are tagged so the executor knows how to run them.
* Hermes bytecode bundles (as encoded by hermesc, not metro) are not treated
* in a special way, they will be identified as ScriptTag::String.
*/
enum struct ScriptTag {
String = 0,
RAMBundle,
};
/**
* RAM bundles and BC bundles begin with headers. For RAM bundles this is
* 4 bytes, for BC bundles this is 12 bytes. This structure holds the first 12
* bytes from a bundle in a way that gives access to that information.
*/
FOLLY_PACK_PUSH
struct FOLLY_PACK_ATTR Magic32 {
uint32_t value;
uint32_t reserved_;
};
struct FOLLY_PACK_ATTR BundleHeader {
BundleHeader()
{
std::memset(this, 0, sizeof(BundleHeader));
}
union {
Magic32 magic32;
uint64_t magic64;
};
uint32_t version;
};
FOLLY_PACK_POP
/**
* Takes the first 8 bytes of a bundle, and returns a tag describing the
* bundle's format.
*/
RN_EXPORT ScriptTag parseTypeFromHeader(const BundleHeader &header);
/**
* Convert an `ScriptTag` enum into a string, useful for emitting in errors
* and diagnostic messages.
*/
RN_EXPORT const char *stringForScriptTag(const ScriptTag &tag);
/**
* Check whether a given bundle is hermesc-generated bytecode
*/
RN_EXPORT bool isHermesBytecodeBundle(const BundleHeader &header);
} // namespace facebook::react

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "JSExecutor.h"
#include "RAMBundleRegistry.h"
#include <jsinspector-modern/ReactCdp.h>
#include <array>
namespace facebook::react {
std::string JSExecutor::getSyntheticBundlePath(
uint32_t bundleId,
const std::string& bundlePath) {
#ifndef RCT_REMOVE_LEGACY_ARCH
if (bundleId == RAMBundleRegistry::MAIN_BUNDLE_ID) {
return bundlePath;
}
#endif // RCT_REMOVE_LEGACY_ARCH
std::array<char, 32> buffer{};
std::snprintf(buffer.data(), buffer.size(), "seg-%u.js", bundleId);
return buffer.data();
}
jsinspector_modern::RuntimeTargetDelegate&
JSExecutor::getRuntimeTargetDelegate() {
if (!runtimeTargetDelegate_) {
runtimeTargetDelegate_.emplace(getDescription());
}
return *runtimeTargetDelegate_;
}
} // namespace facebook::react

View File

@@ -0,0 +1,145 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <string>
#include <cxxreact/NativeModule.h>
#include <folly/dynamic.h>
#include <jsinspector-modern/InspectorInterfaces.h>
#include <jsinspector-modern/ReactCdp.h>
#include <react/timing/primitives.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class JSBigString;
class JSExecutor;
class JSModulesUnbundle;
class MessageQueueThread;
class ModuleRegistry;
class RAMBundleRegistry;
// This interface describes the delegate interface required by
// Executor implementations to call from JS into native code.
class [[deprecated("This API will be removed along with the legacy architecture.")]] ExecutorDelegate {
public:
virtual ~ExecutorDelegate() = default;
virtual std::shared_ptr<ModuleRegistry> getModuleRegistry() = 0;
virtual void callNativeModules(JSExecutor &executor, folly::dynamic &&calls, bool isEndOfBatch) = 0;
virtual MethodCallResult callSerializableNativeHook(
JSExecutor &executor,
unsigned int moduleId,
unsigned int methodId,
folly::dynamic &&args) = 0;
};
class [[deprecated("This API will be removed along with the legacy architecture.")]] JSExecutorFactory {
public:
virtual std::unique_ptr<JSExecutor> createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) = 0;
virtual ~JSExecutorFactory() = default;
};
class RN_EXPORT [[deprecated("This API will be removed along with the legacy architecture.")]] JSExecutor {
public:
/**
* Prepares the JS runtime for React Native by installing global variables.
* Called once before any JS is evaluated.
*/
virtual void initializeRuntime() = 0;
/**
* Execute an application script bundle in the JS context.
*/
virtual void loadBundle(std::unique_ptr<const JSBigString> script, std::string sourceURL) = 0;
#ifndef RCT_REMOVE_LEGACY_ARCH
/**
* Add an application "RAM" bundle registry
*/
virtual void setBundleRegistry(std::unique_ptr<RAMBundleRegistry> bundleRegistry) = 0;
#endif // RCT_REMOVE_LEGACY_ARCH
/**
* Register a file path for an additional "RAM" bundle
*/
virtual void registerBundle(uint32_t bundleId, const std::string &bundlePath) = 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(double callbackId, const folly::dynamic &arguments) = 0;
virtual void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue) = 0;
virtual void *getJavaScriptContext()
{
return nullptr;
}
/**
* Returns whether or not the underlying executor supports debugging via the
* Chrome remote debugging protocol. If true, the executor should also
* override the \c createAgentDelegate method.
*/
virtual bool isInspectable()
{
return false;
}
/**
* The description is displayed in the dev menu, if there is one in
* this build. There is a default, but if this method returns a
* non-empty string, it will be used instead.
*/
virtual std::string getDescription() = 0;
virtual void handleMemoryPressure([[maybe_unused]] int pressureLevel) {}
virtual void destroy() {}
virtual ~JSExecutor() = default;
virtual void flush() {}
static std::string getSyntheticBundlePath(uint32_t bundleId, const std::string &bundlePath);
/**
* Get a reference to the \c RuntimeTargetDelegate owned (or implemented) by
* this executor. This reference must remain valid for the duration of the
* executor's lifetime.
*/
virtual jsinspector_modern::RuntimeTargetDelegate &getRuntimeTargetDelegate();
private:
/**
* Initialized by \c getRuntimeTargetDelegate if not overridden, and then
* never changes.
*/
std::optional<jsinspector_modern::FallbackRuntimeTargetDelegate> runtimeTargetDelegate_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "JSIndexedRAMBundle.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <glog/logging.h>
#include <fstream>
#include <memory>
#include <sstream>
#include <folly/lang/Bits.h>
#include <glog/logging.h>
namespace facebook::react {
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)>
JSIndexedRAMBundle::buildFactory() {
return [](const std::string& bundlePath) {
return std::make_unique<JSIndexedRAMBundle>(bundlePath.c_str());
};
}
JSIndexedRAMBundle::JSIndexedRAMBundle(const char* sourcePath) {
m_bundle = std::make_unique<std::ifstream>(sourcePath, std::ifstream::binary);
if (!m_bundle) {
throw std::ios_base::failure(
std::string("Bundle ") + sourcePath +
"cannot be opened: " + std::to_string(m_bundle->rdstate()));
}
init();
}
JSIndexedRAMBundle::JSIndexedRAMBundle(
std::unique_ptr<const JSBigString> script) {
// tmpStream is needed because m_bundle is std::istream type
// which has no member 'write'
std::unique_ptr<std::stringstream> tmpStream =
std::make_unique<std::stringstream>();
tmpStream->write(script->c_str(), script->size());
m_bundle = std::move(tmpStream);
if (!m_bundle) {
throw std::ios_base::failure(
"Bundle from string cannot be opened: " +
std::to_string(m_bundle->rdstate()));
}
init();
}
void JSIndexedRAMBundle::init() {
// read in magic header, number of entries, and length of the startup section
uint32_t header[3];
static_assert(
sizeof(header) == 12,
"header size must exactly match the input file format");
readBundle(reinterpret_cast<char*>(header), sizeof(header));
const size_t numTableEntries = folly::Endian::little(header[1]);
const size_t startupCodeSize = folly::Endian::little(header[2]);
// allocate memory for meta data and lookup table.
m_table = ModuleTable(numTableEntries);
m_baseOffset = sizeof(header) + m_table.byteLength();
// read the lookup table from the file
readBundle(reinterpret_cast<char*>(m_table.data.get()), m_table.byteLength());
// read the startup code
m_startupCode = std::make_unique<JSBigBufferString>(startupCodeSize - 1);
readBundle(m_startupCode->data(), startupCodeSize - 1);
}
JSIndexedRAMBundle::Module JSIndexedRAMBundle::getModule(
uint32_t moduleId) const {
Module ret;
ret.name = std::to_string(moduleId) + ".js";
ret.code = getModuleCode(moduleId);
return ret;
}
std::unique_ptr<const JSBigString> JSIndexedRAMBundle::getStartupCode() {
CHECK(m_startupCode)
<< "startup code for a RAM Bundle can only be retrieved once";
return std::move(m_startupCode);
}
std::string JSIndexedRAMBundle::getModuleCode(const uint32_t id) const {
const auto moduleData = id < m_table.numEntries ? &m_table.data[id] : nullptr;
// entries without associated code have offset = 0 and length = 0
const uint32_t length =
moduleData != nullptr ? folly::Endian::little(moduleData->length) : 0;
if (length == 0) {
throw std::ios_base::failure(
"Error loading module" + std::to_string(id) + "from RAM Bundle");
}
std::string ret(length - 1, '\0');
readBundle(
&ret.front(),
length - 1,
m_baseOffset + folly::Endian::little(moduleData->offset));
return ret;
}
void JSIndexedRAMBundle::readBundle(char* buffer, const std::streamsize bytes)
const {
if (!m_bundle->read(buffer, bytes)) {
if ((m_bundle->rdstate() & std::ios::eofbit) != 0) {
throw std::ios_base::failure("Unexpected end of RAM Bundle file");
}
throw std::ios_base::failure(
"Error reading RAM Bundle: " + std::to_string(m_bundle->rdstate()));
}
}
void JSIndexedRAMBundle::readBundle(
char* buffer,
const std::streamsize bytes,
const std::ifstream::pos_type position) const {
if (!m_bundle->seekg(position)) {
throw std::ios_base::failure(
"Error reading RAM Bundle: " + std::to_string(m_bundle->rdstate()));
}
readBundle(buffer, bytes);
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <functional>
#include <istream>
#include <memory>
#include <string>
#include <cxxreact/JSBigString.h>
#include <cxxreact/JSModulesUnbundle.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class RN_EXPORT [[deprecated("This API will be removed along with the legacy architecture.")]] JSIndexedRAMBundle
: public JSModulesUnbundle {
public:
static std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> buildFactory();
// Throws std::runtime_error on failure.
explicit JSIndexedRAMBundle(const char *sourcePath);
JSIndexedRAMBundle(std::unique_ptr<const JSBigString> script);
// Throws std::runtime_error on failure.
std::unique_ptr<const JSBigString> getStartupCode();
// Throws std::runtime_error on failure.
Module getModule(uint32_t moduleId) const override;
private:
struct ModuleData {
uint32_t offset;
uint32_t length;
};
static_assert(sizeof(ModuleData) == 8, "ModuleData must not have any padding and use sizes matching input files");
struct ModuleTable {
size_t numEntries;
std::unique_ptr<ModuleData[]> data;
ModuleTable() : numEntries(0) {};
ModuleTable(size_t entries)
: numEntries(entries), data(std::unique_ptr<ModuleData[]>(new ModuleData[numEntries])) {};
size_t byteLength() const
{
return numEntries * sizeof(ModuleData);
}
};
void init();
std::string getModuleCode(uint32_t id) const;
void readBundle(char *buffer, std::streamsize bytes) const;
void readBundle(char *buffer, std::streamsize bytes, std::istream::pos_type position) const;
mutable std::unique_ptr<std::istream> m_bundle;
ModuleTable m_table;
size_t m_baseOffset{};
std::unique_ptr<JSBigBufferString> m_startupCode;
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <cstdint>
#include <stdexcept>
#include <string>
namespace facebook::react {
class [[deprecated("This API will be removed along with the legacy architecture.")]] JSModulesUnbundle {
/**
* 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 {
public:
using std::out_of_range::out_of_range;
explicit ModuleNotFound(uint32_t moduleId)
: std::out_of_range::out_of_range("Module not found: " + std::to_string(moduleId))
{
}
};
struct Module {
std::string name;
std::string code;
};
JSModulesUnbundle() {}
virtual ~JSModulesUnbundle() = default;
virtual Module getModule(uint32_t moduleId) const = 0;
private:
JSModulesUnbundle(const JSModulesUnbundle &) = delete;
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <folly/dynamic.h>
namespace facebook {
namespace xplat {
namespace detail {
inline std::string toStringHelper()
{
return "";
}
template <typename T, typename... Rest>
inline std::string toStringHelper(const T &value, const Rest &...rest)
{
return std::to_string(value) + toStringHelper(rest...);
}
template <typename... Rest>
inline std::string toStringHelper(const char *value, const Rest &...rest)
{
return std::string(value) + toStringHelper(rest...);
}
template <typename R, typename M, typename... T>
R jsArg1(const folly::dynamic &arg, M asFoo, const T &...desc)
{
try {
return (arg.*asFoo)();
} catch (const folly::TypeError &ex) {
throw JsArgumentException("Error converting JavaScript arg " + toStringHelper(desc...) + " to C++: " + ex.what());
} catch (const std::range_error &ex) {
throw JsArgumentException(
"Could not convert argument " + toStringHelper(desc...) + " to required type: " + ex.what());
}
}
} // namespace detail
template <typename R, typename... T>
R jsArg(const folly::dynamic &arg, R (folly::dynamic::*asFoo)() const, const T &...desc)
{
return detail::jsArg1<R>(arg, asFoo, desc...);
}
template <typename R, typename... T>
R jsArg(const folly::dynamic &arg, R (folly::dynamic::*asFoo)() const &, const T &...desc)
{
return detail::jsArg1<R>(arg, asFoo, desc...);
}
template <typename T>
// NOLINTNEXTLINE (T62192316)
typename detail::is_dynamic<T>::type &jsArgAsDynamic(T &&args, size_t n)
{
try {
return args[n];
} catch (const std::out_of_range &ex) {
// Use 1-base counting for argument description.
throw JsArgumentException(
"JavaScript provided " + std::to_string(args.size()) + " arguments for C++ method which references at least " +
std::to_string(n + 1) + " arguments: " + ex.what());
}
}
template <typename R>
R jsArgN(const folly::dynamic &args, size_t n, R (folly::dynamic::*asFoo)() const)
{
return jsArg(jsArgAsDynamic(args, n), asFoo, n);
}
template <typename R>
R jsArgN(const folly::dynamic &args, size_t n, R (folly::dynamic::*asFoo)() const &)
{
return jsArg(jsArgAsDynamic(args, n), asFoo, n);
}
namespace detail {
// This is a helper for jsArgAsArray and jsArgAsObject.
template <typename T>
typename detail::is_dynamic<T>::type &
jsArgAsType(T &&args, size_t n, const char *required, bool (folly::dynamic::*isFoo)() const)
{
T &ret = jsArgAsDynamic(args, n);
if ((ret.*isFoo)()) {
return ret;
}
// Use 1-base counting for argument description.
throw JsArgumentException(
"Argument " + std::to_string(n + 1) + " of type " + ret.typeName() + " is not required type " + required);
}
} // end namespace detail
template <typename T>
typename detail::is_dynamic<T>::type &jsArgAsArray(T &&args, size_t n)
{
return detail::jsArgAsType(args, n, "Array", &folly::dynamic::isArray);
}
template <typename T>
typename detail::is_dynamic<T>::type &jsArgAsObject(T &&args, size_t n)
{
return detail::jsArgAsType(args, n, "Object", &folly::dynamic::isObject);
}
} // namespace xplat
} // namespace facebook

View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <exception>
#include <string>
#include <folly/dynamic.h>
// When building a cross-platform module for React Native, arguments passed
// from JS are represented as a folly::dynamic. This class provides helpers to
// extract arguments from the folly::dynamic to concrete types usable by
// cross-platform code, and converting exceptions to a JsArgumentException so
// they can be caught and reported to RN consistently. The goal is to make the
// jsArgAs... methods at the end simple to use should be most common, but any
// non-detail method can be used when needed.
namespace facebook::xplat {
class JsArgumentException : public std::logic_error {
public:
JsArgumentException(const std::string &msg) : std::logic_error(msg) {}
};
// This extracts a single argument by calling the given method pointer on it.
// If an exception is thrown, the additional arguments are passed to
// std::to_string to be included in the exception string. This will be most
// commonly used when extracting values from non-scalar argument. The second
// overload accepts ref-qualified member functions.
template <typename R, typename... T>
R jsArg(const folly::dynamic &arg, R (folly::dynamic::*asFoo)() const, const T &...desc);
template <typename R, typename... T>
R jsArg(const folly::dynamic &arg, R (folly::dynamic::*asFoo)() const &, const T &...desc);
// This is like jsArg, but a operates on a dynamic representing an array of
// arguments. The argument n is used both to index the array and build the
// exception message, if any. It can be used directly, but will more often be
// used by the type-specific methods following.
template <typename R>
R jsArgN(const folly::dynamic &args, size_t n, R (folly::dynamic::*asFoo)() const);
template <typename R>
R jsArgN(const folly::dynamic &args, size_t n, R (folly::dynamic::*asFoo)() const &);
namespace detail {
// This is a type helper to implement functions which should work on both const
// and non-const folly::dynamic arguments, and return a type with the same
// constness. Basically, it causes the templates which use it to be defined
// only for types compatible with folly::dynamic.
template <typename T>
struct is_dynamic {
using type = typename std::enable_if<std::is_assignable<folly::dynamic, T>::value, T>::type;
};
} // end namespace detail
// Easy to use conversion helpers are here:
// Extract the n'th arg from the given dynamic, as a dynamic. Throws a
// JsArgumentException if there is no n'th arg in the input.
template <typename T>
typename detail::is_dynamic<T>::type &jsArgAsDynamic(T &&args, size_t n);
// Extract the n'th arg from the given dynamic, as a dynamic Array. Throws a
// JsArgumentException if there is no n'th arg in the input, or it is not an
// Array.
template <typename T>
typename detail::is_dynamic<T>::type &jsArgAsArray(T &&args, size_t n);
// Extract the n'th arg from the given dynamic, as a dynamic Object. Throws a
// JsArgumentException if there is no n'th arg in the input, or it is not an
// Object.
template <typename T>
typename detail::is_dynamic<T>::type &jsArgAsObject(T &&args, size_t n);
// Extract the n'th arg from the given dynamic, as a bool. Throws a
// JsArgumentException if this fails for some reason.
inline bool jsArgAsBool(const folly::dynamic &args, size_t n)
{
return jsArgN(args, n, &folly::dynamic::asBool);
}
// Extract the n'th arg from the given dynamic, as an integer. Throws a
// JsArgumentException if this fails for some reason.
inline int64_t jsArgAsInt(const folly::dynamic &args, size_t n)
{
return jsArgN(args, n, &folly::dynamic::asInt);
}
// Extract the n'th arg from the given dynamic, as a double. Throws a
// JsArgumentException if this fails for some reason.
inline double jsArgAsDouble(const folly::dynamic &args, size_t n)
{
return jsArgN(args, n, &folly::dynamic::asDouble);
}
// Extract the n'th arg from the given dynamic, as a string. Throws a
// JsArgumentException if this fails for some reason.
inline std::string jsArgAsString(const folly::dynamic &args, size_t n)
{
return jsArgN(args, n, &folly::dynamic::asString);
}
} // namespace facebook::xplat
#include <cxxreact/JsArgumentHelpers-inl.h>

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <functional>
namespace facebook::react {
class MessageQueueThread {
public:
virtual ~MessageQueueThread() = default;
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;
};
} // namespace facebook::react

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "MethodCall.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <folly/json.h>
#include <stdexcept>
namespace facebook::react {
#define REQUEST_MODULE_IDS 0
#define REQUEST_METHOD_IDS 1
#define REQUEST_PARAMS 2
#define REQUEST_CALLID 3
static const char* errorPrefix = "Malformed calls from JS: ";
std::vector<MethodCall> parseMethodCalls(folly::dynamic&& jsonData) {
if (jsonData.isNull()) {
return {};
}
if (!jsonData.isArray()) {
throw std::invalid_argument(
std::string(errorPrefix) + " input isn't array but " +
jsonData.typeName());
}
if (jsonData.size() < REQUEST_PARAMS + 1) {
throw std::invalid_argument(
std::string(errorPrefix) +
"size == " + std::to_string(jsonData.size()));
}
auto& moduleIds = jsonData[REQUEST_MODULE_IDS];
auto& methodIds = jsonData[REQUEST_METHOD_IDS];
auto& params = jsonData[REQUEST_PARAMS];
int callId = -1;
if (!moduleIds.isArray() || !methodIds.isArray() || !params.isArray()) {
throw std::invalid_argument(
std::string(errorPrefix) + "not all fields are arrays.\n\n" +
folly::toJson(jsonData));
}
if (moduleIds.size() != methodIds.size() ||
moduleIds.size() != params.size()) {
throw std::invalid_argument(
std::string(errorPrefix) + "field sizes are different.\n\n" +
folly::toJson(jsonData));
}
if (jsonData.size() > REQUEST_CALLID) {
if (!jsonData[REQUEST_CALLID].isNumber()) {
throw std::invalid_argument(
std::string(errorPrefix) + "invalid callId" +
jsonData[REQUEST_CALLID].typeName());
}
callId = (int)jsonData[REQUEST_CALLID].asInt();
}
std::vector<MethodCall> methodCalls;
for (size_t i = 0; i < moduleIds.size(); i++) {
if (!params[i].isArray()) {
throw std::invalid_argument(
std::string(errorPrefix) + "method arguments isn't array but " +
params[i].typeName());
}
methodCalls.emplace_back(
static_cast<int>(moduleIds[i].asInt()),
static_cast<int>(methodIds[i].asInt()),
std::move(params[i]),
callId);
// only increment callid if contains valid callid as callid is optional
callId += (callId != -1) ? 1 : 0;
}
return methodCalls;
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <map>
#include <string>
#include <vector>
#include <folly/dynamic.h>
namespace facebook::react {
struct [[deprecated("This API will be removed along with the legacy architecture.")]] 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)
{
}
};
/// \throws std::invalid_argument
[[deprecated("This API will be removed along with the legacy architecture.")]]
std::vector<MethodCall> parseMethodCalls(folly::dynamic &&jsonData);
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,247 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ModuleRegistry.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <glog/logging.h>
#include <reactperflogger/BridgeNativeModulePerfLogger.h>
#include <utility>
#include "NativeModule.h"
#include "TraceSection.h"
namespace facebook::react {
namespace {
std::string normalizeName(std::string name) {
// TODO mhorowitz #10487027: This is super ugly. We should just
// change iOS to emit normalized names, drop the "RK..." from
// names hardcoded in Android, and then delete this and the
// similar hacks in js.
if (name.compare(0, 3, "RCT") == 0) {
return name.substr(3);
} else if (name.compare(0, 2, "RK") == 0) {
return name.substr(2);
}
return name;
}
} // namespace
ModuleRegistry::ModuleRegistry(
std::vector<std::unique_ptr<NativeModule>> modules,
ModuleNotFoundCallback callback)
: modules_{std::move(modules)},
moduleNotFoundCallback_{std::move(callback)} {}
void ModuleRegistry::updateModuleNamesFromIndex(size_t index) {
for (; index < modules_.size(); index++) {
std::string name = normalizeName(modules_[index]->getName());
modulesByName_[name] = index;
}
}
void ModuleRegistry::registerModules(
std::vector<std::unique_ptr<NativeModule>> modules) {
TraceSection s_("ModuleRegistry::registerModules");
// Noop if there are no NativeModules to add
if (modules.empty()) {
return;
}
if (modules_.empty() && unknownModules_.empty()) {
modules_ = std::move(modules);
} else {
size_t modulesSize = modules_.size();
size_t addModulesSize = modules.size();
bool addToNames = !modulesByName_.empty();
modules_.reserve(modulesSize + addModulesSize);
std::move(modules.begin(), modules.end(), std::back_inserter(modules_));
if (!unknownModules_.empty()) {
for (size_t index = modulesSize; index < modulesSize + addModulesSize;
index++) {
std::string name = normalizeName(modules_[index]->getName());
auto it = unknownModules_.find(name);
if (it != unknownModules_.end()) {
throw std::runtime_error(
"module " + name +
" was required without being registered and is now being registered.");
} else if (addToNames) {
modulesByName_[name] = index;
}
}
} else if (addToNames) {
updateModuleNamesFromIndex(modulesSize);
}
}
}
std::vector<std::string> ModuleRegistry::moduleNames() {
TraceSection s_("ModuleRegistry::moduleNames");
std::vector<std::string> names;
for (size_t i = 0; i < modules_.size(); i++) {
std::string name = normalizeName(modules_[i]->getName());
modulesByName_[name] = i;
names.push_back(std::move(name));
}
return names;
}
std::optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name) {
TraceSection s("ModuleRegistry::getConfig", "module", name);
// Initialize modulesByName_
if (modulesByName_.empty() && !modules_.empty()) {
moduleNames();
}
auto it = modulesByName_.find(name);
if (it == modulesByName_.end()) {
if (unknownModules_.find(name) != unknownModules_.end()) {
BridgeNativeModulePerfLogger::moduleJSRequireBeginningFail(name.c_str());
BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str());
return std::nullopt;
}
if (!moduleNotFoundCallback_) {
unknownModules_.insert(name);
BridgeNativeModulePerfLogger::moduleJSRequireBeginningFail(name.c_str());
BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str());
return std::nullopt;
}
BridgeNativeModulePerfLogger::moduleJSRequireBeginningEnd(name.c_str());
bool wasModuleLazilyLoaded = moduleNotFoundCallback_(name);
it = modulesByName_.find(name);
bool wasModuleRegisteredWithRegistry =
wasModuleLazilyLoaded && it != modulesByName_.end();
if (!wasModuleRegisteredWithRegistry) {
BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str());
unknownModules_.insert(name);
return std::nullopt;
}
} else {
BridgeNativeModulePerfLogger::moduleJSRequireBeginningEnd(name.c_str());
}
// If we've gotten this far, then we've signaled moduleJSRequireBeginningEnd
size_t index = it->second;
CHECK(index < modules_.size());
NativeModule* module = modules_[index].get();
// string name, object constants, array methodNames (methodId is index),
// [array promiseMethodIds], [array syncMethodIds]
folly::dynamic config = folly::dynamic::array(name);
{
TraceSection s_("ModuleRegistry::getConstants", "module", name);
/**
* In the case that there are constants, we'll initialize the NativeModule,
* and signal moduleJSRequireEndingStart. Otherwise, we'll simply signal the
* event. The Module will be initialized when we invoke one of its
* NativeModule methods.
*/
config.push_back(module->getConstants());
}
{
TraceSection s_("ModuleRegistry::getMethods", "module", name);
std::vector<MethodDescriptor> methods = module->getMethods();
folly::dynamic methodNames = folly::dynamic::array;
folly::dynamic promiseMethodIds = folly::dynamic::array;
folly::dynamic syncMethodIds = folly::dynamic::array;
for (auto& descriptor : methods) {
// TODO: #10487027 compare tags instead of doing string comparison?
methodNames.push_back(std::move(descriptor.name));
if (descriptor.type == "promise") {
promiseMethodIds.push_back(methodNames.size() - 1);
} else if (descriptor.type == "sync") {
syncMethodIds.push_back(methodNames.size() - 1);
}
}
if (!methodNames.empty()) {
config.push_back(std::move(methodNames));
if (!promiseMethodIds.empty() || !syncMethodIds.empty()) {
config.push_back(std::move(promiseMethodIds));
if (!syncMethodIds.empty()) {
config.push_back(std::move(syncMethodIds));
}
}
}
}
if (config.size() == 2 && config[1].empty()) {
// no constants or methods
return std::nullopt;
} else {
return ModuleConfig{.index = index, .config = std::move(config)};
}
}
std::string ModuleRegistry::getModuleName(unsigned int moduleId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(
"moduleId " + std::to_string(moduleId) + " out of range [0.." +
std::to_string(modules_.size()) + ")");
}
return modules_[moduleId]->getName();
}
std::string ModuleRegistry::getModuleSyncMethodName(
unsigned int moduleId,
unsigned int methodId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(
"moduleId " + std::to_string(moduleId) + " out of range [0.." +
std::to_string(modules_.size()) + ")");
}
return modules_[moduleId]->getSyncMethodName(methodId);
}
void ModuleRegistry::callNativeMethod(
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& params,
int callId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(
"moduleId " + std::to_string(moduleId) + " out of range [0.." +
std::to_string(modules_.size()) + ")");
}
modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
MethodCallResult ModuleRegistry::callSerializableNativeHook(
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& params) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(
"moduleId " + std::to_string(moduleId) + " out of range [0.." +
std::to_string(modules_.size()) + ")");
}
return modules_[moduleId]->callSerializableNativeHook(
methodId, std::move(params));
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <memory>
#include <unordered_set>
#include <vector>
#include <cxxreact/JSExecutor.h>
#include <folly/dynamic.h>
#include <optional>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class NativeModule;
struct [[deprecated("This API will be removed along with the legacy architecture.")]] ModuleConfig {
size_t index;
folly::dynamic config;
};
class RN_EXPORT [[deprecated("This API will be removed along with the legacy architecture.")]] 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
using ModuleNotFoundCallback = std::function<bool(const std::string &name)>;
ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules, ModuleNotFoundCallback callback = nullptr);
void registerModules(std::vector<std::unique_ptr<NativeModule>> modules);
std::vector<std::string> moduleNames();
std::optional<ModuleConfig> getConfig(const std::string &name);
void callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic &&params, int callId);
MethodCallResult callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, folly::dynamic &&params);
std::string getModuleName(unsigned int moduleId);
std::string getModuleSyncMethodName(unsigned int moduleId, unsigned int methodId);
private:
// This is always populated
std::vector<std::unique_ptr<NativeModule>> modules_;
// This is used to extend the population of modulesByName_ if registerModules
// is called after moduleNames
void updateModuleNamesFromIndex(size_t index);
// This is only populated if moduleNames() is called. Values are indices into
// modules_.
std::unordered_map<std::string, size_t> modulesByName_;
// This is populated with modules that are requested via getConfig but are
// unknown. An error will be thrown if they are subsequently added to the
// registry.
std::unordered_set<std::string> unknownModules_;
// Function will be called if a module was requested but was not found.
// If the function returns true, ModuleRegistry will try to find the module
// again (assuming it's registered) If the function returns false,
// ModuleRegistry will not try to find the module and return nullptr instead.
ModuleNotFoundCallback moduleNotFoundCallback_;
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,131 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
namespace facebook::react {
/*
NOTE: we keep this internal copy of folly/MoveWrapper.h to unblock
the the workstream of dropping the dependency on folly in RN!
For a technical explanation on why we still need this we defer
to the doc in folly/Function.h:
"There are some limitations in std::function that folly::Function tries to
avoid. std::function is copy-constructible and requires that the callable that
it wraps is copy-constructible as well, which is a constraint that is often
inconvenient. In most cases when using a std::function you don't make use of
its copy-constructibility, so you might sometimes feel like you get back very
little in return for a noticeable restriction. This restriction becomes
apparent when trying to use a lambda capturing a unique_ptr (or any
non-copyable type) as a callback for a folly::Future.
std::unique_ptr<Foo> foo_ptr = new Foo;
some_future.then(
[foo_ptr = std::move(foo_ptr)] mutable
(int x)
{ foo_ptr->setX(x); }
);
This piece of code did not compile before folly::Future started using
folly::Function instead of std::function to store the callback. Because the
lambda captures something non-copyable (the unique_ptr), it is not copyable
itself. And std::function can only store copyable callables.
The implementation of folly::Future did not make use of the
copy-constructibility of std::function at any point. There was no benefit from
the fact that the std::function is copy-constructible, but the fact that it can
only wrap copy-constructible callables posed a restriction.
A workaround was available: folly::MoveWrapper, which wraps an object that may
be non-copyable and implements copy operations by moving the embedded object.
Using a folly::MoveWrapper, you can capture non-copyable objects in a lambda,
and the lambda itself is still copyable and may be wrapped in a std::function.
It is a pragmatic solution for the above problem, but you have to be a little
careful. The problem is that you cant use a MoveWrapper anywhere where copy
operations are assumed to behave like actual copy operations. Also, a
folly::MoveWrapper<std::unique_ptr<T>> essentially behaves like auto_ptr<T>. Ask
yourself whether youd want to use lots of auto_ptrs in your codebase. And the
original question still persists: we very often dont benefit from
copy-constructibility of std::function, so why do we have to live with this
restriction? I.e. why do we have to use MoveWrapper?"
*/
/** C++11 closures don't support move-in capture. Nor does std::bind.
facepalm.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3610.html
"[...] a work-around that should make people's stomach crawl:
write a wrapper that performs move-on-copy, much like the deprecated
auto_ptr"
Unlike auto_ptr, this doesn't require a heap allocation.
*/
template <class T>
class MoveWrapper {
public:
/** If value can be default-constructed, why not?
Then we don't have to move it in */
MoveWrapper() = default;
/// Move a value in.
explicit MoveWrapper(T &&t) : value(std::move(t)) {}
/// copy is move
MoveWrapper(const MoveWrapper &other) : value(std::move(other.value)) {}
/// move is also move
MoveWrapper(MoveWrapper &&other) noexcept : value(std::move(other.value)) {}
const T &operator*() const
{
return value;
}
T &operator*()
{
return value;
}
const T *operator->() const
{
return &value;
}
T *operator->()
{
return &value;
}
/// move the value out (sugar for std::move(*moveWrapper))
T &&move()
{
return std::move(value);
}
// If you want these you're probably doing it wrong, though they'd be
// easy enough to implement
MoveWrapper &operator=(const MoveWrapper &) = delete;
MoveWrapper &operator=(MoveWrapper &&) = delete;
private:
mutable T value;
};
/// Make a MoveWrapper from the argument. Because the name "makeMoveWrapper"
/// is already quite transparent in its intent, this will work for lvalues as
/// if you had wrapped them in std::move.
template <class T, class T0 = typename std::remove_reference<T>::type>
MoveWrapper<T0> makeMoveWrapper(T &&t)
{
return MoveWrapper<T0>(std::forward<T0>(t));
}
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <string>
#include <vector>
#include <folly/dynamic.h>
#include <optional>
namespace facebook::react {
#ifndef RCT_REMOVE_LEGACY_ARCH
struct [[deprecated("This API will be removed along with the legacy architecture.")]] 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)) {}
};
#endif // RCT_REMOVE_LEGACY_ARCH
using MethodCallResult = std::optional<folly::dynamic>;
#ifndef RCT_REMOVE_LEGACY_ARCH
class [[deprecated("This API will be removed along with the legacy architecture.")]] NativeModule {
public:
virtual ~NativeModule() = default;
virtual std::string getName() = 0;
virtual std::string getSyncMethodName(unsigned int methodId) = 0;
virtual std::vector<MethodDescriptor> getMethods() = 0;
virtual folly::dynamic getConstants() = 0;
virtual void invoke(unsigned int reactMethodId, folly::dynamic &&params, int callId) = 0;
virtual MethodCallResult callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic &&args) = 0;
};
#endif // RCT_REMOVE_LEGACY_ARCH
} // namespace facebook::react

View File

@@ -0,0 +1,354 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "NativeToJsBridge.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <ReactCommon/CallInvoker.h>
#include <folly/json.h>
#include <glog/logging.h>
#include <jsi/jsi.h>
#include <reactperflogger/BridgeNativeModulePerfLogger.h>
#include "ErrorUtils.h"
#include "Instance.h"
#include "JSBigString.h"
#include "MessageQueueThread.h"
#include "MethodCall.h"
#include "ModuleRegistry.h"
#include "MoveWrapper.h"
#include "RAMBundleRegistry.h"
#include "TraceSection.h"
#include <memory>
#include <utility>
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
using fbsystrace::FbSystraceAsyncFlow;
#endif
namespace facebook::react {
// This class manages calls from JS to native code.
class [[deprecated(
"This API will be removed along with the legacy architecture.")]]
JsToNativeBridge : public react::ExecutorDelegate {
public:
JsToNativeBridge(
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<InstanceCallback> callback)
: m_registry(std::move(registry)), m_callback(std::move(callback)) {}
std::shared_ptr<ModuleRegistry> getModuleRegistry() override {
return m_registry;
}
bool isBatchActive() {
return m_batchHadNativeModuleOrTurboModuleCalls;
}
void callNativeModules(
[[maybe_unused]] JSExecutor& executor,
folly::dynamic&& calls,
bool isEndOfBatch) override {
CHECK(m_registry || calls.empty())
<< "native module calls cannot be completed with no native modules";
m_batchHadNativeModuleOrTurboModuleCalls =
m_batchHadNativeModuleOrTurboModuleCalls || !calls.empty();
std::vector<MethodCall> methodCalls = parseMethodCalls(std::move(calls));
BridgeNativeModulePerfLogger::asyncMethodCallBatchPreprocessEnd(
(int)methodCalls.size());
// 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 : methodCalls) {
m_registry->callNativeMethod(
call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
if (isEndOfBatch) {
// onBatchComplete will be called on the native (module) queue, but
// decrementPendingJSCalls will be called sync. Be aware that the bridge
// may still be processing native calls when the bridge idle signaler
// fires.
if (m_batchHadNativeModuleOrTurboModuleCalls) {
m_callback->onBatchComplete();
m_batchHadNativeModuleOrTurboModuleCalls = false;
}
m_callback->decrementPendingJSCalls();
}
}
MethodCallResult callSerializableNativeHook(
[[maybe_unused]] JSExecutor& executor,
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& args) override {
return m_registry->callSerializableNativeHook(
moduleId, methodId, std::move(args));
}
void recordTurboModuleAsyncMethodCall() noexcept {
m_batchHadNativeModuleOrTurboModuleCalls = true;
}
private:
// These methods are always invoked from an Executor. The NativeToJsBridge
// keeps a reference to the executor, and when destroy() is called, the
// executor is destroyed synchronously on its queue.
std::shared_ptr<ModuleRegistry> m_registry;
std::shared_ptr<InstanceCallback> m_callback;
std::atomic<bool> m_batchHadNativeModuleOrTurboModuleCalls{false};
};
NativeToJsBridge::NativeToJsBridge(
JSExecutorFactory* jsExecutorFactory,
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<InstanceCallback> callback)
: m_destroyed(std::make_shared<bool>(false)),
m_delegate(std::make_shared<JsToNativeBridge>(registry, callback)),
m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)),
m_executorMessageQueueThread(std::move(jsQueue)),
m_inspectable(m_executor->isInspectable()) {}
// This must be called on the same thread on which the constructor was called.
NativeToJsBridge::~NativeToJsBridge() {
CHECK(*m_destroyed)
<< "NativeToJsBridge::destroy() must be called before deallocating the NativeToJsBridge!";
}
void NativeToJsBridge::initializeRuntime() {
runOnExecutorQueue(
[](JSExecutor* executor) mutable { executor->initializeRuntime(); });
}
void NativeToJsBridge::loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
runOnExecutorQueue(
[this,
bundleRegistryWrap = makeMoveWrapper(std::move(bundleRegistry)),
startupScript = makeMoveWrapper(std::move(startupScript)),
startupScriptSourceURL =
std::move(startupScriptSourceURL)](JSExecutor* executor) mutable {
auto bundleRegistry = bundleRegistryWrap.move();
if (bundleRegistry) {
executor->setBundleRegistry(std::move(bundleRegistry));
}
try {
executor->loadBundle(
std::move(*startupScript), std::move(startupScriptSourceURL));
} catch (...) {
m_applicationScriptHasFailure = true;
throw;
}
});
}
void NativeToJsBridge::loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
if (bundleRegistry) {
m_executor->setBundleRegistry(std::move(bundleRegistry));
}
try {
m_executor->loadBundle(
std::move(startupScript), std::move(startupScriptSourceURL));
} catch (...) {
m_applicationScriptHasFailure = true;
throw;
}
}
void NativeToJsBridge::callFunction(
std::string&& module,
std::string&& method,
folly::dynamic&& arguments) {
int systraceCookie = -1;
#ifdef WITH_FBSYSTRACE
systraceCookie = m_systraceCookie++;
FbSystraceAsyncFlow::begin(TRACE_TAG_REACT, "JSCall", systraceCookie);
#endif
runOnExecutorQueue([this,
module = std::move(module),
method = std::move(method),
arguments = std::move(arguments),
systraceCookie](JSExecutor* executor) {
if (m_applicationScriptHasFailure) {
LOG(ERROR)
<< "Attempting to call JS function on a bad application bundle: "
<< module.c_str() << "." << method.c_str() << "()";
throw std::runtime_error(
"Attempting to call JS function on a bad application bundle: " +
module + "." + method + "()");
}
#ifdef WITH_FBSYSTRACE
FbSystraceAsyncFlow::end(TRACE_TAG_REACT, "JSCall", systraceCookie);
TraceSection s(
"NativeToJsBridge::callFunction", "module", module, "method", method);
#else
(void)(systraceCookie);
#endif
// This is safe because we are running on the executor's thread: it won't
// destruct until after it's been unregistered (which we check above) and
// that will happen on this thread
executor->callFunction(module, method, arguments);
});
}
void NativeToJsBridge::invokeCallback(
double callbackId,
folly::dynamic&& arguments) {
int systraceCookie = -1;
#ifdef WITH_FBSYSTRACE
systraceCookie = m_systraceCookie++;
FbSystraceAsyncFlow::begin(TRACE_TAG_REACT, "<callback>", systraceCookie);
#endif
runOnExecutorQueue(
[this, callbackId, arguments = std::move(arguments), systraceCookie](
JSExecutor* executor) {
if (m_applicationScriptHasFailure) {
LOG(ERROR)
<< "Attempting to call JS callback on a bad application bundle: "
<< callbackId;
throw std::runtime_error(
"Attempting to invoke JS callback on a bad application bundle.");
}
#ifdef WITH_FBSYSTRACE
FbSystraceAsyncFlow::end(TRACE_TAG_REACT, "<callback>", systraceCookie);
TraceSection s("NativeToJsBridge::invokeCallback");
#else
(void)(systraceCookie);
#endif
executor->invokeCallback(callbackId, arguments);
});
}
void NativeToJsBridge::registerBundle(
uint32_t bundleId,
const std::string& bundlePath) {
runOnExecutorQueue([bundleId, bundlePath](JSExecutor* executor) {
executor->registerBundle(bundleId, bundlePath);
});
}
void NativeToJsBridge::setGlobalVariable(
std::string propName,
std::unique_ptr<const JSBigString> jsonValue) {
runOnExecutorQueue([propName = std::move(propName),
jsonValue = makeMoveWrapper(std::move(jsonValue))](
JSExecutor* executor) mutable {
executor->setGlobalVariable(propName, jsonValue.move());
});
}
void* NativeToJsBridge::getJavaScriptContext() {
// TODO(cjhopman): this seems unsafe unless we require that it is only called
// on the main js queue.
return m_executor->getJavaScriptContext();
}
bool NativeToJsBridge::isInspectable() {
return m_inspectable;
}
bool NativeToJsBridge::isBatchActive() {
return m_delegate->isBatchActive();
}
void NativeToJsBridge::handleMemoryPressure(int pressureLevel) {
runOnExecutorQueue([=](JSExecutor* executor) {
executor->handleMemoryPressure(pressureLevel);
});
}
void NativeToJsBridge::destroy() {
// All calls made through runOnExecutorQueue have an early exit if
// m_destroyed is true. Setting this before the runOnQueueSync will cause
// pending work to be cancelled and we won't have to wait for it.
*m_destroyed = true;
m_executorMessageQueueThread->runOnQueueSync([this] {
m_executor->destroy();
m_executorMessageQueueThread->quitSynchronous();
m_executor = nullptr;
});
}
void NativeToJsBridge::runOnExecutorQueue(
std::function<void(JSExecutor*)>&& task) noexcept {
if (*m_destroyed) {
return;
}
std::shared_ptr<bool> isDestroyed = m_destroyed;
m_executorMessageQueueThread->runOnQueue(
[this, isDestroyed, task = std::move(task)] {
if (*isDestroyed) {
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(m_executor.get());
});
}
std::shared_ptr<NativeMethodCallInvoker>
NativeToJsBridge::getDecoratedNativeMethodCallInvoker(
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker) const {
class NativeMethodCallInvokerImpl : public NativeMethodCallInvoker {
private:
std::weak_ptr<JsToNativeBridge> m_jsToNativeBridge;
std::shared_ptr<NativeMethodCallInvoker> m_nativeInvoker;
public:
NativeMethodCallInvokerImpl(
std::weak_ptr<JsToNativeBridge> jsToNativeBridge,
std::shared_ptr<NativeMethodCallInvoker> nativeInvoker)
: m_jsToNativeBridge(std::move(jsToNativeBridge)),
m_nativeInvoker(std::move(nativeInvoker)) {}
void invokeAsync(
const std::string& methodName,
NativeMethodCallFunc&& func) noexcept override {
if (auto strongJsToNativeBridge = m_jsToNativeBridge.lock()) {
strongJsToNativeBridge->recordTurboModuleAsyncMethodCall();
}
m_nativeInvoker->invokeAsync(methodName, std::move(func));
}
void invokeSync(const std::string& methodName, NativeMethodCallFunc&& func)
override {
m_nativeInvoker->invokeSync(methodName, std::move(func));
}
};
return std::make_shared<NativeMethodCallInvokerImpl>(
m_delegate, std::move(nativeMethodCallInvoker));
}
jsinspector_modern::RuntimeTargetDelegate&
NativeToJsBridge::getInspectorTargetDelegate() {
return m_executor->getRuntimeTargetDelegate();
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <atomic>
#include <functional>
#include <map>
#include <vector>
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/RuntimeExecutor.h>
#include <cxxreact/JSExecutor.h>
#include <jsinspector-modern/ReactCdp.h>
namespace folly {
struct dynamic;
}
namespace facebook::react {
struct InstanceCallback;
class JsToNativeBridge;
class MessageQueueThread;
class ModuleRegistry;
class RAMBundleRegistry;
// This class manages calls from native code to JS. It also manages
// executors and their threads. All functions here can be called from
// any thread.
//
// Except for loadBundleSync(), all void methods will queue
// work to run on the jsQueue passed to the ctor, and return
// immediately.
class [[deprecated("This API will be removed along with the legacy architecture.")]] NativeToJsBridge {
public:
friend class JsToNativeBridge;
/**
* This must be called on the main JS thread.
*/
NativeToJsBridge(
JSExecutorFactory *jsExecutorFactory,
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<InstanceCallback> callback);
virtual ~NativeToJsBridge();
/**
* Executes a function with the module ID and method ID and any additional
* arguments in JS.
*/
void callFunction(std::string &&module, std::string &&method, folly::dynamic &&arguments);
/**
* Invokes a callback with the cbID, and optional additional arguments in JS.
*/
void invokeCallback(double callbackId, folly::dynamic &&arguments);
/**
* Sets global variables in the JS Context.
*/
void initializeRuntime();
/**
* Starts the JS application. If bundleRegistry is non-null, then it is
* used to fetch JavaScript modules as individual scripts.
* Otherwise, the script is assumed to include all the modules.
*/
void loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string sourceURL);
void loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string sourceURL);
void registerBundle(uint32_t bundleId, const std::string &bundlePath);
void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue);
void *getJavaScriptContext();
bool isInspectable();
bool isBatchActive();
void handleMemoryPressure(int pressureLevel);
/**
* Synchronously tears down the bridge and the main executor.
*/
void destroy();
void runOnExecutorQueue(std::function<void(JSExecutor *)> &&task) noexcept;
/**
* NativeMethodCallInvoker is used by TurboModules to schedule work on the
* NativeModule thread(s).
*/
std::shared_ptr<NativeMethodCallInvoker> getDecoratedNativeMethodCallInvoker(
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker) const;
jsinspector_modern::RuntimeTargetDelegate &getInspectorTargetDelegate();
private:
// This is used to avoid a race condition where a proxyCallback gets queued
// after ~NativeToJsBridge(), on the same thread. In that case, the callback
// will try to run the task on m_callback which will have been destroyed
// within ~NativeToJsBridge(), thus causing a SIGSEGV.
std::shared_ptr<bool> m_destroyed;
std::shared_ptr<JsToNativeBridge> m_delegate;
std::unique_ptr<JSExecutor> m_executor;
std::shared_ptr<MessageQueueThread> m_executorMessageQueueThread;
// Memoize this on the JS thread, so that it can be inspected from
// any thread later. This assumes inspectability doesn't change for
// a JSExecutor instance, which is true for all existing implementations.
bool m_inspectable;
// Keep track of whether the JS bundle containing the application logic causes
// exception when evaluated initially. If so, more calls to JS will very
// likely fail as well, so this flag can help prevent them.
bool m_applicationScriptHasFailure = false;
#ifdef WITH_FBSYSTRACE
std::atomic<uint_least32_t> m_systraceCookie{0};
#endif
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,84 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RAMBundleRegistry.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <folly/String.h>
#include <memory>
namespace facebook::react {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
constexpr uint32_t RAMBundleRegistry::MAIN_BUNDLE_ID;
#pragma clang diagnostic pop
std::unique_ptr<RAMBundleRegistry> RAMBundleRegistry::singleBundleRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle) {
return std::make_unique<RAMBundleRegistry>(std::move(mainBundle));
}
std::unique_ptr<RAMBundleRegistry> RAMBundleRegistry::multipleBundlesRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle,
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> factory) {
return std::make_unique<RAMBundleRegistry>(
std::move(mainBundle), std::move(factory));
}
RAMBundleRegistry::RAMBundleRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle,
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> factory)
: m_factory(std::move(factory)) {
m_bundles.emplace(MAIN_BUNDLE_ID, std::move(mainBundle));
}
void RAMBundleRegistry::registerBundle(
uint32_t bundleId,
std::string bundlePath) {
m_bundlePaths.emplace(bundleId, std::move(bundlePath));
}
JSModulesUnbundle::Module RAMBundleRegistry::getModule(
uint32_t bundleId,
uint32_t moduleId) {
if (m_bundles.find(bundleId) == m_bundles.end()) {
if (!m_factory) {
throw std::runtime_error(
"You need to register factory function in order to "
"support multiple RAM bundles.");
}
auto bundlePath = m_bundlePaths.find(bundleId);
if (bundlePath == m_bundlePaths.end()) {
throw std::runtime_error(
"In order to fetch RAM bundle from the registry, its file "
"path needs to be registered first.");
}
m_bundles.emplace(bundleId, m_factory(bundlePath->second));
}
auto module = getBundle(bundleId)->getModule(moduleId);
if (bundleId == MAIN_BUNDLE_ID) {
return module;
}
return {
.name = "seg-" + std::to_string(bundleId) + '_' + module.name,
.code = std::move(module.code),
};
}
JSModulesUnbundle* RAMBundleRegistry::getBundle(uint32_t bundleId) const {
return m_bundles.at(bundleId).get();
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <cstdint>
#include <functional>
#include <memory>
#include <unordered_map>
#include <utility>
#include <cxxreact/JSModulesUnbundle.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class RN_EXPORT [[deprecated("This API will be removed along with the legacy architecture.")]] RAMBundleRegistry {
public:
constexpr static uint32_t MAIN_BUNDLE_ID = 0;
static std::unique_ptr<RAMBundleRegistry> singleBundleRegistry(std::unique_ptr<JSModulesUnbundle> mainBundle);
static std::unique_ptr<RAMBundleRegistry> multipleBundlesRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle,
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> factory);
explicit RAMBundleRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle,
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> factory = nullptr);
RAMBundleRegistry(RAMBundleRegistry &&) = default;
RAMBundleRegistry &operator=(RAMBundleRegistry &&) = default;
void registerBundle(uint32_t bundleId, std::string bundlePath);
JSModulesUnbundle::Module getModule(uint32_t bundleId, uint32_t moduleId);
virtual ~RAMBundleRegistry() = default;
private:
JSModulesUnbundle *getBundle(uint32_t bundleId) const;
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> m_factory;
std::unordered_map<uint32_t, std::string> m_bundlePaths;
std::unordered_map<uint32_t, std::unique_ptr<JSModulesUnbundle>> m_bundles;
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,57 @@
# coding: utf-8
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-cxxreact"
s.version = version
s.summary = "-" # TODO
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("*.{cpp,h}", "*.h")
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers\"",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard()
}
s.header_dir = "cxxreact"
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
s.dependency "React-callinvoker", version
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
s.dependency "React-perflogger", version
s.dependency "React-jsi", version
s.dependency "React-logger", version
s.dependency "React-debug", version
s.dependency "React-timing", version
add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
s.resource_bundles = {'React-cxxreact_privacy' => 'PrivacyInfo.xcprivacy'}
if use_hermes()
s.dependency 'hermes-engine'
end
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,140 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ReactMarker.h"
#include <cxxreact/JSExecutor.h>
namespace facebook::react::ReactMarker {
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#endif
LogTaggedMarker logTaggedMarkerBridgelessImpl = nullptr;
LogTaggedMarker logTaggedMarkerImpl = nullptr;
std::shared_mutex logTaggedMarkerImplMutex;
#if __clang__
#pragma clang diagnostic pop
#endif
void logMarker(const ReactMarkerId markerId) {
logTaggedMarker(markerId, nullptr);
}
void logTaggedMarker(const ReactMarkerId markerId, const char* tag) {
LogTaggedMarker marker = nullptr;
{
std::shared_lock lock(logTaggedMarkerImplMutex);
marker = logTaggedMarkerImpl;
}
if (marker != nullptr) {
marker(markerId, tag);
}
}
void logMarkerBridgeless(const ReactMarkerId markerId) {
logTaggedMarkerBridgeless(markerId, nullptr);
}
void logTaggedMarkerBridgeless(const ReactMarkerId markerId, const char* tag) {
logTaggedMarkerBridgelessImpl(markerId, tag);
}
void logMarkerDone(const ReactMarkerId markerId, double markerTime) {
StartupLogger::getInstance().logStartupEvent(markerId, markerTime);
}
StartupLogger& StartupLogger::getInstance() {
static StartupLogger instance;
return instance;
}
void StartupLogger::logStartupEvent(
const ReactMarkerId markerId,
double markerTime) {
switch (markerId) {
case ReactMarkerId::APP_STARTUP_START:
if (!std::isnan(appStartupStartTime)) {
// We had a startup start time, which indicates a warm start (user
// closed the app and start again). In this case we need to invalidate
// all other startup timings.
reset();
}
appStartupStartTime = markerTime;
return;
case ReactMarkerId::APP_STARTUP_STOP:
if (std::isnan(appStartupEndTime)) {
appStartupEndTime = markerTime;
}
return;
case ReactMarkerId::INIT_REACT_RUNTIME_START:
if (std::isnan(initReactRuntimeStartTime)) {
initReactRuntimeStartTime = markerTime;
}
return;
case ReactMarkerId::INIT_REACT_RUNTIME_STOP:
if (std::isnan(initReactRuntimeEndTime)) {
initReactRuntimeEndTime = markerTime;
}
return;
case ReactMarkerId::RUN_JS_BUNDLE_START:
if (std::isnan(runJSBundleStartTime)) {
runJSBundleStartTime = markerTime;
}
return;
case ReactMarkerId::RUN_JS_BUNDLE_STOP:
if (std::isnan(runJSBundleEndTime)) {
runJSBundleEndTime = markerTime;
}
return;
default:
return;
}
}
void StartupLogger::reset() {
appStartupStartTime = std::nan("");
appStartupEndTime = std::nan("");
initReactRuntimeStartTime = std::nan("");
initReactRuntimeEndTime = std::nan("");
runJSBundleStartTime = std::nan("");
runJSBundleEndTime = std::nan("");
}
double StartupLogger::getAppStartupStartTime() {
return appStartupStartTime;
}
double StartupLogger::getInitReactRuntimeStartTime() {
return initReactRuntimeStartTime;
}
double StartupLogger::getInitReactRuntimeEndTime() {
return initReactRuntimeEndTime;
}
double StartupLogger::getRunJSBundleStartTime() {
return runJSBundleStartTime;
}
double StartupLogger::getRunJSBundleEndTime() {
return runJSBundleEndTime;
}
double StartupLogger::getAppStartupEndTime() {
return appStartupEndTime;
}
} // namespace facebook::react::ReactMarker

View File

@@ -0,0 +1,100 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <cmath>
#include <shared_mutex>
#ifdef __APPLE__
#include <functional>
#endif
namespace facebook::react::ReactMarker {
enum ReactMarkerId {
APP_STARTUP_START,
APP_STARTUP_STOP,
INIT_REACT_RUNTIME_START,
INIT_REACT_RUNTIME_STOP,
NATIVE_REQUIRE_START,
NATIVE_REQUIRE_STOP,
RUN_JS_BUNDLE_START,
RUN_JS_BUNDLE_STOP,
CREATE_REACT_CONTEXT_STOP,
JS_BUNDLE_STRING_CONVERT_START,
JS_BUNDLE_STRING_CONVERT_STOP,
NATIVE_MODULE_SETUP_START,
NATIVE_MODULE_SETUP_STOP,
REGISTER_JS_SEGMENT_START,
REGISTER_JS_SEGMENT_STOP,
REACT_INSTANCE_INIT_START,
REACT_INSTANCE_INIT_STOP
};
#ifdef __APPLE__
using LogTaggedMarker = std::function<void(const ReactMarkerId, const char *tag)>; // Bridge only
using LogTaggedMarkerBridgeless = std::function<void(const ReactMarkerId, const char *tag)>;
#else
typedef void (*LogTaggedMarker)(const ReactMarkerId, const char *tag); // Bridge only
typedef void (*LogTaggedMarkerBridgeless)(const ReactMarkerId, const char *tag);
#endif
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
extern RN_EXPORT std::shared_mutex logTaggedMarkerImplMutex;
/// - important: To ensure this gets read and written to in a thread safe
/// manner, make use of `logTaggedMarkerImplMutex`.
extern RN_EXPORT LogTaggedMarker logTaggedMarkerImpl;
extern RN_EXPORT LogTaggedMarker logTaggedMarkerBridgelessImpl;
extern RN_EXPORT void logMarker(ReactMarkerId markerId); // Bridge only
extern RN_EXPORT void logTaggedMarker(ReactMarkerId markerId,
const char *tag); // Bridge only
extern RN_EXPORT void logMarkerBridgeless(ReactMarkerId markerId);
extern RN_EXPORT void logTaggedMarkerBridgeless(ReactMarkerId markerId, const char *tag);
struct ReactMarkerEvent {
const ReactMarkerId markerId;
const char *tag;
double time;
};
class RN_EXPORT StartupLogger {
public:
static StartupLogger &getInstance();
void logStartupEvent(ReactMarkerId markerId, double markerTime);
void reset();
double getAppStartupStartTime();
double getInitReactRuntimeStartTime();
double getInitReactRuntimeEndTime();
double getRunJSBundleStartTime();
double getRunJSBundleEndTime();
double getAppStartupEndTime();
private:
StartupLogger() = default;
StartupLogger(const StartupLogger &) = delete;
StartupLogger &operator=(const StartupLogger &) = delete;
double appStartupStartTime = std::nan("");
double appStartupEndTime = std::nan("");
double initReactRuntimeStartTime = std::nan("");
double initReactRuntimeEndTime = std::nan("");
double runJSBundleStartTime = std::nan("");
double runJSBundleEndTime = std::nan("");
};
// When the marker got logged from the platform, it will notify here. This is
// used to collect react markers that are logged in the platform instead of in
// C++.
extern RN_EXPORT void logMarkerDone(ReactMarkerId markerId, double markerTime);
} // namespace facebook::react::ReactMarker

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated by scripts/releases/set-version.js
*/
#pragma once
#include <cstdint>
#include <string_view>
#define REACT_NATIVE_VERSION_MAJOR 0
#define REACT_NATIVE_VERSION_MINOR 83
#define REACT_NATIVE_VERSION_PATCH 2
namespace facebook::react {
constexpr struct {
int32_t Major = 0;
int32_t Minor = 83;
int32_t Patch = 2;
std::string_view Prerelease = "";
} ReactNativeVersion;
} // namespace facebook::react

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <exception>
#include <functional>
#include <string>
namespace facebook::react {
/**
* RecoverableError
*
* An exception that it is expected we should be able to recover from.
*/
struct RecoverableError : public std::exception {
explicit RecoverableError(const std::string &what_) : m_what{"facebook::react::Recoverable: " + what_} {}
virtual const char *what() const noexcept override
{
return m_what.c_str();
}
/**
* runRethrowingAsRecoverable
*
* Helper function that converts any exception of type `E`, thrown within the
* `act` routine into a recoverable error with the same message.
*/
template <typename E>
inline static void runRethrowingAsRecoverable(std::function<void()> act)
{
try {
act();
} catch (const E &err) {
throw RecoverableError(err.what());
}
}
private:
std::string m_what;
};
} // namespace facebook::react

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "TraceSection.h"
// NOTE: This is here for a backwards compatibility and should be removed once
// all of the external references to `facebook::react::SystraceSection` are
// gone.
namespace facebook::react {
#if defined(WITH_LOOM_TRACE)
#define SystraceSection TraceSection
#else
struct [[deprecated("Use TraceSection")]] SystraceSection : public TraceSection {
template <typename... ConvertsToStringPiece>
explicit SystraceSection(const char *name, ConvertsToStringPiece &&...args) : TraceSection(name, args...)
{
}
};
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,190 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
#endif
#ifdef WITH_PERFETTO
// clang-format off
// Windows.h macro fail, 'interface' clashes with a function in perfetto.h
#ifdef interface
#pragma push_macro("interface")
#undef interface
#define __RCT_INTERFACE_PUSHED
#endif
#include <perfetto.h>
#ifdef __RCT_INTERFACE_PUSHED
#undef __RCT_INTERFACE_PUSHED
#pragma pop_macro("interface")
#endif
// clang-format on
#include <reactperflogger/ReactPerfettoCategories.h>
#endif
#if defined(__APPLE__)
// This is required so that OS_LOG_TARGET_HAS_10_15_FEATURES will be set.
#include <os/trace_base.h>
#if OS_LOG_TARGET_HAS_10_15_FEATURES && !defined(WITH_LOOM_TRACE)
#include <os/log.h>
#include <os/signpost.h>
#include <sstream>
#endif
#endif
namespace facebook::react {
/**
* Allow providing an fbsystrace implementation that can short-circuit out
* quickly and can throttle too frequent events so we can get useful traces even
* if rendering etc. is spinning. For throttling we'll need file/line info so we
* use a macro.
*/
#if defined(WITH_LOOM_TRACE)
#define TraceSectionUnwrapped \
static constexpr const char systraceSectionFile[] = __FILE__; \
fbsystrace::FbSystraceSection<systraceSectionFile, __LINE__>
/**
* This is a convenience class to avoid lots of verbose profiling
* #ifdefs. If WITH_FBSYSTRACE is not defined, the optimizer will
* remove this completely. If it is defined, it will behave as
* FbSystraceSection, with the right tag provided. Use two separate classes to
* to ensure that the ODR rule isn't violated, that is, if WITH_FBSYSTRACE has
* different values in different files, there is no inconsistency in the sizes
* of defined symbols.
*/
#elif defined(WITH_PERFETTO)
struct TraceSection {
public:
template <typename... ConvertsToStringPiece>
explicit TraceSection([[maybe_unused]] const char *name, [[maybe_unused]] ConvertsToStringPiece &&...args)
{
TRACE_EVENT_BEGIN("react-native", perfetto::DynamicString{name}, args...);
}
~TraceSection()
{
TRACE_EVENT_END("react-native");
}
};
using TraceSectionUnwrapped = TraceSection;
#elif defined(WITH_FBSYSTRACE)
struct ConcreteTraceSection {
public:
template <typename... ConvertsToStringPiece>
explicit ConcreteTraceSection(const char *name, ConvertsToStringPiece &&...args)
: m_section(TRACE_TAG_REACT, name, args...)
{
}
private:
fbsystrace::FbSystraceSection m_section;
};
using TraceSectionUnwrapped = ConcreteTraceSection;
#else
struct DummyTraceSection {
public:
template <typename... ConvertsToStringPiece>
explicit DummyTraceSection([[maybe_unused]] const char *name, [[maybe_unused]] ConvertsToStringPiece &&...args)
{
}
};
using TraceSectionUnwrapped = DummyTraceSection;
#endif
/**
* On recent Apple platforms we want to leverage the Instruments signposts APIs.
* To not break the other TraceSection implementations above we wrap them.
* In the case of WITH_LOOM_TRACE we don't use the signposts APIs because of the
* templated type for TraceSection.
*/
#if defined(__APPLE__) && OS_LOG_TARGET_HAS_10_15_FEATURES && !defined(WITH_LOOM_TRACE)
namespace systrace {
template <typename T, typename = void>
struct renderer {
static std::string render(const T &t)
{
std::ostringstream oss;
oss << t;
return oss.str();
}
};
template <typename T>
static auto render(const T &t) -> decltype(renderer<T>::render(std::declval<const T &>()))
{
return renderer<T>::render(t);
}
inline os_log_t instrumentsLogHandle = nullptr;
static inline os_log_t getOrCreateInstrumentsLogHandle()
{
static std::once_flag flag{};
std::call_once(flag, []() {
if (!instrumentsLogHandle) {
instrumentsLogHandle = os_log_create("dev.reactnative.instruments", OS_LOG_CATEGORY_DYNAMIC_TRACING);
}
});
return instrumentsLogHandle;
}
} // namespace systrace
struct TraceSection {
public:
template <typename... ConvertsToStringPiece>
explicit TraceSection(const char *name, ConvertsToStringPiece &&...args) : systraceSectionUnwrapped_(name, args...)
{
os_log_t instrumentsLogHandle = systrace::getOrCreateInstrumentsLogHandle();
// If the log isn't enabled, we don't want the performance overhead of the
// rest of the code below.
if (!os_signpost_enabled(instrumentsLogHandle)) {
return;
}
name_ = name;
const auto argsVector = std::vector<std::string>{systrace::render(args)...};
std::string argsString = "";
for (size_t i = 0; i < argsVector.size(); i += 2) {
argsString += argsVector[i] + "=" + argsVector[i + 1] + ";";
}
signpostID_ = os_signpost_id_make_with_pointer(instrumentsLogHandle, this);
os_signpost_interval_begin(instrumentsLogHandle, signpostID_, "Systrace", "%s begin: %s", name, argsString.c_str());
}
~TraceSection()
{
// We don't need to gate on os_signpost_enabled here because it's already
// checked in os_signpost_interval_end.
os_signpost_interval_end(systrace::instrumentsLogHandle, signpostID_, "Systrace", "%s end", name_.data());
}
private:
os_signpost_id_t signpostID_ = OS_SIGNPOST_ID_INVALID;
std::string_view name_;
TraceSectionUnwrapped systraceSectionUnwrapped_;
};
#else
#define TraceSection TraceSectionUnwrapped
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <gtest/gtest.h>
#include <exception>
#include <stdexcept>
#include <cxxreact/RecoverableError.h>
using namespace facebook::react;
TEST(RecoverableError, RunRethrowingAsRecoverableRecoverTest) {
try {
RecoverableError::runRethrowingAsRecoverable<std::runtime_error>(
[]() { throw std::runtime_error("catch me"); });
FAIL() << "Unthrown exception";
} catch (const RecoverableError& err) {
ASSERT_STREQ(err.what(), "facebook::react::Recoverable: catch me");
} catch (...) {
FAIL() << "Uncaught exception";
}
}
TEST(RecoverableError, RunRethrowingAsRecoverableFallthroughTest) {
try {
RecoverableError::runRethrowingAsRecoverable<std::runtime_error>(
[]() { throw std::logic_error("catch me"); });
FAIL() << "Unthrown exception";
} catch (const RecoverableError&) {
FAIL() << "Recovered exception that should have fallen through";
} catch (const std::exception& err) {
ASSERT_STREQ(err.what(), "catch me");
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <cxxreact/JsArgumentHelpers.h>
#include <folly/dynamic.h>
#include <gtest/gtest.h>
#include <algorithm>
using namespace facebook::xplat;
using dynamic = folly::dynamic;
#define EXPECT_JSAE(statement, exstr) \
do { \
try { \
statement; \
FAIL() << "Expected JsArgumentException(" << (exstr) << ") not thrown"; \
} catch (const JsArgumentException& ex) { \
EXPECT_EQ(ex.what(), std::string(exstr)); \
} \
} while (0) // let any other exception escape, gtest will deal.
TEST(JsArgumentHelpersTest, args) {
const bool aBool = true;
const int64_t anInt = 17;
const double aDouble = 3.14;
const std::string aString = "word";
const dynamic anArray = dynamic::array("a", "b", "c");
const dynamic anObject = dynamic::object("k1", "v1")("k2", "v2");
const std::string aNumericString = std::to_string(anInt);
folly::dynamic args = dynamic::array(
aBool, anInt, aDouble, aString, anArray, anObject, aNumericString);
EXPECT_EQ(jsArgAsBool(args, 0), aBool);
EXPECT_EQ(jsArgAsInt(args, 1), anInt);
EXPECT_EQ(jsArgAsDouble(args, 2), aDouble);
EXPECT_EQ(jsArgAsString(args, 3), aString);
EXPECT_EQ(jsArgAsArray(args, 4), anArray);
EXPECT_EQ(jsArgAsObject(args, 5), anObject);
// const args
const folly::dynamic& cargs = args;
const folly::dynamic& a4 = jsArgAsArray(cargs, 4);
EXPECT_EQ(a4, anArray);
EXPECT_EQ(jsArgAsObject(cargs, 5), anObject);
// helpers returning dynamic should return same object without copying
EXPECT_EQ(&jsArgAsArray(args, 4), &(args[4]));
EXPECT_EQ(&jsArgAsArray(cargs, 4), &(args[4]));
// dynamics returned for mutable args should be mutable. The test is that
// this compiles.
jsArgAsArray(args, 4)[2] = "d";
jsArgAsArray(args, 4)[2] = "c";
// These fail to compile due to constness.
// jsArgAsArray(cargs, 4)[2] = "d";
// jsArgAsArray(cargs, 4)[2] = "c";
// ref-qualified member function tests
EXPECT_EQ(jsArgN(args, 3, &folly::dynamic::getString), aString);
EXPECT_EQ(jsArg(args[3], &folly::dynamic::getString), aString);
// conversions
EXPECT_EQ(jsArgAsDouble(args, 1), anInt * 1.0);
EXPECT_EQ(jsArgAsString(args, 1), aNumericString);
EXPECT_EQ(jsArgAsInt(args, 6), anInt);
// Test exception messages.
// out_of_range
EXPECT_JSAE(
jsArgAsBool(args, 7),
"JavaScript provided 7 arguments for C++ method which references at least "
"8 arguments: out of range in dynamic array");
// Conv range_error (invalid value conversion)
const std::string exhead = "Could not convert argument 3 to required type: ";
const std::string extail = ": Invalid leading character: \"word\"";
try {
jsArgAsInt(args, 3);
FAIL() << "Expected JsArgumentException(" << exhead << "..." << extail
<< ") not thrown";
} catch (const JsArgumentException& ex) {
const std::string exwhat = ex.what();
EXPECT_GT(exwhat.size(), exhead.size());
EXPECT_GT(exwhat.size(), extail.size());
EXPECT_TRUE(std::equal(exhead.cbegin(), exhead.cend(), exwhat.cbegin()))
<< "JsArgumentException('" << exwhat << "') does not begin with '"
<< exhead << "'";
EXPECT_TRUE(std::equal(extail.crbegin(), extail.crend(), exwhat.crbegin()))
<< "JsArgumentException('" << exwhat << "') does not end with '"
<< extail << "'";
}
// inconvertible types
EXPECT_JSAE(
jsArgAsArray(args, 2),
"Argument 3 of type double is not required type Array");
EXPECT_JSAE(
jsArgAsInt(args, 4),
"Error converting JavaScript arg 4 to C++: "
"TypeError: expected dynamic type 'int/double/bool/string', but had type 'array'");
// type predicate failure
EXPECT_JSAE(
jsArgAsObject(args, 4),
"Argument 5 of type array is not required type Object");
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <fcntl.h>
#include <sys/mman.h>
#include <cxxreact/JSBigString.h>
#include <gtest/gtest.h>
using namespace facebook::react;
namespace {
int tempFileFromString(const std::string& contents) {
const char* tmpDir = getenv("TMPDIR");
if (tmpDir == nullptr) {
tmpDir = "/tmp";
}
std::string tmp{tmpDir};
tmp += "/temp.XXXXXX";
std::vector<char> tmpBuf{tmp.begin(), tmp.end()};
tmpBuf.push_back('\0');
const int fd = mkstemp(tmpBuf.data());
write(fd, contents.c_str(), contents.size() + 1);
return fd;
}
}; // namespace
TEST(JSBigFileString, MapWholeFileTest) {
std::string data{"Hello, world"};
const auto size = data.length() + 1;
// Initialise Big String
int fd = tempFileFromString("Hello, world");
JSBigFileString bigStr{fd, size};
// Test
ASSERT_STREQ(data.c_str(), bigStr.c_str());
}
TEST(JSBigFileString, MapPartTest) {
std::string data{"Hello, world"};
// Sub-string to actually map
std::string needle{"or"};
off_t offset = data.find(needle);
// Initialise Big String
int fd = tempFileFromString(data);
JSBigFileString bigStr{fd, needle.size(), offset};
// Test
EXPECT_EQ(needle.length(), bigStr.size());
for (unsigned int i = 0; i < needle.length(); ++i) {
EXPECT_EQ(needle[i], bigStr.c_str()[i]);
}
}
TEST(JSBigFileString, MapPartAtLargeOffsetTest) {
std::string data(8 * 4096, 'X');
data += "Hello World!";
// Sub-string to actually map
std::string needle{"or"};
off_t offset = data.find(needle);
// Initialise Big String
int fd = tempFileFromString(data);
JSBigFileString bigStr{fd, needle.size(), offset};
// Test
EXPECT_EQ(needle.length(), bigStr.size());
for (unsigned int i = 0; i < needle.length(); ++i) {
EXPECT_EQ(needle[i], bigStr.c_str()[i]);
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <cxxreact/MethodCall.h>
#include <folly/json.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <gtest/gtest.h>
#pragma GCC diagnostic pop
using namespace facebook::react;
using dynamic = folly::dynamic;
TEST(parseMethodCalls, SingleReturnCallNoArgs) {
auto jsText = "[[7],[3],[[]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(0, returnedCall.arguments.size());
EXPECT_EQ(7, returnedCall.moduleId);
EXPECT_EQ(3, returnedCall.methodId);
}
TEST(parseMethodCalls, InvalidReturnFormat) {
try {
auto input = dynamic::object("foo", 1);
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
try {
auto input = dynamic::array(dynamic::object("foo", 1));
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
try {
auto input = dynamic::array(1, 4, dynamic::object("foo", 2));
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
try {
auto input = dynamic::array(
dynamic::array(1), dynamic::array(4), dynamic::object("foo", 2));
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
try {
auto input =
dynamic::array(dynamic::array(1), dynamic::array(4), dynamic::array());
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
}
TEST(parseMethodCalls, NumberReturn) {
auto jsText = "[[0],[0],[[\"foobar\"]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::STRING, returnedCall.arguments[0].type());
EXPECT_EQ("foobar", returnedCall.arguments[0].asString());
}
TEST(parseMethodCalls, StringReturn) {
auto jsText = "[[0],[0],[[42.16]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::DOUBLE, returnedCall.arguments[0].type());
EXPECT_EQ(42.16, returnedCall.arguments[0].asDouble());
}
TEST(parseMethodCalls, BooleanReturn) {
auto jsText = "[[0],[0],[[false]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::BOOL, returnedCall.arguments[0].type());
ASSERT_FALSE(returnedCall.arguments[0].asBool());
}
TEST(parseMethodCalls, NullReturn) {
auto jsText = "[[0],[0],[[null]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::NULLT, returnedCall.arguments[0].type());
}
TEST(parseMethodCalls, MapReturn) {
auto jsText = R"([[0],[0],[[{"foo": "hello", "bar": 4.0, "baz": true}]]])";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::OBJECT, returnedCall.arguments[0].type());
auto& returnedMap = returnedCall.arguments[0];
auto foo = returnedMap.at("foo");
EXPECT_EQ(folly::dynamic("hello"), foo);
auto bar = returnedMap.at("bar");
EXPECT_EQ(folly::dynamic(4.0), bar);
auto baz = returnedMap.at("baz");
EXPECT_EQ(folly::dynamic(true), baz);
}
TEST(parseMethodCalls, ArrayReturn) {
auto jsText = "[[0],[0],[[[\"foo\", 42.0, false]]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::ARRAY, returnedCall.arguments[0].type());
auto& returnedArray = returnedCall.arguments[0];
EXPECT_EQ(3, returnedArray.size());
EXPECT_EQ(folly::dynamic("foo"), returnedArray[0]);
EXPECT_EQ(folly::dynamic(42.0), returnedArray[1]);
EXPECT_EQ(folly::dynamic(false), returnedArray[2]);
}
TEST(parseMethodCalls, ReturnMultipleParams) {
auto jsText = "[[0],[0],[[\"foo\", 14, null, false]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(4, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::STRING, returnedCall.arguments[0].type());
EXPECT_EQ(folly::dynamic::INT64, returnedCall.arguments[1].type());
EXPECT_EQ(folly::dynamic::NULLT, returnedCall.arguments[2].type());
EXPECT_EQ(folly::dynamic::BOOL, returnedCall.arguments[3].type());
}
TEST(parseMethodCalls, ParseTwoCalls) {
auto jsText = "[[0,0],[1,1],[[],[]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(2, returnedCalls.size());
}