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,29 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_nativemodule_dom_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_dom
OBJECT
${react_nativemodule_dom_SRC}
$<TARGET_OBJECTS:react_codegen_rncore>
)
target_include_directories(react_nativemodule_dom PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_nativemodule_dom
rrc_root
react_codegen_rncore
react_cxxreact
react_renderer_bridging
react_renderer_dom
react_renderer_uimanager
)
target_compile_reactnative_options(react_nativemodule_dom PRIVATE)
target_compile_options(react_nativemodule_dom PRIVATE -Wpedantic)

View File

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

View File

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

View File

@@ -0,0 +1,60 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
header_search_paths = [
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
]
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the domnativemodule to access its own files
end
Pod::Spec.new do |s|
s.name = "React-domnativemodule"
s.version = version
s.summary = "React Native DOM native module"
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("*.{cpp,h}", "*.h")
s.header_dir = "react/nativemodule/dom"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"OTHER_CFLAGS" => "$(inherited)",
"DEFINES_MODULE" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_domnativemodule")
s.dependency "React-jsi"
s.dependency "React-jsiexecutor"
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.dependency "Yoga"
s.dependency "ReactCommon/turbomodule/core"
s.dependency "React-Fabric"
s.dependency "React-Fabric/bridging"
s.dependency "React-FabricComponents"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])
add_dependency(s, "React-RCTFBReactNativeSpec")
end