From abaada933dd8e16d1becb50722af2749d3da3b33 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 22 Jul 2026 11:54:09 -0700 Subject: [PATCH] Fix flex-growing content sizing in (#1997) Summary: X-link: https://github.com/react/react-native/pull/57601 Preserve available height for flex-growing subtrees nested inside scroll content. Apply optimized flex-basis measurement only when a conservative proof shows that the subtree is height-independent and connected to the nearest scroll ancestor through column-stretch layout edges. Keep authored flex-basis, overflow, padding, absolute-positioning, wrapping, percentage-owner, and box-sizing semantics intact across layout and measurement passes. Gate `boxSizing` cache invalidation with the same Yoga feature so disabling it preserves legacy caching behavior. Add Fantom coverage for native `boxSizing` updates and for rerendering after both `flexBasis` and intrinsic height change. Verify the expected behavior across both `fixYogaFlexBasisFitContentInMainAxis` states. Changelog: [internal] Reviewed By: rubennorte Differential Revision: D112353599 --- .../fixtures/YGFlexBasisFitContentTest.html | 13 - .../yoga/YGFlexBasisFitContentTest.java | 70 +---- .../YGFlexBasisFitContentTest.test.ts | 67 +---- tests/generated/YGFlexBasisFitContentTest.cpp | 71 +---- yoga/algorithm/CalculateLayout.cpp | 282 +++++++++++++----- 5 files changed, 210 insertions(+), 293 deletions(-) diff --git a/gentest/fixtures/YGFlexBasisFitContentTest.html b/gentest/fixtures/YGFlexBasisFitContentTest.html index a9176e4ce6..1a1479c1ac 100644 --- a/gentest/fixtures/YGFlexBasisFitContentTest.html +++ b/gentest/fixtures/YGFlexBasisFitContentTest.html @@ -53,16 +53,3 @@
- - -
-
-
-
-
-
diff --git a/java/tests/generated/com/facebook/yoga/YGFlexBasisFitContentTest.java b/java/tests/generated/com/facebook/yoga/YGFlexBasisFitContentTest.java index 3941362bb5..35cb154122 100644 --- a/java/tests/generated/com/facebook/yoga/YGFlexBasisFitContentTest.java +++ b/java/tests/generated/com/facebook/yoga/YGFlexBasisFitContentTest.java @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<43053b7b4f9b08ef644e0bb63c44e78f>> + * @generated SignedSource<<94d885664d3bbf8c72280e1b61102fce>> * generated by gentest/src/GentestDriver.ts from gentest/fixtures/YGFlexBasisFitContentTest.html */ @@ -385,74 +385,6 @@ public void test_explicit_and_container_children_column() { assertEquals(500f, root_child1_child0.getLayoutHeight(), 0.0f); } - @Test - public void test_flex_basis_in_scroll_content_container() { - YogaConfig config = YogaConfigFactory.create(); - config.setExperimentalFeatureEnabled(YogaExperimentalFeature.FIX_FLEX_BASIS_FIT_CONTENT, true); - - final YogaNode root = createNode(config); - root.setPositionType(YogaPositionType.ABSOLUTE); - root.setWidth(200f); - root.setHeight(300f); - root.setOverflow(YogaOverflow.SCROLL); - - final YogaNode root_child0 = createNode(config); - root.addChildAt(root_child0, 0); - - final YogaNode root_child0_child0 = createNode(config); - root_child0_child0.setFlexBasis(200f); - root_child0.addChildAt(root_child0_child0, 0); - - final YogaNode root_child0_child1 = createNode(config); - root_child0_child1.setFlexBasis(300f); - root_child0.addChildAt(root_child0_child1, 1); - root.setDirection(YogaDirection.LTR); - root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); - - assertEquals(0f, root.getLayoutX(), 0.0f); - assertEquals(0f, root.getLayoutY(), 0.0f); - assertEquals(200f, root.getLayoutWidth(), 0.0f); - assertEquals(300f, root.getLayoutHeight(), 0.0f); - - assertEquals(0f, root_child0.getLayoutX(), 0.0f); - assertEquals(0f, root_child0.getLayoutY(), 0.0f); - assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); - assertEquals(500f, root_child0.getLayoutHeight(), 0.0f); - - assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); - assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); - assertEquals(200f, root_child0_child0.getLayoutWidth(), 0.0f); - assertEquals(200f, root_child0_child0.getLayoutHeight(), 0.0f); - - assertEquals(0f, root_child0_child1.getLayoutX(), 0.0f); - assertEquals(200f, root_child0_child1.getLayoutY(), 0.0f); - assertEquals(200f, root_child0_child1.getLayoutWidth(), 0.0f); - assertEquals(300f, root_child0_child1.getLayoutHeight(), 0.0f); - - root.setDirection(YogaDirection.RTL); - root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); - - assertEquals(0f, root.getLayoutX(), 0.0f); - assertEquals(0f, root.getLayoutY(), 0.0f); - assertEquals(200f, root.getLayoutWidth(), 0.0f); - assertEquals(300f, root.getLayoutHeight(), 0.0f); - - assertEquals(0f, root_child0.getLayoutX(), 0.0f); - assertEquals(0f, root_child0.getLayoutY(), 0.0f); - assertEquals(200f, root_child0.getLayoutWidth(), 0.0f); - assertEquals(500f, root_child0.getLayoutHeight(), 0.0f); - - assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); - assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); - assertEquals(200f, root_child0_child0.getLayoutWidth(), 0.0f); - assertEquals(200f, root_child0_child0.getLayoutHeight(), 0.0f); - - assertEquals(0f, root_child0_child1.getLayoutX(), 0.0f); - assertEquals(200f, root_child0_child1.getLayoutY(), 0.0f); - assertEquals(200f, root_child0_child1.getLayoutWidth(), 0.0f); - assertEquals(300f, root_child0_child1.getLayoutHeight(), 0.0f); - } - private YogaNode createNode(YogaConfig config) { return mNodeFactory.create(config); } diff --git a/javascript/tests/generated/YGFlexBasisFitContentTest.test.ts b/javascript/tests/generated/YGFlexBasisFitContentTest.test.ts index 1606c49697..906e8925f5 100644 --- a/javascript/tests/generated/YGFlexBasisFitContentTest.test.ts +++ b/javascript/tests/generated/YGFlexBasisFitContentTest.test.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<52cb2c57af8b80a23fa8217c15d0000a>> + * @generated SignedSource<<8265f8738bd09131df2e6ef29f0e34d7>> * generated by gentest/src/GentestDriver.ts from gentest/fixtures/YGFlexBasisFitContentTest.html */ @@ -362,68 +362,3 @@ test('explicit_and_container_children_column', () => { expect(root_child1_child0.getComputedWidth()).toBe(200); expect(root_child1_child0.getComputedHeight()).toBe(500); }); -test('flex_basis_in_scroll_content_container', () => { - const config = Yoga.Config.create(); - - config.setExperimentalFeatureEnabled(ExperimentalFeature.FixFlexBasisFitContent, true); - - const root = Yoga.Node.create(config); - root.setPositionType(PositionType.Absolute); - root.setWidth(200); - root.setHeight(300); - root.setOverflow(Overflow.Scroll); - - const root_child0 = Yoga.Node.create(config); - root.insertChild(root_child0, 0); - - const root_child0_child0 = Yoga.Node.create(config); - root_child0_child0.setFlexBasis(200); - root_child0.insertChild(root_child0_child0, 0); - - const root_child0_child1 = Yoga.Node.create(config); - root_child0_child1.setFlexBasis(300); - root_child0.insertChild(root_child0_child1, 1); - root.calculateLayout(undefined, undefined, Direction.LTR); - - expect(root.getComputedLeft()).toBe(0); - expect(root.getComputedTop()).toBe(0); - expect(root.getComputedWidth()).toBe(200); - expect(root.getComputedHeight()).toBe(300); - - expect(root_child0.getComputedLeft()).toBe(0); - expect(root_child0.getComputedTop()).toBe(0); - expect(root_child0.getComputedWidth()).toBe(200); - expect(root_child0.getComputedHeight()).toBe(500); - - expect(root_child0_child0.getComputedLeft()).toBe(0); - expect(root_child0_child0.getComputedTop()).toBe(0); - expect(root_child0_child0.getComputedWidth()).toBe(200); - expect(root_child0_child0.getComputedHeight()).toBe(200); - - expect(root_child0_child1.getComputedLeft()).toBe(0); - expect(root_child0_child1.getComputedTop()).toBe(200); - expect(root_child0_child1.getComputedWidth()).toBe(200); - expect(root_child0_child1.getComputedHeight()).toBe(300); - - root.calculateLayout(undefined, undefined, Direction.RTL); - - expect(root.getComputedLeft()).toBe(0); - expect(root.getComputedTop()).toBe(0); - expect(root.getComputedWidth()).toBe(200); - expect(root.getComputedHeight()).toBe(300); - - expect(root_child0.getComputedLeft()).toBe(0); - expect(root_child0.getComputedTop()).toBe(0); - expect(root_child0.getComputedWidth()).toBe(200); - expect(root_child0.getComputedHeight()).toBe(500); - - expect(root_child0_child0.getComputedLeft()).toBe(0); - expect(root_child0_child0.getComputedTop()).toBe(0); - expect(root_child0_child0.getComputedWidth()).toBe(200); - expect(root_child0_child0.getComputedHeight()).toBe(200); - - expect(root_child0_child1.getComputedLeft()).toBe(0); - expect(root_child0_child1.getComputedTop()).toBe(200); - expect(root_child0_child1.getComputedWidth()).toBe(200); - expect(root_child0_child1.getComputedHeight()).toBe(300); -}); diff --git a/tests/generated/YGFlexBasisFitContentTest.cpp b/tests/generated/YGFlexBasisFitContentTest.cpp index b1dea09665..2d404f3e2f 100644 --- a/tests/generated/YGFlexBasisFitContentTest.cpp +++ b/tests/generated/YGFlexBasisFitContentTest.cpp @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * clang-format off - * @generated SignedSource<> + * @generated SignedSource<<19f491407474ad588c9bece89d20c226>> * generated by gentest/src/GentestDriver.ts from gentest/fixtures/YGFlexBasisFitContentTest.html */ @@ -376,72 +376,3 @@ TEST(YogaTest, explicit_and_container_children_column) { YGConfigFree(config); } - -TEST(YogaTest, flex_basis_in_scroll_content_container) { - YGConfigRef config = YGConfigNew(); - YGConfigSetExperimentalFeatureEnabled(config, YGExperimentalFeatureFixFlexBasisFitContent, true); - - YGNodeRef root = YGNodeNewWithConfig(config); - YGNodeStyleSetPositionType(root, YGPositionTypeAbsolute); - YGNodeStyleSetWidth(root, 200); - YGNodeStyleSetHeight(root, 300); - YGNodeStyleSetOverflow(root, YGOverflowScroll); - - YGNodeRef root_child0 = YGNodeNewWithConfig(config); - YGNodeInsertChild(root, root_child0, 0); - - YGNodeRef root_child0_child0 = YGNodeNewWithConfig(config); - YGNodeStyleSetFlexBasis(root_child0_child0, 200); - YGNodeInsertChild(root_child0, root_child0_child0, 0); - - YGNodeRef root_child0_child1 = YGNodeNewWithConfig(config); - YGNodeStyleSetFlexBasis(root_child0_child1, 300); - YGNodeInsertChild(root_child0, root_child0_child1, 1); - YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); - - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); - ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); - ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); - - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); - ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); - ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); - - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); - ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); - ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root_child0_child0)); - - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child1)); - ASSERT_FLOAT_EQ(200, YGNodeLayoutGetTop(root_child0_child1)); - ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child1)); - ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root_child0_child1)); - - YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); - - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); - ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); - ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root)); - - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); - ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); - ASSERT_FLOAT_EQ(500, YGNodeLayoutGetHeight(root_child0)); - - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); - ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); - ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root_child0_child0)); - - ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child1)); - ASSERT_FLOAT_EQ(200, YGNodeLayoutGetTop(root_child0_child1)); - ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child1)); - ASSERT_FLOAT_EQ(300, YGNodeLayoutGetHeight(root_child0_child1)); - - YGNodeFreeRecursive(root); - - YGConfigFree(config); -} diff --git a/yoga/algorithm/CalculateLayout.cpp b/yoga/algorithm/CalculateLayout.cpp index e9d62492da..89fbe3cbbb 100644 --- a/yoga/algorithm/CalculateLayout.cpp +++ b/yoga/algorithm/CalculateLayout.cpp @@ -6,9 +6,11 @@ */ #include +#include #include #include #include +#include #include #include @@ -35,6 +37,172 @@ namespace facebook::yoga { std::atomic gCurrentGenerationCount(0); +static bool hasAutoHorizontalMargin(const Style& style) { + for (const auto direction : {Direction::LTR, Direction::RTL}) { + if (style.flexStartMarginIsAuto(FlexDirection::Row, direction) || + style.flexEndMarginIsAuto(FlexDirection::Row, direction)) { + return true; + } + } + return false; +} + +static bool isColumnStretchEdge( + const yoga::Node* const owner, + const yoga::Node* const child) { + if (owner == nullptr || child == nullptr) { + return false; + } + const auto& ownerStyle = owner->style(); + const auto& childStyle = child->style(); + const auto childWidth = child->getProcessedDimension(Dimension::Width); + return ownerStyle.display() == Display::Flex && + isColumn(ownerStyle.flexDirection()) && + ownerStyle.flexWrap() == Wrap::NoWrap && + childStyle.positionType() != PositionType::Absolute && + !childStyle.aspectRatio().isDefined() && + (childWidth.isAuto() || childWidth.isUndefined()) && + !hasAutoHorizontalMargin(childStyle) && + resolveChildAlignment(owner, child) == Align::Stretch; +} + +static bool isInColumnStretchScrollSubtree(const yoga::Node* const node) { + auto current = node; + while (current != nullptr) { + auto owner = current->getOwner(); + while (owner != nullptr && owner->style().display() == Display::Contents) { + owner = owner->getOwner(); + } + if (owner == nullptr || !isColumnStretchEdge(owner, current)) { + return false; + } + if (owner->style().overflow() == Overflow::Scroll) { + return true; + } + current = owner; + } + return false; +} + +static bool isNonZeroLength(const Style::Length& length) { + return length.isAuto() || + (length.value().isDefined() && length.value().unwrap() != 0.0f); +} + +static bool hasNonZeroVerticalSpacing(const Style& style) { + constexpr std::array verticalEdges = { + Edge::Top, Edge::Bottom, Edge::Vertical, Edge::All}; + for (const auto edge : verticalEdges) { + if (isNonZeroLength(style.margin(edge)) || + isNonZeroLength(style.padding(edge)) || + isNonZeroLength(style.border(edge))) { + return true; + } + } + return false; +} + +static bool hasPercentageLength(const Style& style) { + constexpr std::array edges = { + Edge::Left, + Edge::Top, + Edge::Right, + Edge::Bottom, + Edge::Start, + Edge::End, + Edge::Horizontal, + Edge::Vertical, + Edge::All}; + for (const auto edge : edges) { + if (style.margin(edge).isPercent() || style.position(edge).isPercent() || + style.padding(edge).isPercent() || style.border(edge).isPercent()) { + return true; + } + } + + constexpr std::array dimensions = { + Dimension::Width, Dimension::Height}; + for (const auto dimension : dimensions) { + if (style.dimension(dimension).isPercent() || + style.minDimension(dimension).isPercent() || + style.maxDimension(dimension).isPercent()) { + return true; + } + } + + return style.flexBasis().isPercent() || + style.gap(Gutter::Column).isPercent() || + style.gap(Gutter::Row).isPercent() || style.gap(Gutter::All).isPercent(); +} + +static bool hasNonZeroFlex(const yoga::Node& node) { + const auto& style = node.style(); + const auto flex = style.flex(); + const auto flexGrow = style.flexGrow(); + const auto flexShrink = style.flexShrink(); + const auto config = node.getConfig(); + const bool canGrow = flexGrow.isDefined() + ? flexGrow.unwrap() != 0.0f + : flex.isDefined() && flex.unwrap() > 0.0f; + const bool canShrink = flexShrink.isDefined() + ? flexShrink.unwrap() != 0.0f + : (config != nullptr && config->useWebDefaults()) || + (flex.isDefined() && flex.unwrap() < 0.0f); + return canGrow || canShrink; +} + +static bool isHeightFitContentIndependent(const yoga::Node& node) { + const auto& style = node.style(); + const auto height = style.dimension(Dimension::Height); + const auto flexBasis = style.flexBasis(); + const bool hasRelativePercentPosition = + style.position(Edge::Top).isPercent() || + style.position(Edge::Bottom).isPercent() || + style.position(Edge::Vertical).isPercent() || + style.position(Edge::All).isPercent(); + return !node.hasMeasureFunc() && !node.hasMinContentMeasureFunc() && + !node.hasBaselineFunc() && !node.isReferenceBaseline() && + (height.isAuto() || height.isUndefined()) && + style.minDimension(Dimension::Height).isUndefined() && + style.maxDimension(Dimension::Height).isUndefined() && + (flexBasis.isAuto() || flexBasis.isUndefined()) && + !hasNonZeroFlex(node) && style.boxSizing() == BoxSizing::BorderBox && + !style.aspectRatio().isDefined() && + style.positionType() != PositionType::Absolute && + style.overflow() != Overflow::Scroll && + style.display() == Display::Flex && isColumn(style.flexDirection()) && + style.alignItems() == Align::Stretch && + (style.alignSelf() == Align::Auto || + style.alignSelf() == Align::Stretch) && + style.justifyContent() == Justify::FlexStart && + style.flexWrap() == Wrap::NoWrap && !style.gap(Gutter::All).isDefined() && + !style.gap(Gutter::Row).isDefined() && !hasRelativePercentPosition && + !hasNonZeroVerticalSpacing(style) && !hasPercentageLength(style); +} + +static bool canSkipHeightFitContent(const yoga::Node* const root) { + if (root == nullptr) { + return false; + } + + constexpr std::size_t maxPendingNodes = 64; + std::array stack{root}; + std::size_t stackSize = 1; + while (stackSize > 0) { + const auto node = stack[--stackSize]; + if (node == nullptr || !isHeightFitContentIndependent(*node)) { + return false; + } + for (const auto child : node->getLayoutChildren()) { + if (stackSize == stack.size()) { + return false; + } + stack[stackSize++] = child; + } + } + return true; +} + void constrainMaxSizeForMode( const yoga::Node* node, Direction direction, @@ -98,12 +266,8 @@ static void computeFlexBasisForChild( node->getConfig()->isExperimentalFeatureEnabled( ExperimentalFeature::FixFlexBasisFitContent); - bool useResolvedFlexBasis = + const bool useResolvedFlexBasis = resolvedFlexBasis.isDefined() && yoga::isDefined(mainAxisSize); - if (fixFlexBasisFitContent && resolvedFlexBasis.isDefined() && - resolvedFlexBasis.unwrap() > 0) { - useResolvedFlexBasis = true; - } if (useResolvedFlexBasis) { if (child->getLayout().computedFlexBasis.isUndefined() || @@ -175,32 +339,27 @@ static void computeFlexBasisForChild( } } - // For height in the main axis (column direction): when the - // FixFlexBasisFitContent feature is enabled, skip FitContent for - // non-measure container children inside scroll subtrees. This makes the - // flex basis independent of content-determined heights, preventing - // unnecessary re-measurement cascades when a sibling changes size in a - // ScrollView, while preserving viewport bounds for wrappers outside the - // scroll subtree. - // - // We only optimize the height (column) axis because text wrapping depends - // on width constraints propagating through container nodes. Removing - // FitContent from the width axis would cause text inside nested - // containers to stop wrapping. - bool applyHeightFitContent = - isMainAxisRow || node->style().overflow() != Overflow::Scroll; + // A zero-intrinsic-height column subtree has the same layout with an + // unbounded height, allowing its measurement cache to survive unrelated + // size changes elsewhere in a vertical scroll subtree. + const bool parentDoesNotScroll = + node != nullptr && node->style().overflow() != Overflow::Scroll; + bool applyHeightFitContent = isMainAxisRow || parentDoesNotScroll; if (fixFlexBasisFitContent) { - bool nodeHasScrollAncestor = false; - for (auto owner = node->getOwner(); owner != nullptr; - owner = owner->getOwner()) { - if (owner->style().overflow() == Overflow::Scroll) { - nodeHasScrollAncestor = true; - break; - } + const bool childHadOverflow = child != nullptr && child->isDirty() && + child->getLayout().hadOverflow(); + const bool hasHeightIndependentSubtree = node != nullptr && + child != nullptr && !isMainAxisRow && parentDoesNotScroll && + yoga::isUndefined(childHeight) && yoga::isDefined(height) && + isColumnStretchEdge(node, child) && + isInColumnStretchScrollSubtree(node) && + canSkipHeightFitContent(child); + if (hasHeightIndependentSubtree && childHadOverflow) { + child->setLayoutHadOverflow(false); + } + if (hasHeightIndependentSubtree) { + applyHeightFitContent = false; } - applyHeightFitContent = isMainAxisRow || - ((child->hasMeasureFunc() || !nodeHasScrollAncestor) && - node->style().overflow() != Overflow::Scroll); } if (applyHeightFitContent && yoga::isUndefined(childHeight) && yoga::isDefined(height)) { @@ -1565,6 +1724,12 @@ static void calculateLayoutImpl( // Set the resolved resolution in the node's layout. const Direction direction = node->resolveDirection(ownerDirection); node->setLayoutDirection(direction); + const bool fixFlexBasisFitContent = + node->getConfig()->isExperimentalFeatureEnabled( + ExperimentalFeature::FixFlexBasisFitContent); + if (fixFlexBasisFitContent && performLayout) { + node->setLayoutHadOverflow(false); + } const FlexDirection flexRowDirection = resolveDirection(FlexDirection::Row, direction); @@ -1680,9 +1845,9 @@ static void calculateLayoutImpl( // At this point we know we're going to perform work. Ensure that each child // has a mutable copy. node->cloneChildrenIfNeeded(); - // Reset layout flags, as they could have changed. - node->setLayoutHadOverflow(false); - + if (!fixFlexBasisFitContent || !performLayout) { + node->setLayoutHadOverflow(false); + } // Clean and update all display: contents nodes with a direct path to the // current node as they will not be traversed cleanupContentsNodesRecursively(node, performLayout); @@ -1741,53 +1906,14 @@ static void calculateLayoutImpl( // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM - // When this node is measured with MaxContent (FixFlexBasisFitContent - // behavior), availableInnerHeight is NaN. - // To preserve percentage resolution for descendants, derive a definite - // owner-size from the parent-provided ownerHeight. - float ownerWidthForChildren = availableInnerWidth; - float ownerHeightForChildren = availableInnerHeight; - - if (node->getConfig()->isExperimentalFeatureEnabled( - ExperimentalFeature::FixFlexBasisFitContent)) { - const auto* owner = node->getOwner(); - const bool isChildOfScrollContainer = - owner != nullptr && owner->style().overflow() == Overflow::Scroll; - - if (!isChildOfScrollContainer) { - if (yoga::isUndefined(ownerWidthForChildren) && - yoga::isDefined(ownerWidth)) { - ownerWidthForChildren = calculateAvailableInnerDimension( - node, - direction, - Dimension::Width, - ownerWidth - marginAxisRow, - paddingAndBorderAxisRow, - ownerWidth, - ownerWidth); - } - if (yoga::isUndefined(ownerHeightForChildren) && - yoga::isDefined(ownerHeight)) { - ownerHeightForChildren = calculateAvailableInnerDimension( - node, - direction, - Dimension::Height, - ownerHeight - marginAxisColumn, - paddingAndBorderAxisColumn, - ownerHeight, - ownerWidth); - } - } - } - // Computed basis + margins + gap float totalMainDim = 0; totalMainDim += computeFlexBasisForChildren( node, availableInnerWidth, availableInnerHeight, - ownerWidthForChildren, - ownerHeightForChildren, + availableInnerWidth, + availableInnerHeight, widthSizingMode, heightSizingMode, direction, @@ -2724,7 +2850,8 @@ void calculateLayout( // Increment the generation count. This will force the recursive routine to // visit all dirty nodes at least once. Subsequent visits will be skipped if // the input parameters don't change. - gCurrentGenerationCount.fetch_add(1, std::memory_order_relaxed); + const uint32_t currentGenerationCount = + gCurrentGenerationCount.fetch_add(1, std::memory_order_relaxed) + 1; node->processDimensions(); const Direction direction = node->resolveDirection(ownerDirection); float width = YGUndefined; @@ -2781,6 +2908,11 @@ void calculateLayout( heightSizingMode = yoga::isUndefined(height) ? SizingMode::MaxContent : SizingMode::StretchFit; } + const uint32_t generationCount = + node->getConfig()->isExperimentalFeatureEnabled( + ExperimentalFeature::FixFlexBasisFitContent) + ? currentGenerationCount + : gCurrentGenerationCount.load(std::memory_order_relaxed); if (calculateLayoutInternal( node, width, @@ -2794,7 +2926,7 @@ void calculateLayout( LayoutPassReason::kInitial, markerData, 0, // tree root - gCurrentGenerationCount.load(std::memory_order_relaxed))) { + generationCount)) { node->setPosition(node->getLayout().direction(), ownerWidth, ownerHeight); roundLayoutResultsToPixelGrid(node, 0.0f, 0.0f); }