/* * 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 #include #include #include #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) { auto view = std::make_shared(); view->update(shadowView); rootTag_ = shadowView.tag; 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->update(mutation.newChildShadowView); auto tag = mutation.newChildShadowView.tag; STUB_VIEW_LOG({ LOG(ERROR) << "StubView: Create [" << tag << "] ##" << std::hash{}((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{}(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(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(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(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{}(mutation.oldChildShadowView) << " new hash: ##" << std::hash{}(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)*oldStubView) == std::hash{}(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 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)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)lhsStubView) << " RHS: [" << rhsStubView.tag << "] ##" << std::hash{}((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