diff --git a/ReactCommon/fabric/core/shadownode/ShadowNode.cpp b/ReactCommon/fabric/core/shadownode/ShadowNode.cpp index 1a282dd6e..8855179b2 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNode.cpp +++ b/ReactCommon/fabric/core/shadownode/ShadowNode.cpp @@ -146,6 +146,25 @@ void ShadowNode::cloneChildrenIfShared() { children_ = std::make_shared(*children_); } +bool ShadowNode::constructAncestorPath( + const ShadowNode &ancestorShadowNode, + std::vector> &ancestors) const { + // Note: We have a decent idea of how to make it reasonable performant. + // This is not implemented yet though. See T36620537 for more details. + if (this == &ancestorShadowNode) { + return true; + } + + for (const auto &childShadowNode : *ancestorShadowNode.children_) { + if (constructAncestorPath(*childShadowNode, ancestors)) { + ancestors.push_back(std::ref(ancestorShadowNode)); + return true; + } + } + + return false; +} + #pragma mark - DebugStringConvertible #if RN_DEBUG_STRING_CONVERTIBLE diff --git a/ReactCommon/fabric/core/shadownode/ShadowNode.h b/ReactCommon/fabric/core/shadownode/ShadowNode.h index aa1562144..17dbb64bf 100644 --- a/ReactCommon/fabric/core/shadownode/ShadowNode.h +++ b/ReactCommon/fabric/core/shadownode/ShadowNode.h @@ -101,6 +101,21 @@ class ShadowNode : public virtual Sealable, */ void setLocalData(const SharedLocalData &localData); + /* + * Forms a list of all ancestors of the node relative to the given ancestor. + * The list starts from the parent node and ends with the given ancestor node. + * Returns `true` if successful, `false` otherwise. + * Thread-safe if the subtree is immutable. + * The theoretical complexity of this algorithm is `O(n)`. Use it wisely. + * The particular implementation can use some tricks to mitigate the + * complexity problem up to `0(ln(n))` but this is not guaranteed. + * Particular consumers should use appropriate cache techniques based on + * `childIndex` and `nodeId` tracking. + */ + bool constructAncestorPath( + const ShadowNode &rootShadowNode, + std::vector> &ancestors) const; + #pragma mark - DebugStringConvertible #if RN_DEBUG_STRING_CONVERTIBLE diff --git a/ReactCommon/fabric/core/tests/ShadowNodeTest.cpp b/ReactCommon/fabric/core/tests/ShadowNodeTest.cpp index 45311bbfa..cac7546d8 100644 --- a/ReactCommon/fabric/core/tests/ShadowNodeTest.cpp +++ b/ReactCommon/fabric/core/tests/ShadowNodeTest.cpp @@ -202,3 +202,78 @@ TEST(ShadowNodeTest, handleLocalData) { secondNode->sealRecursive(); ASSERT_ANY_THROW(secondNode->setLocalData(localDataOver9000)); } + +TEST(ShadowNodeTest, handleBacktracking) { + /* + * The structure: + * + * + * + * + * + * + * + * + * + */ + + auto props = std::make_shared(); + + auto nodeAA = std::make_shared( + ShadowNodeFragment{ + .props = props, + .children = ShadowNode::emptySharedShadowNodeSharedList()}, + nullptr); + + auto nodeABA = std::make_shared( + ShadowNodeFragment{ + .props = props, + .children = ShadowNode::emptySharedShadowNodeSharedList()}, + nullptr); + auto nodeABB = std::make_shared( + ShadowNodeFragment{ + .props = props, + .children = ShadowNode::emptySharedShadowNodeSharedList()}, + nullptr); + auto nodeABC = std::make_shared( + ShadowNodeFragment{ + .props = props, + .children = ShadowNode::emptySharedShadowNodeSharedList()}, + nullptr); + + auto nodeABChildren = std::make_shared>( + std::vector{nodeABA, nodeABB, nodeABC}); + auto nodeAB = std::make_shared( + ShadowNodeFragment{.props = props, .children = nodeABChildren}, nullptr); + + auto nodeAC = std::make_shared( + ShadowNodeFragment{ + .props = props, + .children = ShadowNode::emptySharedShadowNodeSharedList()}, + nullptr); + + auto nodeAChildren = std::make_shared>( + std::vector{nodeAA, nodeAB, nodeAC}); + auto nodeA = std::make_shared( + ShadowNodeFragment{.props = props, .children = nodeAChildren}, nullptr); + + auto nodeZ = std::make_shared( + ShadowNodeFragment{ + .props = props, + .children = ShadowNode::emptySharedShadowNodeSharedList()}, + nullptr); + + std::vector> ancestors = {}; + + // Negative case: + auto success = nodeZ->constructAncestorPath(*nodeA, ancestors); + ASSERT_FALSE(success); + ASSERT_EQ(ancestors.size(), 0); + + // Positive case: + success = nodeABC->constructAncestorPath(*nodeA, ancestors); + ASSERT_TRUE(success); + ASSERT_EQ(ancestors.size(), 2); + ASSERT_EQ(&ancestors[0].get(), nodeAB.get()); + ASSERT_EQ(&ancestors[1].get(), nodeA.get()); +}