Commit 5cd2cbd
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: 1587151670803ace0eae2ee91883fe4be72bfa271 parent cfdacac commit 5cd2cbd
14 files changed
Lines changed: 1892 additions & 16 deletions
File tree
- gentest/fixtures
- javascript
- src/generated
- tests/generated
- java
- com/facebook/yoga
- tests/generated/com/facebook/yoga
- tests
- generated
- yoga
- algorithm
- enums
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
76 | 76 | | |
77 | 77 | | |
78 | 78 | | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
79 | 82 | | |
80 | 83 | | |
81 | 84 | | |
| |||
Lines changed: 68 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 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 | | |
| |||
0 commit comments