From 2bacf51de56aacdf2007c759696d11936922681d Mon Sep 17 00:00:00 2001 From: kwy404 Date: Mon, 28 Sep 2026 08:27:47 -0700 Subject: [PATCH] Fix stale layout when a child switches to `display: contents` after insertion Summary: Each node caches how many of its children use `display: contents` (`contentsChildrenCount_`, added in https://github.com/react/yoga/issues/1726). The count is updated in `insertChild`, `removeChild`, `replaceChild` and `setChildren`, but not when the display of a child that is already attached changes. So if `YGNodeStyleSetDisplay(child, YGDisplayContents)` is called after the child was inserted, the owner still reports `hasContentsChildren() == false`. The layout algorithm then skips `cleanupContentsNodesRecursively` for it, and the contents node keeps `isDirty == true` after layout. When a descendant of that node changes later, `markDirtyAndPropagate` stops at the contents node because it is already dirty, the root is never marked dirty, and the next `YGNodeCalculateLayout` returns the cached layout. Minimal repro: ```cpp 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); // YGNodeIsDirty(child) is still true YGNodeStyleSetWidth(grandchild, 20); // YGNodeIsDirty(root) is false YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); // YGNodeLayoutGetWidth(grandchild) is 10, expected 20 ``` This affects any binding that mutates nodes in place (the JS and Java bindings both go through `YGNodeStyleSetDisplay`). Setting the display before inserting the child works, which is what the existing tests do. The fix recomputes the owner's count in `YGNodeStyleSetDisplay` when a node that has an owner switches to or from `display: contents`. Changelog: [General][Fixed] - Fix stale layout when a child switches to `display: contents` after being inserted X-link: https://github.com/react/yoga/pull/2028 Reviewed By: javache Differential Revision: D122122520 Pulled By: cipolleschi --- .../react-native/ReactCommon/yoga/yoga/YGNodeStyle.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.cpp b/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.cpp index e43a38e64eb8..86da8ddf2004 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.cpp +++ b/packages/react-native/ReactCommon/yoga/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) {