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,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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
react_native_android_selector(platform_SRC platform/android/ReactCommon/*.cpp "")
file(GLOB react_nativemodule_core_SRC CONFIGURE_DEPENDS
ReactCommon/*.cpp
${platform_SRC})
add_library(react_nativemodule_core
OBJECT
${react_nativemodule_core_SRC})
react_native_android_selector(platform_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platform/android/ "")
target_include_directories(react_nativemodule_core
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${platform_DIR}
)
react_native_android_selector(fbjni fbjni "")
react_native_android_selector(reactnativejni reactnativejni "")
target_link_libraries(react_nativemodule_core
${fbjni}
folly_runtime
glog
jsi
react_bridging
react_debug
react_utils
react_featureflags
reactperflogger
${reactnativejni})
target_compile_reactnative_options(react_nativemodule_core PRIVATE)
target_compile_options(react_nativemodule_core PRIVATE -Wpedantic)

View File

@@ -0,0 +1,35 @@
/*
* 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 "CxxTurboModuleUtils.h"
#include <utility>
namespace facebook::react {
std::unordered_map<
std::string,
std::function<
std::shared_ptr<TurboModule>(std::shared_ptr<CallInvoker> jsInvoker)>>&
globalExportedCxxTurboModuleMap() {
static std::unordered_map<
std::string,
std::function<std::shared_ptr<TurboModule>(
std::shared_ptr<CallInvoker> jsInvoker)>>
map;
return map;
}
void registerCxxModuleToGlobalModuleMap(
std::string name,
std::function<std::shared_ptr<TurboModule>(
std::shared_ptr<CallInvoker> jsInvoker)> moduleProviderFunc) {
globalExportedCxxTurboModuleMap()[std::move(name)] =
std::move(moduleProviderFunc);
}
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* 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 <ReactCommon/CallInvoker.h>
#include <ReactCommon/TurboModule.h>
#include <functional>
#include <memory>
#include <string>
namespace facebook::react {
std::unordered_map<std::string, std::function<std::shared_ptr<TurboModule>(std::shared_ptr<CallInvoker> jsInvoker)>> &
globalExportedCxxTurboModuleMap();
/**
* Registers the given C++ TurboModule initializer function
* in the global module map.
* This needs to be called before the TurboModule is requested from JS,
* for example in a `+ load`, your AppDelegate's start, or from Java init.
*/
void registerCxxModuleToGlobalModuleMap(
std::string name,
std::function<std::shared_ptr<TurboModule>(std::shared_ptr<CallInvoker> jsInvoker)> moduleProviderFunc);
} // namespace facebook::react

View File

@@ -0,0 +1,212 @@
/*
* 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 "TurboCxxModule.h"
#include <vector>
#include <ReactCommon/TurboModuleUtils.h>
#include <glog/logging.h>
#include <jsi/JSIDynamic.h>
using namespace facebook;
using namespace facebook::xplat::module;
namespace facebook::react {
namespace {
CxxModule::Callback makeTurboCxxModuleCallback(
std::weak_ptr<CallbackWrapper> weakWrapper) {
return [weakWrapper, wrapperWasCalled = false](
const std::vector<folly::dynamic>& args) mutable {
if (wrapperWasCalled) {
LOG(FATAL) << "callback arg cannot be called more than once";
}
auto strongWrapper = weakWrapper.lock();
if (!strongWrapper) {
return;
}
strongWrapper->jsInvoker().invokeAsync(
[weakWrapper, args](jsi::Runtime& rt) {
auto strongWrapper2 = weakWrapper.lock();
if (!strongWrapper2) {
return;
}
std::vector<jsi::Value> innerArgs;
innerArgs.reserve(args.size());
for (auto& a : args) {
innerArgs.push_back(jsi::valueFromDynamic(rt, a));
}
strongWrapper2->callback().call(
rt, (const jsi::Value*)innerArgs.data(), innerArgs.size());
strongWrapper2->destroy();
});
wrapperWasCalled = true;
};
}
} // namespace
TurboCxxModule::TurboCxxModule(
std::unique_ptr<CxxModule> cxxModule,
std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule(cxxModule->getName(), std::move(jsInvoker)),
cxxMethods_(cxxModule->getMethods()),
cxxModule_(std::move(cxxModule)) {}
jsi::Value TurboCxxModule::create(
jsi::Runtime& runtime,
const jsi::PropNameID& propName) {
std::string propNameUtf8 = propName.utf8(runtime);
if (propNameUtf8 == "getConstants") {
// This is special cased because `getConstants()` is already a part of
// CxxModule.
return jsi::Function::createFromHostFunction(
runtime,
propName,
0,
[this](
jsi::Runtime& rt,
const jsi::Value& /*thisVal*/,
const jsi::Value* /*args*/,
size_t /*count*/) {
jsi::Object result(rt);
auto constants = cxxModule_->getConstants();
for (auto& pair : constants) {
result.setProperty(
rt, pair.first.c_str(), jsi::valueFromDynamic(rt, pair.second));
}
return result;
});
} else {
for (auto& method : cxxMethods_) {
if (method.name == propNameUtf8) {
return jsi::Function::createFromHostFunction(
runtime,
propName,
0,
[this, propNameUtf8](
jsi::Runtime& rt,
const jsi::Value& /*thisVal*/,
const jsi::Value* args,
size_t count) {
return invokeMethod(rt, propNameUtf8, args, count);
});
}
}
}
return jsi::Value::undefined();
}
std::vector<jsi::PropNameID> TurboCxxModule::getPropertyNames(
jsi::Runtime& runtime) {
std::vector<jsi::PropNameID> result;
result.reserve(cxxMethods_.size() + 1);
result.push_back(jsi::PropNameID::forUtf8(runtime, "getConstants"));
for (auto& cxxMethod : cxxMethods_) {
result.push_back(jsi::PropNameID::forUtf8(runtime, cxxMethod.name));
}
return result;
}
jsi::Value TurboCxxModule::invokeMethod(
jsi::Runtime& runtime,
const std::string& methodName,
const jsi::Value* args,
size_t count) {
auto it = cxxMethods_.begin();
for (; it != cxxMethods_.end(); it++) {
auto method = *it;
if (method.name == methodName) {
break;
}
}
if (it == cxxMethods_.end()) {
throw std::runtime_error(
"Function '" + methodName + "' cannot be found on cxxmodule: " + name_);
}
auto method = *it;
if (method.syncFunc) {
auto innerArgs = folly::dynamic::array();
for (size_t i = 0; i < count; i++) {
innerArgs.push_back(jsi::dynamicFromValue(runtime, args[i]));
}
return jsi::valueFromDynamic(
runtime, method.syncFunc(std::move(innerArgs)));
} else if (method.func && !method.isPromise) {
// Async method.
CxxModule::Callback first;
CxxModule::Callback second;
if (count < method.callbacks) {
throw std::invalid_argument(
"Expected " + std::to_string(method.callbacks) +
" callbacks, but only " + std::to_string(count) +
" parameters provided");
}
if (method.callbacks == 1) {
auto wrapper = CallbackWrapper::createWeak(
args[count - 1].getObject(runtime).getFunction(runtime),
runtime,
jsInvoker_);
first = makeTurboCxxModuleCallback(wrapper);
} else if (method.callbacks == 2) {
auto wrapper1 = CallbackWrapper::createWeak(
args[count - 2].getObject(runtime).getFunction(runtime),
runtime,
jsInvoker_);
auto wrapper2 = CallbackWrapper::createWeak(
args[count - 1].getObject(runtime).getFunction(runtime),
runtime,
jsInvoker_);
first = makeTurboCxxModuleCallback(wrapper1);
second = makeTurboCxxModuleCallback(wrapper2);
}
auto innerArgs = folly::dynamic::array();
for (size_t i = 0; i < count - method.callbacks; i++) {
innerArgs.push_back(jsi::dynamicFromValue(runtime, args[i]));
}
method.func(std::move(innerArgs), first, second);
} else if (method.isPromise) {
return createPromiseAsJSIValue(
runtime,
[method, args, count, this](
jsi::Runtime& rt, std::shared_ptr<Promise> promise) {
auto resolveWrapper = CallbackWrapper::createWeak(
promise->resolve_.getFunction(rt), rt, jsInvoker_);
auto rejectWrapper = CallbackWrapper::createWeak(
promise->reject_.getFunction(rt), rt, jsInvoker_);
CxxModule::Callback resolve =
makeTurboCxxModuleCallback(resolveWrapper);
CxxModule::Callback reject =
makeTurboCxxModuleCallback(rejectWrapper);
auto innerArgs = folly::dynamic::array();
for (size_t i = 0; i < count; i++) {
innerArgs.push_back(jsi::dynamicFromValue(rt, args[i]));
}
method.func(std::move(innerArgs), resolve, reject);
});
}
return jsi::Value::undefined();
}
} // 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.
*/
#pragma once
#include <memory>
#include <vector>
#include <cxxreact/CxxModule.h>
#include "TurboModule.h"
namespace facebook::react {
/**
* A helper class to convert the legacy CxxModule instance to a TurboModule
* instance. This should be used only for migration purpose (to TurboModule),
* since it's not very performant due to a lot of back-and-forth value
* conversions between folly::dynamic and jsi::Value.
*/
class JSI_EXPORT TurboCxxModule : public TurboModule {
public:
TurboCxxModule(std::unique_ptr<facebook::xplat::module::CxxModule> cxxModule, std::shared_ptr<CallInvoker> jsInvoker);
facebook::jsi::Value create(facebook::jsi::Runtime &runtime, const facebook::jsi::PropNameID &propName) override;
std::vector<facebook::jsi::PropNameID> getPropertyNames(facebook::jsi::Runtime &runtime) override;
jsi::Value invokeMethod(jsi::Runtime &runtime, const std::string &methodName, const jsi::Value *args, size_t count);
private:
std::vector<facebook::xplat::module::CxxModule::Method> cxxMethods_;
std::unique_ptr<facebook::xplat::module::CxxModule> cxxModule_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,64 @@
/*
* 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 "TurboModule.h"
#include <react/debug/react_native_assert.h>
namespace facebook::react {
TurboModuleMethodValueKind getTurboModuleMethodValueKind(
jsi::Runtime& rt,
const jsi::Value* value) {
if ((value == nullptr) || value->isUndefined() || value->isNull()) {
return VoidKind;
} else if (value->isBool()) {
return BooleanKind;
} else if (value->isNumber()) {
return NumberKind;
} else if (value->isString()) {
return StringKind;
} else if (value->isObject()) {
auto object = value->asObject(rt);
if (object.isArray(rt)) {
return ArrayKind;
} else if (object.isFunction(rt)) {
return FunctionKind;
}
return ObjectKind;
}
react_native_assert(false && "Unsupported jsi::Value kind");
return VoidKind;
}
TurboModule::TurboModule(
std::string name,
std::shared_ptr<CallInvoker> jsInvoker)
: name_(std::move(name)), jsInvoker_(std::move(jsInvoker)) {}
void TurboModule::emitDeviceEvent(
const std::string& eventName,
ArgFactory&& argFactory) {
jsInvoker_->invokeAsync([eventName, argFactory = std::move(argFactory)](
jsi::Runtime& rt) {
jsi::Value emitter = rt.global().getProperty(rt, "__rctDeviceEventEmitter");
if (!emitter.isUndefined()) {
jsi::Object emitterObject = emitter.asObject(rt);
// TODO: consider caching these
jsi::Function emitFunction =
emitterObject.getPropertyAsFunction(rt, "emit");
std::vector<jsi::Value> args;
args.emplace_back(jsi::String::createFromAscii(rt, eventName.c_str()));
if (argFactory) {
argFactory(rt, args);
}
emitFunction.callWithThis(
rt, emitterObject, (const jsi::Value*)args.data(), args.size());
}
});
}
} // namespace facebook::react

View File

@@ -0,0 +1,150 @@
/*
* 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 <unordered_map>
#include <jsi/jsi.h>
#include <ReactCommon/CallInvoker.h>
#include <react/bridging/EventEmitter.h>
namespace facebook::react {
/**
* For now, support the same set of return types as existing impl.
* This can be improved to support richer typed objects.
*/
enum TurboModuleMethodValueKind {
VoidKind,
BooleanKind,
NumberKind,
StringKind,
ObjectKind,
ArrayKind,
FunctionKind,
PromiseKind,
};
/**
* Determines TurboModuleMethodValueKind based on the jsi::Value type.
*/
TurboModuleMethodValueKind getTurboModuleMethodValueKind(jsi::Runtime &rt, const jsi::Value *value);
class TurboCxxModule;
class TurboModuleBinding;
/**
* Base HostObject class for every module to be exposed to JS
*/
class JSI_EXPORT TurboModule : public jsi::HostObject {
public:
TurboModule(std::string name, std::shared_ptr<CallInvoker> jsInvoker);
// DO NOT OVERRIDE - it will become final in a future release.
// This method provides automatic caching of properties on the TurboModule's
// JS representation. To customize lookup of properties, override `create`.
// Note: keep this method declared inline to avoid conflicts
// between RTTI and non-RTTI compilation units
jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &propName) override
{
auto prop = create(runtime, propName);
// If we have a JS wrapper, cache the result of this lookup
// We don't cache misses, to allow for methodMap_ to dynamically be
// extended
if (jsRepresentation_ && !prop.isUndefined()) {
jsRepresentation_->lock(runtime).asObject(runtime).setProperty(runtime, propName, prop);
}
return prop;
}
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &runtime) override
{
std::vector<jsi::PropNameID> result;
result.reserve(methodMap_.size());
for (auto it = methodMap_.cbegin(); it != methodMap_.cend(); ++it) {
result.push_back(jsi::PropNameID::forUtf8(runtime, it->first));
}
return result;
}
protected:
const std::string name_;
std::shared_ptr<CallInvoker> jsInvoker_;
struct MethodMetadata {
size_t argCount;
jsi::Value (*invoker)(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t count);
};
std::unordered_map<std::string, MethodMetadata> methodMap_;
friend class TurboModuleTestFixtureInternal;
std::unordered_map<std::string, std::shared_ptr<IAsyncEventEmitter>> eventEmitterMap_;
using ArgFactory = std::function<void(jsi::Runtime &runtime, std::vector<jsi::Value> &args)>;
/**
* Calls RCTDeviceEventEmitter.emit to JavaScript, with given event name and
* an optional list of arguments.
* If present, argFactory is a callback used to construct extra arguments,
* e.g.
*
* emitDeviceEvent(rt, "myCustomEvent",
* [](jsi::Runtime& rt, std::vector<jsi::Value>& args) {
* args.emplace_back(jsi::Value(true));
* args.emplace_back(jsi::Value(42));
* });
*/
void emitDeviceEvent(const std::string &eventName, ArgFactory &&argFactory = nullptr);
// Backwards compatibility version
void emitDeviceEvent(
jsi::Runtime & /*runtime*/,
const std::string &eventName,
ArgFactory &&argFactory = nullptr)
{
emitDeviceEvent(eventName, std::move(argFactory));
}
virtual jsi::Value create(jsi::Runtime &runtime, const jsi::PropNameID &propName)
{
std::string propNameUtf8 = propName.utf8(runtime);
if (auto methodIter = methodMap_.find(propNameUtf8); methodIter != methodMap_.end()) {
const MethodMetadata &meta = methodIter->second;
return jsi::Function::createFromHostFunction(
runtime,
propName,
static_cast<unsigned int>(meta.argCount),
[this, meta](
jsi::Runtime &rt, [[maybe_unused]] const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
return meta.invoker(rt, *this, args, count);
});
} else if (auto eventEmitterIter = eventEmitterMap_.find(propNameUtf8);
eventEmitterIter != eventEmitterMap_.end()) {
return eventEmitterIter->second->get(runtime, jsInvoker_);
} else {
// Neither Method nor EventEmitter were found, let JS decide what to do
return jsi::Value::undefined();
}
}
private:
friend class TurboModuleBinding;
std::unique_ptr<jsi::WeakObject> jsRepresentation_;
};
/**
* An app/platform-specific provider function to get an instance of a module
* given a name.
*/
using TurboModuleProviderFunctionType = std::function<std::shared_ptr<TurboModule>(const std::string &name)>;
} // namespace facebook::react

View File

@@ -0,0 +1,200 @@
/*
* 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 "TurboModuleBinding.h"
#include <ReactCommon/TurboModuleWithJSIBindings.h>
#include <cxxreact/TraceSection.h>
#include <react/utils/jsi-utils.h>
#include <stdexcept>
#include <string>
using namespace facebook;
namespace facebook::react {
class BridgelessNativeModuleProxy : public jsi::HostObject {
TurboModuleBinding turboBinding_;
std::unique_ptr<TurboModuleBinding> legacyBinding_;
public:
BridgelessNativeModuleProxy(
jsi::Runtime& runtime,
TurboModuleProviderFunctionType&& moduleProvider,
TurboModuleProviderFunctionType&& legacyModuleProvider,
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection)
: turboBinding_(
runtime,
std::move(moduleProvider),
longLivedObjectCollection),
legacyBinding_(
legacyModuleProvider ? std::make_unique<TurboModuleBinding>(
runtime,
std::move(legacyModuleProvider),
longLivedObjectCollection)
: nullptr) {}
jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override {
/**
* BatchedBridge/NativeModules.js contains this line:
*
* module.exports = global.nativeModuleProxy
*
* This means that NativeModuleProxy is exported as a module from
* 'NativeModules.js'. Whenever some JavaScript requires 'NativeModule.js',
* Metro checks this module's __esModule property to see if the module is an
* ES6 module.
*
* We return false from this property access, so that we can fail on the
* actual NativeModule require that happens later, which is more actionable.
*/
std::string moduleName = name.utf8(runtime);
if (moduleName == "__esModule") {
return {false};
}
auto turboModule = turboBinding_.getModule(runtime, moduleName);
if (turboModule.isObject()) {
return turboModule;
}
if (legacyBinding_) {
auto legacyModule = legacyBinding_->getModule(runtime, moduleName);
if (legacyModule.isObject()) {
return legacyModule;
}
}
return jsi::Value::null();
}
void set(
jsi::Runtime& runtime,
const jsi::PropNameID& /*name*/,
const jsi::Value& /*value*/) override {
throw jsi::JSError(
runtime,
"Tried to insert a NativeModule into the bridge's NativeModule proxy.");
}
};
/**
* Public API to install the TurboModule system.
*/
TurboModuleBinding::TurboModuleBinding(
jsi::Runtime& runtime,
TurboModuleProviderFunctionType&& moduleProvider,
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection)
: runtime_(runtime),
moduleProvider_(std::move(moduleProvider)),
longLivedObjectCollection_(std::move(longLivedObjectCollection)) {}
void TurboModuleBinding::install(
jsi::Runtime& runtime,
TurboModuleProviderFunctionType&& moduleProvider,
TurboModuleProviderFunctionType&& legacyModuleProvider,
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection) {
// TODO(T208105802): We can get this information from the native side!
auto isBridgeless = runtime.global().hasProperty(runtime, "RN$Bridgeless");
if (!isBridgeless) {
runtime.global().setProperty(
runtime,
"__turboModuleProxy",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "__turboModuleProxy"),
1,
[binding = TurboModuleBinding(
runtime,
std::move(moduleProvider),
longLivedObjectCollection)](
jsi::Runtime& rt,
const jsi::Value& /*thisVal*/,
const jsi::Value* args,
size_t count) {
if (count < 1) {
throw std::invalid_argument(
"__turboModuleProxy must be called with at least 1 argument");
}
std::string moduleName = args[0].getString(rt).utf8(rt);
return binding.getModule(rt, moduleName);
}));
return;
}
defineReadOnlyGlobal(
runtime,
"nativeModuleProxy",
jsi::Object::createFromHostObject(
runtime,
std::make_shared<BridgelessNativeModuleProxy>(
runtime,
std::move(moduleProvider),
std::move(legacyModuleProvider),
longLivedObjectCollection)));
}
TurboModuleBinding::~TurboModuleBinding() {
LongLivedObjectCollection::get(runtime_).clear();
if (longLivedObjectCollection_) {
longLivedObjectCollection_->clear();
}
}
jsi::Value TurboModuleBinding::getModule(
jsi::Runtime& runtime,
const std::string& moduleName) const {
std::shared_ptr<TurboModule> module;
{
TraceSection s("TurboModuleBinding::moduleProvider", "module", moduleName);
module = moduleProvider_(moduleName);
}
if (module) {
TurboModuleWithJSIBindings::installJSIBindings(module, runtime);
// What is jsRepresentation? A cache for the TurboModule's properties
// Henceforth, always return the cache (i.e: jsRepresentation) to JavaScript
//
// If a jsRepresentation is found on the TurboModule, return it.
//
// Note: TurboModules are cached by name in TurboModuleManagers. Hence,
// jsRepresentation is also cached by by name by the TurboModuleManager
auto& weakJsRepresentation = module->jsRepresentation_;
if (weakJsRepresentation) {
auto jsRepresentation = weakJsRepresentation->lock(runtime);
if (!jsRepresentation.isUndefined()) {
return jsRepresentation;
}
}
// Status: No jsRepresentation found on TurboModule
// Create a brand new jsRepresentation, and attach it to TurboModule
jsi::Object jsRepresentation(runtime);
weakJsRepresentation =
std::make_unique<jsi::WeakObject>(runtime, jsRepresentation);
// Lazily populate the jsRepresentation, on property access.
//
// How does this work?
// 1. Initially jsRepresentation is empty: {}
// 2. If property lookup on jsRepresentation fails, the JS runtime will
// search jsRepresentation's prototype: jsi::Object(TurboModule).
// 3. TurboModule::get(runtime, propKey) executes. This creates the
// property, caches it on jsRepresentation, then returns it to
// JavaScript.
auto hostObject =
jsi::Object::createFromHostObject(runtime, std::move(module));
jsRepresentation.setProperty(runtime, "__proto__", std::move(hostObject));
return jsRepresentation;
} else {
return jsi::Value::null();
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,57 @@
/*
* 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 <jsi/jsi.h>
#include <react/bridging/LongLivedObject.h>
#include <ReactCommon/TurboModule.h>
namespace facebook::react {
class BridgelessNativeModuleProxy;
/**
* Represents the JavaScript binding for the TurboModule system.
*/
class TurboModuleBinding {
public:
/*
* Installs TurboModuleBinding into JavaScript runtime.
* Thread synchronization must be enforced externally.
*/
static void install(
jsi::Runtime &runtime,
TurboModuleProviderFunctionType &&moduleProvider,
TurboModuleProviderFunctionType &&legacyModuleProvider = nullptr,
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection = nullptr);
TurboModuleBinding(
jsi::Runtime &runtime,
TurboModuleProviderFunctionType &&moduleProvider,
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection);
virtual ~TurboModuleBinding();
private:
friend BridgelessNativeModuleProxy;
/**
* A lookup function exposed to JS to get an instance of a TurboModule
* for the given name.
*/
jsi::Value getModule(jsi::Runtime &runtime, const std::string &moduleName) const;
jsi::Runtime &runtime_;
TurboModuleProviderFunctionType moduleProvider_;
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,327 @@
/*
* 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 "TurboModulePerfLogger.h"
namespace facebook::react::TurboModulePerfLogger {
std::unique_ptr<NativeModulePerfLogger> g_perfLogger = nullptr;
void enableLogging(std::unique_ptr<NativeModulePerfLogger>&& newPerfLogger) {
g_perfLogger = std::move(newPerfLogger);
}
void disableLogging() {
g_perfLogger = nullptr;
}
void moduleDataCreateStart(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleDataCreateStart(moduleName, id);
}
}
void moduleDataCreateEnd(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleDataCreateEnd(moduleName, id);
}
}
void moduleCreateStart(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateStart(moduleName, id);
}
}
void moduleCreateCacheHit(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateCacheHit(moduleName, id);
}
}
void moduleCreateConstructStart(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateConstructStart(moduleName, id);
}
}
void moduleCreateConstructEnd(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateConstructEnd(moduleName, id);
}
}
void moduleCreateSetUpStart(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateSetUpStart(moduleName, id);
}
}
void moduleCreateSetUpEnd(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateSetUpEnd(moduleName, id);
}
}
void moduleCreateEnd(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateEnd(moduleName, id);
}
}
void moduleCreateFail(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateFail(moduleName, id);
}
}
void moduleJSRequireBeginningStart(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireBeginningStart(moduleName);
}
}
void moduleJSRequireBeginningCacheHit(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireBeginningCacheHit(moduleName);
}
}
void moduleJSRequireBeginningEnd(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireBeginningEnd(moduleName);
}
}
void moduleJSRequireBeginningFail(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireBeginningFail(moduleName);
}
}
void moduleJSRequireEndingStart(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireEndingStart(moduleName);
}
}
void moduleJSRequireEndingEnd(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireEndingEnd(moduleName);
}
}
void moduleJSRequireEndingFail(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireEndingFail(moduleName);
}
}
void syncMethodCallStart(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallStart(moduleName, methodName);
}
}
void syncMethodCallArgConversionStart(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallArgConversionStart(moduleName, methodName);
}
}
void syncMethodCallArgConversionEnd(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallArgConversionEnd(moduleName, methodName);
}
}
void syncMethodCallExecutionStart(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallExecutionStart(moduleName, methodName);
}
}
void syncMethodCallExecutionEnd(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallExecutionEnd(moduleName, methodName);
}
}
void syncMethodCallReturnConversionStart(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallReturnConversionStart(moduleName, methodName);
}
}
void syncMethodCallReturnConversionEnd(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallReturnConversionEnd(moduleName, methodName);
}
}
void syncMethodCallEnd(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallEnd(moduleName, methodName);
}
}
void syncMethodCallFail(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallFail(moduleName, methodName);
}
}
void asyncMethodCallStart(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallStart(moduleName, methodName);
}
}
void asyncMethodCallArgConversionStart(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallArgConversionStart(moduleName, methodName);
}
}
void asyncMethodCallArgConversionEnd(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallArgConversionEnd(moduleName, methodName);
}
}
void asyncMethodCallDispatch(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallDispatch(moduleName, methodName);
}
}
void asyncMethodCallEnd(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallEnd(moduleName, methodName);
}
}
void asyncMethodCallFail(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallFail(moduleName, methodName);
}
}
void asyncMethodCallBatchPreprocessStart() {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallBatchPreprocessStart();
}
}
void asyncMethodCallBatchPreprocessEnd(int batchSize) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallBatchPreprocessEnd(batchSize);
}
}
void asyncMethodCallExecutionStart(
const char* moduleName,
const char* methodName,
int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallExecutionStart(moduleName, methodName, id);
}
}
void asyncMethodCallExecutionArgConversionStart(
const char* moduleName,
const char* methodName,
int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallExecutionArgConversionStart(
moduleName, methodName, id);
}
}
void asyncMethodCallExecutionArgConversionEnd(
const char* moduleName,
const char* methodName,
int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallExecutionArgConversionEnd(
moduleName, methodName, id);
}
}
void asyncMethodCallExecutionEnd(
const char* moduleName,
const char* methodName,
int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallExecutionEnd(moduleName, methodName, id);
}
}
void asyncMethodCallExecutionFail(
const char* moduleName,
const char* methodName,
int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallExecutionFail(moduleName, methodName, id);
}
}
} // namespace facebook::react::TurboModulePerfLogger

View File

@@ -0,0 +1,79 @@
/*
* 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 <reactperflogger/NativeModulePerfLogger.h>
#include <memory>
namespace facebook::react::TurboModulePerfLogger {
void enableLogging(std::unique_ptr<NativeModulePerfLogger> &&logger);
void disableLogging();
void moduleDataCreateStart(const char *moduleName, int32_t id);
void moduleDataCreateEnd(const char *moduleName, int32_t id);
/**
* Create NativeModule platform object
*/
void moduleCreateStart(const char *moduleName, int32_t id);
void moduleCreateCacheHit(const char *moduleName, int32_t id);
void moduleCreateConstructStart(const char *moduleName, int32_t id);
void moduleCreateConstructEnd(const char *moduleName, int32_t id);
void moduleCreateSetUpStart(const char *moduleName, int32_t id);
void moduleCreateSetUpEnd(const char *moduleName, int32_t id);
void moduleCreateEnd(const char *moduleName, int32_t id);
void moduleCreateFail(const char *moduleName, int32_t id);
/**
* JS require beginning
*/
void moduleJSRequireBeginningStart(const char *moduleName);
void moduleJSRequireBeginningCacheHit(const char *moduleName);
void moduleJSRequireBeginningEnd(const char *moduleName);
void moduleJSRequireBeginningFail(const char *moduleName);
/**
* JS require ending
*/
void moduleJSRequireEndingStart(const char *moduleName);
void moduleJSRequireEndingEnd(const char *moduleName);
void moduleJSRequireEndingFail(const char *moduleName);
// Sync method calls
void syncMethodCallStart(const char *moduleName, const char *methodName);
void syncMethodCallArgConversionStart(const char *moduleName, const char *methodName);
void syncMethodCallArgConversionEnd(const char *moduleName, const char *methodName);
void syncMethodCallExecutionStart(const char *moduleName, const char *methodName);
void syncMethodCallExecutionEnd(const char *moduleName, const char *methodName);
void syncMethodCallReturnConversionStart(const char *moduleName, const char *methodName);
void syncMethodCallReturnConversionEnd(const char *moduleName, const char *methodName);
void syncMethodCallEnd(const char *moduleName, const char *methodName);
void syncMethodCallFail(const char *moduleName, const char *methodName);
// Async method calls
void asyncMethodCallStart(const char *moduleName, const char *methodName);
void asyncMethodCallArgConversionStart(const char *moduleName, const char *methodName);
void asyncMethodCallArgConversionEnd(const char *moduleName, const char *methodName);
void asyncMethodCallDispatch(const char *moduleName, const char *methodName);
void asyncMethodCallEnd(const char *moduleName, const char *methodName);
void asyncMethodCallFail(const char *moduleName, const char *methodName);
/**
* Pre-processing async method call batch
*/
void asyncMethodCallBatchPreprocessStart();
void asyncMethodCallBatchPreprocessEnd(int batchSize);
// Async method call execution
void asyncMethodCallExecutionStart(const char *moduleName, const char *methodName, int32_t id);
void asyncMethodCallExecutionArgConversionStart(const char *moduleName, const char *methodName, int32_t id);
void asyncMethodCallExecutionArgConversionEnd(const char *moduleName, const char *methodName, int32_t id);
void asyncMethodCallExecutionEnd(const char *moduleName, const char *methodName, int32_t id);
void asyncMethodCallExecutionFail(const char *moduleName, const char *methodName, int32_t id);
} // namespace facebook::react::TurboModulePerfLogger

View File

@@ -0,0 +1,52 @@
/*
* 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 "TurboModuleUtils.h"
namespace facebook::react {
Promise::Promise(jsi::Runtime& rt, jsi::Function resolve, jsi::Function reject)
: LongLivedObject(rt),
resolve_(std::move(resolve)),
reject_(std::move(reject)) {}
void Promise::resolve(const jsi::Value& result) {
resolve_.call(runtime_, result);
}
void Promise::reject(const std::string& message) {
jsi::Object error(runtime_);
error.setProperty(
runtime_, "message", jsi::String::createFromUtf8(runtime_, message));
reject_.call(runtime_, error);
}
jsi::Value createPromiseAsJSIValue(
jsi::Runtime& rt,
PromiseSetupFunctionType&& func) {
jsi::Function JSPromise = rt.global().getPropertyAsFunction(rt, "Promise");
jsi::Function fn = jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "fn"),
2,
[func = std::move(func)](
jsi::Runtime& rt2,
const jsi::Value& /*thisVal*/,
const jsi::Value* args,
size_t /*count*/) {
jsi::Function resolve = args[0].getObject(rt2).getFunction(rt2);
jsi::Function reject = args[1].getObject(rt2).getFunction(rt2);
auto wrapper = std::make_shared<Promise>(
rt2, std::move(resolve), std::move(reject));
func(rt2, wrapper);
return jsi::Value::undefined();
});
return JSPromise.callAsConstructor(rt, fn);
}
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* 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>
#include <react/bridging/LongLivedObject.h>
#include <cassert>
#include <functional>
#include <string>
namespace facebook::react {
struct Promise : public LongLivedObject {
Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject);
void resolve(const jsi::Value &result);
void reject(const std::string &message);
jsi::Function resolve_;
jsi::Function reject_;
};
using PromiseSetupFunctionType = std::function<void(jsi::Runtime &rt, std::shared_ptr<Promise>)>;
jsi::Value createPromiseAsJSIValue(jsi::Runtime &rt, PromiseSetupFunctionType &&func);
} // namespace facebook::react

View File

@@ -0,0 +1,23 @@
/*
* 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 "TurboModuleWithJSIBindings.h"
#include <ReactCommon/TurboModule.h>
namespace facebook::react {
/* static */ void TurboModuleWithJSIBindings::installJSIBindings(
const std::shared_ptr<TurboModule>& cxxModule,
jsi::Runtime& runtime) {
if (auto* cxxModuleWithJSIBindings =
dynamic_cast<TurboModuleWithJSIBindings*>(cxxModule.get())) {
cxxModuleWithJSIBindings->installJSIBindingsWithRuntime(runtime);
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,27 @@
/*
* 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 <ReactCommon/CallInvoker.h>
#include <jsi/jsi.h>
namespace facebook::react {
class TurboModule;
class TurboModuleWithJSIBindings {
public:
virtual ~TurboModuleWithJSIBindings() = default;
static void installJSIBindings(const std::shared_ptr<TurboModule> &cxxModule, jsi::Runtime &runtime);
private:
virtual void installJSIBindingsWithRuntime(jsi::Runtime &runtime) = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,108 @@
/*
* 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 <XCTest/XCTest.h>
#import <ReactCommon/RCTTurboModule.h>
#import <hermes/hermes.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import <OCMock/OCMock.h>
using namespace facebook::react;
@interface RCTTestTurboModule : NSObject <RCTBridgeModule>
@end
@implementation RCTTestTurboModule
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(testMethodWhichTakesObject : (id)object) {}
@end
class StubNativeMethodCallInvoker : public NativeMethodCallInvoker {
public:
void invokeAsync(const std::string &methodName, NativeMethodCallFunc &&func) noexcept override
{
func();
}
void invokeSync(const std::string &methodName, NativeMethodCallFunc &&func) noexcept override
{
func();
}
};
@interface RCTTurboModuleTests : XCTestCase
@end
@implementation RCTTurboModuleTests {
std::unique_ptr<ObjCTurboModule> module_;
RCTTestTurboModule *instance_;
}
- (void)setUp
{
[super setUp];
instance_ = OCMClassMock([RCTTestTurboModule class]);
ObjCTurboModule::InitParams params = {
.moduleName = "TestModule",
.instance = instance_,
.jsInvoker = nullptr,
.nativeMethodCallInvoker = std::make_shared<StubNativeMethodCallInvoker>(),
.isSyncModule = false,
.shouldVoidMethodsExecuteSync = true,
};
module_ = std::make_unique<ObjCTurboModule>(params);
}
- (void)tearDown
{
module_ = nullptr;
instance_ = nil;
[super tearDown];
}
- (void)testInvokeTurboModuleWithNull
{
auto hermesRuntime = facebook::hermes::makeHermesRuntime();
facebook::jsi::Runtime *rt = hermesRuntime.get();
// Empty object
facebook::jsi::Value args[1] = {facebook::jsi::Object(*rt)};
module_->invokeObjCMethod(
*rt, VoidKind, "testMethodWhichTakesObject", @selector(testMethodWhichTakesObject:), args, 1);
OCMVerify(OCMTimes(1), [instance_ testMethodWhichTakesObject:@{}]);
// Object with one key
args[0].asObject(*rt).setProperty(*rt, "foo", "bar");
module_->invokeObjCMethod(
*rt, VoidKind, "testMethodWhichTakesObject", @selector(testMethodWhichTakesObject:), args, 1);
OCMVerify(OCMTimes(1), [instance_ testMethodWhichTakesObject:@{@"foo" : @"bar"}]);
// Object with key without value
args[0].asObject(*rt).setProperty(*rt, "foo", facebook::jsi::Value::null());
module_->invokeObjCMethod(
*rt, VoidKind, "testMethodWhichTakesObject", @selector(testMethodWhichTakesObject:), args, 1);
if (ReactNativeFeatureFlags::enableModuleArgumentNSNullConversionIOS()) {
OCMVerify(OCMTimes(1), [instance_ testMethodWhichTakesObject:@{@"foo" : (id)kCFNull}]);
} else {
OCMVerify(OCMTimes(2), [instance_ testMethodWhichTakesObject:@{}]);
}
// Null
args[0] = facebook::jsi::Value::null();
module_->invokeObjCMethod(
*rt, VoidKind, "testMethodWhichTakesObject", @selector(testMethodWhichTakesObject:), args, 1);
OCMVerify(OCMTimes(1), [instance_ testMethodWhichTakesObject:nil]);
}
@end

View File

@@ -0,0 +1,191 @@
/*
* 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 "JavaInteropTurboModule.h"
namespace facebook::react {
namespace {
// This is used for generating short exception strings.
std::string getType(jsi::Runtime& rt, const jsi::Value& v) {
if (v.isUndefined()) {
return "undefined";
} else if (v.isNull()) {
return "null";
} else if (v.isBool()) {
return v.getBool() ? "true" : "false";
} else if (v.isNumber()) {
return "number";
} else if (v.isString()) {
return "string";
} else if (v.isSymbol()) {
return "symbol";
} else if (v.isBigInt()) {
return "bigint";
} else if (v.isObject()) {
return v.getObject(rt).isFunction(rt) ? "function" : "object";
} else {
return "unknown";
}
}
} // namespace
JavaInteropTurboModule::JavaInteropTurboModule(
const JavaTurboModule::InitParams& params,
const std::vector<MethodDescriptor>& methodDescriptors)
: JavaTurboModule(params),
methodDescriptors_(methodDescriptors),
methodIDs_(methodDescriptors.size()),
constantsCache_(jsi::Value::undefined()) {
for (const auto& methodDescriptor : methodDescriptors) {
methodMap_[methodDescriptor.methodName] = MethodMetadata{
.argCount = static_cast<size_t>(methodDescriptor.jsArgCount),
.invoker = nullptr};
}
}
jsi::Value JavaInteropTurboModule::create(
jsi::Runtime& runtime,
const jsi::PropNameID& propName) {
for (size_t i = 0; i < methodDescriptors_.size(); i += 1) {
if (methodDescriptors_[i].methodName == propName.utf8(runtime)) {
if (propName.utf8(runtime) == "getConstants") {
return jsi::Function::createFromHostFunction(
runtime,
propName,
static_cast<unsigned int>(methodDescriptors_[i].jsArgCount),
[this, i](
jsi::Runtime& rt,
const jsi::Value& /*thisVal*/,
const jsi::Value* args,
size_t count) mutable {
if (!this->constantsCache_.isUndefined()) {
return jsi::Value(rt, this->constantsCache_);
}
jsi::Value ret = this->invokeJavaMethod(
rt,
this->methodDescriptors_[i].jsiReturnKind,
this->methodDescriptors_[i].methodName,
this->methodDescriptors_[i].jniSignature,
args,
count,
this->methodIDs_[i]);
bool isRetValid = ret.isUndefined() || ret.isNull() ||
(ret.isObject() && !ret.asObject(rt).isFunction(rt));
if (!isRetValid) {
throw new jsi::JSError(
rt,
"Expected NativeModule " + this->name_ +
".getConstants() to return: null, undefined, or an object. But, got: " +
getType(rt, ret));
}
if (ret.isUndefined() || ret.isNull()) {
this->constantsCache_ = jsi::Object(rt);
} else {
this->constantsCache_ = jsi::Value(rt, ret);
}
return ret;
});
}
return jsi::Function::createFromHostFunction(
runtime,
propName,
static_cast<unsigned int>(methodDescriptors_[i].jsArgCount),
[this, i](
jsi::Runtime& rt,
const jsi::Value& /*thisVal*/,
const jsi::Value* args,
size_t count) {
return this->invokeJavaMethod(
rt,
this->methodDescriptors_[i].jsiReturnKind,
this->methodDescriptors_[i].methodName,
this->methodDescriptors_[i].jniSignature,
args,
count,
this->methodIDs_[i]);
});
}
}
jsi::Object constants = getConstants(runtime).asObject(runtime);
jsi::Value constant = constants.getProperty(runtime, propName);
if (!constant.isUndefined()) {
// TODO(T145105887): Output warning. Tried to access a constant as a
// property on the native module object. Please migrate to getConstants().
}
return constant;
}
bool JavaInteropTurboModule::exportsConstants() {
for (size_t i = 0; i < methodDescriptors_.size(); i += 1) {
if (methodDescriptors_[i].methodName == "getConstants") {
return true;
}
}
return false;
}
const jsi::Value& JavaInteropTurboModule::getConstants(jsi::Runtime& runtime) {
if (!constantsCache_.isUndefined()) {
return constantsCache_;
}
if (!exportsConstants()) {
constantsCache_ = jsi::Object(runtime);
return constantsCache_;
}
jsi::Value getConstantsProp =
get(runtime, jsi::PropNameID::forAscii(runtime, "getConstants"));
if (getConstantsProp.isObject()) {
jsi::Object getConstantsObj = getConstantsProp.asObject(runtime);
if (getConstantsObj.isFunction(runtime)) {
jsi::Function getConstantsFn = getConstantsObj.asFunction(runtime);
getConstantsFn.call(runtime);
return constantsCache_;
}
}
// Unable to invoke the getConstants() method.
// Maybe the module didn't define a getConstants() method.
// Default constants to {}, so no constants are spread into the NativeModule
constantsCache_ = jsi::Object(runtime);
return constantsCache_;
}
std::vector<facebook::jsi::PropNameID> JavaInteropTurboModule::getPropertyNames(
facebook::jsi::Runtime& runtime) {
std::vector<facebook::jsi::PropNameID> propNames =
JavaTurboModule::getPropertyNames(runtime);
jsi::Object constants = getConstants(runtime).asObject(runtime);
jsi::Array constantNames = constants.getPropertyNames(runtime);
for (size_t i = 0; i < constantNames.size(runtime); i += 1) {
jsi::Value constantName = constantNames.getValueAtIndex(runtime, i);
if (constantName.isString()) {
propNames.push_back(
jsi::PropNameID::forString(runtime, constantName.asString(runtime)));
}
}
return propNames;
}
} // namespace facebook::react

View File

@@ -0,0 +1,48 @@
/*
* 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 <ReactCommon/TurboModule.h>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include "JavaTurboModule.h"
namespace facebook::react {
class JSI_EXPORT JavaInteropTurboModule : public JavaTurboModule {
public:
struct MethodDescriptor {
std::string methodName;
std::string jniSignature;
TurboModuleMethodValueKind jsiReturnKind;
int jsArgCount;
};
JavaInteropTurboModule(
const JavaTurboModule::InitParams &params,
const std::vector<MethodDescriptor> &methodDescriptors);
std::vector<facebook::jsi::PropNameID> getPropertyNames(facebook::jsi::Runtime &runtime) override;
protected:
jsi::Value create(jsi::Runtime &runtime, const jsi::PropNameID &propName) override;
private:
std::vector<MethodDescriptor> methodDescriptors_;
std::vector<jmethodID> methodIDs_;
jsi::Value constantsCache_;
const jsi::Value &getConstants(jsi::Runtime &runtime);
bool exportsConstants();
};
} // namespace facebook::react

View File

@@ -0,0 +1,64 @@
/*
* 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 <ReactCommon/CallInvoker.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
#include <react/bridging/CallbackWrapper.h>
#include <react/jni/JCallback.h>
namespace facebook::react {
struct JTurboModule : jni::JavaClass<JTurboModule> {
static auto constexpr kJavaDescriptor = "Lcom/facebook/react/turbomodule/core/interfaces/TurboModule;";
};
struct JTurboModuleWithJSIBindings : jni::JavaClass<JTurboModuleWithJSIBindings> {
static auto constexpr kJavaDescriptor = "Lcom/facebook/react/turbomodule/core/interfaces/TurboModuleWithJSIBindings;";
};
class JSI_EXPORT JavaTurboModule : public TurboModule {
public:
// TODO(T65603471): Should we unify this with a Fabric abstraction?
struct InitParams {
std::string moduleName;
jni::alias_ref<jobject> instance;
std::shared_ptr<CallInvoker> jsInvoker;
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker;
};
JavaTurboModule(const InitParams &params);
virtual ~JavaTurboModule();
jsi::Value invokeJavaMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind valueKind,
const std::string &methodName,
const std::string &methodSignature,
const jsi::Value *args,
size_t argCount,
jmethodID &cachedMethodID);
protected:
void configureEventEmitterCallback();
[[deprecated]] void setEventEmitterCallback(jni::alias_ref<jobject> /*unused*/)
{
configureEventEmitterCallback();
}
private:
// instance_ can be of type JTurboModule, or JNativeModule
jni::global_ref<jobject> instance_;
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,55 @@
# 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-NativeModulesApple"
s.module_name = "React_NativeModulesApple"
s.header_dir = "ReactCommon" # Use global header_dir for all subspecs for use_frameworks! compatibility
s.version = version
s.summary = "-"
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.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Headers/Private/React-Core\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_featureflags.framework/Headers\"",
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"GCC_WARN_PEDANTIC" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: './')
s.source_files = podspec_sources("ReactCommon/**/*.{mm,cpp,h}", "ReactCommon/**/*.{h}")
s.dependency "ReactCommon/turbomodule/core"
s.dependency "ReactCommon/turbomodule/bridging"
s.dependency "React-callinvoker"
s.dependency "React-Core"
s.dependency "React-cxxreact"
s.dependency "React-jsi"
s.dependency "React-featureflags"
add_dependency(s, "React-debug")
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-featureflags")
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,96 @@
/*
* 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
#import <string>
#import <vector>
#import <ReactCommon/TurboModule.h>
#import <jsi/jsi.h>
#import "RCTTurboModule.h"
namespace facebook::react {
class JSI_EXPORT ObjCInteropTurboModule : public ObjCTurboModule {
public:
struct MethodDescriptor {
std::string methodName;
SEL selector;
size_t jsArgCount;
TurboModuleMethodValueKind jsReturnKind;
};
ObjCInteropTurboModule(const ObjCTurboModule::InitParams &params);
std::vector<facebook::jsi::PropNameID> getPropertyNames(facebook::jsi::Runtime &runtime) override;
protected:
jsi::Value create(jsi::Runtime &runtime, const jsi::PropNameID &propName) override;
/**
* Why is this overriden?
*
* Purpose: Converts native module method returns from Objective C values to JavaScript values.
*
* ObjCTurboModule converts returns by returnType. But, Legacy native modules convert returns by the Objective C type:
* React Native cannot infer a method's returnType from the RCT_EXPORT_METHOD annotations.
*/
jsi::Value convertReturnIdToJSIValue(
jsi::Runtime &runtime,
const char *methodName,
TurboModuleMethodValueKind returnType,
id result) override;
/**
* Why is this overriden?
*
* Purpose: Get a native module method's argument's type, given the method name, and argument index.
*
* This override is meant to serve as a performance optimization.
*
* ObjCTurboModule computes the method argument types from the RCT_EXPORT_METHOD macros lazily.
* ObjCInteropTurboModule computes all the method argument types eagerly on module init.
*
* ObjCInteropTurboModule overrides getArgumentTypeName, so ObjCTurboModule doesn't end up re-computing the argument
* type names again.
*/
NSString *getArgumentTypeName(jsi::Runtime &runtime, NSString *methodName, int argIndex) override;
/**
* Why is this overriden?
*
* Purpose: Convert arguments from JavaScript values to Objective C values. Assign the Objective C argument to the
* method invocation.
*
* ObjCTurboModule tries to minimize reliance on RCTConvert for argument conversion. Why: RCTConvert relies on the
* RCT_EXPORT_METHOD macros, which we want to remove long term. But, Legacy native modules rely heavily on RCTConvert
* for argument conversion.
*/
void setInvocationArg(
jsi::Runtime &runtime,
const char *methodName,
const std::string &objCArgType,
const jsi::Value &arg,
size_t i,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation) override;
private:
std::vector<MethodDescriptor> methodDescriptors_;
NSDictionary<NSString *, NSArray<NSString *> *> *methodArgumentTypeNames_;
jsi::Value constantsCache_;
std::unordered_set<std::string> warnedModuleInvocation_;
const jsi::Value &getConstants(jsi::Runtime &runtime);
bool exportsConstants();
void _logLegacyArchitectureWarning(NSString *moduleName, const std::string &methodName);
};
} // namespace facebook::react

View File

@@ -0,0 +1,699 @@
/*
* 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 "RCTInteropTurboModule.h"
#import <objc/message.h>
#import <objc/runtime.h>
#import <React/RCTAssert.h>
#import <React/RCTConvert.h>
#import <React/RCTCxxConvert.h>
#import <React/RCTManagedPointer.h>
#import <React/RCTModuleMethod.h>
#import <React/RCTParserUtils.h>
namespace facebook::react {
namespace {
// This is used for generating short exception strings.
std::string getType(jsi::Runtime &rt, const jsi::Value &v)
{
if (v.isUndefined()) {
return "undefined";
} else if (v.isNull()) {
return "null";
} else if (v.isBool()) {
return v.getBool() ? "true" : "false";
} else if (v.isNumber()) {
return "number";
} else if (v.isString()) {
return "string";
} else if (v.isSymbol()) {
return "symbol";
} else if (v.isBigInt()) {
return "bigint";
} else if (v.isObject()) {
jsi::Object vObj = v.getObject(rt);
return vObj.isFunction(rt) ? "function" : vObj.isArray(rt) ? "array" : "object";
} else {
return "unknown";
}
}
std::vector<const RCTMethodInfo *> getMethodInfos(Class moduleClass)
{
std::vector<const RCTMethodInfo *> methodInfos;
Class cls = moduleClass;
while ((cls != nullptr) && cls != [NSObject class] && cls != [NSProxy class]) {
unsigned int methodCount;
Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
IMP imp = method_getImplementation(method);
const RCTMethodInfo *methodInfo = ((const RCTMethodInfo *(*)(id, SEL))imp)(moduleClass, selector);
methodInfos.push_back(methodInfo);
}
}
free(methods);
cls = class_getSuperclass(cls);
}
return methodInfos;
}
NSString *getJSMethodName(const RCTMethodInfo *methodInfo)
{
std::string jsName = methodInfo->jsName;
if (!jsName.empty()) {
return @(jsName.c_str());
}
NSString *methodName = @(methodInfo->objcName);
NSRange colonRange = [methodName rangeOfString:@":"];
if (colonRange.location != NSNotFound) {
methodName = [methodName substringToIndex:colonRange.location];
}
methodName = [methodName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
RCTAssert(
methodName.length,
@"%s is not a valid JS function name, please"
" supply an alternative using RCT_REMAP_METHOD()",
methodInfo->objcName);
return methodName;
}
class ObjCInteropTurboModuleParseException : public std::runtime_error {
public:
ObjCInteropTurboModuleParseException(std::string moduleName, std::string methodName, std::string message)
: std::runtime_error(
"Failed to create module \"" + moduleName + "\": Error while parsing method " + moduleName + "." +
methodName + ": " + message)
{
}
};
struct ExportedMethod {
NSString *methodName;
NSArray<NSString *> *argumentTypes;
std::string returnType;
SEL selector;
};
std::vector<ExportedMethod> parseExportedMethods(std::string moduleName, Class moduleClass)
{
std::vector<const RCTMethodInfo *> methodInfos = getMethodInfos(moduleClass);
std::vector<ExportedMethod> methods;
methods.reserve(methodInfos.size());
for (const RCTMethodInfo *methodInfo : methodInfos) {
NSString *jsMethodName = getJSMethodName(methodInfo);
NSArray<RCTMethodArgument *> *arguments;
SEL objCMethodSelector = NSSelectorFromString(RCTParseMethodSignature(methodInfo->objcName, &arguments));
NSMethodSignature *objCMethodSignature = [moduleClass instanceMethodSignatureForSelector:objCMethodSelector];
if (objCMethodSignature == nullptr) {
RCTLogWarn(
@"The Objective-C `%s` method signature for the JS method `%@` can not be found in the Objective-C definition of the %s module.\nThe `%@` JS method will not be available.",
methodInfo->objcName,
jsMethodName,
moduleName.c_str(),
jsMethodName);
continue;
}
std::string objCMethodReturnType = [objCMethodSignature methodReturnType];
if (objCMethodSignature.numberOfArguments - 2 != [arguments count]) {
std::string message = "Parsed argument count (i.e: " + std::to_string([arguments count]) +
") != Objective C method signature argument count (i.e: " +
std::to_string(objCMethodSignature.numberOfArguments - 2) + ").";
throw ObjCInteropTurboModuleParseException(moduleName, [jsMethodName UTF8String], message);
}
NSMutableArray<NSString *> *argumentTypes = [NSMutableArray new];
for (NSUInteger i = 0; i < [arguments count]; i += 1) {
[argumentTypes addObject:arguments[i].type];
}
if ([argumentTypes count] == 1) {
std::string lastArgType = [argumentTypes[[argumentTypes count] - 1] UTF8String];
if (lastArgType == "RCTPromiseResolveBlock" || lastArgType == "RCTPromiseRejectBlock") {
std::string message =
"Methods that return promises must accept a RCTPromiseResolveBlock followed by a RCTPromiseRejectBlock. This method just accepts a " +
lastArgType + ".";
throw ObjCInteropTurboModuleParseException(moduleName, [jsMethodName UTF8String], message);
}
} else if ([argumentTypes count] > 1) {
std::string lastArgType = [argumentTypes[[argumentTypes count] - 1] UTF8String];
std::string secondLastArgType = [argumentTypes[[argumentTypes count] - 2] UTF8String];
if ((secondLastArgType == "RCTPromiseResolveBlock" && lastArgType != "RCTPromiseRejectBlock") ||
(secondLastArgType != "RCTPromiseResolveBlock" && lastArgType == "RCTPromiseRejectBlock")) {
std::string message =
"Methods that return promises must accept a RCTPromiseResolveBlock followed by a RCTPromiseRejectBlock. This method accepts a " +
secondLastArgType + " followed by a " + lastArgType + ".";
throw ObjCInteropTurboModuleParseException(moduleName, [jsMethodName UTF8String], message);
}
}
methods.push_back(
{.methodName = jsMethodName,
.argumentTypes = argumentTypes,
.returnType = objCMethodReturnType,
.selector = objCMethodSelector});
}
return methods;
}
SEL selectorForType(NSString *type)
{
const char *input = type.UTF8String;
return NSSelectorFromString([RCTParseType(&input) stringByAppendingString:@":"]);
}
template <typename T>
T RCTConvertTo(SEL selector, id json)
{
T (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
return convert([RCTConvert class], selector, json);
}
} // namespace
ObjCInteropTurboModule::ObjCInteropTurboModule(const ObjCTurboModule::InitParams &params)
: ObjCTurboModule(params), constantsCache_(jsi::Value::undefined())
{
std::vector<ExportedMethod> methods = parseExportedMethods(name_, [params.instance class]);
methodDescriptors_.reserve(methods.size());
NSMutableDictionary<NSString *, NSArray<NSString *> *> *methodArgTypeNames = [NSMutableDictionary new];
methodArgumentTypeNames_ = methodArgTypeNames;
for (const ExportedMethod &method : methods) {
const size_t numArgs = [method.argumentTypes count];
const bool isPromiseMethod =
numArgs >= 2 && [method.argumentTypes[numArgs - 1] isEqualToString:@"RCTPromiseRejectBlock"];
const size_t jsArgCount = isPromiseMethod ? numArgs - 2 : numArgs;
/**
* In the TurboModule system, only promises and voids are special. So, set those.
* In the else case, just assume ObjectKind. This will be ignored by the interop layer.
* In the else case, the interop layer will just call into ::convertReturnIdToJSIValue()
*/
const TurboModuleMethodValueKind returnKind = isPromiseMethod ? PromiseKind
: method.returnType == @encode(void) ? VoidKind
: ObjectKind;
methodMap_[[method.methodName UTF8String]] =
MethodMetadata{.argCount = static_cast<size_t>(jsArgCount), .invoker = nullptr};
for (NSUInteger i = 0; i < numArgs; i += 1) {
NSString *typeName = method.argumentTypes[i];
if ([typeName hasPrefix:@"JS::"]) {
NSString *rctCxxConvertSelector =
[[typeName stringByReplacingOccurrencesOfString:@"::" withString:@"_"] stringByAppendingString:@":"];
setMethodArgConversionSelector(method.methodName, i, rctCxxConvertSelector);
}
}
methodArgTypeNames[method.methodName] = method.argumentTypes;
methodDescriptors_.push_back({
.methodName = [method.methodName UTF8String],
.selector = method.selector,
.jsArgCount = jsArgCount,
.jsReturnKind = returnKind,
});
}
if ([params.instance respondsToSelector:@selector(constantsToExport)]) {
methodDescriptors_.push_back({
.methodName = "getConstants", .selector = @selector(constantsToExport), .jsArgCount = 0,
.jsReturnKind = ObjectKind,
});
} else {
static SEL getConstantsSelector = NSSelectorFromString(@"getConstants");
if ([params.instance respondsToSelector:getConstantsSelector]) {
methodDescriptors_.push_back({
.methodName = "getConstants",
.selector = getConstantsSelector,
.jsArgCount = 0,
.jsReturnKind = ObjectKind,
});
}
}
}
jsi::Value ObjCInteropTurboModule::create(jsi::Runtime &runtime, const jsi::PropNameID &propName)
{
for (size_t i = 0; i < methodDescriptors_.size(); i += 1) {
if (methodDescriptors_[i].methodName == propName.utf8(runtime)) {
if (propName.utf8(runtime) == "getConstants") {
return jsi::Function::createFromHostFunction(
runtime,
propName,
static_cast<unsigned int>(methodDescriptors_[i].jsArgCount),
[this, i](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) mutable {
if (!this->constantsCache_.isUndefined()) {
return jsi::Value(rt, this->constantsCache_);
}
const std::string &methodName = this->methodDescriptors_[i].methodName;
NSString *moduleName = [[this->instance_ class] description];
this->_logLegacyArchitectureWarning(moduleName, methodName);
// TODO: Dispatch getConstants to the main queue, if the module requires main queue setup
jsi::Value ret = this->invokeObjCMethod(
rt,
this->methodDescriptors_[i].jsReturnKind,
methodName,
this->methodDescriptors_[i].selector,
args,
count);
bool isRetValid = ret.isUndefined() || ret.isNull() ||
(ret.isObject() && !ret.asObject(rt).isFunction(rt) && !ret.asObject(rt).isArray(rt));
if (!isRetValid) {
std::string methodJsSignature = name_ + ".getConstants()";
std::string errorPrefix = methodJsSignature + ": ";
throw jsi::JSError(
rt,
errorPrefix + "Expected return value to be null, undefined, or a plain object. But, got: " +
getType(rt, ret));
}
if (ret.isUndefined() || ret.isNull()) {
this->constantsCache_ = jsi::Object(rt);
} else {
this->constantsCache_ = jsi::Value(rt, ret);
}
return ret;
});
}
return jsi::Function::createFromHostFunction(
runtime,
propName,
static_cast<unsigned int>(methodDescriptors_[i].jsArgCount),
[this, i](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
const std::string &methodName = this->methodDescriptors_[i].methodName;
NSString *moduleName = [[this->instance_ class] description];
this->_logLegacyArchitectureWarning(moduleName, methodName);
return this->invokeObjCMethod(
rt,
this->methodDescriptors_[i].jsReturnKind,
methodName,
this->methodDescriptors_[i].selector,
args,
count);
});
}
}
jsi::Object constants = getConstants(runtime).asObject(runtime);
jsi::Value constant = constants.getProperty(runtime, propName);
if (!constant.isUndefined()) {
// TODO(T145105887): Output warning. Tried to access a constant as a
// property on the native module object. Please migrate to getConstants().
}
return constant;
}
void ObjCInteropTurboModule::_logLegacyArchitectureWarning(NSString *moduleName, const std::string &methodName)
{
if (!RCTAreLegacyLogsEnabled()) {
return;
}
std::string separator = std::string(".");
std::string moduleInvocation = [moduleName cStringUsingEncoding:NSUTF8StringEncoding] + separator + methodName;
if (warnedModuleInvocation_.find(moduleInvocation) == warnedModuleInvocation_.end()) {
RCTLogWarn(
@"The `%@` module is invoking the `%s` method using the TurboModule interop layer. This is part of the compatibility layer with the Legacy Architecture. If `%@` is a local module, please migrate it to be a Native Module as described at https://reactnative.dev/docs/next/turbo-native-modules-introduction. If `%@` is a third party dependency, please open an issue in the library repository.",
moduleName,
methodName.c_str(),
moduleName,
moduleName);
warnedModuleInvocation_.insert(moduleInvocation);
}
}
void ObjCInteropTurboModule::setInvocationArg(
jsi::Runtime &runtime,
const char *methodNameCStr,
const std::string &objCArgType,
const jsi::Value &jsiArg,
size_t index,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation)
{
NSString *methodName = @(methodNameCStr);
std::string methodJsSignature = name_ + "." + methodNameCStr + "()";
NSString *argumentType = getArgumentTypeName(runtime, methodName, static_cast<int>(index));
std::string errorPrefix = methodJsSignature + ": Error while converting JavaScript argument " +
std::to_string(index) + " to Objective C type " + [argumentType UTF8String] + ". ";
SEL selector = selectorForType(argumentType);
if ([RCTConvert respondsToSelector:selector]) {
id objCArg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
if (objCArgType == @encode(char)) {
char arg = RCTConvertTo<char>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(unsigned char)) {
unsigned char arg = RCTConvertTo<unsigned char>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(short)) {
short arg = RCTConvertTo<short>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(unsigned short)) {
unsigned short arg = RCTConvertTo<unsigned short>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(int)) {
int arg = RCTConvertTo<int>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(unsigned int)) {
unsigned int arg = RCTConvertTo<unsigned int>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(long)) {
long arg = RCTConvertTo<long>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(unsigned long)) {
unsigned long arg = RCTConvertTo<unsigned long>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(long long)) {
long long arg = RCTConvertTo<long long>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(unsigned long long)) {
unsigned long long arg = RCTConvertTo<unsigned long long>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(float)) {
float arg = RCTConvertTo<float>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(double)) {
double arg = RCTConvertTo<double>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(BOOL)) {
BOOL arg = RCTConvertTo<BOOL>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(SEL)) {
SEL arg = RCTConvertTo<SEL>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(const char *)) {
const char *arg = RCTConvertTo<const char *>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(void *)) {
void *arg = RCTConvertTo<void *>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(id)) {
id arg = RCTConvertTo<id>(selector, objCArg);
// Handle the special case where there is an argument and it must be nil
// Without this check, the JS side will receive an object.
// See: discussion at
// https://github.com/facebook/react-native/pull/49250#issuecomment-2668465893
if (arg == [NSNull null]) {
return;
}
if (arg != nullptr) {
[retainedObjectsForInvocation addObject:arg];
}
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType[0] == _C_STRUCT_B) {
NSMethodSignature *typeSignature = [RCTConvert methodSignatureForSelector:selector];
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
typeInvocation.selector = selector;
typeInvocation.target = [RCTConvert class];
void *returnValue = malloc(typeSignature.methodReturnLength);
if (returnValue == nullptr) {
// CWE - 391 : Unchecked error condition
// https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html
// https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c
abort();
}
[typeInvocation setArgument:&objCArg atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:returnValue];
[inv setArgument:returnValue atIndex:index + 2];
free(returnValue);
return;
}
const char *BLOCK_TYPE = @encode(
__typeof__(^{
}));
if (objCArgType == BLOCK_TYPE) {
/**
* RCTModuleMethod doesn't actually call into RCTConvert in this case.
*/
id arg = [objCArg copy];
if (arg != nullptr) {
[retainedObjectsForInvocation addObject:arg];
}
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
throw jsi::JSError(runtime, errorPrefix + "Objective C type " + [argumentType UTF8String] + " is unsupported.");
}
if ([argumentType isEqualToString:@"RCTResponseSenderBlock"]) {
if (!(jsiArg.isObject() && jsiArg.asObject(runtime).isFunction(runtime))) {
throw jsi::JSError(
runtime, errorPrefix + "JavaScript argument must be a function. Got " + getType(runtime, jsiArg));
}
RCTResponseSenderBlock arg =
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
if (arg != nullptr) {
[retainedObjectsForInvocation addObject:arg];
}
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if ([argumentType isEqualToString:@"RCTResponseErrorBlock"]) {
if (!(jsiArg.isObject() && jsiArg.asObject(runtime).isFunction(runtime))) {
throw jsi::JSError(
runtime, errorPrefix + "JavaScript argument must be a function. Got " + getType(runtime, jsiArg));
}
RCTResponseSenderBlock senderBlock =
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
RCTResponseErrorBlock arg = ^(NSError *error) {
senderBlock(@[ RCTJSErrorFromNSError(error) ]);
};
[retainedObjectsForInvocation addObject:arg];
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if ([argumentType isEqualToString:@"RCTPromiseResolveBlock"] ||
[argumentType isEqualToString:@"RCTPromiseRejectBlock"]) {
throw jsi::JSError(
runtime,
errorPrefix + "The TurboModule interop layer should not convert JavaScript arguments to " +
[argumentType UTF8String] +
" inside ObjCinteropTurboModule::setInvocationArg(). Please report this as an issue.");
}
if ([argumentType hasPrefix:@"JS::"]) {
NSString *selectorNameForCxxType =
[[argumentType stringByReplacingOccurrencesOfString:@"::" withString:@"_"] stringByAppendingString:@":"];
selector = NSSelectorFromString(selectorNameForCxxType);
bool isPlainObject = jsiArg.isObject() && !jsiArg.asObject(runtime).isFunction(runtime) &&
!jsiArg.asObject(runtime).isArray(runtime);
if (!isPlainObject) {
throw jsi::JSError(
runtime, errorPrefix + "JavaScript argument must be a plain object. Got " + getType(runtime, jsiArg));
}
id arg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCTManagedPointer *box = convert([RCTCxxConvert class], selector, arg);
void *pointer = box.voidPointer;
[inv setArgument:&pointer atIndex:index + 2];
[retainedObjectsForInvocation addObject:box];
return;
}
}
jsi::Value ObjCInteropTurboModule::convertReturnIdToJSIValue(
jsi::Runtime &runtime,
const char *methodNameCStr,
TurboModuleMethodValueKind returnType,
id result)
{
std::string methodJsSignature = name_ + "." + methodNameCStr + "()";
std::string errorPrefix =
methodJsSignature + ": Error while converting return Objective C value to JavaScript type. ";
if (returnType == VoidKind) {
return jsi::Value::undefined();
}
if (result == (id)kCFNull || result == nil) {
return jsi::Value::null();
}
jsi::Value returnValue = TurboModuleConvertUtils::convertObjCObjectToJSIValue(runtime, result);
if (!returnValue.isUndefined()) {
return returnValue;
}
throw jsi::JSError(runtime, methodJsSignature + "Objective C type was unsupported.");
}
NSString *ObjCInteropTurboModule::getArgumentTypeName(jsi::Runtime &runtime, NSString *methodName, int argIndex)
{
const char *methodNameCStr = [methodName UTF8String];
std::string methodJsSignature = name_ + "." + methodNameCStr + "()";
std::string errorPrefix =
methodJsSignature + ": Error while trying to get Objective C type of parameter " + std::to_string(argIndex) + ".";
if (methodArgumentTypeNames_[methodName] == nil) {
throw jsi::JSError(runtime, errorPrefix + "No parameter types found for method.");
}
if ([methodArgumentTypeNames_[methodName] count] <= argIndex) {
size_t paramCount = [methodArgumentTypeNames_[methodName] count];
throw jsi::JSError(runtime, errorPrefix + "Method has only " + std::to_string(paramCount) + " parameter types.");
}
return methodArgumentTypeNames_[methodName][argIndex];
}
bool ObjCInteropTurboModule::exportsConstants()
{
for (size_t i = 0; i < methodDescriptors_.size(); i += 1) {
if (methodDescriptors_[i].methodName == "getConstants") {
return true;
}
}
return false;
}
const jsi::Value &ObjCInteropTurboModule::getConstants(jsi::Runtime &runtime)
{
if (!constantsCache_.isUndefined()) {
return constantsCache_;
}
if (!exportsConstants()) {
constantsCache_ = jsi::Object(runtime);
return constantsCache_;
}
jsi::Value getConstantsProp = get(runtime, jsi::PropNameID::forAscii(runtime, "getConstants"));
if (getConstantsProp.isObject()) {
jsi::Object getConstantsObj = getConstantsProp.asObject(runtime);
if (getConstantsObj.isFunction(runtime)) {
jsi::Function getConstantsFn = getConstantsObj.asFunction(runtime);
getConstantsFn.call(runtime);
return constantsCache_;
}
}
// Unable to invoke the getConstants() method.
// Maybe the module didn't define a getConstants() method.
// Default constants to {}, so no constants are spread into the NativeModule
constantsCache_ = jsi::Object(runtime);
return constantsCache_;
}
std::vector<facebook::jsi::PropNameID> ObjCInteropTurboModule::getPropertyNames(facebook::jsi::Runtime &runtime)
{
std::vector<facebook::jsi::PropNameID> propNames = ObjCTurboModule::getPropertyNames(runtime);
jsi::Object constants = getConstants(runtime).asObject(runtime);
jsi::Array constantNames = constants.getPropertyNames(runtime);
for (size_t i = 0; i < constantNames.size(runtime); i += 1) {
jsi::Value constantName = constantNames.getValueAtIndex(runtime, i);
if (constantName.isString()) {
propNames.push_back(jsi::PropNameID::forString(runtime, constantName.asString(runtime)));
}
}
return propNames;
}
} // namespace facebook::react

View File

@@ -0,0 +1,212 @@
/*
* 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
#import <Foundation/Foundation.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTModuleMethod.h>
#import <ReactCommon/CallInvoker.h>
#import <ReactCommon/TurboModule.h>
#import <functional>
#import <memory>
#import <string>
#import <unordered_map>
#define RCT_IS_TURBO_MODULE_CLASS(klass) \
((RCTTurboModuleEnabled() && [(klass) conformsToProtocol:@protocol(RCTTurboModule)]))
#define RCT_IS_TURBO_MODULE_INSTANCE(module) RCT_IS_TURBO_MODULE_CLASS([(module) class])
namespace facebook::react {
class CallbackWrapper;
class Instance;
using EventEmitterCallback = std::function<void(const std::string &, id)>;
namespace TurboModuleConvertUtils {
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);
id convertJSIValueToObjCObject(
jsi::Runtime &runtime,
const jsi::Value &value,
const std::shared_ptr<CallInvoker> &jsInvoker,
BOOL useNSNull = NO);
} // namespace TurboModuleConvertUtils
template <>
struct Bridging<id> {
static jsi::Value toJs(jsi::Runtime &rt, const id &value)
{
return TurboModuleConvertUtils::convertObjCObjectToJSIValue(rt, value);
}
};
/**
* ObjC++ specific TurboModule base class.
*/
class JSI_EXPORT ObjCTurboModule : public TurboModule {
public:
// TODO(T65603471): Should we unify this with a Fabric abstraction?
struct InitParams {
std::string moduleName;
id<RCTBridgeModule> instance;
std::shared_ptr<CallInvoker> jsInvoker;
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker;
bool isSyncModule;
bool shouldVoidMethodsExecuteSync;
};
ObjCTurboModule(const InitParams &params);
jsi::Value invokeObjCMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind returnType,
const std::string &methodName,
SEL selector,
const jsi::Value *args,
size_t count);
id<RCTBridgeModule> instance_;
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker_;
protected:
void setMethodArgConversionSelector(NSString *methodName, size_t argIndex, NSString *fnName);
void setEventEmitterCallback(EventEmitterCallback eventEmitterCallback);
/**
* Why is this virtual?
*
* Purpose: Converts native module method returns from Objective C values to JavaScript values.
*
* ObjCTurboModule uses TurboModuleMethodValueKind to convert returns from Objective C values to JavaScript values.
* ObjCInteropTurboModule just blindly converts returns from Objective C values to JavaScript values by runtime type,
* because it cannot infer TurboModuleMethodValueKind from the RCT_EXPORT_METHOD annotations.
*/
virtual jsi::Value convertReturnIdToJSIValue(
jsi::Runtime &runtime,
const char *methodName,
TurboModuleMethodValueKind returnType,
id result);
/**
* Why is this virtual?
*
* Purpose: Get a native module method's argument's type, given the method name, and argument index.
*
* ObjCInteropTurboModule computes the argument type names eagerly on module init. So, make this method virtual. That
* way, ObjCInteropTurboModule doesn't end up computing the argument types twice: once on module init, and second on
* method dispatch.
*/
virtual NSString *getArgumentTypeName(jsi::Runtime &runtime, NSString *methodName, int argIndex);
/**
* Why is this virtual?
*
* Purpose: Convert arguments from JavaScript values to Objective C values. Assign the Objective C argument to the
* method invocation.
*
* ObjCInteropTurboModule relies heavily on RCTConvert to convert arguments from JavaScript values to Objective C
* values. ObjCTurboModule tries to minimize reliance on RCTConvert: RCTConvert uses the RCT_EXPORT_METHOD macros,
* which we want to remove long term from React Native.
*/
virtual void setInvocationArg(
jsi::Runtime &runtime,
const char *methodName,
const std::string &objCArgType,
const jsi::Value &arg,
size_t i,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation);
private:
// Does the NativeModule dispatch async methods to the JS thread?
const bool isSyncModule_;
// Should void methods execute synchronously?
const bool shouldVoidMethodsExecuteSync_;
/**
* TODO(ramanpreet):
* Investigate an optimization that'll let us get rid of this NSMutableDictionary.
* Perhaps, have the code-generated TurboModule subclass implement
* getMethodArgConversionSelector below.
*/
NSMutableDictionary<NSString *, NSMutableArray *> *methodArgConversionSelectors_;
NSDictionary<NSString *, NSArray<NSString *> *> *methodArgumentTypeNames_;
bool isMethodSync(TurboModuleMethodValueKind returnType);
BOOL hasMethodArgConversionSelector(NSString *methodName, size_t argIndex);
SEL getMethodArgConversionSelector(NSString *methodName, size_t argIndex);
NSInvocation *createMethodInvocation(
jsi::Runtime &runtime,
bool isSync,
const char *methodName,
SEL selector,
const jsi::Value *args,
size_t count,
NSMutableArray *retainedObjectsForInvocation);
id performMethodInvocation(
jsi::Runtime &runtime,
bool isSync,
const char *methodName,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation);
void performVoidMethodInvocation(
jsi::Runtime &runtime,
const char *methodName,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation);
using PromiseInvocationBlock = void (^)(RCTPromiseResolveBlock resolveWrapper, RCTPromiseRejectBlock rejectWrapper);
jsi::Value createPromise(jsi::Runtime &runtime, const std::string &methodName, PromiseInvocationBlock invoke);
};
} // namespace facebook::react
@interface EventEmitterCallbackWrapper : NSObject {
@public
facebook::react::EventEmitterCallback _eventEmitterCallback;
}
@end
/**
* Factory object that can create a Turbomodule. It could be either a C++ TM or any TurboModule.
* This needs to be an Objective-C class so we can instantiate it at runtime.
*/
@protocol RCTModuleProvider <NSObject>
/**
* Create an instance of a TurboModule with the JS Invoker.
*/
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params;
@end
/**
* Protocol that objects can inherit to conform to be treated as turbomodules.
* It inherits from RCTTurboModuleProvider, meaning that a TurboModule can create itself
*/
@protocol RCTTurboModule <RCTModuleProvider>
@optional
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper;
@end
/**
* These methods are all implemented by RCTCxxBridge, which subclasses RCTBridge. Hence, they must only be used in
* contexts where the concrete class of an RCTBridge instance is RCTCxxBridge. This happens, for example, when
* [RCTCxxBridgeDelegate jsExecutorFactoryForBridge:(RCTBridge *)] is invoked by RCTCxxBridge.
*
* TODO: Consolidate this extension with the one in RCTSurfacePresenter.
*/
@interface RCTBridge (RCTTurboModule)
- (std::shared_ptr<facebook::react::CallInvoker>)jsCallInvoker;
- (std::shared_ptr<facebook::react::NativeMethodCallInvoker>)decorateNativeMethodCallInvoker:
(std::shared_ptr<facebook::react::NativeMethodCallInvoker>)nativeMethodCallInvoker;
@end

View File

@@ -0,0 +1,896 @@
/*
* 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 "RCTTurboModule.h"
#import <React/RCTBridgeModule.h>
#import <React/RCTConvert.h>
#import <React/RCTCxxConvert.h>
#import <React/RCTManagedPointer.h>
#import <React/RCTModuleMethod.h>
#import <React/RCTUtils.h>
#import <ReactCommon/CallInvoker.h>
#import <ReactCommon/TurboModule.h>
#import <ReactCommon/TurboModulePerfLogger.h>
#import <cxxreact/TraceSection.h>
#import <react/bridging/Bridging.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#include <glog/logging.h>
#import <objc/message.h>
#import <objc/runtime.h>
#import <atomic>
#import <iostream>
#import <mutex>
#import <sstream>
#import <vector>
using namespace facebook;
using namespace facebook::react;
using namespace facebook::react::TurboModuleConvertUtils;
static int32_t getUniqueId()
{
static int32_t counter = 0;
return counter++;
}
namespace facebook::react {
namespace TurboModuleConvertUtils {
/**
* All static helper functions are ObjC++ specific.
*/
static jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value)
{
return jsi::Value((bool)[value boolValue]);
}
static jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value)
{
return jsi::Value([value doubleValue]);
}
static jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value)
{
return jsi::String::createFromUtf8(runtime, ([value UTF8String] != nullptr) ? [value UTF8String] : "");
}
static jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value)
{
jsi::Object result = jsi::Object(runtime);
for (NSString *k in value) {
result.setProperty(runtime, convertNSStringToJSIString(runtime, k), convertObjCObjectToJSIValue(runtime, value[k]));
}
return result;
}
static jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value)
{
jsi::Array result = jsi::Array(runtime, value.count);
for (size_t i = 0; i < value.count; i++) {
result.setValueAtIndex(runtime, i, convertObjCObjectToJSIValue(runtime, value[i]));
}
return result;
}
static std::vector<jsi::Value> convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value)
{
std::vector<jsi::Value> result;
for (size_t i = 0; i < value.count; i++) {
result.emplace_back(convertObjCObjectToJSIValue(runtime, value[i]));
}
return result;
}
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value)
{
if ([value isKindOfClass:[NSString class]]) {
return convertNSStringToJSIString(runtime, (NSString *)value);
} else if ([value isKindOfClass:[NSNumber class]]) {
if ([value isKindOfClass:[@YES class]]) {
return convertNSNumberToJSIBoolean(runtime, (NSNumber *)value);
}
return convertNSNumberToJSINumber(runtime, (NSNumber *)value);
} else if ([value isKindOfClass:[NSDictionary class]]) {
return convertNSDictionaryToJSIObject(runtime, (NSDictionary *)value);
} else if ([value isKindOfClass:[NSArray class]]) {
return convertNSArrayToJSIArray(runtime, (NSArray *)value);
} else if (value == (id)kCFNull) {
return jsi::Value::null();
}
return jsi::Value::undefined();
}
static NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value)
{
return [NSString stringWithUTF8String:value.utf8(runtime).c_str()];
}
static NSArray *convertJSIArrayToNSArray(
jsi::Runtime &runtime,
const jsi::Array &value,
const std::shared_ptr<CallInvoker> &jsInvoker,
BOOL useNSNull)
{
size_t size = value.size(runtime);
NSMutableArray *result = [NSMutableArray new];
for (size_t i = 0; i < size; i++) {
// Insert kCFNull when it's `undefined` value to preserve the indices.
id convertedObject = convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker, useNSNull);
[result addObject:(convertedObject != nullptr) ? convertedObject : (id)kCFNull];
}
return result;
}
static NSDictionary *convertJSIObjectToNSDictionary(
jsi::Runtime &runtime,
const jsi::Object &value,
const std::shared_ptr<CallInvoker> &jsInvoker,
BOOL useNSNull)
{
jsi::Array propertyNames = value.getPropertyNames(runtime);
size_t size = propertyNames.size(runtime);
NSMutableDictionary *result = [NSMutableDictionary new];
for (size_t i = 0; i < size; i++) {
jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime);
NSString *k = convertJSIStringToNSString(runtime, name);
id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker, useNSNull);
if (v != nullptr) {
result[k] = v;
}
}
return result;
}
static RCTResponseSenderBlock
convertJSIFunctionToCallback(jsi::Runtime &rt, jsi::Function &&function, const std::shared_ptr<CallInvoker> &jsInvoker)
{
__block std::optional<AsyncCallback<>> callback({rt, std::move(function), jsInvoker});
return ^(NSArray *args) {
if (!callback) {
LOG(FATAL) << "Callback arg cannot be called more than once";
return;
}
callback->call([args](jsi::Runtime &rt, jsi::Function &jsFunction) {
auto jsArgs = convertNSArrayToStdVector(rt, args);
jsFunction.call(rt, (const jsi::Value *)jsArgs.data(), jsArgs.size());
});
callback = std::nullopt;
};
}
id convertJSIValueToObjCObject(
jsi::Runtime &runtime,
const jsi::Value &value,
const std::shared_ptr<CallInvoker> &jsInvoker,
BOOL useNSNull)
{
if (value.isUndefined() || (value.isNull() && !useNSNull)) {
return nil;
}
if (value.isNull() && useNSNull) {
return (id)kCFNull;
}
if (value.isBool()) {
return @(value.getBool());
}
if (value.isNumber()) {
return @(value.getNumber());
}
if (value.isString()) {
return convertJSIStringToNSString(runtime, value.getString(runtime));
}
if (value.isObject()) {
jsi::Object o = value.getObject(runtime);
if (o.isArray(runtime)) {
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker, useNSNull);
}
if (o.isFunction(runtime)) {
return convertJSIFunctionToCallback(runtime, o.getFunction(runtime), jsInvoker);
}
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker, useNSNull);
}
throw std::runtime_error("Unsupported jsi::Value kind");
}
static jsi::Value createJSRuntimeError(jsi::Runtime &runtime, const std::string &message)
{
return runtime.global().getPropertyAsFunction(runtime, "Error").call(runtime, message);
}
/**
* Creates JSError with current JS runtime and NSException stack trace.
*/
static jsi::JSError convertNSExceptionToJSError(
jsi::Runtime &runtime,
NSException *exception,
const std::string &moduleName,
const std::string &methodName)
{
std::string reason = [exception.reason UTF8String];
jsi::Object cause(runtime);
cause.setProperty(runtime, "name", [exception.name UTF8String]);
cause.setProperty(runtime, "message", reason);
cause.setProperty(runtime, "stackSymbols", convertNSArrayToJSIArray(runtime, exception.callStackSymbols));
cause.setProperty(
runtime, "stackReturnAddresses", convertNSArrayToJSIArray(runtime, exception.callStackReturnAddresses));
std::string message = moduleName + "." + methodName + " raised an exception: " + reason;
jsi::Value error = createJSRuntimeError(runtime, message);
error.asObject(runtime).setProperty(runtime, "cause", std::move(cause));
return {runtime, std::move(error)};
}
/**
* Creates JS error value with current JS runtime and error details.
*/
static jsi::Value convertJSErrorDetailsToJSRuntimeError(jsi::Runtime &runtime, NSDictionary *jsErrorDetails)
{
NSString *message = jsErrorDetails[@"message"];
auto jsError = createJSRuntimeError(runtime, [message UTF8String]);
for (NSString *key in jsErrorDetails) {
id value = jsErrorDetails[key];
jsError.asObject(runtime).setProperty(runtime, [key UTF8String], convertObjCObjectToJSIValue(runtime, value));
}
return jsError;
}
} // namespace TurboModuleConvertUtils
jsi::Value
ObjCTurboModule::createPromise(jsi::Runtime &runtime, const std::string &methodName, PromiseInvocationBlock invoke)
{
if (invoke == nullptr) {
return jsi::Value::undefined();
}
jsi::Function Promise = runtime.global().getPropertyAsFunction(runtime, "Promise");
// Note: the passed invoke() block is not retained by default, so let's retain it here to help keep it longer.
// Otherwise, there's a risk of it getting released before the promise function below executes.
PromiseInvocationBlock invokeCopy = [invoke copy];
return Promise.callAsConstructor(
runtime,
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "fn"),
2,
[invokeCopy, jsInvoker = jsInvoker_, moduleName = name_, methodName](
jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
// FIXME: do not allocate this upfront
std::string moduleMethod = moduleName + "." + methodName + "()";
if (count != 2) {
throw std::invalid_argument(
moduleMethod + ": Promise must pass constructor function two args. Passed " + std::to_string(count) +
" args.");
}
if (!invokeCopy) {
return jsi::Value::undefined();
}
__block BOOL resolveWasCalled = NO;
__block std::optional<AsyncCallback<>> resolve(
{rt, args[0].getObject(rt).getFunction(rt), std::move(jsInvoker)});
__block std::optional<AsyncCallback<>> reject(
{rt, args[1].getObject(rt).getFunction(rt), std::move(jsInvoker)});
__block std::shared_ptr<std::mutex> mutex = std::make_shared<std::mutex>();
RCTPromiseResolveBlock resolveBlock = ^(id result) {
std::optional<AsyncCallback<>> localResolve;
bool alreadyResolved = false;
bool alreadyRejected = false;
{
std::lock_guard<std::mutex> lock(*mutex);
if (!resolve || !reject) {
alreadyResolved = resolveWasCalled;
alreadyRejected = !resolveWasCalled;
} else {
resolveWasCalled = YES;
localResolve = std::move(resolve);
resolve = std::nullopt;
reject = std::nullopt;
}
}
if (alreadyResolved) {
RCTLogError(@"%s: Tried to resolve a promise more than once.", moduleMethod.c_str());
return;
}
if (alreadyRejected) {
RCTLogError(@"%s: Tried to resolve a promise after it's already been rejected.", moduleMethod.c_str());
return;
}
localResolve->call([result](jsi::Runtime &rt, jsi::Function &jsFunction) {
jsFunction.call(rt, convertObjCObjectToJSIValue(rt, result));
});
};
RCTPromiseRejectBlock rejectBlock = ^(NSString *code, NSString *message, NSError *error) {
std::optional<AsyncCallback<>> localReject;
bool alreadyResolved = false;
bool alreadyRejected = false;
{
std::lock_guard<std::mutex> lock(*mutex);
if (!resolve || !reject) {
alreadyResolved = resolveWasCalled;
alreadyRejected = !resolveWasCalled;
} else {
resolveWasCalled = NO;
localReject = std::move(reject);
reject = std::nullopt;
resolve = std::nullopt;
}
}
if (alreadyResolved) {
RCTLogError(
@"%s: Tried to reject a promise after it's already been resolved. Message: %s",
moduleMethod.c_str(),
message.UTF8String);
return;
}
if (alreadyRejected) {
RCTLogError(
@"%s: Tried to reject a promise more than once. Message: %s",
moduleMethod.c_str(),
message.UTF8String);
return;
}
NSDictionary *jsErrorDetails = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
localReject->call([jsErrorDetails](jsi::Runtime &rt, jsi::Function &jsFunction) {
jsFunction.call(rt, convertJSErrorDetailsToJSRuntimeError(rt, jsErrorDetails));
});
};
invokeCopy(resolveBlock, rejectBlock);
return jsi::Value::undefined();
}));
}
/**
* Perform method invocation on a specific queue as configured by the module class.
* This serves as a backward-compatible support for RCTBridgeModule's methodQueue API.
*
* In the future:
* - This methodQueue support may be removed for simplicity and consistency with Android.
* - ObjC module methods will be always be called from JS thread.
* They may decide to dispatch to a different queue as needed.
*/
id ObjCTurboModule::performMethodInvocation(
jsi::Runtime &runtime,
bool isSync,
const char *methodName,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation)
{
__block id result;
__weak id<RCTBridgeModule> weakModule = instance_;
const char *moduleName = name_.c_str();
std::string methodNameStr{methodName};
__block int32_t asyncCallCounter = 0;
void (^block)() = ^{
id<RCTBridgeModule> strongModule = weakModule;
if (strongModule == nullptr) {
return;
}
if (isSync) {
TurboModulePerfLogger::syncMethodCallExecutionStart(moduleName, methodName);
} else {
TurboModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodName, asyncCallCounter);
}
@try {
[inv invokeWithTarget:strongModule];
} @catch (NSException *exception) {
if (isSync) {
// We can only convert NSException to JSError in sync method calls.
// See https://github.com/reactwg/react-native-new-architecture/discussions/276#discussioncomment-12567155
throw convertNSExceptionToJSError(runtime, exception, std::string{moduleName}, methodNameStr);
} else {
@throw exception;
}
} @finally {
[retainedObjectsForInvocation removeAllObjects];
}
if (!isSync) {
TurboModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodName, asyncCallCounter);
return;
}
void *rawResult;
[inv getReturnValue:&rawResult];
result = (__bridge id)rawResult;
TurboModulePerfLogger::syncMethodCallExecutionEnd(moduleName, methodName);
};
if (isSync) {
nativeMethodCallInvoker_->invokeSync(methodNameStr, [&]() -> void { block(); });
return result;
} else {
asyncCallCounter = getUniqueId();
TurboModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName);
nativeMethodCallInvoker_->invokeAsync(methodNameStr, [block, moduleName, methodNameStr]() -> void {
TraceSection s(
"RCTTurboModuleAsyncMethodInvocation",
"module",
moduleName,
"method",
methodNameStr,
"returnType",
"promise");
block();
});
return nil;
}
}
void ObjCTurboModule::performVoidMethodInvocation(
jsi::Runtime &runtime,
const char *methodName,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation)
{
__weak id<RCTBridgeModule> weakModule = instance_;
const char *moduleName = name_.c_str();
std::string methodNameStr{methodName};
__block int32_t asyncCallCounter = 0;
void (^block)() = ^{
id<RCTBridgeModule> strongModule = weakModule;
if (strongModule == nullptr) {
return;
}
if (shouldVoidMethodsExecuteSync_) {
TurboModulePerfLogger::syncMethodCallExecutionStart(moduleName, methodName);
} else {
TurboModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodName, asyncCallCounter);
}
@try {
[inv invokeWithTarget:strongModule];
} @catch (NSException *exception) {
throw convertNSExceptionToJSError(runtime, exception, std::string{moduleName}, methodNameStr);
} @finally {
[retainedObjectsForInvocation removeAllObjects];
}
if (shouldVoidMethodsExecuteSync_) {
TurboModulePerfLogger::syncMethodCallExecutionEnd(moduleName, methodName);
} else {
TurboModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodName, asyncCallCounter);
}
return;
};
if (shouldVoidMethodsExecuteSync_) {
nativeMethodCallInvoker_->invokeSync(methodNameStr, [&]() -> void { block(); });
} else {
asyncCallCounter = getUniqueId();
TurboModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName);
nativeMethodCallInvoker_->invokeAsync(methodNameStr, [moduleName, methodNameStr, block]() -> void {
TraceSection s(
"RCTTurboModuleAsyncMethodInvocation", "module", moduleName, "method", methodNameStr, "returnType", "void");
block();
});
}
}
jsi::Value ObjCTurboModule::convertReturnIdToJSIValue(
jsi::Runtime &runtime,
const char *methodName,
TurboModuleMethodValueKind returnType,
id result)
{
if (returnType == VoidKind) {
return jsi::Value::undefined();
}
if (result == (id)kCFNull || result == nil) {
return jsi::Value::null();
}
jsi::Value returnValue = jsi::Value::undefined();
// TODO: Re-use value conversion logic from existing impl, if possible.
switch (returnType) {
case VoidKind: {
break;
}
case BooleanKind: {
returnValue = convertNSNumberToJSIBoolean(runtime, (NSNumber *)result);
break;
}
case NumberKind: {
returnValue = convertNSNumberToJSINumber(runtime, (NSNumber *)result);
break;
}
case StringKind: {
returnValue = convertNSStringToJSIString(runtime, (NSString *)result);
break;
}
case ObjectKind: {
returnValue = convertNSDictionaryToJSIObject(runtime, (NSDictionary *)result);
break;
}
case ArrayKind: {
returnValue = convertNSArrayToJSIArray(runtime, (NSArray *)result);
break;
}
case FunctionKind:
throw std::runtime_error("convertReturnIdToJSIValue: FunctionKind is not supported yet.");
case PromiseKind:
throw std::runtime_error("convertReturnIdToJSIValue: PromiseKind wasn't handled properly.");
}
return returnValue;
}
/**
* Given a method name, and an argument index, return type of that argument.
* Prerequisite: You must wrap the method declaration inside some variant of the
* RCT_EXPORT_METHOD macro.
*
* This method returns nil if the method for which you're querying the argument type
* is not wrapped in an RCT_EXPORT_METHOD.
*
* Note: This is only being introduced for backward compatibility. It will be removed
* in the future.
*/
NSString *ObjCTurboModule::getArgumentTypeName(jsi::Runtime &runtime, NSString *methodName, int argIndex)
{
if (methodArgumentTypeNames_ == nullptr) {
NSMutableDictionary<NSString *, NSArray<NSString *> *> *methodArgumentTypeNames = [NSMutableDictionary new];
unsigned int numberOfMethods;
Class cls = [instance_ class];
Method *methods = class_copyMethodList(object_getClass(cls), &numberOfMethods);
if (methods != nullptr) {
for (unsigned int i = 0; i < numberOfMethods; i++) {
SEL s = method_getName(methods[i]);
NSString *mName = NSStringFromSelector(s);
if (![mName hasPrefix:@"__rct_export__"]) {
continue;
}
// Message dispatch logic from old infra
RCTMethodInfo *(*getMethodInfo)(id, SEL) = (__typeof__(getMethodInfo))objc_msgSend;
RCTMethodInfo *methodInfo = getMethodInfo(cls, s);
NSArray<RCTMethodArgument *> *arguments;
NSString *otherMethodName = RCTParseMethodSignature(methodInfo->objcName, &arguments);
NSMutableArray *argumentTypes = [NSMutableArray arrayWithCapacity:[arguments count]];
for (int j = 0; j < [arguments count]; j += 1) {
[argumentTypes addObject:arguments[j].type];
}
NSString *normalizedOtherMethodName = [otherMethodName componentsSeparatedByString:@":"][0];
methodArgumentTypeNames[normalizedOtherMethodName] = argumentTypes;
}
free(methods);
}
methodArgumentTypeNames_ = methodArgumentTypeNames;
}
if (methodArgumentTypeNames_[methodName] != nullptr) {
assert([methodArgumentTypeNames_[methodName] count] > argIndex);
return methodArgumentTypeNames_[methodName][argIndex];
}
return nil;
}
void ObjCTurboModule::setInvocationArg(
jsi::Runtime &runtime,
const char *methodName,
const std::string &objCArgType,
const jsi::Value &arg,
size_t i,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation)
{
if (arg.isBool()) {
bool v = arg.getBool();
/**
* JS type checking ensures the Objective C argument here is either a BOOL or NSNumber*.
*/
if (objCArgType == @encode(id)) {
id objCArg = [NSNumber numberWithBool:v];
[inv setArgument:(void *)&objCArg atIndex:i + 2];
[retainedObjectsForInvocation addObject:objCArg];
} else {
[inv setArgument:(void *)&v atIndex:i + 2];
}
return;
}
if (arg.isNumber()) {
double v = arg.getNumber();
/**
* JS type checking ensures the Objective C argument here is either a double or NSNumber* or NSInteger.
*/
if (objCArgType == @encode(id)) {
id objCArg = [NSNumber numberWithDouble:v];
[inv setArgument:(void *)&objCArg atIndex:i + 2];
[retainedObjectsForInvocation addObject:objCArg];
} else if (objCArgType == @encode(NSInteger)) {
NSInteger integer = v;
[inv setArgument:&integer atIndex:i + 2];
} else {
[inv setArgument:(void *)&v atIndex:i + 2];
}
return;
}
/**
* Convert arg to ObjC objects.
*/
BOOL enableModuleArgumentNSNullConversionIOS = ReactNativeFeatureFlags::enableModuleArgumentNSNullConversionIOS();
id objCArg = convertJSIValueToObjCObject(runtime, arg, jsInvoker_, enableModuleArgumentNSNullConversionIOS);
if (objCArg != nullptr) {
NSString *methodNameNSString = @(methodName);
/**
* Convert objects using RCTConvert.
*/
if (objCArgType == @encode(id)) {
NSString *argumentType = getArgumentTypeName(runtime, methodNameNSString, static_cast<int>(i));
if (argumentType != nil) {
NSString *rctConvertMethodName = [NSString stringWithFormat:@"%@:", argumentType];
SEL rctConvertSelector = NSSelectorFromString(rctConvertMethodName);
if ([RCTConvert respondsToSelector:rctConvertSelector]) {
// Message dispatch logic from old infra
id (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
id convertedObjCArg = convert([RCTConvert class], rctConvertSelector, objCArg);
if (enableModuleArgumentNSNullConversionIOS && convertedObjCArg == (id)kCFNull) {
return;
}
[inv setArgument:(void *)&convertedObjCArg atIndex:i + 2];
if (convertedObjCArg != nullptr) {
[retainedObjectsForInvocation addObject:convertedObjCArg];
}
return;
}
}
}
/**
* Convert objects using RCTCxxConvert to structs.
*/
if ([objCArg isKindOfClass:[NSDictionary class]] && hasMethodArgConversionSelector(methodNameNSString, i)) {
SEL methodArgConversionSelector = getMethodArgConversionSelector(methodNameNSString, i);
// Message dispatch logic from old infra (link:
// https://github.com/facebook/react-native/commit/6783694158057662fd7b11fc123c339b2b21bfe6#diff-263fc157dfce55895cdc16495b55d190R350)
RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCTManagedPointer *box = convert([RCTCxxConvert class], methodArgConversionSelector, objCArg);
void *pointer = box.voidPointer;
[inv setArgument:&pointer atIndex:i + 2];
[retainedObjectsForInvocation addObject:box];
return;
}
}
/**
* Insert converted args unmodified.
*/
[inv setArgument:(void *)&objCArg atIndex:i + 2];
if (objCArg != nullptr) {
[retainedObjectsForInvocation addObject:objCArg];
}
}
NSInvocation *ObjCTurboModule::createMethodInvocation(
jsi::Runtime &runtime,
bool isSync,
const char *methodName,
SEL selector,
const jsi::Value *args,
size_t count,
NSMutableArray *retainedObjectsForInvocation)
{
const char *moduleName = name_.c_str();
const NSObject<RCTBridgeModule> *module = instance_;
if (isSync) {
TurboModulePerfLogger::syncMethodCallArgConversionStart(moduleName, methodName);
} else {
TurboModulePerfLogger::asyncMethodCallArgConversionStart(moduleName, methodName);
}
NSMethodSignature *methodSignature = [module methodSignatureForSelector:selector];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:methodSignature];
[inv setSelector:selector];
for (size_t i = 0; i < count; i++) {
const jsi::Value &arg = args[i];
const std::string objCArgType = [methodSignature getArgumentTypeAtIndex:i + 2];
setInvocationArg(runtime, methodName, objCArgType, arg, i, inv, retainedObjectsForInvocation);
}
if (isSync) {
TurboModulePerfLogger::syncMethodCallArgConversionEnd(moduleName, methodName);
} else {
TurboModulePerfLogger::asyncMethodCallArgConversionEnd(moduleName, methodName);
}
return inv;
}
bool ObjCTurboModule::isMethodSync(TurboModuleMethodValueKind returnType)
{
if (isSyncModule_) {
return true;
}
if (returnType == VoidKind && shouldVoidMethodsExecuteSync_) {
return true;
}
return !(returnType == VoidKind || returnType == PromiseKind);
}
ObjCTurboModule::ObjCTurboModule(const InitParams &params)
: TurboModule(params.moduleName, params.jsInvoker),
instance_(params.instance),
nativeMethodCallInvoker_(params.nativeMethodCallInvoker),
isSyncModule_(params.isSyncModule),
shouldVoidMethodsExecuteSync_(params.shouldVoidMethodsExecuteSync)
{
}
jsi::Value ObjCTurboModule::invokeObjCMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind returnType,
const std::string &methodNameStr,
SEL selector,
const jsi::Value *args,
size_t count)
{
const char *moduleName = name_.c_str();
const char *methodName = methodNameStr.c_str();
bool isSyncInvocation = isMethodSync(returnType);
if (isSyncInvocation) {
TurboModulePerfLogger::syncMethodCallStart(moduleName, methodName);
} else {
TurboModulePerfLogger::asyncMethodCallStart(moduleName, methodName);
}
NSMutableArray *retainedObjectsForInvocation = [NSMutableArray arrayWithCapacity:count + 2];
NSInvocation *inv = createMethodInvocation(
runtime, isSyncInvocation, methodName, selector, args, count, retainedObjectsForInvocation);
jsi::Value returnValue = jsi::Value::undefined();
switch (returnType) {
case PromiseKind: {
returnValue = createPromise(
runtime, methodNameStr, ^(RCTPromiseResolveBlock resolveBlock, RCTPromiseRejectBlock rejectBlock) {
RCTPromiseResolveBlock resolveCopy = [resolveBlock copy];
RCTPromiseRejectBlock rejectCopy = [rejectBlock copy];
[inv setArgument:(void *)&resolveCopy atIndex:count + 2];
[inv setArgument:(void *)&rejectCopy atIndex:count + 3];
[retainedObjectsForInvocation addObject:resolveCopy];
[retainedObjectsForInvocation addObject:rejectCopy];
// The return type becomes void in the ObjC side.
performMethodInvocation(runtime, isSyncInvocation, methodName, inv, retainedObjectsForInvocation);
});
break;
}
case VoidKind: {
performVoidMethodInvocation(runtime, methodName, inv, retainedObjectsForInvocation);
if (isSyncInvocation) {
TurboModulePerfLogger::syncMethodCallReturnConversionStart(moduleName, methodName);
}
returnValue = jsi::Value::undefined();
if (isSyncInvocation) {
TurboModulePerfLogger::syncMethodCallReturnConversionEnd(moduleName, methodName);
}
break;
}
case BooleanKind:
case NumberKind:
case StringKind:
case ObjectKind:
case ArrayKind:
case FunctionKind: {
id result = performMethodInvocation(runtime, true, methodName, inv, retainedObjectsForInvocation);
TurboModulePerfLogger::syncMethodCallReturnConversionStart(moduleName, methodName);
returnValue = convertReturnIdToJSIValue(runtime, methodName, returnType, result);
TurboModulePerfLogger::syncMethodCallReturnConversionEnd(moduleName, methodName);
} break;
}
if (isSyncInvocation) {
TurboModulePerfLogger::syncMethodCallEnd(moduleName, methodName);
} else {
TurboModulePerfLogger::asyncMethodCallEnd(moduleName, methodName);
}
return returnValue;
}
BOOL ObjCTurboModule::hasMethodArgConversionSelector(NSString *methodName, size_t argIndex)
{
return (methodArgConversionSelectors_ != nullptr) && (methodArgConversionSelectors_[methodName] != nullptr) &&
![methodArgConversionSelectors_[methodName][argIndex] isEqual:(id)kCFNull];
}
SEL ObjCTurboModule::getMethodArgConversionSelector(NSString *methodName, size_t argIndex)
{
assert(hasMethodArgConversionSelector(methodName, argIndex));
return (SEL)((NSValue *)methodArgConversionSelectors_[methodName][argIndex]).pointerValue;
}
void ObjCTurboModule::setMethodArgConversionSelector(NSString *methodName, size_t argIndex, NSString *fnName)
{
if (methodArgConversionSelectors_ == nullptr) {
methodArgConversionSelectors_ = [NSMutableDictionary new];
}
if (methodArgConversionSelectors_[methodName] == nullptr) {
auto metaData = methodMap_.at([methodName UTF8String]);
auto argCount = metaData.argCount;
methodArgConversionSelectors_[methodName] = [NSMutableArray arrayWithCapacity:argCount];
for (int i = 0; i < argCount; i += 1) {
[methodArgConversionSelectors_[methodName] addObject:(id)kCFNull];
}
}
SEL selector = NSSelectorFromString(fnName);
NSValue *selectorValue = [NSValue valueWithPointer:selector];
methodArgConversionSelectors_[methodName][argIndex] = selectorValue;
}
void ObjCTurboModule::setEventEmitterCallback(EventEmitterCallback eventEmitterCallback)
{
if ([instance_ conformsToProtocol:@protocol(RCTTurboModule)] &&
[instance_ respondsToSelector:@selector(setEventEmitterCallback:)]) {
EventEmitterCallbackWrapper *wrapper = [EventEmitterCallbackWrapper new];
wrapper->_eventEmitterCallback = std::move(eventEmitterCallback);
[(id<RCTTurboModule>)instance_ setEventEmitterCallback:wrapper];
}
}
} // namespace facebook::react
@implementation EventEmitterCallbackWrapper
@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.
*/
#pragma once
#import <memory>
#import <React/RCTBridgeModuleDecorator.h>
#import <React/RCTDefines.h>
#import <React/RCTTurboModuleRegistry.h>
#import <ReactCommon/RuntimeExecutor.h>
#import <ReactCommon/TurboModuleBinding.h>
#import "RCTTurboModule.h"
@class RCTBridgeProxy;
@class RCTTurboModuleManager;
@class RCTDevMenuConfigurationDecorator;
@protocol RCTTurboModuleManagerDelegate <NSObject>
/**
* Given a module name, return its actual class. If nil is returned, basic ObjC class lookup is performed.
*/
- (Class)getModuleClassFromName:(const char *)name;
/**
* Given a module class, provide an instance for it. If nil is returned, default initializer is used.
*/
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass;
@optional
/**
* This method is used to retrieve a factory object that can create a `facebook::react::TurboModule`,
* The class implementing `RCTTurboModuleProvider` must be an Objective-C class so that we can
* initialize it dynamically with Codegen.
*/
- (id<RCTModuleProvider>)getModuleProvider:(const char *)name;
/**
* Create an instance of a TurboModule without relying on any ObjC++ module instance.
*/
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
jsInvoker:
(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker;
/**
* Return a pre-initialized list of leagcy native modules.
* These modules shouldn't be TurboModule-compatible (i.e: they should not conform to RCTTurboModule).
*
* This method is only used by the TurboModule interop layer.
*
* It must match the signature of RCTBridgeDelegate extraModulesForBridge:
* - (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge;
*/
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
__attribute((deprecated("Please make all native modules returned from this method TurboModule-compatible.")));
@end
@interface RCTTurboModuleManager : NSObject <RCTTurboModuleRegistry>
- (instancetype)initWithBridge:(RCTBridge *)bridge
delegate:(id<RCTTurboModuleManagerDelegate>)delegate
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker;
- (instancetype)initWithBridgeProxy:(RCTBridgeProxy *)bridgeProxy
bridgeModuleDecorator:(RCTBridgeModuleDecorator *)bridgeModuleDecorator
delegate:(id<RCTTurboModuleManagerDelegate>)delegate
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker;
- (instancetype)initWithBridgeProxy:(RCTBridgeProxy *)bridgeProxy
bridgeModuleDecorator:(RCTBridgeModuleDecorator *)bridgeModuleDecorator
delegate:(id<RCTTurboModuleManagerDelegate>)delegate
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
devMenuConfigurationDecorator:(RCTDevMenuConfigurationDecorator *)devMenuConfigurationDecorator;
- (void)installJSBindings:(facebook::jsi::Runtime &)runtime;
- (void)invalidate;
@end

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.
*/
#import <Foundation/Foundation.h>
#ifdef __cplusplus
#include <ReactCommon/CallInvoker.h>
#include <jsi/jsi.h>
#endif
@protocol RCTTurboModuleWithJSIBindings <NSObject>
#ifdef __cplusplus
@optional
- (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime
callInvoker:(const std::shared_ptr<facebook::react::CallInvoker> &)callinvoker;
- (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime
__attribute__((deprecated("Use 'installJSIBindingsWithRuntime:callInvoker:' instead")));
#endif
@end

View File

@@ -0,0 +1,155 @@
/*
* 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 <ReactCommon/TestCallInvoker.h>
#include <ReactCommon/TurboModule.h>
#include <gtest/gtest.h>
#include <hermes/hermes.h>
#include <react/bridging/Bridging.h>
#include <react/debug/react_native_assert.h>
#include <memory>
#include <optional>
namespace facebook::react {
class TurboModuleTestFixtureInternal {
public:
static bool containsEventEmitter(TurboModule &turboModule, const std::string &eventEmitterName)
{
return turboModule.eventEmitterMap_.contains(eventEmitterName);
}
static const std::shared_ptr<IAsyncEventEmitter> getEventEmitter(
TurboModule &turboModule,
const std::string &eventEmitterName)
{
return turboModule.eventEmitterMap_.at(eventEmitterName);
}
};
template <typename T, typename... Args>
class TurboModuleTestFixture : public TurboModuleTestFixtureInternal, public ::testing::Test {
static_assert(std::is_base_of<TurboModule, T>::value, "T must be derived from TurboModule");
public:
explicit TurboModuleTestFixture(Args... args)
: runtime_(hermes::makeHermesRuntime()),
jsInvoker_(std::make_shared<TestCallInvoker>(*runtime_)),
module_(std::make_shared<T>(jsInvoker_, std::forward<Args>(args)...))
{
}
void SetUp() override
{
auto setImmediateName = jsi::PropNameID::forAscii(*runtime_, "setImmediate");
runtime_->global().setProperty(
*runtime_,
setImmediateName,
jsi::Function::createFromHostFunction(
*runtime_,
setImmediateName,
1,
[jsInvoker = jsInvoker_](
jsi::Runtime &rt, [[maybe_unused]] const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
react_native_assert(count >= 1);
jsInvoker->invokeAsync([cb = std::make_shared<jsi::Value>(rt, args[0])](jsi::Runtime &rt) {
cb->asObject(rt).asFunction(rt).call(rt);
});
return jsi::Value::undefined();
}));
}
template <typename... EventType, typename Listener>
EventSubscription addEventEmitterListener(jsi::Runtime &rt, const std::string &eventEmitterName, Listener &&listener)
{
EXPECT_TRUE(containsEventEmitter(*module_, eventEmitterName));
auto listenJs = bridging::toJs(
rt,
[listener = std::forward<Listener>(listener)](const EventType &...event) { listener(event...); },
jsInvoker_);
std::shared_ptr<AsyncEventEmitter<EventType...>> eventEmitter =
std::static_pointer_cast<AsyncEventEmitter<EventType...>>(getEventEmitter(*module_, eventEmitterName));
jsi::Object eventEmitterJs = bridging::toJs(rt, *eventEmitter, jsInvoker_);
auto eventSubscriptionJs =
jsi::Object(eventEmitterJs.asFunction(rt).callWithThis(rt, eventEmitterJs, listenJs).asObject(rt));
return bridging::fromJs<EventSubscription>(rt, eventSubscriptionJs, jsInvoker_);
}
void TearDown() override
{
module_ = nullptr;
jsInvoker_ = nullptr;
runtime_ = nullptr;
}
template <typename TPromise>
std::optional<TPromise> resolvePromise(const AsyncPromise<TPromise> &asyncPromise)
{
auto promise = asyncPromise.get(*runtime_);
std::optional<TPromise> result = std::nullopt;
promise.getPropertyAsFunction(*runtime_, "then")
.callWithThis(
*runtime_,
promise,
bridging::toJs(*runtime_, [&](TPromise value) { result = std::move(value); }, jsInvoker_));
jsInvoker_->flushQueue();
return result;
}
template <typename TPromise>
std::optional<std::string> handleError(const AsyncPromise<TPromise> &asyncPromise)
{
auto promise = asyncPromise.get(*runtime_);
std::optional<std::string> message;
promise.getPropertyAsFunction(*runtime_, "catch")
.callWithThis(
*runtime_,
promise,
bridging::toJs(
*runtime_,
[&](jsi::Object error) {
message =
bridging::fromJs<std::string>(*runtime_, error.getProperty(*runtime_, "message"), jsInvoker_);
},
jsInvoker_));
jsInvoker_->flushQueue();
return message;
}
template <typename TEvent>
AsyncCallback<TEvent> makeAsyncCallback(std::function<void(TEvent)> &&callback)
{
auto func = jsi::Function::createFromHostFunction(
*runtime_,
jsi::PropNameID::forAscii(*runtime_, "callback"),
1,
[jsInvoker = jsInvoker_, callback = std::move(callback)](
jsi::Runtime &rt, [[maybe_unused]] const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
if (count < 1) {
throw jsi::JSINativeException("callback: Missing argument");
}
callback(Bridging<TEvent>::fromJs(rt, args[0].asObject(rt), jsInvoker));
return jsi::Value::undefined();
});
return {*runtime_, std::move(func), jsInvoker_};
}
void expectAndFlushQueue(size_t queueSize)
{
EXPECT_EQ(jsInvoker_->queueSize(), queueSize);
jsInvoker_->flushQueue();
}
protected:
std::shared_ptr<jsi::Runtime> runtime_{};
std::shared_ptr<TestCallInvoker> jsInvoker_{};
std::shared_ptr<T> module_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,20 @@
# 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_nativemodule_cpu_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_cpu OBJECT ${react_nativemodule_cpu_SRC})
target_include_directories(react_nativemodule_cpu PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_nativemodule_cpu
react_codegen_rncore
)
target_compile_reactnative_options(react_nativemodule_cpu PRIVATE)
target_compile_options(react_nativemodule_cpu PRIVATE -Wpedantic)

View File

@@ -0,0 +1,86 @@
/*
* 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
#if defined USE_POSIX_TIME
#include <time.h>
#elif defined __MACH__
#include <mach/mach_time.h>
#else
#include <chrono>
#endif
#ifdef USE_POSIX_TIME
namespace {
const double NANOSECONDS_IN_A_SECOND = 1000000000;
} // namespace
#endif
#ifdef __MACH__
namespace {
inline double getConversionFactor()
{
double conversionFactor;
mach_timebase_info_data_t info;
mach_timebase_info(&info);
conversionFactor = static_cast<double>(info.numer) / info.denom;
return conversionFactor;
}
} // namespace
#endif
namespace facebook::react {
#if defined USE_POSIX_TIME
inline double getCPUTimeNanos()
{
struct timespec time{};
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time);
return static_cast<double>(time.tv_sec) * NANOSECONDS_IN_A_SECOND + static_cast<double>(time.tv_nsec);
}
inline bool hasAccurateCPUTimeNanosForBenchmarks()
{
return true;
}
#elif defined __MACH__
inline double getCPUTimeNanos()
{
static auto conversionFactor = getConversionFactor();
uint64_t time = mach_absolute_time();
return static_cast<double>(time) * conversionFactor;
}
inline bool hasAccurateCPUTimeNanosForBenchmarks()
{
return true;
}
#else
inline double getCPUTimeNanos()
{
auto now = std::chrono::steady_clock::now();
return static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count());
}
inline bool hasAccurateCPUTimeNanosForBenchmarks()
{
return false;
}
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,35 @@
/*
* 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 "NativeCPUTime.h"
#include "CPUTime.h"
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
#include "Plugins.h"
#endif
std::shared_ptr<facebook::react::TurboModule> NativeCPUTimeModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::NativeCPUTime>(std::move(jsInvoker));
}
namespace facebook::react {
NativeCPUTime::NativeCPUTime(std::shared_ptr<CallInvoker> jsInvoker)
: NativeCPUTimeCxxSpec(std::move(jsInvoker)) {}
double NativeCPUTime::getCPUTimeNanos(jsi::Runtime& /*runtime*/) {
return facebook::react::getCPUTimeNanos();
}
bool NativeCPUTime::hasAccurateCPUTimeNanosForBenchmarks(
jsi::Runtime& /*runtime*/) {
return facebook::react::hasAccurateCPUTimeNanosForBenchmarks();
}
} // 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
#if __has_include("rncoreJSI.h") // Cmake headers on Android
#include "rncoreJSI.h"
#elif __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
#include "FBReactNativeSpecJSI.h"
#else
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#endif
namespace facebook::react {
class NativeCPUTime : public NativeCPUTimeCxxSpec<NativeCPUTime> {
public:
explicit NativeCPUTime(std::shared_ptr<CallInvoker> jsInvoker);
double getCPUTimeNanos(jsi::Runtime &runtime);
bool hasAccurateCPUTimeNanosForBenchmarks(jsi::Runtime &runtime);
};
} // 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_nativemodule_defaults_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_defaults OBJECT ${react_nativemodule_defaults_SRC})
target_include_directories(react_nativemodule_defaults PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_nativemodule_defaults
react_codegen_rncore
react_nativemodule_core
react_nativemodule_dom
react_nativemodule_devtoolsruntimesettings
react_nativemodule_featureflags
react_nativemodule_microtasks
react_nativemodule_idlecallbacks
react_nativemodule_intersectionobserver
react_nativemodule_webperformance
)
target_compile_reactnative_options(react_nativemodule_defaults PRIVATE)
target_compile_options(react_nativemodule_defaults PRIVATE -Wpedantic)

View File

@@ -0,0 +1,63 @@
/*
* 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 "DefaultTurboModules.h"
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/nativemodule/dom/NativeDOM.h>
#include <react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h>
#include <react/nativemodule/idlecallbacks/NativeIdleCallbacks.h>
#include <react/nativemodule/intersectionobserver/NativeIntersectionObserver.h>
#include <react/nativemodule/microtasks/NativeMicrotasks.h>
#include <react/nativemodule/webperformance/NativePerformance.h>
#ifdef REACT_NATIVE_DEBUGGER_ENABLED_DEVONLY
#include <react/nativemodule/devtoolsruntimesettings/DevToolsRuntimeSettingsModule.h>
#endif
namespace facebook::react {
/* static */ std::shared_ptr<TurboModule> DefaultTurboModules::getTurboModule(
const std::string& name,
const std::shared_ptr<CallInvoker>& jsInvoker) {
if (name == NativeReactNativeFeatureFlags::kModuleName) {
return std::make_shared<NativeReactNativeFeatureFlags>(jsInvoker);
}
if (name == NativeMicrotasks::kModuleName) {
return std::make_shared<NativeMicrotasks>(jsInvoker);
}
if (name == NativeIdleCallbacks::kModuleName) {
return std::make_shared<NativeIdleCallbacks>(jsInvoker);
}
if (name == NativeDOM::kModuleName) {
return std::make_shared<NativeDOM>(jsInvoker);
}
if (ReactNativeFeatureFlags::enableWebPerformanceAPIsByDefault()) {
if (name == NativePerformance::kModuleName) {
return std::make_shared<NativePerformance>(jsInvoker);
}
}
if (ReactNativeFeatureFlags::enableIntersectionObserverByDefault()) {
if (name == NativeIntersectionObserver::kModuleName) {
return std::make_shared<NativeIntersectionObserver>(jsInvoker);
}
}
#ifdef REACT_NATIVE_DEBUGGER_ENABLED_DEVONLY
if (name == DevToolsRuntimeSettingsModule::kModuleName) {
return std::make_shared<DevToolsRuntimeSettingsModule>(jsInvoker);
}
#endif
return nullptr;
}
} // namespace facebook::react

View File

@@ -0,0 +1,18 @@
/*
* 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 <ReactCommon/TurboModule.h>
namespace facebook::react {
struct DefaultTurboModules {
static std::shared_ptr<TurboModule> getTurboModule(
const std::string &name,
const std::shared_ptr<CallInvoker> &jsInvoker);
};
} // namespace facebook::react

View File

@@ -0,0 +1,60 @@
# 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
header_search_paths = [
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
]
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the defaultsnativemodule to access its own files
end
Pod::Spec.new do |s|
s.name = "React-defaultsnativemodule"
s.version = version
s.summary = "React Native Default native modules"
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.header_dir = "react/nativemodule/defaults"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"OTHER_CFLAGS" => "$(inherited)",
"DEFINES_MODULE" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_defaultsnativemodule")
s.dependency "Yoga"
s.dependency "React-jsi"
s.dependency "React-jsiexecutor"
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.dependency "React-domnativemodule"
s.dependency "React-microtasksnativemodule"
s.dependency "React-idlecallbacksnativemodule"
s.dependency "React-intersectionobservernativemodule"
s.dependency "React-webperformancenativemodule"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-featureflags")
add_dependency(s, "React-featureflagsnativemodule")
end

View File

@@ -0,0 +1,20 @@
# 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_nativemodule_devtoolsruntimesettings_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_devtoolsruntimesettings OBJECT ${react_nativemodule_devtoolsruntimesettings_SRC})
target_include_directories(react_nativemodule_devtoolsruntimesettings PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_nativemodule_devtoolsruntimesettings
devtoolsruntimesettings
)
target_compile_reactnative_options(react_nativemodule_devtoolsruntimesettings PRIVATE)
target_compile_options(react_nativemodule_devtoolsruntimesettings PRIVATE -Wpedantic)

View File

@@ -0,0 +1,37 @@
/*
* 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 "DevToolsRuntimeSettingsModule.h"
#if RN_DISABLE_OSS_PLUGIN_HEADER
#include "Plugins.h"
#endif
std::shared_ptr<facebook::react::TurboModule>
ReactDevToolsRuntimeSettingsModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::DevToolsRuntimeSettingsModule>(
std::move(jsInvoker));
}
namespace facebook::react {
DevToolsRuntimeSettingsModule::DevToolsRuntimeSettingsModule(
std::shared_ptr<CallInvoker> jsInvoker)
: NativeReactDevToolsRuntimeSettingsModuleCxxSpec(std::move(jsInvoker)) {}
void DevToolsRuntimeSettingsModule::setReloadAndProfileConfig(
jsi::Runtime& /*rt*/,
NativePartialReloadAndProfileConfig config) {
DevToolsRuntimeSettings::getInstance().setReloadAndProfileConfig(config);
};
NativeReloadAndProfileConfig
DevToolsRuntimeSettingsModule::getReloadAndProfileConfig(jsi::Runtime& /*rt*/) {
return DevToolsRuntimeSettings::getInstance().getReloadAndProfileConfig();
};
} // namespace facebook::react

View File

@@ -0,0 +1,24 @@
/*
* 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 <devtoolsruntimesettings/DevToolsRuntimeSettings.h>
namespace facebook::react {
class DevToolsRuntimeSettingsModule
: public NativeReactDevToolsRuntimeSettingsModuleCxxSpec<DevToolsRuntimeSettingsModule> {
public:
DevToolsRuntimeSettingsModule(std::shared_ptr<CallInvoker> jsInvoker);
void setReloadAndProfileConfig(jsi::Runtime &rt, NativePartialReloadAndProfileConfig config);
NativeReloadAndProfileConfig getReloadAndProfileConfig(jsi::Runtime &rt);
};
} // namespace facebook::react

View File

@@ -0,0 +1,6 @@
# Options for DevTools Settings
| Module | Survives native restarts | Survives JavaScript VM restarts |
| --- | --- | --- |
| DevToolsRuntimeSettings | No | Yes
| DevToolsSettings | Yes | Yes

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_nativemodule_dom_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_dom
OBJECT
${react_nativemodule_dom_SRC}
$<TARGET_OBJECTS:react_codegen_rncore>
)
target_include_directories(react_nativemodule_dom PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_nativemodule_dom
rrc_root
react_codegen_rncore
react_cxxreact
react_renderer_bridging
react_renderer_dom
react_renderer_uimanager
)
target_compile_reactnative_options(react_nativemodule_dom PRIVATE)
target_compile_options(react_nativemodule_dom PRIVATE -Wpedantic)

View File

@@ -0,0 +1,479 @@
/*
* 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 "NativeDOM.h"
#include <react/renderer/bridging/bridging.h>
#include <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/dom/DOM.h>
#include <react/renderer/uimanager/PointerEventsProcessor.h>
#include <react/renderer/uimanager/UIManagerBinding.h>
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
#include "Plugins.h"
#endif
std::shared_ptr<facebook::react::TurboModule> NativeDOMModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::NativeDOM>(std::move(jsInvoker));
}
namespace facebook::react {
namespace {
inline std::shared_ptr<const ShadowNode> getShadowNode(
facebook::jsi::Runtime& runtime,
jsi::Value& shadowNodeValue) {
return Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
runtime, shadowNodeValue);
}
#pragma mark - Private helpers
UIManager& getUIManagerFromRuntime(facebook::jsi::Runtime& runtime) {
return UIManagerBinding::getBinding(runtime)->getUIManager();
}
RootShadowNode::Shared getCurrentShadowTreeRevision(
facebook::jsi::Runtime& runtime,
SurfaceId surfaceId) {
auto shadowTreeRevisionProvider =
getUIManagerFromRuntime(runtime).getShadowTreeRevisionProvider();
return shadowTreeRevisionProvider->getCurrentRevision(surfaceId);
}
RootShadowNode::Shared getCurrentShadowTreeRevision(
facebook::jsi::Runtime& runtime,
jsi::Value& nativeNodeReference) {
if (nativeNodeReference.isNumber()) {
return getCurrentShadowTreeRevision(
runtime, static_cast<SurfaceId>(nativeNodeReference.asNumber()));
}
return getCurrentShadowTreeRevision(
runtime, getShadowNode(runtime, nativeNodeReference)->getSurfaceId());
}
facebook::react::PointerEventsProcessor& getPointerEventsProcessorFromRuntime(
facebook::jsi::Runtime& runtime) {
return facebook::react::UIManagerBinding::getBinding(runtime)
->getPointerEventsProcessor();
}
std::vector<facebook::jsi::Value> getArrayOfInstanceHandlesFromShadowNodes(
const std::vector<std::shared_ptr<const ShadowNode>>& nodes,
facebook::jsi::Runtime& runtime) {
// JSI doesn't support adding elements to an array after creation,
// so we need to accumulate the values in a vector and then create
// the array when we know the size.
std::vector<facebook::jsi::Value> nonNullInstanceHandles;
nonNullInstanceHandles.reserve(nodes.size());
for (const auto& shadowNode : nodes) {
auto instanceHandle = (*shadowNode).getInstanceHandle(runtime);
if (!instanceHandle.isNull()) {
nonNullInstanceHandles.push_back(std::move(instanceHandle));
}
}
return nonNullInstanceHandles;
}
bool isRootShadowNode(const ShadowNode& shadowNode) {
return shadowNode.getTraits().check(ShadowNodeTraits::Trait::RootNodeKind);
}
} // namespace
#pragma mark - NativeDOM
NativeDOM::NativeDOM(std::shared_ptr<CallInvoker> jsInvoker)
: NativeDOMCxxSpec(std::move(jsInvoker)) {}
#pragma mark - Methods from the `Node` interface (for `ReadOnlyNode`).
double NativeDOM::compareDocumentPosition(
jsi::Runtime& rt,
jsi::Value nativeNodeReference,
jsi::Value otherNativeNodeReference) {
auto currentRevision = getCurrentShadowTreeRevision(rt, nativeNodeReference);
if (currentRevision == nullptr) {
return dom::DOCUMENT_POSITION_DISCONNECTED;
}
std::shared_ptr<const ShadowNode> shadowNode;
std::shared_ptr<const ShadowNode> otherShadowNode;
// Check if document references are used
if (nativeNodeReference.isNumber() || otherNativeNodeReference.isNumber()) {
if (nativeNodeReference.isNumber() && otherNativeNodeReference.isNumber()) {
// Both are documents (and equality is handled in JS directly).
return dom::DOCUMENT_POSITION_DISCONNECTED;
} else if (nativeNodeReference.isNumber()) {
// Only the first is a document
auto surfaceId = nativeNodeReference.asNumber();
shadowNode = currentRevision;
otherShadowNode = getShadowNode(rt, otherNativeNodeReference);
if (isRootShadowNode(*otherShadowNode)) {
// If the other is a root node, we just need to check if it is its
// `documentElement`
return (surfaceId == otherShadowNode->getSurfaceId())
? dom::DOCUMENT_POSITION_CONTAINED_BY |
dom::DOCUMENT_POSITION_FOLLOWING
: dom::DOCUMENT_POSITION_DISCONNECTED;
} else {
// Otherwise, we'll use the root node to represent the document
// (the result should be the same)
}
} else {
// Only the second is a document
auto otherSurfaceId = otherNativeNodeReference.asNumber();
shadowNode = getShadowNode(rt, nativeNodeReference);
otherShadowNode = getCurrentShadowTreeRevision(rt, otherSurfaceId);
if (isRootShadowNode(*shadowNode)) {
// If this is a root node, we just need to check if the other is its
// document.
return (otherSurfaceId == shadowNode->getSurfaceId())
? dom::DOCUMENT_POSITION_CONTAINS | dom::DOCUMENT_POSITION_PRECEDING
: dom::DOCUMENT_POSITION_DISCONNECTED;
} else {
// Otherwise, we'll use the root node to represent the document
// (the result should be the same)
}
}
} else {
shadowNode = getShadowNode(rt, nativeNodeReference);
otherShadowNode = getShadowNode(rt, otherNativeNodeReference);
}
return dom::compareDocumentPosition(
currentRevision, *shadowNode, *otherShadowNode);
}
std::vector<jsi::Value> NativeDOM::getChildNodes(
jsi::Runtime& rt,
jsi::Value nativeNodeReference) {
auto currentRevision = getCurrentShadowTreeRevision(rt, nativeNodeReference);
if (currentRevision == nullptr) {
return std::vector<jsi::Value>{};
}
// The only child node of the document is the root node.
if (nativeNodeReference.isNumber()) {
return getArrayOfInstanceHandlesFromShadowNodes({currentRevision}, rt);
}
auto childNodes = dom::getChildNodes(
currentRevision, *getShadowNode(rt, nativeNodeReference));
return getArrayOfInstanceHandlesFromShadowNodes(childNodes, rt);
}
jsi::Value NativeDOM::getElementById(
jsi::Runtime& rt,
SurfaceId surfaceId,
const std::string& id) {
auto currentRevision = getCurrentShadowTreeRevision(rt, surfaceId);
if (currentRevision == nullptr) {
return jsi::Value::undefined();
}
auto elementById = dom::getElementById(currentRevision, id);
if (elementById == nullptr) {
return jsi::Value::undefined();
}
return elementById->getInstanceHandle(rt);
}
jsi::Value NativeDOM::getParentNode(
jsi::Runtime& rt,
jsi::Value nativeNodeReference) {
// The document does not have a parent node.
if (nativeNodeReference.isNumber()) {
return jsi::Value::undefined();
}
auto shadowNode = getShadowNode(rt, nativeNodeReference);
if (isRootShadowNode(*shadowNode)) {
// The parent of the root node is the document.
return jsi::Value{shadowNode->getSurfaceId()};
}
auto currentRevision =
getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
return jsi::Value::undefined();
}
auto parentShadowNode = dom::getParentNode(currentRevision, *shadowNode);
if (parentShadowNode == nullptr) {
return jsi::Value::undefined();
}
return parentShadowNode->getInstanceHandle(rt);
}
bool NativeDOM::isConnected(jsi::Runtime& rt, jsi::Value nativeNodeReference) {
auto currentRevision = getCurrentShadowTreeRevision(rt, nativeNodeReference);
if (currentRevision == nullptr) {
return false;
}
// The document is connected because we got a value for current revision.
if (nativeNodeReference.isNumber()) {
return true;
}
auto shadowNode = getShadowNode(rt, nativeNodeReference);
return dom::isConnected(currentRevision, *shadowNode);
}
#pragma mark - Methods from the `Element` interface (for `ReactNativeElement`).
std::tuple<
/* topWidth: */ int,
/* rightWidth: */ int,
/* bottomWidth: */ int,
/* leftWidth: */ int>
NativeDOM::getBorderWidth(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode) {
auto currentRevision =
getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
return {0, 0, 0, 0};
}
auto borderWidth = dom::getBorderWidth(currentRevision, *shadowNode);
return std::tuple{
borderWidth.top, borderWidth.right, borderWidth.bottom, borderWidth.left};
}
std::tuple<
/* x: */ double,
/* y: */ double,
/* width: */ double,
/* height: */ double>
NativeDOM::getBoundingClientRect(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode,
bool includeTransform) {
auto currentRevision =
getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
return {0, 0, 0, 0};
}
auto domRect = dom::getBoundingClientRect(
currentRevision, *shadowNode, includeTransform);
return std::tuple{domRect.x, domRect.y, domRect.width, domRect.height};
}
std::tuple</* width: */ int, /* height: */ int> NativeDOM::getInnerSize(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode) {
auto currentRevision =
getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
return {0, 0};
}
auto innerSize = dom::getInnerSize(currentRevision, *shadowNode);
return std::tuple{innerSize.width, innerSize.height};
}
std::tuple</* scrollLeft: */ double, /* scrollTop: */ double>
NativeDOM::getScrollPosition(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode) {
auto currentRevision =
getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
return {0, 0};
}
auto domPoint = dom::getScrollPosition(currentRevision, *shadowNode);
return std::tuple{domPoint.x, domPoint.y};
}
std::tuple</* scrollWidth: */ int, /* scrollHeight */ int>
NativeDOM::getScrollSize(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode) {
auto currentRevision =
getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
return {0, 0};
}
auto scrollSize = dom::getScrollSize(currentRevision, *shadowNode);
return std::tuple{scrollSize.width, scrollSize.height};
}
std::string NativeDOM::getTagName(
jsi::Runtime& /*rt*/,
std::shared_ptr<const ShadowNode> shadowNode) {
return dom::getTagName(*shadowNode);
}
std::string NativeDOM::getTextContent(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode) {
auto currentRevision =
getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
return "";
}
return dom::getTextContent(currentRevision, *shadowNode);
}
bool NativeDOM::hasPointerCapture(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode,
double pointerId) {
bool isCapturing = getPointerEventsProcessorFromRuntime(rt).hasPointerCapture(
static_cast<PointerIdentifier>(pointerId), shadowNode.get());
return isCapturing;
}
void NativeDOM::releasePointerCapture(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode,
double pointerId) {
getPointerEventsProcessorFromRuntime(rt).releasePointerCapture(
static_cast<PointerIdentifier>(pointerId), shadowNode.get());
}
void NativeDOM::setPointerCapture(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode,
double pointerId) {
getPointerEventsProcessorFromRuntime(rt).setPointerCapture(
static_cast<PointerIdentifier>(pointerId), shadowNode);
}
#pragma mark - Methods from the `HTMLElement` interface (for `ReactNativeElement`).
std::tuple<
/* offsetParent: */ jsi::Value,
/* top: */ double,
/* left: */ double>
NativeDOM::getOffset(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode) {
auto currentRevision =
getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
return {jsi::Value::undefined(), 0, 0};
}
auto domOffset = dom::getOffset(currentRevision, *shadowNode);
return std::tuple{
domOffset.offsetParent == nullptr
? jsi::Value::undefined()
: domOffset.offsetParent->getInstanceHandle(rt),
domOffset.top,
domOffset.left};
}
#pragma mark - Special methods to handle the root node.
jsi::Value NativeDOM::linkRootNode(
jsi::Runtime& rt,
SurfaceId surfaceId,
jsi::Value instanceHandle) {
auto currentRevision = getCurrentShadowTreeRevision(rt, surfaceId);
if (currentRevision == nullptr) {
return jsi::Value::undefined();
}
auto instanceHandleWrapper =
std::make_shared<InstanceHandle>(rt, instanceHandle, surfaceId);
currentRevision->setInstanceHandle(instanceHandleWrapper);
return Bridging<std::shared_ptr<const ShadowNode>>::toJs(rt, currentRevision);
}
#pragma mark - Legacy layout APIs (for `ReactNativeElement`).
void NativeDOM::measure(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode,
const MeasureOnSuccessCallback& callback) {
auto currentRevision =
getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
callback(0, 0, 0, 0, 0, 0);
return;
}
auto measureRect = dom::measure(currentRevision, *shadowNode);
callback(
measureRect.x,
measureRect.y,
measureRect.width,
measureRect.height,
measureRect.pageX,
measureRect.pageY);
}
void NativeDOM::measureInWindow(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode,
const MeasureInWindowOnSuccessCallback& callback) {
auto currentRevision =
getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
callback(0, 0, 0, 0);
return;
}
auto rect = dom::measureInWindow(currentRevision, *shadowNode);
callback(rect.x, rect.y, rect.width, rect.height);
}
void NativeDOM::measureLayout(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode,
std::shared_ptr<const ShadowNode> relativeToShadowNode,
jsi::Function onFail,
const MeasureLayoutOnSuccessCallback& onSuccess) {
auto currentRevision =
getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId());
if (currentRevision == nullptr) {
onFail.call(rt);
return;
}
auto maybeRect =
dom::measureLayout(currentRevision, *shadowNode, *relativeToShadowNode);
if (!maybeRect) {
onFail.call(rt);
return;
}
auto rect = maybeRect.value();
onSuccess(rect.x, rect.y, rect.width, rect.height);
}
#pragma mark - Legacy direct manipulation APIs (for `ReactNativeElement`).
void NativeDOM::setNativeProps(
jsi::Runtime& rt,
std::shared_ptr<const ShadowNode> shadowNode,
jsi::Value updatePayload) {
getUIManagerFromRuntime(rt).setNativeProps_DEPRECATED(
shadowNode, RawProps(rt, updatePayload));
}
} // namespace facebook::react

View File

@@ -0,0 +1,116 @@
/*
* 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>
#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
#include "FBReactNativeSpecJSI.h"
#else
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#endif
#include <react/renderer/bridging/bridging.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/core/ShadowNodeFamily.h>
namespace facebook::react {
using MeasureOnSuccessCallback = SyncCallback<void(double, double, double, double, double, double)>;
using MeasureInWindowOnSuccessCallback = SyncCallback<void(double, double, double, double)>;
using MeasureLayoutOnSuccessCallback = SyncCallback<void(double, double, double, double)>;
class NativeDOM : public NativeDOMCxxSpec<NativeDOM> {
public:
NativeDOM(std::shared_ptr<CallInvoker> jsInvoker);
#pragma mark - Methods from the `Node` interface (for `ReadOnlyNode`).
double compareDocumentPosition(jsi::Runtime &rt, jsi::Value nativeNodeReference, jsi::Value otherNativeNodeReference);
std::vector<jsi::Value> getChildNodes(jsi::Runtime &rt, jsi::Value nativeNodeReference);
jsi::Value getElementById(jsi::Runtime &rt, SurfaceId surfaceId, const std::string &id);
jsi::Value getParentNode(jsi::Runtime &rt, jsi::Value nativeNodeReference);
bool isConnected(jsi::Runtime &rt, jsi::Value nativeNodeReference);
#pragma mark - Methods from the `Element` interface (for `ReactNativeElement`).
std::tuple<
/* topWidth: */ int,
/* rightWidth: */ int,
/* bottomWidth: */ int,
/* leftWidth: */ int> getBorderWidth(jsi::Runtime &rt, std::shared_ptr<const ShadowNode> shadowNode);
std::tuple<
/* x: */ double,
/* y: */ double,
/* width: */ double,
/* height: */ double>
getBoundingClientRect(jsi::Runtime &rt, std::shared_ptr<const ShadowNode> shadowNode, bool includeTransform);
std::tuple</* width: */ int, /* height: */ int> getInnerSize(
jsi::Runtime &rt,
std::shared_ptr<const ShadowNode> shadowNode);
std::tuple</* scrollLeft: */ double, /* scrollTop: */ double> getScrollPosition(
jsi::Runtime &rt,
std::shared_ptr<const ShadowNode> shadowNode);
std::tuple</* scrollWidth: */ int, /* scrollHeight */ int> getScrollSize(
jsi::Runtime &rt,
std::shared_ptr<const ShadowNode> shadowNode);
std::string getTagName(jsi::Runtime &rt, std::shared_ptr<const ShadowNode> shadowNode);
std::string getTextContent(jsi::Runtime &rt, std::shared_ptr<const ShadowNode> shadowNode);
bool hasPointerCapture(jsi::Runtime &rt, std::shared_ptr<const ShadowNode> shadowNode, double pointerId);
void releasePointerCapture(jsi::Runtime &rt, std::shared_ptr<const ShadowNode> shadowNode, double pointerId);
void setPointerCapture(jsi::Runtime &rt, std::shared_ptr<const ShadowNode> shadowNode, double pointerId);
#pragma mark - Methods from the `HTMLElement` interface (for `ReactNativeElement`).
std::tuple<
/* offsetParent: */ jsi::Value,
/* top: */ double,
/* left: */ double> getOffset(jsi::Runtime &rt, std::shared_ptr<const ShadowNode> shadowNode);
#pragma mark - Special methods to handle the root node.
jsi::Value linkRootNode(jsi::Runtime &rt, SurfaceId surfaceId, jsi::Value instanceHandle);
#pragma mark - Legacy layout APIs (for `ReactNativeElement`).
void
measure(jsi::Runtime &rt, std::shared_ptr<const ShadowNode> shadowNode, const MeasureOnSuccessCallback &callback);
void measureInWindow(
jsi::Runtime &rt,
std::shared_ptr<const ShadowNode> shadowNode,
const MeasureInWindowOnSuccessCallback &callback);
void measureLayout(
jsi::Runtime &rt,
std::shared_ptr<const ShadowNode> shadowNode,
std::shared_ptr<const ShadowNode> relativeToShadowNode,
jsi::Function onFail,
const MeasureLayoutOnSuccessCallback &onSuccess);
#pragma mark - Legacy direct manipulation APIs (for `ReactNativeElement`).
void setNativeProps(jsi::Runtime &rt, std::shared_ptr<const ShadowNode> shadowNode, jsi::Value updatePayload);
};
} // namespace facebook::react

View File

@@ -0,0 +1,60 @@
# 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
header_search_paths = [
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
]
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the domnativemodule to access its own files
end
Pod::Spec.new do |s|
s.name = "React-domnativemodule"
s.version = version
s.summary = "React Native DOM native module"
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.header_dir = "react/nativemodule/dom"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"OTHER_CFLAGS" => "$(inherited)",
"DEFINES_MODULE" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_domnativemodule")
s.dependency "React-jsi"
s.dependency "React-jsiexecutor"
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.dependency "Yoga"
s.dependency "ReactCommon/turbomodule/core"
s.dependency "React-Fabric"
s.dependency "React-Fabric/bridging"
s.dependency "React-FabricComponents"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])
add_dependency(s, "React-RCTFBReactNativeSpec")
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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_nativemodule_fantomspecificmethods_SRC CONFIGURE_DEPENDS *.cpp internal/*.cpp)
add_library(react_nativemodule_fantomspecificmethods OBJECT ${react_nativemodule_fantomspecificmethods_SRC})
target_include_directories(react_nativemodule_fantomspecificmethods PUBLIC ${REACT_COMMON_DIR})
target_include_directories(react_nativemodule_fantomspecificmethods PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/internal)
target_link_libraries(react_nativemodule_fantomspecificmethods
react_codegen_rncore
react_cxxreact
react_renderer_bridging
react_renderer_core
react_renderer_graphics
react_renderer_observers_intersection
react_renderer_runtimescheduler
react_renderer_uimanager
rrc_view
)
target_compile_reactnative_options(react_nativemodule_fantomspecificmethods PRIVATE)
target_compile_options(react_nativemodule_fantomspecificmethods PRIVATE -Wpedantic -Wno-deprecated-declarations)

View File

@@ -0,0 +1,52 @@
/*
* 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 "NativeFantomTestSpecificMethods.h"
#include <react/renderer/uimanager/UIManager.h>
#include <react/renderer/uimanager/UIManagerBinding.h>
#include "internal/FantomForcedCloneCommitHook.h"
#if RN_DISABLE_OSS_PLUGIN_HEADER
#include "Plugins.h"
#endif
std::shared_ptr<facebook::react::TurboModule>
NativeFantomTestSpecificMethodsModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::NativeFantomTestSpecificMethods>(
std::move(jsInvoker));
}
namespace {
facebook::react::UIManager& getUIManagerFromRuntime(
facebook::jsi::Runtime& runtime) {
return facebook::react::UIManagerBinding::getBinding(runtime)->getUIManager();
}
} // namespace
namespace facebook::react {
NativeFantomTestSpecificMethods::NativeFantomTestSpecificMethods(
std::shared_ptr<CallInvoker> jsInvoker)
: NativeFantomTestSpecificMethodsCxxSpec(std::move(jsInvoker)),
fantomForcedCloneCommitHook_(
std::make_shared<FantomForcedCloneCommitHook>()) {}
void NativeFantomTestSpecificMethods::registerForcedCloneCommitHook(
jsi::Runtime& runtime) {
auto& uiManager = getUIManagerFromRuntime(runtime);
uiManager.registerCommitHook(*fantomForcedCloneCommitHook_);
}
void NativeFantomTestSpecificMethods::takeFunctionAndNoop(
jsi::Runtime& runtime,
jsi::Function function) {}
} // 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 <FBReactNativeSpec/FBReactNativeSpecJSI.h>
namespace facebook::react {
struct FantomForcedCloneCommitHook;
class NativeFantomTestSpecificMethods : public NativeFantomTestSpecificMethodsCxxSpec<NativeFantomTestSpecificMethods> {
public:
explicit NativeFantomTestSpecificMethods(std::shared_ptr<CallInvoker> jsInvoker);
void registerForcedCloneCommitHook(jsi::Runtime &runtime);
void takeFunctionAndNoop(jsi::Runtime &runtime, jsi::Function callback);
private:
std::shared_ptr<FantomForcedCloneCommitHook> fantomForcedCloneCommitHook_{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,57 @@
/*
* 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 "FantomForcedCloneCommitHook.h"
#include <react/renderer/uimanager/UIManagerBinding.h>
#include <react/renderer/uimanager/primitives.h>
namespace facebook::react {
namespace {
std::shared_ptr<const ShadowNode> findAndClone(
const std::shared_ptr<const ShadowNode>& node) {
if (node->getProps()->nativeId == "to-be-cloned-in-the-commit-hook") {
return node->clone({});
}
auto children = node->getChildren();
for (int i = 0; i < children.size(); i++) {
auto& child = children[i];
auto maybeClone = findAndClone(child);
if (maybeClone != child) {
children[i] = maybeClone;
return node->clone(
{.props = ShadowNodeFragment::propsPlaceholder(),
.children =
std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>(
children)});
}
}
return node;
}
} // namespace
void FantomForcedCloneCommitHook::commitHookWasRegistered(
const UIManager& /*uiManager*/) noexcept {}
void FantomForcedCloneCommitHook::commitHookWasUnregistered(
const UIManager& /*uiManager*/) noexcept {}
RootShadowNode::Unshared FantomForcedCloneCommitHook::shadowTreeWillCommit(
const ShadowTree& /*shadowTree*/,
const std::shared_ptr<const RootShadowNode>& /*oldRootShadowNode*/,
const RootShadowNode::Unshared& newRootShadowNode) noexcept {
auto result = findAndClone(newRootShadowNode);
return std::static_pointer_cast<RootShadowNode>(
std::const_pointer_cast<ShadowNode>(result));
}
} // namespace facebook::react

View File

@@ -0,0 +1,27 @@
/*
* 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 <react/renderer/core/ShadowNode.h>
#include <react/renderer/uimanager/UIManager.h>
#include <react/renderer/uimanager/UIManagerCommitHook.h>
namespace facebook::react {
struct FantomForcedCloneCommitHook : public UIManagerCommitHook {
void commitHookWasRegistered(const UIManager & /*uiManager*/) noexcept override;
void commitHookWasUnregistered(const UIManager & /*uiManager*/) noexcept override;
RootShadowNode::Unshared shadowTreeWillCommit(
const ShadowTree &shadowTree,
const std::shared_ptr<const RootShadowNode> &oldRootShadowNode,
const RootShadowNode::Unshared &newRootShadowNode) noexcept override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,22 @@
# 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_nativemodule_featureflags_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_featureflags OBJECT ${react_nativemodule_featureflags_SRC})
target_include_directories(react_nativemodule_featureflags PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_nativemodule_featureflags
react_codegen_rncore
react_cxxreact
react_featureflags
)
target_compile_reactnative_options(react_nativemodule_featureflags PRIVATE)
target_compile_options(react_nativemodule_featureflags PRIVATE -Wpedantic)

View File

@@ -0,0 +1,472 @@
/*
* 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 SignedSource<<3f6cc9604905bb29a9524a97eaa294bd>>
*/
/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags --update
*/
#include "NativeReactNativeFeatureFlags.h"
#include <react/featureflags/ReactNativeFeatureFlags.h>
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
#include "Plugins.h"
#endif
std::shared_ptr<facebook::react::TurboModule>
NativeReactNativeFeatureFlagsModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::NativeReactNativeFeatureFlags>(
std::move(jsInvoker));
}
namespace facebook::react {
NativeReactNativeFeatureFlags::NativeReactNativeFeatureFlags(
std::shared_ptr<CallInvoker> jsInvoker)
: NativeReactNativeFeatureFlagsCxxSpec(std::move(jsInvoker)) {}
bool NativeReactNativeFeatureFlags::commonTestFlag(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::commonTestFlag();
}
bool NativeReactNativeFeatureFlags::commonTestFlagWithoutNativeImplementation(
jsi::Runtime& /*runtime*/) {
// This flag is configured with `skipNativeAPI: true`.
// TODO(T204838867): Implement support for optional methods in C++ TM codegen and remove the method definition altogether.
return false;
}
bool NativeReactNativeFeatureFlags::cdpInteractionMetricsEnabled(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::cdpInteractionMetricsEnabled();
}
bool NativeReactNativeFeatureFlags::cxxNativeAnimatedEnabled(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::cxxNativeAnimatedEnabled();
}
bool NativeReactNativeFeatureFlags::cxxNativeAnimatedRemoveJsSync(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::cxxNativeAnimatedRemoveJsSync();
}
bool NativeReactNativeFeatureFlags::disableEarlyViewCommandExecution(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::disableEarlyViewCommandExecution();
}
bool NativeReactNativeFeatureFlags::disableFabricCommitInCXXAnimated(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::disableFabricCommitInCXXAnimated();
}
bool NativeReactNativeFeatureFlags::disableMountItemReorderingAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::disableMountItemReorderingAndroid();
}
bool NativeReactNativeFeatureFlags::disableOldAndroidAttachmentMetricsWorkarounds(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::disableOldAndroidAttachmentMetricsWorkarounds();
}
bool NativeReactNativeFeatureFlags::disableTextLayoutManagerCacheAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::disableTextLayoutManagerCacheAndroid();
}
bool NativeReactNativeFeatureFlags::enableAccessibilityOrder(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableAccessibilityOrder();
}
bool NativeReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid();
}
bool NativeReactNativeFeatureFlags::enableAndroidLinearText(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableAndroidLinearText();
}
bool NativeReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations();
}
bool NativeReactNativeFeatureFlags::enableBridgelessArchitecture(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableBridgelessArchitecture();
}
bool NativeReactNativeFeatureFlags::enableCppPropsIteratorSetter(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableCppPropsIteratorSetter();
}
bool NativeReactNativeFeatureFlags::enableCustomFocusSearchOnClippedElementsAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableCustomFocusSearchOnClippedElementsAndroid();
}
bool NativeReactNativeFeatureFlags::enableDestroyShadowTreeRevisionAsync(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableDestroyShadowTreeRevisionAsync();
}
bool NativeReactNativeFeatureFlags::enableDoubleMeasurementFixAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableDoubleMeasurementFixAndroid();
}
bool NativeReactNativeFeatureFlags::enableEagerMainQueueModulesOnIOS(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableEagerMainQueueModulesOnIOS();
}
bool NativeReactNativeFeatureFlags::enableEagerRootViewAttachment(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableEagerRootViewAttachment();
}
bool NativeReactNativeFeatureFlags::enableFabricLogs(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableFabricLogs();
}
bool NativeReactNativeFeatureFlags::enableFabricRenderer(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableFabricRenderer();
}
bool NativeReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout();
}
bool NativeReactNativeFeatureFlags::enableIOSTextBaselineOffsetPerLine(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableIOSTextBaselineOffsetPerLine();
}
bool NativeReactNativeFeatureFlags::enableIOSViewClipToPaddingBox(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox();
}
bool NativeReactNativeFeatureFlags::enableImagePrefetchingAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableImagePrefetchingAndroid();
}
bool NativeReactNativeFeatureFlags::enableImagePrefetchingOnUiThreadAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableImagePrefetchingOnUiThreadAndroid();
}
bool NativeReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges();
}
bool NativeReactNativeFeatureFlags::enableImperativeFocus(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableImperativeFocus();
}
bool NativeReactNativeFeatureFlags::enableInteropViewManagerClassLookUpOptimizationIOS(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableInteropViewManagerClassLookUpOptimizationIOS();
}
bool NativeReactNativeFeatureFlags::enableIntersectionObserverByDefault(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableIntersectionObserverByDefault();
}
bool NativeReactNativeFeatureFlags::enableKeyEvents(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableKeyEvents();
}
bool NativeReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid();
}
bool NativeReactNativeFeatureFlags::enableLayoutAnimationsOnIOS(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableLayoutAnimationsOnIOS();
}
bool NativeReactNativeFeatureFlags::enableMainQueueCoordinatorOnIOS(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableMainQueueCoordinatorOnIOS();
}
bool NativeReactNativeFeatureFlags::enableModuleArgumentNSNullConversionIOS(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableModuleArgumentNSNullConversionIOS();
}
bool NativeReactNativeFeatureFlags::enableNativeCSSParsing(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableNativeCSSParsing();
}
bool NativeReactNativeFeatureFlags::enableNetworkEventReporting(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableNetworkEventReporting();
}
bool NativeReactNativeFeatureFlags::enablePreparedTextLayout(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enablePreparedTextLayout();
}
bool NativeReactNativeFeatureFlags::enablePropsUpdateReconciliationAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enablePropsUpdateReconciliationAndroid();
}
bool NativeReactNativeFeatureFlags::enableResourceTimingAPI(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableResourceTimingAPI();
}
bool NativeReactNativeFeatureFlags::enableSwiftUIBasedFilters(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableSwiftUIBasedFilters();
}
bool NativeReactNativeFeatureFlags::enableViewCulling(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableViewCulling();
}
bool NativeReactNativeFeatureFlags::enableViewRecycling(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableViewRecycling();
}
bool NativeReactNativeFeatureFlags::enableViewRecyclingForImage(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableViewRecyclingForImage();
}
bool NativeReactNativeFeatureFlags::enableViewRecyclingForScrollView(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableViewRecyclingForScrollView();
}
bool NativeReactNativeFeatureFlags::enableViewRecyclingForText(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableViewRecyclingForText();
}
bool NativeReactNativeFeatureFlags::enableViewRecyclingForView(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableViewRecyclingForView();
}
bool NativeReactNativeFeatureFlags::enableVirtualViewClippingWithoutScrollViewClipping(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableVirtualViewClippingWithoutScrollViewClipping();
}
bool NativeReactNativeFeatureFlags::enableVirtualViewContainerStateExperimental(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableVirtualViewContainerStateExperimental();
}
bool NativeReactNativeFeatureFlags::enableVirtualViewDebugFeatures(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableVirtualViewDebugFeatures();
}
bool NativeReactNativeFeatureFlags::enableVirtualViewRenderState(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableVirtualViewRenderState();
}
bool NativeReactNativeFeatureFlags::enableVirtualViewWindowFocusDetection(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableVirtualViewWindowFocusDetection();
}
bool NativeReactNativeFeatureFlags::enableWebPerformanceAPIsByDefault(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableWebPerformanceAPIsByDefault();
}
bool NativeReactNativeFeatureFlags::fixMappingOfEventPrioritiesBetweenFabricAndReact(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::fixMappingOfEventPrioritiesBetweenFabricAndReact();
}
bool NativeReactNativeFeatureFlags::fuseboxAssertSingleHostState(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::fuseboxAssertSingleHostState();
}
bool NativeReactNativeFeatureFlags::fuseboxEnabledRelease(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::fuseboxEnabledRelease();
}
bool NativeReactNativeFeatureFlags::fuseboxNetworkInspectionEnabled(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::fuseboxNetworkInspectionEnabled();
}
bool NativeReactNativeFeatureFlags::hideOffscreenVirtualViewsOnIOS(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::hideOffscreenVirtualViewsOnIOS();
}
bool NativeReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid();
}
bool NativeReactNativeFeatureFlags::perfIssuesEnabled(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::perfIssuesEnabled();
}
bool NativeReactNativeFeatureFlags::perfMonitorV2Enabled(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::perfMonitorV2Enabled();
}
double NativeReactNativeFeatureFlags::preparedTextCacheSize(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::preparedTextCacheSize();
}
bool NativeReactNativeFeatureFlags::preventShadowTreeCommitExhaustion(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion();
}
bool NativeReactNativeFeatureFlags::shouldPressibilityUseW3CPointerEventsForHover(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::shouldPressibilityUseW3CPointerEventsForHover();
}
bool NativeReactNativeFeatureFlags::shouldTriggerResponderTransferOnScrollAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::shouldTriggerResponderTransferOnScrollAndroid();
}
bool NativeReactNativeFeatureFlags::skipActivityIdentityAssertionOnHostPause(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::skipActivityIdentityAssertionOnHostPause();
}
bool NativeReactNativeFeatureFlags::sweepActiveTouchOnChildNativeGesturesAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::sweepActiveTouchOnChildNativeGesturesAndroid();
}
bool NativeReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid();
}
bool NativeReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommit(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommit();
}
bool NativeReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling();
}
bool NativeReactNativeFeatureFlags::useFabricInterop(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useFabricInterop();
}
bool NativeReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid();
}
bool NativeReactNativeFeatureFlags::useNativeTransformHelperAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useNativeTransformHelperAndroid();
}
bool NativeReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode();
}
bool NativeReactNativeFeatureFlags::useOptimizedEventBatchingOnAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useOptimizedEventBatchingOnAndroid();
}
bool NativeReactNativeFeatureFlags::useRawPropsJsiValue(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useRawPropsJsiValue();
}
bool NativeReactNativeFeatureFlags::useShadowNodeStateOnClone(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useShadowNodeStateOnClone();
}
bool NativeReactNativeFeatureFlags::useSharedAnimatedBackend(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useSharedAnimatedBackend();
}
bool NativeReactNativeFeatureFlags::useTraitHiddenOnAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useTraitHiddenOnAndroid();
}
bool NativeReactNativeFeatureFlags::useTurboModuleInterop(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useTurboModuleInterop();
}
bool NativeReactNativeFeatureFlags::useTurboModules(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useTurboModules();
}
double NativeReactNativeFeatureFlags::viewCullingOutsetRatio(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::viewCullingOutsetRatio();
}
double NativeReactNativeFeatureFlags::virtualViewHysteresisRatio(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::virtualViewHysteresisRatio();
}
double NativeReactNativeFeatureFlags::virtualViewPrerenderRatio(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::virtualViewPrerenderRatio();
}
} // namespace facebook::react

View File

@@ -0,0 +1,210 @@
/*
* 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 SignedSource<<dece70d27e5d2e4d052ca4e158c4b968>>
*/
/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags --update
*/
#pragma once
#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
#include "FBReactNativeSpecJSI.h"
#else
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#endif
namespace facebook::react {
class NativeReactNativeFeatureFlags
: public NativeReactNativeFeatureFlagsCxxSpec<NativeReactNativeFeatureFlags> {
public:
NativeReactNativeFeatureFlags(std::shared_ptr<CallInvoker> jsInvoker);
static constexpr std::string_view kModuleName = "NativeReactNativeFeatureFlagsCxx";
bool commonTestFlag(jsi::Runtime& runtime);
bool commonTestFlagWithoutNativeImplementation(jsi::Runtime& runtime);
bool cdpInteractionMetricsEnabled(jsi::Runtime& runtime);
bool cxxNativeAnimatedEnabled(jsi::Runtime& runtime);
bool cxxNativeAnimatedRemoveJsSync(jsi::Runtime& runtime);
bool disableEarlyViewCommandExecution(jsi::Runtime& runtime);
bool disableFabricCommitInCXXAnimated(jsi::Runtime& runtime);
bool disableMountItemReorderingAndroid(jsi::Runtime& runtime);
bool disableOldAndroidAttachmentMetricsWorkarounds(jsi::Runtime& runtime);
bool disableTextLayoutManagerCacheAndroid(jsi::Runtime& runtime);
bool enableAccessibilityOrder(jsi::Runtime& runtime);
bool enableAccumulatedUpdatesInRawPropsAndroid(jsi::Runtime& runtime);
bool enableAndroidLinearText(jsi::Runtime& runtime);
bool enableAndroidTextMeasurementOptimizations(jsi::Runtime& runtime);
bool enableBridgelessArchitecture(jsi::Runtime& runtime);
bool enableCppPropsIteratorSetter(jsi::Runtime& runtime);
bool enableCustomFocusSearchOnClippedElementsAndroid(jsi::Runtime& runtime);
bool enableDestroyShadowTreeRevisionAsync(jsi::Runtime& runtime);
bool enableDoubleMeasurementFixAndroid(jsi::Runtime& runtime);
bool enableEagerMainQueueModulesOnIOS(jsi::Runtime& runtime);
bool enableEagerRootViewAttachment(jsi::Runtime& runtime);
bool enableFabricLogs(jsi::Runtime& runtime);
bool enableFabricRenderer(jsi::Runtime& runtime);
bool enableFontScaleChangesUpdatingLayout(jsi::Runtime& runtime);
bool enableIOSTextBaselineOffsetPerLine(jsi::Runtime& runtime);
bool enableIOSViewClipToPaddingBox(jsi::Runtime& runtime);
bool enableImagePrefetchingAndroid(jsi::Runtime& runtime);
bool enableImagePrefetchingOnUiThreadAndroid(jsi::Runtime& runtime);
bool enableImmediateUpdateModeForContentOffsetChanges(jsi::Runtime& runtime);
bool enableImperativeFocus(jsi::Runtime& runtime);
bool enableInteropViewManagerClassLookUpOptimizationIOS(jsi::Runtime& runtime);
bool enableIntersectionObserverByDefault(jsi::Runtime& runtime);
bool enableKeyEvents(jsi::Runtime& runtime);
bool enableLayoutAnimationsOnAndroid(jsi::Runtime& runtime);
bool enableLayoutAnimationsOnIOS(jsi::Runtime& runtime);
bool enableMainQueueCoordinatorOnIOS(jsi::Runtime& runtime);
bool enableModuleArgumentNSNullConversionIOS(jsi::Runtime& runtime);
bool enableNativeCSSParsing(jsi::Runtime& runtime);
bool enableNetworkEventReporting(jsi::Runtime& runtime);
bool enablePreparedTextLayout(jsi::Runtime& runtime);
bool enablePropsUpdateReconciliationAndroid(jsi::Runtime& runtime);
bool enableResourceTimingAPI(jsi::Runtime& runtime);
bool enableSwiftUIBasedFilters(jsi::Runtime& runtime);
bool enableViewCulling(jsi::Runtime& runtime);
bool enableViewRecycling(jsi::Runtime& runtime);
bool enableViewRecyclingForImage(jsi::Runtime& runtime);
bool enableViewRecyclingForScrollView(jsi::Runtime& runtime);
bool enableViewRecyclingForText(jsi::Runtime& runtime);
bool enableViewRecyclingForView(jsi::Runtime& runtime);
bool enableVirtualViewClippingWithoutScrollViewClipping(jsi::Runtime& runtime);
bool enableVirtualViewContainerStateExperimental(jsi::Runtime& runtime);
bool enableVirtualViewDebugFeatures(jsi::Runtime& runtime);
bool enableVirtualViewRenderState(jsi::Runtime& runtime);
bool enableVirtualViewWindowFocusDetection(jsi::Runtime& runtime);
bool enableWebPerformanceAPIsByDefault(jsi::Runtime& runtime);
bool fixMappingOfEventPrioritiesBetweenFabricAndReact(jsi::Runtime& runtime);
bool fuseboxAssertSingleHostState(jsi::Runtime& runtime);
bool fuseboxEnabledRelease(jsi::Runtime& runtime);
bool fuseboxNetworkInspectionEnabled(jsi::Runtime& runtime);
bool hideOffscreenVirtualViewsOnIOS(jsi::Runtime& runtime);
bool overrideBySynchronousMountPropsAtMountingAndroid(jsi::Runtime& runtime);
bool perfIssuesEnabled(jsi::Runtime& runtime);
bool perfMonitorV2Enabled(jsi::Runtime& runtime);
double preparedTextCacheSize(jsi::Runtime& runtime);
bool preventShadowTreeCommitExhaustion(jsi::Runtime& runtime);
bool shouldPressibilityUseW3CPointerEventsForHover(jsi::Runtime& runtime);
bool shouldTriggerResponderTransferOnScrollAndroid(jsi::Runtime& runtime);
bool skipActivityIdentityAssertionOnHostPause(jsi::Runtime& runtime);
bool sweepActiveTouchOnChildNativeGesturesAndroid(jsi::Runtime& runtime);
bool traceTurboModulePromiseRejectionsOnAndroid(jsi::Runtime& runtime);
bool updateRuntimeShadowNodeReferencesOnCommit(jsi::Runtime& runtime);
bool useAlwaysAvailableJSErrorHandling(jsi::Runtime& runtime);
bool useFabricInterop(jsi::Runtime& runtime);
bool useNativeEqualsInNativeReadableArrayAndroid(jsi::Runtime& runtime);
bool useNativeTransformHelperAndroid(jsi::Runtime& runtime);
bool useNativeViewConfigsInBridgelessMode(jsi::Runtime& runtime);
bool useOptimizedEventBatchingOnAndroid(jsi::Runtime& runtime);
bool useRawPropsJsiValue(jsi::Runtime& runtime);
bool useShadowNodeStateOnClone(jsi::Runtime& runtime);
bool useSharedAnimatedBackend(jsi::Runtime& runtime);
bool useTraitHiddenOnAndroid(jsi::Runtime& runtime);
bool useTurboModuleInterop(jsi::Runtime& runtime);
bool useTurboModules(jsi::Runtime& runtime);
double viewCullingOutsetRatio(jsi::Runtime& runtime);
double virtualViewHysteresisRatio(jsi::Runtime& runtime);
double virtualViewPrerenderRatio(jsi::Runtime& runtime);
};
} // namespace facebook::react

View File

@@ -0,0 +1,53 @@
# 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
header_search_paths = []
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the feature flags access its own files
end
Pod::Spec.new do |s|
s.name = "React-featureflagsnativemodule"
s.version = version
s.summary = "React Native internal feature flags"
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.header_dir = "react/nativemodule/featureflags"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"OTHER_CFLAGS" => "$(inherited)",
"DEFINES_MODULE" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_featureflagsnativemodule")
s.dependency "React-jsi"
s.dependency "React-jsiexecutor"
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.dependency "ReactCommon/turbomodule/core"
s.dependency "React-RCTFBReactNativeSpec"
s.dependency "React-featureflags"
end

View File

@@ -0,0 +1,21 @@
# 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_nativemodule_idlecallbacks_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_idlecallbacks OBJECT ${react_nativemodule_idlecallbacks_SRC})
target_include_directories(react_nativemodule_idlecallbacks PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_nativemodule_idlecallbacks
react_codegen_rncore
react_cxxreact
react_renderer_runtimescheduler
)
target_compile_reactnative_options(react_nativemodule_idlecallbacks PRIVATE)
target_compile_options(react_nativemodule_idlecallbacks PRIVATE -Wpedantic)

View File

@@ -0,0 +1,161 @@
/*
* 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 "NativeIdleCallbacks.h"
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
#include <react/renderer/runtimescheduler/Task.h>
#include <react/timing/primitives.h>
#include <utility>
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
#include "Plugins.h"
#endif
std::shared_ptr<facebook::react::TurboModule> NativeIdleCallbacksModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::NativeIdleCallbacks>(
std::move(jsInvoker));
}
namespace facebook::react {
namespace {
class IdleTaskRef : public jsi::NativeState {
public:
explicit IdleTaskRef(std::shared_ptr<Task> task) : task(std::move(task)) {}
std::shared_ptr<Task> task;
};
jsi::Function makeTimeRemainingFunction(
jsi::Runtime& runtime,
std::shared_ptr<RuntimeScheduler> runtimeScheduler,
HighResTimeStamp deadline) {
return jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "timeRemaining"),
0,
[runtimeScheduler, deadline, expired = false](
jsi::Runtime& runtime,
const jsi::Value& /* unused */,
const jsi::Value* /* unused */,
size_t /* unused */) mutable {
double remainingTime = 0;
// No need to access the runtime scheduler if this idle callback expired
// already.
if (!expired) {
if (runtimeScheduler->getShouldYield()) {
expired = true;
} else {
auto now = runtimeScheduler->now();
auto diff = deadline - now;
remainingTime = std::max(diff.toDOMHighResTimeStamp(), 0.0);
if (remainingTime == 0) {
expired = true;
}
}
}
return jsi::Value(runtime, remainingTime);
});
}
} // namespace
NativeIdleCallbacks::NativeIdleCallbacks(std::shared_ptr<CallInvoker> jsInvoker)
: NativeIdleCallbacksCxxSpec(std::move(jsInvoker)) {}
CallbackHandle NativeIdleCallbacks::requestIdleCallback(
jsi::Runtime& runtime,
SyncCallback<void(jsi::Object)>&& userCallback,
std::optional<NativeRequestIdleCallbackOptions> options) {
auto binding = RuntimeSchedulerBinding::getBinding(runtime);
auto runtimeScheduler = binding->getRuntimeScheduler();
// handle timeout parameter
std::optional<HighResDuration> timeout;
std::optional<HighResTimeStamp> expirationTime;
if (options.has_value() && options.value().timeout.has_value()) {
HighResDuration userTimeout = options.value().timeout.value();
if (userTimeout > HighResDuration::zero()) {
expirationTime = runtimeScheduler->now() + userTimeout;
}
}
auto userCallbackShared = std::make_shared<SyncCallback<void(jsi::Object)>>(
std::move(userCallback));
auto wrappedCallback = [runtimeScheduler, expirationTime, userCallbackShared](
jsi::Runtime& runtime) -> void {
// This implementation gives each idle callback a 50ms deadline, instead of
// being shared by all idle callbacks. This is ok because we don't really
// have idle periods, and if a higher priority task comes in while we're
// executing an idle callback, we don't execute any more idle callbacks and
// we interrupt the current one. The general outcome should be the same.
auto executionStartTime = runtimeScheduler->now();
auto deadline = executionStartTime + HighResDuration::fromMilliseconds(50);
auto didTimeout = expirationTime.has_value()
? executionStartTime > expirationTime
: false;
jsi::Object idleDeadline{runtime};
idleDeadline.setProperty(runtime, "didTimeout", didTimeout);
idleDeadline.setProperty(
runtime,
"timeRemaining",
makeTimeRemainingFunction(runtime, runtimeScheduler, deadline));
userCallbackShared->call(std::move(idleDeadline));
};
std::shared_ptr<Task> task;
if (timeout.has_value()) {
task = runtimeScheduler->scheduleIdleTask(
std::move(wrappedCallback), timeout.value());
} else {
task = runtimeScheduler->scheduleIdleTask(std::move(wrappedCallback));
}
if (task == nullptr) {
throw jsi::JSError(
runtime,
"requestIdleCallback is not supported in legacy runtime scheduler");
}
jsi::Object taskHandle{runtime};
auto taskNativeState = std::make_shared<IdleTaskRef>(task);
taskHandle.setNativeState(runtime, std::move(taskNativeState));
return taskHandle;
}
void NativeIdleCallbacks::cancelIdleCallback(
jsi::Runtime& runtime,
jsi::Object handle) {
auto binding = RuntimeSchedulerBinding::getBinding(runtime);
auto runtimeScheduler = binding->getRuntimeScheduler();
if (!handle.hasNativeState(runtime)) {
return;
}
auto taskHandle =
std::dynamic_pointer_cast<IdleTaskRef>(handle.getNativeState(runtime));
if (!taskHandle) {
return;
}
runtimeScheduler->cancelTask(*taskHandle->task);
}
} // namespace facebook::react

View File

@@ -0,0 +1,37 @@
/*
* 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
#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
#include "FBReactNativeSpecJSI.h"
#else
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#endif
namespace facebook::react {
using CallbackHandle = jsi::Object;
using NativeRequestIdleCallbackOptions = NativeIdleCallbacksRequestIdleCallbackOptions<std::optional<HighResDuration>>;
template <>
struct Bridging<NativeRequestIdleCallbackOptions>
: NativeIdleCallbacksRequestIdleCallbackOptionsBridging<NativeRequestIdleCallbackOptions> {};
class NativeIdleCallbacks : public NativeIdleCallbacksCxxSpec<NativeIdleCallbacks> {
public:
NativeIdleCallbacks(std::shared_ptr<CallInvoker> jsInvoker);
CallbackHandle requestIdleCallback(
jsi::Runtime &runtime,
SyncCallback<void(jsi::Object)> &&callback,
std::optional<NativeRequestIdleCallbackOptions> options);
void cancelIdleCallback(jsi::Runtime &runtime, jsi::Object handle);
};
} // namespace facebook::react

View File

@@ -0,0 +1,55 @@
# 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
header_search_paths = []
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the module access its own files
end
Pod::Spec.new do |s|
s.name = "React-idlecallbacksnativemodule"
s.version = version
s.summary = "React Native idle callbacks native module"
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.header_dir = "react/nativemodule/idlecallbacks"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"OTHER_CFLAGS" => "$(inherited)",
"DEFINES_MODULE" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "idlecallbacksnativemodule")
s.dependency "React-jsi"
s.dependency "React-jsiexecutor"
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.dependency "ReactCommon/turbomodule/core"
s.dependency "React-runtimescheduler"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
end

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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_nativemodule_intersectionobserver_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_intersectionobserver OBJECT ${react_nativemodule_intersectionobserver_SRC})
target_include_directories(react_nativemodule_intersectionobserver PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_nativemodule_intersectionobserver
react_codegen_rncore
react_cxxreact
react_renderer_bridging
react_renderer_core
react_renderer_graphics
react_renderer_observers_intersection
react_renderer_runtimescheduler
react_renderer_uimanager
rrc_view
)
target_compile_reactnative_options(react_nativemodule_intersectionobserver PRIVATE)
target_compile_options(react_nativemodule_intersectionobserver PRIVATE -Wpedantic -Wno-deprecated-declarations)

View File

@@ -0,0 +1,162 @@
/*
* 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 "NativeIntersectionObserver.h"
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
#include <react/renderer/uimanager/UIManagerBinding.h>
#include <react/renderer/uimanager/primitives.h>
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
#include "Plugins.h"
#endif
std::shared_ptr<facebook::react::TurboModule>
NativeIntersectionObserverModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::NativeIntersectionObserver>(
std::move(jsInvoker));
}
namespace facebook::react {
namespace {
jsi::Object tokenFromShadowNodeFamily(
jsi::Runtime& runtime,
ShadowNodeFamily::Shared shadowNodeFamily) {
jsi::Object obj(runtime);
// Need to const_cast since JSI only allows non-const pointees
obj.setNativeState(
runtime,
std::const_pointer_cast<ShadowNodeFamily>(std::move(shadowNodeFamily)));
return obj;
}
ShadowNodeFamily::Shared shadowNodeFamilyFromToken(
jsi::Runtime& runtime,
jsi::Object token) {
return token.getNativeState<ShadowNodeFamily>(runtime);
}
} // namespace
NativeIntersectionObserver::NativeIntersectionObserver(
std::shared_ptr<CallInvoker> jsInvoker)
: NativeIntersectionObserverCxxSpec(std::move(jsInvoker)) {}
jsi::Object NativeIntersectionObserver::observeV2(
jsi::Runtime& runtime,
NativeIntersectionObserverObserveOptions options) {
auto intersectionObserverId = options.intersectionObserverId;
auto shadowNode = options.targetShadowNode;
auto shadowNodeFamily = shadowNode->getFamilyShared();
std::optional<ShadowNodeFamily::Shared> observationRootShadowNodeFamily;
if (options.rootShadowNode.has_value()) {
observationRootShadowNodeFamily =
options.rootShadowNode.value()->getFamilyShared();
}
auto thresholds = options.thresholds;
auto rootThresholds = options.rootThresholds;
auto rootMargin = options.rootMargin;
auto& uiManager = getUIManagerFromRuntime(runtime);
intersectionObserverManager_.observe(
intersectionObserverId,
observationRootShadowNodeFamily,
shadowNodeFamily,
thresholds,
rootThresholds,
rootMargin,
uiManager);
return tokenFromShadowNodeFamily(runtime, shadowNodeFamily);
}
void NativeIntersectionObserver::unobserveV2(
jsi::Runtime& runtime,
IntersectionObserverObserverId intersectionObserverId,
jsi::Object targetToken) {
auto shadowNodeFamily =
shadowNodeFamilyFromToken(runtime, std::move(targetToken));
intersectionObserverManager_.unobserve(
intersectionObserverId, shadowNodeFamily);
}
void NativeIntersectionObserver::connect(
jsi::Runtime& runtime,
AsyncCallback<> notifyIntersectionObserversCallback) {
auto& uiManager = getUIManagerFromRuntime(runtime);
intersectionObserverManager_.connect(
*RuntimeSchedulerBinding::getBinding(runtime)->getRuntimeScheduler(),
uiManager,
std::move(notifyIntersectionObserversCallback));
}
void NativeIntersectionObserver::disconnect(jsi::Runtime& runtime) {
auto& uiManager = getUIManagerFromRuntime(runtime);
intersectionObserverManager_.disconnect(
*RuntimeSchedulerBinding::getBinding(runtime)->getRuntimeScheduler(),
uiManager);
}
std::vector<NativeIntersectionObserverEntry>
NativeIntersectionObserver::takeRecords(jsi::Runtime& runtime) {
auto entries = intersectionObserverManager_.takeRecords();
std::vector<NativeIntersectionObserverEntry> nativeModuleEntries;
nativeModuleEntries.reserve(entries.size());
for (const auto& entry : entries) {
nativeModuleEntries.emplace_back(
convertToNativeModuleEntry(entry, runtime));
}
return nativeModuleEntries;
}
NativeIntersectionObserverEntry
NativeIntersectionObserver::convertToNativeModuleEntry(
const IntersectionObserverEntry& entry,
jsi::Runtime& runtime) {
RectAsTuple targetRect = {
entry.targetRect.origin.x,
entry.targetRect.origin.y,
entry.targetRect.size.width,
entry.targetRect.size.height};
RectAsTuple rootRect = {
entry.rootRect.origin.x,
entry.rootRect.origin.y,
entry.rootRect.size.width,
entry.rootRect.size.height};
RectAsTuple intersectionRect = {
entry.intersectionRect.origin.x,
entry.intersectionRect.origin.y,
entry.intersectionRect.size.width,
entry.intersectionRect.size.height};
NativeIntersectionObserverEntry nativeModuleEntry = {
entry.intersectionObserverId,
(*entry.shadowNodeFamily).getInstanceHandle(runtime),
targetRect,
rootRect,
intersectionRect,
entry.isIntersectingAboveThresholds,
entry.time,
};
return nativeModuleEntry;
}
UIManager& NativeIntersectionObserver::getUIManagerFromRuntime(
jsi::Runtime& runtime) {
return UIManagerBinding::getBinding(runtime)->getUIManager();
}
} // namespace facebook::react

View File

@@ -0,0 +1,89 @@
/*
* 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
#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
#include "FBReactNativeSpecJSI.h"
#else
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#endif
#include <react/renderer/bridging/bridging.h>
#include <react/renderer/observers/intersection/IntersectionObserverManager.h>
#include <optional>
#include <tuple>
#include <vector>
namespace facebook::react {
using NativeIntersectionObserverIntersectionObserverId = int32_t;
using RectAsTuple = std::tuple<Float, Float, Float, Float>;
using NativeIntersectionObserverObserveOptions = NativeIntersectionObserverNativeIntersectionObserverObserveOptions<
// intersectionObserverId
NativeIntersectionObserverIntersectionObserverId,
// rootShadowNode
std::optional<std::shared_ptr<const ShadowNode>>,
// targetShadowNode
std::shared_ptr<const ShadowNode>,
// thresholds
std::vector<Float>,
// rootThresholds
std::optional<std::vector<Float>>,
// rootMargin
std::optional<std::string>>;
template <>
struct Bridging<NativeIntersectionObserverObserveOptions>
: NativeIntersectionObserverNativeIntersectionObserverObserveOptionsBridging<
NativeIntersectionObserverObserveOptions> {};
using NativeIntersectionObserverEntry = NativeIntersectionObserverNativeIntersectionObserverEntry<
// intersectionObserverId
NativeIntersectionObserverIntersectionObserverId,
// targetInstanceHandle
jsi::Value,
// targetRect
RectAsTuple,
// rootRect
RectAsTuple,
// intersectionRect
RectAsTuple,
// isIntersectingAboveThresholds
bool,
// time
HighResTimeStamp>;
template <>
struct Bridging<NativeIntersectionObserverEntry>
: NativeIntersectionObserverNativeIntersectionObserverEntryBridging<NativeIntersectionObserverEntry> {};
class NativeIntersectionObserver : public NativeIntersectionObserverCxxSpec<NativeIntersectionObserver> {
public:
NativeIntersectionObserver(std::shared_ptr<CallInvoker> jsInvoker);
jsi::Object observeV2(jsi::Runtime &runtime, NativeIntersectionObserverObserveOptions options);
void
unobserveV2(jsi::Runtime &runtime, IntersectionObserverObserverId intersectionObserverId, jsi::Object targetToken);
void connect(jsi::Runtime &runtime, AsyncCallback<> notifyIntersectionObserversCallback);
void disconnect(jsi::Runtime &runtime);
std::vector<NativeIntersectionObserverEntry> takeRecords(jsi::Runtime &runtime);
private:
IntersectionObserverManager intersectionObserverManager_{};
static UIManager &getUIManagerFromRuntime(jsi::Runtime &runtime);
static NativeIntersectionObserverEntry convertToNativeModuleEntry(
const IntersectionObserverEntry &entry,
jsi::Runtime &runtime);
};
} // namespace facebook::react

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.
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 we're 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
header_search_paths = [
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
]
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the module access its own files
end
Pod::Spec.new do |s|
s.name = "React-intersectionobservernativemodule"
s.version = version
s.summary = "React Native intersection observer native module"
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.header_dir = "react/nativemodule/intersectionobserver"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"OTHER_CFLAGS" => "$(inherited)",
"DEFINES_MODULE" => "YES" }
if ENV['USE_FRAMEWORKS']
s.module_name = "intersectionobservernativemodule"
s.header_mappings_dir = "../.."
end
s.dependency "Yoga"
s.dependency "React-jsi"
s.dependency "React-jsiexecutor"
s.dependency "React-cxxreact"
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.dependency "ReactCommon/turbomodule/core"
s.dependency "React-Fabric"
s.dependency "React-Fabric/bridging"
s.dependency "React-runtimescheduler"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])
end

View File

@@ -0,0 +1,21 @@
# 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_nativemodule_microtasks_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_microtasks OBJECT ${react_nativemodule_microtasks_SRC})
target_include_directories(react_nativemodule_microtasks PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_nativemodule_microtasks
react_codegen_rncore
react_cxxreact
)
target_compile_reactnative_options(react_nativemodule_microtasks PRIVATE)
target_compile_options(react_nativemodule_microtasks PRIVATE -Wpedantic)

View File

@@ -0,0 +1,31 @@
/*
* 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 "NativeMicrotasks.h"
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
#include "Plugins.h"
#endif
std::shared_ptr<facebook::react::TurboModule> NativeMicrotasksModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::NativeMicrotasks>(
std::move(jsInvoker));
}
namespace facebook::react {
NativeMicrotasks::NativeMicrotasks(std::shared_ptr<CallInvoker> jsInvoker)
: NativeMicrotasksCxxSpec(std::move(jsInvoker)) {}
void NativeMicrotasks::queueMicrotask(
jsi::Runtime& runtime,
jsi::Function callback) {
runtime.queueMicrotask(callback);
}
} // namespace facebook::react

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
#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
#include "FBReactNativeSpecJSI.h"
#else
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#endif
namespace facebook::react {
class NativeMicrotasks : public NativeMicrotasksCxxSpec<NativeMicrotasks> {
public:
NativeMicrotasks(std::shared_ptr<CallInvoker> jsInvoker);
void queueMicrotask(jsi::Runtime &runtime, jsi::Function callback);
};
} // namespace facebook::react

View File

@@ -0,0 +1,52 @@
# 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
header_search_paths = []
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the microtasks module access its own files
end
Pod::Spec.new do |s|
s.name = "React-microtasksnativemodule"
s.version = version
s.summary = "React Native microtasks native module"
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.header_dir = "react/nativemodule/microtasks"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"OTHER_CFLAGS" => "$(inherited)",
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"DEFINES_MODULE" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_microtasksnativemodule")
s.dependency "React-jsi"
s.dependency "React-jsiexecutor"
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.dependency "ReactCommon/turbomodule/core"
add_dependency(s, "React-RCTFBReactNativeSpec")
end

View File

@@ -0,0 +1,27 @@
# 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_nativemodule_mutationobserver_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_mutationobserver OBJECT ${react_nativemodule_mutationobserver_SRC})
target_include_directories(react_nativemodule_mutationobserver PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_nativemodule_mutationobserver
react_codegen_rncore
react_renderer_bridging
react_renderer_core
react_renderer_uimanager
react_featureflags
react_renderer_observers_mutation
react_cxxreact
rrc_view
)
target_compile_reactnative_options(react_nativemodule_mutationobserver PRIVATE)
target_compile_options(react_nativemodule_mutationobserver PRIVATE -Wpedantic)

View File

@@ -0,0 +1,159 @@
/*
* 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 "NativeMutationObserver.h"
#include <cxxreact/TraceSection.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/uimanager/UIManagerBinding.h>
#include <react/renderer/uimanager/primitives.h>
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
#include "Plugins.h"
#endif
std::shared_ptr<facebook::react::TurboModule>
NativeMutationObserverModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::NativeMutationObserver>(
std::move(jsInvoker));
}
namespace facebook::react {
static UIManager& getUIManagerFromRuntime(jsi::Runtime& runtime) {
return UIManagerBinding::getBinding(runtime)->getUIManager();
}
NativeMutationObserver::NativeMutationObserver(
std::shared_ptr<CallInvoker> jsInvoker)
: NativeMutationObserverCxxSpec(std::move(jsInvoker)) {}
void NativeMutationObserver::observe(
jsi::Runtime& runtime,
const NativeMutationObserverObserveOptions& options) {
auto mutationObserverId = options.mutationObserverId;
auto subtree = options.subtree;
auto shadowNode = options.targetShadowNode;
auto& uiManager = getUIManagerFromRuntime(runtime);
mutationObserverManager_.observe(
mutationObserverId, shadowNode, subtree, uiManager);
}
void NativeMutationObserver::unobserveAll(
jsi::Runtime& /*runtime*/,
MutationObserverId mutationObserverId) {
mutationObserverManager_.unobserveAll(mutationObserverId);
}
void NativeMutationObserver::connect(
jsi::Runtime& runtime,
jsi::Function notifyMutationObservers,
SyncCallback<jsi::Value(jsi::Value)> getPublicInstanceFromInstanceHandle) {
auto& uiManager = getUIManagerFromRuntime(runtime);
runtime_ = &runtime;
notifyMutationObservers_.emplace(std::move(notifyMutationObservers));
getPublicInstanceFromInstanceHandle_.emplace(
std::move(getPublicInstanceFromInstanceHandle));
auto onMutationsCallback = [&](std::vector<MutationRecord>& records) {
return onMutations(records);
};
mutationObserverManager_.connect(uiManager, std::move(onMutationsCallback));
}
void NativeMutationObserver::disconnect(jsi::Runtime& runtime) {
auto& uiManager = getUIManagerFromRuntime(runtime);
mutationObserverManager_.disconnect(uiManager);
runtime_ = nullptr;
notifyMutationObservers_.reset();
getPublicInstanceFromInstanceHandle_.reset();
}
std::vector<NativeMutationRecord> NativeMutationObserver::takeRecords(
jsi::Runtime& /*runtime*/) {
notifiedMutationObservers_ = false;
std::vector<NativeMutationRecord> records;
pendingRecords_.swap(records);
return records;
}
jsi::Value NativeMutationObserver::getPublicInstanceFromShadowNode(
const ShadowNode& shadowNode) const {
auto instanceHandle = shadowNode.getInstanceHandle(*runtime_);
if (!instanceHandle.isObject()) {
return jsi::Value::null();
}
return getPublicInstanceFromInstanceHandle_.value().call(
std::move(instanceHandle));
}
std::vector<jsi::Value>
NativeMutationObserver::getPublicInstancesFromShadowNodes(
const std::vector<std::shared_ptr<const ShadowNode>>& shadowNodes) const {
std::vector<jsi::Value> publicInstances;
publicInstances.reserve(shadowNodes.size());
for (const auto& shadowNode : shadowNodes) {
publicInstances.push_back(getPublicInstanceFromShadowNode(*shadowNode));
}
return publicInstances;
}
void NativeMutationObserver::onMutations(std::vector<MutationRecord>& records) {
TraceSection s("NativeMutationObserver::onMutations");
for (const auto& record : records) {
pendingRecords_.emplace_back(
NativeMutationRecord{
record.mutationObserverId,
// FIXME(T157129303) Instead of assuming we can call into JS from
// here, we should use an API that explicitly indicates it.
getPublicInstanceFromShadowNode(*record.targetShadowNode),
getPublicInstancesFromShadowNodes(record.addedShadowNodes),
getPublicInstancesFromShadowNodes(record.removedShadowNodes)});
}
notifyMutationObserversIfNecessary();
}
/**
* This method allows us to avoid scheduling multiple calls to notify observers
* in the JS thread. We schedule one and skip subsequent ones (we just append
* the records to the pending list and wait for the scheduled task to consume
* all of them).
*/
void NativeMutationObserver::notifyMutationObserversIfNecessary() {
bool dispatchNotification = false;
if (!pendingRecords_.empty() && !notifiedMutationObservers_) {
notifiedMutationObservers_ = true;
dispatchNotification = true;
}
if (dispatchNotification) {
TraceSection s("NativeMutationObserver::notifyObservers");
if (ReactNativeFeatureFlags::enableBridgelessArchitecture()) {
runtime_->queueMicrotask(notifyMutationObservers_.value());
} else {
jsInvoker_->invokeAsync([&](jsi::Runtime& runtime) {
// It's possible that the last observer was disconnected before we could
// dispatch this notification.
if (notifyMutationObservers_) {
notifyMutationObservers_.value().call(runtime);
}
});
}
}
}
} // namespace facebook::react

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
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#include <react/renderer/bridging/bridging.h>
#include <react/renderer/observers/mutation/MutationObserverManager.h>
#include <react/renderer/uimanager/UIManager.h>
#include <vector>
namespace facebook::react {
using NativeMutationObserverObserveOptions = NativeMutationObserverNativeMutationObserverObserveOptions<
// mutationObserverId
MutationObserverId,
// targetShadowNode
std::shared_ptr<const ShadowNode>,
// subtree
bool>;
template <>
struct Bridging<NativeMutationObserverObserveOptions>
: NativeMutationObserverNativeMutationObserverObserveOptionsBridging<NativeMutationObserverObserveOptions> {};
using NativeMutationRecord = NativeMutationObserverNativeMutationRecord<
// mutationObserverId
MutationObserverId,
// target
jsi::Value,
// addedNodes
std::vector<jsi::Value>,
// removedNodes
std::vector<jsi::Value>>;
template <>
struct Bridging<NativeMutationRecord> : NativeMutationObserverNativeMutationRecordBridging<NativeMutationRecord> {};
class NativeMutationObserver : public NativeMutationObserverCxxSpec<NativeMutationObserver> {
public:
NativeMutationObserver(std::shared_ptr<CallInvoker> jsInvoker);
void observe(jsi::Runtime &runtime, const NativeMutationObserverObserveOptions &options);
void unobserveAll(jsi::Runtime &runtime, MutationObserverId mutationObserverId);
void connect(
jsi::Runtime &runtime,
jsi::Function notifyMutationObservers,
SyncCallback<jsi::Value(jsi::Value)> getPublicInstanceFromInstanceHandle);
void disconnect(jsi::Runtime &runtime);
std::vector<NativeMutationRecord> takeRecords(jsi::Runtime &runtime);
private:
MutationObserverManager mutationObserverManager_{};
std::vector<NativeMutationRecord> pendingRecords_;
// We need to keep a reference to the JS runtime so we can schedule the
// notifications as microtasks when mutations occur. This is safe because
// mutations will only happen when executing JavaScript and because this
// native module will never survive the runtime.
jsi::Runtime *runtime_{};
bool notifiedMutationObservers_{};
std::optional<jsi::Function> notifyMutationObservers_;
std::optional<SyncCallback<jsi::Value(jsi::Value)>> getPublicInstanceFromInstanceHandle_;
void onMutations(std::vector<MutationRecord> &records);
void notifyMutationObserversIfNecessary();
jsi::Value getPublicInstanceFromShadowNode(const ShadowNode &shadowNode) const;
std::vector<jsi::Value> getPublicInstancesFromShadowNodes(
const std::vector<std::shared_ptr<const ShadowNode>> &shadowNodes) const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,63 @@
# 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
header_search_paths = [
"\"$(PODS_ROOT)/Headers/Private/React-Core\"",
]
create_header_search_path_for_frameworks("ReactCommon-Samples").each { |search_path| header_search_paths << "\"#{search_path}\""}
Pod::Spec.new do |s|
s.name = "ReactCommon-Samples"
s.module_name = "ReactCommon_Samples"
s.header_dir = "ReactCommon"
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.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => header_search_paths,
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"GCC_WARN_PEDANTIC" => "YES" }
s.framework = "UIKit"
if ENV['USE_FRAMEWORKS']
# Do not use resolve_use_frameworks here - since we're including source files.
# Then it is not needed.
s.header_mappings_dir = './'
end
s.source_files = "ReactCommon/**/*.{cpp,h}",
"platform/ios/**/*.{mm,cpp,h}"
s.dependency "React-Core"
s.dependency "React-cxxreact"
s.dependency "React-jsi"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
add_dependency(s, "React-NativeModulesApple")
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,2 @@
Refer to packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h for a concrete example.
See: https://reactnative.dev/docs/the-new-architecture/pure-cxx-modules

View File

@@ -0,0 +1,23 @@
# 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 sampleturbomodule_SRC CONFIGURE_DEPENDS ReactCommon/*.cpp)
add_library(sampleturbomodule STATIC ${sampleturbomodule_SRC})
target_include_directories(sampleturbomodule PUBLIC .)
target_link_libraries(sampleturbomodule
fbjni
jsi
reactnative
)
target_compile_reactnative_options(sampleturbomodule PRIVATE)
target_compile_options(sampleturbomodule PRIVATE -Wpedantic)

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.
*/
// NOTE: This entire file should be codegen'ed.
package com.facebook.fbreact.specs;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaModule
implements TurboModule {
public static final String NAME = "SampleTurboModule";
public NativeSampleTurboModuleSpec(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public @Nonnull String getName() {
return NAME;
}
protected final void emitOnPress() {
mEventEmitterCallback.invoke("onPress");
}
protected final void emitOnClick(String value) {
mEventEmitterCallback.invoke("onClick", value);
}
protected final void emitOnChange(ReadableMap value) {
mEventEmitterCallback.invoke("onChange", value);
}
protected void emitOnSubmit(ReadableArray value) {
mEventEmitterCallback.invoke("onSubmit", value);
}
protected abstract Map<String, Object> getTypedExportedConstants();
@Override
public final @Nullable Map<String, Object> getConstants() {
Map<String, Object> constants = getTypedExportedConstants();
if (ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD) {
Set<String> obligatoryFlowConstants =
new HashSet<>(Arrays.asList("const1", "const2", "const3"));
Set<String> optionalFlowConstants = new HashSet<>();
Set<String> undeclaredConstants = new HashSet<>(constants.keySet());
undeclaredConstants.removeAll(obligatoryFlowConstants);
undeclaredConstants.removeAll(optionalFlowConstants);
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(
"Native Module Flow doesn't declare constants: " + undeclaredConstants);
}
undeclaredConstants = obligatoryFlowConstants;
undeclaredConstants.removeAll(constants.keySet());
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(
"Native Module doesn't fill in constants: " + undeclaredConstants);
}
}
return constants;
}
@ReactMethod
public abstract void voidFunc();
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract boolean getBool(boolean arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public double getEnum(double arg) {
return 0;
}
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract double getNumber(double arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract String getString(String arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableArray getArray(ReadableArray arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getObject(ReadableMap arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getUnsafeObject(ReadableMap arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract double getRootTag(double arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getValue(double x, String y, ReadableMap z);
@ReactMethod
public abstract void getValueWithCallback(Callback callback);
@ReactMethod
public abstract void getValueWithPromise(boolean error, Promise promise);
@ReactMethod
public void voidFuncThrows() {}
@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap getObjectThrows(ReadableMap arg) {
return null;
}
@ReactMethod
public void promiseThrows(Promise promise) {}
@ReactMethod
public void voidFuncAssert() {}
@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap getObjectAssert(ReadableMap arg) {
return null;
}
@ReactMethod
public void promiseAssert(Promise promise) {}
@ReactMethod
public void getImageUrl(Promise promise) {}
}

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 <ReactCommon/SampleTurboModuleJSIBindings.h>
namespace facebook::react {
// static
void SampleTurboModuleJSIBindings::registerNatives() {
javaClassLocal()->registerNatives({
makeNativeMethod(
"getBindingsInstaller",
SampleTurboModuleJSIBindings::getBindingsInstaller),
});
}
// static
jni::local_ref<BindingsInstallerHolder::javaobject>
SampleTurboModuleJSIBindings::getBindingsInstaller(
jni::alias_ref<SampleTurboModuleJSIBindings> /*jobj*/) {
return BindingsInstallerHolder::newObjectCxxArgs(
[](jsi::Runtime& runtime, const std::shared_ptr<CallInvoker>&) {
runtime.global().setProperty(
runtime, "__SampleTurboModuleJSIBindings", "Hello JSI!");
});
}
} // namespace facebook::react

View File

@@ -0,0 +1,30 @@
/*
* 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 <ReactCommon/BindingsInstallerHolder.h>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
namespace facebook::react {
class SampleTurboModuleJSIBindings : public jni::JavaClass<SampleTurboModuleJSIBindings> {
public:
static constexpr const char *kJavaDescriptor = "Lcom/facebook/fbreact/specs/SampleTurboModule;";
SampleTurboModuleJSIBindings() = default;
static void registerNatives();
private:
// Using static function as a simple demonstration
static jni::local_ref<BindingsInstallerHolder::javaobject> getBindingsInstaller(
jni::alias_ref<SampleTurboModuleJSIBindings> jobj);
};
} // namespace facebook::react

View File

@@ -0,0 +1,416 @@
/*
* 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.
*/
// NOTE: This entire file should be codegen'ed.
#include <ReactCommon/SampleTurboModuleSpec.h>
namespace facebook::react {
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getConstants(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getConstants",
"()Ljava/util/Map;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, VoidKind, "voidFunc", "()V", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getBool(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, BooleanKind, "getBool", "(Z)Z", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getEnum(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, NumberKind, "getEnum", "(D)D", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getNumber(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, NumberKind, "getNumber", "(D)D", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getString(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
StringKind,
"getString",
"(Ljava/lang/String;)Ljava/lang/String;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getArray(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ArrayKind,
"getArray",
"(Lcom/facebook/react/bridge/ReadableArray;)Lcom/facebook/react/bridge/WritableArray;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getObject(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getObject",
"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getUnsafeObject(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getUnsafeObject",
"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, NumberKind, "getRootTag", "(D)D", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getValue(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getValue",
"(DLjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
VoidKind,
"getValueWithCallback",
"(Lcom/facebook/react/bridge/Callback;)V",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
PromiseKind,
"getValueWithPromise",
"(ZLcom/facebook/react/bridge/Promise;)V",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncThrows(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, VoidKind, "voidFuncThrows", "()V", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getObjectThrows(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getObjectThrows",
"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_promiseThrows(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
PromiseKind,
"promiseThrows",
"(Lcom/facebook/react/bridge/Promise;)V",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, VoidKind, "voidFuncAssert", "()V", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getObjectAssert",
"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
PromiseKind,
"promiseAssert",
"(Lcom/facebook/react/bridge/Promise;)V",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getImageUrl(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
PromiseKind,
"getImageUrl",
"(Lcom/facebook/react/bridge/Promise;)V",
args,
count,
cachedMethodId);
}
NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(
const JavaTurboModule::InitParams& params)
: JavaTurboModule(params) {
methodMap_["getConstants"] = MethodMetadata{
.argCount = 0,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants};
methodMap_["voidFunc"] = MethodMetadata{
.argCount = 0,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc};
methodMap_["getBool"] = MethodMetadata{
.argCount = 1,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getBool};
methodMap_["getEnum"] = MethodMetadata{
.argCount = 1,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getEnum};
methodMap_["getNumber"] = MethodMetadata{
.argCount = 1,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getNumber};
methodMap_["getString"] = MethodMetadata{
.argCount = 1,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getString};
methodMap_["getArray"] = MethodMetadata{
.argCount = 1,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getArray};
methodMap_["getObject"] = MethodMetadata{
.argCount = 1,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getObject};
methodMap_["getUnsafeObject"] = MethodMetadata{
.argCount = 1,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getUnsafeObject};
methodMap_["getRootTag"] = MethodMetadata{
.argCount = 1,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag};
methodMap_["getValue"] = MethodMetadata{
.argCount = 3,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getValue};
methodMap_["getValueWithCallback"] = MethodMetadata{
.argCount = 1,
.invoker =
__hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback};
methodMap_["getValueWithPromise"] = MethodMetadata{
.argCount = 1,
.invoker =
__hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise};
methodMap_["voidFuncThrows"] = MethodMetadata{
.argCount = 0,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncThrows};
methodMap_["getObjectThrows"] = MethodMetadata{
.argCount = 1,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectThrows};
methodMap_["promiseThrows"] = MethodMetadata{
.argCount = 0,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_promiseThrows};
methodMap_["voidFuncAssert"] = MethodMetadata{
.argCount = 0,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert};
methodMap_["getObjectAssert"] = MethodMetadata{
.argCount = 1,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert};
methodMap_["promiseAssert"] = MethodMetadata{
.argCount = 0,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert};
methodMap_["getImageUrl"] = MethodMetadata{
.argCount = 0,
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getImageUrl};
eventEmitterMap_["onPress"] =
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
eventEmitterMap_["onClick"] =
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
eventEmitterMap_["onChange"] =
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
eventEmitterMap_["onSubmit"] =
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
configureEventEmitterCallback();
}
std::shared_ptr<TurboModule> SampleTurboModuleSpec_ModuleProvider(
const std::string& moduleName,
const JavaTurboModule::InitParams& params) {
if (moduleName == "SampleTurboModule") {
return std::make_shared<NativeSampleTurboModuleSpecJSI>(params);
}
return nullptr;
}
} // namespace facebook::react

View File

@@ -0,0 +1,31 @@
/*
* 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.
*/
// NOTE: This entire file should be codegen'ed.
#pragma once
#include <ReactCommon/JavaTurboModule.h>
#include <ReactCommon/TurboModule.h>
#include <fbjni/fbjni.h>
namespace facebook::react {
/**
* JNI C++ class for module 'NativeSampleTurboModule'
*/
class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public JavaTurboModule {
public:
NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams &params);
};
JSI_EXPORT
std::shared_ptr<TurboModule> SampleTurboModuleSpec_ModuleProvider(
const std::string &moduleName,
const JavaTurboModule::InitParams &params);
} // namespace facebook::react

View File

@@ -0,0 +1,281 @@
/*
* 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.
*/
package com.facebook.fbreact.specs
import android.widget.Toast
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.Dynamic
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.ReadableType
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeArray
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.module.annotations.ReactModule
@ReactModule(name = SampleLegacyModule.NAME)
public class SampleLegacyModule(private val context: ReactApplicationContext) :
ReactContextBaseJavaModule(context) {
private var toast: Toast? = null
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getBool(arg: Boolean?): Boolean? {
log("getBool", arg, arg)
return arg
}
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getEnum(arg: Double?): Double? {
log("getEnum", arg, arg)
return arg
}
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getDouble(arg: Double?): Double? {
log("getDouble", arg, arg)
return arg
}
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getInt(arg: Int?): Int? {
log("getInt", arg, arg)
return arg
}
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getFloat(arg: Float?): Float? {
log("getFloat", arg, arg)
return arg
}
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getObjectDouble(arg: Double?): Double? {
log("getObjectDouble", arg, arg)
return arg
}
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getObjectInteger(arg: Int?): Int? {
log("getObjectInteger", arg, arg)
return arg
}
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getObjectFloat(arg: Float?): Float? {
log("getObjectFloat", arg, arg)
return arg
}
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getString(arg: String?): String? {
log("getString", arg, arg)
return arg
}
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getRootTag(arg: Double?): Double? {
log("getRootTag", arg, arg)
return arg
}
@DoNotStrip
@Suppress("unused")
@ReactMethod
public fun voidFunc() {
log("voidFunc", "<void>", "<void>")
return
}
// This function returns {@link WritableMap} instead of {@link Map} for backward compat with
// existing native modules that use this Writable* as return types or in events. {@link
// WritableMap} is modified in the Java side, and read (or consumed) on the C++ side.
// In the future, all native modules should ideally return an immutable Map
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getObject(arg: ReadableMap?): WritableMap {
val map = WritableNativeMap()
arg?.let { map.merge(it) }
log("getObject", arg, map)
return map
}
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getUnsafeObject(arg: ReadableMap?): WritableMap {
val map = WritableNativeMap()
arg?.let { map.merge(it) }
log("getUnsafeObject", arg, map)
return map
}
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getDynamic(dynamic: Dynamic?): WritableMap {
val resultMap = WritableNativeMap()
when (dynamic?.type) {
ReadableType.Null -> {
log("getDynamic as Null", dynamic, dynamic)
resultMap.putString("type", "Null")
resultMap.putNull("value")
}
ReadableType.Boolean -> {
val result = dynamic.asBoolean()
log("getDynamic as Boolean", dynamic, result)
resultMap.putString("type", "Boolean")
resultMap.putBoolean("value", result)
}
ReadableType.Number -> {
val result = dynamic.asInt()
log("getDynamic as Number", dynamic, result)
resultMap.putString("type", "Number")
resultMap.putInt("value", result)
}
ReadableType.String -> {
val result = dynamic.asString()
log("getDynamic as String", dynamic, result)
resultMap.putString("type", "String")
resultMap.putString("value", result)
}
ReadableType.Array -> {
val result = dynamic.asArray()
log("getDynamic as Array", dynamic, result)
resultMap.putString("type", "Array")
resultMap.putArray("value", result)
}
ReadableType.Map -> {
val result = dynamic.asMap()
log("getDynamic as Map", dynamic, result)
resultMap.putString("type", "Map")
resultMap.putMap("value", result)
}
else -> error("Unsupported dynamic type")
}
return resultMap
}
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getValue(numberArg: Double?, stringArg: String?, mapArg: ReadableMap?): WritableMap {
val map: WritableMap =
WritableNativeMap().apply {
putDouble("x", numberArg ?: 0.0)
putString("y", stringArg)
}
val zMap: WritableMap = WritableNativeMap()
mapArg?.let { zMap.merge(it) }
map.putMap("z", zMap)
log(
"getValue",
mapOf("1-numberArg" to numberArg, "2-stringArg" to stringArg, "3-mapArg" to mapArg),
map,
)
return map
}
@DoNotStrip
@Suppress("unused")
@ReactMethod
public fun getValueWithCallback(callback: Callback?) {
val result = "Value From Callback"
log("Callback", "Return Time", result)
callback?.invoke(result)
}
@DoNotStrip
@Suppress("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public fun getArray(arg: ReadableArray?): WritableArray {
if (arg == null || Arguments.toList(arg) == null) {
// Returning an empty array, since the super class always returns non-null
return WritableNativeArray()
}
val result: WritableArray = Arguments.makeNativeArray(Arguments.toList(arg))
log("getArray", arg, result)
return result
}
@DoNotStrip
@Suppress("unused")
@ReactMethod
public fun getValueWithPromise(error: Boolean, promise: Promise?) {
if (error) {
promise?.reject(
"code 1",
"intentional promise rejection",
Throwable("promise intentionally rejected"),
)
} else {
promise?.resolve("result")
}
}
override fun getConstants(): Map<String, Any> {
val result: MutableMap<String, Any> = mutableMapOf()
val activity = context.currentActivity
if (activity != null) {
result["const2"] = 390
}
result["const1"] = true
result["const3"] = "something"
log("constantsToExport", "", result)
return result
}
private fun log(method: String, input: Any?, output: Any?) {
toast?.cancel()
val message = StringBuilder("Method :")
message
.append(method)
.append("\nInputs: ")
.append(input.toString())
.append("\nOutputs: ")
.append(output.toString())
toast = Toast.makeText(context, message.toString(), Toast.LENGTH_LONG)
toast?.show()
}
override fun invalidate(): Unit = Unit
override fun getName(): String {
return NAME
}
public companion object {
public const val NAME: String = "SampleLegacyModule"
}
}

View File

@@ -0,0 +1,276 @@
/*
* 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.
*/
package com.facebook.fbreact.specs
import android.net.Uri
import android.os.Build
import android.util.DisplayMetrics
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeArray
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder
import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings
import java.util.UUID
@DoNotStrip
@ReactModule(name = SampleTurboModule.NAME)
public class SampleTurboModule(private val context: ReactApplicationContext) :
NativeSampleTurboModuleSpec(context), TurboModuleWithJSIBindings {
private var toast: Toast? = null
@DoNotStrip
override fun getBool(arg: Boolean): Boolean {
log("getBool", arg, arg)
return arg
}
@DoNotStrip
override fun getEnum(arg: Double): Double {
log("getEnum", arg, arg)
return arg
}
override fun getTypedExportedConstants(): MutableMap<String, Any> {
val result: MutableMap<String, Any> = mutableMapOf()
val activity = context.currentActivity
if (activity != null) {
@Suppress("DEPRECATION")
val widthPixels =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
activity.windowManager.currentWindowMetrics.bounds.width()
} else {
val displayMetrics = DisplayMetrics()
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
displayMetrics.widthPixels
}
result["const2"] = widthPixels
}
result["const1"] = true
result["const3"] = "something"
log("constantsToExport", "", result)
return result
}
@DoNotStrip
override fun getNumber(arg: Double): Double {
log("getNumber", arg, arg)
return arg
}
@DoNotStrip
override fun getString(arg: String?): String? {
log("getString", arg, arg)
return arg
}
@DoNotStrip
@Suppress("unused")
override fun getRootTag(arg: Double): Double {
log("getRootTag", arg, arg)
return arg
}
@DoNotStrip
override fun voidFunc() {
log("voidFunc", "<void>", "<void>")
emitOnPress()
emitOnClick("click")
run {
val map =
WritableNativeMap().apply {
putInt("a", 1)
putString("b", "two")
}
emitOnChange(map)
}
run {
val array = WritableNativeArray()
val map1 =
WritableNativeMap().apply {
putInt("a", 1)
putString("b", "two")
}
val map2 =
WritableNativeMap().apply {
putInt("a", 3)
putString("b", "four")
}
array.pushMap(map1)
array.pushMap(map2)
emitOnSubmit(array)
}
}
// This function returns {@link WritableMap} instead of {@link Map} for backward compat with
// existing native modules that use this Writable* as return types or in events. {@link
// WritableMap} is modified in the Java side, and read (or consumed) on the C++ side.
// In the future, all native modules should ideally return an immutable Map
@DoNotStrip
@Suppress("unused")
override fun getObject(arg: ReadableMap?): WritableMap {
val map = WritableNativeMap()
arg?.let { map.merge(it) }
log("getObject", arg, map)
return map
}
@DoNotStrip
@Suppress("unused")
override fun getUnsafeObject(arg: ReadableMap?): WritableMap {
val map = WritableNativeMap()
arg?.let { map.merge(it) }
log("getUnsafeObject", arg, map)
return map
}
@DoNotStrip
@Suppress("unused")
override fun getValue(x: Double, y: String?, z: ReadableMap?): WritableMap {
val map: WritableMap = WritableNativeMap()
map.putDouble("x", x)
map.putString("y", y)
val zMap: WritableMap = WritableNativeMap()
z?.let { zMap.merge(it) }
map.putMap("z", zMap)
log("getValue", mapOf("1-numberArg" to x, "2-stringArg" to y, "3-mapArg" to z), map)
return map
}
@DoNotStrip
@Suppress("unused")
override fun getValueWithCallback(callback: Callback?) {
val result = "Value From Callback"
log("Callback", "Return Time", result)
callback?.invoke(result)
}
@DoNotStrip
@Suppress("unused")
override fun getArray(arg: ReadableArray?): WritableArray {
if (arg == null || Arguments.toList(arg) == null) {
// Returning an empty array, since the super class always returns non-null
return WritableNativeArray()
}
val result: WritableArray = Arguments.makeNativeArray(Arguments.toList(arg))
log("getArray", arg, result)
return result
}
@DoNotStrip
@Suppress("unused")
override fun getValueWithPromise(error: Boolean, promise: Promise) {
if (error) {
promise?.reject(
"code 1",
"intentional promise rejection",
Throwable("promise intentionally rejected"),
)
} else {
promise?.resolve("result")
}
}
@DoNotStrip
@Suppress("unused")
override fun voidFuncThrows() {
error("Intentional exception from JVM voidFuncThrows")
}
@DoNotStrip
@Suppress("unused")
override fun getObjectThrows(arg: ReadableMap): WritableMap {
error("Intentional exception from JVM getObjectThrows with $arg")
}
@DoNotStrip
@Suppress("unused")
override fun promiseThrows(promise: Promise) {
error("Intentional exception from JVM promiseThrows")
}
@DoNotStrip
@Suppress("unused")
override fun voidFuncAssert() {
assert(false) { "Intentional assert from JVM voidFuncAssert" }
}
@DoNotStrip
@Suppress("unused")
override fun getObjectAssert(arg: ReadableMap): WritableMap? {
assert(false) { "Intentional assert from JVM getObjectAssert with $arg" }
return null
}
@DoNotStrip
@Suppress("unused")
override fun promiseAssert(promise: Promise) {
assert(false) { "Intentional assert from JVM promiseAssert" }
}
@DoNotStrip
@Suppress("unused")
override fun getImageUrl(promise: Promise) {
val activity = context.getCurrentActivity() as? ComponentActivity
if (activity != null) {
val key = UUID.randomUUID().toString()
activity.activityResultRegistry
.register(
key,
ActivityResultContracts.GetContent(),
{ uri: Uri? ->
if (uri != null) {
promise.resolve(uri.toString())
} else {
promise.resolve(null)
}
},
)
.launch("image/*")
} else {
promise.reject("error", "Unable to obtain an image uri without current activity")
}
}
private fun log(method: String, input: Any?, output: Any?) {
toast?.cancel()
val message = StringBuilder("Method :")
message
.append(method)
.append("\nInputs: ")
.append(input.toString())
.append("\nOutputs: ")
.append(output.toString())
toast = Toast.makeText(context, message.toString(), Toast.LENGTH_LONG)
toast?.show()
}
override fun invalidate(): Unit = Unit
override fun getName(): String {
return NAME
}
@DoNotStrip external override fun getBindingsInstaller(): BindingsInstallerHolder
public companion object {
public const val NAME: String = "SampleTurboModule"
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.
*/
// NOTE: This entire file should be codegen'ed.
#import <vector>
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <ReactCommon/RCTTurboModule.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The ObjC protocol based on the JS Flow type for SampleTurboModule.
*/
@protocol NativeSampleTurboModuleSpec <RCTBridgeModule, RCTTurboModule>
- (void)voidFunc;
- (NSNumber *)getBool:(BOOL)arg;
- (NSNumber *)getEnum:(double)arg;
- (NSNumber *)getNumber:(double)arg;
- (NSString *)getString:(NSString *)arg;
- (NSArray<id<NSObject>> *)getArray:(NSArray *)arg;
- (NSDictionary *)getObject:(NSDictionary *)arg;
- (NSDictionary *)getUnsafeObject:(NSDictionary *)arg;
- (NSNumber *)getRootTag:(double)arg;
- (NSDictionary *)getValue:(double)x y:(NSString *)y z:(NSDictionary *)z;
- (void)getValueWithCallback:(RCTResponseSenderBlock)callback;
- (void)getValueWithPromise:(BOOL)error resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
- (void)voidFuncThrows;
- (NSDictionary *)getObjectThrows:(NSDictionary *)arg;
- (void)promiseThrows:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
- (void)voidFuncAssert;
- (NSDictionary *)getObjectAssert:(NSDictionary *)arg;
- (void)promiseAssert:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
- (NSDictionary *)constantsToExport;
- (NSDictionary *)getConstants;
@end
@interface NativeSampleTurboModuleSpecBase : NSObject {
@protected
facebook::react::EventEmitterCallback _eventEmitterCallback;
}
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *_Nonnull)eventEmitterCallbackWrapper;
- (void)emitOnPress;
- (void)emitOnClick:(NSString *)value;
- (void)emitOnChange:(NSDictionary *)value;
- (void)emitOnSubmit:(NSArray *)value;
@end
namespace facebook::react {
/**
* The iOS TurboModule impl specific to SampleTurboModule.
*/
class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public ObjCTurboModule {
public:
NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams &params);
};
} // namespace facebook::react
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,280 @@
/*
* 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 "RCTNativeSampleTurboModuleSpec.h"
namespace facebook::react {
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "voidFunc", @selector(voidFunc), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getBool(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, BooleanKind, "getBool", @selector(getBool:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getEnum(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, NumberKind, "getEnum", @selector(getEnum:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getNumber(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, NumberKind, "getNumber", @selector(getNumber:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getString(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, StringKind, "getString", @selector(getString:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArray(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ArrayKind, "getArray", @selector(getArray:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getObject(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getObject", @selector(getObject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getUnsafeObject(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getUnsafeObject", @selector(getUnsafeObject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, NumberKind, "getRootTag", @selector(getRootTag:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValue(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getValue", @selector(getValue:y:z:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "getValueWithCallback", @selector(getValueWithCallback:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(
rt, PromiseKind, "getValueWithPromise", @selector(getValueWithPromise:resolve:reject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncThrows(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "voidFuncThrows", @selector(voidFuncThrows), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectThrows(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getObjectThrows", @selector(getObjectThrows:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseThrows(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, PromiseKind, "promiseThrows", @selector(promiseThrows:reject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "voidFuncAssert", @selector(voidFuncAssert), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getObjectAssert", @selector(getObjectAssert:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, PromiseKind, "promiseAssert", @selector(promiseAssert:reject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getConstants", @selector(getConstants), args, count);
}
NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams &params)
: ObjCTurboModule(params)
{
methodMap_["voidFunc"] =
MethodMetadata{.argCount = 0, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc};
methodMap_["getBool"] =
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getBool};
methodMap_["getEnum"] =
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getEnum};
methodMap_["getNumber"] =
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getNumber};
methodMap_["getString"] =
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getString};
methodMap_["getArray"] =
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getArray};
methodMap_["getObject"] =
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getObject};
methodMap_["getUnsafeObject"] =
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getUnsafeObject};
methodMap_["getRootTag"] =
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag};
methodMap_["getValue"] =
MethodMetadata{.argCount = 3, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getValue};
methodMap_["getValueWithCallback"] =
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback};
methodMap_["getValueWithPromise"] =
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise};
methodMap_["voidFuncThrows"] =
MethodMetadata{.argCount = 0, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncThrows};
methodMap_["getObjectThrows"] =
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectThrows};
methodMap_["promiseThrows"] =
MethodMetadata{.argCount = 0, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_promiseThrows};
methodMap_["voidFuncAssert"] =
MethodMetadata{.argCount = 0, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert};
methodMap_["getObjectAssert"] =
MethodMetadata{.argCount = 1, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert};
methodMap_["promiseAssert"] =
MethodMetadata{.argCount = 0, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert};
methodMap_["getConstants"] =
MethodMetadata{.argCount = 0, .invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants};
eventEmitterMap_["onPress"] = std::make_shared<AsyncEventEmitter<id>>();
eventEmitterMap_["onClick"] = std::make_shared<AsyncEventEmitter<id>>();
eventEmitterMap_["onChange"] = std::make_shared<AsyncEventEmitter<id>>();
eventEmitterMap_["onSubmit"] = std::make_shared<AsyncEventEmitter<id>>();
setEventEmitterCallback([&](const std::string &name, id value) {
static_cast<AsyncEventEmitter<id> &>(*eventEmitterMap_[name]).emit(value);
});
}
} // namespace facebook::react
@implementation NativeSampleTurboModuleSpecBase
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *_Nonnull)eventEmitterCallbackWrapper
{
_eventEmitterCallback = std::move(eventEmitterCallbackWrapper->_eventEmitterCallback);
}
- (void)emitOnPress
{
_eventEmitterCallback("onPress", nil);
}
- (void)emitOnClick:(NSString *)value
{
_eventEmitterCallback("onClick", value);
}
- (void)emitOnChange:(NSDictionary *)value
{
_eventEmitterCallback("onChange", value);
}
- (void)emitOnSubmit:(NSArray *)value
{
_eventEmitterCallback("onSubmit", value);
}
@end

View File

@@ -0,0 +1,16 @@
/*
* 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 <UIKit/UIKit.h>
#import <React/RCTAssert.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTInvalidating.h>
#import <React/RCTUtils.h>
@interface RCTSampleLegacyModule : NSObject <RCTBridgeModule, RCTInvalidating>
@end

View File

@@ -0,0 +1,211 @@
/*
* 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 "RCTSampleLegacyModule.h"
@implementation RCTSampleLegacyModule {
RCTBridge *_bridge;
}
// Backward-compatible export
RCT_EXPORT_MODULE()
// Backward-compatible queue configuration
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
// Backward compatible invalidation
- (void)invalidate
{
// Actually do nothing here.
NSLog(@"Invalidating RCTSampleTurboModule...");
}
- (NSDictionary *)getConstants
{
return @{
@"const1" : @YES,
@"const2" : @(390),
@"const3" : @"something",
};
}
// TODO: Remove once fully migrated to TurboModule.
- (NSDictionary *)constantsToExport
{
return [self getConstants];
}
RCT_EXPORT_METHOD(voidFunc)
{
// Nothing to do
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getBool : (BOOL)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getEnum : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getNumber : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getFloat : (float)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getInt : (int)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getLongLong : (int64_t)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getUnsignedLongLong : (uint64_t)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getNSInteger : (NSInteger)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getNSUInteger : (NSUInteger)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSArray<id<NSObject>> *, getArray : (NSArray *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObject : (NSDictionary *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getString : (NSString *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getNSNumber : (nonnull NSNumber *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getUnsafeObject : (NSDictionary *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getRootTag : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getValue : (double)x y : (NSString *)y z : (NSDictionary *)z)
{
return @{
@"x" : @(x),
@"y" : (y != nullptr) ? y : [NSNull null],
@"z" : (z != nullptr) ? z : [NSNull null],
};
}
RCT_EXPORT_METHOD(getValueWithCallback : (RCTResponseSenderBlock)callback)
{
if (callback == nullptr) {
return;
}
callback(@[ @"value from callback!" ]);
}
RCT_EXPORT_METHOD(
getValueWithPromise : (BOOL)error resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
{
if ((resolve == nullptr) || (reject == nullptr)) {
return;
}
if (error) {
reject(
@"code_1",
@"intentional promise rejection",
[NSError errorWithDomain:@"RCTSampleTurboModule" code:1 userInfo:nil]);
} else {
resolve(@"result!");
}
}
RCT_EXPORT_METHOD(voidFuncThrows)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC voidFuncThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectThrows : (NSDictionary *)arg)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC getObjectThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_METHOD(
promiseThrows : (BOOL)error resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC promiseThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_METHOD(voidFuncAssert)
{
RCTAssert(false, @"Intentional assert from ObjC voidFuncAssert");
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectAssert : (NSDictionary *)arg)
{
RCTAssert(false, @"Intentional assert from ObjC getObjectAssert");
return arg;
}
RCT_EXPORT_METHOD(
promiseAssert : (BOOL)error resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
{
RCTAssert(false, @"Intentional assert from ObjC promiseAssert");
}
@end

View File

@@ -0,0 +1,18 @@
/*
* 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 "RCTNativeSampleTurboModuleSpec.h"
/**
* Sample iOS-specific impl of a TurboModule, conforming to the spec protocol.
* This class is also 100% compatible with the NativeModule system.
*/
@interface RCTSampleTurboModule : NativeSampleTurboModuleSpecBase <NativeSampleTurboModuleSpec>
@end

View File

@@ -0,0 +1,214 @@
/*
* 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 "RCTSampleTurboModule.h"
#import "RCTSampleTurboModulePlugin.h"
#import <React/RCTAssert.h>
#import <React/RCTInitializing.h>
#import <React/RCTUtils.h>
#import <ReactCommon/RCTTurboModuleWithJSIBindings.h>
#import <UIKit/UIKit.h>
using namespace facebook::react;
@interface RCTSampleTurboModule () <RCTTurboModuleWithJSIBindings, RCTInitializing>
@end
@implementation RCTSampleTurboModule {
NSDictionary *_constants;
}
// Backward-compatible export
RCT_EXPORT_MODULE()
// Backward-compatible queue configuration
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
- (void)initialize
{
UIScreen *mainScreen = UIScreen.mainScreen;
CGSize screenSize = mainScreen.bounds.size;
_constants = @{
@"const1" : @YES,
@"const2" : @(screenSize.width),
@"const3" : @"something",
};
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<NativeSampleTurboModuleSpecJSI>(params);
}
// Backward compatible invalidation
- (void)invalidate
{
// Actually do nothing here.
NSLog(@"Invalidating RCTSampleTurboModule...");
}
- (NSDictionary *)getConstants
{
return _constants;
}
// TODO: Remove once fully migrated to TurboModule.
- (NSDictionary *)constantsToExport
{
return [self getConstants];
}
#pragma mark - RCTTurboModuleWithJSIBindings
- (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime
callInvoker:(const std::shared_ptr<CallInvoker> &)callinvoker
{
runtime.global().setProperty(runtime, "__SampleTurboModuleJSIBindings", "Hello JSI!");
}
#pragma mark - Spec Methods
RCT_EXPORT_METHOD(voidFunc)
{
// Nothing to do
[self emitOnPress];
[self emitOnClick:@"click"];
[self emitOnChange:@{@"a" : @1, @"b" : @"two"}];
[self emitOnSubmit:@[ @{@"a" : @1, @"b" : @"two"}, @{@"a" : @3, @"b" : @"four"} ]];
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getBool : (BOOL)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getEnum : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getNumber : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getString : (NSString *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSArray<id<NSObject>> *, getArray : (NSArray *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObject : (NSDictionary *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getUnsafeObject : (NSDictionary *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getRootTag : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getValue : (double)x y : (NSString *)y z : (NSDictionary *)z)
{
return @{
@"x" : @(x),
@"y" : (y != nullptr) ? y : [NSNull null],
@"z" : (z != nullptr) ? z : [NSNull null],
};
}
RCT_EXPORT_METHOD(getValueWithCallback : (RCTResponseSenderBlock)callback)
{
if (callback == nullptr) {
return;
}
callback(@[ @"value from callback!" ]);
}
RCT_EXPORT_METHOD(
getValueWithPromise : (BOOL)error resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
{
if ((resolve == nullptr) || (reject == nullptr)) {
return;
}
if (error) {
reject(
@"code_1",
@"intentional promise rejection",
[NSError errorWithDomain:@"RCTSampleTurboModule" code:1 userInfo:nil]);
} else {
resolve(@"result!");
}
}
RCT_EXPORT_METHOD(voidFuncThrows)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC voidFuncThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectThrows : (NSDictionary *)arg)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC getObjectThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_METHOD(promiseThrows : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC promiseThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_METHOD(voidFuncAssert)
{
RCTAssert(false, @"Intentional assert from ObjC voidFuncAssert");
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectAssert : (NSDictionary *)arg)
{
RCTAssert(false, @"Intentional assert from ObjC getObjectAssert");
return arg;
}
RCT_EXPORT_METHOD(promiseAssert : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
{
RCTAssert(false, @"Intentional assert from ObjC promiseAssert");
}
@end
Class _Nonnull RCTSampleTurboModuleCls(void)
{
return RCTSampleTurboModule.class;
}

View File

@@ -0,0 +1,37 @@
/**
* 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 an internal plugin build system
*/
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
// FB Internal: Plugins.h is autogenerated by the build system.
#import "Plugins.h"
#else
// OSS-compatibility layer
#import <Foundation/Foundation.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
#ifdef __cplusplus
extern "C" {
#endif
// RCTTurboModuleManagerDelegate should call this to resolve module classes.
Class _Nonnull RCTSampleTurboModuleCls(void);
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
#endif // RN_DISABLE_OSS_PLUGIN_HEADER

View File

@@ -0,0 +1,21 @@
# 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_nativemodule_webperformance_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_webperformance OBJECT ${react_nativemodule_webperformance_SRC})
target_include_directories(react_nativemodule_webperformance PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_nativemodule_webperformance
react_performance_timeline
react_codegen_rncore
react_cxxreact
)
target_compile_reactnative_options(react_nativemodule_webperformance PRIVATE)
target_compile_options(react_nativemodule_webperformance PRIVATE -Wpedantic -Wno-deprecated-declarations)

View File

@@ -0,0 +1,405 @@
/*
* 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 "NativePerformance.h"
#include <memory>
#include <unordered_map>
#include <variant>
#include <cxxreact/JSExecutor.h>
#include <cxxreact/ReactMarker.h>
#include <jsi/JSIDynamic.h>
#include <jsi/instrumentation.h>
#include <react/performance/timeline/PerformanceEntryReporter.h>
#include <react/performance/timeline/PerformanceObserver.h>
#include "NativePerformance.h"
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
#include "Plugins.h"
#endif
std::shared_ptr<facebook::react::TurboModule> NativePerformanceModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::NativePerformance>(
std::move(jsInvoker));
}
namespace facebook::react {
namespace {
class PerformanceObserverWrapper : public jsi::NativeState {
public:
explicit PerformanceObserverWrapper(
const std::shared_ptr<PerformanceObserver> observer)
: observer(observer) {}
const std::shared_ptr<PerformanceObserver> observer;
};
void sortEntries(std::vector<PerformanceEntry>& entries) {
return std::stable_sort(
entries.begin(), entries.end(), PerformanceEntrySorter{});
}
NativePerformanceEntry toNativePerformanceEntry(const PerformanceEntry& entry) {
auto nativeEntry = std::visit(
[](const auto& entryData) -> NativePerformanceEntry {
return {
.name = entryData.name,
.entryType = entryData.entryType,
.startTime = entryData.startTime,
.duration = entryData.duration,
};
},
entry);
if (std::holds_alternative<PerformanceEventTiming>(entry)) {
auto eventEntry = std::get<PerformanceEventTiming>(entry);
nativeEntry.processingStart = eventEntry.processingStart;
nativeEntry.processingEnd = eventEntry.processingEnd;
nativeEntry.interactionId = eventEntry.interactionId;
}
if (std::holds_alternative<PerformanceResourceTiming>(entry)) {
auto resourceEntry = std::get<PerformanceResourceTiming>(entry);
nativeEntry.fetchStart = resourceEntry.fetchStart;
nativeEntry.requestStart = resourceEntry.requestStart;
nativeEntry.connectStart = resourceEntry.connectStart;
nativeEntry.connectEnd = resourceEntry.connectEnd;
nativeEntry.responseStart = resourceEntry.responseStart;
nativeEntry.responseEnd = resourceEntry.responseEnd;
nativeEntry.responseStatus = resourceEntry.responseStatus;
nativeEntry.contentType = resourceEntry.contentType;
nativeEntry.encodedBodySize = resourceEntry.encodedBodySize;
nativeEntry.decodedBodySize = resourceEntry.decodedBodySize;
}
return nativeEntry;
}
std::vector<NativePerformanceEntry> toNativePerformanceEntries(
std::vector<PerformanceEntry>& entries) {
std::vector<NativePerformanceEntry> result;
result.reserve(entries.size());
for (auto& entry : entries) {
result.emplace_back(toNativePerformanceEntry(entry));
}
return result;
}
const std::array<PerformanceEntryType, 2> ENTRY_TYPES_AVAILABLE_FROM_TIMELINE{
{PerformanceEntryType::MARK, PerformanceEntryType::MEASURE}};
bool isAvailableFromTimeline(PerformanceEntryType entryType) {
return entryType == PerformanceEntryType::MARK ||
entryType == PerformanceEntryType::MEASURE;
}
std::shared_ptr<PerformanceObserver> tryGetObserver(
jsi::Runtime& rt,
jsi::Object& observerObj) {
if (!observerObj.hasNativeState(rt)) {
return nullptr;
}
auto observerWrapper = std::dynamic_pointer_cast<PerformanceObserverWrapper>(
observerObj.getNativeState(rt));
return observerWrapper ? observerWrapper->observer : nullptr;
}
PerformanceEntryReporter::UserTimingDetailProvider getDetailProviderFromEntry(
jsi::Runtime& rt,
jsi::Value& entry) {
return [&rt, &entry]() -> folly::dynamic {
try {
auto detail = entry.asObject(rt).getProperty(rt, "detail");
return jsi::dynamicFromValue(rt, detail);
} catch (jsi::JSIException&) {
return nullptr;
}
};
}
} // namespace
NativePerformance::NativePerformance(std::shared_ptr<CallInvoker> jsInvoker)
: NativePerformanceCxxSpec(std::move(jsInvoker)) {}
HighResTimeStamp NativePerformance::now(jsi::Runtime& /*rt*/) {
// This is not spec-compliant, as this is the duration from system boot to
// now, instead of from app startup to now.
// This should be carefully changed eventually.
return HighResTimeStamp::now();
}
HighResDuration NativePerformance::timeOrigin(jsi::Runtime& /*rt*/) {
// This is not spec-compliant, as this is an approximation from Unix epoch to
// system boot, instead of a precise duration from Unix epoch to app startup.
return HighResTimeStamp::unsafeOriginFromUnixTimeStamp();
}
void NativePerformance::reportMark(
jsi::Runtime& rt,
const std::string& name,
HighResTimeStamp startTime,
jsi::Value entry) {
PerformanceEntryReporter::getInstance()->reportMark(
name, startTime, getDetailProviderFromEntry(rt, entry));
}
void NativePerformance::reportMeasure(
jsi::Runtime& rt,
const std::string& name,
HighResTimeStamp startTime,
HighResDuration duration,
jsi::Value entry) {
PerformanceEntryReporter::getInstance()->reportMeasure(
name, startTime, duration, getDetailProviderFromEntry(rt, entry));
}
std::optional<double> NativePerformance::getMarkTime(
jsi::Runtime& /*rt*/,
const std::string& name) {
auto markTime = PerformanceEntryReporter::getInstance()->getMarkTime(name);
return markTime ? std::optional{(*markTime).toDOMHighResTimeStamp()}
: std::nullopt;
}
void NativePerformance::clearMarks(
jsi::Runtime& /*rt*/,
std::optional<std::string> entryName) {
if (entryName) {
PerformanceEntryReporter::getInstance()->clearEntries(
PerformanceEntryType::MARK, *entryName);
} else {
PerformanceEntryReporter::getInstance()->clearEntries(
PerformanceEntryType::MARK);
}
}
void NativePerformance::clearMeasures(
jsi::Runtime& /*rt*/,
std::optional<std::string> entryName) {
if (entryName) {
PerformanceEntryReporter::getInstance()->clearEntries(
PerformanceEntryType::MEASURE, *entryName);
} else {
PerformanceEntryReporter::getInstance()->clearEntries(
PerformanceEntryType::MEASURE);
}
}
std::vector<NativePerformanceEntry> NativePerformance::getEntries(
jsi::Runtime& /*rt*/) {
std::vector<PerformanceEntry> entries;
for (auto entryType : ENTRY_TYPES_AVAILABLE_FROM_TIMELINE) {
PerformanceEntryReporter::getInstance()->getEntries(entries, entryType);
}
sortEntries(entries);
return toNativePerformanceEntries(entries);
}
std::vector<NativePerformanceEntry> NativePerformance::getEntriesByName(
jsi::Runtime& /*rt*/,
const std::string& entryName,
std::optional<PerformanceEntryType> entryType) {
std::vector<PerformanceEntry> entries;
if (entryType) {
if (isAvailableFromTimeline(*entryType)) {
PerformanceEntryReporter::getInstance()->getEntries(
entries, *entryType, entryName);
}
} else {
for (auto type : ENTRY_TYPES_AVAILABLE_FROM_TIMELINE) {
PerformanceEntryReporter::getInstance()->getEntries(
entries, type, entryName);
}
}
sortEntries(entries);
return toNativePerformanceEntries(entries);
}
std::vector<NativePerformanceEntry> NativePerformance::getEntriesByType(
jsi::Runtime& /*rt*/,
PerformanceEntryType entryType) {
std::vector<PerformanceEntry> entries;
if (isAvailableFromTimeline(entryType)) {
PerformanceEntryReporter::getInstance()->getEntries(entries, entryType);
}
sortEntries(entries);
return toNativePerformanceEntries(entries);
}
std::vector<std::pair<std::string, uint32_t>> NativePerformance::getEventCounts(
jsi::Runtime& /*rt*/) {
const auto& eventCounts =
PerformanceEntryReporter::getInstance()->getEventCounts();
return {eventCounts.begin(), eventCounts.end()};
}
std::unordered_map<std::string, double> NativePerformance::getSimpleMemoryInfo(
jsi::Runtime& rt) {
auto heapInfo = rt.instrumentation().getHeapInfo(false);
std::unordered_map<std::string, double> heapInfoToJs;
for (auto& entry : heapInfo) {
heapInfoToJs[entry.first] = static_cast<double>(entry.second);
}
return heapInfoToJs;
}
std::unordered_map<std::string, double>
NativePerformance::getReactNativeStartupTiming(jsi::Runtime& /*rt*/) {
std::unordered_map<std::string, double> result;
ReactMarker::StartupLogger& startupLogger =
ReactMarker::StartupLogger::getInstance();
if (!std::isnan(startupLogger.getAppStartupStartTime())) {
result["startTime"] = startupLogger.getAppStartupStartTime();
} else if (!std::isnan(startupLogger.getInitReactRuntimeStartTime())) {
result["startTime"] = startupLogger.getInitReactRuntimeStartTime();
}
if (!std::isnan(startupLogger.getInitReactRuntimeStartTime())) {
result["initializeRuntimeStart"] =
startupLogger.getInitReactRuntimeStartTime();
}
if (!std::isnan(startupLogger.getRunJSBundleStartTime())) {
result["executeJavaScriptBundleEntryPointStart"] =
startupLogger.getRunJSBundleStartTime();
}
if (!std::isnan(startupLogger.getAppStartupEndTime())) {
result["endTime"] = startupLogger.getAppStartupEndTime();
}
return result;
}
jsi::Object NativePerformance::createObserver(
jsi::Runtime& rt,
NativePerformancePerformanceObserverCallback callback) {
// The way we dispatch performance observer callbacks is a bit different from
// the spec. The specification requires us to queue a single task that
// dispatches observer callbacks. Instead, we are queuing all callbacks as
// separate tasks in the scheduler.
PerformanceObserverCallback cb = [callback = std::move(callback)]() {
callback.callWithPriority(SchedulerPriority::IdlePriority);
};
auto& registry =
PerformanceEntryReporter::getInstance()->getObserverRegistry();
auto observer = PerformanceObserver::create(registry, std::move(cb));
auto observerWrapper = std::make_shared<PerformanceObserverWrapper>(observer);
jsi::Object observerObj{rt};
observerObj.setNativeState(rt, observerWrapper);
return observerObj;
}
double NativePerformance::getDroppedEntriesCount(
jsi::Runtime& rt,
jsi::Object observerObj) {
auto observer = tryGetObserver(rt, observerObj);
if (!observer) {
return 0;
}
return observer->getDroppedEntriesCount();
}
void NativePerformance::observe(
jsi::Runtime& rt,
jsi::Object observerObj,
NativePerformancePerformanceObserverObserveOptions options) {
auto observer = tryGetObserver(rt, observerObj);
if (!observer) {
return;
}
auto durationThreshold =
options.durationThreshold.value_or(HighResDuration::zero());
// observer of type multiple
if (options.entryTypes.has_value()) {
std::unordered_set<PerformanceEntryType> entryTypes;
auto rawTypes = options.entryTypes.value();
for (auto rawType : rawTypes) {
entryTypes.insert(Bridging<PerformanceEntryType>::fromJs(rt, rawType));
}
observer->observe(entryTypes);
} else { // single
auto buffered = options.buffered.value_or(false);
if (options.type.has_value()) {
observer->observe(
static_cast<PerformanceEntryType>(options.type.value()),
{.buffered = buffered, .durationThreshold = durationThreshold});
}
}
}
void NativePerformance::disconnect(jsi::Runtime& rt, jsi::Object observerObj) {
auto observerWrapper = std::dynamic_pointer_cast<PerformanceObserverWrapper>(
observerObj.getNativeState(rt));
if (!observerWrapper) {
return;
}
auto observer = observerWrapper->observer;
observer->disconnect();
}
std::vector<NativePerformanceEntry> NativePerformance::takeRecords(
jsi::Runtime& rt,
jsi::Object observerObj,
bool sort) {
auto observerWrapper = std::dynamic_pointer_cast<PerformanceObserverWrapper>(
observerObj.getNativeState(rt));
if (!observerWrapper) {
return {};
}
auto observer = observerWrapper->observer;
auto records = observer->takeRecords();
if (sort) {
sortEntries(records);
}
return toNativePerformanceEntries(records);
}
std::vector<PerformanceEntryType>
NativePerformance::getSupportedPerformanceEntryTypes(jsi::Runtime& /*rt*/) {
auto supportedEntryTypes = PerformanceEntryReporter::getSupportedEntryTypes();
return {supportedEntryTypes.begin(), supportedEntryTypes.end()};
}
#pragma mark - Testing
void NativePerformance::clearEventCountsForTesting(jsi::Runtime& /*rt*/) {
PerformanceEntryReporter::getInstance()->clearEventCounts();
}
} // namespace facebook::react

View File

@@ -0,0 +1,177 @@
/*
* 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
#if __has_include("rncoreJSI.h") // Cmake headers on Android
#include "rncoreJSI.h"
#elif __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
#include "FBReactNativeSpecJSI.h"
#else
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#endif
#include <react/performance/timeline/PerformanceEntry.h>
#include <memory>
#include <optional>
#include <string>
namespace facebook::react {
using NativePerformancePerformanceObserverCallback = AsyncCallback<>;
using NativePerformancePerformanceObserverObserveOptions = NativePerformancePerformanceObserverInit<
// entryTypes
std::optional<std::vector<int>>,
// type
std::optional<int>,
// buffered
std::optional<bool>,
// durationThreshold
std::optional<HighResDuration>>;
template <>
struct Bridging<PerformanceEntryType> {
static PerformanceEntryType fromJs(jsi::Runtime & /*rt*/, const jsi::Value &value)
{
return static_cast<PerformanceEntryType>(value.asNumber());
}
static int toJs(jsi::Runtime & /*rt*/, const PerformanceEntryType &value)
{
return static_cast<int>(value);
}
};
// Our Native Module codegen does not support JS type unions, so we use a
// flattened object here as an intermediate format.
struct NativePerformanceEntry {
std::string name;
PerformanceEntryType entryType;
HighResTimeStamp startTime;
HighResDuration duration;
// For PerformanceEventTiming only
std::optional<HighResTimeStamp> processingStart;
std::optional<HighResTimeStamp> processingEnd;
std::optional<PerformanceEntryInteractionId> interactionId;
// For PerformanceResourceTiming only
std::optional<HighResTimeStamp> fetchStart;
std::optional<HighResTimeStamp> requestStart;
std::optional<HighResTimeStamp> connectStart;
std::optional<HighResTimeStamp> connectEnd;
std::optional<HighResTimeStamp> responseStart;
std::optional<HighResTimeStamp> responseEnd;
std::optional<int> responseStatus;
std::optional<std::string> contentType;
std::optional<int> encodedBodySize;
std::optional<int> decodedBodySize;
};
template <>
struct Bridging<NativePerformanceEntry> : NativePerformanceRawPerformanceEntryBridging<NativePerformanceEntry> {};
template <>
struct Bridging<NativePerformancePerformanceObserverObserveOptions>
: NativePerformancePerformanceObserverInitBridging<NativePerformancePerformanceObserverObserveOptions> {};
class NativePerformance : public NativePerformanceCxxSpec<NativePerformance> {
public:
NativePerformance(std::shared_ptr<CallInvoker> jsInvoker);
#pragma mark - DOM Performance (High Resolution Time) (https://www.w3.org/TR/hr-time-3/#dom-performance)
// https://www.w3.org/TR/hr-time-3/#now-method
HighResTimeStamp now(jsi::Runtime &rt);
// https://www.w3.org/TR/hr-time-3/#timeorigin-attribute
HighResDuration timeOrigin(jsi::Runtime &rt);
#pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/)
void reportMark(jsi::Runtime &rt, const std::string &name, HighResTimeStamp time, jsi::Value entry);
void reportMeasure(
jsi::Runtime &rt,
const std::string &name,
HighResTimeStamp startTime,
HighResDuration duration,
jsi::Value entry);
std::optional<double> getMarkTime(jsi::Runtime &rt, const std::string &name);
// https://w3c.github.io/user-timing/#clearmarks-method
void clearMarks(jsi::Runtime &rt, std::optional<std::string> entryName = std::nullopt);
// https://w3c.github.io/user-timing/#clearmeasures-method
void clearMeasures(jsi::Runtime &rt, std::optional<std::string> entryName = std::nullopt);
#pragma mark - Performance Timeline (https://w3c.github.io/performance-timeline/#performance-timeline)
// https://www.w3.org/TR/performance-timeline/#getentries-method
std::vector<NativePerformanceEntry> getEntries(jsi::Runtime &rt);
// https://www.w3.org/TR/performance-timeline/#getentriesbytype-method
std::vector<NativePerformanceEntry> getEntriesByType(jsi::Runtime &rt, PerformanceEntryType entryType);
// https://www.w3.org/TR/performance-timeline/#getentriesbyname-method
std::vector<NativePerformanceEntry> getEntriesByName(
jsi::Runtime &rt,
const std::string &entryName,
std::optional<PerformanceEntryType> entryType = std::nullopt);
#pragma mark - Performance Observer (https://w3c.github.io/performance-timeline/#the-performanceobserver-interface)
jsi::Object createObserver(jsi::Runtime &rt, NativePerformancePerformanceObserverCallback callback);
// https://www.w3.org/TR/performance-timeline/#dom-performanceobservercallbackoptions-droppedentriescount
double getDroppedEntriesCount(jsi::Runtime &rt, jsi::Object observerObj);
void observe(jsi::Runtime &rt, jsi::Object observer, NativePerformancePerformanceObserverObserveOptions options);
void disconnect(jsi::Runtime &rt, jsi::Object observer);
std::vector<NativePerformanceEntry> takeRecords(
jsi::Runtime &rt,
jsi::Object observerObj,
// When called via `observer.takeRecords` it should be in insertion order.
// When called via the observer callback, it should be in chronological
// order with respect to `startTime`.
bool sort);
std::vector<PerformanceEntryType> getSupportedPerformanceEntryTypes(jsi::Runtime &rt);
#pragma mark - Event Timing API functions (https://www.w3.org/TR/event-timing/)
// https://www.w3.org/TR/event-timing/#dom-performance-eventcounts
std::vector<std::pair<std::string, uint32_t>> getEventCounts(jsi::Runtime &rt);
#pragma mark - Non-standard memory functions
// To align with web API, we will make sure to return three properties
// (jsHeapSizeLimit, totalJSHeapSize, usedJSHeapSize) + anything needed from
// the VM side.
// `jsHeapSizeLimit`: The maximum size of the heap, in bytes, that
// is available to the context.
// `totalJSHeapSize`: The total allocated heap size, in bytes.
// `usedJSHeapSize`: The currently active segment of JS heap, in
// bytes.
//
// Note that we use int64_t here and it's ok to lose precision in JS doubles
// for heap size information, as double's 2^53 sig bytes is large enough.
std::unordered_map<std::string, double> getSimpleMemoryInfo(jsi::Runtime &rt);
#pragma mark - RN-specific startup timing
// Collect and return the RN app startup timing information for performance
// tracking.
std::unordered_map<std::string, double> getReactNativeStartupTiming(jsi::Runtime &rt);
#pragma mark - Testing
void clearEventCountsForTesting(jsi::Runtime &rt);
};
} // namespace facebook::react

View File

@@ -0,0 +1,60 @@
# 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
header_search_paths = []
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the module access its own files
end
Pod::Spec.new do |s|
s.name = "React-webperformancenativemodule"
s.version = version
s.summary = "React Native idle callbacks native module"
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.header_dir = "react/nativemodule/webperformance"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"OTHER_CFLAGS" => "$(inherited)",
"DEFINES_MODULE" => "YES" }
if ENV['USE_FRAMEWORKS']
s.module_name = "webperformancenativemodule"
s.header_mappings_dir = "../.."
end
s.dependency "React-jsi"
s.dependency "React-jsiexecutor"
s.dependency "React-cxxreact"
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.dependency "ReactCommon/turbomodule/core"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-performancetimeline")
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
end