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,50 @@
/*
* 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 <glog/logging.h>
#include <React/RCTLog.h>
#include <cxxreact/MessageQueueThread.h>
#ifndef RCT_REMOVE_LEGACY_ARCH
namespace facebook::react {
// RCTNativeModule arranges for native methods to be invoked on a queue which
// is not the JS thread. C++ modules don't use RCTNativeModule, so this little
// adapter does the work.
class [[deprecated("This API will be removed along with the legacy architecture.")]] DispatchMessageQueueThread
: public MessageQueueThread {
public:
DispatchMessageQueueThread(RCTModuleData *moduleData) : moduleData_(moduleData) {}
void runOnQueue(std::function<void()> &&func) override
{
dispatch_queue_t queue = moduleData_.methodQueue;
dispatch_block_t block = [func = std::move(func)] { func(); };
RCTAssert(block != nullptr, @"Invalid block generated in call to %@", moduleData_);
if (queue && block) {
dispatch_async(queue, block);
}
}
void runOnQueueSync(std::function<void()> &&__unused func) override
{
LOG(FATAL) << "Unsupported operation";
}
void quitSynchronous() override
{
LOG(FATAL) << "Unsupported operation";
}
private:
RCTModuleData *moduleData_;
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,17 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
#import <React/RCTBridgeMethod.h>
#import <cxxreact/CxxModule.h>
@interface RCTCxxMethod : NSObject <RCTBridgeMethod>
- (instancetype)initWithCxxMethod:(const facebook::xplat::module::CxxModule::Method &)cxxMethod;
@end

View File

@@ -0,0 +1,147 @@
/*
* 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.
*/
#import "RCTCxxMethod.h"
#import <React/RCTBridge+Private.h>
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <cxxreact/JsArgumentHelpers.h>
#import <react/utils/FollyConvert.h>
#import "RCTCxxUtils.h"
#import <memory>
using facebook::xplat::module::CxxModule;
using namespace facebook::react;
@implementation RCTCxxMethod {
std::unique_ptr<CxxModule::Method> _method;
}
- (instancetype)initWithCxxMethod:(const CxxModule::Method &)method
{
if ((self = [super init]) != nullptr) {
_method = std::make_unique<CxxModule::Method>(method);
}
return self;
}
- (const char *)JSMethodName
{
return _method->name.c_str();
}
- (RCTFunctionType)functionType
{
std::string type(_method->getType());
if (type == "sync") {
return RCTFunctionTypeSync;
} else if (type == "async") {
return RCTFunctionTypeNormal;
} else {
return RCTFunctionTypePromise;
}
}
- (id)invokeWithBridge:(RCTBridge *)bridge module:(id)module arguments:(NSArray *)arguments
{
// module is unused except for printing errors. The C++ object it represents
// is also baked into _method.
// the last N arguments are callbacks, according to the Method data. The
// preceding arguments are values which have already been parsed from JS: they
// may be NSNumber (bool, int, double), NSString, NSArray, or NSObject.
CxxModule::Callback first;
CxxModule::Callback second;
if (arguments.count < _method->callbacks) {
RCTLogError(
@"Method %@.%s expects at least %zu arguments, but got %tu",
RCTBridgeModuleNameForClass([module class]),
_method->name.c_str(),
_method->callbacks,
arguments.count);
return nil;
}
if (_method->callbacks >= 1) {
if (![arguments[arguments.count - 1] isKindOfClass:[NSNumber class]]) {
RCTLogError(
@"Argument %tu (%@) of %@.%s should be a function",
arguments.count - 1,
arguments[arguments.count - 1],
RCTBridgeModuleNameForClass([module class]),
_method->name.c_str());
return nil;
}
NSNumber *id1;
if (_method->callbacks == 2) {
if (![arguments[arguments.count - 2] isKindOfClass:[NSNumber class]]) {
RCTLogError(
@"Argument %tu (%@) of %@.%s should be a function",
arguments.count - 2,
arguments[arguments.count - 2],
RCTBridgeModuleNameForClass([module class]),
_method->name.c_str());
return nil;
}
id1 = arguments[arguments.count - 2];
NSNumber *id2 = arguments[arguments.count - 1];
second = ^(std::vector<folly::dynamic> args) {
folly::dynamic obj = folly::dynamic::array;
for (auto &arg : args) {
obj.push_back(std::move(arg));
}
[bridge enqueueCallback:id2 args:convertFollyDynamicToId(std::move(obj))];
};
} else {
id1 = arguments[arguments.count - 1];
}
first = ^(std::vector<folly::dynamic> args) {
folly::dynamic obj = folly::dynamic::array;
for (auto &arg : args) {
obj.push_back(std::move(arg));
}
[bridge enqueueCallback:id1 args:convertFollyDynamicToId(std::move(obj))];
};
}
folly::dynamic args = convertIdToFollyDynamic(arguments);
args.resize(args.size() - _method->callbacks);
try {
if (_method->func) {
_method->func(std::move(args), first, second);
return nil;
} else {
auto result = _method->syncFunc(std::move(args));
// TODO: we should convert this to JSValue directly
return convertFollyDynamicToId(result);
}
} catch (const facebook::xplat::JsArgumentException &ex) {
RCTLogError(
@"Method %@.%s argument error: %s",
RCTBridgeModuleNameForClass([module class]),
_method->name.c_str(),
ex.what());
return nil;
}
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; name = %s>", [self class], self, self.JSMethodName];
}
@end

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.
*/
#import <memory>
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
namespace facebook::xplat::module {
class CxxModule;
} // namespace facebook::xplat::module
/**
* Subclass RCTCxxModule to use cross-platform CxxModule on iOS.
*
* Subclasses must implement the createModule method to lazily produce the module. When running under the Cxx bridge
* modules will be accessed directly, under the Objective-C bridge method access is wrapped through RCTCxxMethod.
*/
@interface RCTCxxModule : NSObject <RCTBridgeModule>
// To be implemented by subclasses
- (std::unique_ptr<facebook::xplat::module::CxxModule>)createModule;
@end

View File

@@ -0,0 +1,87 @@
/*
* 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.
*/
#import "RCTCxxModule.h"
#import <React/RCTBridge.h>
#import <React/RCTLog.h>
#import <cxxreact/CxxModule.h>
#import <react/utils/FollyConvert.h>
#import "RCTCxxMethod.h"
using namespace facebook::react;
@implementation RCTCxxModule {
std::unique_ptr<facebook::xplat::module::CxxModule> _module;
}
+ (NSString *)moduleName
{
return @"";
}
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
- (void)lazyInit
{
if (!_module) {
_module = [self createModule];
if (_module) {
RCTAssert(
[RCTBridgeModuleNameForClass([self class]) isEqualToString:@(_module->getName().c_str())],
@"CxxModule class name %@ does not match runtime name %s",
RCTBridgeModuleNameForClass([self class]),
_module->getName().c_str());
}
}
}
- (std::unique_ptr<facebook::xplat::module::CxxModule>)createModule
{
RCTAssert(NO, @"Subclass %@ must override createModule", [self class]);
return nullptr;
}
- (NSArray<id<RCTBridgeMethod>> *)methodsToExport
{
[self lazyInit];
if (!_module) {
return nil;
}
NSMutableArray *moduleMethods = [NSMutableArray new];
for (const auto &method : _module->getMethods()) {
[moduleMethods addObject:[[RCTCxxMethod alloc] initWithCxxMethod:method]];
}
return moduleMethods;
}
- (NSDictionary<NSString *, id> *)constantsToExport
{
return [self getConstants];
}
- (NSDictionary<NSString *, id> *)getConstants
{
[self lazyInit];
if (!_module) {
return nil;
}
NSMutableDictionary *moduleConstants = [NSMutableDictionary new];
for (const auto &c : _module->getConstants()) {
moduleConstants[@(c.first.c_str())] = convertFollyDynamicToId(c.second);
}
return moduleConstants;
}
@end

View File

@@ -0,0 +1,32 @@
/*
* 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 <functional>
#include <memory>
#import <Foundation/Foundation.h>
@class RCTBridge;
@class RCTModuleData;
namespace facebook::react {
class Instance;
class NativeModule;
#ifndef RCT_REMOVE_LEGACY_ARCH
[[deprecated("This API will be removed along with the legacy architecture.")]]
std::vector<std::unique_ptr<NativeModule>>
createNativeModules(NSArray<RCTModuleData *> *modules, RCTBridge *bridge, const std::shared_ptr<Instance> &instance);
#endif // RCT_REMOVE_LEGACY_ARCH
NSError *tryAndReturnError(const std::function<void()> &func);
NSString *deriveSourceURL(NSURL *url);
} // namespace facebook::react

View File

@@ -0,0 +1,107 @@
/*
* 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.
*/
#import "RCTCxxUtils.h"
#import <React/RCTModuleData.h>
#import <React/RCTUtils.h>
#import <cxxreact/CxxNativeModule.h>
#import <jsi/jsi.h>
#import "DispatchMessageQueueThread.h"
#import "RCTCxxModule.h"
#import "RCTNativeModule.h"
namespace facebook::react {
using facebook::jsi::JSError;
#ifndef RCT_REMOVE_LEGACY_ARCH
std::vector<std::unique_ptr<NativeModule>>
createNativeModules(NSArray<RCTModuleData *> *modules, RCTBridge *bridge, const std::shared_ptr<Instance> &instance)
{
std::vector<std::unique_ptr<NativeModule>> nativeModules;
for (RCTModuleData *moduleData in modules) {
if ([moduleData.moduleClass isSubclassOfClass:[RCTCxxModule class]]) {
nativeModules.emplace_back(
std::make_unique<CxxNativeModule>(
instance,
[moduleData.name UTF8String],
[moduleData] { return [(RCTCxxModule *)(moduleData.instance) createModule]; },
std::make_shared<DispatchMessageQueueThread>(moduleData)));
} else {
nativeModules.emplace_back(std::make_unique<RCTNativeModule>(bridge, moduleData));
}
}
return nativeModules;
}
#endif // RCT_REMOVE_LEGACY_ARCH
static NSError *errorWithException(const std::exception &e)
{
NSString *msg = @(e.what());
NSMutableDictionary *errorInfo = [NSMutableDictionary dictionary];
const auto *jsError = dynamic_cast<const JSError *>(&e);
if (jsError) {
errorInfo[RCTJSRawStackTraceKey] = @(jsError->getStack().c_str());
msg = [@"Unhandled JS Exception: " stringByAppendingString:msg];
}
NSError *nestedError;
try {
std::rethrow_if_nested(e);
} catch (const std::exception &e) {
nestedError = errorWithException(e);
} catch (...) {
}
if (nestedError) {
msg = [NSString stringWithFormat:@"%@\n\n%@", msg, [nestedError localizedDescription]];
}
errorInfo[NSLocalizedDescriptionKey] = msg;
return [NSError errorWithDomain:RCTErrorDomain code:1 userInfo:errorInfo];
}
NSError *tryAndReturnError(const std::function<void()> &func)
{
try {
@try {
func();
return nil;
} @catch (NSException *exception) {
return RCTErrorWithNSException(exception);
} @catch (id exception) {
// This will catch any other ObjC exception, but no C++ exceptions
return RCTErrorWithMessage(@"non-std ObjC Exception");
}
} catch (const std::exception &ex) {
return errorWithException(ex);
} catch (...) {
// On a 64-bit platform, this would catch ObjC exceptions, too, but not on
// 32-bit platforms, so we catch those with id exceptions above.
return RCTErrorWithMessage(@"non-std C++ exception");
}
}
NSString *deriveSourceURL(NSURL *url)
{
NSString *sourceUrl;
if (url.isFileURL) {
// Url will contain only path to resource (i.g. file:// will be removed)
sourceUrl = url.path;
} else {
// Url will include protocol (e.g. http://)
sourceUrl = url.absoluteString;
}
return sourceUrl ?: @"";
}
} // namespace facebook::react

View File

@@ -0,0 +1,34 @@
/*
* 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.
*/
#import <React/RCTModuleData.h>
#import <cxxreact/NativeModule.h>
#ifndef RCT_REMOVE_LEGACY_ARCH
namespace facebook::react {
class [[deprecated("This API will be removed along with the legacy architecture.")]] RCTNativeModule
: public NativeModule {
public:
RCTNativeModule(RCTBridge *bridge, RCTModuleData *moduleData);
std::string getName() override;
std::string getSyncMethodName(unsigned int methodId) override;
std::vector<MethodDescriptor> getMethods() override;
folly::dynamic getConstants() override;
void invoke(unsigned int methodId, folly::dynamic &&params, int callId) override;
MethodCallResult callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic &&params) override;
private:
__weak RCTBridge *m_bridge;
RCTModuleData *m_moduleData;
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,241 @@
/*
* 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.
*/
#import "RCTNativeModule.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#import <Foundation/Foundation.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeMethod.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTCxxUtils.h>
#import <React/RCTLog.h>
#import <React/RCTProfile.h>
#import <React/RCTUtils.h>
#import <react/utils/FollyConvert.h>
#import <reactperflogger/BridgeNativeModulePerfLogger.h>
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
#endif
namespace {
enum SchedulingContext { Sync, Async };
}
namespace facebook::react {
static MethodCallResult invokeInner(
RCTBridge *bridge,
RCTModuleData *moduleData,
unsigned int methodId,
const folly::dynamic &params,
int callId,
SchedulingContext context);
RCTNativeModule::RCTNativeModule(RCTBridge *bridge, RCTModuleData *moduleData)
: m_bridge(bridge), m_moduleData(moduleData)
{
}
std::string RCTNativeModule::getName()
{
return [m_moduleData.name UTF8String];
}
std::string RCTNativeModule::getSyncMethodName(unsigned int methodId)
{
return m_moduleData.methods[methodId].JSMethodName;
}
std::vector<MethodDescriptor> RCTNativeModule::getMethods()
{
std::vector<MethodDescriptor> descs;
for (id<RCTBridgeMethod> method in m_moduleData.methods) {
descs.emplace_back(method.JSMethodName, RCTFunctionDescriptorFromType(method.functionType));
}
return descs;
}
folly::dynamic RCTNativeModule::getConstants()
{
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTNativeModule getConstants] moduleData.exportedConstants", nil);
NSDictionary *constants = m_moduleData.exportedConstants;
folly::dynamic ret = convertIdToFollyDynamic(constants);
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
return ret;
}
void RCTNativeModule::invoke(unsigned int methodId, folly::dynamic &&params, int callId)
{
id<RCTBridgeMethod> method = m_moduleData.methods[methodId];
if (method != nullptr) {
RCT_PROFILE_BEGIN_EVENT(
RCTProfileTagAlways,
@"[RCTNativeModule invoke]",
@{@"method" : [NSString stringWithUTF8String:method.JSMethodName]});
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
const char *moduleName = [m_moduleData.name UTF8String];
const char *methodName = m_moduleData.methods[methodId].JSMethodName;
dispatch_queue_t queue = m_moduleData.methodQueue;
const bool isSyncModule = queue == RCTJSThread;
if (isSyncModule) {
BridgeNativeModulePerfLogger::syncMethodCallStart(moduleName, methodName);
BridgeNativeModulePerfLogger::syncMethodCallArgConversionStart(moduleName, methodName);
} else {
BridgeNativeModulePerfLogger::asyncMethodCallStart(moduleName, methodName);
}
// capture by weak pointer so that we can safely use these variables in a callback
__weak RCTBridge *weakBridge = m_bridge;
__weak RCTModuleData *weakModuleData = m_moduleData;
// The BatchedBridge version of this buckets all the callbacks by thread, and
// queues one block on each. This is much simpler; we'll see how it goes and
// iterate.
dispatch_block_t block = [weakBridge, weakModuleData, methodId, params = std::move(params), callId, isSyncModule] {
#ifdef WITH_FBSYSTRACE
if (callId != -1) {
fbsystrace_end_async_flow(TRACE_TAG_REACT, "native", callId);
}
#else
(void)(callId);
#endif
@autoreleasepool {
invokeInner(weakBridge, weakModuleData, methodId, std::move(params), callId, isSyncModule ? Sync : Async);
}
};
if (isSyncModule) {
block();
BridgeNativeModulePerfLogger::syncMethodCallReturnConversionEnd(moduleName, methodName);
} else if (queue != nullptr) {
BridgeNativeModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName);
dispatch_async(queue, block);
}
#ifdef RCT_DEV
if (queue == nullptr) {
RCTLog(
@"Attempted to invoke `%u` (method ID) on `%@` (NativeModule name) without a method queue.",
methodId,
m_moduleData.name);
}
#endif
if (isSyncModule) {
BridgeNativeModulePerfLogger::syncMethodCallEnd(moduleName, methodName);
} else {
BridgeNativeModulePerfLogger::asyncMethodCallEnd(moduleName, methodName);
}
}
MethodCallResult RCTNativeModule::callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic &&params)
{
return invokeInner(m_bridge, m_moduleData, reactMethodId, params, 0, Sync);
}
static MethodCallResult invokeInner(
RCTBridge *bridge,
RCTModuleData *moduleData,
unsigned int methodId,
const folly::dynamic &params,
int callId,
SchedulingContext context)
{
if ((bridge == nullptr) || !bridge.valid || (moduleData == nullptr)) {
if (context == Sync) {
/**
* NOTE: moduleName and methodName are "". This shouldn't be an issue because there can only be one ongoing sync
* call at a time, and when we call syncMethodCallFail, that one call should terminate. This is also an
* exceptional scenario, so it shouldn't occur often.
*/
BridgeNativeModulePerfLogger::syncMethodCallFail("N/A", "N/A");
}
return std::nullopt;
}
id<RCTBridgeMethod> method = moduleData.methods[methodId];
if (RCT_DEBUG && (method == nullptr)) {
RCTLogError(@"Unknown methodID: %ud for module: %@", methodId, moduleData.name);
}
const char *moduleName = [moduleData.name UTF8String];
const char *methodName = moduleData.methods[methodId].JSMethodName;
if (context == Async) {
BridgeNativeModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodName, (int32_t)callId);
BridgeNativeModulePerfLogger::asyncMethodCallExecutionArgConversionStart(moduleName, methodName, (int32_t)callId);
}
NSArray *objcParams = convertFollyDynamicToId(params);
if (context == Sync) {
BridgeNativeModulePerfLogger::syncMethodCallArgConversionEnd(moduleName, methodName);
}
RCT_PROFILE_BEGIN_EVENT(
RCTProfileTagAlways,
@"[RCTNativeModule invokeInner]",
@{@"method" : [NSString stringWithUTF8String:method.JSMethodName]});
@try {
if (context == Sync) {
BridgeNativeModulePerfLogger::syncMethodCallExecutionStart(moduleName, methodName);
} else {
BridgeNativeModulePerfLogger::asyncMethodCallExecutionArgConversionEnd(moduleName, methodName, (int32_t)callId);
}
id result = [method invokeWithBridge:bridge module:moduleData.instance arguments:objcParams];
if (context == Sync) {
BridgeNativeModulePerfLogger::syncMethodCallExecutionEnd(moduleName, methodName);
BridgeNativeModulePerfLogger::syncMethodCallReturnConversionStart(moduleName, methodName);
} else {
BridgeNativeModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodName, (int32_t)callId);
}
return convertIdToFollyDynamic(result);
} @catch (NSException *exception) {
if (context == Sync) {
BridgeNativeModulePerfLogger::syncMethodCallFail(moduleName, methodName);
} else {
BridgeNativeModulePerfLogger::asyncMethodCallExecutionFail(moduleName, methodName, (int32_t)callId);
}
// Pass on JS exceptions
if ([exception.name hasPrefix:RCTFatalExceptionName]) {
@throw exception;
}
#if RCT_DEBUG
NSString *message = [NSString
stringWithFormat:@"Exception '%@' was thrown while invoking %s on target %@ with params %@\ncallstack: %@",
exception,
method.JSMethodName,
moduleData.name,
objcParams,
exception.callStackSymbols];
RCTFatal(RCTErrorWithMessage(message));
#else
RCTFatalException(exception);
#endif
} @finally {
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
return std::nullopt;
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH