From 4cb9bddcd2ed510282ea29ae4a5ff8243fd1c41d Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Mon, 20 Apr 2026 22:00:54 -0700 Subject: [PATCH] Fix node ownership when `display: contents` is used (#1924) Summary: X-link: https://github.com/facebook/react-native/pull/56422 Changelog: [GENERAL][FIXED] Fixed Yoga node ownership when `display: contents` is used in absolutely positioned subtrees Fixes an edge case where nodes with `display: contents` weren't cloned properly inside absolutely positioned subtrees. Adds a test case covering this scenario. Reviewed By: NickGerleman Differential Revision: D100581579 Pulled By: j-piasecki --- tests/YGCloneNodeTest.cpp | 42 +++++++++++++++++++++++++++++++ yoga/algorithm/AbsoluteLayout.cpp | 1 + yoga/node/Node.cpp | 7 +++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/tests/YGCloneNodeTest.cpp b/tests/YGCloneNodeTest.cpp index d029fbe027..004b39457a 100644 --- a/tests/YGCloneNodeTest.cpp +++ b/tests/YGCloneNodeTest.cpp @@ -45,6 +45,48 @@ TEST(YogaTest, absolute_node_cloned_with_static_parent) { YGNodeFreeRecursive(clonedRoot); } +TEST(YogaTest, absolute_node_cloned_through_nested_display_contents) { + YGNodeRef root = YGNodeNew(); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + + YGNodeRef wrapper = YGNodeNew(); + YGNodeStyleSetPositionType(wrapper, YGPositionTypeStatic); + YGNodeStyleSetWidth(wrapper, 50); + YGNodeStyleSetHeight(wrapper, 50); + YGNodeInsertChild(root, wrapper, 0); + + YGNodeRef static1 = YGNodeNew(); + YGNodeStyleSetPositionType(static1, YGPositionTypeStatic); + YGNodeStyleSetFlexGrow(static1, 1); + YGNodeInsertChild(wrapper, static1, 0); + + YGNodeRef contents1 = YGNodeNew(); + YGNodeStyleSetDisplay(contents1, YGDisplayContents); + YGNodeInsertChild(static1, contents1, 0); + + YGNodeRef contents2 = YGNodeNew(); + YGNodeStyleSetDisplay(contents2, YGDisplayContents); + YGNodeInsertChild(contents1, contents2, 0); + + YGNodeRef absolute = YGNodeNew(); + YGNodeStyleSetPositionType(absolute, YGPositionTypeAbsolute); + YGNodeStyleSetWidthPercent(absolute, 50); + YGNodeStyleSetHeight(absolute, 1); + YGNodeInsertChild(contents2, absolute, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + YGNodeRef clonedRoot = YGNodeClone(root); + YGNodeStyleSetWidth(clonedRoot, 200); + YGNodeCalculateLayout(clonedRoot, YGUndefined, YGUndefined, YGDirectionLTR); + + recursivelyAssertProperNodeOwnership(clonedRoot); + + YGNodeFreeRecursive(root); + YGNodeFreeRecursive(clonedRoot); +} + TEST(YogaTest, absolute_node_cloned_with_static_ancestors) { YGNodeRef root = YGNodeNew(); YGNodeStyleSetWidth(root, 100); diff --git a/yoga/algorithm/AbsoluteLayout.cpp b/yoga/algorithm/AbsoluteLayout.cpp index 181dfcb1b0..4f14165d7e 100644 --- a/yoga/algorithm/AbsoluteLayout.cpp +++ b/yoga/algorithm/AbsoluteLayout.cpp @@ -558,6 +558,7 @@ bool layoutAbsoluteDescendants( // we need to mutate these descendents. Make sure the path of // nodes to them is mutable before positioning. child->cloneChildrenIfNeeded(); + cleanupContentsNodesRecursively(child); const Direction childDirection = child->resolveDirection(currentNodeDirection); // By now all descendants of the containing block that are not absolute diff --git a/yoga/node/Node.cpp b/yoga/node/Node.cpp index d42bd5f9e7..d04127733d 100644 --- a/yoga/node/Node.cpp +++ b/yoga/node/Node.cpp @@ -392,7 +392,12 @@ void Node::cloneChildrenIfNeeded() { child = resolveRef(config_->cloneNode(child, this, i)); child->setOwner(this); - if (child->hasContentsChildren()) [[unlikely]] { + if (child->style().display() == Display::Contents) [[unlikely]] { + // The contents node's children are treated as children of the + // contents node's parent for layout purposes, so they need + // to be cloned as well. + child->cloneChildrenIfNeeded(); + } else if (child->hasContentsChildren()) [[unlikely]] { child->cloneContentsChildrenIfNeeded(); } }