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,44 @@
/*
* 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/bridging/Base.h>
#include <string>
#include <string_view>
namespace facebook::react {
template <>
struct Bridging<std::string> {
static std::string fromJs(jsi::Runtime &rt, const jsi::String &value)
{
return value.utf8(rt);
}
static jsi::String toJs(jsi::Runtime &rt, const std::string &value)
{
return jsi::String::createFromUtf8(rt, value);
}
};
template <>
struct Bridging<std::string_view> {
static jsi::String toJs(jsi::Runtime &rt, std::string_view value)
{
return jsi::String::createFromUtf8(rt, reinterpret_cast<const uint8_t *>(value.data()), value.length());
}
};
template <>
struct Bridging<const char *> : Bridging<std::string_view> {};
template <size_t N>
struct Bridging<char[N]> : Bridging<std::string_view> {};
} // namespace facebook::react

View File

@@ -0,0 +1,134 @@
/*
* 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/bridging/Base.h>
#include <array>
#include <deque>
#include <initializer_list>
#include <list>
#include <set>
#include <tuple>
#include <utility>
#include <vector>
namespace facebook::react {
namespace array_detail {
template <typename T, size_t N>
struct BridgingStatic {
static jsi::Array toJs(jsi::Runtime &rt, const T &array, const std::shared_ptr<CallInvoker> &jsInvoker)
{
return toJs(rt, array, jsInvoker, std::make_index_sequence<N>{});
}
private:
template <size_t... Index>
static jsi::Array toJs(
facebook::jsi::Runtime &rt,
const T &array,
const std::shared_ptr<CallInvoker> &jsInvoker,
std::index_sequence<Index...> /*unused*/)
{
return jsi::Array::createWithElements(rt, bridging::toJs(rt, std::get<Index>(array), jsInvoker)...);
}
};
template <typename T>
struct BridgingDynamic {
static jsi::Array toJs(jsi::Runtime &rt, const T &list, const std::shared_ptr<CallInvoker> &jsInvoker)
{
jsi::Array result(rt, list.size());
size_t index = 0;
for (const auto &item : list) {
result.setValueAtIndex(rt, index++, bridging::toJs(rt, item, jsInvoker));
}
return result;
}
};
} // namespace array_detail
template <typename T, size_t N>
struct Bridging<std::array<T, N>> : array_detail::BridgingStatic<std::array<T, N>, N> {
static std::array<T, N>
fromJs(facebook::jsi::Runtime &rt, const jsi::Array &array, const std::shared_ptr<CallInvoker> &jsInvoker)
{
size_t length = array.length(rt);
std::array<T, N> result;
for (size_t i = 0; i < length; i++) {
result[i] = bridging::fromJs<T>(rt, array.getValueAtIndex(rt, i), jsInvoker);
}
return result;
}
};
template <typename T1, typename T2>
struct Bridging<std::pair<T1, T2>> : array_detail::BridgingStatic<std::pair<T1, T2>, 2> {
static std::pair<T1, T1>
fromJs(facebook::jsi::Runtime &rt, const jsi::Array &array, const std::shared_ptr<CallInvoker> &jsInvoker)
{
return std::make_pair(
bridging::fromJs<T1>(rt, array.getValueAtIndex(rt, 0), jsInvoker),
bridging::fromJs<T2>(rt, array.getValueAtIndex(rt, 1), jsInvoker));
}
};
template <typename... Types>
struct Bridging<std::tuple<Types...>> : array_detail::BridgingStatic<std::tuple<Types...>, sizeof...(Types)> {};
template <typename T>
struct Bridging<std::deque<T>> : array_detail::BridgingDynamic<std::deque<T>> {};
template <typename T>
struct Bridging<std::initializer_list<T>> : array_detail::BridgingDynamic<std::initializer_list<T>> {};
template <typename T>
struct Bridging<std::list<T>> : array_detail::BridgingDynamic<std::list<T>> {};
template <typename T>
struct Bridging<std::vector<T>> : array_detail::BridgingDynamic<std::vector<T>> {
static std::vector<T>
fromJs(facebook::jsi::Runtime &rt, const jsi::Array &array, const std::shared_ptr<CallInvoker> &jsInvoker)
{
size_t length = array.length(rt);
std::vector<T> vector;
vector.reserve(length);
for (size_t i = 0; i < length; i++) {
vector.push_back(bridging::fromJs<T>(rt, array.getValueAtIndex(rt, i), jsInvoker));
}
return vector;
}
};
template <typename T>
struct Bridging<std::set<T>> : array_detail::BridgingDynamic<std::set<T>> {
static std::set<T>
fromJs(facebook::jsi::Runtime &rt, const jsi::Array &array, const std::shared_ptr<CallInvoker> &jsInvoker)
{
size_t length = array.length(rt);
std::set<T> set;
for (size_t i = 0; i < length; i++) {
set.insert(bridging::fromJs<T>(rt, array.getValueAtIndex(rt, i), jsInvoker));
}
return set;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,139 @@
/*
* 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/Convert.h>
#include <cstdint>
#include <memory>
#include <type_traits>
namespace facebook::react {
class CallInvoker;
template <typename T, typename = void>
struct Bridging;
template <>
struct Bridging<void> {
// Highly generic code may result in "casting" to void.
static void fromJs(jsi::Runtime & /*unused*/, const jsi::Value & /*unused*/) {}
};
namespace bridging {
namespace detail {
template <typename F>
struct function_wrapper;
template <typename ClassT, typename ReturnT, typename... ArgsT>
struct function_wrapper<ReturnT (ClassT::*)(ArgsT...)> {
using type = std::function<ReturnT(ArgsT...)>;
};
template <typename ClassT, typename ReturnT, typename... ArgsT>
struct function_wrapper<ReturnT (ClassT::*)(ArgsT...) const> {
using type = std::function<ReturnT(ArgsT...)>;
};
template <typename T, typename = void>
struct bridging_wrapper {
using type = remove_cvref_t<T>;
};
// Convert lambda types to move-only function types since we can't specialize
// Bridging templates for arbitrary lambdas.
template <typename T>
struct bridging_wrapper<T, std::void_t<decltype(&remove_cvref_t<T>::operator())>>
: function_wrapper<decltype(&remove_cvref_t<T>::operator())> {};
} // namespace detail
template <typename T>
using bridging_t = typename detail::bridging_wrapper<T>::type;
template <typename ReturnT, typename JSArgT>
requires is_jsi_v<JSArgT>
auto fromJs(jsi::Runtime &rt, JSArgT &&value, const std::shared_ptr<CallInvoker> & /*unused*/)
-> decltype(static_cast<ReturnT>(std::move(convert(rt, std::forward<JSArgT>(value)))))
{
return static_cast<ReturnT>(std::move(convert(rt, std::forward<JSArgT>(value))));
}
template <typename ReturnT, typename JSArgT>
auto fromJs(jsi::Runtime &rt, JSArgT &&value, const std::shared_ptr<CallInvoker> & /*unused*/)
-> decltype(Bridging<remove_cvref_t<ReturnT>>::fromJs(rt, convert(rt, std::forward<JSArgT>(value))))
{
return Bridging<remove_cvref_t<ReturnT>>::fromJs(rt, convert(rt, std::forward<JSArgT>(value)));
}
template <typename ReturnT, typename JSArgT>
auto fromJs(jsi::Runtime &rt, JSArgT &&value, const std::shared_ptr<CallInvoker> &jsInvoker)
-> decltype(Bridging<remove_cvref_t<ReturnT>>::fromJs(rt, convert(rt, std::forward<JSArgT>(value)), jsInvoker))
{
return Bridging<remove_cvref_t<ReturnT>>::fromJs(rt, convert(rt, std::forward<JSArgT>(value)), jsInvoker);
}
template <typename T>
requires is_jsi_v<T>
auto toJs(jsi::Runtime &rt, T &&value, const std::shared_ptr<CallInvoker> & /*unused*/ = nullptr) -> remove_cvref_t<T>
{
return convert(rt, std::forward<T>(value));
}
template <typename T>
auto toJs(jsi::Runtime &rt, T &&value, const std::shared_ptr<CallInvoker> & /*unused*/ = nullptr)
-> decltype(Bridging<bridging_t<T>>::toJs(rt, std::forward<T>(value)))
{
return Bridging<bridging_t<T>>::toJs(rt, std::forward<T>(value));
}
template <typename T>
auto toJs(jsi::Runtime &rt, T &&value, const std::shared_ptr<CallInvoker> &jsInvoker)
-> decltype(Bridging<bridging_t<T>>::toJs(rt, std::forward<T>(value), jsInvoker))
{
return Bridging<bridging_t<T>>::toJs(rt, std::forward<T>(value), jsInvoker);
}
template <typename, typename = jsi::Value, typename = void>
inline constexpr bool supportsFromJs = false;
template <typename T, typename Arg>
inline constexpr bool supportsFromJs<
T,
Arg,
std::void_t<decltype(fromJs<T>(std::declval<jsi::Runtime &>(), std::declval<Arg>(), nullptr))>> = true;
template <typename T>
inline constexpr bool supportsFromJs<
T,
jsi::Value,
std::void_t<decltype(fromJs<T>(std::declval<jsi::Runtime &>(), std::declval<jsi::Value>(), nullptr))>> = true;
template <typename, typename = jsi::Value, typename = void>
inline constexpr bool supportsToJs = false;
template <typename JSReturnT, typename ReturnT>
inline constexpr bool supportsToJs<
JSReturnT,
ReturnT,
std::void_t<decltype(toJs(std::declval<jsi::Runtime &>(), std::declval<JSReturnT>(), nullptr))>> =
std::is_convertible_v<decltype(toJs(std::declval<jsi::Runtime &>(), std::declval<JSReturnT>(), nullptr)), ReturnT>;
template <typename ReturnT>
inline constexpr bool supportsToJs<
ReturnT,
jsi::Value,
std::void_t<decltype(toJs(std::declval<jsi::Runtime &>(), std::declval<ReturnT>(), nullptr))>> =
std::is_convertible_v<decltype(toJs(std::declval<jsi::Runtime &>(), std::declval<ReturnT>(), nullptr)), jsi::Value>;
} // namespace bridging
} // 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/bridging/Base.h>
namespace facebook::react {
template <>
struct Bridging<bool> {
static bool fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value)
{
return value.asBool();
}
static bool toJs(jsi::Runtime & /*unused*/, bool value)
{
return value;
}
};
} // 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.
*/
#pragma once
#include <react/bridging/AString.h>
#include <react/bridging/Array.h>
#include <react/bridging/Bool.h>
#include <react/bridging/Class.h>
#include <react/bridging/Dynamic.h>
#include <react/bridging/Error.h>
#include <react/bridging/EventEmitter.h>
#include <react/bridging/Function.h>
#include <react/bridging/HighResTimeStamp.h>
#include <react/bridging/Number.h>
#include <react/bridging/Object.h>
#include <react/bridging/Promise.h>
#include <react/bridging/Value.h>

View File

@@ -0,0 +1,19 @@
# 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_bridging_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_bridging OBJECT ${react_bridging_SRC})
target_include_directories(react_bridging PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_bridging jsi callinvoker react_timing)
target_compile_reactnative_options(react_bridging PRIVATE)
target_compile_options(react_bridging PRIVATE -Wpedantic)

View File

@@ -0,0 +1,68 @@
/*
* 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 <memory>
#include <react/bridging/LongLivedObject.h>
namespace facebook::react {
class CallInvoker;
// Helper for passing jsi::Function arg to other methods.
class CallbackWrapper : public LongLivedObject {
private:
CallbackWrapper(jsi::Function &&callback, jsi::Runtime &runtime, std::shared_ptr<CallInvoker> jsInvoker)
: LongLivedObject(runtime), callback_(std::move(callback)), jsInvoker_(std::move(jsInvoker))
{
}
jsi::Function callback_;
std::shared_ptr<CallInvoker> jsInvoker_;
public:
static std::weak_ptr<CallbackWrapper>
createWeak(jsi::Function &&callback, jsi::Runtime &runtime, std::shared_ptr<CallInvoker> jsInvoker)
{
auto wrapper =
std::shared_ptr<CallbackWrapper>(new CallbackWrapper(std::move(callback), runtime, std::move(jsInvoker)));
LongLivedObjectCollection::get(runtime).add(wrapper);
return wrapper;
}
// Delete the enclosed jsi::Function
void destroy()
{
allowRelease();
}
jsi::Function &callback() noexcept
{
return callback_;
}
jsi::Runtime &runtime() noexcept
{
return runtime_;
}
CallInvoker &jsInvoker() noexcept
{
return *(jsInvoker_);
}
std::shared_ptr<CallInvoker> jsInvokerPtr() noexcept
{
return jsInvoker_;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,75 @@
/*
* 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/bridging/Base.h>
namespace facebook::react::bridging {
template <typename JSReturnT, typename ClassT, typename ReturnT, typename... ArgsT, typename... JSArgsT>
JSReturnT callFromJs(
jsi::Runtime &rt,
ReturnT (ClassT::*method)(jsi::Runtime &, ArgsT...),
const std::shared_ptr<CallInvoker> &jsInvoker,
ClassT *instance,
JSArgsT &&...args)
{
static_assert(sizeof...(ArgsT) == sizeof...(JSArgsT), "Incorrect arguments length");
static_assert((supportsFromJs<ArgsT, JSArgsT> && ...), "Incompatible arguments");
if constexpr (std::is_void_v<JSReturnT>) {
static_assert(std::is_void_v<ReturnT>, "Method must return void when JSReturnT is void");
}
if constexpr (std::is_void_v<JSReturnT>) {
(instance->*method)(rt, fromJs<ArgsT>(rt, std::forward<JSArgsT>(args), jsInvoker)...);
} else if constexpr (std::is_void_v<ReturnT>) {
static_assert(std::is_same_v<JSReturnT, jsi::Value>, "Void functions may only return undefined");
(instance->*method)(rt, fromJs<ArgsT>(rt, std::forward<JSArgsT>(args), jsInvoker)...);
return jsi::Value();
} else if constexpr (is_jsi_v<JSReturnT> || supportsToJs<ReturnT, JSReturnT>) {
static_assert(supportsToJs<ReturnT, JSReturnT>, "Incompatible return type");
return toJs(rt, (instance->*method)(rt, fromJs<ArgsT>(rt, std::forward<JSArgsT>(args), jsInvoker)...), jsInvoker);
} else if constexpr (is_optional_jsi_v<JSReturnT>) {
static_assert(
is_optional_v<ReturnT> ? supportsToJs<typename ReturnT::value_type, typename JSReturnT::value_type>
: supportsToJs<ReturnT, typename JSReturnT::value_type>,
"Incompatible return type");
auto result =
toJs(rt, (instance->*method)(rt, fromJs<ArgsT>(rt, std::forward<JSArgsT>(args), jsInvoker)...), jsInvoker);
if constexpr (std::is_same_v<decltype(result), jsi::Value>) {
if (result.isNull() || result.isUndefined()) {
return std::nullopt;
}
}
return convert(rt, std::move(result));
} else {
static_assert(std::is_convertible_v<ReturnT, JSReturnT>, "Incompatible return type");
return (instance->*method)(rt, fromJs<ArgsT>(rt, std::forward<JSArgsT>(args), jsInvoker)...);
}
}
template <typename ReturnT, typename... ArgsT>
constexpr size_t getParameterCount(ReturnT (* /*unused*/)(ArgsT...))
{
return sizeof...(ArgsT);
}
template <typename Class, typename ReturnT, typename... ArgsT>
constexpr size_t getParameterCount(ReturnT (Class::* /*unused*/)(ArgsT...))
{
return sizeof...(ArgsT);
}
} // namespace facebook::react::bridging

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
#include <jsi/jsi.h>
#include <optional>
#include <type_traits>
namespace facebook::react::bridging {
// std::remove_cvref_t is not available until C++20.
template <typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
template <typename T>
inline constexpr bool is_jsi_v = std::is_same_v<jsi::Value, remove_cvref_t<T>> ||
std::is_same_v<jsi::String, remove_cvref_t<T>> || std::is_base_of_v<jsi::Object, remove_cvref_t<T>>;
template <typename>
struct is_optional : std::false_type {};
template <typename T>
struct is_optional<std::optional<T>> : std::true_type {};
template <typename T>
inline constexpr bool is_optional_v = is_optional<T>::value;
template <typename T, typename = void>
inline constexpr bool is_optional_jsi_v = false;
template <typename T>
inline constexpr bool is_optional_jsi_v<T, typename std::enable_if_t<is_optional_v<T>>> =
is_jsi_v<typename T::value_type>;
template <typename T>
struct Converter;
template <typename T>
struct ConverterBase {
using BaseT = remove_cvref_t<T>;
ConverterBase(jsi::Runtime &rt, T &&value) : rt_(rt), value_(std::forward<T>(value)) {}
operator BaseT() &&
{
if constexpr (std::is_lvalue_reference_v<T>) {
// Copy the reference into a Value that then can be moved from.
auto value = jsi::Value(rt_, value_);
if constexpr (std::is_same_v<BaseT, jsi::Value>) {
return std::move(value);
} else if constexpr (std::is_same_v<BaseT, jsi::String>) {
return std::move(value).getString(rt_);
} else if constexpr (std::is_same_v<BaseT, jsi::Object>) {
return std::move(value).getObject(rt_);
} else if constexpr (std::is_same_v<BaseT, jsi::Array>) {
return std::move(value).getObject(rt_).getArray(rt_);
} else if constexpr (std::is_same_v<BaseT, jsi::Function>) {
return std::move(value).getObject(rt_).getFunction(rt_);
}
} else {
return std::move(value_);
}
}
template <
typename U,
std::enable_if_t<
std::is_lvalue_reference_v<T> &&
// Ensure non-reference type can be converted to the desired type.
std::is_convertible_v<Converter<BaseT>, U>,
int> = 0>
operator U() &&
{
return Converter<BaseT>(rt_, std::move(*this).operator BaseT());
}
template <typename U, std::enable_if_t<is_jsi_v<T> && std::is_same_v<U, jsi::Value>, int> = 0>
operator U() && = delete; // Prevent unwanted upcasting of JSI values.
protected:
jsi::Runtime &rt_;
T value_;
};
template <typename T>
struct Converter : public ConverterBase<T> {
using ConverterBase<T>::ConverterBase;
};
template <>
struct Converter<jsi::Value> : public ConverterBase<jsi::Value> {
using ConverterBase<jsi::Value>::ConverterBase;
operator jsi::String() &&
{
return std::move(value_).asString(rt_);
}
operator jsi::Object() &&
{
return std::move(value_).asObject(rt_);
}
operator jsi::Array() &&
{
return std::move(value_).asObject(rt_).asArray(rt_);
}
operator jsi::Function() &&
{
return std::move(value_).asObject(rt_).asFunction(rt_);
}
};
template <>
struct Converter<jsi::Object> : public ConverterBase<jsi::Object> {
using ConverterBase<jsi::Object>::ConverterBase;
operator jsi::Array() &&
{
return std::move(value_).asArray(rt_);
}
operator jsi::Function() &&
{
return std::move(value_).asFunction(rt_);
}
};
template <typename T>
struct Converter<std::optional<T>> : public ConverterBase<jsi::Value> {
Converter(jsi::Runtime &rt, std::optional<T> value)
: ConverterBase(rt, value ? std::move(*value) : jsi::Value::null())
{
}
operator std::optional<T>() &&
{
if (value_.isNull() || value_.isUndefined()) {
return {};
}
return std::move(value_);
}
};
template <typename T, std::enable_if_t<is_jsi_v<T>, int> = 0>
auto convert(jsi::Runtime &rt, T &&value)
{
return Converter<T>(rt, std::forward<T>(value));
}
template <typename T, std::enable_if_t<is_jsi_v<T> || std::is_scalar_v<T>, int> = 0>
auto convert(jsi::Runtime &rt, std::optional<T> value)
{
return Converter<std::optional<T>>(rt, std::move(value));
}
template <typename T, std::enable_if_t<std::is_scalar_v<T>, int> = 0>
auto convert(jsi::Runtime & /*rt*/, T &&value)
{
return value;
}
template <typename T>
auto convert(jsi::Runtime & /*rt*/, Converter<T> &&converter)
{
return std::move(converter);
}
} // namespace facebook::react::bridging

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 <folly/dynamic.h>
#include <jsi/JSIDynamic.h>
namespace facebook::react {
template <>
struct Bridging<folly::dynamic> {
static folly::dynamic fromJs(jsi::Runtime &rt, const jsi::Value &value)
{
return jsi::dynamicFromValue(rt, value);
}
static jsi::Value toJs(jsi::Runtime &rt, const folly::dynamic &value)
{
return jsi::valueFromDynamic(rt, value);
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/bridging/Base.h>
namespace facebook::react {
class Error {
public:
// TODO (T114055466): Retain stack trace (at least caller location)
Error(std::string message) : message_(std::move(message)) {}
Error(const char *message) : Error(std::string(message)) {}
const std::string &message() const
{
return message_;
}
private:
std::string message_;
};
template <>
struct Bridging<jsi::JSError> {
static jsi::JSError fromJs(jsi::Runtime &rt, const jsi::Value &value)
{
return jsi::JSError(rt, jsi::Value(rt, value));
}
static jsi::JSError fromJs(jsi::Runtime &rt, jsi::Value &&value)
{
return jsi::JSError(rt, std::move(value));
}
static jsi::Value toJs(jsi::Runtime &rt, std::string message)
{
return jsi::Value(rt, jsi::JSError(rt, std::move(message)).value());
}
};
template <>
struct Bridging<Error> {
static jsi::Value toJs(jsi::Runtime &rt, const Error &error)
{
return jsi::Value(rt, jsi::JSError(rt, error.message()).value());
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/bridging/Function.h>
#include <functional>
#include <memory>
#include <mutex>
#include <unordered_map>
#define FRIEND_TEST(test_case_name, test_name) friend class test_case_name##_##test_name##_Test
namespace facebook::react {
class EventSubscription {
public:
explicit EventSubscription(std::function<void()> remove) : remove_(std::move(remove)) {}
~EventSubscription() = default;
EventSubscription(EventSubscription &&) noexcept = default;
EventSubscription &operator=(EventSubscription &&) noexcept = default;
EventSubscription(const EventSubscription &) = delete;
EventSubscription &operator=(const EventSubscription &) = delete;
void remove()
{
remove_();
}
private:
friend Bridging<EventSubscription>;
std::function<void()> remove_;
};
template <>
struct Bridging<EventSubscription> {
static EventSubscription
fromJs(jsi::Runtime &rt, const jsi::Object &value, const std::shared_ptr<CallInvoker> &jsInvoker)
{
auto listener = bridging::fromJs<AsyncCallback<>>(rt, value.getProperty(rt, "remove"), jsInvoker);
return EventSubscription([listener = std::move(listener)]() mutable { listener(); });
}
static jsi::Object
toJs(jsi::Runtime &rt, const EventSubscription &eventSubscription, const std::shared_ptr<CallInvoker> &jsInvoker)
{
auto result = jsi::Object(rt);
result.setProperty(rt, "remove", bridging::toJs(rt, eventSubscription.remove_, jsInvoker));
return result;
}
};
class IAsyncEventEmitter {
public:
IAsyncEventEmitter() noexcept = default;
virtual ~IAsyncEventEmitter() noexcept = default;
IAsyncEventEmitter(IAsyncEventEmitter &&) noexcept = default;
IAsyncEventEmitter &operator=(IAsyncEventEmitter &&) noexcept = default;
IAsyncEventEmitter(const IAsyncEventEmitter &) = delete;
IAsyncEventEmitter &operator=(const IAsyncEventEmitter &) = delete;
virtual jsi::Object get(jsi::Runtime &rt, const std::shared_ptr<CallInvoker> &jsInvoker) const = 0;
};
template <typename... Args>
class AsyncEventEmitter : public IAsyncEventEmitter {
static_assert(sizeof...(Args) <= 1, "AsyncEventEmitter must have at most one argument");
public:
AsyncEventEmitter() : state_(std::make_shared<SharedState>())
{
listen_ = [state = state_](AsyncCallback<Args...> listener) {
std::lock_guard<std::mutex> lock(state->mutex);
auto listenerId = state->listenerId++;
state->listeners.emplace(listenerId, std::move(listener));
return EventSubscription([state, listenerId]() {
std::lock_guard<std::mutex> innerLock(state->mutex);
state->listeners.erase(listenerId);
});
};
}
~AsyncEventEmitter() override = default;
AsyncEventEmitter(AsyncEventEmitter &&) noexcept = default;
AsyncEventEmitter &operator=(AsyncEventEmitter &&) noexcept = default;
AsyncEventEmitter(const AsyncEventEmitter &) = delete;
AsyncEventEmitter &operator=(const AsyncEventEmitter &) = delete;
void emit(std::function<jsi::Value(jsi::Runtime &)> &&converter)
{
std::lock_guard<std::mutex> lock(state_->mutex);
for (auto &[_, listener] : state_->listeners) {
listener.call([converter](jsi::Runtime &rt, jsi::Function &jsFunction) { jsFunction.call(rt, converter(rt)); });
}
}
void emit(Args... value)
{
std::lock_guard<std::mutex> lock(state_->mutex);
for (const auto &[_, listener] : state_->listeners) {
listener.call(static_cast<Args>(value)...);
}
}
jsi::Object get(jsi::Runtime &rt, const std::shared_ptr<CallInvoker> &jsInvoker) const override
{
return bridging::toJs(rt, listen_, jsInvoker);
}
private:
friend Bridging<AsyncEventEmitter>;
FRIEND_TEST(BridgingTest, eventEmitterTest);
struct SharedState {
std::mutex mutex;
std::unordered_map<size_t, AsyncCallback<Args...>> listeners;
size_t listenerId{};
};
std::function<EventSubscription(AsyncCallback<Args...>)> listen_;
std::shared_ptr<SharedState> state_;
};
template <typename... Args>
struct Bridging<AsyncEventEmitter<Args...>> {
static jsi::Object
toJs(jsi::Runtime &rt, const AsyncEventEmitter<Args...> &eventEmitter, const std::shared_ptr<CallInvoker> &jsInvoker)
{
return eventEmitter.get(rt, jsInvoker);
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,263 @@
/*
* 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/bridging/Base.h>
#include <react/bridging/CallbackWrapper.h>
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/SchedulerPriority.h>
namespace facebook::react {
template <typename F>
class SyncCallback;
template <typename... Args>
class AsyncCallback {
public:
AsyncCallback(jsi::Runtime &runtime, jsi::Function function, std::shared_ptr<CallInvoker> jsInvoker)
: callback_(std::make_shared<SyncCallback<void(Args...)>>(runtime, std::move(function), std::move(jsInvoker)))
{
}
void operator()(Args... args) const noexcept
{
call(std::forward<Args>(args)...);
}
void call(Args... args) const noexcept
{
callWithArgs(std::nullopt, std::forward<Args>(args)...);
}
void callWithPriority(SchedulerPriority priority, Args... args) const noexcept
{
callWithArgs(priority, std::forward<Args>(args)...);
}
void call(std::function<void(jsi::Runtime &, jsi::Function &)> &&callImpl) const noexcept
{
callWithFunction(std::nullopt, std::move(callImpl));
}
void callWithPriority(SchedulerPriority priority, std::function<void(jsi::Runtime &, jsi::Function &)> &&callImpl)
const noexcept
{
callWithFunction(priority, std::move(callImpl));
}
private:
friend Bridging<AsyncCallback>;
std::shared_ptr<SyncCallback<void(Args...)>> callback_;
void callWithArgs(std::optional<SchedulerPriority> priority, Args... args) const noexcept
{
if (auto wrapper = callback_->wrapper_.lock()) {
auto fn = [callback = callback_,
argsPtr = std::make_shared<std::tuple<Args...>>(std::make_tuple(std::forward<Args>(args)...))](
jsi::Runtime &) { callback->apply(std::move(*argsPtr)); };
auto &jsInvoker = wrapper->jsInvoker();
if (priority) {
jsInvoker.invokeAsync(*priority, std::move(fn));
} else {
jsInvoker.invokeAsync(std::move(fn));
}
}
}
void callWithFunction(
std::optional<SchedulerPriority> priority,
std::function<void(jsi::Runtime &, jsi::Function &)> &&callImpl) const noexcept
{
if (auto wrapper = callback_->wrapper_.lock()) {
// Capture callback_ and not wrapper_. If callback_ is deallocated or the
// JSVM is shutdown before the async task is scheduled, the underlying
// function will have been deallocated.
auto fn = [callback = callback_, callImpl = std::move(callImpl)](jsi::Runtime &rt) {
if (auto wrapper2 = callback->wrapper_.lock()) {
callImpl(rt, wrapper2->callback());
}
};
auto &jsInvoker = wrapper->jsInvoker();
if (priority) {
jsInvoker.invokeAsync(*priority, std::move(fn));
} else {
jsInvoker.invokeAsync(std::move(fn));
}
}
}
};
// You must ensure that when invoking this you're located on the JS thread, or
// have exclusive control of the JS VM context. If you cannot ensure this, use
// AsyncCallback instead.
template <typename R, typename... Args>
class SyncCallback<R(Args...)> {
public:
SyncCallback(jsi::Runtime &rt, jsi::Function function, std::shared_ptr<CallInvoker> jsInvoker)
: wrapper_(CallbackWrapper::createWeak(std::move(function), rt, std::move(jsInvoker)))
{
}
// Disallow copying, as we can no longer safely destroy the callback
// from the destructor if there's multiple copies
SyncCallback(const SyncCallback &) = delete;
SyncCallback &operator=(const SyncCallback &) = delete;
// Allow move
SyncCallback(SyncCallback &&other) noexcept : wrapper_(std::move(other.wrapper_)) {}
SyncCallback &operator=(SyncCallback &&other) noexcept
{
wrapper_ = std::move(other.wrapper_);
return *this;
}
~SyncCallback()
{
if (auto wrapper = wrapper_.lock()) {
wrapper->destroy();
}
}
R operator()(Args... args) const
{
return call(std::forward<Args>(args)...);
}
R call(Args... args) const
{
auto wrapper = wrapper_.lock();
// If the wrapper has been deallocated, we can no longer provide a return
// value consistently, so our only option is to throw
if (!wrapper) {
if constexpr (std::is_void_v<R>) {
return;
} else {
throw std::runtime_error("Failed to call invalidated sync callback");
}
}
auto &callback = wrapper->callback();
auto &rt = wrapper->runtime();
auto jsInvoker = wrapper->jsInvokerPtr();
if constexpr (std::is_void_v<R>) {
callback.call(rt, bridging::toJs(rt, std::forward<Args>(args), jsInvoker)...);
} else {
return bridging::fromJs<R>(
rt, callback.call(rt, bridging::toJs(rt, std::forward<Args>(args), jsInvoker)...), jsInvoker);
}
}
private:
friend AsyncCallback<Args...>;
friend Bridging<SyncCallback>;
R apply(std::tuple<Args...> &&args) const
{
return apply(std::move(args), std::index_sequence_for<Args...>{});
}
template <size_t... Index>
R apply(std::tuple<Args...> &&args, std::index_sequence<Index...> /*unused*/) const
{
return call(std::move(std::get<Index>(args))...);
}
// Held weakly so lifetime is managed by LongLivedObjectCollection.
std::weak_ptr<CallbackWrapper> wrapper_;
};
template <typename... Args>
struct Bridging<AsyncCallback<Args...>> {
static AsyncCallback<Args...>
fromJs(jsi::Runtime &rt, jsi::Function &&value, const std::shared_ptr<CallInvoker> &jsInvoker)
{
return AsyncCallback<Args...>(rt, std::move(value), jsInvoker);
}
static jsi::Function toJs(jsi::Runtime &rt, const AsyncCallback<Args...> &value)
{
return value.callback_->function_.getFunction(rt);
}
};
template <typename R, typename... Args>
struct Bridging<SyncCallback<R(Args...)>> {
static SyncCallback<R(Args...)>
fromJs(jsi::Runtime &rt, jsi::Function &&value, const std::shared_ptr<CallInvoker> &jsInvoker)
{
return SyncCallback<R(Args...)>(rt, std::move(value), jsInvoker);
}
static jsi::Function toJs(jsi::Runtime &rt, const SyncCallback<R(Args...)> &value)
{
return value.function_.getFunction(rt);
}
};
template <typename R, typename... Args>
struct Bridging<std::function<R(Args...)>> {
using Func = std::function<R(Args...)>;
using IndexSequence = std::index_sequence_for<Args...>;
static constexpr size_t kArgumentCount = sizeof...(Args);
static jsi::Function toJs(jsi::Runtime &rt, Func fn, const std::shared_ptr<CallInvoker> &jsInvoker)
{
return jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "BridgedFunction"),
kArgumentCount,
[fn = std::make_shared<Func>(std::move(fn)), jsInvoker](
jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value {
if (count < kArgumentCount) {
throw jsi::JSError(rt, "Incorrect number of arguments");
}
if constexpr (std::is_void_v<R>) {
callFromJs(*fn, rt, args, jsInvoker, IndexSequence{});
return jsi::Value();
} else {
return bridging::toJs(rt, callFromJs(*fn, rt, args, jsInvoker, IndexSequence{}), jsInvoker);
}
});
}
private:
template <size_t... Index>
static R callFromJs(
Func &fn,
jsi::Runtime &rt,
const jsi::Value *args,
const std::shared_ptr<CallInvoker> &jsInvoker,
std::index_sequence<Index...> /*unused*/)
{
return fn(bridging::fromJs<Args>(rt, args[Index], jsInvoker)...);
}
};
template <typename R, typename... Args>
struct Bridging<
std::function<R(Args...)>,
std::enable_if_t<!std::is_same_v<std::function<R(Args...)>, std::function<R(Args...)>>>>
: Bridging<std::function<R(Args...)>> {};
template <typename R, typename... Args>
struct Bridging<R(Args...)> : Bridging<std::function<R(Args...)>> {};
template <typename R, typename... Args>
struct Bridging<R (*)(Args...)> : Bridging<std::function<R(Args...)>> {};
} // namespace facebook::react

View File

@@ -0,0 +1,41 @@
/*
* 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/bridging/Base.h>
#include <react/timing/primitives.h>
namespace facebook::react {
template <>
struct Bridging<HighResTimeStamp> {
static HighResTimeStamp fromJs(jsi::Runtime & /*rt*/, const jsi::Value &jsiValue)
{
return HighResTimeStamp::fromDOMHighResTimeStamp(jsiValue.asNumber());
}
static double toJs(jsi::Runtime & /*rt*/, const HighResTimeStamp &value)
{
return value.toDOMHighResTimeStamp();
}
};
template <>
struct Bridging<HighResDuration> {
static HighResDuration fromJs(jsi::Runtime & /*rt*/, const jsi::Value &jsiValue)
{
return HighResDuration::fromDOMHighResTimeStamp(jsiValue.asNumber());
}
static double toJs(jsi::Runtime & /*rt*/, const HighResDuration &value)
{
return value.toDOMHighResTimeStamp();
}
};
} // 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.
*/
#include "LongLivedObject.h"
#include <unordered_map>
namespace facebook::react {
// LongLivedObjectCollection
LongLivedObjectCollection& LongLivedObjectCollection::get(
jsi::Runtime& runtime) {
static std::unordered_map<void*, std::shared_ptr<LongLivedObjectCollection>>
instances;
static std::mutex instancesMutex;
std::scoped_lock lock(instancesMutex);
void* key = static_cast<void*>(&runtime);
auto entry = instances.find(key);
if (entry == instances.end()) {
entry =
instances.emplace(key, std::make_shared<LongLivedObjectCollection>())
.first;
}
return *(entry->second);
}
void LongLivedObjectCollection::add(std::shared_ptr<LongLivedObject> so) {
std::scoped_lock lock(collectionMutex_);
collection_.insert(std::move(so));
}
void LongLivedObjectCollection::remove(const LongLivedObject* o) {
std::scoped_lock lock(collectionMutex_);
for (auto p = collection_.begin(); p != collection_.end(); p++) {
if (p->get() == o) {
collection_.erase(p);
break;
}
}
}
void LongLivedObjectCollection::clear() {
std::scoped_lock lock(collectionMutex_);
collection_.clear();
}
size_t LongLivedObjectCollection::size() const {
std::scoped_lock lock(collectionMutex_);
return collection_.size();
}
// LongLivedObject
void LongLivedObject::allowRelease() {
LongLivedObjectCollection::get(runtime_).remove(this);
}
} // namespace facebook::react

View File

@@ -0,0 +1,61 @@
/*
* 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 <memory>
#include <mutex>
#include <unordered_set>
namespace facebook::react {
/**
* A simple wrapper class that can be registered to a collection that keep it
* alive for extended period of time. This object can be removed from the
* collection when needed.
*
* The subclass of this class must be created using std::make_shared<T>().
* After creation, add it to the `LongLivedObjectCollection`. When done with the
* object, call `allowRelease()` to reclaim its memory.
*
* When using LongLivedObject to keep JS values alive, ensure you only hold weak
* references to the object outside the JS thread to avoid accessing deallocated
* values when the JS VM is shutdown.
*/
class LongLivedObject {
public:
virtual void allowRelease();
protected:
explicit LongLivedObject(jsi::Runtime &runtime) : runtime_(runtime) {}
virtual ~LongLivedObject() = default;
jsi::Runtime &runtime_;
};
/**
* A singleton, thread-safe, write-only collection for the `LongLivedObject`s.
*/
class LongLivedObjectCollection {
public:
static LongLivedObjectCollection &get(jsi::Runtime &runtime);
LongLivedObjectCollection() = default;
LongLivedObjectCollection(const LongLivedObjectCollection &) = delete;
void operator=(const LongLivedObjectCollection &) = delete;
void add(std::shared_ptr<LongLivedObject> o);
void remove(const LongLivedObject *o);
void clear();
size_t size() const;
private:
std::unordered_set<std::shared_ptr<LongLivedObject>> collection_;
mutable std::mutex collectionMutex_;
};
} // 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.
*/
#pragma once
#include <react/bridging/Base.h>
namespace facebook::react {
template <>
struct Bridging<double> {
static double fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value)
{
return value.asNumber();
}
static double toJs(jsi::Runtime & /*unused*/, double value)
{
return value;
}
};
template <>
struct Bridging<float> {
static float fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value)
{
return (float)value.asNumber();
}
static float toJs(jsi::Runtime & /*unused*/, float value)
{
return value;
}
};
template <>
struct Bridging<int32_t> {
static int32_t fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value)
{
return (int32_t)value.asNumber();
}
static int32_t toJs(jsi::Runtime & /*unused*/, int32_t value)
{
return value;
}
};
template <>
struct Bridging<uint32_t> {
static uint32_t fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value)
{
return (uint32_t)value.asNumber();
}
static jsi::Value toJs(jsi::Runtime & /*unused*/, uint32_t value)
{
return double(value);
}
};
} // namespace facebook::react

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
#include <react/bridging/AString.h>
#include <react/bridging/Base.h>
#include <map>
#include <unordered_map>
namespace facebook::react {
template <>
struct Bridging<jsi::WeakObject> {
static jsi::WeakObject fromJs(jsi::Runtime &rt, const jsi::Object &value)
{
return jsi::WeakObject(rt, value);
}
static jsi::Value toJs(jsi::Runtime &rt, jsi::WeakObject &value)
{
return value.lock(rt);
}
};
template <typename T>
struct Bridging<std::shared_ptr<T>, std::enable_if_t<std::is_base_of_v<jsi::HostObject, T>>> {
static std::shared_ptr<T> fromJs(jsi::Runtime &rt, const jsi::Object &value)
{
return value.getHostObject<T>(rt);
}
static jsi::Object toJs(jsi::Runtime &rt, std::shared_ptr<T> value)
{
return jsi::Object::createFromHostObject(rt, std::move(value));
}
};
namespace map_detail {
template <typename T>
struct Bridging {
static T fromJs(jsi::Runtime &rt, const jsi::Object &value, const std::shared_ptr<CallInvoker> &jsInvoker)
{
T result;
auto propertyNames = value.getPropertyNames(rt);
auto length = propertyNames.length(rt);
for (size_t i = 0; i < length; i++) {
auto propertyName = propertyNames.getValueAtIndex(rt, i);
result.emplace(
bridging::fromJs<std::string>(rt, propertyName, jsInvoker),
bridging::fromJs<typename T::mapped_type>(rt, value.getProperty(rt, propertyName.asString(rt)), jsInvoker));
}
return result;
}
static jsi::Object toJs(jsi::Runtime &rt, const T &map, const std::shared_ptr<CallInvoker> &jsInvoker)
{
auto resultObject = jsi::Object(rt);
for (const auto &[key, value] : map) {
resultObject.setProperty(rt, jsi::PropNameID::forUtf8(rt, key), bridging::toJs(rt, value, jsInvoker));
}
return resultObject;
}
};
} // namespace map_detail
template <typename... Args>
struct Bridging<std::map<std::string, Args...>> : map_detail::Bridging<std::map<std::string, Args...>> {};
template <typename... Args>
struct Bridging<std::unordered_map<std::string, Args...>>
: map_detail::Bridging<std::unordered_map<std::string, Args...>> {};
} // namespace facebook::react

View File

@@ -0,0 +1,110 @@
/*
* 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/bridging/Error.h>
#include <react/bridging/Function.h>
#include <react/bridging/LongLivedObject.h>
#include <mutex>
#include <optional>
namespace facebook::react {
template <typename... T>
class AsyncPromise {
static_assert(sizeof...(T) <= 1, "AsyncPromise must have at most one argument");
public:
AsyncPromise(jsi::Runtime &rt, const std::shared_ptr<CallInvoker> &jsInvoker)
: state_(std::make_shared<SharedState>())
{
auto constructor = rt.global().getPropertyAsFunction(rt, "Promise");
auto promise = constructor.callAsConstructor(
rt,
bridging::toJs(
rt,
// Safe to capture this since this is called synchronously.
[this](AsyncCallback<T...> resolve, const AsyncCallback<Error> &reject) {
state_->resolve = std::move(resolve);
state_->reject = std::move(reject);
},
jsInvoker));
auto promiseHolder = std::make_shared<PromiseHolder>(rt, promise.asObject(rt));
LongLivedObjectCollection::get(rt).add(promiseHolder);
// The shared state can retain the promise holder weakly now.
state_->promiseHolder = promiseHolder;
}
void resolve(T... value)
{
std::lock_guard<std::mutex> lock(state_->mutex);
if (state_->resolve) {
state_->resolve->call(std::forward<T>(value)...);
state_->resolve.reset();
state_->reject.reset();
}
}
void reject(Error error)
{
std::lock_guard<std::mutex> lock(state_->mutex);
if (state_->reject) {
state_->reject->call(std::move(error));
state_->reject.reset();
state_->resolve.reset();
}
}
jsi::Object get(jsi::Runtime &rt) const
{
if (auto holder = state_->promiseHolder.lock()) {
return jsi::Value(rt, holder->promise).asObject(rt);
} else {
throw jsi::JSError(rt, "Failed to get invalidated promise");
}
}
private:
struct PromiseHolder : LongLivedObject {
PromiseHolder(jsi::Runtime &runtime, jsi::Object p) : LongLivedObject(runtime), promise(std::move(p)) {}
jsi::Object promise;
};
struct SharedState {
~SharedState()
{
if (auto holder = promiseHolder.lock()) {
holder->allowRelease();
}
}
std::mutex mutex;
std::weak_ptr<PromiseHolder> promiseHolder;
std::optional<AsyncCallback<T...>> resolve;
std::optional<AsyncCallback<Error>> reject;
};
std::shared_ptr<SharedState> state_;
};
template <typename... T>
struct Bridging<AsyncPromise<T...>> {
static jsi::Object toJs(jsi::Runtime &rt, const AsyncPromise<T...> &promise)
{
return promise.get(rt);
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,98 @@
/*
* 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/bridging/Base.h>
#include <memory>
#include <optional>
namespace facebook::react {
template <>
struct Bridging<std::nullptr_t> {
static std::nullptr_t fromJs(jsi::Runtime &rt, const jsi::Value &value)
{
if (value.isNull() || value.isUndefined()) {
return nullptr;
} else {
throw jsi::JSError(rt, "Cannot convert value to nullptr");
}
}
static std::nullptr_t toJs(jsi::Runtime & /*unused*/, std::nullptr_t)
{
return nullptr;
}
};
template <typename T>
struct Bridging<std::optional<T>> {
static std::optional<T>
fromJs(jsi::Runtime &rt, const jsi::Value &value, const std::shared_ptr<CallInvoker> &jsInvoker)
{
if (value.isNull() || value.isUndefined()) {
return {};
}
return bridging::fromJs<T>(rt, value, jsInvoker);
}
template <typename U>
static std::optional<T>
fromJs(jsi::Runtime &rt, const std::optional<U> &value, const std::shared_ptr<CallInvoker> &jsInvoker)
{
if (value) {
return bridging::fromJs<T>(rt, *value, jsInvoker);
}
return {};
}
static jsi::Value toJs(jsi::Runtime &rt, const std::optional<T> &value, const std::shared_ptr<CallInvoker> &jsInvoker)
{
if (value) {
return bridging::toJs(rt, *value, jsInvoker);
}
return jsi::Value::null();
}
};
template <typename T>
struct Bridging<std::shared_ptr<T>, std::enable_if_t<!std::is_base_of_v<jsi::HostObject, T>>> {
static jsi::Value toJs(jsi::Runtime &rt, const std::shared_ptr<T> &ptr, const std::shared_ptr<CallInvoker> &jsInvoker)
{
if (ptr) {
return bridging::toJs(rt, *ptr, jsInvoker);
}
return jsi::Value::null();
}
};
template <typename T>
struct Bridging<std::unique_ptr<T>> {
static jsi::Value toJs(jsi::Runtime &rt, const std::unique_ptr<T> &ptr, const std::shared_ptr<CallInvoker> &jsInvoker)
{
if (ptr) {
return bridging::toJs(rt, *ptr, jsInvoker);
}
return jsi::Value::null();
}
};
template <typename T>
struct Bridging<std::weak_ptr<T>> {
static jsi::Value
toJs(jsi::Runtime &rt, const std::weak_ptr<T> &weakPtr, const std::shared_ptr<CallInvoker> &jsInvoker)
{
if (auto ptr = weakPtr.lock()) {
return bridging::toJs(rt, *ptr, jsInvoker);
}
return jsi::Value::null();
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,801 @@
/*
* 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 "BridgingTest.h"
namespace facebook::react {
using namespace std::literals;
TEST_F(BridgingTest, jsiTest) {
jsi::Value value = true;
jsi::Value string = jsi::String::createFromAscii(rt, "hello");
jsi::Value object = jsi::Object(rt);
jsi::Value array = jsi::Array::createWithElements(rt, value, object);
jsi::Value func = function("() => {}");
// The bridging mechanism needs to know how to copy and downcast values.
EXPECT_NO_THROW(bridging::fromJs<jsi::Value>(rt, value, invoker));
EXPECT_NO_THROW(bridging::fromJs<jsi::String>(rt, string, invoker));
EXPECT_NO_THROW(bridging::fromJs<jsi::Object>(rt, object, invoker));
EXPECT_NO_THROW(bridging::fromJs<jsi::Array>(rt, array, invoker));
EXPECT_NO_THROW(bridging::fromJs<jsi::Function>(rt, func, invoker));
// Should throw when attempting an invalid cast.
EXPECT_JSI_THROW(bridging::fromJs<jsi::Object>(rt, value, invoker));
EXPECT_JSI_THROW(bridging::fromJs<jsi::String>(rt, array, invoker));
EXPECT_JSI_THROW(bridging::fromJs<jsi::Array>(rt, object, invoker));
EXPECT_JSI_THROW(bridging::fromJs<jsi::Array>(rt, string, invoker));
EXPECT_JSI_THROW(bridging::fromJs<jsi::Array>(rt, func, invoker));
// Should be able to generically no-op convert JSI.
EXPECT_NO_THROW(bridging::toJs(rt, value, invoker));
EXPECT_NO_THROW(bridging::toJs(rt, string.asString(rt), invoker));
EXPECT_NO_THROW(bridging::toJs(rt, object.asObject(rt), invoker));
EXPECT_NO_THROW(bridging::toJs(rt, array.asObject(rt).asArray(rt), invoker));
EXPECT_NO_THROW(
bridging::toJs(rt, func.asObject(rt).asFunction(rt), invoker));
}
TEST_F(BridgingTest, boolTest) {
EXPECT_TRUE(bridging::fromJs<bool>(rt, jsi::Value(true), invoker));
EXPECT_FALSE(bridging::fromJs<bool>(rt, jsi::Value(false), invoker));
EXPECT_JSI_THROW(bridging::fromJs<bool>(rt, jsi::Value(1), invoker));
EXPECT_TRUE(bridging::toJs(rt, true));
EXPECT_FALSE(bridging::toJs(rt, false));
}
TEST_F(BridgingTest, numberTest) {
EXPECT_EQ(1, bridging::fromJs<int>(rt, jsi::Value(1), invoker));
EXPECT_FLOAT_EQ(1.2f, bridging::fromJs<float>(rt, jsi::Value(1.2), invoker));
EXPECT_DOUBLE_EQ(1.2, bridging::fromJs<double>(rt, jsi::Value(1.2), invoker));
EXPECT_JSI_THROW(bridging::fromJs<double>(rt, jsi::Value(true), invoker));
EXPECT_EQ(1, static_cast<int>(bridging::toJs(rt, 1)));
EXPECT_FLOAT_EQ(1.2f, static_cast<float>(bridging::toJs(rt, 1.2f)));
EXPECT_DOUBLE_EQ(1.2, bridging::toJs(rt, 1.2));
EXPECT_EQ(
42,
static_cast<uint32_t>(
bridging::toJs(rt, static_cast<uint32_t>(42)).asNumber()));
EXPECT_EQ(
-42,
static_cast<uint32_t>(
bridging::toJs(rt, static_cast<uint32_t>(-42)).asNumber()));
EXPECT_FALSE(
-42 ==
static_cast<int32_t>(
bridging::toJs(rt, static_cast<uint32_t>(-42)).asNumber()));
}
TEST_F(BridgingTest, stringTest) {
auto string = jsi::String::createFromAscii(rt, "hello");
EXPECT_EQ("hello"s, bridging::fromJs<std::string>(rt, string, invoker));
EXPECT_JSI_THROW(bridging::fromJs<std::string>(rt, jsi::Value(1), invoker));
EXPECT_TRUE(
jsi::String::strictEquals(rt, string, bridging::toJs(rt, "hello")));
EXPECT_TRUE(
jsi::String::strictEquals(rt, string, bridging::toJs(rt, "hello"s)));
EXPECT_TRUE(
jsi::String::strictEquals(rt, string, bridging::toJs(rt, "hello"sv)));
}
TEST_F(BridgingTest, objectTest) {
auto object = jsi::Object(rt);
object.setProperty(rt, "foo", "bar");
auto omap =
bridging::fromJs<std::map<std::string, std::string>>(rt, object, invoker);
auto umap = bridging::fromJs<std::unordered_map<std::string, std::string>>(
rt, object, invoker);
EXPECT_EQ(1, omap.size());
EXPECT_EQ(1, umap.size());
EXPECT_EQ("bar"s, omap["foo"]);
EXPECT_EQ("bar"s, umap["foo"]);
EXPECT_EQ(
"bar"s,
bridging::toJs(rt, omap, invoker)
.getProperty(rt, "foo")
.asString(rt)
.utf8(rt));
EXPECT_EQ(
"bar"s,
bridging::toJs(rt, umap, invoker)
.getProperty(rt, "foo")
.asString(rt)
.utf8(rt));
}
TEST_F(BridgingTest, hostObjectTest) {
struct TestHostObject : public jsi::HostObject {
jsi::Value get(jsi::Runtime& rt, const jsi::PropNameID& name) override {
if (name.utf8(rt) == "test") {
return {1};
}
return jsi::Value::undefined();
}
};
auto hostobject = std::make_shared<TestHostObject>();
auto object = bridging::toJs(rt, hostobject);
EXPECT_EQ(1, object.getProperty(rt, "test").asNumber());
EXPECT_EQ(
hostobject, bridging::fromJs<decltype(hostobject)>(rt, object, invoker));
}
TEST_F(BridgingTest, weakbjectTest) {
auto object = jsi::Object(rt);
auto weakobject = jsi::WeakObject(rt, object);
EXPECT_TRUE(
jsi::Object::strictEquals(
rt,
object,
bridging::fromJs<jsi::WeakObject>(rt, object, invoker)
.lock(rt)
.asObject(rt)));
EXPECT_TRUE(
jsi::Object::strictEquals(
rt, object, bridging::toJs(rt, weakobject).asObject(rt)));
}
TEST_F(BridgingTest, arrayTest) {
auto vec = std::vector({"foo"s, "bar"s});
auto array = jsi::Array::createWithElements(rt, "foo", "bar");
EXPECT_EQ(
vec, bridging::fromJs<std::vector<std::string>>(rt, array, invoker));
auto arr = bridging::fromJs<std::array<std::string, 2>>(rt, array, invoker);
EXPECT_EQ(vec[0], arr[0]);
EXPECT_EQ(vec[1], arr[1]);
auto pair =
bridging::fromJs<std::pair<std::string, std::string>>(rt, array, invoker);
EXPECT_EQ(vec[0], pair.first);
EXPECT_EQ(vec[1], pair.second);
EXPECT_EQ(vec.size(), bridging::toJs(rt, vec, invoker).size(rt));
for (size_t i = 0; i < vec.size(); i++) {
EXPECT_EQ(
vec[i],
bridging::toJs(rt, vec, invoker)
.getValueAtIndex(rt, i)
.asString(rt)
.utf8(rt));
}
EXPECT_EQ(2, bridging::toJs(rt, std::make_pair(1, "2"), invoker).size(rt));
EXPECT_EQ(2, bridging::toJs(rt, std::make_tuple(1, "2"), invoker).size(rt));
EXPECT_EQ(2, bridging::toJs(rt, std::array<int, 2>{1, 2}, invoker).size(rt));
EXPECT_EQ(
2,
bridging::toJs(rt, std::array<std::string, 2>{"1", "2"}, invoker)
.size(rt));
EXPECT_EQ(2, bridging::toJs(rt, std::deque<int>{1, 2}, invoker).size(rt));
EXPECT_EQ(2, bridging::toJs(rt, std::list<int>{1, 2}, invoker).size(rt));
EXPECT_EQ(
2,
bridging::toJs(rt, std::initializer_list<int>{1, 2}, invoker).size(rt));
std::vector<std::array<std::string, 2>> headers{
{"foo", "bar"}, {"baz", "qux"}};
auto jsiHeaders = bridging::toJs(rt, headers, invoker);
EXPECT_EQ(headers.size(), jsiHeaders.size(rt));
}
TEST_F(BridgingTest, functionTest) {
auto object = jsi::Object(rt);
object.setProperty(rt, "foo", "bar");
auto lambda = [](std::map<std::string, std::string> map, std::string key) {
return map[key];
};
auto func = bridging::toJs(rt, lambda, invoker);
EXPECT_EQ(
"bar"s,
func.call(rt, object, jsi::String::createFromAscii(rt, "foo"))
.asString(rt)
.utf8(rt));
// Should throw if not enough arguments are passed or are the wrong types.
EXPECT_JSI_THROW(func.call(rt, object));
EXPECT_JSI_THROW(func.call(rt, object, jsi::Value(1)));
// Test with non-capturing lambda converted to function pointer.
func = bridging::toJs(rt, +lambda, invoker);
EXPECT_EQ(
"bar"s,
func.call(rt, object, jsi::String::createFromAscii(rt, "foo"))
.asString(rt)
.utf8(rt));
}
TEST_F(BridgingTest, syncCallbackTest) {
auto fn = function("(a, b) => a + b");
auto cb = bridging::fromJs<SyncCallback<std::string(std::string, int)>>(
rt, fn, invoker);
auto foo = "foo"s;
EXPECT_EQ("foo1"s, cb(foo, 1)); // Tests lvalue string
EXPECT_EQ("bar2", cb("bar", 2)); // Tests rvalue C string
EXPECT_TRUE(fn.isFunction(rt)); // Ensure the function wasn't invalidated.
}
TEST_F(BridgingTest, syncCallbackImplicitBridgingTest) {
{ // Value
auto fn = function("(a, b) => a + b");
auto cb = bridging::fromJs<SyncCallback<std::string(jsi::Value, int)>>(
rt, fn, invoker);
jsi::Value foo(jsi::String::createFromAscii(rt, "foo"));
EXPECT_EQ(cb(std::move(foo), 1), "foo1");
EXPECT_EQ(cb(jsi::String::createFromAscii(rt, "bar"), 2), "bar2");
EXPECT_TRUE(fn.isFunction(rt));
}
{ // Object
auto fn = function("(a, b) => a.obj + b");
auto cb = bridging::fromJs<SyncCallback<std::string(jsi::Object, int)>>(
rt, fn, invoker);
jsi::Object foo(rt);
foo.setProperty(rt, "obj", "foo");
EXPECT_EQ(cb(std::move(foo), 1), "foo1");
EXPECT_TRUE(fn.isFunction(rt));
}
{ // String
auto fn = function("(a, b) => a + b");
auto cb = bridging::fromJs<SyncCallback<std::string(jsi::String, int)>>(
rt, fn, invoker);
jsi::String foo(jsi::String::createFromAscii(rt, "foo"));
EXPECT_EQ(cb(std::move(foo), 1), "foo1");
EXPECT_EQ(cb(jsi::String::createFromAscii(rt, "bar"), 2), "bar2");
EXPECT_TRUE(fn.isFunction(rt));
}
{ // Array
auto fn = function("(a, b) => a[0] + b");
auto cb = bridging::fromJs<SyncCallback<std::string(jsi::Array, int)>>(
rt, fn, invoker);
jsi::Array foo(rt, 1);
foo.setValueAtIndex(rt, 0, jsi::String::createFromAscii(rt, "foo"));
EXPECT_EQ(cb(std::move(foo), 1), "foo1");
EXPECT_TRUE(fn.isFunction(rt));
}
}
TEST_F(BridgingTest, asyncCallbackTest) {
std::string output;
auto func = std::function<void(std::string)>([&](auto str) { output = str; });
auto cb = bridging::fromJs<AsyncCallback<decltype(func), std::string>>(
rt, function("(func, str) => func(str)"), invoker);
cb(func, "hello");
flushQueue(); // Run scheduled async work
EXPECT_EQ("hello"s, output);
// Test with lambda invocation
cb.call([func, jsInvoker = invoker](jsi::Runtime& rt, jsi::Function& f) {
f.call(
rt,
bridging::toJs(rt, func, jsInvoker),
bridging::toJs(rt, "hello again", jsInvoker));
});
flushQueue();
EXPECT_EQ("hello again"s, output);
}
TEST_F(BridgingTest, asyncCallbackInvalidation) {
std::string output;
std::function<void(std::string)> func = [&](auto str) { output = str; };
auto jsCallback = bridging::fromJs<AsyncCallback<>>(
rt, bridging::toJs(rt, func, invoker), invoker);
jsCallback.call(
[](jsi::Runtime& rt, jsi::Function& f) { f.call(rt, "hello"); });
// LongLivedObjectCollection goes away before callback is executed
LongLivedObjectCollection::get(rt).clear();
flushQueue();
// Assert native callback is never invoked
ASSERT_EQ(""s, output);
}
TEST_F(BridgingTest, asyncCallbackImplicitBridgingTest) {
std::string output;
auto func = std::function<void(std::string)>([&](auto str) { output = str; });
{ // Value
auto cb = bridging::fromJs<AsyncCallback<decltype(func), jsi::Value, int>>(
rt, function("(func, a, b) => func(a + b)"), invoker);
jsi::Value foo(jsi::String::createFromAscii(rt, "foo"));
cb(func, std::move(foo), 1);
flushQueue();
EXPECT_EQ(output, "foo1");
cb(func, jsi::String::createFromAscii(rt, "bar"), 2);
flushQueue();
EXPECT_EQ(output, "bar2");
output.clear();
}
{ // Object
auto cb = bridging::fromJs<AsyncCallback<decltype(func), jsi::Object, int>>(
rt, function("(func, a, b) => func(a.obj + b)"), invoker);
jsi::Object foo(rt);
foo.setProperty(rt, "obj", "foo");
cb(func, std::move(foo), 1);
flushQueue();
EXPECT_EQ(output, "foo1");
output.clear();
}
{ // String
auto cb = bridging::fromJs<AsyncCallback<decltype(func), jsi::String, int>>(
rt, function("(func, a, b) => func(a + b)"), invoker);
jsi::String foo(jsi::String::createFromAscii(rt, "foo"));
cb(func, std::move(foo), 1);
flushQueue();
EXPECT_EQ(output, "foo1");
cb(func, jsi::String::createFromAscii(rt, "bar"), 2);
flushQueue();
EXPECT_EQ(output, "bar2");
output.clear();
}
{ // Array
auto fn = function("(func, a, b) => func(a[0] + b)");
auto cb = bridging::fromJs<AsyncCallback<decltype(func), jsi::Array, int>>(
rt, fn, invoker);
jsi::Array foo(rt, 1);
foo.setValueAtIndex(rt, 0, jsi::String::createFromAscii(rt, "foo"));
cb(func, std::move(foo), 1);
flushQueue();
EXPECT_EQ(output, "foo1");
output.clear();
}
}
TEST_F(BridgingTest, promiseTest) {
auto func = function(
"(promise, obj) => {"
" promise.then("
" (res) => { obj.res = res; },"
" (err) => { obj.err = err; }"
" )"
"}");
auto promise = AsyncPromise<std::vector<std::string>>(rt, invoker);
auto output = jsi::Object(rt);
func.call(rt, bridging::toJs(rt, promise, invoker), output);
promise.resolve({"foo"s, "bar"s});
flushQueue();
EXPECT_EQ(1, output.getPropertyNames(rt).size(rt));
EXPECT_EQ(2, output.getProperty(rt, "res").asObject(rt).asArray(rt).size(rt));
EXPECT_NO_THROW(promise.resolve({"ignored"}));
EXPECT_NO_THROW(promise.reject("ignored"));
promise = AsyncPromise<std::vector<std::string>>(rt, invoker);
output = jsi::Object(rt);
func.call(rt, bridging::toJs(rt, promise, invoker), output);
promise.reject("fail");
flushQueue();
EXPECT_EQ(1, output.getPropertyNames(rt).size(rt));
EXPECT_EQ(
"fail"s,
output.getProperty(rt, "err")
.asObject(rt)
.getProperty(rt, "message")
.asString(rt)
.utf8(rt));
EXPECT_NO_THROW(promise.resolve({"ignored"}));
EXPECT_NO_THROW(promise.reject("ignored"));
}
using EventType = std::vector<std::string>;
using EventSubscriptionsWithLastEvent =
std::vector<std::pair<jsi::Object, std::shared_ptr<EventType>>>;
namespace {
template <typename EventType>
void addEventSubscription(
jsi::Runtime& rt,
const AsyncEventEmitter<EventType>& eventEmitter,
EventSubscriptionsWithLastEvent& eventSubscriptionsWithListener,
const std::shared_ptr<TestCallInvoker>& invoker) {
auto eventEmitterJs = bridging::toJs(rt, eventEmitter, invoker);
auto lastEvent = std::make_shared<EventType>();
auto listenJs = bridging::toJs(
rt,
[lastEvent = lastEvent](const EventType& event) { *lastEvent = event; },
invoker);
eventSubscriptionsWithListener.emplace_back(
std::make_pair(
jsi::Object(eventEmitterJs.asFunction(rt)
.callWithThis(rt, eventEmitterJs, listenJs)
.asObject(rt)),
std::move(lastEvent)));
}
} // namespace
TEST_F(BridgingTest, eventEmitterTest) {
EventSubscriptionsWithLastEvent eventSubscriptionsWithListener;
AsyncEventEmitter<EventType> eventEmitter;
EXPECT_NO_THROW(eventEmitter.emit({"one", "two", "three"}));
EXPECT_EQ(0, eventSubscriptionsWithListener.size());
// register 3 JavaScript listeners to the event emitter
for (int i = 0; i < 3; ++i) {
addEventSubscription<EventType>(
rt, eventEmitter, eventSubscriptionsWithListener, invoker);
}
EXPECT_TRUE(eventEmitter.state_->listeners.contains(0));
EXPECT_TRUE(eventEmitter.state_->listeners.contains(1));
EXPECT_TRUE(eventEmitter.state_->listeners.contains(2));
// emit with args
EXPECT_NO_THROW(eventEmitter.emit({"four", "five", "six"}));
flushQueue();
// verify all listeners received the event
for (const auto& [_, lastEvent] : eventSubscriptionsWithListener) {
EXPECT_EQ(3, lastEvent->size());
EXPECT_EQ("four", lastEvent->at(0));
EXPECT_EQ("five", lastEvent->at(1));
EXPECT_EQ("six", lastEvent->at(2));
}
// Remove 2nd eventSubscriptions
eventSubscriptionsWithListener[1]
.first.getPropertyAsFunction(rt, "remove")
.callWithThis(rt, eventSubscriptionsWithListener[1].first);
eventSubscriptionsWithListener.erase(
eventSubscriptionsWithListener.begin() + 1);
// Add 4th and 5th eventSubscriptions
addEventSubscription<EventType>(
rt, eventEmitter, eventSubscriptionsWithListener, invoker);
addEventSubscription<EventType>(
rt, eventEmitter, eventSubscriptionsWithListener, invoker);
EXPECT_TRUE(eventEmitter.state_->listeners.contains(0));
EXPECT_FALSE(eventEmitter.state_->listeners.contains(1));
EXPECT_TRUE(eventEmitter.state_->listeners.contains(2));
EXPECT_TRUE(eventEmitter.state_->listeners.contains(3));
EXPECT_TRUE(eventEmitter.state_->listeners.contains(4));
// Emit more events
EXPECT_NO_THROW(eventEmitter.emit({"seven", "eight", "nine"}));
flushQueue();
for (const auto& [_, lastEvent] : eventSubscriptionsWithListener) {
EXPECT_EQ(3, lastEvent->size());
EXPECT_EQ("seven", lastEvent->at(0));
EXPECT_EQ("eight", lastEvent->at(1));
EXPECT_EQ("nine", lastEvent->at(2));
}
// clean-up the event subscriptions
for (const auto& [eventSubscription, _] : eventSubscriptionsWithListener) {
eventSubscription.getPropertyAsFunction(rt, "remove")
.callWithThis(rt, eventSubscription);
}
flushQueue();
// Emit with function
EXPECT_NO_THROW(eventEmitter.emit(
[jsInvoker = invoker,
value = {"ten", "eleven", "twelve"}](jsi::Runtime& rt) -> jsi::Value {
return bridging::toJs(rt, value, jsInvoker);
}));
flushQueue();
// no new data as listeners had been removed
for (const auto& [_, lastEvent] : eventSubscriptionsWithListener) {
EXPECT_EQ(3, lastEvent->size());
EXPECT_EQ("seven", lastEvent->at(0));
EXPECT_EQ("eight", lastEvent->at(1));
EXPECT_EQ("nine", lastEvent->at(2));
}
}
TEST_F(BridgingTest, optionalTest) {
EXPECT_EQ(
1, bridging::fromJs<std::optional<int>>(rt, jsi::Value(1), invoker));
EXPECT_EQ(
1,
bridging::fromJs<std::optional<int>>(
rt, std::make_optional(jsi::Value(1)), invoker));
EXPECT_EQ(
"hi"s,
bridging::fromJs<std::optional<std::string>>(
rt,
std::make_optional(jsi::String::createFromAscii(rt, "hi")),
invoker));
EXPECT_FALSE(
bridging::fromJs<std::optional<int>>(rt, jsi::Value::undefined(), invoker)
.has_value());
EXPECT_FALSE(
bridging::fromJs<std::optional<int>>(rt, jsi::Value::null(), invoker)
.has_value());
EXPECT_TRUE(bridging::toJs(rt, std::optional<int>(), invoker).isNull());
EXPECT_EQ(1, bridging::toJs(rt, std::optional<int>(1), invoker).asNumber());
}
TEST_F(BridgingTest, pointerTest) {
auto str = "hi"s;
auto unique = std::make_unique<std::string>(str);
auto shared = std::make_shared<std::string>(str);
auto weak = std::weak_ptr<std::string>(shared);
EXPECT_EQ(str, bridging::toJs(rt, unique, invoker).asString(rt).utf8(rt));
EXPECT_EQ(str, bridging::toJs(rt, shared, invoker).asString(rt).utf8(rt));
EXPECT_EQ(str, bridging::toJs(rt, weak, invoker).asString(rt).utf8(rt));
shared.reset();
EXPECT_TRUE(bridging::toJs(rt, weak, invoker).isNull());
}
TEST_F(BridgingTest, supportTest) {
// Ensure sure can convert some basic types, including primitives that can be
// trivially converted to JSI values.
EXPECT_TRUE((bridging::supportsFromJs<bool>));
EXPECT_TRUE((bridging::supportsFromJs<bool, bool>));
EXPECT_TRUE((bridging::supportsFromJs<bool, jsi::Value&>));
EXPECT_TRUE((bridging::supportsFromJs<int>));
EXPECT_TRUE((bridging::supportsFromJs<int, int>));
EXPECT_TRUE((bridging::supportsFromJs<int, jsi::Value&>));
EXPECT_TRUE((bridging::supportsFromJs<double>));
EXPECT_TRUE((bridging::supportsFromJs<double, double>));
EXPECT_TRUE((bridging::supportsFromJs<double, jsi::Value&>));
EXPECT_TRUE((bridging::supportsFromJs<std::string>));
EXPECT_TRUE((bridging::supportsFromJs<std::string, jsi::String>));
EXPECT_TRUE((bridging::supportsFromJs<std::string, jsi::String&>));
EXPECT_TRUE((bridging::supportsFromJs<std::set<int>, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<std::set<int>, jsi::Array&>));
EXPECT_TRUE((bridging::supportsFromJs<std::vector<int>, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<std::vector<int>, jsi::Array&>));
EXPECT_TRUE((
bridging::
supportsFromJs<std::vector<std::array<std::string, 2>>, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<
std::vector<std::array<std::string, 2>>,
jsi::Array&>));
EXPECT_TRUE(
(bridging::supportsFromJs<std::map<std::string, int>, jsi::Object>));
EXPECT_TRUE(
(bridging::supportsFromJs<std::map<std::string, int>, jsi::Object&>));
// Ensure incompatible conversions will fail.
EXPECT_FALSE((bridging::supportsFromJs<bool, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<bool, jsi::String&>));
EXPECT_FALSE((bridging::supportsFromJs<int, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<int, jsi::String&>));
EXPECT_FALSE((bridging::supportsFromJs<double, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<double, jsi::String&>));
EXPECT_FALSE((bridging::supportsFromJs<bool, jsi::Object>));
EXPECT_FALSE((bridging::supportsFromJs<bool, jsi::Object&>));
EXPECT_FALSE((bridging::supportsFromJs<int, jsi::Object>));
EXPECT_FALSE((bridging::supportsFromJs<int, jsi::Object&>));
EXPECT_FALSE((bridging::supportsFromJs<double, jsi::Object>));
EXPECT_FALSE((bridging::supportsFromJs<double, jsi::Object&>));
EXPECT_FALSE((bridging::supportsFromJs<std::string, jsi::Object>));
EXPECT_FALSE((bridging::supportsFromJs<std::string, jsi::Object&>));
EXPECT_FALSE((bridging::supportsFromJs<std::set<int>, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<std::set<int>, jsi::String&>));
EXPECT_FALSE((bridging::supportsFromJs<std::vector<int>, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<std::vector<int>, jsi::String&>));
// Ensure copying and down casting JSI values is also supported.
EXPECT_TRUE((bridging::supportsFromJs<jsi::Value>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Value, jsi::Value&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::String>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::String, jsi::String>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::String, jsi::String&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Object&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Array&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Function>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Function&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array, jsi::Array&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array, jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array, jsi::Object&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function, jsi::Function>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function, jsi::Function&>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function, jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function, jsi::Object&>));
// Ensure incorrect casts will fail.
EXPECT_FALSE((bridging::supportsFromJs<jsi::Array, jsi::Function>));
EXPECT_FALSE((bridging::supportsFromJs<jsi::Array, jsi::Function&>));
EXPECT_FALSE((bridging::supportsFromJs<jsi::Function, jsi::Array>));
EXPECT_FALSE((bridging::supportsFromJs<jsi::Function, jsi::Array&>));
// Ensure we can create HighResTimeStamp and HighResDuration from JSI
// values.
EXPECT_TRUE((bridging::supportsFromJs<HighResTimeStamp, jsi::Value>));
EXPECT_TRUE((bridging::supportsFromJs<HighResTimeStamp, jsi::Value&>));
EXPECT_TRUE((bridging::supportsFromJs<HighResDuration, jsi::Value>));
EXPECT_TRUE((bridging::supportsFromJs<HighResDuration, jsi::Value&>));
// Ensure we can convert some basic types to JSI values.
EXPECT_TRUE((bridging::supportsToJs<bool>));
EXPECT_TRUE((bridging::supportsToJs<int>));
EXPECT_TRUE((bridging::supportsToJs<double>));
EXPECT_TRUE((bridging::supportsToJs<std::string>));
EXPECT_TRUE((bridging::supportsToJs<std::string, jsi::String>));
EXPECT_TRUE((bridging::supportsToJs<std::set<int>>));
EXPECT_TRUE((bridging::supportsToJs<std::set<int>, jsi::Array>));
EXPECT_TRUE((bridging::supportsToJs<std::vector<int>>));
EXPECT_TRUE((bridging::supportsToJs<std::vector<int>, jsi::Array>));
EXPECT_TRUE((bridging::supportsToJs<std::map<std::string, int>>));
EXPECT_TRUE(
(bridging::supportsToJs<std::map<std::string, int>, jsi::Object>));
EXPECT_TRUE((bridging::supportsToJs<void (*)()>));
EXPECT_TRUE((bridging::supportsToJs<void (*)(), jsi::Function>));
// Ensure invalid conversions to JSI values are not supported.
EXPECT_FALSE((bridging::supportsToJs<void*>));
EXPECT_FALSE((bridging::supportsToJs<bool, jsi::Object>));
EXPECT_FALSE((bridging::supportsToJs<int, jsi::Object>));
EXPECT_FALSE((bridging::supportsToJs<double, jsi::Object>));
EXPECT_FALSE((bridging::supportsToJs<std::string, jsi::Object>));
EXPECT_FALSE((bridging::supportsToJs<std::vector<int>, jsi::Function>));
// Ensure we can convert HighResTimeStamp and HighResDuration to
// DOMHighResTimeStamp (double).
EXPECT_TRUE((bridging::supportsToJs<HighResTimeStamp, double>));
EXPECT_TRUE((bridging::supportsToJs<HighResDuration, double>));
}
TEST_F(BridgingTest, dynamicTest) {
// Null
auto nullFromJsResult =
bridging::fromJs<folly::dynamic>(rt, jsi::Value::null(), invoker);
EXPECT_TRUE(nullFromJsResult.isNull());
auto nullToJsResult = bridging::toJs<folly::dynamic>(rt, nullptr, invoker);
EXPECT_TRUE(nullToJsResult.isNull());
// Boolean
auto booleanFromJsResult =
bridging::fromJs<folly::dynamic>(rt, jsi::Value(true), invoker);
EXPECT_TRUE(booleanFromJsResult.isBool());
EXPECT_TRUE(booleanFromJsResult.asBool());
auto booleanToJsResult = bridging::toJs<folly::dynamic>(rt, true, invoker);
EXPECT_TRUE(booleanToJsResult.isBool());
EXPECT_TRUE(booleanToJsResult.asBool());
// Number
auto numberFromJsResult =
bridging::fromJs<folly::dynamic>(rt, jsi::Value(1.2), invoker);
EXPECT_TRUE(numberFromJsResult.isNumber());
EXPECT_DOUBLE_EQ(1.2, numberFromJsResult.asDouble());
auto numberToJsResult = bridging::toJs<folly::dynamic>(rt, 1.2, invoker);
EXPECT_TRUE(numberToJsResult.isNumber());
EXPECT_DOUBLE_EQ(1.2, numberToJsResult.asNumber());
// String
auto stringFromJsResult = bridging::fromJs<folly::dynamic>(
rt, jsi::Value(jsi::String::createFromAscii(rt, "hello")), invoker);
EXPECT_TRUE(stringFromJsResult.isString());
EXPECT_EQ("hello"s, stringFromJsResult.asString());
auto stringToJsResult = bridging::toJs<folly::dynamic>(rt, "hello", invoker);
EXPECT_TRUE(stringToJsResult.isString());
EXPECT_EQ("hello"s, stringToJsResult.asString(rt).utf8(rt));
// Array
auto arrayFromJsResult = bridging::fromJs<folly::dynamic>(
rt,
jsi::Value(jsi::Array::createWithElements(rt, "foo", "bar")),
invoker);
EXPECT_TRUE(arrayFromJsResult.isArray());
EXPECT_EQ(2, arrayFromJsResult.size());
EXPECT_EQ("foo"s, arrayFromJsResult[0].asString());
EXPECT_EQ("bar"s, arrayFromJsResult[1].asString());
auto arrayToJsResult = bridging::toJs<folly::dynamic>(
rt, folly::dynamic::array("foo", "bar"), invoker);
EXPECT_TRUE(arrayToJsResult.isObject());
EXPECT_TRUE(arrayToJsResult.asObject(rt).isArray(rt));
auto arrayToJsResultArray = arrayToJsResult.asObject(rt).asArray(rt);
EXPECT_EQ(2, arrayToJsResultArray.size(rt));
EXPECT_EQ(
"foo"s,
arrayToJsResultArray.getValueAtIndex(rt, 0).asString(rt).utf8(rt));
EXPECT_EQ(
"bar"s,
arrayToJsResultArray.getValueAtIndex(rt, 1).asString(rt).utf8(rt));
// Object
auto jsiObject = jsi::Object(rt);
jsiObject.setProperty(rt, "foo", "bar");
auto objectFromJsResult = bridging::fromJs<folly::dynamic>(
rt, jsi::Value(std::move(jsiObject)), invoker);
EXPECT_TRUE(objectFromJsResult.isObject());
EXPECT_EQ(1, objectFromJsResult.size());
EXPECT_EQ("bar"s, objectFromJsResult["foo"].asString());
auto objectToJsResult = bridging::toJs<folly::dynamic>(
rt, folly::dynamic::object("foo", "bar"), invoker);
EXPECT_TRUE(objectToJsResult.isObject());
auto objectToJsResultObject = objectToJsResult.asObject(rt);
EXPECT_EQ(
"bar"s,
objectToJsResultObject.getProperty(rt, "foo").asString(rt).utf8(rt));
// Undefined
auto undefinedFromJsResult =
bridging::fromJs<folly::dynamic>(rt, jsi::Value::undefined(), invoker);
EXPECT_TRUE(undefinedFromJsResult.isNull());
}
TEST_F(BridgingTest, highResTimeStampTest) {
HighResTimeStamp timestamp = HighResTimeStamp::now();
EXPECT_EQ(
timestamp,
bridging::fromJs<HighResTimeStamp>(
rt, bridging::toJs(rt, timestamp), invoker));
auto duration = HighResDuration::fromNanoseconds(1);
EXPECT_EQ(
duration,
bridging::fromJs<HighResDuration>(
rt, bridging::toJs(rt, duration), invoker));
EXPECT_EQ(1.0, bridging::toJs(rt, HighResDuration::fromNanoseconds(1e6)));
EXPECT_EQ(
1.000001, bridging::toJs(rt, HighResDuration::fromNanoseconds(1e6 + 1)));
}
} // namespace facebook::react

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <ReactCommon/TestCallInvoker.h>
#include <gtest/gtest.h>
#include <hermes/hermes.h>
#include <react/bridging/Bridging.h>
#define EXPECT_JSI_THROW(expr) EXPECT_THROW((expr), facebook::jsi::JSIException)
namespace facebook::react {
class BridgingTest : public ::testing::Test {
public:
BridgingTest(BridgingTest &other) = delete;
BridgingTest &operator=(BridgingTest &other) = delete;
BridgingTest(BridgingTest &&other) = delete;
BridgingTest &operator=(BridgingTest &&other) = delete;
protected:
BridgingTest()
: runtime(
hermes::makeHermesRuntime(
::hermes::vm::RuntimeConfig::Builder()
// Make promises work with Hermes microtasks.
.withMicrotaskQueue(true)
.build())),
rt(*runtime),
invoker(std::make_shared<TestCallInvoker>(*runtime))
{
}
~BridgingTest() override
{
LongLivedObjectCollection::get(rt).clear();
}
void TearDown() override
{
flushQueue();
// After flushing the invoker queue, we shouldn't leak memory.
EXPECT_EQ(0, LongLivedObjectCollection::get(rt).size());
}
jsi::Value eval(const std::string &js)
{
return rt.global().getPropertyAsFunction(rt, "eval").call(rt, js);
}
jsi::Function function(const std::string &js)
{
return eval(("(" + js + ")").c_str()).getObject(rt).getFunction(rt);
}
void flushQueue()
{
invoker->flushQueue();
}
std::shared_ptr<jsi::Runtime> runtime;
jsi::Runtime &rt;
std::shared_ptr<TestCallInvoker> invoker;
};
} // namespace facebook::react

View File

@@ -0,0 +1,160 @@
/*
* 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 "BridgingTest.h"
#include <utility>
namespace facebook::react {
using namespace std::literals;
struct TestClass {
explicit TestClass(std::shared_ptr<CallInvoker> invoker)
: invoker_(std::move(invoker)) {}
double add(jsi::Runtime& /*unused*/, int a, float b) {
return a + b;
}
jsi::Object getObject(jsi::Runtime& /*unused*/, jsi::Object obj) {
return obj;
}
AsyncPromise<std::string> getPromise(jsi::Runtime& rt, std::string result) {
auto promise = AsyncPromise<std::string>(rt, invoker_);
promise.resolve(std::move(result));
return promise;
}
std::string callFunc(
jsi::Runtime& /*unused*/,
SyncCallback<std::string(int)> func,
int num) {
return func(num);
}
void callAsync(jsi::Runtime& /*unused*/, const AsyncCallback<>& callback) {
callback();
}
private:
std::shared_ptr<CallInvoker> invoker_;
};
TEST_F(BridgingTest, callFromJsTest) {
auto instance = TestClass(invoker);
EXPECT_EQ(
3.0,
bridging::callFromJs<double>(
rt, &TestClass::add, invoker, &instance, 1, 2.0));
auto object = jsi::Object(rt);
EXPECT_TRUE(
jsi::Object::strictEquals(
rt,
object,
bridging::callFromJs<jsi::Object>(
rt, &TestClass::getObject, invoker, &instance, object)));
auto promise = bridging::callFromJs<jsi::Object>(
rt,
&TestClass::getPromise,
invoker,
&instance,
jsi::String::createFromAscii(rt, "hi"));
auto then = promise.getPropertyAsFunction(rt, "then");
std::string result;
then.callWithThis(
rt,
promise,
bridging::toJs(
rt, [&](std::string res) { result = std::move(res); }, invoker));
flushQueue();
EXPECT_EQ("hi"s, result);
auto func = function("(num) => String(num)");
EXPECT_EQ(
"1"s,
bridging::callFromJs<jsi::String>(
rt, &TestClass::callFunc, invoker, &instance, func, 1)
.utf8(rt));
bool called = false;
func = bridging::toJs(rt, [&] { called = true; }, invoker);
bridging::callFromJs<void>(
rt, &TestClass::callAsync, invoker, &instance, func);
flushQueue();
EXPECT_TRUE(called);
}
struct MethodReturnTypeCastingTestObject {
public:
explicit MethodReturnTypeCastingTestObject(int value) : value_(value) {}
int toInteger() const {
return value_;
}
private:
int value_;
};
template <>
struct Bridging<MethodReturnTypeCastingTestObject> {
static MethodReturnTypeCastingTestObject fromJs(
jsi::Runtime& /*rt*/,
const jsi::Value& value) {
return MethodReturnTypeCastingTestObject(
static_cast<int>(value.asNumber()));
}
static int toJs(
jsi::Runtime& /*rt*/,
const MethodReturnTypeCastingTestObject& value) {
return value.toInteger();
}
};
struct MethodReturnTypeCastingTestClass {
explicit MethodReturnTypeCastingTestClass(
std::shared_ptr<CallInvoker> invoker)
: invoker_(std::move(invoker)) {}
// This is the key, return type is not a primitive, but an object with defined
// bridging template.
MethodReturnTypeCastingTestObject
add(jsi::Runtime& /*unused*/, int a, int b) {
return MethodReturnTypeCastingTestObject(a + b);
}
private:
std::shared_ptr<CallInvoker> invoker_;
};
TEST_F(BridgingTest, methodReturnTypeCastingTest) {
auto instance = MethodReturnTypeCastingTestClass(invoker);
EXPECT_EQ(
2,
bridging::callFromJs<int>(
rt,
&MethodReturnTypeCastingTestClass::add,
invoker,
&instance,
1,
1));
}
} // namespace facebook::react

View File

@@ -0,0 +1,26 @@
# 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_debug_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_debug OBJECT ${react_debug_SRC})
target_include_directories(react_debug PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_debug folly_runtime)
if(ANDROID)
target_link_libraries(react_debug log)
endif()
target_compile_reactnative_options(react_debug PRIVATE)
target_compile_options(react_debug PRIVATE -Wpedantic)
if(NOT ${CMAKE_BUILD_TYPE} MATCHES Debug AND NOT REACT_NATIVE_DEBUG_OPTIMIZED)
target_compile_options(react_debug PUBLIC -DNDEBUG)
endif()

View File

@@ -0,0 +1,34 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
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-debug"
s.version = version
s.summary = "-" # TODO
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("**/*.{cpp,h}", "**/*.h")
s.header_dir = "react/debug"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"DEFINES_MODULE" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_debug")
end

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.
*/
#pragma once
//
// Preprocessor flags which control whether code meant for debugging the
// internals of React Native is included in the build. E.g. debug assertions.
//
// This flag is normally derived from NDEBUG, but may be set explicitly by
// defining `REACT_NATIVE_DEBUG` or `REACT_NATIVE_PRODUCTION`.
#if !(defined(REACT_NATIVE_DEBUG) || defined(REACT_NATIVE_PRODUCTION))
#ifdef NDEBUG
#define REACT_NATIVE_PRODUCTION
#else
#define REACT_NATIVE_DEBUG
#endif
#endif

View File

@@ -0,0 +1,45 @@
/*
* 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.
*/
#ifdef __ANDROID__
#include <android/log.h>
// Provide a prototype to silence missing prototype warning in release
// mode.
extern "C" void react_native_assert_fail(
const char* func,
const char* file,
int line,
const char* expr);
extern "C" void react_native_assert_fail(
const char* func,
const char* file,
int line,
const char* expr) {
// Print as an error so it shows up in logcat before crash...
__android_log_print(
ANDROID_LOG_ERROR,
"ReactNative",
"%s:%d: function %s: assertion failed (%s)",
file,
line,
func,
expr);
// ...and trigger an abort so it crashes and shows up in uploaded logs.
__android_log_assert(
nullptr,
"ReactNative",
"%s:%d: function %s: assertion failed (%s)",
file,
line,
func,
expr);
}
#endif // __ANDROID__

View File

@@ -0,0 +1,67 @@
/*
* 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.
*/
// No header guards since it is legitimately possible to include this file more
// than once with and without REACT_NATIVE_DEBUG.
// react_native_assert allows us to opt-in to specific asserts on Android and
// test before moving on. When all issues have been found, maybe we can use
// `UNDEBUG` flag to disable NDEBUG in debug builds on Android.
// Asserting is appropriate for conditions that:
// 1. May or may not be recoverable, and
// 2. imply there is a bug in React Native when violated.
// For recoverable conditions that can be violated by user mistake (e.g. JS
// code passes an unexpected prop value), consider react_native_expect instead.
#pragma once
#include "flags.h"
#undef react_native_assert
#ifndef REACT_NATIVE_DEBUG
#define react_native_assert(e) ((void)0)
#else // REACT_NATIVE_DEBUG
#ifdef __ANDROID__
#include <android/log.h>
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
void react_native_assert_fail(const char *func, const char *file, int line, const char *expr);
#ifdef __cplusplus
}
#endif // __cpusplus
#define react_native_assert(e) ((e) ? (void)0 : react_native_assert_fail(__func__, __FILE__, __LINE__, #e))
#else // __ANDROID__
#include <glog/logging.h>
#include <cassert>
// For all platforms, but iOS+Xcode especially: flush logs because some might be
// lost on iOS if an assert is hit right after this. If you are trying to debug
// something actively and have added lots of LOG statements to track down an
// issue, there is race between flushing the final logs and stopping execution
// when the assert hits. Thus, if we know an assert will fail, we force flushing
// to happen right before the assert.
#define react_native_assert(cond) \
if (!(cond)) { \
LOG(ERROR) << "react_native_assert failure: " << #cond; \
google::FlushLogFiles(google::GLOG_INFO); \
assert(cond); \
}
#endif // platforms besides __ANDROID__
#endif // REACT_NATIVE_DEBUG

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// No header guards since it is legitimately possible to include this file more
// than once with and without REACT_NATIVE_DEBUG.
// react_native_expect is a non-fatal counterpart of react_native_assert.
// In debug builds, when an expectation fails, we log and move on.
// In release builds, react_native_expect is a noop.
// react_native_expect is appropriate for recoverable conditions that can be
// violated by user mistake (e.g. JS code passes an unexpected prop value).
// To enforce invariants that are internal to React Native, consider
// react_native_assert (or a stronger mechanism).
// Calling react_native_expect does NOT, by itself, guarantee that the user
// will see a helpful diagnostic (beyond a low level log). That concern is the
// caller's responsibility.
#pragma once
#include "flags.h"
#undef react_native_expect
#ifndef REACT_NATIVE_DEBUG
#define react_native_expect(e) ((void)0)
#else // REACT_NATIVE_DEBUG
#include <glog/logging.h>
#include <cassert>
#define react_native_expect(cond) \
if (!(cond)) { \
LOG(ERROR) << "react_native_expect failure: " << #cond; \
}
#endif // REACT_NATIVE_DEBUG

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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_featureflags_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_featureflags OBJECT ${react_featureflags_SRC})
target_include_directories(react_featureflags PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_featureflags folly_runtime)
target_compile_reactnative_options(react_featureflags PRIVATE)
target_compile_options(react_featureflags PRIVATE -Wpedantic)

View File

@@ -0,0 +1,44 @@
# 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-featureflags"
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/featureflags"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"DEFINES_MODULE" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_featureflags")
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,397 @@
/*
* 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<<6728f8cada1d0d9d21800b4fefe76b77>>
*/
/**
* 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 "ReactNativeFeatureFlags.h"
namespace facebook::react {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wglobal-constructors"
std::unique_ptr<ReactNativeFeatureFlagsAccessor> accessor_;
#pragma GCC diagnostic pop
bool ReactNativeFeatureFlags::commonTestFlag() {
return getAccessor().commonTestFlag();
}
bool ReactNativeFeatureFlags::cdpInteractionMetricsEnabled() {
return getAccessor().cdpInteractionMetricsEnabled();
}
bool ReactNativeFeatureFlags::cxxNativeAnimatedEnabled() {
return getAccessor().cxxNativeAnimatedEnabled();
}
bool ReactNativeFeatureFlags::cxxNativeAnimatedRemoveJsSync() {
return getAccessor().cxxNativeAnimatedRemoveJsSync();
}
bool ReactNativeFeatureFlags::disableEarlyViewCommandExecution() {
return getAccessor().disableEarlyViewCommandExecution();
}
bool ReactNativeFeatureFlags::disableFabricCommitInCXXAnimated() {
return getAccessor().disableFabricCommitInCXXAnimated();
}
bool ReactNativeFeatureFlags::disableMountItemReorderingAndroid() {
return getAccessor().disableMountItemReorderingAndroid();
}
bool ReactNativeFeatureFlags::disableOldAndroidAttachmentMetricsWorkarounds() {
return getAccessor().disableOldAndroidAttachmentMetricsWorkarounds();
}
bool ReactNativeFeatureFlags::disableTextLayoutManagerCacheAndroid() {
return getAccessor().disableTextLayoutManagerCacheAndroid();
}
bool ReactNativeFeatureFlags::enableAccessibilityOrder() {
return getAccessor().enableAccessibilityOrder();
}
bool ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid() {
return getAccessor().enableAccumulatedUpdatesInRawPropsAndroid();
}
bool ReactNativeFeatureFlags::enableAndroidLinearText() {
return getAccessor().enableAndroidLinearText();
}
bool ReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations() {
return getAccessor().enableAndroidTextMeasurementOptimizations();
}
bool ReactNativeFeatureFlags::enableBridgelessArchitecture() {
return getAccessor().enableBridgelessArchitecture();
}
bool ReactNativeFeatureFlags::enableCppPropsIteratorSetter() {
return getAccessor().enableCppPropsIteratorSetter();
}
bool ReactNativeFeatureFlags::enableCustomFocusSearchOnClippedElementsAndroid() {
return getAccessor().enableCustomFocusSearchOnClippedElementsAndroid();
}
bool ReactNativeFeatureFlags::enableDestroyShadowTreeRevisionAsync() {
return getAccessor().enableDestroyShadowTreeRevisionAsync();
}
bool ReactNativeFeatureFlags::enableDoubleMeasurementFixAndroid() {
return getAccessor().enableDoubleMeasurementFixAndroid();
}
bool ReactNativeFeatureFlags::enableEagerMainQueueModulesOnIOS() {
return getAccessor().enableEagerMainQueueModulesOnIOS();
}
bool ReactNativeFeatureFlags::enableEagerRootViewAttachment() {
return getAccessor().enableEagerRootViewAttachment();
}
bool ReactNativeFeatureFlags::enableFabricLogs() {
return getAccessor().enableFabricLogs();
}
bool ReactNativeFeatureFlags::enableFabricRenderer() {
return getAccessor().enableFabricRenderer();
}
bool ReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout() {
return getAccessor().enableFontScaleChangesUpdatingLayout();
}
bool ReactNativeFeatureFlags::enableIOSTextBaselineOffsetPerLine() {
return getAccessor().enableIOSTextBaselineOffsetPerLine();
}
bool ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox() {
return getAccessor().enableIOSViewClipToPaddingBox();
}
bool ReactNativeFeatureFlags::enableImagePrefetchingAndroid() {
return getAccessor().enableImagePrefetchingAndroid();
}
bool ReactNativeFeatureFlags::enableImagePrefetchingOnUiThreadAndroid() {
return getAccessor().enableImagePrefetchingOnUiThreadAndroid();
}
bool ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges() {
return getAccessor().enableImmediateUpdateModeForContentOffsetChanges();
}
bool ReactNativeFeatureFlags::enableImperativeFocus() {
return getAccessor().enableImperativeFocus();
}
bool ReactNativeFeatureFlags::enableInteropViewManagerClassLookUpOptimizationIOS() {
return getAccessor().enableInteropViewManagerClassLookUpOptimizationIOS();
}
bool ReactNativeFeatureFlags::enableIntersectionObserverByDefault() {
return getAccessor().enableIntersectionObserverByDefault();
}
bool ReactNativeFeatureFlags::enableKeyEvents() {
return getAccessor().enableKeyEvents();
}
bool ReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid() {
return getAccessor().enableLayoutAnimationsOnAndroid();
}
bool ReactNativeFeatureFlags::enableLayoutAnimationsOnIOS() {
return getAccessor().enableLayoutAnimationsOnIOS();
}
bool ReactNativeFeatureFlags::enableMainQueueCoordinatorOnIOS() {
return getAccessor().enableMainQueueCoordinatorOnIOS();
}
bool ReactNativeFeatureFlags::enableModuleArgumentNSNullConversionIOS() {
return getAccessor().enableModuleArgumentNSNullConversionIOS();
}
bool ReactNativeFeatureFlags::enableNativeCSSParsing() {
return getAccessor().enableNativeCSSParsing();
}
bool ReactNativeFeatureFlags::enableNetworkEventReporting() {
return getAccessor().enableNetworkEventReporting();
}
bool ReactNativeFeatureFlags::enablePreparedTextLayout() {
return getAccessor().enablePreparedTextLayout();
}
bool ReactNativeFeatureFlags::enablePropsUpdateReconciliationAndroid() {
return getAccessor().enablePropsUpdateReconciliationAndroid();
}
bool ReactNativeFeatureFlags::enableResourceTimingAPI() {
return getAccessor().enableResourceTimingAPI();
}
bool ReactNativeFeatureFlags::enableSwiftUIBasedFilters() {
return getAccessor().enableSwiftUIBasedFilters();
}
bool ReactNativeFeatureFlags::enableViewCulling() {
return getAccessor().enableViewCulling();
}
bool ReactNativeFeatureFlags::enableViewRecycling() {
return getAccessor().enableViewRecycling();
}
bool ReactNativeFeatureFlags::enableViewRecyclingForImage() {
return getAccessor().enableViewRecyclingForImage();
}
bool ReactNativeFeatureFlags::enableViewRecyclingForScrollView() {
return getAccessor().enableViewRecyclingForScrollView();
}
bool ReactNativeFeatureFlags::enableViewRecyclingForText() {
return getAccessor().enableViewRecyclingForText();
}
bool ReactNativeFeatureFlags::enableViewRecyclingForView() {
return getAccessor().enableViewRecyclingForView();
}
bool ReactNativeFeatureFlags::enableVirtualViewClippingWithoutScrollViewClipping() {
return getAccessor().enableVirtualViewClippingWithoutScrollViewClipping();
}
bool ReactNativeFeatureFlags::enableVirtualViewContainerStateExperimental() {
return getAccessor().enableVirtualViewContainerStateExperimental();
}
bool ReactNativeFeatureFlags::enableVirtualViewDebugFeatures() {
return getAccessor().enableVirtualViewDebugFeatures();
}
bool ReactNativeFeatureFlags::enableVirtualViewRenderState() {
return getAccessor().enableVirtualViewRenderState();
}
bool ReactNativeFeatureFlags::enableVirtualViewWindowFocusDetection() {
return getAccessor().enableVirtualViewWindowFocusDetection();
}
bool ReactNativeFeatureFlags::enableWebPerformanceAPIsByDefault() {
return getAccessor().enableWebPerformanceAPIsByDefault();
}
bool ReactNativeFeatureFlags::fixMappingOfEventPrioritiesBetweenFabricAndReact() {
return getAccessor().fixMappingOfEventPrioritiesBetweenFabricAndReact();
}
bool ReactNativeFeatureFlags::fuseboxAssertSingleHostState() {
return getAccessor().fuseboxAssertSingleHostState();
}
bool ReactNativeFeatureFlags::fuseboxEnabledRelease() {
return getAccessor().fuseboxEnabledRelease();
}
bool ReactNativeFeatureFlags::fuseboxNetworkInspectionEnabled() {
return getAccessor().fuseboxNetworkInspectionEnabled();
}
bool ReactNativeFeatureFlags::hideOffscreenVirtualViewsOnIOS() {
return getAccessor().hideOffscreenVirtualViewsOnIOS();
}
bool ReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid() {
return getAccessor().overrideBySynchronousMountPropsAtMountingAndroid();
}
bool ReactNativeFeatureFlags::perfIssuesEnabled() {
return getAccessor().perfIssuesEnabled();
}
bool ReactNativeFeatureFlags::perfMonitorV2Enabled() {
return getAccessor().perfMonitorV2Enabled();
}
double ReactNativeFeatureFlags::preparedTextCacheSize() {
return getAccessor().preparedTextCacheSize();
}
bool ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion() {
return getAccessor().preventShadowTreeCommitExhaustion();
}
bool ReactNativeFeatureFlags::shouldPressibilityUseW3CPointerEventsForHover() {
return getAccessor().shouldPressibilityUseW3CPointerEventsForHover();
}
bool ReactNativeFeatureFlags::shouldTriggerResponderTransferOnScrollAndroid() {
return getAccessor().shouldTriggerResponderTransferOnScrollAndroid();
}
bool ReactNativeFeatureFlags::skipActivityIdentityAssertionOnHostPause() {
return getAccessor().skipActivityIdentityAssertionOnHostPause();
}
bool ReactNativeFeatureFlags::sweepActiveTouchOnChildNativeGesturesAndroid() {
return getAccessor().sweepActiveTouchOnChildNativeGesturesAndroid();
}
bool ReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid() {
return getAccessor().traceTurboModulePromiseRejectionsOnAndroid();
}
bool ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommit() {
return getAccessor().updateRuntimeShadowNodeReferencesOnCommit();
}
bool ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling() {
return getAccessor().useAlwaysAvailableJSErrorHandling();
}
bool ReactNativeFeatureFlags::useFabricInterop() {
return getAccessor().useFabricInterop();
}
bool ReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid() {
return getAccessor().useNativeEqualsInNativeReadableArrayAndroid();
}
bool ReactNativeFeatureFlags::useNativeTransformHelperAndroid() {
return getAccessor().useNativeTransformHelperAndroid();
}
bool ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode() {
return getAccessor().useNativeViewConfigsInBridgelessMode();
}
bool ReactNativeFeatureFlags::useOptimizedEventBatchingOnAndroid() {
return getAccessor().useOptimizedEventBatchingOnAndroid();
}
bool ReactNativeFeatureFlags::useRawPropsJsiValue() {
return getAccessor().useRawPropsJsiValue();
}
bool ReactNativeFeatureFlags::useShadowNodeStateOnClone() {
return getAccessor().useShadowNodeStateOnClone();
}
bool ReactNativeFeatureFlags::useSharedAnimatedBackend() {
return getAccessor().useSharedAnimatedBackend();
}
bool ReactNativeFeatureFlags::useTraitHiddenOnAndroid() {
return getAccessor().useTraitHiddenOnAndroid();
}
bool ReactNativeFeatureFlags::useTurboModuleInterop() {
return getAccessor().useTurboModuleInterop();
}
bool ReactNativeFeatureFlags::useTurboModules() {
return getAccessor().useTurboModules();
}
double ReactNativeFeatureFlags::viewCullingOutsetRatio() {
return getAccessor().viewCullingOutsetRatio();
}
double ReactNativeFeatureFlags::virtualViewHysteresisRatio() {
return getAccessor().virtualViewHysteresisRatio();
}
double ReactNativeFeatureFlags::virtualViewPrerenderRatio() {
return getAccessor().virtualViewPrerenderRatio();
}
void ReactNativeFeatureFlags::override(
std::unique_ptr<ReactNativeFeatureFlagsProvider> provider) {
getAccessor().override(std::move(provider));
}
void ReactNativeFeatureFlags::dangerouslyReset() {
accessor_ = std::make_unique<ReactNativeFeatureFlagsAccessor>();
}
std::optional<std::string> ReactNativeFeatureFlags::dangerouslyForceOverride(
std::unique_ptr<ReactNativeFeatureFlagsProvider> provider) {
auto accessor = std::make_unique<ReactNativeFeatureFlagsAccessor>();
accessor->override(std::move(provider));
std::swap(accessor_, accessor);
// Now accessor is the old accessor
return accessor == nullptr ? std::nullopt
: accessor->getAccessedFeatureFlagNames();
}
ReactNativeFeatureFlagsAccessor& ReactNativeFeatureFlags::getAccessor() {
if (accessor_ == nullptr) {
accessor_ = std::make_unique<ReactNativeFeatureFlagsAccessor>();
}
return *accessor_;
}
} // namespace facebook::react

View File

@@ -0,0 +1,522 @@
/*
* 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<<4b8574d0682b5e9644affc89559393ac>>
*/
/**
* 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
#include <react/featureflags/ReactNativeFeatureFlagsAccessor.h>
#include <react/featureflags/ReactNativeFeatureFlagsProvider.h>
#include <memory>
#include <optional>
#include <string>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
/**
* This class provides access to internal React Native feature flags.
*
* All the methods are thread-safe (as long as the methods in the overridden
* provider are).
*/
class ReactNativeFeatureFlags {
public:
/**
* Common flag for testing. Do NOT modify.
*/
RN_EXPORT static bool commonTestFlag();
/**
* Enable emitting of InteractionEntry live metrics to the debugger. Requires `enableBridgelessArchitecture`.
*/
RN_EXPORT static bool cdpInteractionMetricsEnabled();
/**
* Use a C++ implementation of Native Animated instead of the platform implementation.
*/
RN_EXPORT static bool cxxNativeAnimatedEnabled();
/**
* Removes JS sync at end of native animation
*/
RN_EXPORT static bool cxxNativeAnimatedRemoveJsSync();
/**
* Dispatch view commands in mount item order.
*/
RN_EXPORT static bool disableEarlyViewCommandExecution();
/**
* Prevents use of Fabric commit in C++ Animated implementation
*/
RN_EXPORT static bool disableFabricCommitInCXXAnimated();
/**
* Prevent FabricMountingManager from reordering mountItems, which may lead to invalid state on the UI thread
*/
RN_EXPORT static bool disableMountItemReorderingAndroid();
/**
* Disable some workarounds for old Android versions in TextLayoutManager logic for retrieving attachment metrics
*/
RN_EXPORT static bool disableOldAndroidAttachmentMetricsWorkarounds();
/**
* Turns off the global measurement cache used by TextLayoutManager on Android.
*/
RN_EXPORT static bool disableTextLayoutManagerCacheAndroid();
/**
* When enabled, the accessibilityOrder prop will propagate to native platforms and define the accessibility order.
*/
RN_EXPORT static bool enableAccessibilityOrder();
/**
* When enabled, Android will accumulate updates in rawProps to reduce the number of mounting instructions for cascading re-renders.
*/
RN_EXPORT static bool enableAccumulatedUpdatesInRawPropsAndroid();
/**
* Enables linear text rendering on Android wherever subpixel text rendering is enabled
*/
RN_EXPORT static bool enableAndroidLinearText();
/**
* Enables various optimizations throughout the path of measuring text on Android.
*/
RN_EXPORT static bool enableAndroidTextMeasurementOptimizations();
/**
* Feature flag to enable the new bridgeless architecture. Note: Enabling this will force enable the following flags: `useTurboModules` & `enableFabricRenderer`.
*/
RN_EXPORT static bool enableBridgelessArchitecture();
/**
* Enable prop iterator setter-style construction of Props in C++ (this flag is not used in Java).
*/
RN_EXPORT static bool enableCppPropsIteratorSetter();
/**
* This enables the fabric implementation of focus search so that we can focus clipped elements
*/
RN_EXPORT static bool enableCustomFocusSearchOnClippedElementsAndroid();
/**
* Enables destructor calls for ShadowTreeRevision in the background to reduce UI thread work.
*/
RN_EXPORT static bool enableDestroyShadowTreeRevisionAsync();
/**
* When enabled a subset of components will avoid double measurement on Android.
*/
RN_EXPORT static bool enableDoubleMeasurementFixAndroid();
/**
* This infra allows native modules to initialize on the main thread, during React Native init.
*/
RN_EXPORT static bool enableEagerMainQueueModulesOnIOS();
/**
* Feature flag to configure eager attachment of the root view/initialisation of the JS code.
*/
RN_EXPORT static bool enableEagerRootViewAttachment();
/**
* This feature flag enables logs for Fabric.
*/
RN_EXPORT static bool enableFabricLogs();
/**
* Enables the use of the Fabric renderer in the whole app.
*/
RN_EXPORT static bool enableFabricRenderer();
/**
* Enables font scale changes updating layout for measurable nodes.
*/
RN_EXPORT static bool enableFontScaleChangesUpdatingLayout();
/**
* Applies base offset for each line of text separately on iOS.
*/
RN_EXPORT static bool enableIOSTextBaselineOffsetPerLine();
/**
* iOS Views will clip to their padding box vs border box
*/
RN_EXPORT static bool enableIOSViewClipToPaddingBox();
/**
* When enabled, Android will build and initiate image prefetch requests on ImageShadowNode::layout
*/
RN_EXPORT static bool enableImagePrefetchingAndroid();
/**
* When enabled, Android will initiate image prefetch requested on ImageShadowNode::layout on the UI thread
*/
RN_EXPORT static bool enableImagePrefetchingOnUiThreadAndroid();
/**
* Dispatches state updates for content offset changes synchronously on the main thread.
*/
RN_EXPORT static bool enableImmediateUpdateModeForContentOffsetChanges();
/**
* Enable ref.focus() and ref.blur() for all views, not just TextInput.
*/
RN_EXPORT static bool enableImperativeFocus();
/**
* This is to fix the issue with interop view manager where component descriptor lookup is causing ViewManager to preload.
*/
RN_EXPORT static bool enableInteropViewManagerClassLookUpOptimizationIOS();
/**
* Enables the IntersectionObserver Web API in React Native.
*/
RN_EXPORT static bool enableIntersectionObserverByDefault();
/**
* Enables key up/down/press events to be sent to JS from components
*/
RN_EXPORT static bool enableKeyEvents();
/**
* When enabled, LayoutAnimations API will animate state changes on Android.
*/
RN_EXPORT static bool enableLayoutAnimationsOnAndroid();
/**
* When enabled, LayoutAnimations API will animate state changes on iOS.
*/
RN_EXPORT static bool enableLayoutAnimationsOnIOS();
/**
* Make RCTUnsafeExecuteOnMainQueueSync less likely to deadlock, when used in conjuction with sync rendering/events.
*/
RN_EXPORT static bool enableMainQueueCoordinatorOnIOS();
/**
* Enable NSNull conversion when handling module arguments on iOS
*/
RN_EXPORT static bool enableModuleArgumentNSNullConversionIOS();
/**
* Parse CSS strings using the Fabric CSS parser instead of ViewConfig processing
*/
RN_EXPORT static bool enableNativeCSSParsing();
/**
* Enable network event reporting hooks in each native platform through `NetworkReporter`. This flag should be combined with `enableResourceTimingAPI` and `fuseboxNetworkInspectionEnabled` to enable end-to-end reporting behaviour via the Web Performance API and CDP debugging respectively.
*/
RN_EXPORT static bool enableNetworkEventReporting();
/**
* Enables caching text layout artifacts for later reuse
*/
RN_EXPORT static bool enablePreparedTextLayout();
/**
* When enabled, Android will receive prop updates based on the differences between the last rendered shadow node and the last committed shadow node.
*/
RN_EXPORT static bool enablePropsUpdateReconciliationAndroid();
/**
* Enables the reporting of network resource timings through `PerformanceObserver`.
*/
RN_EXPORT static bool enableResourceTimingAPI();
/**
* When enabled, it will use SwiftUI for filter effects like blur on iOS.
*/
RN_EXPORT static bool enableSwiftUIBasedFilters();
/**
* Enables View Culling: as soon as a view goes off screen, it can be reused anywhere in the UI and pieced together with other items to create new UI elements.
*/
RN_EXPORT static bool enableViewCulling();
/**
* Enables View Recycling. When enabled, individual ViewManagers must still opt-in.
*/
RN_EXPORT static bool enableViewRecycling();
/**
* Enables View Recycling for <Image> via ReactViewGroup/ReactViewManager.
*/
RN_EXPORT static bool enableViewRecyclingForImage();
/**
* Enables View Recycling for <ScrollView> via ReactViewGroup/ReactViewManager.
*/
RN_EXPORT static bool enableViewRecyclingForScrollView();
/**
* Enables View Recycling for <Text> via ReactTextView/ReactTextViewManager.
*/
RN_EXPORT static bool enableViewRecyclingForText();
/**
* Enables View Recycling for <View> via ReactViewGroup/ReactViewManager.
*/
RN_EXPORT static bool enableViewRecyclingForView();
/**
* Set clipping to drawingRect of ScrollView.
*/
RN_EXPORT static bool enableVirtualViewClippingWithoutScrollViewClipping();
/**
* Enables the experimental version of `VirtualViewContainerState`.
*/
RN_EXPORT static bool enableVirtualViewContainerStateExperimental();
/**
* Enables VirtualView debug features such as logging and overlays.
*/
RN_EXPORT static bool enableVirtualViewDebugFeatures();
/**
* Enables reading render state when dispatching VirtualView events.
*/
RN_EXPORT static bool enableVirtualViewRenderState();
/**
* Enables window focus detection for prioritizing VirtualView events.
*/
RN_EXPORT static bool enableVirtualViewWindowFocusDetection();
/**
* Enable Web Performance APIs (Performance Timeline, User Timings, etc.) by default.
*/
RN_EXPORT static bool enableWebPerformanceAPIsByDefault();
/**
* Uses the default event priority instead of the discreet event priority by default when dispatching events from Fabric to React.
*/
RN_EXPORT static bool fixMappingOfEventPrioritiesBetweenFabricAndReact();
/**
* Enable system assertion validating that Fusebox is configured with a single host. When set, the CDP backend will dynamically disable features (Perf and Network) in the event that multiple hosts are registered (undefined behaviour), and broadcast this over `ReactNativeApplication.systemStateChanged`.
*/
RN_EXPORT static bool fuseboxAssertSingleHostState();
/**
* Flag determining if the React Native DevTools (Fusebox) CDP backend should be enabled in release builds. This flag is global and should not be changed across React Host lifetimes.
*/
RN_EXPORT static bool fuseboxEnabledRelease();
/**
* Enable network inspection support in the React Native DevTools CDP backend. Requires `enableBridgelessArchitecture`. This flag is global and should not be changed across React Host lifetimes.
*/
RN_EXPORT static bool fuseboxNetworkInspectionEnabled();
/**
* Hides offscreen VirtualViews on iOS by setting hidden = YES to avoid extra cost of views
*/
RN_EXPORT static bool hideOffscreenVirtualViewsOnIOS();
/**
* Override props at mounting with synchronously mounted (i.e. direct manipulation) props from Native Animated.
*/
RN_EXPORT static bool overrideBySynchronousMountPropsAtMountingAndroid();
/**
* Enable reporting Performance Issues (`detail.rnPerfIssue`). Displayed in the V2 Performance Monitor and the "Performance Issues" sub-panel in DevTools.
*/
RN_EXPORT static bool perfIssuesEnabled();
/**
* Enable the V2 in-app Performance Monitor. This flag is global and should not be changed across React Host lifetimes.
*/
RN_EXPORT static bool perfMonitorV2Enabled();
/**
* Number cached PreparedLayouts in TextLayoutManager cache
*/
RN_EXPORT static double preparedTextCacheSize();
/**
* Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again.
*/
RN_EXPORT static bool preventShadowTreeCommitExhaustion();
/**
* Function used to enable / disable Pressibility from using W3C Pointer Events for its hover callbacks
*/
RN_EXPORT static bool shouldPressibilityUseW3CPointerEventsForHover();
/**
* Do not emit touchcancel from Android ScrollView, instead native topScroll event will trigger responder transfer and terminate in RN renderer.
*/
RN_EXPORT static bool shouldTriggerResponderTransferOnScrollAndroid();
/**
* Skip activity identity assertion in ReactHostImpl::onHostPause()
*/
RN_EXPORT static bool skipActivityIdentityAssertionOnHostPause();
/**
* A flag to tell Fabric to sweep active touches from JSTouchDispatcher in Android when a child native gesture is started.
*/
RN_EXPORT static bool sweepActiveTouchOnChildNativeGesturesAndroid();
/**
* Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause.
*/
RN_EXPORT static bool traceTurboModulePromiseRejectionsOnAndroid();
/**
* When enabled, runtime shadow node references will be updated during the commit. This allows running RSNRU from any thread without corrupting the renderer state.
*/
RN_EXPORT static bool updateRuntimeShadowNodeReferencesOnCommit();
/**
* In Bridgeless mode, use the always available javascript error reporting pipeline.
*/
RN_EXPORT static bool useAlwaysAvailableJSErrorHandling();
/**
* Should this application enable the Fabric Interop Layer for Android? If yes, the application will behave so that it can accept non-Fabric components and render them on Fabric. This toggle is controlling extra logic such as custom event dispatching that are needed for the Fabric Interop Layer to work correctly.
*/
RN_EXPORT static bool useFabricInterop();
/**
* Use a native implementation of equals in NativeReadableArray.
*/
RN_EXPORT static bool useNativeEqualsInNativeReadableArrayAndroid();
/**
* Use a native implementation of TransformHelper
*/
RN_EXPORT static bool useNativeTransformHelperAndroid();
/**
* When enabled, the native view configs are used in bridgeless mode.
*/
RN_EXPORT static bool useNativeViewConfigsInBridgelessMode();
/**
* Uses an optimized mechanism for event batching on Android that does not need to wait for a Choreographer frame callback.
*/
RN_EXPORT static bool useOptimizedEventBatchingOnAndroid();
/**
* Instead of using folly::dynamic as internal representation in RawProps and RawValue, use jsi::Value
*/
RN_EXPORT static bool useRawPropsJsiValue();
/**
* Use the state stored on the source shadow node when cloning it instead of reading in the most recent state on the shadow node family.
*/
RN_EXPORT static bool useShadowNodeStateOnClone();
/**
* Use shared animation backend in C++ Animated
*/
RN_EXPORT static bool useSharedAnimatedBackend();
/**
* Use Trait::hidden on Android
*/
RN_EXPORT static bool useTraitHiddenOnAndroid();
/**
* In Bridgeless mode, should legacy NativeModules use the TurboModule system?
*/
RN_EXPORT static bool useTurboModuleInterop();
/**
* When enabled, NativeModules will be executed by using the TurboModule system
*/
RN_EXPORT static bool useTurboModules();
/**
* Outset the culling context frame with the provided ratio. The culling context frame size will be outset by width * ratio on the left and right, and height * ratio on the top and bottom.
*/
RN_EXPORT static double viewCullingOutsetRatio();
/**
* Sets a hysteresis window for transition between prerender and hidden modes.
*/
RN_EXPORT static double virtualViewHysteresisRatio();
/**
* Initial prerender ratio for VirtualView.
*/
RN_EXPORT static double virtualViewPrerenderRatio();
/**
* Overrides the feature flags with the ones provided by the given provider
* (generally one that extends `ReactNativeFeatureFlagsDefaults`).
*
* This method must be called before you initialize the React Native runtime.
*
* @example
*
* ```
* class MyReactNativeFeatureFlags : public ReactNativeFeatureFlagsDefaults {
* public:
* bool someFlag() override;
* };
*
* ReactNativeFeatureFlags.override(
* std::make_unique<MyReactNativeFeatureFlags>());
* ```
*/
RN_EXPORT static void override(
std::unique_ptr<ReactNativeFeatureFlagsProvider> provider);
/**
* Removes the overridden feature flags and makes the API return default
* values again.
*
* This is **dangerous**. Use it only if you really understand the
* implications of this method.
*
* This should only be called if you destroy the React Native runtime and
* need to create a new one with different overrides. In that case,
* call `dangerouslyReset` after destroying the runtime and `override` again
* before initializing the new one.
*/
RN_EXPORT static void dangerouslyReset();
/**
* This is a combination of `dangerouslyReset` and `override` that reduces
* the likeliness of a race condition between the two calls.
*
* This is **dangerous** because it can introduce consistency issues that will
* be much harder to debug. For example, it could hide the fact that feature
* flags are read before you set the values you want to use everywhere. It
* could also cause a workflow to suddenly have different feature flags for
* behaviors that were configured with different values before.
*
* Please see the documentation of `dangerouslyReset` for additional details.
*/
RN_EXPORT static std::optional<std::string> dangerouslyForceOverride(
std::unique_ptr<ReactNativeFeatureFlagsProvider> provider);
private:
ReactNativeFeatureFlags() = delete;
static ReactNativeFeatureFlagsAccessor& getAccessor();
};
} // namespace facebook::react

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,220 @@
/*
* 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<<b88e97176f25900602b0821e16af4d12>>
*/
/**
* 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
#include <react/featureflags/ReactNativeFeatureFlagsProvider.h>
#include <array>
#include <atomic>
#include <memory>
#include <optional>
#include <string>
namespace facebook::react {
class ReactNativeFeatureFlagsAccessor {
public:
ReactNativeFeatureFlagsAccessor();
bool commonTestFlag();
bool cdpInteractionMetricsEnabled();
bool cxxNativeAnimatedEnabled();
bool cxxNativeAnimatedRemoveJsSync();
bool disableEarlyViewCommandExecution();
bool disableFabricCommitInCXXAnimated();
bool disableMountItemReorderingAndroid();
bool disableOldAndroidAttachmentMetricsWorkarounds();
bool disableTextLayoutManagerCacheAndroid();
bool enableAccessibilityOrder();
bool enableAccumulatedUpdatesInRawPropsAndroid();
bool enableAndroidLinearText();
bool enableAndroidTextMeasurementOptimizations();
bool enableBridgelessArchitecture();
bool enableCppPropsIteratorSetter();
bool enableCustomFocusSearchOnClippedElementsAndroid();
bool enableDestroyShadowTreeRevisionAsync();
bool enableDoubleMeasurementFixAndroid();
bool enableEagerMainQueueModulesOnIOS();
bool enableEagerRootViewAttachment();
bool enableFabricLogs();
bool enableFabricRenderer();
bool enableFontScaleChangesUpdatingLayout();
bool enableIOSTextBaselineOffsetPerLine();
bool enableIOSViewClipToPaddingBox();
bool enableImagePrefetchingAndroid();
bool enableImagePrefetchingOnUiThreadAndroid();
bool enableImmediateUpdateModeForContentOffsetChanges();
bool enableImperativeFocus();
bool enableInteropViewManagerClassLookUpOptimizationIOS();
bool enableIntersectionObserverByDefault();
bool enableKeyEvents();
bool enableLayoutAnimationsOnAndroid();
bool enableLayoutAnimationsOnIOS();
bool enableMainQueueCoordinatorOnIOS();
bool enableModuleArgumentNSNullConversionIOS();
bool enableNativeCSSParsing();
bool enableNetworkEventReporting();
bool enablePreparedTextLayout();
bool enablePropsUpdateReconciliationAndroid();
bool enableResourceTimingAPI();
bool enableSwiftUIBasedFilters();
bool enableViewCulling();
bool enableViewRecycling();
bool enableViewRecyclingForImage();
bool enableViewRecyclingForScrollView();
bool enableViewRecyclingForText();
bool enableViewRecyclingForView();
bool enableVirtualViewClippingWithoutScrollViewClipping();
bool enableVirtualViewContainerStateExperimental();
bool enableVirtualViewDebugFeatures();
bool enableVirtualViewRenderState();
bool enableVirtualViewWindowFocusDetection();
bool enableWebPerformanceAPIsByDefault();
bool fixMappingOfEventPrioritiesBetweenFabricAndReact();
bool fuseboxAssertSingleHostState();
bool fuseboxEnabledRelease();
bool fuseboxNetworkInspectionEnabled();
bool hideOffscreenVirtualViewsOnIOS();
bool overrideBySynchronousMountPropsAtMountingAndroid();
bool perfIssuesEnabled();
bool perfMonitorV2Enabled();
double preparedTextCacheSize();
bool preventShadowTreeCommitExhaustion();
bool shouldPressibilityUseW3CPointerEventsForHover();
bool shouldTriggerResponderTransferOnScrollAndroid();
bool skipActivityIdentityAssertionOnHostPause();
bool sweepActiveTouchOnChildNativeGesturesAndroid();
bool traceTurboModulePromiseRejectionsOnAndroid();
bool updateRuntimeShadowNodeReferencesOnCommit();
bool useAlwaysAvailableJSErrorHandling();
bool useFabricInterop();
bool useNativeEqualsInNativeReadableArrayAndroid();
bool useNativeTransformHelperAndroid();
bool useNativeViewConfigsInBridgelessMode();
bool useOptimizedEventBatchingOnAndroid();
bool useRawPropsJsiValue();
bool useShadowNodeStateOnClone();
bool useSharedAnimatedBackend();
bool useTraitHiddenOnAndroid();
bool useTurboModuleInterop();
bool useTurboModules();
double viewCullingOutsetRatio();
double virtualViewHysteresisRatio();
double virtualViewPrerenderRatio();
void override(std::unique_ptr<ReactNativeFeatureFlagsProvider> provider);
std::optional<std::string> getAccessedFeatureFlagNames() const;
private:
void markFlagAsAccessed(int position, const char* flagName);
void ensureFlagsNotAccessed();
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
bool wasOverridden_;
std::array<std::atomic<const char*>, 85> accessedFeatureFlags_;
std::atomic<std::optional<bool>> commonTestFlag_;
std::atomic<std::optional<bool>> cdpInteractionMetricsEnabled_;
std::atomic<std::optional<bool>> cxxNativeAnimatedEnabled_;
std::atomic<std::optional<bool>> cxxNativeAnimatedRemoveJsSync_;
std::atomic<std::optional<bool>> disableEarlyViewCommandExecution_;
std::atomic<std::optional<bool>> disableFabricCommitInCXXAnimated_;
std::atomic<std::optional<bool>> disableMountItemReorderingAndroid_;
std::atomic<std::optional<bool>> disableOldAndroidAttachmentMetricsWorkarounds_;
std::atomic<std::optional<bool>> disableTextLayoutManagerCacheAndroid_;
std::atomic<std::optional<bool>> enableAccessibilityOrder_;
std::atomic<std::optional<bool>> enableAccumulatedUpdatesInRawPropsAndroid_;
std::atomic<std::optional<bool>> enableAndroidLinearText_;
std::atomic<std::optional<bool>> enableAndroidTextMeasurementOptimizations_;
std::atomic<std::optional<bool>> enableBridgelessArchitecture_;
std::atomic<std::optional<bool>> enableCppPropsIteratorSetter_;
std::atomic<std::optional<bool>> enableCustomFocusSearchOnClippedElementsAndroid_;
std::atomic<std::optional<bool>> enableDestroyShadowTreeRevisionAsync_;
std::atomic<std::optional<bool>> enableDoubleMeasurementFixAndroid_;
std::atomic<std::optional<bool>> enableEagerMainQueueModulesOnIOS_;
std::atomic<std::optional<bool>> enableEagerRootViewAttachment_;
std::atomic<std::optional<bool>> enableFabricLogs_;
std::atomic<std::optional<bool>> enableFabricRenderer_;
std::atomic<std::optional<bool>> enableFontScaleChangesUpdatingLayout_;
std::atomic<std::optional<bool>> enableIOSTextBaselineOffsetPerLine_;
std::atomic<std::optional<bool>> enableIOSViewClipToPaddingBox_;
std::atomic<std::optional<bool>> enableImagePrefetchingAndroid_;
std::atomic<std::optional<bool>> enableImagePrefetchingOnUiThreadAndroid_;
std::atomic<std::optional<bool>> enableImmediateUpdateModeForContentOffsetChanges_;
std::atomic<std::optional<bool>> enableImperativeFocus_;
std::atomic<std::optional<bool>> enableInteropViewManagerClassLookUpOptimizationIOS_;
std::atomic<std::optional<bool>> enableIntersectionObserverByDefault_;
std::atomic<std::optional<bool>> enableKeyEvents_;
std::atomic<std::optional<bool>> enableLayoutAnimationsOnAndroid_;
std::atomic<std::optional<bool>> enableLayoutAnimationsOnIOS_;
std::atomic<std::optional<bool>> enableMainQueueCoordinatorOnIOS_;
std::atomic<std::optional<bool>> enableModuleArgumentNSNullConversionIOS_;
std::atomic<std::optional<bool>> enableNativeCSSParsing_;
std::atomic<std::optional<bool>> enableNetworkEventReporting_;
std::atomic<std::optional<bool>> enablePreparedTextLayout_;
std::atomic<std::optional<bool>> enablePropsUpdateReconciliationAndroid_;
std::atomic<std::optional<bool>> enableResourceTimingAPI_;
std::atomic<std::optional<bool>> enableSwiftUIBasedFilters_;
std::atomic<std::optional<bool>> enableViewCulling_;
std::atomic<std::optional<bool>> enableViewRecycling_;
std::atomic<std::optional<bool>> enableViewRecyclingForImage_;
std::atomic<std::optional<bool>> enableViewRecyclingForScrollView_;
std::atomic<std::optional<bool>> enableViewRecyclingForText_;
std::atomic<std::optional<bool>> enableViewRecyclingForView_;
std::atomic<std::optional<bool>> enableVirtualViewClippingWithoutScrollViewClipping_;
std::atomic<std::optional<bool>> enableVirtualViewContainerStateExperimental_;
std::atomic<std::optional<bool>> enableVirtualViewDebugFeatures_;
std::atomic<std::optional<bool>> enableVirtualViewRenderState_;
std::atomic<std::optional<bool>> enableVirtualViewWindowFocusDetection_;
std::atomic<std::optional<bool>> enableWebPerformanceAPIsByDefault_;
std::atomic<std::optional<bool>> fixMappingOfEventPrioritiesBetweenFabricAndReact_;
std::atomic<std::optional<bool>> fuseboxAssertSingleHostState_;
std::atomic<std::optional<bool>> fuseboxEnabledRelease_;
std::atomic<std::optional<bool>> fuseboxNetworkInspectionEnabled_;
std::atomic<std::optional<bool>> hideOffscreenVirtualViewsOnIOS_;
std::atomic<std::optional<bool>> overrideBySynchronousMountPropsAtMountingAndroid_;
std::atomic<std::optional<bool>> perfIssuesEnabled_;
std::atomic<std::optional<bool>> perfMonitorV2Enabled_;
std::atomic<std::optional<double>> preparedTextCacheSize_;
std::atomic<std::optional<bool>> preventShadowTreeCommitExhaustion_;
std::atomic<std::optional<bool>> shouldPressibilityUseW3CPointerEventsForHover_;
std::atomic<std::optional<bool>> shouldTriggerResponderTransferOnScrollAndroid_;
std::atomic<std::optional<bool>> skipActivityIdentityAssertionOnHostPause_;
std::atomic<std::optional<bool>> sweepActiveTouchOnChildNativeGesturesAndroid_;
std::atomic<std::optional<bool>> traceTurboModulePromiseRejectionsOnAndroid_;
std::atomic<std::optional<bool>> updateRuntimeShadowNodeReferencesOnCommit_;
std::atomic<std::optional<bool>> useAlwaysAvailableJSErrorHandling_;
std::atomic<std::optional<bool>> useFabricInterop_;
std::atomic<std::optional<bool>> useNativeEqualsInNativeReadableArrayAndroid_;
std::atomic<std::optional<bool>> useNativeTransformHelperAndroid_;
std::atomic<std::optional<bool>> useNativeViewConfigsInBridgelessMode_;
std::atomic<std::optional<bool>> useOptimizedEventBatchingOnAndroid_;
std::atomic<std::optional<bool>> useRawPropsJsiValue_;
std::atomic<std::optional<bool>> useShadowNodeStateOnClone_;
std::atomic<std::optional<bool>> useSharedAnimatedBackend_;
std::atomic<std::optional<bool>> useTraitHiddenOnAndroid_;
std::atomic<std::optional<bool>> useTurboModuleInterop_;
std::atomic<std::optional<bool>> useTurboModules_;
std::atomic<std::optional<double>> viewCullingOutsetRatio_;
std::atomic<std::optional<double>> virtualViewHysteresisRatio_;
std::atomic<std::optional<double>> virtualViewPrerenderRatio_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,371 @@
/*
* 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<<f8c2279957d1c654502ea5aa0f66beba>>
*/
/**
* 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
#include <react/featureflags/ReactNativeFeatureFlagsProvider.h>
namespace facebook::react {
class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
public:
ReactNativeFeatureFlagsDefaults() = default;
bool commonTestFlag() override {
return false;
}
bool cdpInteractionMetricsEnabled() override {
return false;
}
bool cxxNativeAnimatedEnabled() override {
return false;
}
bool cxxNativeAnimatedRemoveJsSync() override {
return false;
}
bool disableEarlyViewCommandExecution() override {
return false;
}
bool disableFabricCommitInCXXAnimated() override {
return false;
}
bool disableMountItemReorderingAndroid() override {
return false;
}
bool disableOldAndroidAttachmentMetricsWorkarounds() override {
return true;
}
bool disableTextLayoutManagerCacheAndroid() override {
return false;
}
bool enableAccessibilityOrder() override {
return false;
}
bool enableAccumulatedUpdatesInRawPropsAndroid() override {
return false;
}
bool enableAndroidLinearText() override {
return false;
}
bool enableAndroidTextMeasurementOptimizations() override {
return false;
}
bool enableBridgelessArchitecture() override {
return false;
}
bool enableCppPropsIteratorSetter() override {
return false;
}
bool enableCustomFocusSearchOnClippedElementsAndroid() override {
return true;
}
bool enableDestroyShadowTreeRevisionAsync() override {
return false;
}
bool enableDoubleMeasurementFixAndroid() override {
return false;
}
bool enableEagerMainQueueModulesOnIOS() override {
return false;
}
bool enableEagerRootViewAttachment() override {
return false;
}
bool enableFabricLogs() override {
return false;
}
bool enableFabricRenderer() override {
return false;
}
bool enableFontScaleChangesUpdatingLayout() override {
return true;
}
bool enableIOSTextBaselineOffsetPerLine() override {
return false;
}
bool enableIOSViewClipToPaddingBox() override {
return false;
}
bool enableImagePrefetchingAndroid() override {
return false;
}
bool enableImagePrefetchingOnUiThreadAndroid() override {
return false;
}
bool enableImmediateUpdateModeForContentOffsetChanges() override {
return false;
}
bool enableImperativeFocus() override {
return false;
}
bool enableInteropViewManagerClassLookUpOptimizationIOS() override {
return false;
}
bool enableIntersectionObserverByDefault() override {
return false;
}
bool enableKeyEvents() override {
return false;
}
bool enableLayoutAnimationsOnAndroid() override {
return false;
}
bool enableLayoutAnimationsOnIOS() override {
return true;
}
bool enableMainQueueCoordinatorOnIOS() override {
return false;
}
bool enableModuleArgumentNSNullConversionIOS() override {
return false;
}
bool enableNativeCSSParsing() override {
return false;
}
bool enableNetworkEventReporting() override {
return true;
}
bool enablePreparedTextLayout() override {
return false;
}
bool enablePropsUpdateReconciliationAndroid() override {
return false;
}
bool enableResourceTimingAPI() override {
return true;
}
bool enableSwiftUIBasedFilters() override {
return false;
}
bool enableViewCulling() override {
return false;
}
bool enableViewRecycling() override {
return false;
}
bool enableViewRecyclingForImage() override {
return true;
}
bool enableViewRecyclingForScrollView() override {
return false;
}
bool enableViewRecyclingForText() override {
return true;
}
bool enableViewRecyclingForView() override {
return true;
}
bool enableVirtualViewClippingWithoutScrollViewClipping() override {
return true;
}
bool enableVirtualViewContainerStateExperimental() override {
return false;
}
bool enableVirtualViewDebugFeatures() override {
return false;
}
bool enableVirtualViewRenderState() override {
return true;
}
bool enableVirtualViewWindowFocusDetection() override {
return false;
}
bool enableWebPerformanceAPIsByDefault() override {
return true;
}
bool fixMappingOfEventPrioritiesBetweenFabricAndReact() override {
return false;
}
bool fuseboxAssertSingleHostState() override {
return true;
}
bool fuseboxEnabledRelease() override {
return false;
}
bool fuseboxNetworkInspectionEnabled() override {
return true;
}
bool hideOffscreenVirtualViewsOnIOS() override {
return false;
}
bool overrideBySynchronousMountPropsAtMountingAndroid() override {
return false;
}
bool perfIssuesEnabled() override {
return false;
}
bool perfMonitorV2Enabled() override {
return false;
}
double preparedTextCacheSize() override {
return 200.0;
}
bool preventShadowTreeCommitExhaustion() override {
return false;
}
bool shouldPressibilityUseW3CPointerEventsForHover() override {
return false;
}
bool shouldTriggerResponderTransferOnScrollAndroid() override {
return false;
}
bool skipActivityIdentityAssertionOnHostPause() override {
return false;
}
bool sweepActiveTouchOnChildNativeGesturesAndroid() override {
return true;
}
bool traceTurboModulePromiseRejectionsOnAndroid() override {
return false;
}
bool updateRuntimeShadowNodeReferencesOnCommit() override {
return false;
}
bool useAlwaysAvailableJSErrorHandling() override {
return false;
}
bool useFabricInterop() override {
return true;
}
bool useNativeEqualsInNativeReadableArrayAndroid() override {
return true;
}
bool useNativeTransformHelperAndroid() override {
return true;
}
bool useNativeViewConfigsInBridgelessMode() override {
return false;
}
bool useOptimizedEventBatchingOnAndroid() override {
return false;
}
bool useRawPropsJsiValue() override {
return true;
}
bool useShadowNodeStateOnClone() override {
return false;
}
bool useSharedAnimatedBackend() override {
return false;
}
bool useTraitHiddenOnAndroid() override {
return false;
}
bool useTurboModuleInterop() override {
return false;
}
bool useTurboModules() override {
return false;
}
double viewCullingOutsetRatio() override {
return 0.0;
}
double virtualViewHysteresisRatio() override {
return 0.0;
}
double virtualViewPrerenderRatio() override {
return 5.0;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,814 @@
/*
* 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<<b4abe68bba3cca50754f060a278f9896>>
*/
/**
* 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
#include <folly/dynamic.h>
#include <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
namespace facebook::react {
/**
* This class is a ReactNativeFeatureFlags provider that takes the values for
* feature flags from a folly::dynamic object (e.g. from a JSON object), if
* they are defined. For the flags not defined in the object, it falls back to
* the default values defined in ReactNativeFeatureFlagsDefaults.
*
* The API is strict about typing. It ignores null values from the
* folly::dynamic object, but if the key is defined, the value must have the
* correct type or otherwise throws an exception.
*/
class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDefaults {
private:
folly::dynamic values_;
public:
ReactNativeFeatureFlagsDynamicProvider(folly::dynamic values): values_(std::move(values)) {
if (!values_.isObject()) {
throw std::invalid_argument("ReactNativeFeatureFlagsDynamicProvider: values must be an object");
}
}
bool commonTestFlag() override {
auto value = values_["commonTestFlag"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::commonTestFlag();
}
bool cdpInteractionMetricsEnabled() override {
auto value = values_["cdpInteractionMetricsEnabled"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::cdpInteractionMetricsEnabled();
}
bool cxxNativeAnimatedEnabled() override {
auto value = values_["cxxNativeAnimatedEnabled"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::cxxNativeAnimatedEnabled();
}
bool cxxNativeAnimatedRemoveJsSync() override {
auto value = values_["cxxNativeAnimatedRemoveJsSync"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::cxxNativeAnimatedRemoveJsSync();
}
bool disableEarlyViewCommandExecution() override {
auto value = values_["disableEarlyViewCommandExecution"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::disableEarlyViewCommandExecution();
}
bool disableFabricCommitInCXXAnimated() override {
auto value = values_["disableFabricCommitInCXXAnimated"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::disableFabricCommitInCXXAnimated();
}
bool disableMountItemReorderingAndroid() override {
auto value = values_["disableMountItemReorderingAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::disableMountItemReorderingAndroid();
}
bool disableOldAndroidAttachmentMetricsWorkarounds() override {
auto value = values_["disableOldAndroidAttachmentMetricsWorkarounds"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::disableOldAndroidAttachmentMetricsWorkarounds();
}
bool disableTextLayoutManagerCacheAndroid() override {
auto value = values_["disableTextLayoutManagerCacheAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::disableTextLayoutManagerCacheAndroid();
}
bool enableAccessibilityOrder() override {
auto value = values_["enableAccessibilityOrder"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableAccessibilityOrder();
}
bool enableAccumulatedUpdatesInRawPropsAndroid() override {
auto value = values_["enableAccumulatedUpdatesInRawPropsAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableAccumulatedUpdatesInRawPropsAndroid();
}
bool enableAndroidLinearText() override {
auto value = values_["enableAndroidLinearText"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableAndroidLinearText();
}
bool enableAndroidTextMeasurementOptimizations() override {
auto value = values_["enableAndroidTextMeasurementOptimizations"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableAndroidTextMeasurementOptimizations();
}
bool enableBridgelessArchitecture() override {
auto value = values_["enableBridgelessArchitecture"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableBridgelessArchitecture();
}
bool enableCppPropsIteratorSetter() override {
auto value = values_["enableCppPropsIteratorSetter"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableCppPropsIteratorSetter();
}
bool enableCustomFocusSearchOnClippedElementsAndroid() override {
auto value = values_["enableCustomFocusSearchOnClippedElementsAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableCustomFocusSearchOnClippedElementsAndroid();
}
bool enableDestroyShadowTreeRevisionAsync() override {
auto value = values_["enableDestroyShadowTreeRevisionAsync"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableDestroyShadowTreeRevisionAsync();
}
bool enableDoubleMeasurementFixAndroid() override {
auto value = values_["enableDoubleMeasurementFixAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableDoubleMeasurementFixAndroid();
}
bool enableEagerMainQueueModulesOnIOS() override {
auto value = values_["enableEagerMainQueueModulesOnIOS"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableEagerMainQueueModulesOnIOS();
}
bool enableEagerRootViewAttachment() override {
auto value = values_["enableEagerRootViewAttachment"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableEagerRootViewAttachment();
}
bool enableFabricLogs() override {
auto value = values_["enableFabricLogs"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableFabricLogs();
}
bool enableFabricRenderer() override {
auto value = values_["enableFabricRenderer"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableFabricRenderer();
}
bool enableFontScaleChangesUpdatingLayout() override {
auto value = values_["enableFontScaleChangesUpdatingLayout"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableFontScaleChangesUpdatingLayout();
}
bool enableIOSTextBaselineOffsetPerLine() override {
auto value = values_["enableIOSTextBaselineOffsetPerLine"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableIOSTextBaselineOffsetPerLine();
}
bool enableIOSViewClipToPaddingBox() override {
auto value = values_["enableIOSViewClipToPaddingBox"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableIOSViewClipToPaddingBox();
}
bool enableImagePrefetchingAndroid() override {
auto value = values_["enableImagePrefetchingAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableImagePrefetchingAndroid();
}
bool enableImagePrefetchingOnUiThreadAndroid() override {
auto value = values_["enableImagePrefetchingOnUiThreadAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableImagePrefetchingOnUiThreadAndroid();
}
bool enableImmediateUpdateModeForContentOffsetChanges() override {
auto value = values_["enableImmediateUpdateModeForContentOffsetChanges"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableImmediateUpdateModeForContentOffsetChanges();
}
bool enableImperativeFocus() override {
auto value = values_["enableImperativeFocus"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableImperativeFocus();
}
bool enableInteropViewManagerClassLookUpOptimizationIOS() override {
auto value = values_["enableInteropViewManagerClassLookUpOptimizationIOS"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableInteropViewManagerClassLookUpOptimizationIOS();
}
bool enableIntersectionObserverByDefault() override {
auto value = values_["enableIntersectionObserverByDefault"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableIntersectionObserverByDefault();
}
bool enableKeyEvents() override {
auto value = values_["enableKeyEvents"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableKeyEvents();
}
bool enableLayoutAnimationsOnAndroid() override {
auto value = values_["enableLayoutAnimationsOnAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableLayoutAnimationsOnAndroid();
}
bool enableLayoutAnimationsOnIOS() override {
auto value = values_["enableLayoutAnimationsOnIOS"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableLayoutAnimationsOnIOS();
}
bool enableMainQueueCoordinatorOnIOS() override {
auto value = values_["enableMainQueueCoordinatorOnIOS"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableMainQueueCoordinatorOnIOS();
}
bool enableModuleArgumentNSNullConversionIOS() override {
auto value = values_["enableModuleArgumentNSNullConversionIOS"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableModuleArgumentNSNullConversionIOS();
}
bool enableNativeCSSParsing() override {
auto value = values_["enableNativeCSSParsing"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableNativeCSSParsing();
}
bool enableNetworkEventReporting() override {
auto value = values_["enableNetworkEventReporting"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableNetworkEventReporting();
}
bool enablePreparedTextLayout() override {
auto value = values_["enablePreparedTextLayout"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enablePreparedTextLayout();
}
bool enablePropsUpdateReconciliationAndroid() override {
auto value = values_["enablePropsUpdateReconciliationAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enablePropsUpdateReconciliationAndroid();
}
bool enableResourceTimingAPI() override {
auto value = values_["enableResourceTimingAPI"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableResourceTimingAPI();
}
bool enableSwiftUIBasedFilters() override {
auto value = values_["enableSwiftUIBasedFilters"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableSwiftUIBasedFilters();
}
bool enableViewCulling() override {
auto value = values_["enableViewCulling"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableViewCulling();
}
bool enableViewRecycling() override {
auto value = values_["enableViewRecycling"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableViewRecycling();
}
bool enableViewRecyclingForImage() override {
auto value = values_["enableViewRecyclingForImage"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableViewRecyclingForImage();
}
bool enableViewRecyclingForScrollView() override {
auto value = values_["enableViewRecyclingForScrollView"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableViewRecyclingForScrollView();
}
bool enableViewRecyclingForText() override {
auto value = values_["enableViewRecyclingForText"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableViewRecyclingForText();
}
bool enableViewRecyclingForView() override {
auto value = values_["enableViewRecyclingForView"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableViewRecyclingForView();
}
bool enableVirtualViewClippingWithoutScrollViewClipping() override {
auto value = values_["enableVirtualViewClippingWithoutScrollViewClipping"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableVirtualViewClippingWithoutScrollViewClipping();
}
bool enableVirtualViewContainerStateExperimental() override {
auto value = values_["enableVirtualViewContainerStateExperimental"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableVirtualViewContainerStateExperimental();
}
bool enableVirtualViewDebugFeatures() override {
auto value = values_["enableVirtualViewDebugFeatures"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableVirtualViewDebugFeatures();
}
bool enableVirtualViewRenderState() override {
auto value = values_["enableVirtualViewRenderState"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableVirtualViewRenderState();
}
bool enableVirtualViewWindowFocusDetection() override {
auto value = values_["enableVirtualViewWindowFocusDetection"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableVirtualViewWindowFocusDetection();
}
bool enableWebPerformanceAPIsByDefault() override {
auto value = values_["enableWebPerformanceAPIsByDefault"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableWebPerformanceAPIsByDefault();
}
bool fixMappingOfEventPrioritiesBetweenFabricAndReact() override {
auto value = values_["fixMappingOfEventPrioritiesBetweenFabricAndReact"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::fixMappingOfEventPrioritiesBetweenFabricAndReact();
}
bool fuseboxAssertSingleHostState() override {
auto value = values_["fuseboxAssertSingleHostState"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::fuseboxAssertSingleHostState();
}
bool fuseboxEnabledRelease() override {
auto value = values_["fuseboxEnabledRelease"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::fuseboxEnabledRelease();
}
bool fuseboxNetworkInspectionEnabled() override {
auto value = values_["fuseboxNetworkInspectionEnabled"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::fuseboxNetworkInspectionEnabled();
}
bool hideOffscreenVirtualViewsOnIOS() override {
auto value = values_["hideOffscreenVirtualViewsOnIOS"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::hideOffscreenVirtualViewsOnIOS();
}
bool overrideBySynchronousMountPropsAtMountingAndroid() override {
auto value = values_["overrideBySynchronousMountPropsAtMountingAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::overrideBySynchronousMountPropsAtMountingAndroid();
}
bool perfIssuesEnabled() override {
auto value = values_["perfIssuesEnabled"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::perfIssuesEnabled();
}
bool perfMonitorV2Enabled() override {
auto value = values_["perfMonitorV2Enabled"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::perfMonitorV2Enabled();
}
double preparedTextCacheSize() override {
auto value = values_["preparedTextCacheSize"];
if (!value.isNull()) {
return value.getDouble();
}
return ReactNativeFeatureFlagsDefaults::preparedTextCacheSize();
}
bool preventShadowTreeCommitExhaustion() override {
auto value = values_["preventShadowTreeCommitExhaustion"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::preventShadowTreeCommitExhaustion();
}
bool shouldPressibilityUseW3CPointerEventsForHover() override {
auto value = values_["shouldPressibilityUseW3CPointerEventsForHover"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::shouldPressibilityUseW3CPointerEventsForHover();
}
bool shouldTriggerResponderTransferOnScrollAndroid() override {
auto value = values_["shouldTriggerResponderTransferOnScrollAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::shouldTriggerResponderTransferOnScrollAndroid();
}
bool skipActivityIdentityAssertionOnHostPause() override {
auto value = values_["skipActivityIdentityAssertionOnHostPause"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::skipActivityIdentityAssertionOnHostPause();
}
bool sweepActiveTouchOnChildNativeGesturesAndroid() override {
auto value = values_["sweepActiveTouchOnChildNativeGesturesAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::sweepActiveTouchOnChildNativeGesturesAndroid();
}
bool traceTurboModulePromiseRejectionsOnAndroid() override {
auto value = values_["traceTurboModulePromiseRejectionsOnAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::traceTurboModulePromiseRejectionsOnAndroid();
}
bool updateRuntimeShadowNodeReferencesOnCommit() override {
auto value = values_["updateRuntimeShadowNodeReferencesOnCommit"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::updateRuntimeShadowNodeReferencesOnCommit();
}
bool useAlwaysAvailableJSErrorHandling() override {
auto value = values_["useAlwaysAvailableJSErrorHandling"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useAlwaysAvailableJSErrorHandling();
}
bool useFabricInterop() override {
auto value = values_["useFabricInterop"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useFabricInterop();
}
bool useNativeEqualsInNativeReadableArrayAndroid() override {
auto value = values_["useNativeEqualsInNativeReadableArrayAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useNativeEqualsInNativeReadableArrayAndroid();
}
bool useNativeTransformHelperAndroid() override {
auto value = values_["useNativeTransformHelperAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useNativeTransformHelperAndroid();
}
bool useNativeViewConfigsInBridgelessMode() override {
auto value = values_["useNativeViewConfigsInBridgelessMode"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useNativeViewConfigsInBridgelessMode();
}
bool useOptimizedEventBatchingOnAndroid() override {
auto value = values_["useOptimizedEventBatchingOnAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useOptimizedEventBatchingOnAndroid();
}
bool useRawPropsJsiValue() override {
auto value = values_["useRawPropsJsiValue"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useRawPropsJsiValue();
}
bool useShadowNodeStateOnClone() override {
auto value = values_["useShadowNodeStateOnClone"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useShadowNodeStateOnClone();
}
bool useSharedAnimatedBackend() override {
auto value = values_["useSharedAnimatedBackend"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useSharedAnimatedBackend();
}
bool useTraitHiddenOnAndroid() override {
auto value = values_["useTraitHiddenOnAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useTraitHiddenOnAndroid();
}
bool useTurboModuleInterop() override {
auto value = values_["useTurboModuleInterop"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useTurboModuleInterop();
}
bool useTurboModules() override {
auto value = values_["useTurboModules"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useTurboModules();
}
double viewCullingOutsetRatio() override {
auto value = values_["viewCullingOutsetRatio"];
if (!value.isNull()) {
return value.getDouble();
}
return ReactNativeFeatureFlagsDefaults::viewCullingOutsetRatio();
}
double virtualViewHysteresisRatio() override {
auto value = values_["virtualViewHysteresisRatio"];
if (!value.isNull()) {
return value.getDouble();
}
return ReactNativeFeatureFlagsDefaults::virtualViewHysteresisRatio();
}
double virtualViewPrerenderRatio() override {
auto value = values_["virtualViewPrerenderRatio"];
if (!value.isNull()) {
return value.getDouble();
}
return ReactNativeFeatureFlagsDefaults::virtualViewPrerenderRatio();
}
};
} // 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.
*
* @generated SignedSource<<1b2061068e0d6c9ca362ceddd97862da>>
*/
/**
* 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
#include <react/featureflags/ReactNativeFeatureFlagsOverridesOSSStable.h>
namespace facebook::react {
class ReactNativeFeatureFlagsOverridesOSSCanary : public ReactNativeFeatureFlagsOverridesOSSStable {
public:
ReactNativeFeatureFlagsOverridesOSSCanary() = default;
bool enableBridgelessArchitecture() override {
return true;
}
bool enableFabricRenderer() override {
return true;
}
bool enableIntersectionObserverByDefault() override {
return true;
}
bool useNativeViewConfigsInBridgelessMode() override {
return true;
}
bool useTurboModuleInterop() override {
return true;
}
bool useTurboModules() override {
return true;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<c5b1dc86ec11d40e5d26ae8a6f0ee6ff>>
*/
/**
* 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
#include <react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h>
namespace facebook::react {
class ReactNativeFeatureFlagsOverridesOSSExperimental : public ReactNativeFeatureFlagsOverridesOSSCanary {
public:
ReactNativeFeatureFlagsOverridesOSSExperimental() = default;
bool enableAccessibilityOrder() override {
return true;
}
bool enableSwiftUIBasedFilters() override {
return true;
}
bool preventShadowTreeCommitExhaustion() override {
return true;
}
};
} // 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
#include <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
namespace facebook::react {
class ReactNativeFeatureFlagsOverridesOSSStable : public ReactNativeFeatureFlagsDefaults {
public:
bool enableBridgelessArchitecture() override
{
return true;
}
bool enableFabricRenderer() override
{
return true;
}
bool useTurboModules() override
{
return true;
}
bool useNativeViewConfigsInBridgelessMode() override
{
return true;
}
bool useShadowNodeStateOnClone() override
{
return true;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,115 @@
/*
* 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<<06c6740fe7d613f235ae791da750bd52>>
*/
/**
* 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
namespace facebook::react {
class ReactNativeFeatureFlagsProvider {
public:
virtual ~ReactNativeFeatureFlagsProvider() = default;
virtual bool commonTestFlag() = 0;
virtual bool cdpInteractionMetricsEnabled() = 0;
virtual bool cxxNativeAnimatedEnabled() = 0;
virtual bool cxxNativeAnimatedRemoveJsSync() = 0;
virtual bool disableEarlyViewCommandExecution() = 0;
virtual bool disableFabricCommitInCXXAnimated() = 0;
virtual bool disableMountItemReorderingAndroid() = 0;
virtual bool disableOldAndroidAttachmentMetricsWorkarounds() = 0;
virtual bool disableTextLayoutManagerCacheAndroid() = 0;
virtual bool enableAccessibilityOrder() = 0;
virtual bool enableAccumulatedUpdatesInRawPropsAndroid() = 0;
virtual bool enableAndroidLinearText() = 0;
virtual bool enableAndroidTextMeasurementOptimizations() = 0;
virtual bool enableBridgelessArchitecture() = 0;
virtual bool enableCppPropsIteratorSetter() = 0;
virtual bool enableCustomFocusSearchOnClippedElementsAndroid() = 0;
virtual bool enableDestroyShadowTreeRevisionAsync() = 0;
virtual bool enableDoubleMeasurementFixAndroid() = 0;
virtual bool enableEagerMainQueueModulesOnIOS() = 0;
virtual bool enableEagerRootViewAttachment() = 0;
virtual bool enableFabricLogs() = 0;
virtual bool enableFabricRenderer() = 0;
virtual bool enableFontScaleChangesUpdatingLayout() = 0;
virtual bool enableIOSTextBaselineOffsetPerLine() = 0;
virtual bool enableIOSViewClipToPaddingBox() = 0;
virtual bool enableImagePrefetchingAndroid() = 0;
virtual bool enableImagePrefetchingOnUiThreadAndroid() = 0;
virtual bool enableImmediateUpdateModeForContentOffsetChanges() = 0;
virtual bool enableImperativeFocus() = 0;
virtual bool enableInteropViewManagerClassLookUpOptimizationIOS() = 0;
virtual bool enableIntersectionObserverByDefault() = 0;
virtual bool enableKeyEvents() = 0;
virtual bool enableLayoutAnimationsOnAndroid() = 0;
virtual bool enableLayoutAnimationsOnIOS() = 0;
virtual bool enableMainQueueCoordinatorOnIOS() = 0;
virtual bool enableModuleArgumentNSNullConversionIOS() = 0;
virtual bool enableNativeCSSParsing() = 0;
virtual bool enableNetworkEventReporting() = 0;
virtual bool enablePreparedTextLayout() = 0;
virtual bool enablePropsUpdateReconciliationAndroid() = 0;
virtual bool enableResourceTimingAPI() = 0;
virtual bool enableSwiftUIBasedFilters() = 0;
virtual bool enableViewCulling() = 0;
virtual bool enableViewRecycling() = 0;
virtual bool enableViewRecyclingForImage() = 0;
virtual bool enableViewRecyclingForScrollView() = 0;
virtual bool enableViewRecyclingForText() = 0;
virtual bool enableViewRecyclingForView() = 0;
virtual bool enableVirtualViewClippingWithoutScrollViewClipping() = 0;
virtual bool enableVirtualViewContainerStateExperimental() = 0;
virtual bool enableVirtualViewDebugFeatures() = 0;
virtual bool enableVirtualViewRenderState() = 0;
virtual bool enableVirtualViewWindowFocusDetection() = 0;
virtual bool enableWebPerformanceAPIsByDefault() = 0;
virtual bool fixMappingOfEventPrioritiesBetweenFabricAndReact() = 0;
virtual bool fuseboxAssertSingleHostState() = 0;
virtual bool fuseboxEnabledRelease() = 0;
virtual bool fuseboxNetworkInspectionEnabled() = 0;
virtual bool hideOffscreenVirtualViewsOnIOS() = 0;
virtual bool overrideBySynchronousMountPropsAtMountingAndroid() = 0;
virtual bool perfIssuesEnabled() = 0;
virtual bool perfMonitorV2Enabled() = 0;
virtual double preparedTextCacheSize() = 0;
virtual bool preventShadowTreeCommitExhaustion() = 0;
virtual bool shouldPressibilityUseW3CPointerEventsForHover() = 0;
virtual bool shouldTriggerResponderTransferOnScrollAndroid() = 0;
virtual bool skipActivityIdentityAssertionOnHostPause() = 0;
virtual bool sweepActiveTouchOnChildNativeGesturesAndroid() = 0;
virtual bool traceTurboModulePromiseRejectionsOnAndroid() = 0;
virtual bool updateRuntimeShadowNodeReferencesOnCommit() = 0;
virtual bool useAlwaysAvailableJSErrorHandling() = 0;
virtual bool useFabricInterop() = 0;
virtual bool useNativeEqualsInNativeReadableArrayAndroid() = 0;
virtual bool useNativeTransformHelperAndroid() = 0;
virtual bool useNativeViewConfigsInBridgelessMode() = 0;
virtual bool useOptimizedEventBatchingOnAndroid() = 0;
virtual bool useRawPropsJsiValue() = 0;
virtual bool useShadowNodeStateOnClone() = 0;
virtual bool useSharedAnimatedBackend() = 0;
virtual bool useTraitHiddenOnAndroid() = 0;
virtual bool useTurboModuleInterop() = 0;
virtual bool useTurboModules() = 0;
virtual double viewCullingOutsetRatio() = 0;
virtual double virtualViewHysteresisRatio() = 0;
virtual double virtualViewPrerenderRatio() = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <gtest/gtest.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h>
namespace facebook::react {
class ReactNativeFeatureFlagsDynamicProviderTest : public testing::Test {
protected:
void TearDown() override {
ReactNativeFeatureFlags::dangerouslyReset();
}
};
TEST_F(ReactNativeFeatureFlagsDynamicProviderTest, providesDefaults) {
auto values = folly::dynamic::object();
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsDynamicProvider>(
std::move(values)));
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), false);
}
TEST_F(ReactNativeFeatureFlagsDynamicProviderTest, providesDynamicOverrides) {
folly::dynamic values = folly::dynamic::object();
values["commonTestFlag"] = true;
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsDynamicProvider>(values));
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
}
TEST_F(
ReactNativeFeatureFlagsDynamicProviderTest,
throwsWithIncorrectFlagTypes) {
folly::dynamic values = folly::dynamic::object();
values["commonTestFlag"] = 12;
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsDynamicProvider>(values));
try {
ReactNativeFeatureFlags::commonTestFlag();
FAIL()
<< "Expected ReactNativeFeatureFlags::commonTestFlag() to throw an exception";
} catch (const std::runtime_error& e) {
EXPECT_STREQ(
"TypeError: expected dynamic type 'boolean', but had type 'int64'",
e.what());
}
}
TEST_F(ReactNativeFeatureFlagsDynamicProviderTest, throwsWithNonObjectValues) {
folly::dynamic values = folly::dynamic("string");
try {
auto provider =
std::make_unique<ReactNativeFeatureFlagsDynamicProvider>(values);
FAIL()
<< "Expected ReactNativeFeatureFlagsDynamicProvider constructor to throw an exception";
} catch (const std::invalid_argument& e) {
EXPECT_STREQ(
"ReactNativeFeatureFlagsDynamicProvider: values must be an object",
e.what());
}
}
} // namespace facebook::react

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.
*/
#include <gtest/gtest.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
#include <stdexcept>
namespace facebook::react {
uint overrideAccessCount = 0;
class ReactNativeFeatureFlagsTestOverrides
: public ReactNativeFeatureFlagsDefaults {
public:
bool commonTestFlag() override {
overrideAccessCount++;
return true;
}
};
class ReactNativeFeatureFlagsTest : public testing::Test {
protected:
void SetUp() override {
overrideAccessCount = 0;
}
void TearDown() override {
ReactNativeFeatureFlags::dangerouslyReset();
}
};
TEST_F(ReactNativeFeatureFlagsTest, providesDefaults) {
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), false);
}
TEST_F(ReactNativeFeatureFlagsTest, providesOverriddenValues) {
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
}
TEST_F(ReactNativeFeatureFlagsTest, preventsOverridingAfterAccess) {
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), false);
try {
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
FAIL()
<< "Expected ReactNativeFeatureFlags::override() to throw an exception";
} catch (const std::runtime_error& e) {
EXPECT_STREQ(
"Feature flags were accessed before being overridden: commonTestFlag",
e.what());
}
// Overrides shouldn't be applied after they've been accessed
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), false);
}
TEST_F(ReactNativeFeatureFlagsTest, preventsOverridingAfterOverride) {
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
try {
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
FAIL()
<< "Expected ReactNativeFeatureFlags::override() to throw an exception";
} catch (const std::runtime_error& e) {
EXPECT_STREQ("Feature flags cannot be overridden more than once", e.what());
}
// Original overrides should still work
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
}
TEST_F(ReactNativeFeatureFlagsTest, cachesValuesFromOverride) {
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
EXPECT_EQ(overrideAccessCount, 0);
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
EXPECT_EQ(overrideAccessCount, 1);
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
EXPECT_EQ(overrideAccessCount, 1);
}
TEST_F(
ReactNativeFeatureFlagsTest,
providesDefaulValuesAgainWhenResettingAfterAnOverride) {
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
ReactNativeFeatureFlags::dangerouslyReset();
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), false);
}
TEST_F(ReactNativeFeatureFlagsTest, allowsOverridingAgainAfterReset) {
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
ReactNativeFeatureFlags::dangerouslyReset();
ReactNativeFeatureFlags::override(
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
}
TEST_F(
ReactNativeFeatureFlagsTest,
allowsDangerouslyForcingOverridesWhenValuesHaveNotBeenAccessed) {
auto accessedFlags = ReactNativeFeatureFlags::dangerouslyForceOverride(
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
EXPECT_EQ(accessedFlags.has_value(), false);
}
TEST_F(
ReactNativeFeatureFlagsTest,
allowsDangerouslyForcingOverridesWhenValuesHaveBeenAccessed) {
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), false);
auto accessedFlags = ReactNativeFeatureFlags::dangerouslyForceOverride(
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
EXPECT_EQ(accessedFlags.has_value(), true);
EXPECT_EQ(accessedFlags.value(), "commonTestFlag");
}
} // 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.
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

Some files were not shown because too many files have changed in this diff Show More