From a1954c8935118e8e98c0af7a79cb1e028bd23a94 Mon Sep 17 00:00:00 2001 From: kwy404 Date: Fri, 25 Sep 2026 16:17:23 -0300 Subject: [PATCH] Fix stale layout when a child switches to display: contents after insertion Each node caches how many of its children use `display: contents`, but the count is only updated when children are inserted, removed or replaced. Setting `display: contents` on a child that is already attached left the count at zero, so the owner skipped `cleanupContentsNodesRecursively` and the contents node stayed dirty after layout. Dirty marking from its children then stopped at that node and never reached the root, so later style changes were ignored. Recompute the owner's count in `YGNodeStyleSetDisplay` when a node with an owner switches to or from `display: contents`. --- tests/YGDirtyMarkingTest.cpp | 26 ++++++++++++++++++++++++++ yoga/YGNodeStyle.cpp | 9 +++++++++ 2 files changed, 35 insertions(+) diff --git a/tests/YGDirtyMarkingTest.cpp b/tests/YGDirtyMarkingTest.cpp index 9e098a4561..f0d22312ee 100644 --- a/tests/YGDirtyMarkingTest.cpp +++ b/tests/YGDirtyMarkingTest.cpp @@ -339,3 +339,29 @@ TEST(YogaTest, dirty_parent_when_subtree_freed_recursive) { EXPECT_TRUE(YGNodeIsDirty(root)); YGNodeFree(root); } + +TEST(YogaTest, dirty_propagation_through_child_set_to_display_contents) { + YGNodeRef root = YGNodeNew(); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + + YGNodeRef child = YGNodeNew(); + YGNodeInsertChild(root, child, 0); + + YGNodeRef grandchild = YGNodeNew(); + YGNodeStyleSetWidth(grandchild, 10); + YGNodeStyleSetHeight(grandchild, 10); + YGNodeInsertChild(child, grandchild, 0); + + YGNodeStyleSetDisplay(child, YGDisplayContents); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + EXPECT_FALSE(YGNodeIsDirty(child)); + + YGNodeStyleSetWidth(grandchild, 20); + EXPECT_TRUE(YGNodeIsDirty(root)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + ASSERT_FLOAT_EQ(20, YGNodeLayoutGetWidth(grandchild)); + + YGNodeFreeRecursive(root); +} diff --git a/yoga/YGNodeStyle.cpp b/yoga/YGNodeStyle.cpp index e43a38e64e..86da8ddf20 100644 --- a/yoga/YGNodeStyle.cpp +++ b/yoga/YGNodeStyle.cpp @@ -152,7 +152,16 @@ YGOverflow YGNodeStyleGetOverflow(const YGNodeConstRef node) { } void YGNodeStyleSetDisplay(const YGNodeRef node, const YGDisplay display) { + const bool wasContents = + resolveRef(node)->style().display() == Display::Contents; updateStyle<&Style::display, &Style::setDisplay>(node, scopedEnum(display)); + + // The owner caches how many of its children use display: contents, so it + // has to be recomputed when an attached child switches to or from it. + auto owner = resolveRef(node)->getOwner(); + if (owner != nullptr && wasContents != (display == YGDisplayContents)) { + owner->setChildren(owner->getChildren()); + } } YGDisplay YGNodeStyleGetDisplay(const YGNodeConstRef node) {