feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <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,151 @@
/*
* 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...>) {
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,152 @@
/*
* 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/Convert.h>
#include <ReactCommon/CallInvoker.h>
#include <jsi/jsi.h>
#include <cstdint>
#include <memory>
#include <type_traits>
namespace facebook::react {
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&, const jsi::Value&) {}
};
namespace bridging {
namespace detail {
template <typename F>
struct function_wrapper;
template <typename C, typename R, typename... Args>
struct function_wrapper<R (C::*)(Args...)> {
using type = std::function<R(Args...)>;
};
template <typename C, typename R, typename... Args>
struct function_wrapper<R (C::*)(Args...) const> {
using type = std::function<R(Args...)>;
};
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 R, typename T, std::enable_if_t<is_jsi_v<T>, int> = 0>
auto fromJs(jsi::Runtime& rt, T&& value, const std::shared_ptr<CallInvoker>&)
-> decltype(static_cast<R>(convert(rt, std::forward<T>(value)))) {
return convert(rt, std::forward<T>(value));
}
template <typename R, typename T>
auto fromJs(jsi::Runtime& rt, T&& value, const std::shared_ptr<CallInvoker>&)
-> decltype(Bridging<remove_cvref_t<R>>::fromJs(
rt,
convert(rt, std::forward<T>(value)))) {
return Bridging<remove_cvref_t<R>>::fromJs(
rt, convert(rt, std::forward<T>(value)));
}
template <typename R, typename T>
auto fromJs(
jsi::Runtime& rt,
T&& value,
const std::shared_ptr<CallInvoker>& jsInvoker)
-> decltype(Bridging<remove_cvref_t<R>>::fromJs(
rt,
convert(rt, std::forward<T>(value)),
jsInvoker)) {
return Bridging<remove_cvref_t<R>>::fromJs(
rt, convert(rt, std::forward<T>(value)), jsInvoker);
}
template <typename T, std::enable_if_t<is_jsi_v<T>, int> = 0>
auto toJs(
jsi::Runtime& rt,
T&& value,
const std::shared_ptr<CallInvoker>& = 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>& = 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 = jsi::Value>
inline constexpr bool supportsFromJs<
T,
Arg,
std::void_t<decltype(fromJs<T>(
std::declval<jsi::Runtime&>(),
std::declval<Arg>(),
nullptr))>> = true;
template <typename, typename = jsi::Value, typename = void>
inline constexpr bool supportsToJs = false;
template <typename T, typename Ret = jsi::Value>
inline constexpr bool supportsToJs<
T,
Ret,
std::void_t<decltype(toJs(
std::declval<jsi::Runtime&>(),
std::declval<T>(),
nullptr))>> =
std::is_convertible_v<
decltype(toJs(
std::declval<jsi::Runtime&>(),
std::declval<T>(),
nullptr)),
Ret>;
} // namespace bridging
} // namespace facebook::react

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/bridging/Base.h>
namespace facebook::react {
template <>
struct Bridging<bool> {
static bool fromJs(jsi::Runtime& rt, const jsi::Value& value) {
return value.asBool();
}
static jsi::Value toJs(jsi::Runtime&, bool value) {
return value;
}
};
} // 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.
*/
#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/Function.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,23 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"ReactNative\")
file(GLOB react_bridging_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_bridging STATIC ${react_bridging_SRC})
target_include_directories(react_bridging PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_bridging jsi callinvoker)

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 <ReactCommon/CallInvoker.h>
#include "LongLivedObject.h"
namespace facebook::react {
// 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)
: callback_(std::move(callback)),
runtime_(runtime),
jsInvoker_(std::move(jsInvoker)) {}
jsi::Function callback_;
jsi::Runtime& runtime_;
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().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,90 @@
/*
* 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 T,
typename C,
typename R,
typename... Args,
typename... JSArgs>
T callFromJs(
jsi::Runtime& rt,
R (C::*method)(jsi::Runtime&, Args...),
const std::shared_ptr<CallInvoker>& jsInvoker,
C* instance,
JSArgs&&... args) {
static_assert(
sizeof...(Args) == sizeof...(JSArgs), "Incorrect arguments length");
static_assert(
(supportsFromJs<Args, JSArgs> && ...), "Incompatible arguments");
if constexpr (std::is_void_v<T>) {
(instance->*method)(
rt, fromJs<Args>(rt, std::forward<JSArgs>(args), jsInvoker)...);
} else if constexpr (std::is_void_v<R>) {
static_assert(
std::is_same_v<T, jsi::Value>,
"Void functions may only return undefined");
(instance->*method)(
rt, fromJs<Args>(rt, std::forward<JSArgs>(args), jsInvoker)...);
return jsi::Value();
} else if constexpr (is_jsi_v<T>) {
static_assert(supportsToJs<R, T>, "Incompatible return type");
return toJs(
rt,
(instance->*method)(
rt, fromJs<Args>(rt, std::forward<JSArgs>(args), jsInvoker)...),
jsInvoker);
} else if constexpr (is_optional_jsi_v<T>) {
static_assert(
is_optional_v<R>
? supportsToJs<typename R::value_type, typename T::value_type>
: supportsToJs<R, typename T::value_type>,
"Incompatible return type");
auto result = toJs(
rt,
(instance->*method)(
rt, fromJs<Args>(rt, std::forward<JSArgs>(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<R, T>, "Incompatible return type");
return (instance->*method)(
rt, fromJs<Args>(rt, std::forward<JSArgs>(args), jsInvoker)...);
}
}
template <typename R, typename... Args>
constexpr size_t getParameterCount(R (*)(Args...)) {
return sizeof...(Args);
}
template <typename C, typename R, typename... Args>
constexpr size_t getParameterCount(R (C::*)(Args...)) {
return sizeof...(Args);
}
} // namespace facebook::react::bridging

View File

@@ -0,0 +1,170 @@
/*
* 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,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.
*/
#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,51 @@
/*
* 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,269 @@
/*
* 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/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)...))] {
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)]() {
if (auto wrapper2 = callback->wrapper_.lock()) {
callImpl(wrapper2->runtime(), wrapper2->callback());
}
};
auto& jsInvoker = wrapper->jsInvoker();
if (priority) {
jsInvoker.invokeAsync(*priority, std::move(fn));
} else {
jsInvoker.invokeAsync(std::move(fn));
}
}
}
};
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 moving to prevent function from get called on another thread.
SyncCallback(SyncCallback&&) = delete;
SyncCallback& operator=(SyncCallback&&) = delete;
~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...>) 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...>) {
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,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "LongLivedObject.h"
namespace facebook::react {
// LongLivedObjectCollection
LongLivedObjectCollection& LongLivedObjectCollection::get() {
static LongLivedObjectCollection instance;
return instance;
}
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().remove(this);
}
} // namespace facebook::react

View File

@@ -0,0 +1,59 @@
/*
* 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 <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:
LongLivedObject() = default;
virtual ~LongLivedObject() = default;
};
/**
* A singleton, thread-safe, write-only collection for the `LongLivedObject`s.
*/
class LongLivedObjectCollection {
public:
static LongLivedObjectCollection& get();
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,58 @@
/*
* 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&, const jsi::Value& value) {
return value.asNumber();
}
static jsi::Value toJs(jsi::Runtime&, double value) {
return value;
}
};
template <>
struct Bridging<float> {
static float fromJs(jsi::Runtime&, const jsi::Value& value) {
return (float)value.asNumber();
}
static jsi::Value toJs(jsi::Runtime&, float value) {
return (double)value;
}
};
template <>
struct Bridging<int32_t> {
static int32_t fromJs(jsi::Runtime&, const jsi::Value& value) {
return (int32_t)value.asNumber();
}
static jsi::Value toJs(jsi::Runtime&, int32_t value) {
return value;
}
};
template <>
struct Bridging<uint32_t> {
static uint32_t fromJs(jsi::Runtime&, const jsi::Value& value) {
return (uint32_t)value.asNumber();
}
static jsi::Value toJs(jsi::Runtime&, uint32_t value) {
return (double)value;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,93 @@
/*
* 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,102 @@
/*
* 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 {
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, AsyncCallback<Error> reject) {
state_->resolve = std::move(resolve);
state_->reject = std::move(reject);
},
jsInvoker));
auto promiseHolder = std::make_shared<PromiseHolder>(promise.asObject(rt));
LongLivedObjectCollection::get().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::move(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::Object p) : 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,107 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#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&, 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,659 @@
/*
* 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).asBool());
EXPECT_FALSE(bridging::toJs(rt, false).asBool());
}
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).asNumber()));
EXPECT_FLOAT_EQ(
1.2f, static_cast<float>(bridging::toJs(rt, 1.2f).asNumber()));
EXPECT_DOUBLE_EQ(1.2, bridging::toJs(rt, 1.2).asNumber());
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 jsi::Value(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().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"));
}
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 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>));
}
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());
}
} // namespace facebook::react

View File

@@ -0,0 +1,77 @@
/*
* 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 <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 TestCallInvoker : public CallInvoker {
public:
void invokeAsync(std::function<void()>&& fn) noexcept override {
queue_.push_back(std::move(fn));
}
void invokeSync(std::function<void()>&&) override {
FAIL() << "JSCallInvoker does not support invokeSync()";
}
private:
friend class BridgingTest;
std::list<std::function<void()>> queue_;
};
class BridgingTest : public ::testing::Test {
protected:
BridgingTest()
: invoker(std::make_shared<TestCallInvoker>()),
runtime(hermes::makeHermesRuntime(
::hermes::vm::RuntimeConfig::Builder()
// Make promises work with Hermes microtasks.
.withMicrotaskQueue(true)
.build())),
rt(*runtime) {}
~BridgingTest() {
LongLivedObjectCollection::get().clear();
}
void TearDown() override {
flushQueue();
// After flushing the invoker queue, we shouldn't leak memory.
EXPECT_EQ(0, LongLivedObjectCollection::get().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() {
while (!invoker->queue_.empty()) {
invoker->queue_.front()();
invoker->queue_.pop_front();
rt.drainMicrotasks(); // Run microtasks every cycle.
}
}
std::shared_ptr<TestCallInvoker> invoker;
std::unique_ptr<jsi::Runtime> runtime;
jsi::Runtime& rt;
};
} // namespace facebook::react

View File

@@ -0,0 +1,97 @@
/*
* 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;
struct TestClass {
TestClass(std::shared_ptr<CallInvoker> invoker) : invoker_(invoker) {}
double add(jsi::Runtime&, int a, float b) {
return a + b;
}
jsi::Object getObject(jsi::Runtime&, jsi::Object obj) {
return obj;
}
AsyncPromise<std::string> getPromise(jsi::Runtime& rt, std::string result) {
auto promise = AsyncPromise<std::string>(rt, invoker_);
promise.resolve(result);
return promise;
}
std::string
callFunc(jsi::Runtime&, SyncCallback<std::string(int)> func, int num) {
return func(num);
}
void callAsync(jsi::Runtime&, 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 = 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);
}
} // namespace facebook::react

View File

@@ -0,0 +1,6 @@
---
Checks: '>
clang-diagnostic-*,
'
InheritParentConfig: true
...

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)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB react_config_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_config STATIC ${react_config_SRC})
target_include_directories(react_config PUBLIC ${REACT_COMMON_DIR})

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.
*/
#include "ReactNativeConfig.h"
namespace facebook::react {
bool EmptyReactNativeConfig::getBool(const std::string& param) const {
if (param == "react_fabric:enabled_layout_animations_ios") {
return true;
}
if (param == "react_fabric:enabled_automatic_interop_android") {
return true;
}
return false;
}
std::string EmptyReactNativeConfig::getString(const std::string& param) const {
return "";
}
int64_t EmptyReactNativeConfig::getInt64(const std::string& param) const {
return 0;
}
double EmptyReactNativeConfig::getDouble(const std::string& param) const {
return 0.0;
}
} // namespace facebook::react

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <string>
namespace facebook::react {
/**
* ReactNative configuration as provided by the hosting app.
* Provide a sub-class implementation to allow app specific customization.
*/
class ReactNativeConfig {
public:
ReactNativeConfig() = default;
virtual ~ReactNativeConfig() = default;
virtual bool getBool(const std::string& param) const = 0;
virtual std::string getString(const std::string& param) const = 0;
virtual int64_t getInt64(const std::string& param) const = 0;
virtual double getDouble(const std::string& param) const = 0;
};
/**
* Empty configuration that will provide hardcoded default values.
*/
class EmptyReactNativeConfig : public ReactNativeConfig {
public:
EmptyReactNativeConfig() = default;
bool getBool(const std::string& param) const override;
std::string getString(const std::string& param) const override;
int64_t getInt64(const std::string& param) const override;
double getDouble(const std::string& param) const override;
};
} // 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB react_debug_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_debug SHARED ${react_debug_SRC})
target_include_directories(react_debug PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_debug log folly_runtime)
if(NOT ${CMAKE_BUILD_TYPE} MATCHES Debug)
target_compile_options(react_debug PUBLIC -DNDEBUG)
endif()

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.
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 = "**/*.{cpp,h}"
s.header_dir = "react/debug"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"DEFINES_MODULE" => "YES" }
if ENV['USE_FRAMEWORKS']
s.module_name = "React_debug"
s.header_mappings_dir = "../.."
end
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,70 @@
/*
* 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.
#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,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.
*/
// 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.
#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,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)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"ReactNative\")
file(GLOB react_featureflags_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_featureflags SHARED ${react_featureflags_SRC})
target_include_directories(react_featureflags PUBLIC ${REACT_COMMON_DIR})

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 = "*.{cpp,h}"
s.header_dir = "react/featureflags"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"DEFINES_MODULE" => "YES" }
if ENV['USE_FRAMEWORKS']
s.module_name = "React_featureflags"
s.header_mappings_dir = "../.."
end
end

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<82f226df2b3824d03b755a042b20bec5>>
*/
/**
* 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 {
bool ReactNativeFeatureFlags::commonTestFlag() {
return getAccessor().commonTestFlag();
}
bool ReactNativeFeatureFlags::enableBackgroundExecutor() {
return getAccessor().enableBackgroundExecutor();
}
bool ReactNativeFeatureFlags::useModernRuntimeScheduler() {
return getAccessor().useModernRuntimeScheduler();
}
bool ReactNativeFeatureFlags::enableMicrotasks() {
return getAccessor().enableMicrotasks();
}
bool ReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop() {
return getAccessor().batchRenderingUpdatesInEventLoop();
}
bool ReactNativeFeatureFlags::enableSpannableBuildingUnification() {
return getAccessor().enableSpannableBuildingUnification();
}
bool ReactNativeFeatureFlags::enableCustomDrawOrderFabric() {
return getAccessor().enableCustomDrawOrderFabric();
}
bool ReactNativeFeatureFlags::enableFixForClippedSubviewsCrash() {
return getAccessor().enableFixForClippedSubviewsCrash();
}
bool ReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection() {
return getAccessor().inspectorEnableCxxInspectorPackagerConnection();
}
bool ReactNativeFeatureFlags::inspectorEnableModernCDPRegistry() {
return getAccessor().inspectorEnableModernCDPRegistry();
}
void ReactNativeFeatureFlags::override(
std::unique_ptr<ReactNativeFeatureFlagsProvider> provider) {
getAccessor().override(std::move(provider));
}
void ReactNativeFeatureFlags::dangerouslyReset() {
getAccessor(true);
}
ReactNativeFeatureFlagsAccessor& ReactNativeFeatureFlags::getAccessor(
bool reset) {
static std::unique_ptr<ReactNativeFeatureFlagsAccessor> accessor;
if (accessor == nullptr || reset) {
accessor = std::make_unique<ReactNativeFeatureFlagsAccessor>();
}
return *accessor;
}
} // namespace facebook::react

View File

@@ -0,0 +1,126 @@
/*
* 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<<3bfae310dfd28040f4b80d1a1df8b7b3>>
*/
/**
* 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>
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.
*/
static bool commonTestFlag();
/**
* Enables the use of a background executor to compute layout and commit updates on Fabric (this system is deprecated and should not be used).
*/
static bool enableBackgroundExecutor();
/**
* When enabled, it uses the modern fork of RuntimeScheduler that allows scheduling tasks with priorities from any thread.
*/
static bool useModernRuntimeScheduler();
/**
* Enables the use of microtasks in Hermes (scheduling) and RuntimeScheduler (execution).
*/
static bool enableMicrotasks();
/**
* When enabled, the RuntimeScheduler processing the event loop will batch all rendering updates and dispatch them together at the end of each iteration of the loop.
*/
static bool batchRenderingUpdatesInEventLoop();
/**
* Uses new, deduplicated logic for constructing Android Spannables from text fragments
*/
static bool enableSpannableBuildingUnification();
/**
* When enabled, Fabric will use customDrawOrder in ReactViewGroup (similar to old architecture).
*/
static bool enableCustomDrawOrderFabric();
/**
* Attempt at fixing a crash related to subview clipping on Android. This is a kill switch for the fix
*/
static bool enableFixForClippedSubviewsCrash();
/**
* Flag determining if the C++ implementation of InspectorPackagerConnection should be used instead of the per-platform one. This flag is global and should not be changed across React Host lifetimes.
*/
static bool inspectorEnableCxxInspectorPackagerConnection();
/**
* Flag determining if the modern CDP backend should be enabled. This flag is global and should not be changed across React Host lifetimes.
*/
static bool inspectorEnableModernCDPRegistry();
/**
* 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>());
* ```
*/
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.
*/
static void dangerouslyReset();
private:
ReactNativeFeatureFlags() = delete;
static ReactNativeFeatureFlagsAccessor& getAccessor(bool reset = false);
};
} // namespace facebook::react

View File

@@ -0,0 +1,251 @@
/*
* 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<<2154292f89306f25289583537833a065>>
*/
/**
* 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 <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
#include <sstream>
#include <stdexcept>
#include <string>
#include "ReactNativeFeatureFlags.h"
namespace facebook::react {
ReactNativeFeatureFlagsAccessor::ReactNativeFeatureFlagsAccessor()
: currentProvider_(std::make_unique<ReactNativeFeatureFlagsDefaults>()),
wasOverridden_(false) {}
bool ReactNativeFeatureFlagsAccessor::commonTestFlag() {
auto flagValue = commonTestFlag_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(0, "commonTestFlag");
flagValue = currentProvider_->commonTestFlag();
commonTestFlag_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::enableBackgroundExecutor() {
auto flagValue = enableBackgroundExecutor_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(1, "enableBackgroundExecutor");
flagValue = currentProvider_->enableBackgroundExecutor();
enableBackgroundExecutor_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::useModernRuntimeScheduler() {
auto flagValue = useModernRuntimeScheduler_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(2, "useModernRuntimeScheduler");
flagValue = currentProvider_->useModernRuntimeScheduler();
useModernRuntimeScheduler_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::enableMicrotasks() {
auto flagValue = enableMicrotasks_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(3, "enableMicrotasks");
flagValue = currentProvider_->enableMicrotasks();
enableMicrotasks_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::batchRenderingUpdatesInEventLoop() {
auto flagValue = batchRenderingUpdatesInEventLoop_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(4, "batchRenderingUpdatesInEventLoop");
flagValue = currentProvider_->batchRenderingUpdatesInEventLoop();
batchRenderingUpdatesInEventLoop_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::enableSpannableBuildingUnification() {
auto flagValue = enableSpannableBuildingUnification_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(5, "enableSpannableBuildingUnification");
flagValue = currentProvider_->enableSpannableBuildingUnification();
enableSpannableBuildingUnification_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::enableCustomDrawOrderFabric() {
auto flagValue = enableCustomDrawOrderFabric_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(6, "enableCustomDrawOrderFabric");
flagValue = currentProvider_->enableCustomDrawOrderFabric();
enableCustomDrawOrderFabric_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::enableFixForClippedSubviewsCrash() {
auto flagValue = enableFixForClippedSubviewsCrash_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(7, "enableFixForClippedSubviewsCrash");
flagValue = currentProvider_->enableFixForClippedSubviewsCrash();
enableFixForClippedSubviewsCrash_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::inspectorEnableCxxInspectorPackagerConnection() {
auto flagValue = inspectorEnableCxxInspectorPackagerConnection_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(8, "inspectorEnableCxxInspectorPackagerConnection");
flagValue = currentProvider_->inspectorEnableCxxInspectorPackagerConnection();
inspectorEnableCxxInspectorPackagerConnection_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::inspectorEnableModernCDPRegistry() {
auto flagValue = inspectorEnableModernCDPRegistry_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(9, "inspectorEnableModernCDPRegistry");
flagValue = currentProvider_->inspectorEnableModernCDPRegistry();
inspectorEnableModernCDPRegistry_ = flagValue;
}
return flagValue.value();
}
void ReactNativeFeatureFlagsAccessor::override(
std::unique_ptr<ReactNativeFeatureFlagsProvider> provider) {
if (wasOverridden_) {
throw std::runtime_error(
"Feature flags cannot be overridden more than once");
}
ensureFlagsNotAccessed();
wasOverridden_ = true;
currentProvider_ = std::move(provider);
}
void ReactNativeFeatureFlagsAccessor::markFlagAsAccessed(
int position,
const char* flagName) {
accessedFeatureFlags_[position] = flagName;
}
void ReactNativeFeatureFlagsAccessor::ensureFlagsNotAccessed() {
std::ostringstream featureFlagListBuilder;
for (const auto& featureFlagName : accessedFeatureFlags_) {
if (featureFlagName != nullptr) {
featureFlagListBuilder << featureFlagName << ", ";
}
}
std::string accessedFeatureFlagNames = featureFlagListBuilder.str();
if (!accessedFeatureFlagNames.empty()) {
accessedFeatureFlagNames =
accessedFeatureFlagNames.substr(0, accessedFeatureFlagNames.size() - 2);
}
if (!accessedFeatureFlagNames.empty()) {
throw std::runtime_error(
"Feature flags were accessed before being overridden: " +
accessedFeatureFlagNames);
}
}
} // namespace facebook::react

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.
*
* @generated SignedSource<<95487968b66d40e1ec53936b06084931>>
*/
/**
* 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>
namespace facebook::react {
class ReactNativeFeatureFlagsAccessor {
public:
ReactNativeFeatureFlagsAccessor();
bool commonTestFlag();
bool enableBackgroundExecutor();
bool useModernRuntimeScheduler();
bool enableMicrotasks();
bool batchRenderingUpdatesInEventLoop();
bool enableSpannableBuildingUnification();
bool enableCustomDrawOrderFabric();
bool enableFixForClippedSubviewsCrash();
bool inspectorEnableCxxInspectorPackagerConnection();
bool inspectorEnableModernCDPRegistry();
void override(std::unique_ptr<ReactNativeFeatureFlagsProvider> provider);
private:
void markFlagAsAccessed(int position, const char* flagName);
void ensureFlagsNotAccessed();
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
bool wasOverridden_;
std::array<std::atomic<const char*>, 10> accessedFeatureFlags_;
std::atomic<std::optional<bool>> commonTestFlag_;
std::atomic<std::optional<bool>> enableBackgroundExecutor_;
std::atomic<std::optional<bool>> useModernRuntimeScheduler_;
std::atomic<std::optional<bool>> enableMicrotasks_;
std::atomic<std::optional<bool>> batchRenderingUpdatesInEventLoop_;
std::atomic<std::optional<bool>> enableSpannableBuildingUnification_;
std::atomic<std::optional<bool>> enableCustomDrawOrderFabric_;
std::atomic<std::optional<bool>> enableFixForClippedSubviewsCrash_;
std::atomic<std::optional<bool>> inspectorEnableCxxInspectorPackagerConnection_;
std::atomic<std::optional<bool>> inspectorEnableModernCDPRegistry_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<4832483bb3648380f2bb9312311f579c>>
*/
/**
* 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 enableBackgroundExecutor() override {
return false;
}
bool useModernRuntimeScheduler() override {
return false;
}
bool enableMicrotasks() override {
return false;
}
bool batchRenderingUpdatesInEventLoop() override {
return false;
}
bool enableSpannableBuildingUnification() override {
return false;
}
bool enableCustomDrawOrderFabric() override {
return false;
}
bool enableFixForClippedSubviewsCrash() override {
return false;
}
bool inspectorEnableCxxInspectorPackagerConnection() override {
return false;
}
bool inspectorEnableModernCDPRegistry() override {
return false;
}
};
} // 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.
*
* @generated SignedSource<<f707d15cf978d7342cdf5aab18444219>>
*/
/**
* 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 enableBackgroundExecutor() = 0;
virtual bool useModernRuntimeScheduler() = 0;
virtual bool enableMicrotasks() = 0;
virtual bool batchRenderingUpdatesInEventLoop() = 0;
virtual bool enableSpannableBuildingUnification() = 0;
virtual bool enableCustomDrawOrderFabric() = 0;
virtual bool enableFixForClippedSubviewsCrash() = 0;
virtual bool inspectorEnableCxxInspectorPackagerConnection() = 0;
virtual bool inspectorEnableModernCDPRegistry() = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,124 @@
/*
* 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);
}
} // namespace facebook::react

View File

@@ -0,0 +1,6 @@
---
Checks: '>
clang-diagnostic-*,
'
InheritParentConfig: true
...

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)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"ReactNative\")
file(GLOB react_nativemodule_core_SRC CONFIGURE_DEPENDS
ReactCommon/*.cpp
platform/android/ReactCommon/*.cpp)
add_library(react_nativemodule_core
SHARED
${react_nativemodule_core_SRC})
target_include_directories(react_nativemodule_core
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/platform/android/
)
target_link_libraries(react_nativemodule_core
fbjni
folly_runtime
glog
jsi
react_bridging
react_debug
react_utils
reactperflogger
reactnativejni)

View File

@@ -0,0 +1,11 @@
/*
* 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
// This header is left here for compatibility reasons.
#include <react/bridging/CallbackWrapper.h>

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "CxxTurboModuleUtils.h"
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()[name] = moduleProviderFunc;
}
} // namespace facebook::react

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.
*/
#pragma once
#include <string>
#include <unordered_map>
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/TurboModule.h>
namespace facebook::react {
std::unordered_map<
std::string,
std::function<
std::shared_ptr<TurboModule>(std::shared_ptr<CallInvoker> jsInvoker)>>&
globalExportedCxxTurboModuleMap();
void registerCxxModuleToGlobalModuleMap(
std::string name,
std::function<std::shared_ptr<TurboModule>(
std::shared_ptr<CallInvoker> jsInvoker)> moduleProviderFunc);
} // namespace facebook::react
/*
* You can use this macro to register your C++ TurboModule in your .cpp
* implementation if you do not have access to getTurboModule:jsInvoker:
* callback. This will register the module before main() is called,
* so it will incur a startup cost.
*
* RCT_EXPORT_CXX_MODULE_EXPERIMENTAL(ModuleExample) becomes:
*
* #pragma clang diagnostic push
* #pragma clang diagnostic ignored "-Wglobal-constructors"
* struct ModuleExampleLoad {
* ModuleExampleLoad() {
* facebook::react::registerCxxModule(name,
* [&](std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
* return
* std::make_shared<facebook::react::ModuleExample>(jsInvoker);
* });
* }
* };
* static ModuleExampleLoad moduleExampleLoad;
* #pragma clang diagnostic pop
*
*/
#define RCT_EXPORT_CXX_MODULE_EXPERIMENTAL(name) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") struct \
name##Load { \
name##Load() { \
facebook::react::registerCxxModuleToGlobalModuleMap( \
#name, \
[&](std::shared_ptr<facebook::react::CallInvoker> jsInvoker) { \
return std::make_shared<facebook::react::name>(jsInvoker); \
}); \
} \
}; \
static name##Load _##name##Load; \
_Pragma("clang diagnostic pop")

View File

@@ -0,0 +1,11 @@
/*
* 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
// This header is left here for compatibility reasons.
#include <react/bridging/LongLivedObject.h>

View File

@@ -0,0 +1,228 @@
/*
* 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(
jsi::Runtime& runtime,
std::weak_ptr<CallbackWrapper> weakWrapper) {
return [weakWrapper,
wrapperWasCalled = false](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]() {
auto strongWrapper2 = weakWrapper.lock();
if (!strongWrapper2) {
return;
}
std::vector<jsi::Value> innerArgs;
for (auto& a : args) {
innerArgs.push_back(
jsi::valueFromDynamic(strongWrapper2->runtime(), a));
}
strongWrapper2->callback().call(
strongWrapper2->runtime(),
(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::get(
jsi::Runtime& runtime,
const jsi::PropNameID& propName) {
std::string propNameUtf8 = propName.utf8(runtime);
auto result = jsi::Value::undefined();
if (propNameUtf8 == "getConstants") {
// This is special cased because `getConstants()` is already a part of
// CxxModule.
result = 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) {
result = 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);
});
}
}
}
// If we have a JS wrapper, cache the result of this lookup
if (jsRepresentation_) {
auto jsRepresentation = jsRepresentation_->lock(runtime);
if (!jsRepresentation.isUndefined()) {
std::move(jsRepresentation)
.asObject(runtime)
.setProperty(runtime, propName, result);
}
}
return result;
}
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 it = cxxMethods_.begin(); it != cxxMethods_.end(); it++) {
result.push_back(jsi::PropNameID::forUtf8(runtime, it->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(folly::to<std::string>(
"Expected ",
method.callbacks,
" callbacks, but only ",
count,
" parameters provided"));
}
if (method.callbacks == 1) {
auto wrapper = CallbackWrapper::createWeak(
args[count - 1].getObject(runtime).getFunction(runtime),
runtime,
jsInvoker_);
first = makeTurboCxxModuleCallback(runtime, 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(runtime, wrapper1);
second = makeTurboCxxModuleCallback(runtime, 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(rt, resolveWrapper);
CxxModule::Callback reject =
makeTurboCxxModuleCallback(rt, 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,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <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 get(
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,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.
*/
#include "TurboModule.h"
#include <react/debug/react_native_assert.h>
namespace facebook::react {
TurboModuleMethodValueKind getTurboModuleMethodValueKind(
jsi::Runtime& rt,
const jsi::Value* value) {
if (!value || 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(
jsi::Runtime& runtime,
const std::string& eventName,
ArgFactory argFactory) {
jsInvoker_->invokeAsync([&runtime, eventName, argFactory]() {
jsi::Value emitter =
runtime.global().getProperty(runtime, "__rctDeviceEventEmitter");
if (!emitter.isUndefined()) {
jsi::Object emitterObject = emitter.asObject(runtime);
// TODO: consider caching these
jsi::Function emitFunction =
emitterObject.getPropertyAsFunction(runtime, "emit");
std::vector<jsi::Value> args;
args.emplace_back(
jsi::String::createFromAscii(runtime, eventName.c_str()));
if (argFactory) {
argFactory(runtime, args);
}
emitFunction.callWithThis(
runtime, emitterObject, (const jsi::Value*)args.data(), args.size());
}
});
}
} // namespace facebook::react

View File

@@ -0,0 +1,148 @@
/*
* 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 <unordered_map>
#include <jsi/jsi.h>
#include <ReactCommon/CallInvoker.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 facebook::jsi::HostObject {
public:
TurboModule(std::string name, std::shared_ptr<CallInvoker> jsInvoker);
// Note: keep this method declared inline to avoid conflicts
// between RTTI and non-RTTI compilation units
facebook::jsi::Value get(
facebook::jsi::Runtime& runtime,
const facebook::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;
}
}
virtual std::vector<facebook::jsi::PropNameID> getPropertyNames(
facebook::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;
facebook::jsi::Value (*invoker)(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count);
};
std::unordered_map<std::string, MethodMetadata> methodMap_;
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(
jsi::Runtime& runtime,
const std::string& eventName,
ArgFactory argFactory = nullptr);
virtual jsi::Value create(
jsi::Runtime& runtime,
const jsi::PropNameID& propName) {
std::string propNameUtf8 = propName.utf8(runtime);
auto p = methodMap_.find(propNameUtf8);
if (p == methodMap_.end()) {
// Method was not found, let JS decide what to do.
return facebook::jsi::Value::undefined();
} else {
const MethodMetadata& meta = p->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); });
}
}
private:
friend class TurboCxxModule;
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,176 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TurboModuleBinding.h"
#include <cxxreact/SystraceSection.h>
#include <react/utils/jsi.h>
#include <stdexcept>
#include <string>
using namespace facebook;
namespace facebook::react {
class BridgelessNativeModuleProxy : public jsi::HostObject {
std::unique_ptr<TurboModuleBinding> binding_;
public:
BridgelessNativeModuleProxy(std::unique_ptr<TurboModuleBinding> binding)
: binding_(std::move(binding)) {}
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 jsi::Value(false);
}
if (binding_) {
return binding_->getModule(runtime, moduleName);
}
throw jsi::JSError(
runtime,
"Tried to access NativeModule \"" + name.utf8(runtime) +
"\" from the bridge. This isn't allowed in Bridgeless mode.");
}
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(
TurboModuleProviderFunctionType&& moduleProvider,
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection)
: moduleProvider_(std::move(moduleProvider)),
longLivedObjectCollection_(std::move(longLivedObjectCollection)) {}
void TurboModuleBinding::install(
jsi::Runtime& runtime,
TurboModuleProviderFunctionType&& moduleProvider,
TurboModuleProviderFunctionType&& legacyModuleProvider,
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection) {
runtime.global().setProperty(
runtime,
"__turboModuleProxy",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "__turboModuleProxy"),
1,
[binding = TurboModuleBinding(
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);
}));
if (runtime.global().hasProperty(runtime, "RN$Bridgeless")) {
bool rnTurboInterop = legacyModuleProvider != nullptr;
auto turboModuleBinding = legacyModuleProvider
? std::make_unique<TurboModuleBinding>(
std::move(legacyModuleProvider), longLivedObjectCollection)
: nullptr;
auto nativeModuleProxy = std::make_shared<BridgelessNativeModuleProxy>(
std::move(turboModuleBinding));
defineReadOnlyGlobal(
runtime, "RN$TurboInterop", jsi::Value(rnTurboInterop));
defineReadOnlyGlobal(
runtime,
"nativeModuleProxy",
jsi::Object::createFromHostObject(runtime, nativeModuleProxy));
}
}
TurboModuleBinding::~TurboModuleBinding() {
if (longLivedObjectCollection_) {
longLivedObjectCollection_->clear();
} else {
LongLivedObjectCollection::get().clear();
}
}
jsi::Value TurboModuleBinding::getModule(
jsi::Runtime& runtime,
const std::string& moduleName) const {
std::shared_ptr<TurboModule> module;
{
SystraceSection s(
"TurboModuleBinding::moduleProvider", "module", moduleName);
module = moduleProvider_(moduleName);
}
if (module) {
// 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,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 <string>
#include <ReactCommon/LongLivedObject.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.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(
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;
TurboModuleProviderFunctionType moduleProvider_;
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,329 @@
/*
* 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 {
namespace 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 TurboModulePerfLogger
} // 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 <reactperflogger/NativeModulePerfLogger.h>
#include <memory>
namespace facebook::react {
namespace 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 TurboModulePerfLogger
} // namespace facebook::react

View File

@@ -0,0 +1,103 @@
/*
* 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 {
static jsi::Value deepCopyJSIValue(jsi::Runtime& rt, const jsi::Value& value) {
if (value.isNull()) {
return jsi::Value::null();
}
if (value.isBool()) {
return jsi::Value(value.getBool());
}
if (value.isNumber()) {
return jsi::Value(value.getNumber());
}
if (value.isString()) {
return value.getString(rt);
}
if (value.isObject()) {
jsi::Object o = value.getObject(rt);
if (o.isArray(rt)) {
return deepCopyJSIArray(rt, o.getArray(rt));
}
if (o.isFunction(rt)) {
return o.getFunction(rt);
}
return deepCopyJSIObject(rt, o);
}
return jsi::Value::undefined();
}
jsi::Object deepCopyJSIObject(jsi::Runtime& rt, const jsi::Object& obj) {
jsi::Object copy(rt);
jsi::Array propertyNames = obj.getPropertyNames(rt);
size_t size = propertyNames.size(rt);
for (size_t i = 0; i < size; i++) {
jsi::String name = propertyNames.getValueAtIndex(rt, i).getString(rt);
jsi::Value value = obj.getProperty(rt, name);
copy.setProperty(rt, name, deepCopyJSIValue(rt, value));
}
return copy;
}
jsi::Array deepCopyJSIArray(jsi::Runtime& rt, const jsi::Array& arr) {
size_t size = arr.size(rt);
jsi::Array copy(rt, size);
for (size_t i = 0; i < size; i++) {
copy.setValueAtIndex(
rt, i, deepCopyJSIValue(rt, arr.getValueAtIndex(rt, i)));
}
return copy;
}
Promise::Promise(jsi::Runtime& rt, jsi::Function resolve, jsi::Function reject)
: runtime_(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,59 @@
/*
* 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 <cassert>
#include <string>
#include <jsi/jsi.h>
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/CallbackWrapper.h>
namespace facebook::react {
jsi::Object deepCopyJSIObject(jsi::Runtime& rt, const jsi::Object& obj);
jsi::Array deepCopyJSIArray(jsi::Runtime& rt, const jsi::Array& arr);
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& error);
jsi::Runtime& runtime_;
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);
// Deprecated. Use AsyncCallback instead.
class RAIICallbackWrapperDestroyer {
public:
RAIICallbackWrapperDestroyer(std::weak_ptr<CallbackWrapper> callbackWrapper)
: callbackWrapper_(callbackWrapper) {}
~RAIICallbackWrapperDestroyer() {
auto strongWrapper = callbackWrapper_.lock();
if (!strongWrapper) {
return;
}
strongWrapper->destroy();
}
private:
std::weak_ptr<CallbackWrapper> callbackWrapper_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,190 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#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,
std::vector<MethodDescriptor> methodDescriptors)
: JavaTurboModule(params),
methodDescriptors_(methodDescriptors),
methodIDs_(methodDescriptors.size()),
constantsCache_(jsi::Value::undefined()) {
for (const auto& methodDescriptor : methodDescriptors) {
methodMap_[methodDescriptor.methodName] = MethodMetadata{
static_cast<size_t>(methodDescriptor.jsArgCount), 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,50 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#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,
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,947 @@
/*
* 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 <memory>
#include <string>
#include <cxxreact/SystraceSection.h>
#include <fbjni/fbjni.h>
#include <glog/logging.h>
#include <jsi/jsi.h>
#include <ReactCommon/TurboModule.h>
#include <ReactCommon/TurboModulePerfLogger.h>
#include <ReactCommon/TurboModuleUtils.h>
#include <jsi/JSIDynamic.h>
#include <react/bridging/Bridging.h>
#include <react/debug/react_native_assert.h>
#include <react/jni/NativeMap.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/jni/WritableNativeMap.h>
#include "JavaTurboModule.h"
namespace facebook::react {
namespace TMPL = TurboModulePerfLogger;
JavaTurboModule::JavaTurboModule(const InitParams& params)
: TurboModule(params.moduleName, params.jsInvoker),
instance_(jni::make_global(params.instance)),
nativeMethodCallInvoker_(params.nativeMethodCallInvoker),
shouldVoidMethodsExecuteSync_(params.shouldVoidMethodsExecuteSync) {}
JavaTurboModule::~JavaTurboModule() {
/**
* TODO(T75896241): In E2E tests, instance_ is null. Investigate why. Can we
* get rid of this null check?
*/
if (!instance_) {
return;
}
nativeMethodCallInvoker_->invokeAsync(
"~" + name_, [instance = std::move(instance_)]() mutable {
/**
* Reset the global NativeModule ref on the NativeModules thread. Why:
* - ~JavaTurboModule() can be called on a non-JVM thread. If we reset
* the global ref in ~JavaTurboModule(), we might access the JVM from a
* non-JVM thread, which will crash the app.
*/
instance.reset();
});
}
namespace {
constexpr auto kReactFeatureFlagsJavaDescriptor =
"com/facebook/react/config/ReactFeatureFlags";
bool getFeatureFlagBoolValue(const char* name) {
static const auto reactFeatureFlagsClass =
facebook::jni::findClassStatic(kReactFeatureFlagsJavaDescriptor);
const auto field = reactFeatureFlagsClass->getStaticField<jboolean>(name);
return reactFeatureFlagsClass->getStaticFieldValue(field);
}
bool traceTurboModulePromiseRejections() {
static bool traceRejections =
getFeatureFlagBoolValue("traceTurboModulePromiseRejections");
return traceRejections;
}
bool rejectTurboModulePromiseOnNativeError() {
static bool rejectOnError =
getFeatureFlagBoolValue("rejectTurboModulePromiseOnNativeError");
return rejectOnError;
}
struct JNIArgs {
JNIArgs(size_t count) : args_(count) {}
std::vector<jvalue> args_;
std::vector<jobject> globalRefs_;
};
auto createJavaCallback(
jsi::Runtime& rt,
jsi::Function&& function,
std::shared_ptr<CallInvoker> jsInvoker) {
std::optional<AsyncCallback<>> callback(
{rt, std::move(function), std::move(jsInvoker)});
return JCxxCallbackImpl::newObjectCxxArgs(
[callback = std::move(callback)](folly::dynamic args) mutable {
if (!callback) {
LOG(FATAL) << "Callback arg cannot be called more than once";
return;
}
callback->call([args = std::move(args)](
jsi::Runtime& rt, jsi::Function& jsFunction) {
std::vector<jsi::Value> jsArgs;
jsArgs.reserve(args.size());
for (const auto& val : args) {
jsArgs.emplace_back(jsi::valueFromDynamic(rt, val));
}
jsFunction.call(rt, (const jsi::Value*)jsArgs.data(), jsArgs.size());
});
callback = std::nullopt;
});
}
struct JPromiseImpl : public jni::JavaClass<JPromiseImpl> {
constexpr static auto kJavaDescriptor =
"Lcom/facebook/react/bridge/PromiseImpl;";
static jni::local_ref<javaobject> create(
jni::local_ref<JCallback::javaobject> resolve,
jni::local_ref<JCallback::javaobject> reject) {
return newInstance(resolve, reject);
}
};
// This is used for generating short exception strings.
std::string stringifyJSIValue(const jsi::Value& v, jsi::Runtime* rt = nullptr) {
if (v.isUndefined()) {
return "undefined";
}
if (v.isNull()) {
return "null";
}
if (v.isBool()) {
return std::string("a boolean (") + (v.getBool() ? "true" : "false") + ")";
}
if (v.isNumber()) {
return "a number (" + std::to_string(v.getNumber()) + ")";
}
if (v.isString()) {
return "a string (\"" + v.getString(*rt).utf8(*rt) + "\")";
}
react_native_assert(v.isObject() && "Expecting object.");
return rt != nullptr && v.getObject(*rt).isFunction(*rt) ? "a function"
: "an object";
}
class JavaTurboModuleArgumentConversionException : public std::runtime_error {
public:
JavaTurboModuleArgumentConversionException(
const std::string& expectedType,
int index,
const std::string& methodName,
const jsi::Value* arg,
jsi::Runtime* rt)
: std::runtime_error(
"Expected argument " + std::to_string(index) + " of method \"" +
methodName + "\" to be a " + expectedType + ", but got " +
stringifyJSIValue(*arg, rt)) {}
};
class JavaTurboModuleInvalidArgumentTypeException : public std::runtime_error {
public:
JavaTurboModuleInvalidArgumentTypeException(
const std::string& actualType,
int argIndex,
const std::string& methodName)
: std::runtime_error(
"Called method \"" + methodName + "\" with unsupported type " +
actualType + " at argument " + std::to_string(argIndex)) {}
};
class JavaTurboModuleInvalidArgumentCountException : public std::runtime_error {
public:
JavaTurboModuleInvalidArgumentCountException(
const std::string& methodName,
int actualArgCount,
int expectedArgCount)
: std::runtime_error(
"TurboModule method \"" + methodName + "\" called with " +
std::to_string(actualArgCount) +
" arguments (expected argument count: " +
std::to_string(expectedArgCount) + ").") {}
};
/**
* See
* https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html
* for a description of Java method signature structure.
*/
std::vector<std::string> getMethodArgTypesFromSignature(
const std::string& methodSignature) {
std::vector<std::string> methodArgs;
for (auto it = methodSignature.begin(); it != methodSignature.end();
it += 1) {
if (*it == '(') {
continue;
}
if (*it == ')') {
break;
}
std::string type;
if (*it == '[') {
type += *it;
it += 1;
}
if (*it == 'L') {
for (; it != methodSignature.end(); it += 1) {
type += *it;
if (*it == ';') {
break;
}
}
} else {
type += *it;
}
methodArgs.push_back(type);
}
return methodArgs;
}
int32_t getUniqueId() {
static int32_t counter = 0;
return counter++;
}
// fbjni already does this conversion, but since we are using plain JNI, this
// needs to be done again
// TODO (axe) Reuse existing implementation as needed - the exist in
// MethodInvoker.cpp
JNIArgs convertJSIArgsToJNIArgs(
JNIEnv* env,
jsi::Runtime& rt,
const std::string& methodName,
const std::vector<std::string>& methodArgTypes,
const jsi::Value* args,
size_t count,
const std::shared_ptr<CallInvoker>& jsInvoker,
TurboModuleMethodValueKind valueKind) {
size_t expectedArgumentCount = valueKind == PromiseKind
? methodArgTypes.size() - 1
: methodArgTypes.size();
if (expectedArgumentCount != count) {
throw JavaTurboModuleInvalidArgumentCountException(
methodName,
static_cast<int>(count),
static_cast<int>(expectedArgumentCount));
}
JNIArgs jniArgs(valueKind == PromiseKind ? count + 1 : count);
auto& jargs = jniArgs.args_;
auto& globalRefs = jniArgs.globalRefs_;
auto makeGlobalIfNecessary =
[&globalRefs, env, valueKind](jobject obj) -> jobject {
if (valueKind == VoidKind || valueKind == PromiseKind) {
jobject globalObj = env->NewGlobalRef(obj);
globalRefs.push_back(globalObj);
env->DeleteLocalRef(obj);
return globalObj;
}
return obj;
};
for (unsigned int argIndex = 0; argIndex < count; argIndex += 1) {
const std::string& type = methodArgTypes.at(argIndex);
const jsi::Value* arg = &args[argIndex];
jvalue* jarg = &jargs[argIndex];
if (type == "D") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->d = arg->getNumber();
continue;
}
if (type == "F") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->f = (float)arg->getNumber();
continue;
}
if (type == "I") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->i = (int)arg->getNumber();
continue;
}
if (type == "Z") {
if (!arg->isBool()) {
throw JavaTurboModuleArgumentConversionException(
"boolean", argIndex, methodName, arg, &rt);
}
jarg->z = (jboolean)arg->getBool();
continue;
}
if (arg->isNull() || arg->isUndefined()) {
jarg->l = nullptr;
} else if (type == "Ljava/lang/Double;") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->l = makeGlobalIfNecessary(
jni::JDouble::valueOf(arg->getNumber()).release());
} else if (type == "Ljava/lang/Float;") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->l = makeGlobalIfNecessary(
jni::JFloat::valueOf(arg->getNumber()).release());
} else if (type == "Ljava/lang/Integer;") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->l = makeGlobalIfNecessary(
jni::JInteger::valueOf(arg->getNumber()).release());
} else if (type == "Ljava/lang/Boolean;") {
if (!arg->isBool()) {
throw JavaTurboModuleArgumentConversionException(
"boolean", argIndex, methodName, arg, &rt);
}
jarg->l = makeGlobalIfNecessary(
jni::JBoolean::valueOf(arg->getBool()).release());
} else if (type == "Ljava/lang/String;") {
if (!arg->isString()) {
throw JavaTurboModuleArgumentConversionException(
"string", argIndex, methodName, arg, &rt);
}
jarg->l = makeGlobalIfNecessary(
env->NewStringUTF(arg->getString(rt).utf8(rt).c_str()));
} else if (type == "Lcom/facebook/react/bridge/Callback;") {
if (!(arg->isObject() && arg->getObject(rt).isFunction(rt))) {
throw JavaTurboModuleArgumentConversionException(
"Function", argIndex, methodName, arg, &rt);
}
jsi::Function fn = arg->getObject(rt).getFunction(rt);
jarg->l = makeGlobalIfNecessary(
createJavaCallback(rt, std::move(fn), jsInvoker).release());
} else if (type == "Lcom/facebook/react/bridge/ReadableArray;") {
if (!(arg->isObject() && arg->getObject(rt).isArray(rt))) {
throw JavaTurboModuleArgumentConversionException(
"Array", argIndex, methodName, arg, &rt);
}
auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg);
auto jParams =
ReadableNativeArray::newObjectCxxArgs(std::move(dynamicFromValue));
jarg->l = makeGlobalIfNecessary(jParams.release());
} else if (type == "Lcom/facebook/react/bridge/ReadableMap;") {
if (!(arg->isObject())) {
throw JavaTurboModuleArgumentConversionException(
"Object", argIndex, methodName, arg, &rt);
}
auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg);
auto jParams =
ReadableNativeMap::createWithContents(std::move(dynamicFromValue));
jarg->l = makeGlobalIfNecessary(jParams.release());
} else {
throw JavaTurboModuleInvalidArgumentTypeException(
type, argIndex, methodName);
}
}
return jniArgs;
}
jsi::Value convertFromJMapToValue(JNIEnv* env, jsi::Runtime& rt, jobject arg) {
// We currently use Java Argument.makeNativeMap() method to do this conversion
// This could also be done purely in C++, but iterative over map methods
// but those may end up calling reflection methods anyway
// TODO (axe) Investigate the best way to convert Java Map to Value
jclass jArguments = env->FindClass("com/facebook/react/bridge/Arguments");
static jmethodID jMakeNativeMap = env->GetStaticMethodID(
jArguments,
"makeNativeMap",
"(Ljava/util/Map;)Lcom/facebook/react/bridge/WritableNativeMap;");
auto constants = env->CallStaticObjectMethod(jArguments, jMakeNativeMap, arg);
auto jResult = jni::adopt_local(constants);
auto result = jni::static_ref_cast<NativeMap::jhybridobject>(jResult);
return jsi::valueFromDynamic(rt, result->cthis()->consume());
}
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 stack and Throwable stack trace.
*/
jsi::JSError convertThrowableToJSError(
jsi::Runtime& runtime,
jni::alias_ref<jni::JThrowable> throwable) {
auto stackTrace = throwable->getStackTrace();
jsi::Array stackElements(runtime, stackTrace->size());
for (int i = 0; i < stackTrace->size(); ++i) {
auto frame = stackTrace->getElement(i);
jsi::Object frameObject(runtime);
frameObject.setProperty(runtime, "className", frame->getClassName());
frameObject.setProperty(runtime, "fileName", frame->getFileName());
frameObject.setProperty(runtime, "lineNumber", frame->getLineNumber());
frameObject.setProperty(runtime, "methodName", frame->getMethodName());
stackElements.setValueAtIndex(runtime, i, std::move(frameObject));
}
jsi::Object cause(runtime);
auto name = throwable->getClass()->getCanonicalName()->toStdString();
auto message = throwable->getMessage()->toStdString();
cause.setProperty(runtime, "name", name);
cause.setProperty(runtime, "message", message);
cause.setProperty(runtime, "stackElements", std::move(stackElements));
jsi::Value error =
createJSRuntimeError(runtime, "Exception in HostFunction: " + message);
error.asObject(runtime).setProperty(runtime, "cause", std::move(cause));
return {runtime, std::move(error)};
}
void rejectWithException(
AsyncCallback<>& reject,
std::exception_ptr exception,
std::optional<std::string>& jsInvocationStack) {
auto throwable = jni::getJavaExceptionForCppException(exception);
reject.call([jsInvocationStack, throwable = jni::make_global(throwable)](
jsi::Runtime& rt, jsi::Function& jsFunction) {
auto jsError = convertThrowableToJSError(rt, throwable);
if (jsInvocationStack.has_value()) {
jsError.value().asObject(rt).setProperty(
rt, "stack", jsInvocationStack.value());
}
jsFunction.call(rt, jsError.value());
});
}
} // namespace
jsi::Value JavaTurboModule::invokeJavaMethod(
jsi::Runtime& runtime,
TurboModuleMethodValueKind valueKind,
const std::string& methodNameStr,
const std::string& methodSignature,
const jsi::Value* args,
size_t argCount,
jmethodID& methodID) {
const char* methodName = methodNameStr.c_str();
const char* moduleName = name_.c_str();
bool isMethodSync =
(valueKind == VoidKind && shouldVoidMethodsExecuteSync_) ||
!(valueKind == VoidKind || valueKind == PromiseKind);
if (isMethodSync) {
TMPL::syncMethodCallStart(moduleName, methodName);
TMPL::syncMethodCallArgConversionStart(moduleName, methodName);
} else {
TMPL::asyncMethodCallStart(moduleName, methodName);
TMPL::asyncMethodCallArgConversionStart(moduleName, methodName);
}
JNIEnv* env = jni::Environment::current();
auto instance = instance_.get();
/**
* To account for jclasses and other misc LocalReferences we create.
*/
unsigned int buffer = 6;
/**
* For promises, we have to create a resolve fn, a reject fn, and a promise
* object. For normal returns, we just create the return object.
*/
unsigned int maxReturnObjects = 3;
/**
* When the return type is void, all JNI LocalReferences are converted to
* GlobalReferences. The LocalReferences are then promptly deleted
* after the conversion.
*/
unsigned int actualArgCount =
valueKind == VoidKind ? 0 : static_cast<unsigned int>(argCount);
unsigned int estimatedLocalRefCount =
actualArgCount + maxReturnObjects + buffer;
/**
* This will push a new JNI stack frame for the LocalReferences in this
* function call. When the stack frame for invokeJavaMethod is popped,
* all LocalReferences are deleted.
*
* In total, there can be at most kJniLocalRefMax (= 512) Jni
* LocalReferences alive at a time. estimatedLocalRefCount is provided
* so that PushLocalFrame can throw an out of memory error when the total
* number of alive LocalReferences is estimatedLocalRefCount smaller than
* kJniLocalRefMax.
*/
jni::JniLocalScope scope(env, estimatedLocalRefCount);
auto checkJNIErrorForMethodCall = [&]() -> void {
try {
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
} catch (...) {
if (isMethodSync) {
TMPL::syncMethodCallFail(moduleName, methodName);
} else {
TMPL::asyncMethodCallFail(moduleName, methodName);
}
auto exception = std::current_exception();
auto throwable = jni::getJavaExceptionForCppException(exception);
throw convertThrowableToJSError(runtime, throwable);
}
};
if (!methodID) {
jclass cls = env->GetObjectClass(instance);
methodID = env->GetMethodID(cls, methodName, methodSignature.c_str());
// If the method signature doesn't match, show a redbox here instead of
// crashing later.
checkJNIErrorForMethodCall();
}
// TODO(T43933641): Refactor to remove this special-casing
if (methodNameStr == "getConstants") {
TMPL::syncMethodCallArgConversionEnd(moduleName, methodName);
TMPL::syncMethodCallExecutionStart(moduleName, methodName);
auto constantsMap = (jobject)env->CallObjectMethod(instance, methodID);
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
jsi::Value returnValue = constantsMap == nullptr
? jsi::Value::undefined()
: convertFromJMapToValue(env, runtime, constantsMap);
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
}
std::vector<std::string> methodArgTypes =
getMethodArgTypesFromSignature(methodSignature);
JNIArgs jniArgs = convertJSIArgsToJNIArgs(
env,
runtime,
methodNameStr,
methodArgTypes,
args,
argCount,
jsInvoker_,
valueKind);
if (isMethodSync && valueKind != PromiseKind) {
TMPL::syncMethodCallArgConversionEnd(moduleName, methodName);
TMPL::syncMethodCallExecutionStart(moduleName, methodName);
}
auto& jargs = jniArgs.args_;
auto& globalRefs = jniArgs.globalRefs_;
switch (valueKind) {
case BooleanKind: {
std::string returnType =
methodSignature.substr(methodSignature.find_last_of(')') + 1);
if (returnType == "Ljava/lang/Boolean;") {
auto returnObject =
env->CallObjectMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
auto returnValue = jsi::Value::null();
if (returnObject) {
auto booleanObj = jni::adopt_local(
static_cast<jni::JBoolean::javaobject>(returnObject));
returnValue = jsi::Value(static_cast<bool>(booleanObj->value()));
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
} else {
bool returnBoolean =
(bool)env->CallBooleanMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
jsi::Value returnValue = jsi::Value(returnBoolean);
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
}
}
case NumberKind: {
std::string returnType =
methodSignature.substr(methodSignature.find_last_of(')') + 1);
if (returnType == "Ljava/lang/Double;" ||
returnType == "Ljava/lang/Float;" ||
returnType == "Ljava/lang/Integer;") {
auto returnObject =
env->CallObjectMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
auto returnValue = jsi::Value::null();
if (returnObject) {
if (returnType == "Ljava/lang/Double;") {
auto doubleObj = jni::adopt_local(
static_cast<jni::JDouble::javaobject>(returnObject));
returnValue = jsi::Value(doubleObj->value());
} else if (returnType == "Ljava/lang/Float;") {
auto floatObj = jni::adopt_local(
static_cast<jni::JFloat::javaobject>(returnObject));
returnValue = jsi::Value((double)floatObj->value());
} else if (returnType == "Ljava/lang/Integer;") {
auto intObj = jni::adopt_local(
static_cast<jni::JInteger::javaobject>(returnObject));
returnValue = jsi::Value(intObj->value());
}
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
} else if (returnType == "D" || returnType == "F" || returnType == "I") {
jsi::Value returnValue = jsi::Value::undefined();
if (returnType == "D") {
double returnDouble =
(double)env->CallDoubleMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
returnValue = jsi::Value(returnDouble);
} else if (returnType == "F") {
float returnFloat =
(float)env->CallFloatMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
returnValue = jsi::Value((double)returnFloat);
} else if (returnType == "I") {
int returnInt =
(int)env->CallIntMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
returnValue = jsi::Value(returnInt);
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
}
}
case StringKind: {
auto returnString =
(jstring)env->CallObjectMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
jsi::Value returnValue = jsi::Value::null();
if (returnString != nullptr) {
const char* js = env->GetStringUTFChars(returnString, nullptr);
std::string result = js;
env->ReleaseStringUTFChars(returnString, js);
returnValue =
jsi::Value(runtime, jsi::String::createFromUtf8(runtime, result));
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
}
case ObjectKind: {
auto returnObject =
env->CallObjectMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
jsi::Value returnValue = jsi::Value::null();
if (returnObject != nullptr) {
auto jResult = jni::adopt_local(returnObject);
auto result = jni::static_ref_cast<NativeMap::jhybridobject>(jResult);
returnValue =
jsi::valueFromDynamic(runtime, result->cthis()->consume());
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
}
case ArrayKind: {
auto returnObject =
(jobject)env->CallObjectMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
jsi::Value returnValue = jsi::Value::null();
if (returnObject != nullptr) {
auto jResult = jni::adopt_local(returnObject);
auto result = jni::static_ref_cast<NativeArray::jhybridobject>(jResult);
returnValue =
jsi::valueFromDynamic(runtime, result->cthis()->consume());
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
}
case VoidKind: {
if (shouldVoidMethodsExecuteSync_) {
env->CallVoidMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return jsi::Value::undefined();
}
TMPL::asyncMethodCallArgConversionEnd(moduleName, methodName);
TMPL::asyncMethodCallDispatch(moduleName, methodName);
nativeMethodCallInvoker_->invokeAsync(
methodName,
[jargs,
globalRefs,
methodID,
instance_ = jni::make_weak(instance_),
moduleNameStr = name_,
methodNameStr,
id = getUniqueId()]() mutable {
SystraceSection s(
"JavaTurboModuleAsyncMethodInvocation",
"module",
moduleNameStr,
"method",
methodNameStr,
"returnType",
"void");
auto instance = instance_.lockLocal();
if (!instance) {
return;
}
// Require the env from the current scope, which may be
// different from the original invocation's scope
JNIEnv* env = jni::Environment::current();
const char* moduleName = moduleNameStr.c_str();
const char* methodName = methodNameStr.c_str();
TMPL::asyncMethodCallExecutionStart(moduleName, methodName, id);
env->CallVoidMethodA(instance.get(), methodID, jargs.data());
try {
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
} catch (...) {
TMPL::asyncMethodCallExecutionFail(moduleName, methodName, id);
throw;
}
for (auto globalRef : globalRefs) {
env->DeleteGlobalRef(globalRef);
}
TMPL::asyncMethodCallExecutionEnd(moduleName, methodName, id);
});
TMPL::asyncMethodCallEnd(moduleName, methodName);
return jsi::Value::undefined();
}
case PromiseKind: {
// We could use AsyncPromise here, but this avoids the overhead of
// the shared_ptr for PromiseHolder
jsi::Function Promise =
runtime.global().getPropertyAsFunction(runtime, "Promise");
// The callback is used for auto rejecting if error is caught from method
// invocation
std::optional<AsyncCallback<>> nativeRejectCallback;
// The promise constructor runs its arg immediately, so this is safe
jobject javaPromise;
jsi::Value jsPromise = Promise.callAsConstructor(
runtime,
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "fn"),
2,
[&](jsi::Runtime& runtime,
const jsi::Value&,
const jsi::Value* args,
size_t argCount) {
if (argCount != 2) {
throw jsi::JSError(runtime, "Incorrect number of arguments");
}
if (rejectTurboModulePromiseOnNativeError()) {
nativeRejectCallback = AsyncCallback(
runtime,
args[1].getObject(runtime).getFunction(runtime),
jsInvoker_);
}
auto resolve = createJavaCallback(
runtime,
args[0].getObject(runtime).getFunction(runtime),
jsInvoker_);
auto reject = createJavaCallback(
runtime,
args[1].getObject(runtime).getFunction(runtime),
jsInvoker_);
javaPromise = JPromiseImpl::create(resolve, reject).release();
return jsi::Value::undefined();
}));
jobject globalPromise = env->NewGlobalRef(javaPromise);
globalRefs.push_back(globalPromise);
env->DeleteLocalRef(javaPromise);
jargs[argCount].l = globalPromise;
// JS Stack at the time when the promise is created.
std::optional<std::string> jsInvocationStack;
if (traceTurboModulePromiseRejections()) {
jsInvocationStack = createJSRuntimeError(runtime, "")
.asObject(runtime)
.getProperty(runtime, "stack")
.toString(runtime)
.utf8(runtime);
}
const char* moduleName = name_.c_str();
const char* methodName = methodNameStr.c_str();
TMPL::asyncMethodCallArgConversionEnd(moduleName, methodName);
TMPL::asyncMethodCallDispatch(moduleName, methodName);
nativeMethodCallInvoker_->invokeAsync(
methodName,
[jargs,
rejectCallback = std::move(nativeRejectCallback),
jsInvocationStack = std::move(jsInvocationStack),
globalRefs,
methodID,
instance_ = jni::make_weak(instance_),
moduleNameStr = name_,
methodNameStr,
id = getUniqueId()]() mutable {
SystraceSection s(
"JavaTurboModuleAsyncMethodInvocation",
"module",
moduleNameStr,
"method",
methodNameStr,
"returnType",
"promise");
auto instance = instance_.lockLocal();
if (!instance) {
return;
}
// Require the env from the current scope, which may be
// different from the original invocation's scope
JNIEnv* env = jni::Environment::current();
const char* moduleName = moduleNameStr.c_str();
const char* methodName = methodNameStr.c_str();
TMPL::asyncMethodCallExecutionStart(moduleName, methodName, id);
env->CallVoidMethodA(instance.get(), methodID, jargs.data());
try {
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
} catch (...) {
TMPL::asyncMethodCallExecutionFail(moduleName, methodName, id);
if (rejectTurboModulePromiseOnNativeError() && rejectCallback) {
auto exception = std::current_exception();
rejectWithException(
*rejectCallback, exception, jsInvocationStack);
rejectCallback = std::nullopt;
} else {
throw;
}
}
for (auto globalRef : globalRefs) {
env->DeleteGlobalRef(globalRef);
}
TMPL::asyncMethodCallExecutionEnd(moduleName, methodName, id);
});
TMPL::asyncMethodCallEnd(moduleName, methodName);
return jsPromise;
}
default:
throw std::runtime_error(
"Unable to find method module: " + methodNameStr + "(" +
methodSignature + ")");
}
}
} // 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.
*/
#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;";
};
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;
bool shouldVoidMethodsExecuteSync;
};
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);
private:
// instance_ can be of type JTurboModule, or JNativeModule
jni::global_ref<jobject> instance_;
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker_;
bool shouldVoidMethodsExecuteSync_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,62 @@
# 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
folly_config = get_folly_config()
folly_compiler_flags = folly_config[:compiler_flags]
folly_version = folly_config[:version]
boost_compiler_flags = '-Wno-documentation'
using_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
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.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fmt/include\" \"$(PODS_ROOT)/Headers/Private/React-Core\"",
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"GCC_WARN_PEDANTIC" => "YES" }
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = './'
end
s.source_files = "ReactCommon/**/*.{mm,cpp,h}"
s.dependency "glog"
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-runtimeexecutor"
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
if using_hermes
s.dependency "hermes-engine"
else
s.dependency "React-jsc"
end
end

View File

@@ -0,0 +1,95 @@
/*
* 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 {
namespace 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_;
const jsi::Value &getConstants(jsi::Runtime &runtime);
bool exportsConstants();
};
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,654 @@
/*
* 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 {
namespace 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 && 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 != "") {
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];
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{static_cast<size_t>(jsArgCount), 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_);
}
// TODO: Dispatch getConstants to the main queue, if the module requires main queue setup
jsi::Value ret = this->invokeObjCMethod(
rt,
this->methodDescriptors_[i].jsReturnKind,
this->methodDescriptors_[i].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) {
return this->invokeObjCMethod(
rt,
this->methodDescriptors_[i].jsReturnKind,
this->methodDescriptors_[i].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::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_);
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);
if (arg) {
[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) {
// 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) {
[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_);
if (arg) {
[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_);
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_);
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 react
} // namespace facebook

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <ReactCommon/RuntimeExecutor.h>
#import <jsi/jsi.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^RCTJSIRuntimeHandlingBlock)(facebook::jsi::Runtime &runtime);
@interface RCTRuntimeExecutor : NSObject
- (instancetype)init NS_UNAVAILABLE;
/**
Initializes an object that wraps ways to access the RuntimeExecutor.
@param runtimeExecutor The instance of RuntimeExecutor.
*/
- (instancetype)initWithRuntimeExecutor:(facebook::react::RuntimeExecutor)runtimeExecutor NS_DESIGNATED_INITIALIZER;
- (void)execute:(RCTJSIRuntimeHandlingBlock)block;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTRuntimeExecutor.h"
@implementation RCTRuntimeExecutor {
facebook::react::RuntimeExecutor _runtimeExecutor;
}
#pragma mark - Initializer
- (instancetype)initWithRuntimeExecutor:(facebook::react::RuntimeExecutor)runtimeExecutor
{
if (self = [super init]) {
_runtimeExecutor = runtimeExecutor;
}
return self;
}
#pragma mark - Public API
- (void)execute:(RCTJSIRuntimeHandlingBlock)block
{
if (_runtimeExecutor) {
_runtimeExecutor([=](facebook::jsi::Runtime &runtime) { block(runtime); });
}
}
@end

View File

@@ -0,0 +1,173 @@
/*
* 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 <Foundation/Foundation.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTModuleMethod.h>
#import <ReactCommon/CallInvoker.h>
#import <ReactCommon/TurboModule.h>
#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;
namespace TurboModuleConvertUtils {
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);
id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker);
}
/**
* 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);
/**
* 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, std::string methodName, PromiseInvocationBlock invoke);
};
} // namespace facebook::react
@protocol RCTTurboModule <NSObject>
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params;
@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,817 @@
/*
* 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/SystraceSection.h>
#import <react/bridging/Bridging.h>
#include <glog/logging.h>
#import <objc/message.h>
#import <objc/runtime.h>
#import <atomic>
#import <iostream>
#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 {
namespace 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] ?: "");
}
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, std::shared_ptr<CallInvoker> jsInvoker)
{
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.
[result
addObject:convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker) ?: (id)kCFNull];
}
return [result copy];
}
static NSDictionary *
convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value, std::shared_ptr<CallInvoker> jsInvoker)
{
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);
if (v) {
result[k] = v;
}
}
return [result copy];
}
static RCTResponseSenderBlock
convertJSIFunctionToCallback(jsi::Runtime &rt, jsi::Function &&function, std::shared_ptr<CallInvoker> jsInvoker)
{
__block std::optional<AsyncCallback<>> callback({rt, std::move(function), std::move(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, std::shared_ptr<CallInvoker> jsInvoker)
{
if (value.isUndefined() || value.isNull()) {
return nil;
}
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);
}
if (o.isFunction(runtime)) {
return convertJSIFunctionToCallback(runtime, o.getFunction(runtime), jsInvoker);
}
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker);
}
throw std::runtime_error("Unsupported jsi::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)
{
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));
jsi::Value error = createJSRuntimeError(runtime, "Exception in HostFunction: " + reason);
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;
}
}
jsi::Value ObjCTurboModule::createPromise(jsi::Runtime &runtime, std::string methodName, PromiseInvocationBlock invoke)
{
if (!invoke) {
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) {
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)});
RCTPromiseResolveBlock resolveBlock = ^(id result) {
if (!resolve || !reject) {
if (resolveWasCalled) {
RCTLogError(@"%s: Tried to resolve a promise more than once.", moduleMethod.c_str());
} else {
RCTLogError(
@"%s: Tried to resolve a promise after it's already been rejected.", moduleMethod.c_str());
}
return;
}
resolve->call([result](jsi::Runtime &rt, jsi::Function &jsFunction) {
jsFunction.call(rt, convertObjCObjectToJSIValue(rt, result));
});
resolveWasCalled = YES;
resolve = std::nullopt;
reject = std::nullopt;
};
RCTPromiseRejectBlock rejectBlock = ^(NSString *code, NSString *message, NSError *error) {
if (!resolve || !reject) {
if (resolveWasCalled) {
RCTLogError(@"%s: Tried to reject a promise after it's already been resolved.", moduleMethod.c_str());
} else {
RCTLogError(@"%s: Tried to reject a promise more than once.", moduleMethod.c_str());
}
return;
}
NSDictionary *jsErrorDetails = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
reject->call([jsErrorDetails](jsi::Runtime &rt, jsi::Function &jsFunction) {
jsFunction.call(rt, convertJSErrorDetailsToJSRuntimeError(rt, jsErrorDetails));
});
resolveWasCalled = NO;
resolve = std::nullopt;
reject = std::nullopt;
};
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) {
return;
}
if (isSync) {
TurboModulePerfLogger::syncMethodCallExecutionStart(moduleName, methodNameStr.c_str());
} else {
TurboModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodNameStr.c_str(), asyncCallCounter);
}
@try {
[inv invokeWithTarget:strongModule];
} @catch (NSException *exception) {
throw convertNSExceptionToJSError(runtime, exception);
} @finally {
[retainedObjectsForInvocation removeAllObjects];
}
if (!isSync) {
TurboModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodNameStr.c_str(), asyncCallCounter);
return;
}
void *rawResult;
[inv getReturnValue:&rawResult];
result = (__bridge id)rawResult;
TurboModulePerfLogger::syncMethodCallExecutionEnd(moduleName, methodNameStr.c_str());
};
if (isSync) {
nativeMethodCallInvoker_->invokeSync(methodNameStr, [&]() -> void { block(); });
return result;
} else {
asyncCallCounter = getUniqueId();
TurboModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName);
nativeMethodCallInvoker_->invokeAsync(methodNameStr, [block, moduleName, methodNameStr]() -> void {
SystraceSection 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) {
return;
}
if (shouldVoidMethodsExecuteSync_) {
TurboModulePerfLogger::syncMethodCallExecutionStart(moduleName, methodNameStr.c_str());
} else {
TurboModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodNameStr.c_str(), asyncCallCounter);
}
@try {
[inv invokeWithTarget:strongModule];
} @catch (NSException *exception) {
throw convertNSExceptionToJSError(runtime, exception);
} @finally {
[retainedObjectsForInvocation removeAllObjects];
}
if (shouldVoidMethodsExecuteSync_) {
TurboModulePerfLogger::syncMethodCallExecutionEnd(moduleName, methodNameStr.c_str());
} else {
TurboModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodNameStr.c_str(), asyncCallCounter);
}
return;
};
if (shouldVoidMethodsExecuteSync_) {
nativeMethodCallInvoker_->invokeSync(methodNameStr, [&]() -> void { block(); });
} else {
asyncCallCounter = getUniqueId();
TurboModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName);
nativeMethodCallInvoker_->invokeAsync(methodNameStr, [moduleName, methodNameStr, block]() -> void {
SystraceSection 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_) {
NSMutableDictionary<NSString *, NSArray<NSString *> *> *methodArgumentTypeNames = [NSMutableDictionary new];
unsigned int numberOfMethods;
Class cls = [instance_ class];
Method *methods = class_copyMethodList(object_getClass(cls), &numberOfMethods);
if (methods) {
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]) {
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*.
*/
if (objCArgType == @encode(id)) {
id objCArg = [NSNumber numberWithDouble:v];
[inv setArgument:(void *)&objCArg atIndex:i + 2];
[retainedObjectsForInvocation addObject:objCArg];
} else {
[inv setArgument:(void *)&v atIndex:i + 2];
}
return;
}
/**
* Convert arg to ObjC objects.
*/
id objCArg = convertJSIValueToObjCObject(runtime, arg, jsInvoker_);
if (objCArg) {
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);
[inv setArgument:(void *)&convertedObjCArg atIndex:i + 2];
if (convertedObjCArg) {
[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) {
[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 id<RCTBridgeModule> module = instance_;
if (isSync) {
TurboModulePerfLogger::syncMethodCallArgConversionStart(moduleName, methodName);
} else {
TurboModulePerfLogger::asyncMethodCallArgConversionStart(moduleName, methodName);
}
NSInvocation *inv =
[NSInvocation invocationWithMethodSignature:[[module class] instanceMethodSignatureForSelector:selector]];
[inv setSelector:selector];
NSMethodSignature *methodSignature = [[module class] instanceMethodSignatureForSelector: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_ && methodArgConversionSelectors_[methodName] &&
![methodArgConversionSelectors_[methodName][argIndex] isEqual:[NSNull null]];
}
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_) {
methodArgConversionSelectors_ = [NSMutableDictionary new];
}
if (!methodArgConversionSelectors_[methodName]) {
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:[NSNull null]];
}
}
SEL selector = NSSelectorFromString(fnName);
NSValue *selectorValue = [NSValue valueWithPointer:selector];
methodArgConversionSelectors_[methodName][argIndex] = selectorValue;
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,81 @@
/*
* 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/RCTBridgeProxy.h>
#import <React/RCTDefines.h>
#import <React/RCTTurboModuleRegistry.h>
#import <ReactCommon/RuntimeExecutor.h>
#import <ReactCommon/TurboModuleBinding.h>
#import "RCTTurboModule.h"
@class RCTTurboModuleManager;
@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
/**
* 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
@protocol RCTTurboModuleManagerRuntimeHandler <NSObject>
- (facebook::react::RuntimeExecutor)runtimeExecutorForTurboModuleManager:(RCTTurboModuleManager *)turboModuleManager;
@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;
- (void)installJSBindings:(facebook::jsi::Runtime &)runtime;
- (void)invalidate;
@property (nonatomic, weak, readwrite) id<RCTTurboModuleManagerRuntimeHandler> runtimeHandler;
@end

View File

@@ -0,0 +1,88 @@
/*
* 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<<2466af777a1d69da2c3810e5a2d53a70>>
*/
/**
* 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>
#include "Plugins.h"
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::enableBackgroundExecutor(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableBackgroundExecutor();
}
bool NativeReactNativeFeatureFlags::useModernRuntimeScheduler(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useModernRuntimeScheduler();
}
bool NativeReactNativeFeatureFlags::enableMicrotasks(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableMicrotasks();
}
bool NativeReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop();
}
bool NativeReactNativeFeatureFlags::enableSpannableBuildingUnification(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableSpannableBuildingUnification();
}
bool NativeReactNativeFeatureFlags::enableCustomDrawOrderFabric(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableCustomDrawOrderFabric();
}
bool NativeReactNativeFeatureFlags::enableFixForClippedSubviewsCrash(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableFixForClippedSubviewsCrash();
}
bool NativeReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection();
}
bool NativeReactNativeFeatureFlags::inspectorEnableModernCDPRegistry(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::inspectorEnableModernCDPRegistry();
}
} // namespace facebook::react

View File

@@ -0,0 +1,54 @@
/*
* 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<<30481dc57cc7f389d1ed87d097caedda>>
*/
/**
* 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 <FBReactNativeSpec/FBReactNativeSpecJSI.h>
namespace facebook::react {
class NativeReactNativeFeatureFlags
: public NativeReactNativeFeatureFlagsCxxSpec<
NativeReactNativeFeatureFlags>,
std::enable_shared_from_this<NativeReactNativeFeatureFlags> {
public:
NativeReactNativeFeatureFlags(std::shared_ptr<CallInvoker> jsInvoker);
bool commonTestFlag(jsi::Runtime& runtime);
bool enableBackgroundExecutor(jsi::Runtime& runtime);
bool useModernRuntimeScheduler(jsi::Runtime& runtime);
bool enableMicrotasks(jsi::Runtime& runtime);
bool batchRenderingUpdatesInEventLoop(jsi::Runtime& runtime);
bool enableSpannableBuildingUnification(jsi::Runtime& runtime);
bool enableCustomDrawOrderFabric(jsi::Runtime& runtime);
bool enableFixForClippedSubviewsCrash(jsi::Runtime& runtime);
bool inspectorEnableCxxInspectorPackagerConnection(jsi::Runtime& runtime);
bool inspectorEnableModernCDPRegistry(jsi::Runtime& runtime);
};
} // namespace facebook::react

View File

@@ -0,0 +1,76 @@
# 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
folly_config = get_folly_config()
folly_compiler_flags = folly_config[:compiler_flags]
folly_version = folly_config[:version]
boost_compiler_flags = '-Wno-documentation'
using_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
header_search_paths = [
"\"$(PODS_ROOT)/boost\"",
"\"$(PODS_ROOT)/RCT-Folly\"",
"\"$(PODS_ROOT)/DoubleConversion\"",
"\"$(PODS_ROOT)/fmt/include\"",
"\"$(PODS_ROOT)/Headers/Private/React-Core\"",
]
create_header_search_path_for_frameworks("ReactCommon-Samples").each { |search_path| header_search_paths << "\"#{search_path}\""}
Pod::Spec.new do |s|
s.name = "ReactCommon-Samples"
s.module_name = "ReactCommon_Samples"
s.header_dir = "ReactCommon"
s.version = version
s.summary = "-" # TODO
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => header_search_paths,
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"GCC_WARN_PEDANTIC" => "YES" }
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = './'
end
s.source_files = "ReactCommon/**/*.{cpp,h}",
"platform/ios/**/*.{mm,cpp,h}"
s.dependency "RCT-Folly"
s.dependency "DoubleConversion"
s.dependency "fmt", "9.1.0"
s.dependency "React-Core"
s.dependency "React-cxxreact"
s.dependency "React-jsi"
add_dependency(s, "React-Codegen", :additional_framework_paths => ["build/generated/ios"])
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
add_dependency(s, "React-NativeModulesApple", :additional_framework_paths => ["build/generated/ios"])
if using_hermes
s.dependency "hermes-engine"
else
s.dependency "React-jsc"
end
end

View File

@@ -0,0 +1,151 @@
/*
* 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 "NativeSampleTurboCxxModuleSpecJSI.h"
// NOTE: This entire file should be codegen'ed.
namespace facebook::react {
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_voidFunc(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)->voidFunc(rt);
return jsi::Value::undefined();
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getBool(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return jsi::Value(
static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getBool(rt, args[0].getBool()));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getEnum(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return jsi::Value(
static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getEnum(rt, args[0].getNumber()));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getNumber(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return jsi::Value(
static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getNumber(rt, args[0].getNumber()));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getString(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getString(rt, args[0].getString(rt));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getArray(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getArray(rt, args[0].getObject(rt).getArray(rt));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getObject(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getObject(rt, args[0].getObject(rt));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValue(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getValue(
rt,
args[0].getNumber(),
args[1].getString(rt),
args[2].getObject(rt));
}
static jsi::Value
__hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValueWithCallback(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getValueWithCallback(
rt, std::move(args[0].getObject(rt).getFunction(rt)));
return jsi::Value::undefined();
}
static jsi::Value
__hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValueWithPromise(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getValueWithPromise(rt, args[0].getBool());
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getConstants(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getConstants(rt);
}
NativeSampleTurboCxxModuleSpecJSI::NativeSampleTurboCxxModuleSpecJSI(
std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule("SampleTurboCxxModule", jsInvoker) {
methodMap_["voidFunc"] = MethodMetadata{
0, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_voidFunc};
methodMap_["getBool"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getBool};
methodMap_["getEnum"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getEnum};
methodMap_["getNumber"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getNumber};
methodMap_["getString"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getString};
methodMap_["getArray"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getArray};
methodMap_["getObject"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getObject};
methodMap_["getValue"] = MethodMetadata{
3, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValue};
methodMap_["getValueWithCallback"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValueWithCallback};
methodMap_["getValueWithPromise"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValueWithPromise};
methodMap_["getConstants"] = MethodMetadata{
0, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getConstants};
}
} // namespace facebook::react

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <string>
#include <ReactCommon/TurboModule.h>
namespace facebook::react {
// TODO: This definition should be codegen'ed for type-safety purpose.
class JSI_EXPORT NativeSampleTurboCxxModuleSpecJSI : public TurboModule {
protected:
NativeSampleTurboCxxModuleSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
public:
virtual void voidFunc(jsi::Runtime& rt) = 0;
virtual bool getBool(jsi::Runtime& rt, bool arg) = 0;
virtual double getEnum(jsi::Runtime& rt, double arg) = 0;
virtual double getNumber(jsi::Runtime& rt, double arg) = 0;
virtual jsi::String getString(jsi::Runtime& rt, const jsi::String& arg) = 0;
virtual jsi::Array getArray(jsi::Runtime& rt, const jsi::Array& arg) = 0;
virtual jsi::Object getObject(jsi::Runtime& rt, const jsi::Object& arg) = 0;
virtual jsi::Object getValue(
jsi::Runtime& rt,
double x,
const jsi::String& y,
const jsi::Object& z) = 0;
virtual void getValueWithCallback(
jsi::Runtime& rt,
const jsi::Function& callback) = 0;
virtual jsi::Value getValueWithPromise(jsi::Runtime& rt, bool error) = 0;
virtual jsi::Object getConstants(jsi::Runtime& rt) = 0;
};
} // namespace facebook::react

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.
*/
#include "SampleTurboCxxModule.h"
#include <ReactCommon/TurboModuleUtils.h>
using namespace facebook;
namespace facebook::react {
SampleTurboCxxModule::SampleTurboCxxModule(
std::shared_ptr<CallInvoker> jsInvoker)
: NativeSampleTurboCxxModuleSpecJSI(jsInvoker) {}
void SampleTurboCxxModule::voidFunc(jsi::Runtime& rt) {
// Nothing to do
}
bool SampleTurboCxxModule::getBool(jsi::Runtime& rt, bool arg) {
return arg;
}
double SampleTurboCxxModule::getEnum(jsi::Runtime& rt, double arg) {
return arg;
}
double SampleTurboCxxModule::getNumber(jsi::Runtime& rt, double arg) {
return arg;
}
jsi::String SampleTurboCxxModule::getString(
jsi::Runtime& rt,
const jsi::String& arg) {
return jsi::String::createFromUtf8(rt, arg.utf8(rt));
}
jsi::Array SampleTurboCxxModule::getArray(
jsi::Runtime& rt,
const jsi::Array& arg) {
return deepCopyJSIArray(rt, arg);
}
jsi::Object SampleTurboCxxModule::getObject(
jsi::Runtime& rt,
const jsi::Object& arg) {
return deepCopyJSIObject(rt, arg);
}
jsi::Object SampleTurboCxxModule::getValue(
jsi::Runtime& rt,
double x,
const jsi::String& y,
const jsi::Object& z) {
// Note: return type isn't type-safe.
jsi::Object result(rt);
result.setProperty(rt, "x", jsi::Value(x));
result.setProperty(rt, "y", jsi::String::createFromUtf8(rt, y.utf8(rt)));
result.setProperty(rt, "z", deepCopyJSIObject(rt, z));
return result;
}
void SampleTurboCxxModule::getValueWithCallback(
jsi::Runtime& rt,
const jsi::Function& callback) {
callback.call(rt, jsi::String::createFromUtf8(rt, "value from callback!"));
}
jsi::Value SampleTurboCxxModule::getValueWithPromise(
jsi::Runtime& rt,
bool error) {
return createPromiseAsJSIValue(
rt, [error](jsi::Runtime& rt2, std::shared_ptr<Promise> promise) {
if (error) {
promise->reject("intentional promise rejection");
} else {
promise->resolve(jsi::String::createFromUtf8(rt2, "result!"));
}
});
}
jsi::Object SampleTurboCxxModule::getConstants(jsi::Runtime& rt) {
// Note: return type isn't type-safe.
jsi::Object result(rt);
result.setProperty(rt, "const1", jsi::Value(true));
result.setProperty(rt, "const2", jsi::Value(375));
result.setProperty(
rt, "const3", jsi::String::createFromUtf8(rt, "something"));
return result;
}
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include "NativeSampleTurboCxxModuleSpecJSI.h"
namespace facebook::react {
/**
* A sample implementation of the C++ spec. In practice, this class can just
* extend jsi::HostObject directly, but using the spec provides build-time
* type-safety.
*/
class SampleTurboCxxModule : public NativeSampleTurboCxxModuleSpecJSI {
public:
SampleTurboCxxModule(std::shared_ptr<CallInvoker> jsInvoker);
void voidFunc(jsi::Runtime& rt) override;
bool getBool(jsi::Runtime& rt, bool arg) override;
double getEnum(jsi::Runtime& rt, double arg) override;
double getNumber(jsi::Runtime& rt, double arg) override;
jsi::String getString(jsi::Runtime& rt, const jsi::String& arg) override;
jsi::Array getArray(jsi::Runtime& rt, const jsi::Array& arg) override;
jsi::Object getObject(jsi::Runtime& rt, const jsi::Object& arg) override;
jsi::Object getValue(
jsi::Runtime& rt,
double x,
const jsi::String& y,
const jsi::Object& z) override;
void getValueWithCallback(jsi::Runtime& rt, const jsi::Function& callback)
override;
jsi::Value getValueWithPromise(jsi::Runtime& rt, bool error) override;
jsi::Object getConstants(jsi::Runtime& rt) override;
};
} // 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)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DFOLLY_NO_CONFIG=1
-DLOG_TAG=\"ReactNative\")
file(GLOB sampleturbomodule_SRC CONFIGURE_DEPENDS ReactCommon/*.cpp)
add_library(sampleturbomodule STATIC ${sampleturbomodule_SRC})
target_include_directories(sampleturbomodule PUBLIC .)
target_link_libraries(sampleturbomodule
fbjni
jsi
react_nativemodule_core)

View File

@@ -0,0 +1,114 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// NOTE: This entire file should be codegen'ed.
package com.facebook.fbreact.specs;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaModule
implements TurboModule {
public NativeSampleTurboModuleSpec(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract double getNumber(double arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getValue(double x, String y, ReadableMap z);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getObject(ReadableMap arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getUnsafeObject(ReadableMap arg);
@ReactMethod
public abstract void voidFunc();
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableArray getArray(ReadableArray arg);
@ReactMethod
public abstract void getValueWithPromise(boolean error, Promise promise);
@ReactMethod
public abstract void getValueWithCallback(Callback callback);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract String getString(String arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract double getRootTag(double arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract boolean getBool(boolean arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract double getEnum(double arg);
@ReactMethod()
public abstract void voidFuncThrows();
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getObjectThrows(ReadableMap arg);
@ReactMethod()
public abstract void promiseThrows(Promise promise);
@ReactMethod()
public abstract void voidFuncAssert();
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getObjectAssert(ReadableMap arg);
@ReactMethod()
public abstract void promiseAssert(Promise promise);
protected abstract Map<String, Object> getTypedExportedConstants();
@Override
public final @Nullable Map<String, Object> getConstants() {
Map<String, Object> constants = getTypedExportedConstants();
if (ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD) {
Set<String> obligatoryFlowConstants =
new HashSet<>(Arrays.asList("const2", "const1", "const3"));
Set<String> optionalFlowConstants = new HashSet<>();
Set<String> undeclaredConstants = new HashSet<>(constants.keySet());
undeclaredConstants.removeAll(obligatoryFlowConstants);
undeclaredConstants.removeAll(optionalFlowConstants);
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(
String.format("Native Module Flow doesn't declare constants: %s", undeclaredConstants));
}
undeclaredConstants = obligatoryFlowConstants;
undeclaredConstants.removeAll(constants.keySet());
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(
String.format("Native Module doesn't fill in constants: %s", undeclaredConstants));
}
}
return constants;
}
}

View File

@@ -0,0 +1,363 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// NOTE: This entire file should be codegen'ed.
#include <ReactCommon/SampleTurboModuleSpec.h>
namespace facebook::react {
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, VoidKind, "voidFunc", "()V", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getBool(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, BooleanKind, "getBool", "(Z)Z", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getEnum(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, NumberKind, "getEnum", "(D)D", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getNumber(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, NumberKind, "getNumber", "(D)D", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getString(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
StringKind,
"getString",
"(Ljava/lang/String;)Ljava/lang/String;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getArray(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ArrayKind,
"getArray",
"(Lcom/facebook/react/bridge/ReadableArray;)Lcom/facebook/react/bridge/WritableArray;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getObject(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getObject",
"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, NumberKind, "getRootTag", "(D)D", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getValue(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getValue",
"(DLjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
VoidKind,
"getValueWithCallback",
"(Lcom/facebook/react/bridge/Callback;)V",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
PromiseKind,
"getValueWithPromise",
"(ZLcom/facebook/react/bridge/Promise;)V",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncThrows(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, VoidKind, "voidFuncThrows", "()V", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getObjectThrows(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getObjectThrows",
"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_promiseThrows(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
PromiseKind,
"promiseThrows",
"(Lcom/facebook/react/bridge/Promise;)V",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, VoidKind, "voidFuncAssert", "()V", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getObjectAssert",
"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
PromiseKind,
"promiseAssert",
"(Lcom/facebook/react/bridge/Promise;)V",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getConstants(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getConstants",
"()Ljava/util/Map;",
args,
count,
cachedMethodId);
}
NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(
const JavaTurboModule::InitParams& params)
: JavaTurboModule(params) {
methodMap_["voidFunc"] =
MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc};
methodMap_["getBool"] =
MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getBool};
methodMap_["getEnum"] =
MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getEnum};
methodMap_["getNumber"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getNumber};
methodMap_["getString"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getString};
methodMap_["getArray"] =
MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getArray};
methodMap_["getObject"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObject};
methodMap_["getRootTag"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag};
methodMap_["getValue"] =
MethodMetadata{3, __hostFunction_NativeSampleTurboModuleSpecJSI_getValue};
methodMap_["getValueWithCallback"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback};
methodMap_["getValueWithPromise"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise};
methodMap_["voidFuncThrows"] = MethodMetadata{
0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncThrows};
methodMap_["getObjectThrows"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectThrows};
methodMap_["promiseThrows"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseThrows};
methodMap_["voidFuncAssert"] = MethodMetadata{
0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert};
methodMap_["getObjectAssert"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert};
methodMap_["promiseAssert"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert};
methodMap_["getConstants"] = MethodMetadata{
0, __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants};
}
std::shared_ptr<TurboModule> SampleTurboModuleSpec_ModuleProvider(
const std::string& moduleName,
const JavaTurboModule::InitParams& params) {
if (moduleName == "SampleTurboModule") {
return std::make_shared<NativeSampleTurboModuleSpecJSI>(params);
}
return nullptr;
}
} // namespace facebook::react

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// NOTE: This entire file should be codegen'ed.
#pragma once
#include <ReactCommon/JavaTurboModule.h>
#include <ReactCommon/TurboModule.h>
#include <fbjni/fbjni.h>
namespace facebook::react {
/**
* C++ class for module 'SampleTurboModule'
*/
class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public JavaTurboModule {
public:
NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams& params);
};
std::shared_ptr<TurboModule> SampleTurboModuleSpec_ModuleProvider(
const std::string& moduleName,
const JavaTurboModule::InitParams& params);
} // namespace facebook::react

View File

@@ -0,0 +1,246 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.fbreact.specs;
import android.app.Activity;
import android.util.DisplayMetrics;
import android.widget.Toast;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
@ReactModule(name = SampleLegacyModule.NAME)
public class SampleLegacyModule extends ReactContextBaseJavaModule {
public static final String NAME = "SampleLegacyModule";
private static final String TAG = SampleLegacyModule.class.getName();
private final ReactApplicationContext mContext;
private Toast mToast;
public SampleLegacyModule(ReactApplicationContext context) {
super(context);
mContext = context;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public boolean getBool(boolean arg) {
log("getBool", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public double getEnum(double arg) {
log("getEnum", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public double getDouble(double arg) {
log("getDouble", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public int getInt(int arg) {
log("getInt", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public float getFloat(float arg) {
log("getFloat", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public double getObjectDouble(Double arg) {
log("getObjectDouble", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public Integer getObjectInteger(Integer arg) {
log("getObjectInteger", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public Float getObjectFloat(Float arg) {
log("getObjectFloat", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public String getString(String arg) {
log("getString", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public double getRootTag(double arg) {
log("getRootTag", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod
public void voidFunc() {
log("voidFunc", "<void>", "<void>");
return;
}
// This function returns {@link WritableMap} instead of {@link Map} for backward compat with
// existing native modules that use this Writable* as return types or in events. {@link
// WritableMap} is modified in the Java side, and read (or consumed) on the C++ side.
// In the future, all native modules should ideally return an immutable Map
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap getObject(ReadableMap arg) {
WritableNativeMap map = new WritableNativeMap();
map.merge(arg);
log("getObject", arg, map);
return map;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap getUnsafeObject(ReadableMap arg) {
WritableNativeMap map = new WritableNativeMap();
map.merge(arg);
log("getUnsafeObject", arg, map);
return map;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap getValue(double numberArg, String stringArg, ReadableMap mapArg) {
WritableMap map = new WritableNativeMap();
map.putDouble("x", numberArg);
map.putString("y", stringArg);
WritableMap zMap = new WritableNativeMap();
zMap.merge(mapArg);
map.putMap("z", zMap);
log(
"getValue",
MapBuilder.of("1-numberArg", numberArg, "2-stringArg", stringArg, "3-mapArg", mapArg),
map);
return map;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod
public void getValueWithCallback(final Callback callback) {
String result = "Value From Callback";
log("Callback", "Return Time", result);
callback.invoke(result);
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public WritableArray getArray(ReadableArray arg) {
if (arg == null || Arguments.toList(arg) == null) {
// Returning an empty array, since the super class always returns non-null
return new WritableNativeArray();
}
WritableArray result = Arguments.makeNativeArray(Arguments.toList(arg));
log("getArray", arg, result);
return result;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod
public void getValueWithPromise(boolean error, Promise promise) {
if (error) {
promise.reject(
"code 1",
"intentional promise rejection",
new Throwable("promise intentionally rejected"));
} else {
promise.resolve("result");
}
}
@Override
public final @Nullable Map<String, Object> getConstants() {
Map<String, Object> result = new HashMap<>();
DisplayMetrics displayMetrics = new DisplayMetrics();
Activity activity = mContext.getCurrentActivity();
if (activity != null) {
result.put("const2", 390);
}
result.put("const1", true);
result.put("const3", "something");
log("constantsToExport", "", result);
return result;
}
private void log(String method, Object input, Object output) {
if (mToast != null) {
mToast.cancel();
}
StringBuilder message = new StringBuilder("Method :");
message
.append(method)
.append("\nInputs: ")
.append(input.toString())
.append("\nOutputs: ")
.append(output.toString());
mToast = Toast.makeText(mContext, message.toString(), Toast.LENGTH_LONG);
mToast.show();
}
public void invalidate() {}
@Override
public String getName() {
return NAME;
}
}

View File

@@ -0,0 +1,248 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.fbreact.specs;
import android.app.Activity;
import android.util.DisplayMetrics;
import android.widget.Toast;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = SampleTurboModule.NAME)
public class SampleTurboModule extends NativeSampleTurboModuleSpec {
public static final String NAME = "SampleTurboModule";
private static final String TAG = SampleTurboModule.class.getName();
private final ReactApplicationContext mContext;
private Toast mToast;
public SampleTurboModule(ReactApplicationContext context) {
super(context);
mContext = context;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public boolean getBool(boolean arg) {
log("getBool", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public double getEnum(double arg) {
log("getEnum", arg, arg);
return arg;
}
@Override
protected Map<String, Object> getTypedExportedConstants() {
Map<String, Object> result = new HashMap<>();
DisplayMetrics displayMetrics = new DisplayMetrics();
Activity activity = mContext.getCurrentActivity();
if (activity != null) {
activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
result.put("const2", displayMetrics.widthPixels);
}
result.put("const1", true);
result.put("const3", "something");
log("constantsToExport", "", result);
return result;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public double getNumber(double arg) {
log("getNumber", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public String getString(String arg) {
log("getString", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public double getRootTag(double arg) {
log("getRootTag", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public void voidFunc() {
log("voidFunc", "<void>", "<void>");
return;
}
// This function returns {@link WritableMap} instead of {@link Map} for backward compat with
// existing native modules that use this Writable* as return types or in events. {@link
// WritableMap} is modified in the Java side, and read (or consumed) on the C++ side.
// In the future, all native modules should ideally return an immutable Map
@DoNotStrip
@Override
@SuppressWarnings("unused")
public WritableMap getObject(ReadableMap arg) {
WritableNativeMap map = new WritableNativeMap();
map.merge(arg);
log("getObject", arg, map);
return map;
}
@DoNotStrip
@Override
@SuppressWarnings("unused")
public WritableMap getUnsafeObject(ReadableMap arg) {
WritableNativeMap map = new WritableNativeMap();
map.merge(arg);
log("getUnsafeObject", arg, map);
return map;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public WritableMap getValue(double numberArg, String stringArg, ReadableMap mapArg) {
WritableMap map = new WritableNativeMap();
map.putDouble("x", numberArg);
map.putString("y", stringArg);
WritableMap zMap = new WritableNativeMap();
zMap.merge(mapArg);
map.putMap("z", zMap);
log(
"getValue",
MapBuilder.of("1-numberArg", numberArg, "2-stringArg", stringArg, "3-mapArg", mapArg),
map);
return map;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public void getValueWithCallback(final Callback callback) {
String result = "Value From Callback";
log("Callback", "Return Time", result);
callback.invoke(result);
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public WritableArray getArray(ReadableArray arg) {
if (arg == null || Arguments.toList(arg) == null) {
// Returning an empty array, since the super class always returns non-null
return new WritableNativeArray();
}
WritableArray result = Arguments.makeNativeArray(Arguments.toList(arg));
log("getArray", arg, result);
return result;
}
@Override
@DoNotStrip
@SuppressWarnings("unused")
public void getValueWithPromise(boolean error, Promise promise) {
if (error) {
promise.reject(
"code 1",
"intentional promise rejection",
new Throwable("promise intentionally rejected"));
} else {
promise.resolve("result");
}
}
@Override
@DoNotStrip
@SuppressWarnings("unused")
public void voidFuncThrows() {
throw new RuntimeException("Intentional exception from JVM voidFuncThrows");
};
@Override
@DoNotStrip
@SuppressWarnings("unused")
public WritableMap getObjectThrows(ReadableMap arg) {
throw new RuntimeException(
"Intentional exception from JVM getObjectThrows with " + arg.toString());
};
@Override
@DoNotStrip
@SuppressWarnings("unused")
public void promiseThrows(Promise promise) {
throw new RuntimeException("Intentional exception from JVM promiseThrows");
};
@Override
@DoNotStrip
@SuppressWarnings("unused")
public void voidFuncAssert() {
assert false : "Intentional assert from JVM voidFuncAssert";
};
@Override
@DoNotStrip
@SuppressWarnings("unused")
public WritableMap getObjectAssert(ReadableMap arg) {
assert false : "Intentional assert from JVM getObjectAssert with " + arg.toString();
return null;
};
@Override
@DoNotStrip
@SuppressWarnings("unused")
public void promiseAssert(Promise promise) {
assert false : "Intentional assert from JVM promiseAssert";
};
private void log(String method, Object input, Object output) {
if (mToast != null) {
mToast.cancel();
}
StringBuilder message = new StringBuilder("Method :");
message
.append(method)
.append("\nInputs: ")
.append(input.toString())
.append("\nOutputs: ")
.append(output.toString());
mToast = Toast.makeText(mContext, message.toString(), Toast.LENGTH_LONG);
mToast.show();
}
public void invalidate() {}
@Override
public String getName() {
return NAME;
}
}

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

View File

@@ -0,0 +1,229 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTNativeSampleTurboModuleSpec.h"
namespace facebook::react {
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "voidFunc", @selector(voidFunc), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getBool(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, BooleanKind, "getBool", @selector(getBool:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getEnum(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, NumberKind, "getEnum", @selector(getEnum:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getNumber(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, NumberKind, "getNumber", @selector(getNumber:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getString(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, StringKind, "getString", @selector(getString:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArray(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ArrayKind, "getArray", @selector(getArray:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getObject(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getObject", @selector(getObject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getUnsafeObject(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getUnsafeObject", @selector(getUnsafeObject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, NumberKind, "getRootTag", @selector(getRootTag:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValue(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getValue", @selector(getValue:y:z:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "getValueWithCallback", @selector(getValueWithCallback:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(
rt, PromiseKind, "getValueWithPromise", @selector(getValueWithPromise:resolve:reject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getConstants", @selector(getConstants), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncThrows(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "voidFuncThrows", @selector(voidFuncThrows), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectThrows(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getObjectThrows", @selector(getObjectThrows:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseThrows(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, PromiseKind, "promiseThrows", @selector(promiseThrows:resolve:reject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "voidFuncAssert", @selector(voidFuncAssert), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getObjectAssert", @selector(getObjectAssert:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, PromiseKind, "promiseAssert", @selector(promiseAssert:resolve:reject:), args, count);
}
NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams &params)
: ObjCTurboModule(params)
{
methodMap_["voidFunc"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc};
methodMap_["getBool"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getBool};
methodMap_["getEnum"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getEnum};
methodMap_["getNumber"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getNumber};
methodMap_["getString"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getString};
methodMap_["getArray"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getArray};
methodMap_["getObject"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObject};
methodMap_["getUnsafeObject"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getUnsafeObject};
methodMap_["getRootTag"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag};
methodMap_["getValue"] = MethodMetadata{3, __hostFunction_NativeSampleTurboModuleSpecJSI_getValue};
methodMap_["getValueWithCallback"] =
MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback};
methodMap_["getValueWithPromise"] =
MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise};
methodMap_["voidFuncThrows"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncThrows};
methodMap_["getObjectThrows"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectThrows};
methodMap_["promiseThrows"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseThrows};
methodMap_["voidFuncAssert"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert};
methodMap_["getObjectAssert"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert};
methodMap_["promiseAssert"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert};
methodMap_["getConstants"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants};
}
} // namespace facebook::react

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTAssert.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTInvalidating.h>
#import <React/RCTUtils.h>
@interface RCTSampleLegacyModule : NSObject <RCTBridgeModule, RCTInvalidating>
@end

View File

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

View File

@@ -0,0 +1,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
#import <React/RCTCxxModule.h>
#import <ReactCommon/RCTTurboModule.h>
/**
* Sample backward-compatible RCTCxxModule-based module.
* With jsi::HostObject, this class is no longer necessary, but the system supports it for
* backward compatibility.
*/
@interface RCTSampleTurboCxxModule_v1 : RCTCxxModule <RCTTurboModule>
@end
/**
* Second variant of a sample backward-compatible RCTCxxModule-based module.
*/
@interface RCTSampleTurboCxxModule_v2 : RCTCxxModule <RCTTurboModule>
@end

View File

@@ -0,0 +1,54 @@
/*
* 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 "RCTSampleTurboCxxModule.h"
#import <ReactCommon/SampleTurboCxxModule.h>
#import <cxxreact/CxxModule.h>
#import "SampleTurboCxxModuleLegacyImpl.h"
using namespace facebook;
// ObjC++ wrapper.
@implementation RCTSampleTurboCxxModule_v1
RCT_EXPORT_MODULE();
- (std::shared_ptr<react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<react::CallInvoker>)jsInvoker
{
return std::make_shared<react::SampleTurboCxxModule>(jsInvoker);
}
- (std::unique_ptr<xplat::module::CxxModule>)createModule
{
return nullptr;
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return nullptr;
}
@end
@implementation RCTSampleTurboCxxModule_v2
RCT_EXPORT_MODULE();
- (std::unique_ptr<xplat::module::CxxModule>)createModule
{
return std::make_unique<react::SampleTurboCxxModuleLegacyImpl>();
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return nullptr;
}
@end

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "RCTNativeSampleTurboModuleSpec.h"
/**
* Sample iOS-specific impl of a TurboModule, conforming to the spec protocol.
* This class is also 100% compatible with the NativeModule system.
*/
@interface RCTSampleTurboModule : NSObject <NativeSampleTurboModuleSpec>
@end

View File

@@ -0,0 +1,195 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTSampleTurboModule.h"
#import <React/RCTAssert.h>
#import <React/RCTUtils.h>
#import <UIKit/UIKit.h>
using namespace facebook::react;
@implementation RCTSampleTurboModule
// Backward-compatible export
RCT_EXPORT_MODULE()
// Backward-compatible queue configuration
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<NativeSampleTurboModuleSpecJSI>(params);
}
// Backward compatible invalidation
- (void)invalidate
{
// Actually do nothing here.
NSLog(@"Invalidating RCTSampleTurboModule...");
}
- (NSDictionary *)getConstants
{
__block NSDictionary *constants;
RCTUnsafeExecuteOnMainQueueSync(^{
UIScreen *mainScreen = UIScreen.mainScreen;
CGSize screenSize = mainScreen.bounds.size;
constants = @{
@"const1" : @YES,
@"const2" : @(screenSize.width),
@"const3" : @"something",
};
});
return constants;
}
// TODO: Remove once fully migrated to TurboModule.
- (NSDictionary *)constantsToExport
{
return [self getConstants];
}
RCT_EXPORT_METHOD(voidFunc)
{
// Nothing to do
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getBool : (BOOL)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getEnum : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getNumber : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getString : (NSString *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSArray<id<NSObject>> *, getArray : (NSArray *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObject : (NSDictionary *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getUnsafeObject : (NSDictionary *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getRootTag : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getValue : (double)x y : (NSString *)y z : (NSDictionary *)z)
{
return @{
@"x" : @(x),
@"y" : y ?: [NSNull null],
@"z" : z ?: [NSNull null],
};
}
RCT_EXPORT_METHOD(getValueWithCallback : (RCTResponseSenderBlock)callback)
{
if (!callback) {
return;
}
callback(@[ @"value from callback!" ]);
}
RCT_EXPORT_METHOD(getValueWithPromise
: (BOOL)error resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
{
if (!resolve || !reject) {
return;
}
if (error) {
reject(
@"code_1",
@"intentional promise rejection",
[NSError errorWithDomain:@"RCTSampleTurboModule" code:1 userInfo:nil]);
} else {
resolve(@"result!");
}
}
RCT_EXPORT_METHOD(voidFuncThrows)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC voidFuncThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectThrows : (NSDictionary *)arg)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC getObjectThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_METHOD(promiseThrows
: (BOOL)error resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC promiseThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_METHOD(voidFuncAssert)
{
RCTAssert(false, @"Intentional assert from ObjC voidFuncAssert");
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectAssert : (NSDictionary *)arg)
{
RCTAssert(false, @"Intentional assert from ObjC getObjectAssert");
return arg;
}
RCT_EXPORT_METHOD(promiseAssert
: (BOOL)error resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
{
RCTAssert(false, @"Intentional assert from ObjC promiseAssert");
}
@end

View File

@@ -0,0 +1,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.
*/
#include "SampleTurboCxxModuleLegacyImpl.h"
#include <cxxreact/JsArgumentHelpers.h>
using namespace facebook::xplat::module;
namespace facebook::react {
SampleTurboCxxModuleLegacyImpl::SampleTurboCxxModuleLegacyImpl() {}
std::string SampleTurboCxxModuleLegacyImpl::getName() {
return "SampleTurboCxxModule_v2";
}
std::map<std::string, folly::dynamic>
SampleTurboCxxModuleLegacyImpl::getConstants() {
return {
{"const1", true},
{"const2", 375},
{"const3", "something"},
};
};
std::vector<CxxModule::Method> SampleTurboCxxModuleLegacyImpl::getMethods() {
return {
CxxModule::Method(
"voidFunc", [this](folly::dynamic args) { voidFunc(); }),
CxxModule::Method(
"getBool",
[this](folly::dynamic args) {
return getBool(xplat::jsArgAsBool(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getEnum",
[this](folly::dynamic args) {
return getEnum(xplat::jsArgAsDouble(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getNumber",
[this](folly::dynamic args) {
return getNumber(xplat::jsArgAsDouble(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getString",
[this](folly::dynamic args) {
return getString(xplat::jsArgAsString(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getString",
[this](folly::dynamic args) {
return getString(xplat::jsArgAsString(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getArray",
[this](folly::dynamic args) {
return getArray(xplat::jsArgAsArray(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getObject",
[this](folly::dynamic args) {
return getObject(xplat::jsArgAsObject(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getUnsafeObject",
[this](folly::dynamic args) {
return getUnsafeObject(xplat::jsArgAsObject(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getRootTag",
[this](folly::dynamic args) {
return getNumber(xplat::jsArgAsDouble(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getValue",
[this](folly::dynamic args) {
return getValue(
xplat::jsArgAsDouble(args, 0),
xplat::jsArgAsString(args, 1),
xplat::jsArgAsObject(args, 2));
},
CxxModule::SyncTag),
CxxModule::Method(
"getValueWithCallback",
[this](folly::dynamic args, CxxModule::Callback callback) {
getValueWithCallback(callback);
}),
CxxModule::Method(
"getValueWithPromise",
[this](
folly::dynamic args,
CxxModule::Callback resolve,
CxxModule::Callback reject) {
getValueWithPromise(xplat::jsArgAsBool(args, 0), resolve, reject);
}),
};
};
void SampleTurboCxxModuleLegacyImpl::voidFunc() {
// Do nothing.
}
bool SampleTurboCxxModuleLegacyImpl::getBool(bool arg) {
return arg;
}
double SampleTurboCxxModuleLegacyImpl::getEnum(double arg) {
return arg;
}
double SampleTurboCxxModuleLegacyImpl::getNumber(double arg) {
return arg;
}
std::string SampleTurboCxxModuleLegacyImpl::getString(const std::string& arg) {
return arg;
}
folly::dynamic SampleTurboCxxModuleLegacyImpl::getArray(
const folly::dynamic& arg) {
return arg;
}
folly::dynamic SampleTurboCxxModuleLegacyImpl::getObject(
const folly::dynamic& arg) {
return arg;
}
folly::dynamic SampleTurboCxxModuleLegacyImpl::getUnsafeObject(
const folly::dynamic& arg) {
return arg;
}
double SampleTurboCxxModuleLegacyImpl::getRootTag(double arg) {
return arg;
}
folly::dynamic SampleTurboCxxModuleLegacyImpl::getValue(
double x,
const std::string& y,
const folly::dynamic& z) {
return folly::dynamic::object("x", x)("y", y)("z", z);
}
void SampleTurboCxxModuleLegacyImpl::getValueWithCallback(
const CxxModule::Callback& callback) {
callback({"value from callback!"});
}
void SampleTurboCxxModuleLegacyImpl::getValueWithPromise(
bool error,
const CxxModule::Callback& resolve,
const CxxModule::Callback& reject) {
if (!error) {
resolve({"result!"});
} else {
reject(
{folly::dynamic::object("message", "intentional promise rejection")});
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,46 @@
/*
* 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 <cxxreact/CxxModule.h>
namespace facebook::react {
/**
* A sample CxxModule (legacy system) implementation.
*/
class SampleTurboCxxModuleLegacyImpl
: public facebook::xplat::module::CxxModule {
public:
SampleTurboCxxModuleLegacyImpl();
std::string getName() override;
std::map<std::string, folly::dynamic> getConstants() override;
std::vector<facebook::xplat::module::CxxModule::Method> getMethods() override;
// API
void voidFunc();
bool getBool(bool arg);
double getEnum(double arg);
double getNumber(double arg);
std::string getString(const std::string& arg);
folly::dynamic getArray(const folly::dynamic& arg);
folly::dynamic getObject(const folly::dynamic& arg);
folly::dynamic getUnsafeObject(const folly::dynamic& arg);
double getRootTag(double arg);
folly::dynamic
getValue(double x, const std::string& y, const folly::dynamic& z);
void getValueWithCallback(
const facebook::xplat::module::CxxModule::Callback& callback);
void getValueWithPromise(
bool error,
const facebook::xplat::module::CxxModule::Callback& resolve,
const facebook::xplat::module::CxxModule::Callback& reject);
};
} // namespace facebook::react

View File

@@ -0,0 +1,195 @@
---
InheritParentConfig: true
Checks: '>
clang-diagnostic-*,
modernize-avoid-bind,
modernize-avoid-c-arrays,
modernize-concat-nested-namespaces,
modernize-deprecated-headers,
modernize-deprecated-ios-base-aliases,
modernize-loop-convert,
modernize-make-shared,
modernize-redundant-void-arg,
modernize-return-braced-init-list,
modernize-use-auto,
modernize-make-unique,
modernize-pass-by-value,
modernize-raw-string-literal,
modernize-replace-auto-ptr,
modernize-use-bool-literals,
modernize-replace-random-shuffle,
modernize-unary-static-assert,
modernize-use-emplace,
modernize-shrink-to-fit,
modernize-use-equals-default,
modernize-use-default-member-init,
modernize-use-nullptr,
modernize-use-noexcept,
modernize-use-equals-delete,
modernize-use-override,
modernize-use-using,
modernize-use-transparent-functors,
performance-faster-string-find,
performance-for-range-copy,
performance-implicit-conversion-in-loop,
performance-inefficient-algorithm,
performance-inefficient-string-concatenation,
performance-inefficient-vector-operation,
performance-move-const-arg,
performance-move-constructor-init,
performance-noexcept-move-constructor,
performance-type-promotion-in-math-fn,
performance-unnecessary-copy-initialization,
performance-unnecessary-value-param,
cppcoreguidelines-macro-usage,
cppcoreguidelines-narrowing-conversions,
cppcoreguidelines-no-malloc,
cppcoreguidelines-pro-bounds-pointer-arithmetic,
cppcoreguidelines-pro-type-const-cast,
cppcoreguidelines-pro-type-cstyle-cast,
cppcoreguidelines-pro-type-member-init,
cppcoreguidelines-pro-type-reinterpret-cast,
cppcoreguidelines-pro-type-union-access,
cppcoreguidelines-pro-type-vararg,
cppcoreguidelines-slicing,
cppcoreguidelines-special-member-functions,
readability-avoid-const-params-in-decls,
readability-braces-around-statements,
readability-const-return-type,
readability-container-size-empty,
readability-deleted-default,
readability-delete-null-pointer,
readability-implicit-bool-conversion,
readability-inconsistent-declaration-parameter-name,
readability-isolate-declaration,
readability-misplaced-array-index,
readability-named-parameter,
readability-non-const-parameter,
readability-redundant-control-flow,
readability-redundant-declaration,
readability-redundant-function-ptr-dereference,
readability-redundant-preprocessor,
readability-redundant-smartptr-get,
readability-redundant-string-cstr,
readability-redundant-string-init,
readability-simplify-boolean-expr,
readability-simplify-subscript-expr,
readability-static-accessed-through-instance,
readability-static-definition-in-anonymous-namespace,
readability-string-compare,
readability-uniqueptr-delete-release,
misc-definitions-in-headers,
misc-new-delete-overloads,
misc-non-copyable-objects,
misc-static-assert,
misc-throw-by-value-catch-by-reference,
misc-unconventional-assign-operator,
misc-uniqueptr-reset-release,
misc-unused-alias-decls,
misc-unused-parameters,
misc-unused-using-decls,
bugprone-argument-comment,
bugprone-assert-side-effect,
bugprone-bool-pointer-implicit-conversion,
bugprone-copy-constructor-init,
bugprone-dangling-handle,
bugprone-exception-escape,
bugprone-fold-init-type,
bugprone-forward-declaration-namespace,
bugprone-forwarding-reference-overload,
bugprone-inaccurate-erase,
bugprone-incorrect-roundings,
bugprone-integer-division,
bugprone-macro-parentheses,
bugprone-macro-repeated-side-effects,
bugprone-misplaced-operator-in-strlen-in-alloc,
bugprone-misplaced-widening-cast,
bugprone-move-forwarding-reference,
bugprone-multiple-statement-macro,
bugprone-parent-virtual-call,
bugprone-sizeof-container,
bugprone-sizeof-expression,
bugprone-string-constructor,
bugprone-string-integer-assignment,
bugprone-string-literal-with-embedded-nul,
bugprone-suspicious-enum-usage,
bugprone-suspicious-memset-usage,
bugprone-suspicious-missing-comma,
bugprone-suspicious-semicolon,
bugprone-suspicious-string-compare,
bugprone-swapped-arguments,
bugprone-terminating-continue,
bugprone-throw-keyword-missing,
bugprone-too-small-loop-variable,
bugprone-undefined-memory-manipulation,
bugprone-undelegated-constructor,
bugprone-unused-return-value,
bugprone-use-after-move,
bugprone-virtual-near-miss,
clang-analyzer-apiModeling.StdCLibraryFunctions,
clang-analyzer-apiModeling.TrustNonnull,
clang-analyzer-apiModeling.google.GTest,
clang-analyzer-core.CallAndMessage,
clang-analyzer-core.DivideZero,
clang-analyzer-core.DynamicTypePropagation,
clang-analyzer-core.NonNullParamChecker,
clang-analyzer-core.NonnilStringConstants,
clang-analyzer-core.NullDereference,
clang-analyzer-core.StackAddressEscape,
clang-analyzer-core.UndefinedBinaryOperatorResult,
clang-analyzer-core.VLASize,
clang-analyzer-core.builtin.BuiltinFunctions,
clang-analyzer-core.builtin.NoReturnFunctions,
clang-analyzer-core.uninitialized.ArraySubscript,
clang-analyzer-core.uninitialized.Assign,
clang-analyzer-core.uninitialized.Branch,
clang-analyzer-core.uninitialized.CapturedBlockVariable,
clang-analyzer-core.uninitialized.UndefReturn,
clang-analyzer-cplusplus.InnerPointer,
clang-analyzer-cplusplus.Move,
clang-analyzer-cplusplus.NewDelete,
clang-analyzer-cplusplus.NewDeleteLeaks,
clang-analyzer-cplusplus.SelfAssignment,
clang-analyzer-deadcode.DeadStores,
clang-analyzer-optin.cplusplus.VirtualCall,
clang-analyzer-optin.mpi.MPI-Checker,
clang-analyzer-optin.performance.GCDAntipattern,
clang-analyzer-optin.performance.Padding,
clang-analyzer-optin.portability.UnixAPI,
clang-analyzer-nullability.NullPassedToNonnull,
clang-analyzer-nullability.NullReturnedFromNonnull,
clang-analyzer-nullability.NullableDereferenced,
clang-analyzer-nullability.NullablePassedToNonnull,
clang-analyzer-nullability.NullableReturnedFromNonnull,
clang-analyzer-security.FloatLoopCounter,
clang-analyzer-security.insecureAPI.UncheckedReturn,
clang-analyzer-security.insecureAPI.bcmp,
clang-analyzer-security.insecureAPI.bcopy,
clang-analyzer-security.insecureAPI.bzero,
clang-analyzer-security.insecureAPI.getpw,
clang-analyzer-security.insecureAPI.gets,
clang-analyzer-security.insecureAPI.mkstemp,
clang-analyzer-security.insecureAPI.mktemp,
clang-analyzer-security.insecureAPI.rand,
clang-analyzer-security.insecureAPI.strcpy,
clang-analyzer-security.insecureAPI.vfork,
clang-analyzer-unix.API,
clang-analyzer-unix.Malloc,
clang-analyzer-unix.MallocSizeof,
clang-analyzer-unix.MismatchedDeallocator,
clang-analyzer-unix.Vfork,
clang-analyzer-unix.cstring.BadSizeArg,
clang-analyzer-unix.cstring.NullArg,
clang-analyzer-valist.CopyToSelf,
clang-analyzer-valist.Uninitialized,
clang-analyzer-valist.Unterminated,
google-build-using-namespace,
'
CheckOptions:
- key: performance-unnecessary-value-param.AllowedTypes
value: '[Pp]ointer$;[Pp]tr$;[Rr]ef(erence)?$;'
- key: performance-unnecessary-copy-initialization.AllowedTypes
value: '[Pp]ointer$;[Pp]tr$;[Rr]ef(erence)?$'
...

View File

@@ -0,0 +1,38 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB react_render_animations_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_render_animations STATIC ${react_render_animations_SRC})
target_include_directories(react_render_animations PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_render_animations
folly_runtime
glog
glog_init
jsi
react_config
react_debug
react_render_componentregistry
react_render_core
react_render_debug
react_render_graphics
react_render_mounting
react_render_uimanager
rrc_view
runtimeexecutor
yoga
)

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <jsi/jsi.h>
#include <memory>
namespace facebook::react {
class LayoutAnimationCallbackWrapper {
public:
LayoutAnimationCallbackWrapper(jsi::Function&& callback)
: callback_(std::make_shared<jsi::Function>(std::move(callback))) {}
LayoutAnimationCallbackWrapper() : callback_(nullptr) {}
void call(jsi::Runtime& runtime) const {
if (callback_) {
callback_->call(runtime);
callback_.reset();
}
}
private:
mutable std::shared_ptr<jsi::Function> callback_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,109 @@
/*
* 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 "LayoutAnimationDriver.h"
#include <glog/logging.h>
#include <react/debug/react_native_assert.h>
#include <react/renderer/animations/utils.h>
#include <algorithm>
namespace facebook::react {
void LayoutAnimationDriver::animationMutationsForFrame(
SurfaceId surfaceId,
ShadowViewMutation::List& mutationsList,
uint64_t now) const {
for (auto& animation : inflightAnimations_) {
if (animation.surfaceId != surfaceId) {
continue;
}
if (animation.completed) {
continue;
}
int incompleteAnimations = 0;
for (auto& keyframe : animation.keyFrames) {
if (keyframe.invalidated) {
continue;
}
const auto& baselineShadowView = keyframe.viewStart;
const auto& finalShadowView = keyframe.viewEnd;
// The contract with the "keyframes generation" phase is that any animated
// node will have a valid configuration.
const auto layoutAnimationConfig = animation.layoutAnimationConfig;
const auto& mutationConfig =
(keyframe.type == AnimationConfigurationType::Delete
? layoutAnimationConfig.deleteConfig
: (keyframe.type == AnimationConfigurationType::Create
? layoutAnimationConfig.createConfig
: layoutAnimationConfig.updateConfig));
// Interpolate
auto progress =
calculateAnimationProgress(now, animation, mutationConfig);
auto animationTimeProgressLinear = progress.first;
auto animationInterpolationFactor = progress.second;
auto mutatedShadowView = createInterpolatedShadowView(
animationInterpolationFactor, baselineShadowView, finalShadowView);
// Create the mutation instruction
mutationsList.emplace_back(ShadowViewMutation::UpdateMutation(
keyframe.viewPrev, mutatedShadowView, keyframe.parentView));
PrintMutationInstruction("Animation Progress:", mutationsList.back());
keyframe.viewPrev = std::move(mutatedShadowView);
if (animationTimeProgressLinear < 1) {
incompleteAnimations++;
}
}
// Are there no ongoing mutations left in this animation?
if (incompleteAnimations == 0) {
animation.completed = true;
}
}
// Clear out finished animations
for (auto it = inflightAnimations_.begin();
it != inflightAnimations_.end();) {
const auto& animation = *it;
if (animation.completed) {
callCallback(animation.successCallback);
// Queue up "final" mutations for all keyframes in the completed animation
for (const auto& keyframe : animation.keyFrames) {
if (keyframe.invalidated) {
continue;
}
queueFinalMutationsForCompletedKeyFrame(
keyframe,
mutationsList,
false,
"LayoutAnimationDriver: Animation Completed");
}
it = inflightAnimations_.erase(it);
} else {
it++;
}
}
// Final step: make sure that all operations execute in the proper order.
// REMOVE operations with highest indices must operate first.
std::stable_sort(
mutationsList.begin(),
mutationsList.end(),
&shouldFirstComeBeforeSecondMutation);
}
} // namespace facebook::react

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/animations/LayoutAnimationKeyFrameManager.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
namespace facebook::react {
class LayoutAnimationDriver : public LayoutAnimationKeyFrameManager {
public:
LayoutAnimationDriver(
RuntimeExecutor runtimeExecutor,
ContextContainer::Shared& contextContainer,
LayoutAnimationStatusDelegate* delegate)
: LayoutAnimationKeyFrameManager(
runtimeExecutor,
contextContainer,
delegate) {}
protected:
virtual void animationMutationsForFrame(
SurfaceId surfaceId,
ShadowViewMutation::List& mutationsList,
uint64_t now) const override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,185 @@
/*
* 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/RuntimeExecutor.h>
#include <react/renderer/animations/LayoutAnimationCallbackWrapper.h>
#include <react/renderer/animations/primitives.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/debug/flags.h>
#include <react/renderer/mounting/MountingOverrideDelegate.h>
#include <react/renderer/mounting/MountingTransaction.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <react/renderer/uimanager/LayoutAnimationStatusDelegate.h>
#include <react/renderer/uimanager/UIManagerAnimationDelegate.h>
#include <optional>
#include <unordered_set>
namespace facebook::react {
#ifdef LAYOUT_ANIMATION_VERBOSE_LOGGING
void PrintMutationInstruction(
std::string message,
const ShadowViewMutation& mutation);
void PrintMutationInstructionRelative(
std::string message,
const ShadowViewMutation& mutation,
const ShadowViewMutation& relativeMutation);
#else
#define PrintMutationInstruction(a, b)
#define PrintMutationInstructionRelative(a, b, c)
#endif
class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate,
public MountingOverrideDelegate {
public:
LayoutAnimationKeyFrameManager(
RuntimeExecutor runtimeExecutor,
ContextContainer::Shared& contextContainer,
LayoutAnimationStatusDelegate* delegate);
#pragma mark - UIManagerAnimationDelegate methods
void uiManagerDidConfigureNextLayoutAnimation(
jsi::Runtime& runtime,
const RawValue& config,
const jsi::Value& successCallbackValue,
const jsi::Value& failureCallbackValue) const override;
void setComponentDescriptorRegistry(const SharedComponentDescriptorRegistry&
componentDescriptorRegistry) override;
// TODO: add SurfaceId to this API as well
bool shouldAnimateFrame() const override;
void stopSurface(SurfaceId surfaceId) override;
#pragma mark - MountingOverrideDelegate methods
bool shouldOverridePullTransaction() const override;
// This is used to "hijack" the diffing process to figure out which mutations
// should be animated. The mutations returned by this function will be
// executed immediately.
std::optional<MountingTransaction> pullTransaction(
SurfaceId surfaceId,
MountingTransaction::Number number,
const TransactionTelemetry& telemetry,
ShadowViewMutationList mutations) const override;
// Exposed for testing.
void uiManagerDidConfigureNextLayoutAnimation(
LayoutAnimation layoutAnimation) const;
// LayoutAnimationStatusDelegate - this is for the platform to get
// signal when animations start and complete. Setting and resetting this
// delegate is protected by a mutex; ALL method calls into this delegate are
// also protected by the mutex! The only way to set this without a mutex is
// via a constructor.
void setLayoutAnimationStatusDelegate(
LayoutAnimationStatusDelegate* delegate) const;
void setClockNow(std::function<uint64_t()> now);
protected:
SharedComponentDescriptorRegistry componentDescriptorRegistry_;
mutable std::optional<LayoutAnimation> currentAnimation_{};
mutable std::mutex currentAnimationMutex_;
/**
* All mutations of inflightAnimations_ are thread-safe as long as
* we keep the contract of: only mutate it within the context of
* `pullTransaction`. If that contract is held, this is implicitly protected
* by the MountingCoordinator's mutex.
*/
mutable std::vector<LayoutAnimation> inflightAnimations_{};
bool hasComponentDescriptorForShadowView(const ShadowView& shadowView) const;
const ComponentDescriptor& getComponentDescriptorForShadowView(
const ShadowView& shadowView) const;
/**
* Given a `progress` between 0 and 1, a mutation and LayoutAnimation config,
* return a ShadowView with mutated props and/or LayoutMetrics.
*
* @param progress
* @param layoutAnimation
* @param animatedMutation
* @return
*/
ShadowView createInterpolatedShadowView(
Float progress,
const ShadowView& startingView,
const ShadowView& finalView) const;
void callCallback(const LayoutAnimationCallbackWrapper& callback) const;
virtual void animationMutationsForFrame(
SurfaceId surfaceId,
ShadowViewMutation::List& mutationsList,
uint64_t now) const = 0;
/**
* Queue (and potentially synthesize) final mutations for a finished keyframe.
* Keyframe animation may have timed-out, or be canceled due to a conflict.
*/
void queueFinalMutationsForCompletedKeyFrame(
const AnimationKeyFrame& keyframe,
ShadowViewMutation::List& mutationsList,
bool interrupted,
const std::string& logPrefix) const;
private:
RuntimeExecutor runtimeExecutor_;
ContextContainer::Shared contextContainer_;
mutable std::mutex layoutAnimationStatusDelegateMutex_;
mutable LayoutAnimationStatusDelegate* layoutAnimationStatusDelegate_{};
mutable std::mutex surfaceIdsToStopMutex_;
mutable std::unordered_set<SurfaceId> surfaceIdsToStop_{};
// Function that returns current time in milliseconds
std::function<uint64_t()> now_;
void adjustImmediateMutationIndicesForDelayedMutations(
SurfaceId surfaceId,
ShadowViewMutation& mutation,
bool skipLastAnimation = false,
bool lastAnimationOnly = false) const;
void adjustDelayedMutationIndicesForMutation(
SurfaceId surfaceId,
const ShadowViewMutation& mutation,
bool skipLastAnimation = false) const;
void getAndEraseConflictingAnimations(
SurfaceId surfaceId,
const ShadowViewMutationList& mutations,
std::vector<AnimationKeyFrame>& conflictingAnimations) const;
/*
* Removes animations from `inflightAnimations_` for stopped surfaces.
*/
void deleteAnimationsForStoppedSurfaces() const;
void simulateImagePropsMemoryAccess(
const ShadowViewMutationList& mutations) const;
/**
* Interpolates the props values.
*/
Props::Shared interpolateProps(
const ComponentDescriptor& componentDescriptor,
const PropsParserContext& context,
Float animationProgress,
const Props::Shared& props,
const Props::Shared& newProps) const;
};
} // 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.
*/
#include <glog/logging.h>
#include <react/renderer/animations/primitives.h>
#include <optional>
namespace facebook::react {
static inline std::optional<AnimationType> parseAnimationType(
std::string param) {
if (param == "spring") {
return AnimationType::Spring;
}
if (param == "linear") {
return AnimationType::Linear;
}
if (param == "easeInEaseOut") {
return AnimationType::EaseInEaseOut;
}
if (param == "easeIn") {
return AnimationType::EaseIn;
}
if (param == "easeOut") {
return AnimationType::EaseOut;
}
if (param == "keyboard") {
return AnimationType::Keyboard;
}
LOG(ERROR) << "Error parsing animation type: " << param;
return {};
}
static inline std::optional<AnimationProperty> parseAnimationProperty(
std::string param) {
if (param == "opacity") {
return AnimationProperty::Opacity;
}
if (param == "scaleX") {
return AnimationProperty::ScaleX;
}
if (param == "scaleY") {
return AnimationProperty::ScaleY;
}
if (param == "scaleXY") {
return AnimationProperty::ScaleXY;
}
LOG(ERROR) << "Error parsing animation property: " << param;
return {};
}
static inline std::optional<AnimationConfig> parseAnimationConfig(
const folly::dynamic& config,
double defaultDuration,
bool parsePropertyType) {
if (config.empty() || !config.isObject()) {
return AnimationConfig{
AnimationType::Linear,
AnimationProperty::NotApplicable,
defaultDuration,
0,
0,
0};
}
const auto typeIt = config.find("type");
if (typeIt == config.items().end()) {
LOG(ERROR) << "Error parsing animation config: could not find field `type`";
return {};
}
const auto animationTypeParam = typeIt->second;
if (animationTypeParam.empty() || !animationTypeParam.isString()) {
LOG(ERROR)
<< "Error parsing animation config: could not unwrap field `type`";
return {};
}
const auto animationType = parseAnimationType(animationTypeParam.asString());
if (!animationType) {
LOG(ERROR)
<< "Error parsing animation config: could not parse field `type`";
return {};
}
AnimationProperty animationProperty = AnimationProperty::NotApplicable;
if (parsePropertyType) {
const auto propertyIt = config.find("property");
if (propertyIt == config.items().end()) {
LOG(ERROR)
<< "Error parsing animation config: could not find field `property`";
return {};
}
const auto animationPropertyParam = propertyIt->second;
if (animationPropertyParam.empty() || !animationPropertyParam.isString()) {
LOG(ERROR)
<< "Error parsing animation config: could not unwrap field `property`";
return {};
}
const auto animationPropertyParsed =
parseAnimationProperty(animationPropertyParam.asString());
if (!animationPropertyParsed) {
LOG(ERROR)
<< "Error parsing animation config: could not parse field `property`";
return {};
}
animationProperty = *animationPropertyParsed;
}
double duration = defaultDuration;
double delay = 0;
Float springDamping = 0.5;
Float initialVelocity = 0;
const auto durationIt = config.find("duration");
if (durationIt != config.items().end()) {
if (durationIt->second.isDouble()) {
duration = durationIt->second.asDouble();
} else {
LOG(ERROR)
<< "Error parsing animation config: field `duration` must be a number";
return {};
}
}
const auto delayIt = config.find("delay");
if (delayIt != config.items().end()) {
if (delayIt->second.isDouble()) {
delay = delayIt->second.asDouble();
} else {
LOG(ERROR)
<< "Error parsing animation config: field `delay` must be a number";
return {};
}
}
const auto springDampingIt = config.find("springDamping");
if (springDampingIt != config.items().end() &&
springDampingIt->second.isDouble()) {
if (springDampingIt->second.isDouble()) {
springDamping = (Float)springDampingIt->second.asDouble();
} else {
LOG(ERROR)
<< "Error parsing animation config: field `springDamping` must be a number";
return {};
}
}
const auto initialVelocityIt = config.find("initialVelocity");
if (initialVelocityIt != config.items().end()) {
if (initialVelocityIt->second.isDouble()) {
initialVelocity = (Float)initialVelocityIt->second.asDouble();
} else {
LOG(ERROR)
<< "Error parsing animation config: field `initialVelocity` must be a number";
return {};
}
}
return std::optional<AnimationConfig>(AnimationConfig{
*animationType,
animationProperty,
duration,
delay,
springDamping,
initialVelocity});
}
// Parse animation config from JS
static inline std::optional<LayoutAnimationConfig> parseLayoutAnimationConfig(
const folly::dynamic& config) {
if (config.empty() || !config.isObject()) {
return {};
}
const auto durationIt = config.find("duration");
if (durationIt == config.items().end() || !durationIt->second.isDouble()) {
return {};
}
const double duration = durationIt->second.asDouble();
const auto createConfigIt = config.find("create");
const auto createConfig = createConfigIt == config.items().end()
? std::optional<AnimationConfig>(AnimationConfig{})
: parseAnimationConfig(createConfigIt->second, duration, true);
const auto updateConfigIt = config.find("update");
const auto updateConfig = updateConfigIt == config.items().end()
? std::optional<AnimationConfig>(AnimationConfig{})
: parseAnimationConfig(updateConfigIt->second, duration, false);
const auto deleteConfigIt = config.find("delete");
const auto deleteConfig = deleteConfigIt == config.items().end()
? std::optional<AnimationConfig>(AnimationConfig{})
: parseAnimationConfig(deleteConfigIt->second, duration, true);
if (!createConfig || !updateConfig || !deleteConfig) {
return {};
}
return LayoutAnimationConfig{
duration, *createConfig, *updateConfig, *deleteConfig};
}
} // namespace facebook::react

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