Commit 8df90ef
Add fixYogaFlexBasisFitContentInMainAxis flag to avoid unnecessary re-measurement
Summary:
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).
Differential Revision: D946584921 parent 851e5ae commit 8df90ef
27 files changed
Lines changed: 286 additions & 68 deletions
File tree
- packages/react-native
- ReactAndroid/src/main
- java/com/facebook
- react/internal/featureflags
- yoga
- jni/react/featureflags
- ReactCommon
- react
- featureflags
- nativemodule/featureflags
- renderer/components/view
- yoga/yoga
- algorithm
- enums
- scripts/featureflags
- src/private
- __tests__/utilities/__tests__
- featureflags
- specs
Lines changed: 7 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
378 | 378 | | |
379 | 379 | | |
380 | 380 | | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
381 | 387 | | |
382 | 388 | | |
383 | 389 | | |
| |||
Lines changed: 11 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| 81 | + | |
81 | 82 | | |
82 | 83 | | |
83 | 84 | | |
| |||
629 | 630 | | |
630 | 631 | | |
631 | 632 | | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
632 | 642 | | |
633 | 643 | | |
634 | 644 | | |
| |||
Lines changed: 3 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
| 147 | + | |
| 148 | + | |
147 | 149 | | |
148 | 150 | | |
149 | 151 | | |
| |||
Lines changed: 3 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
139 | 139 | | |
140 | 140 | | |
141 | 141 | | |
| 142 | + | |
| 143 | + | |
142 | 144 | | |
143 | 145 | | |
144 | 146 | | |
| |||
Lines changed: 12 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
| 85 | + | |
85 | 86 | | |
86 | 87 | | |
87 | 88 | | |
| |||
691 | 692 | | |
692 | 693 | | |
693 | 694 | | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
694 | 705 | | |
695 | 706 | | |
696 | 707 | | |
| |||
Lines changed: 3 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
139 | 139 | | |
140 | 140 | | |
141 | 141 | | |
| 142 | + | |
| 143 | + | |
142 | 144 | | |
143 | 145 | | |
144 | 146 | | |
| |||
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
17 | 18 | | |
18 | 19 | | |
19 | 20 | | |
| |||
33 | 34 | | |
34 | 35 | | |
35 | 36 | | |
| 37 | + | |
36 | 38 | | |
37 | 39 | | |
38 | 40 | | |
| |||
Lines changed: 15 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
387 | 387 | | |
388 | 388 | | |
389 | 389 | | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
390 | 396 | | |
391 | 397 | | |
392 | 398 | | |
| |||
849 | 855 | | |
850 | 856 | | |
851 | 857 | | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
| 861 | + | |
| 862 | + | |
852 | 863 | | |
853 | 864 | | |
854 | 865 | | |
| |||
1194 | 1205 | | |
1195 | 1206 | | |
1196 | 1207 | | |
| 1208 | + | |
| 1209 | + | |
| 1210 | + | |
1197 | 1211 | | |
1198 | 1212 | | |
1199 | 1213 | | |
| |||
Lines changed: 4 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
204 | 204 | | |
205 | 205 | | |
206 | 206 | | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
207 | 210 | | |
208 | 211 | | |
209 | 212 | | |
| |||
Lines changed: 5 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
258 | 258 | | |
259 | 259 | | |
260 | 260 | | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
261 | 265 | | |
262 | 266 | | |
263 | 267 | | |
| |||
0 commit comments