first commit

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

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/bridging/Base.h>
#include <string>
#include <string_view>
namespace facebook::react {
template <>
struct Bridging<std::string> {
static std::string fromJs(jsi::Runtime &rt, const jsi::String &value)
{
return value.utf8(rt);
}
static jsi::String toJs(jsi::Runtime &rt, const std::string &value)
{
return jsi::String::createFromUtf8(rt, value);
}
};
template <>
struct Bridging<std::string_view> {
static jsi::String toJs(jsi::Runtime &rt, std::string_view value)
{
return jsi::String::createFromUtf8(rt, reinterpret_cast<const uint8_t *>(value.data()), value.length());
}
};
template <>
struct Bridging<const char *> : Bridging<std::string_view> {};
template <size_t N>
struct Bridging<char[N]> : Bridging<std::string_view> {};
} // namespace facebook::react

View File

@@ -0,0 +1,134 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/bridging/Base.h>
#include <array>
#include <deque>
#include <initializer_list>
#include <list>
#include <set>
#include <tuple>
#include <utility>
#include <vector>
namespace facebook::react {
namespace array_detail {
template <typename T, size_t N>
struct BridgingStatic {
static jsi::Array toJs(jsi::Runtime &rt, const T &array, const std::shared_ptr<CallInvoker> &jsInvoker)
{
return toJs(rt, array, jsInvoker, std::make_index_sequence<N>{});
}
private:
template <size_t... Index>
static jsi::Array toJs(
facebook::jsi::Runtime &rt,
const T &array,
const std::shared_ptr<CallInvoker> &jsInvoker,
std::index_sequence<Index...> /*unused*/)
{
return jsi::Array::createWithElements(rt, bridging::toJs(rt, std::get<Index>(array), jsInvoker)...);
}
};
template <typename T>
struct BridgingDynamic {
static jsi::Array toJs(jsi::Runtime &rt, const T &list, const std::shared_ptr<CallInvoker> &jsInvoker)
{
jsi::Array result(rt, list.size());
size_t index = 0;
for (const auto &item : list) {
result.setValueAtIndex(rt, index++, bridging::toJs(rt, item, jsInvoker));
}
return result;
}
};
} // namespace array_detail
template <typename T, size_t N>
struct Bridging<std::array<T, N>> : array_detail::BridgingStatic<std::array<T, N>, N> {
static std::array<T, N>
fromJs(facebook::jsi::Runtime &rt, const jsi::Array &array, const std::shared_ptr<CallInvoker> &jsInvoker)
{
size_t length = array.length(rt);
std::array<T, N> result;
for (size_t i = 0; i < length; i++) {
result[i] = bridging::fromJs<T>(rt, array.getValueAtIndex(rt, i), jsInvoker);
}
return result;
}
};
template <typename T1, typename T2>
struct Bridging<std::pair<T1, T2>> : array_detail::BridgingStatic<std::pair<T1, T2>, 2> {
static std::pair<T1, T1>
fromJs(facebook::jsi::Runtime &rt, const jsi::Array &array, const std::shared_ptr<CallInvoker> &jsInvoker)
{
return std::make_pair(
bridging::fromJs<T1>(rt, array.getValueAtIndex(rt, 0), jsInvoker),
bridging::fromJs<T2>(rt, array.getValueAtIndex(rt, 1), jsInvoker));
}
};
template <typename... Types>
struct Bridging<std::tuple<Types...>> : array_detail::BridgingStatic<std::tuple<Types...>, sizeof...(Types)> {};
template <typename T>
struct Bridging<std::deque<T>> : array_detail::BridgingDynamic<std::deque<T>> {};
template <typename T>
struct Bridging<std::initializer_list<T>> : array_detail::BridgingDynamic<std::initializer_list<T>> {};
template <typename T>
struct Bridging<std::list<T>> : array_detail::BridgingDynamic<std::list<T>> {};
template <typename T>
struct Bridging<std::vector<T>> : array_detail::BridgingDynamic<std::vector<T>> {
static std::vector<T>
fromJs(facebook::jsi::Runtime &rt, const jsi::Array &array, const std::shared_ptr<CallInvoker> &jsInvoker)
{
size_t length = array.length(rt);
std::vector<T> vector;
vector.reserve(length);
for (size_t i = 0; i < length; i++) {
vector.push_back(bridging::fromJs<T>(rt, array.getValueAtIndex(rt, i), jsInvoker));
}
return vector;
}
};
template <typename T>
struct Bridging<std::set<T>> : array_detail::BridgingDynamic<std::set<T>> {
static std::set<T>
fromJs(facebook::jsi::Runtime &rt, const jsi::Array &array, const std::shared_ptr<CallInvoker> &jsInvoker)
{
size_t length = array.length(rt);
std::set<T> set;
for (size_t i = 0; i < length; i++) {
set.insert(bridging::fromJs<T>(rt, array.getValueAtIndex(rt, i), jsInvoker));
}
return set;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,139 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <jsi/jsi.h>
#include <react/bridging/Convert.h>
#include <cstdint>
#include <memory>
#include <type_traits>
namespace facebook::react {
class CallInvoker;
template <typename T, typename = void>
struct Bridging;
template <>
struct Bridging<void> {
// Highly generic code may result in "casting" to void.
static void fromJs(jsi::Runtime & /*unused*/, const jsi::Value & /*unused*/) {}
};
namespace bridging {
namespace detail {
template <typename F>
struct function_wrapper;
template <typename ClassT, typename ReturnT, typename... ArgsT>
struct function_wrapper<ReturnT (ClassT::*)(ArgsT...)> {
using type = std::function<ReturnT(ArgsT...)>;
};
template <typename ClassT, typename ReturnT, typename... ArgsT>
struct function_wrapper<ReturnT (ClassT::*)(ArgsT...) const> {
using type = std::function<ReturnT(ArgsT...)>;
};
template <typename T, typename = void>
struct bridging_wrapper {
using type = remove_cvref_t<T>;
};
// Convert lambda types to move-only function types since we can't specialize
// Bridging templates for arbitrary lambdas.
template <typename T>
struct bridging_wrapper<T, std::void_t<decltype(&remove_cvref_t<T>::operator())>>
: function_wrapper<decltype(&remove_cvref_t<T>::operator())> {};
} // namespace detail
template <typename T>
using bridging_t = typename detail::bridging_wrapper<T>::type;
template <typename ReturnT, typename JSArgT>
requires is_jsi_v<JSArgT>
auto fromJs(jsi::Runtime &rt, JSArgT &&value, const std::shared_ptr<CallInvoker> & /*unused*/)
-> decltype(static_cast<ReturnT>(std::move(convert(rt, std::forward<JSArgT>(value)))))
{
return static_cast<ReturnT>(std::move(convert(rt, std::forward<JSArgT>(value))));
}
template <typename ReturnT, typename JSArgT>
auto fromJs(jsi::Runtime &rt, JSArgT &&value, const std::shared_ptr<CallInvoker> & /*unused*/)
-> decltype(Bridging<remove_cvref_t<ReturnT>>::fromJs(rt, convert(rt, std::forward<JSArgT>(value))))
{
return Bridging<remove_cvref_t<ReturnT>>::fromJs(rt, convert(rt, std::forward<JSArgT>(value)));
}
template <typename ReturnT, typename JSArgT>
auto fromJs(jsi::Runtime &rt, JSArgT &&value, const std::shared_ptr<CallInvoker> &jsInvoker)
-> decltype(Bridging<remove_cvref_t<ReturnT>>::fromJs(rt, convert(rt, std::forward<JSArgT>(value)), jsInvoker))
{
return Bridging<remove_cvref_t<ReturnT>>::fromJs(rt, convert(rt, std::forward<JSArgT>(value)), jsInvoker);
}
template <typename T>
requires is_jsi_v<T>
auto toJs(jsi::Runtime &rt, T &&value, const std::shared_ptr<CallInvoker> & /*unused*/ = nullptr) -> remove_cvref_t<T>
{
return convert(rt, std::forward<T>(value));
}
template <typename T>
auto toJs(jsi::Runtime &rt, T &&value, const std::shared_ptr<CallInvoker> & /*unused*/ = nullptr)
-> decltype(Bridging<bridging_t<T>>::toJs(rt, std::forward<T>(value)))
{
return Bridging<bridging_t<T>>::toJs(rt, std::forward<T>(value));
}
template <typename T>
auto toJs(jsi::Runtime &rt, T &&value, const std::shared_ptr<CallInvoker> &jsInvoker)
-> decltype(Bridging<bridging_t<T>>::toJs(rt, std::forward<T>(value), jsInvoker))
{
return Bridging<bridging_t<T>>::toJs(rt, std::forward<T>(value), jsInvoker);
}
template <typename, typename = jsi::Value, typename = void>
inline constexpr bool supportsFromJs = false;
template <typename T, typename Arg>
inline constexpr bool supportsFromJs<
T,
Arg,
std::void_t<decltype(fromJs<T>(std::declval<jsi::Runtime &>(), std::declval<Arg>(), nullptr))>> = true;
template <typename T>
inline constexpr bool supportsFromJs<
T,
jsi::Value,
std::void_t<decltype(fromJs<T>(std::declval<jsi::Runtime &>(), std::declval<jsi::Value>(), nullptr))>> = true;
template <typename, typename = jsi::Value, typename = void>
inline constexpr bool supportsToJs = false;
template <typename JSReturnT, typename ReturnT>
inline constexpr bool supportsToJs<
JSReturnT,
ReturnT,
std::void_t<decltype(toJs(std::declval<jsi::Runtime &>(), std::declval<JSReturnT>(), nullptr))>> =
std::is_convertible_v<decltype(toJs(std::declval<jsi::Runtime &>(), std::declval<JSReturnT>(), nullptr)), ReturnT>;
template <typename ReturnT>
inline constexpr bool supportsToJs<
ReturnT,
jsi::Value,
std::void_t<decltype(toJs(std::declval<jsi::Runtime &>(), std::declval<ReturnT>(), nullptr))>> =
std::is_convertible_v<decltype(toJs(std::declval<jsi::Runtime &>(), std::declval<ReturnT>(), nullptr)), jsi::Value>;
} // namespace bridging
} // namespace facebook::react

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/bridging/Base.h>
namespace facebook::react {
template <>
struct Bridging<bool> {
static bool fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value)
{
return value.asBool();
}
static bool toJs(jsi::Runtime & /*unused*/, bool value)
{
return value;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/bridging/AString.h>
#include <react/bridging/Array.h>
#include <react/bridging/Bool.h>
#include <react/bridging/Class.h>
#include <react/bridging/Dynamic.h>
#include <react/bridging/Error.h>
#include <react/bridging/EventEmitter.h>
#include <react/bridging/Function.h>
#include <react/bridging/HighResTimeStamp.h>
#include <react/bridging/Number.h>
#include <react/bridging/Object.h>
#include <react/bridging/Promise.h>
#include <react/bridging/Value.h>

View File

@@ -0,0 +1,19 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_bridging_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_bridging OBJECT ${react_bridging_SRC})
target_include_directories(react_bridging PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_bridging jsi callinvoker react_timing)
target_compile_reactnative_options(react_bridging PRIVATE)
target_compile_options(react_bridging PRIVATE -Wpedantic)

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <jsi/jsi.h>
#include <memory>
#include <react/bridging/LongLivedObject.h>
namespace facebook::react {
class CallInvoker;
// Helper for passing jsi::Function arg to other methods.
class CallbackWrapper : public LongLivedObject {
private:
CallbackWrapper(jsi::Function &&callback, jsi::Runtime &runtime, std::shared_ptr<CallInvoker> jsInvoker)
: LongLivedObject(runtime), callback_(std::move(callback)), jsInvoker_(std::move(jsInvoker))
{
}
jsi::Function callback_;
std::shared_ptr<CallInvoker> jsInvoker_;
public:
static std::weak_ptr<CallbackWrapper>
createWeak(jsi::Function &&callback, jsi::Runtime &runtime, std::shared_ptr<CallInvoker> jsInvoker)
{
auto wrapper =
std::shared_ptr<CallbackWrapper>(new CallbackWrapper(std::move(callback), runtime, std::move(jsInvoker)));
LongLivedObjectCollection::get(runtime).add(wrapper);
return wrapper;
}
// Delete the enclosed jsi::Function
void destroy()
{
allowRelease();
}
jsi::Function &callback() noexcept
{
return callback_;
}
jsi::Runtime &runtime() noexcept
{
return runtime_;
}
CallInvoker &jsInvoker() noexcept
{
return *(jsInvoker_);
}
std::shared_ptr<CallInvoker> jsInvokerPtr() noexcept
{
return jsInvoker_;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/bridging/Base.h>
namespace facebook::react::bridging {
template <typename JSReturnT, typename ClassT, typename ReturnT, typename... ArgsT, typename... JSArgsT>
JSReturnT callFromJs(
jsi::Runtime &rt,
ReturnT (ClassT::*method)(jsi::Runtime &, ArgsT...),
const std::shared_ptr<CallInvoker> &jsInvoker,
ClassT *instance,
JSArgsT &&...args)
{
static_assert(sizeof...(ArgsT) == sizeof...(JSArgsT), "Incorrect arguments length");
static_assert((supportsFromJs<ArgsT, JSArgsT> && ...), "Incompatible arguments");
if constexpr (std::is_void_v<JSReturnT>) {
static_assert(std::is_void_v<ReturnT>, "Method must return void when JSReturnT is void");
}
if constexpr (std::is_void_v<JSReturnT>) {
(instance->*method)(rt, fromJs<ArgsT>(rt, std::forward<JSArgsT>(args), jsInvoker)...);
} else if constexpr (std::is_void_v<ReturnT>) {
static_assert(std::is_same_v<JSReturnT, jsi::Value>, "Void functions may only return undefined");
(instance->*method)(rt, fromJs<ArgsT>(rt, std::forward<JSArgsT>(args), jsInvoker)...);
return jsi::Value();
} else if constexpr (is_jsi_v<JSReturnT> || supportsToJs<ReturnT, JSReturnT>) {
static_assert(supportsToJs<ReturnT, JSReturnT>, "Incompatible return type");
return toJs(rt, (instance->*method)(rt, fromJs<ArgsT>(rt, std::forward<JSArgsT>(args), jsInvoker)...), jsInvoker);
} else if constexpr (is_optional_jsi_v<JSReturnT>) {
static_assert(
is_optional_v<ReturnT> ? supportsToJs<typename ReturnT::value_type, typename JSReturnT::value_type>
: supportsToJs<ReturnT, typename JSReturnT::value_type>,
"Incompatible return type");
auto result =
toJs(rt, (instance->*method)(rt, fromJs<ArgsT>(rt, std::forward<JSArgsT>(args), jsInvoker)...), jsInvoker);
if constexpr (std::is_same_v<decltype(result), jsi::Value>) {
if (result.isNull() || result.isUndefined()) {
return std::nullopt;
}
}
return convert(rt, std::move(result));
} else {
static_assert(std::is_convertible_v<ReturnT, JSReturnT>, "Incompatible return type");
return (instance->*method)(rt, fromJs<ArgsT>(rt, std::forward<JSArgsT>(args), jsInvoker)...);
}
}
template <typename ReturnT, typename... ArgsT>
constexpr size_t getParameterCount(ReturnT (* /*unused*/)(ArgsT...))
{
return sizeof...(ArgsT);
}
template <typename Class, typename ReturnT, typename... ArgsT>
constexpr size_t getParameterCount(ReturnT (Class::* /*unused*/)(ArgsT...))
{
return sizeof...(ArgsT);
}
} // namespace facebook::react::bridging

View File

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

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <folly/dynamic.h>
#include <jsi/JSIDynamic.h>
namespace facebook::react {
template <>
struct Bridging<folly::dynamic> {
static folly::dynamic fromJs(jsi::Runtime &rt, const jsi::Value &value)
{
return jsi::dynamicFromValue(rt, value);
}
static jsi::Value toJs(jsi::Runtime &rt, const folly::dynamic &value)
{
return jsi::valueFromDynamic(rt, value);
}
};
} // namespace facebook::react

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/bridging/Base.h>
#include <react/timing/primitives.h>
namespace facebook::react {
template <>
struct Bridging<HighResTimeStamp> {
static HighResTimeStamp fromJs(jsi::Runtime & /*rt*/, const jsi::Value &jsiValue)
{
return HighResTimeStamp::fromDOMHighResTimeStamp(jsiValue.asNumber());
}
static double toJs(jsi::Runtime & /*rt*/, const HighResTimeStamp &value)
{
return value.toDOMHighResTimeStamp();
}
};
template <>
struct Bridging<HighResDuration> {
static HighResDuration fromJs(jsi::Runtime & /*rt*/, const jsi::Value &jsiValue)
{
return HighResDuration::fromDOMHighResTimeStamp(jsiValue.asNumber());
}
static double toJs(jsi::Runtime & /*rt*/, const HighResDuration &value)
{
return value.toDOMHighResTimeStamp();
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "LongLivedObject.h"
#include <unordered_map>
namespace facebook::react {
// LongLivedObjectCollection
LongLivedObjectCollection& LongLivedObjectCollection::get(
jsi::Runtime& runtime) {
static std::unordered_map<void*, std::shared_ptr<LongLivedObjectCollection>>
instances;
static std::mutex instancesMutex;
std::scoped_lock lock(instancesMutex);
void* key = static_cast<void*>(&runtime);
auto entry = instances.find(key);
if (entry == instances.end()) {
entry =
instances.emplace(key, std::make_shared<LongLivedObjectCollection>())
.first;
}
return *(entry->second);
}
void LongLivedObjectCollection::add(std::shared_ptr<LongLivedObject> so) {
std::scoped_lock lock(collectionMutex_);
collection_.insert(std::move(so));
}
void LongLivedObjectCollection::remove(const LongLivedObject* o) {
std::scoped_lock lock(collectionMutex_);
for (auto p = collection_.begin(); p != collection_.end(); p++) {
if (p->get() == o) {
collection_.erase(p);
break;
}
}
}
void LongLivedObjectCollection::clear() {
std::scoped_lock lock(collectionMutex_);
collection_.clear();
}
size_t LongLivedObjectCollection::size() const {
std::scoped_lock lock(collectionMutex_);
return collection_.size();
}
// LongLivedObject
void LongLivedObject::allowRelease() {
LongLivedObjectCollection::get(runtime_).remove(this);
}
} // namespace facebook::react

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <jsi/jsi.h>
#include <memory>
#include <mutex>
#include <unordered_set>
namespace facebook::react {
/**
* A simple wrapper class that can be registered to a collection that keep it
* alive for extended period of time. This object can be removed from the
* collection when needed.
*
* The subclass of this class must be created using std::make_shared<T>().
* After creation, add it to the `LongLivedObjectCollection`. When done with the
* object, call `allowRelease()` to reclaim its memory.
*
* When using LongLivedObject to keep JS values alive, ensure you only hold weak
* references to the object outside the JS thread to avoid accessing deallocated
* values when the JS VM is shutdown.
*/
class LongLivedObject {
public:
virtual void allowRelease();
protected:
explicit LongLivedObject(jsi::Runtime &runtime) : runtime_(runtime) {}
virtual ~LongLivedObject() = default;
jsi::Runtime &runtime_;
};
/**
* A singleton, thread-safe, write-only collection for the `LongLivedObject`s.
*/
class LongLivedObjectCollection {
public:
static LongLivedObjectCollection &get(jsi::Runtime &runtime);
LongLivedObjectCollection() = default;
LongLivedObjectCollection(const LongLivedObjectCollection &) = delete;
void operator=(const LongLivedObjectCollection &) = delete;
void add(std::shared_ptr<LongLivedObject> o);
void remove(const LongLivedObject *o);
void clear();
size_t size() const;
private:
std::unordered_set<std::shared_ptr<LongLivedObject>> collection_;
mutable std::mutex collectionMutex_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/bridging/Base.h>
namespace facebook::react {
template <>
struct Bridging<double> {
static double fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value)
{
return value.asNumber();
}
static double toJs(jsi::Runtime & /*unused*/, double value)
{
return value;
}
};
template <>
struct Bridging<float> {
static float fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value)
{
return (float)value.asNumber();
}
static float toJs(jsi::Runtime & /*unused*/, float value)
{
return value;
}
};
template <>
struct Bridging<int32_t> {
static int32_t fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value)
{
return (int32_t)value.asNumber();
}
static int32_t toJs(jsi::Runtime & /*unused*/, int32_t value)
{
return value;
}
};
template <>
struct Bridging<uint32_t> {
static uint32_t fromJs(jsi::Runtime & /*unused*/, const jsi::Value &value)
{
return (uint32_t)value.asNumber();
}
static jsi::Value toJs(jsi::Runtime & /*unused*/, uint32_t value)
{
return double(value);
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/bridging/AString.h>
#include <react/bridging/Base.h>
#include <map>
#include <unordered_map>
namespace facebook::react {
template <>
struct Bridging<jsi::WeakObject> {
static jsi::WeakObject fromJs(jsi::Runtime &rt, const jsi::Object &value)
{
return jsi::WeakObject(rt, value);
}
static jsi::Value toJs(jsi::Runtime &rt, jsi::WeakObject &value)
{
return value.lock(rt);
}
};
template <typename T>
struct Bridging<std::shared_ptr<T>, std::enable_if_t<std::is_base_of_v<jsi::HostObject, T>>> {
static std::shared_ptr<T> fromJs(jsi::Runtime &rt, const jsi::Object &value)
{
return value.getHostObject<T>(rt);
}
static jsi::Object toJs(jsi::Runtime &rt, std::shared_ptr<T> value)
{
return jsi::Object::createFromHostObject(rt, std::move(value));
}
};
namespace map_detail {
template <typename T>
struct Bridging {
static T fromJs(jsi::Runtime &rt, const jsi::Object &value, const std::shared_ptr<CallInvoker> &jsInvoker)
{
T result;
auto propertyNames = value.getPropertyNames(rt);
auto length = propertyNames.length(rt);
for (size_t i = 0; i < length; i++) {
auto propertyName = propertyNames.getValueAtIndex(rt, i);
result.emplace(
bridging::fromJs<std::string>(rt, propertyName, jsInvoker),
bridging::fromJs<typename T::mapped_type>(rt, value.getProperty(rt, propertyName.asString(rt)), jsInvoker));
}
return result;
}
static jsi::Object toJs(jsi::Runtime &rt, const T &map, const std::shared_ptr<CallInvoker> &jsInvoker)
{
auto resultObject = jsi::Object(rt);
for (const auto &[key, value] : map) {
resultObject.setProperty(rt, jsi::PropNameID::forUtf8(rt, key), bridging::toJs(rt, value, jsInvoker));
}
return resultObject;
}
};
} // namespace map_detail
template <typename... Args>
struct Bridging<std::map<std::string, Args...>> : map_detail::Bridging<std::map<std::string, Args...>> {};
template <typename... Args>
struct Bridging<std::unordered_map<std::string, Args...>>
: map_detail::Bridging<std::unordered_map<std::string, Args...>> {};
} // namespace facebook::react

View File

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

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/bridging/Base.h>
#include <memory>
#include <optional>
namespace facebook::react {
template <>
struct Bridging<std::nullptr_t> {
static std::nullptr_t fromJs(jsi::Runtime &rt, const jsi::Value &value)
{
if (value.isNull() || value.isUndefined()) {
return nullptr;
} else {
throw jsi::JSError(rt, "Cannot convert value to nullptr");
}
}
static std::nullptr_t toJs(jsi::Runtime & /*unused*/, std::nullptr_t)
{
return nullptr;
}
};
template <typename T>
struct Bridging<std::optional<T>> {
static std::optional<T>
fromJs(jsi::Runtime &rt, const jsi::Value &value, const std::shared_ptr<CallInvoker> &jsInvoker)
{
if (value.isNull() || value.isUndefined()) {
return {};
}
return bridging::fromJs<T>(rt, value, jsInvoker);
}
template <typename U>
static std::optional<T>
fromJs(jsi::Runtime &rt, const std::optional<U> &value, const std::shared_ptr<CallInvoker> &jsInvoker)
{
if (value) {
return bridging::fromJs<T>(rt, *value, jsInvoker);
}
return {};
}
static jsi::Value toJs(jsi::Runtime &rt, const std::optional<T> &value, const std::shared_ptr<CallInvoker> &jsInvoker)
{
if (value) {
return bridging::toJs(rt, *value, jsInvoker);
}
return jsi::Value::null();
}
};
template <typename T>
struct Bridging<std::shared_ptr<T>, std::enable_if_t<!std::is_base_of_v<jsi::HostObject, T>>> {
static jsi::Value toJs(jsi::Runtime &rt, const std::shared_ptr<T> &ptr, const std::shared_ptr<CallInvoker> &jsInvoker)
{
if (ptr) {
return bridging::toJs(rt, *ptr, jsInvoker);
}
return jsi::Value::null();
}
};
template <typename T>
struct Bridging<std::unique_ptr<T>> {
static jsi::Value toJs(jsi::Runtime &rt, const std::unique_ptr<T> &ptr, const std::shared_ptr<CallInvoker> &jsInvoker)
{
if (ptr) {
return bridging::toJs(rt, *ptr, jsInvoker);
}
return jsi::Value::null();
}
};
template <typename T>
struct Bridging<std::weak_ptr<T>> {
static jsi::Value
toJs(jsi::Runtime &rt, const std::weak_ptr<T> &weakPtr, const std::shared_ptr<CallInvoker> &jsInvoker)
{
if (auto ptr = weakPtr.lock()) {
return bridging::toJs(rt, *ptr, jsInvoker);
}
return jsi::Value::null();
}
};
} // namespace facebook::react

View File

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

View File

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

View File

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