Skip to content

Commit 5cd2cbd

Browse files
sammy-SCmeta-codesync[bot]
authored andcommitted
Add fixYogaFlexBasisFitContentInMainAxis flag to avoid unnecessary re-measurement (#1909)
Summary: Pull Request resolved: #1909 X-link: facebook/react-native#55897 changelog: [internal] this change is gated. ## Problem When Yoga computes flex basis for container children, the legacy behavior applies a `FitContent` constraint in the **main axis**, bounding the child's measurement by the parent's available space. This creates a dependency between the child's flex basis and the parent's content-determined size, causing **unnecessary re-measurement and cascading ownership clones** when siblings change size. ### The re-measurement cascade (before fix) ``` ScrollView (overflow: scroll) +-------------------------------+ | Content Container (auto h) | | +---------------------------+ | | | Item A h=200 | | <-- Item A height changes | +---------------------------+ | | | Item B h=300 | | | +---------------------------+ | | | Item C h=150 | | | +---------------------------+ | +-------------------------------+ | v Content container height changes (200+300+150 = 650) | v FitContent(650) re-measures ALL items <-- PROBLEM | because their flex basis was FitContent(old_height) v Cascading clones of the entire subtree ``` With the legacy `FitContent` in the main axis, each item's flex basis is `min(content, parent_height)`. When Item A changes height, the content container's height changes, which invalidates the FitContent constraint for ALL items, triggering a full re-measurement cascade. ### After fix (MaxContent in main axis) ``` ScrollView (overflow: scroll) +-------------------------------+ | Content Container (auto h) | | +---------------------------+ | | | Item A h=200 -> 250 | | <-- Item A height changes | +---------------------------+ | | | Item B h=300 | | <-- NOT re-measured (basis unchanged) | +---------------------------+ | | | Item C h=150 | | <-- NOT re-measured (basis unchanged) | +---------------------------+ | +-------------------------------+ | v Content container height changes (250+300+150 = 700) | v Only Item A is re-measured. B and C keep their MaxContent flex basis (independent of parent height). ``` With `MaxContent`, each item's flex basis is its intrinsic content size, independent of the parent. Changing one item doesn't invalidate siblings. ## Solution This diff adds a `FlexBasisFitContentInMainAxis` errata bit gated by the `fixYogaFlexBasisFitContentInMainAxis` feature flag. When the fix is active, flex basis measurement uses `MaxContent` (unbounded) instead of `FitContent` for container children in the main axis. ### Three check points in `computeFlexBasisForChild` ``` computeFlexBasisForChild(parent, child) | |-- Check 1: Accept positive flex basis when mainAxisSize is NaN | (fixes flexBasis:200 items in ScrollView getting height 0) | |-- Check 2: FitContent vs MaxContent constraint | +--------------------------------------------------+ | | Parent type | Legacy (errata) | Fix | | |--------------------+-----------------+-----------| | | Auto height | FitContent | MaxContent| <-- key change | | Definite height | FitContent | FitContent| <-- preserved | | Scroll container | MaxContent | MaxContent| <-- unchanged | | Text child (any) | FitContent | FitContent| <-- preserved | +--------------------------------------------------+ | |-- Check 3: ownerHeightForChildren fallback (preserves percentage resolution when availableInnerHeight is NaN) ``` ### Why definite-height parents keep FitContent Yoga's default `flexShrink` is 0 (unlike CSS's default of 1). Without FitContent, a child measured at MaxContent would get a flex basis equal to its full content height and never shrink to fit: ``` View (height: 760) View (height: 760) +-------------------+ +-------------------+ | Wrapper (auto h) | | Wrapper (auto h) | | +-----------+ | | +-----------+-----|----+ | | ScrollView| | | | ScrollView| | | | | content: | | | | content: | | | | | 1800px | | | | 1800px | | | | +-----------+ | | | | | | | h=760 (bounded) | | +-----------+ | | +-------------------+ +---|---------+-----|----+ FitContent: wrapper=760 | h=1800 (overflows!) ScrollView can scroll MaxContent: wrapper=1800 flexShrink=0, no shrinking ScrollView frame=1800=content scrollable range = 0! ``` For **definite-height** parents, FitContent is safe (the parent's size is fixed, so no re-measurement cascade). For **auto-height** parents, MaxContent is used to avoid the cascade. ### Percentage resolution preservation (Check 3) When MaxContent is used, `availableInnerHeight` becomes NaN. This would break percentage-height grandchildren. Check 3 derives a definite `ownerHeightForChildren` from the parent-provided `ownerHeight`: ``` View (height: 844) +---------------------------+ | Wrapper (auto h) | availableInnerHeight = NaN (MaxContent) | ownerHeight = 844 | ownerHeightForChildren = 844 (from Check 3) | +-----+ +-----------+ | | |h:500| |h:'50%' | | 50% resolves against 844, not NaN | | | |= 422 | | | +-----+ +-----------+ | +---------------------------+ ``` Children of scroll containers skip this fallback (scroll content is intentionally unbounded). Reviewed By: javache Differential Revision: D94658492 fbshipit-source-id: 1587151670803ace0eae2ee91883fe4be72bfa27
1 parent cfdacac commit 5cd2cbd

14 files changed

Lines changed: 1892 additions & 16 deletions

File tree

enums.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@
7676
# Absolute nodes will resolve percentages against the inner size of
7777
# their containing node, not the padding box
7878
("AbsolutePercentAgainstInnerSize", 1 << 2),
79+
# Applies a FitContent constraint in the main axis during flex basis
80+
# computation for non-measure container nodes
81+
("FlexBasisFitContentInMainAxis", 1 << 3),
7982
# Enable all incorrect behavior (preserve compatibility)
8083
("All", 0x7FFFFFFF),
8184
# Enable all errata except for "StretchFlexBasis" (Defaults behavior
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<!-- Container child with content overflowing definite column parent.
2+
Without FlexBasisFitContentInMainAxis errata (corrected behavior),
3+
flex basis uses MaxContent: child height = content height (500),
4+
not capped by parent's available height. -->
5+
<div id="container_child_overflows_definite_parent_column"
6+
style="width: 200px; height: 300px;">
7+
<div>
8+
<div style="height: 500px; width: 50px;"></div>
9+
</div>
10+
</div>
11+
12+
<!-- Row variant: container child overflows definite row parent. -->
13+
<div id="container_child_overflows_definite_parent_row"
14+
style="width: 300px; height: 200px; flex-direction: row;">
15+
<div>
16+
<div style="width: 500px; height: 50px;"></div>
17+
</div>
18+
</div>
19+
20+
<!-- Container child content fits within parent bounds.
21+
Same behavior with and without errata (no capping needed). -->
22+
<div id="container_child_within_bounds_column"
23+
style="width: 200px; height: 300px;">
24+
<div>
25+
<div style="height: 100px; width: 50px;"></div>
26+
</div>
27+
</div>
28+
29+
<!-- Multiple container children, both overflowing definite column parent. -->
30+
<div id="multiple_container_children_overflow_column"
31+
style="width: 200px; height: 300px;">
32+
<div>
33+
<div style="height: 400px;"></div>
34+
</div>
35+
<div>
36+
<div style="height: 500px;"></div>
37+
</div>
38+
</div>
39+
40+
<!-- Scroll container: children always use MaxContent in main axis.
41+
Same behavior with and without errata. -->
42+
<div id="scroll_container_column"
43+
style="width: 200px; height: 300px; overflow: scroll;">
44+
<div>
45+
<div style="height: 500px;"></div>
46+
</div>
47+
</div>
48+
49+
<!-- Mix of explicit-height child and overflowing container child. -->
50+
<div id="explicit_and_container_children_column"
51+
style="width: 200px; height: 300px;">
52+
<div style="height: 100px;"></div>
53+
<div>
54+
<div style="height: 500px;"></div>
55+
</div>
56+
</div>
57+
58+
<!-- Items with flex-basis inside a scroll container's auto-height
59+
content container. Without errata, flex-basis is respected even
60+
when the content container's main axis size is indefinite (NaN).
61+
With errata, flex-basis would be ignored. -->
62+
<div id="flex_basis_in_scroll_content_container"
63+
style="width: 200px; height: 300px; overflow: scroll;">
64+
<div>
65+
<div style="flex-basis: 200px;"></div>
66+
<div style="flex-basis: 300px;"></div>
67+
</div>
68+
</div>

java/com/facebook/yoga/YogaErrata.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public enum YogaErrata {
1414
STRETCH_FLEX_BASIS(1),
1515
ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING(2),
1616
ABSOLUTE_PERCENT_AGAINST_INNER_SIZE(4),
17+
FLEX_BASIS_FIT_CONTENT_IN_MAIN_AXIS(8),
1718
ALL(2147483647),
1819
CLASSIC(2147483646);
1920

@@ -33,6 +34,7 @@ public static YogaErrata fromInt(int value) {
3334
case 1: return STRETCH_FLEX_BASIS;
3435
case 2: return ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING;
3536
case 4: return ABSOLUTE_PERCENT_AGAINST_INNER_SIZE;
37+
case 8: return FLEX_BASIS_FIT_CONTENT_IN_MAIN_AXIS;
3638
case 2147483647: return ALL;
3739
case 2147483646: return CLASSIC;
3840
default: throw new IllegalArgumentException("Unknown enum value: " + value);

0 commit comments

Comments
 (0)