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,100 @@
/*
* Copyright (c) Meta Platforms, Inc. 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 "StubView.h"
#ifdef STUB_VIEW_TREE_VERBOSE
#include <glog/logging.h>
#endif
namespace facebook::react {
StubView::operator ShadowView() const {
auto shadowView = ShadowView{};
shadowView.componentName = componentName;
shadowView.componentHandle = componentHandle;
shadowView.surfaceId = surfaceId;
shadowView.tag = tag;
shadowView.props = props;
shadowView.eventEmitter = eventEmitter;
shadowView.layoutMetrics = layoutMetrics;
shadowView.state = state;
shadowView.traits = traits;
return shadowView;
}
void StubView::update(const ShadowView& shadowView) {
componentName = shadowView.componentName;
componentHandle = shadowView.componentHandle;
surfaceId = shadowView.surfaceId;
tag = shadowView.tag;
props = shadowView.props;
eventEmitter = shadowView.eventEmitter;
layoutMetrics = shadowView.layoutMetrics;
state = shadowView.state;
traits = shadowView.traits;
}
bool operator==(const StubView& lhs, const StubView& rhs) {
if (lhs.props != rhs.props) {
#ifdef STUB_VIEW_TREE_VERBOSE
LOG(ERROR) << "StubView: props do not match. lhs hash: "
<< std::hash<ShadowView>{}((ShadowView)lhs)
<< " rhs hash: " << std::hash<ShadowView>{}((ShadowView)rhs);
#endif
return false;
}
if (lhs.layoutMetrics != rhs.layoutMetrics) {
#ifdef STUB_VIEW_TREE_VERBOSE
LOG(ERROR) << "StubView: layoutMetrics do not match lhs hash: "
<< std::hash<ShadowView>{}((ShadowView)lhs)
<< " rhs hash: " << std::hash<ShadowView>{}((ShadowView)rhs);
#endif
return false;
}
return true;
}
bool operator!=(const StubView& lhs, const StubView& rhs) {
return !(lhs == rhs);
}
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(const StubView& stubView) {
return std::string{"Stub"} +
std::string{
stubView.componentHandle != 0 ? stubView.componentName : "[invalid]"};
}
std::vector<DebugStringConvertibleObject> getDebugProps(
const StubView& stubView,
DebugStringConvertibleOptions options) {
return {
{"surfaceId", getDebugDescription(stubView.surfaceId, options)},
{"tag", getDebugDescription(stubView.tag, options)},
{"props", getDebugDescription(stubView.props, options)},
{"eventEmitter", getDebugDescription(stubView.eventEmitter, options)},
{"layoutMetrics", getDebugDescription(stubView.layoutMetrics, options)},
{"state", getDebugDescription(stubView.state, options)},
};
}
std::vector<StubView> getDebugChildren(
const StubView& stubView,
DebugStringConvertibleOptions /*options*/) {
std::vector<StubView> result;
result.reserve(stubView.children.size());
for (const auto& child : stubView.children) {
result.push_back(*child);
}
return result;
}
#endif
} // 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.
*/
#pragma once
#include <memory>
#include <vector>
#include <react/renderer/core/LayoutMetrics.h>
#include <react/renderer/core/State.h>
#include <react/renderer/debug/debugStringConvertibleUtils.h>
#include <react/renderer/mounting/ShadowView.h>
namespace facebook::react {
static const int NO_VIEW_TAG = -1;
class StubView final {
public:
using Shared = std::shared_ptr<StubView>;
StubView() = default;
StubView(const StubView &stubView) = default;
operator ShadowView() const;
void update(const ShadowView &shadowView);
ComponentName componentName;
ComponentHandle componentHandle;
SurfaceId surfaceId;
Tag tag;
ShadowNodeTraits traits{};
Props::Shared props;
SharedEventEmitter eventEmitter;
LayoutMetrics layoutMetrics;
State::Shared state;
std::vector<StubView::Shared> children;
Tag parentTag{NO_VIEW_TAG};
};
bool operator==(const StubView &lhs, const StubView &rhs);
bool operator!=(const StubView &lhs, const StubView &rhs);
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(const StubView &stubView);
std::vector<DebugStringConvertibleObject> getDebugProps(
const StubView &stubView,
DebugStringConvertibleOptions options);
std::vector<StubView> getDebugChildren(const StubView &stubView, DebugStringConvertibleOptions options);
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,390 @@
/*
* Copyright (c) Meta Platforms, Inc. 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 "StubViewTree.h"
#include <folly/json.h>
#include <glog/logging.h>
#include <react/debug/react_native_assert.h>
#include <sstream>
#ifdef STUB_VIEW_TREE_VERBOSE
#define STUB_VIEW_LOG(code) code
#else
#define STUB_VIEW_LOG(code)
#endif
namespace facebook::react {
namespace {
std::string getComponentName(const ShadowView& shadowView) {
return std::string{"\""} + shadowView.componentName + "\"";
}
} // namespace
StubViewTree::StubViewTree(const ShadowView& shadowView)
: rootTag_(shadowView.tag) {
auto view = std::make_shared<StubView>();
view->update(shadowView);
registry_[shadowView.tag] = view;
}
const StubView& StubViewTree::getRootStubView() const {
return *registry_.at(rootTag_);
}
const StubView& StubViewTree::getStubView(Tag tag) const {
return *registry_.at(tag);
}
size_t StubViewTree::size() const {
return registry_.size();
}
void StubViewTree::mutate(const ShadowViewMutationList& mutations) {
STUB_VIEW_LOG({ LOG(ERROR) << "StubView: Mutating Begin"; });
for (const auto& mutation : mutations) {
switch (mutation.type) {
case ShadowViewMutation::Create: {
react_native_assert(mutation.parentTag == -1);
react_native_assert(mutation.oldChildShadowView == ShadowView{});
react_native_assert(mutation.newChildShadowView.props);
auto stubView = std::make_shared<StubView>();
stubView->update(mutation.newChildShadowView);
auto tag = mutation.newChildShadowView.tag;
STUB_VIEW_LOG({
LOG(ERROR) << "StubView: Create [" << tag << "] ##"
<< std::hash<ShadowView>{}((ShadowView)*stubView);
});
if (hasTag(tag)) {
LOG(ERROR) << "StubView: Create [" << tag << "]: tag already exists"
<< (tag == rootTag_ ? " (and it's the root tag)" : "")
<< ". The current registry: ";
dumpTags(LOG(ERROR));
}
react_native_assert(!hasTag(tag));
registry_[tag] = stubView;
recordMutation(mutation);
break;
}
case ShadowViewMutation::Delete: {
STUB_VIEW_LOG({
LOG(ERROR) << "StubView: Delete [" << mutation.oldChildShadowView.tag
<< "] ##"
<< std::hash<ShadowView>{}(mutation.oldChildShadowView);
});
react_native_assert(mutation.parentTag == -1);
react_native_assert(mutation.newChildShadowView == ShadowView{});
auto tag = mutation.oldChildShadowView.tag;
react_native_assert(hasTag(tag));
auto stubView = registry_[tag];
registry_.erase(tag);
recordMutation(mutation);
break;
}
case ShadowViewMutation::Insert: {
if (!mutation.mutatedViewIsVirtual()) {
react_native_assert(mutation.oldChildShadowView == ShadowView{});
auto parentTag = mutation.parentTag;
auto childTag = mutation.newChildShadowView.tag;
if (!hasTag(parentTag)) {
LOG(ERROR)
<< "StubView: ASSERT FAILURE: INSERT mutation assertion failure: parentTag not found: ["
<< parentTag << "] inserting child: [" << childTag << "]";
}
if (!hasTag(childTag)) {
LOG(ERROR)
<< "StubView: ASSERT FAILURE: INSERT mutation assertion failure: childTag not found: ["
<< parentTag << "] inserting child: [" << childTag << "]";
}
react_native_assert(hasTag(parentTag));
auto parentStubView = registry_[parentTag];
react_native_assert(hasTag(childTag));
auto childStubView = registry_[childTag];
childStubView->update(mutation.newChildShadowView);
STUB_VIEW_LOG({
LOG(ERROR) << "StubView: Insert [" << childTag << "] into ["
<< parentTag << "] @" << mutation.index << "("
<< parentStubView->children.size() << " children)";
});
react_native_assert(childStubView->parentTag == NO_VIEW_TAG);
react_native_assert(
mutation.index >= 0 &&
parentStubView->children.size() >=
static_cast<size_t>(mutation.index));
childStubView->parentTag = parentTag;
parentStubView->children.insert(
parentStubView->children.begin() + mutation.index, childStubView);
} else {
auto childTag = mutation.newChildShadowView.tag;
react_native_assert(hasTag(childTag));
auto childStubView = registry_[childTag];
childStubView->update(mutation.newChildShadowView);
}
recordMutation(mutation);
break;
}
case ShadowViewMutation::Remove: {
if (!mutation.mutatedViewIsVirtual()) {
react_native_assert(mutation.newChildShadowView == ShadowView{});
auto parentTag = mutation.parentTag;
auto childTag = mutation.oldChildShadowView.tag;
if (!hasTag(parentTag)) {
LOG(ERROR)
<< "StubView: ASSERT FAILURE: REMOVE mutation assertion failure: parentTag not found: ["
<< parentTag << "] removing child: [" << childTag << "]";
}
react_native_assert(hasTag(parentTag));
auto parentStubView = registry_[parentTag];
STUB_VIEW_LOG({
LOG(ERROR) << "StubView: Remove [" << childTag << "] from ["
<< parentTag << "] @" << mutation.index << " with "
<< parentStubView->children.size() << " children";
});
react_native_assert(
mutation.index >= 0 &&
parentStubView->children.size() >
static_cast<size_t>(mutation.index));
react_native_assert(hasTag(childTag));
auto childStubView = registry_[childTag];
react_native_assert(childStubView->parentTag == parentTag);
STUB_VIEW_LOG({
std::string strChildList = "";
int i = 0;
for (const auto& child : parentStubView->children) {
strChildList.append(std::to_string(i));
strChildList.append(":");
strChildList.append(std::to_string(child->tag));
strChildList.append(", ");
i++;
}
LOG(ERROR) << "StubView: BEFORE REMOVE: Children of " << parentTag
<< ": " << strChildList;
});
react_native_assert(
mutation.index >= 0 &&
parentStubView->children.size() >
static_cast<size_t>(mutation.index) &&
parentStubView->children[mutation.index]->tag ==
childStubView->tag);
childStubView->parentTag = NO_VIEW_TAG;
parentStubView->children.erase(
parentStubView->children.begin() + mutation.index);
}
recordMutation(mutation);
break;
}
case ShadowViewMutation::Update: {
STUB_VIEW_LOG({
LOG(ERROR) << "StubView: Update [" << mutation.newChildShadowView.tag
<< "] old hash: ##"
<< std::hash<ShadowView>{}(mutation.oldChildShadowView)
<< " new hash: ##"
<< std::hash<ShadowView>{}(mutation.newChildShadowView);
});
react_native_assert(mutation.oldChildShadowView.tag != 0);
react_native_assert(mutation.newChildShadowView.tag != 0);
react_native_assert(mutation.newChildShadowView.props);
react_native_assert(
mutation.newChildShadowView.tag == mutation.oldChildShadowView.tag);
react_native_assert(hasTag(mutation.newChildShadowView.tag));
auto oldStubView = registry_[mutation.newChildShadowView.tag];
react_native_assert(oldStubView->tag != 0);
if (mutation.parentTag != 0) {
react_native_assert(hasTag(mutation.parentTag));
react_native_assert(oldStubView->parentTag == mutation.parentTag);
}
oldStubView->update(mutation.newChildShadowView);
// Hash for stub view and the ShadowView should be identical - this
// tests that StubView and ShadowView hash are equivalent.
react_native_assert(
std::hash<ShadowView>{}((ShadowView)*oldStubView) ==
std::hash<ShadowView>{}(mutation.newChildShadowView));
recordMutation(mutation);
break;
}
}
}
STUB_VIEW_LOG({ LOG(ERROR) << "StubView: Mutating End"; });
// For iOS especially: flush logs because some might be lost on iOS if an
// assert is hit right after this.
google::FlushLogFiles(google::GLOG_INFO);
}
void StubViewTree::dispatchCommand(
const ShadowView& shadowView,
const std::string& commandName,
const folly::dynamic& args) {
auto stream = std::ostringstream();
stream << "Command {type: " << getComponentName(shadowView)
<< ", nativeID: " << getNativeId(shadowView) << ", name: \""
<< commandName;
if (!args.empty()) {
stream << ", args: " << folly::toJson(args);
}
stream << "\"}";
mountingLogs_.push_back(std::string(stream.str()));
}
std::vector<std::string> StubViewTree::takeMountingLogs() {
auto logs = mountingLogs_;
mountingLogs_.clear();
return logs;
}
std::ostream& StubViewTree::dumpTags(std::ostream& stream) const {
for (const auto& pair : registry_) {
auto& stubView = *pair.second;
stream << "[" << stubView.tag << "]##"
<< std::hash<ShadowView>{}((ShadowView)stubView) << " ";
}
return stream;
}
bool operator==(const StubViewTree& lhs, const StubViewTree& rhs) {
if (lhs.registry_.size() != rhs.registry_.size()) {
STUB_VIEW_LOG({
LOG(ERROR) << "Registry sizes are different. Sizes: LHS: "
<< lhs.registry_.size() << " RHS: " << rhs.registry_.size();
LOG(ERROR) << "Tags in LHS: ";
lhs.dumpTags(LOG(ERROR));
LOG(ERROR) << "Tags in RHS: ";
rhs.dumpTags(LOG(ERROR));
});
return false;
}
for (const auto& pair : lhs.registry_) {
auto& lhsStubView = *lhs.registry_.at(pair.first);
auto& rhsStubView = *rhs.registry_.at(pair.first);
if (lhsStubView != rhsStubView) {
STUB_VIEW_LOG({
LOG(ERROR) << "Registry entries are different. LHS: ["
<< lhsStubView.tag << "] ##"
<< std::hash<ShadowView>{}((ShadowView)lhsStubView)
<< " RHS: [" << rhsStubView.tag << "] ##"
<< std::hash<ShadowView>{}((ShadowView)rhsStubView);
});
return false;
}
}
return true;
}
bool operator!=(const StubViewTree& lhs, const StubViewTree& rhs) {
return !(lhs == rhs);
}
void StubViewTree::recordMutation(const ShadowViewMutation& mutation) {
switch (mutation.type) {
case ShadowViewMutation::Create: {
auto stream = std::ostringstream();
stream << "Create {type: "
<< getComponentName(mutation.newChildShadowView)
<< ", nativeID: " << getNativeId(mutation.newChildShadowView)
<< "}";
mountingLogs_.push_back(std::string(stream.str()));
break;
}
case ShadowViewMutation::Delete: {
auto stream = std::ostringstream();
stream << "Delete {type: "
<< getComponentName(mutation.oldChildShadowView)
<< ", nativeID: " << getNativeId(mutation.oldChildShadowView)
<< "}";
mountingLogs_.push_back(std::string(stream.str()));
break;
}
case ShadowViewMutation::Insert: {
auto stream = std::ostringstream();
stream << "Insert {type: "
<< getComponentName(mutation.newChildShadowView)
<< ", parentNativeID: " << getNativeId(mutation.parentTag)
<< ", index: " << mutation.index
<< ", nativeID: " << getNativeId(mutation.newChildShadowView)
<< "}";
mountingLogs_.push_back(std::string(stream.str()));
break;
}
case ShadowViewMutation::Remove: {
auto stream = std::ostringstream();
stream << "Remove {type: "
<< getComponentName(mutation.oldChildShadowView)
<< ", parentNativeID: " << getNativeId(mutation.parentTag)
<< ", index: " << mutation.index
<< ", nativeID: " << getNativeId(mutation.oldChildShadowView)
<< "}";
mountingLogs_.push_back(std::string(stream.str()));
break;
}
case ShadowViewMutation::Update: {
auto stream = std::ostringstream();
stream << "Update {type: "
<< getComponentName(mutation.newChildShadowView)
<< ", nativeID: " << getNativeId(mutation.newChildShadowView)
<< "}";
mountingLogs_.push_back(std::string(stream.str()));
break;
}
}
}
std::string StubViewTree::getNativeId(Tag tag) {
auto& view = getStubView(tag);
return getNativeId(view);
}
std::string StubViewTree::getNativeId(const ShadowView& shadowView) {
if (shadowView.traits.check(ShadowNodeTraits::Trait::RootNodeKind)) {
return "(root)";
}
if (shadowView.props->nativeId.empty()) {
return "(N/A)";
}
return "\"" + shadowView.props->nativeId + "\"";
}
} // namespace facebook::react

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <unordered_map>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <react/renderer/mounting/stubs/StubView.h>
namespace facebook::react {
class StubViewTree {
public:
StubViewTree() = default;
StubViewTree(const ShadowView &shadowView);
void mutate(const ShadowViewMutationList &mutations);
void dispatchCommand(const ShadowView &shadowView, const std::string &commandName, const folly::dynamic &args);
const StubView &getRootStubView() const;
/*
* Returns a view with given tag.
*/
const StubView &getStubView(Tag tag) const;
/*
* Returns the total amount of views in the tree.
*/
size_t size() const;
/**
* Returns the list of mounting operations in the buffer and clears it.
*/
std::vector<std::string> takeMountingLogs();
bool hasTag(Tag tag) const
{
return registry_.find(tag) != registry_.end();
}
private:
Tag rootTag_{};
std::unordered_map<Tag, StubView::Shared> registry_{};
std::vector<std::string> mountingLogs_{};
friend bool operator==(const StubViewTree &lhs, const StubViewTree &rhs);
friend bool operator!=(const StubViewTree &lhs, const StubViewTree &rhs);
std::ostream &dumpTags(std::ostream &stream) const;
std::string getNativeId(Tag tag);
std::string getNativeId(const ShadowView &shadowView);
void recordMutation(const ShadowViewMutation &mutation);
};
bool operator==(const StubViewTree &lhs, const StubViewTree &rhs);
bool operator!=(const StubViewTree &lhs, const StubViewTree &rhs);
} // namespace facebook::react

View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) Meta Platforms, Inc. 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 "stubs.h"
#include <react/renderer/core/LayoutableShadowNode.h>
#include <react/renderer/core/ShadowNodeFragment.h>
#include <react/renderer/mounting/Differentiator.h>
#include "../internal/ShadowViewNodePair.h"
#include "../internal/sliceChildShadowNodeViewPairs.h"
namespace facebook::react {
/*
* Sorting comparator for `reorderInPlaceIfNeeded`.
*/
static bool shouldFirstPairComesBeforeSecondOne(
const ShadowViewNodePair* lhs,
const ShadowViewNodePair* rhs) noexcept {
return lhs->shadowNode->getOrderIndex() < rhs->shadowNode->getOrderIndex();
}
/*
* Reorders pairs in-place based on `orderIndex` using a stable sort algorithm.
*/
static void reorderInPlaceIfNeeded(
std::vector<ShadowViewNodePair*>& pairs) noexcept {
// This is a simplified version of the function intentionally copied from
// `Differentiator.cpp`.
std::stable_sort(
pairs.begin(), pairs.end(), &shouldFirstPairComesBeforeSecondOne);
}
/*
* Generates `create` and `insert` instructions recursively traversing a shadow
* tree.
* This is a trivial implementation of diffing algorithm that can only "diff"
* an empty tree with some other one.
*/
static void calculateShadowViewMutationsForNewTree(
ShadowViewMutation::List& mutations,
ViewNodePairScope& scope,
const ShadowView& parentShadowView,
std::vector<ShadowViewNodePair*> newChildPairs) {
// Sorting pairs based on `orderIndex` if needed.
reorderInPlaceIfNeeded(newChildPairs);
for (auto newChildPair : newChildPairs) {
if (!newChildPair->isConcreteView) {
continue;
}
mutations.push_back(
ShadowViewMutation::CreateMutation(newChildPair->shadowView));
mutations.push_back(
ShadowViewMutation::InsertMutation(
parentShadowView.tag,
newChildPair->shadowView,
static_cast<int>(newChildPair->mountIndex)));
auto newGrandChildPairs =
sliceChildShadowNodeViewPairs(*newChildPair, scope, false, {}, {});
calculateShadowViewMutationsForNewTree(
mutations, scope, newChildPair->shadowView, newGrandChildPairs);
}
}
StubViewTree buildStubViewTreeWithoutUsingDifferentiator(
const ShadowNode& rootShadowNode) {
auto mutations = ShadowViewMutation::List{};
mutations.reserve(256);
ViewNodePairScope scope;
ShadowViewNodePair rootShadowNodePair{.shadowNode = &rootShadowNode};
calculateShadowViewMutationsForNewTree(
mutations,
scope,
ShadowView(rootShadowNode),
sliceChildShadowNodeViewPairs(rootShadowNodePair, scope, false, {}, {}));
auto emptyRootShadowNode = rootShadowNode.clone(
ShadowNodeFragment{
.props = ShadowNodeFragment::propsPlaceholder(),
.children = ShadowNode::emptySharedShadowNodeSharedList()});
auto stubViewTree = StubViewTree(ShadowView(*emptyRootShadowNode));
stubViewTree.mutate(mutations);
return stubViewTree;
}
StubViewTree buildStubViewTreeUsingDifferentiator(
const ShadowNode& rootShadowNode) {
auto emptyRootShadowNode = rootShadowNode.clone(
ShadowNodeFragment{
.props = ShadowNodeFragment::propsPlaceholder(),
.children = ShadowNode::emptySharedShadowNodeSharedList()});
auto mutations =
calculateShadowViewMutations(*emptyRootShadowNode, rootShadowNode);
auto stubViewTree = StubViewTree(ShadowView(*emptyRootShadowNode));
stubViewTree.mutate(mutations);
return stubViewTree;
}
} // namespace facebook::react

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/mounting/stubs/StubView.h>
#include <react/renderer/mounting/stubs/StubViewTree.h>
namespace facebook::react {
/*
* Builds a ShadowView tree from given root ShadowNode using custom built-in
* implementation (*without* using Differentiator).
*/
StubViewTree buildStubViewTreeWithoutUsingDifferentiator(const ShadowNode &rootShadowNode);
/*
* Builds a ShadowView tree from given root ShadowNode using Differentiator by
* generating mutation instructions between empty and final trees.
*/
StubViewTree buildStubViewTreeUsingDifferentiator(const ShadowNode &rootShadowNode);
} // namespace facebook::react