Skip to content

Only keep a cloned node's layout when its children's layout results match - #59984

Merged
meta-codesync[bot] merged 2 commits into
react:mainfrom
lenaic:export-D131502045
Nov 13, 2026
Merged

meta-codesync[bot] merged 2 commits into
react:mainfrom
lenaic:export-D131502045

Conversation

@lenaic

@lenaic lenaic commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary:
When a host node is cloned with its children passed in (passChildrenWhenCloningPersistedNodes), YogaLayoutableShadowNode::updateYogaChildren keeps the clone's layout clean if every new child is clean and styled like the child it replaces. That misses children that are clean and styled the same but carry a different layout. Yoga then reuses the parent's cached layout, never visits those children, and they keep their stale layout.

Runtime shadow node reference updates produce exactly such children when <Activity> hides a subtree:

  1. The renderer commits a display: none clone H of the hidden subtree's host root V. H gets its own runtime reference; the renderer keeps V, which still holds the layout and layout cache from when it was visible.
  2. Laying out H zeroes out clones of V's children. Those clones inherit the children's runtime references, so the renderer now holds the zeroed revisions of V's children while it still holds the non-zeroed V.
  3. Any re-render inside the subtree clones those zeroed children and then V with the new children. Every check passes, so V's clone keeps V's cached layout.
  4. When the subtree is revealed, Yoga reuses that cache and the content stays at 0x0.

With the children appended one by one instead, the append always dirties the parent, so this only shows up with the flag on.

Also require each new child's layout results (yoga::LayoutResults: computed layout and layout cache) to match those of the child it replaces before keeping the clone clean. A parent's cached layout is only valid for the layouts its children had when it was computed. A child cloned without layout changes copies its layout results, so the common case keeps the clone clean.

configVersion is left out of the comparison: every clone gets its own Yoga config, and Node::setConfig() re-aligns the layout's configVersion with it. Comparing it would, for example, dirty a display: none parent's ancestors on every commit that clones it, which YogaDirtyFlagTest.dirtyChildOfHiddenNodeMustNotDirtyIt guards against.

Changelog: [General][Fixed] - Fix content laid out at zero size after revealing an <Activity> subtree when children are passed into clones

Reviewed By: javache

Differential Revision: D131502045

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Nov 12, 2026
@meta-codesync

meta-codesync Bot commented Nov 12, 2026

Copy link
Copy Markdown

@lenaic has exported this pull request. If you are a Meta employee, you can view the originating Diff in D131502045.

@javache javache left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review automatically exported from Phabricator review in Meta.

Summary:

`yoga::Node` is exported from the Yoga library and hands out its `LayoutResults` through `getLayout()`, but `LayoutResults::operator==` is defined out of line and not exported. Code outside Yoga that compares two layout results fails to link wherever Yoga is built as a shared library with hidden visibility.

Mark the operator `YG_EXPORT` so React Native can compare a node's layout results with those of the node it replaces.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D131770771
…atch

Summary:
When a host node is cloned with its children passed in (`passChildrenWhenCloningPersistedNodes`), `YogaLayoutableShadowNode::updateYogaChildren` keeps the clone's layout clean if every new child is clean and styled like the child it replaces. That misses children that are clean and styled the same but carry a different layout. Yoga then reuses the parent's cached layout, never visits those children, and they keep their stale layout.

Runtime shadow node reference updates produce exactly such children when `<Activity>` hides a subtree:

1. The renderer commits a `display: none` clone H of the hidden subtree's host root V. H gets its own runtime reference; the renderer keeps V, which still holds the layout and layout cache from when it was visible.
2. Laying out H zeroes out clones of V's children. Those clones inherit the children's runtime references, so the renderer now holds the zeroed revisions of V's children while it still holds the non-zeroed V.
3. Any re-render inside the subtree clones those zeroed children and then V with the new children. Every check passes, so V's clone keeps V's cached layout.
4. When the subtree is revealed, Yoga reuses that cache and the content stays at 0x0.

With the children appended one by one instead, the append always dirties the parent, so this only shows up with the flag on.

Also require each new child's layout results (`yoga::LayoutResults`: computed layout and layout cache) to match those of the child it replaces before keeping the clone clean. A parent's cached layout is only valid for the layouts its children had when it was computed. A child cloned without layout changes copies its layout results, so the common case keeps the clone clean.

`configVersion` is left out of the comparison: every clone gets its own Yoga config, and `Node::setConfig()` re-aligns the layout's `configVersion` with it. Comparing it would, for example, dirty a `display: none` parent's ancestors on every commit that clones it, which `YogaDirtyFlagTest.dirtyChildOfHiddenNodeMustNotDirtyIt` guards against.

Changelog: [General][Fixed] - Fix content laid out at zero size after revealing an `<Activity>` subtree when children are passed into clones

Reviewed By: javache

Differential Revision: D131502045
@meta-codesync meta-codesync Bot changed the title Only keep a cloned node's layout when its children's computed layouts match Only keep a cloned node's layout when its children's layout results match Nov 13, 2026
@meta-codesync
meta-codesync Bot merged commit 70f655a into react:main Nov 13, 2026
96 checks passed
@react-native-bot

Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @lenaic in 70f655a

When will my fix make it into a release? | How to file a pick request?

@react-native-bot react-native-bot added the Merged This PR has been merged. label Nov 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants