Changeset 277222 in webkit


Ignore:
Timestamp:
May 7, 2021 6:48:22 PM (15 months ago)
Author:
Alan Bujtas
Message:

[css-flexbox] Flex item construction may affect sibling flex item height computation
https://bugs.webkit.org/show_bug.cgi?id=225489

Reviewed by Sergio Villar Senin.

Source/WebCore:

Flex item construction triggers layout both on the flex item and on its descendants (see constructFlexItem).
During this layout a percent height descendant may indirectly set an incorrect value on the flex container's
m_hasDefiniteHeight (this is due to the odd way we choose to resolve percent height values on the ancestor chain,
see setOverridingContainingBlockContentLogicalHeight(WTF::nullopt)).
Now this incorrect m_hasDefiniteHeight value (Indefinite) causes the next sibling's (also) percent height
resolve fail as it tells the flex item that the flex container can't help with resolving the percent height value.
As the result we end up with an incorrect, 0px height value for this sibling.
e.g.
<div style="height: 100px; display: flex; flex-direction: column;">

<div id=flexItemA style="height: 50px;"><div style="height: 100%;"></div></div>
<div id=flexItemB style="height: 50%;"></div>

</div>
By the time we get to the construction of flexItemB, the RenderFlexibleBox's (flex container) m_hasDefiniteHeight
is already (and incorrectly) set to Indefinite as the result of us trying to resolve flexItemA's descendant height percentage.
This Indefinite value makes the flexItemB's height resolution fail as we believe that the flex container has indefinite height
e.g "height: auto", while it is clearly resolvable (50% of 100px).
Now if we flip the order and flexItemB comes first, the test case passes fine.
It is very unfortunate that some descendant height resolving process can trigger a state change on the ancestor RenderFlexibleBox, but
fixing it would certainly require some substantial architectural change (e.g. pushing down the constraints to the geometry functions instead of
walking the containing block chain).
In this patch, we just scope m_hasDefiniteHeight so that the RenderFlexibleBox's state is not effected by the flex item construction.

Test: fast/flexbox/flex-column-with-percent-height-descendants.html

  • rendering/RenderFlexibleBox.cpp:

(WebCore::RenderFlexibleBox::layoutFlexItems):

LayoutTests:

  • fast/flexbox/flex-column-with-percent-height-descendants-expected.html: Added.
  • fast/flexbox/flex-column-with-percent-height-descendants.html: Added.
Location:
trunk
Files:
2 added
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r277216 r277222  
     12021-05-07  Zalan Bujtas  <zalan@apple.com>
     2
     3        [css-flexbox] Flex item construction may affect sibling flex item height computation
     4        https://bugs.webkit.org/show_bug.cgi?id=225489
     5
     6        Reviewed by Sergio Villar Senin.
     7
     8        * fast/flexbox/flex-column-with-percent-height-descendants-expected.html: Added.
     9        * fast/flexbox/flex-column-with-percent-height-descendants.html: Added.
     10
    1112021-05-07  Amir Mark Jr  <amir_mark@apple.com>
    212
  • trunk/Source/WebCore/ChangeLog

    r277203 r277222  
     12021-05-07  Zalan Bujtas  <zalan@apple.com>
     2
     3        [css-flexbox] Flex item construction may affect sibling flex item height computation
     4        https://bugs.webkit.org/show_bug.cgi?id=225489
     5
     6        Reviewed by Sergio Villar Senin.
     7
     8        Flex item construction triggers layout both on the flex item and on its descendants (see constructFlexItem).
     9        During this layout a percent height descendant may indirectly set an incorrect value on the flex container's
     10        m_hasDefiniteHeight (this is due to the odd way we choose to resolve percent height values on the ancestor chain,
     11        see setOverridingContainingBlockContentLogicalHeight(WTF::nullopt)).
     12        Now this incorrect m_hasDefiniteHeight value (Indefinite) causes the next sibling's (also) percent height
     13        resolve fail as it tells the flex item that the flex container can't help with resolving the percent height value.
     14        As the result we end up with an incorrect, 0px height value for this sibling.
     15        e.g.
     16        <div style="height: 100px; display: flex; flex-direction: column;">
     17          <div id=flexItemA style="height: 50px;"><div style="height: 100%;"></div></div>
     18          <div id=flexItemB style="height: 50%;"></div>
     19        </div>
     20        By the time we get to the construction of flexItemB, the RenderFlexibleBox's (flex container) m_hasDefiniteHeight
     21        is already (and incorrectly) set to Indefinite as the result of us trying to resolve flexItemA's descendant height percentage.
     22        This Indefinite value makes the flexItemB's height resolution fail as we believe that the flex container has indefinite height
     23        e.g "height: auto", while it is clearly resolvable (50% of 100px).
     24        Now if we flip the order and flexItemB comes first, the test case passes fine.
     25        It is very unfortunate that some descendant height resolving process can trigger a state change on the ancestor RenderFlexibleBox, but
     26        fixing it would certainly require some substantial architectural change (e.g. pushing down the constraints to the geometry functions instead of
     27        walking the containing block chain).
     28        In this patch, we just scope m_hasDefiniteHeight so that the RenderFlexibleBox's state is not effected by the flex item construction.
     29
     30        Test: fast/flexbox/flex-column-with-percent-height-descendants.html
     31
     32        * rendering/RenderFlexibleBox.cpp:
     33        (WebCore::RenderFlexibleBox::layoutFlexItems):
     34
    1352021-05-07  Devin Rousso  <drousso@apple.com>
    236
  • trunk/Source/WebCore/rendering/RenderFlexibleBox.cpp

    r276835 r277222  
    269269    m_relaidOutChildren.clear();
    270270   
    271     bool oldInLayout = m_inLayout;
    272     m_inLayout = true;
    273    
    274271    if (recomputeLogicalWidth())
    275272        relayoutChildren = true;
     
    322319
    323320    clearNeedsLayout();
    324    
    325     m_inLayout = oldInLayout;
    326321}
    327322
     
    846841        return false;
    847842    bool definite = child.computePercentageLogicalHeight(flexBasis, updateDescendants).hasValue();
    848     if (m_inLayout && (isHorizontalWritingMode() == child.isHorizontalWritingMode())) {
     843    if (!m_inFlexItemConstruction && (isHorizontalWritingMode() == child.isHorizontalWritingMode())) {
    849844        // We can reach this code even while we're not laying ourselves out, such
    850845        // as from mainSizeForPercentageResolution.
     
    966961    // should work off this list of a subset.
    967962    // TODO(cbiesinger): That second part is not yet true.
    968     Vector<FlexItem> allItems;
    969     m_orderIterator.first();
    970     for (RenderBox* child = m_orderIterator.currentChild(); child; child = m_orderIterator.next()) {
    971         if (m_orderIterator.shouldSkipChild(*child)) {
    972             // Out-of-flow children are not flex items, so we skip them here.
    973             if (child->isOutOfFlowPositioned())
    974                 prepareChildForPositionedLayout(*child);
    975             continue;
    976         }
    977         allItems.append(constructFlexItem(*child, relayoutChildren));
    978     }
    979 
    980     // constructFlexItem() might set the override containing block height so any value cached for definiteness might be incorrect.
    981     m_hasDefiniteHeight = SizeDefiniteness::Unknown;
    982    
     963    auto allItems = constructFlexItems(relayoutChildren);
    983964    const LayoutUnit lineBreakLength = mainAxisContentExtent(LayoutUnit::max());
    984965    LayoutUnit gapBetweenItems = computeGap(GapType::BetweenItems);
     
    12951276}
    12961277
    1297 FlexItem RenderFlexibleBox::constructFlexItem(RenderBox& child, bool relayoutChildren)
    1298 {
    1299     child.clearOverridingContentSize();
    1300     if (childHasIntrinsicMainAxisSize(child)) {
    1301         // If this condition is true, then computeMainAxisExtentForChild will call
    1302         // child.intrinsicContentLogicalHeight() and child.scrollbarLogicalHeight(),
    1303         // so if the child has intrinsic min/max/preferred size, run layout on it now to make sure
    1304         // its logical height and scroll bars are up to date.
    1305         updateBlockChildDirtyBitsBeforeLayout(relayoutChildren, child);
    1306         // Don't resolve percentages in children. This is especially important for the min-height calculation,
    1307         // where we want percentages to be treated as auto. For flex-basis itself, this is not a problem because
    1308         // by definition we have an indefinite flex basis here and thus percentages should not resolve.
    1309         if (child.needsLayout() || !m_intrinsicSizeAlongMainAxis.contains(&child)) {
    1310             if (isHorizontalWritingMode() == child.isHorizontalWritingMode())
    1311                 child.setOverridingContainingBlockContentLogicalHeight(WTF::nullopt);
    1312             else
    1313                 child.setOverridingContainingBlockContentLogicalWidth(WTF::nullopt);
    1314             child.clearOverridingContentSize();
    1315             child.setChildNeedsLayout(MarkOnlyThis);
    1316             child.layoutIfNeeded();
    1317             cacheChildMainSize(child);
    1318             child.clearOverridingContainingBlockContentSize();
    1319         }
    1320     }
    1321    
    1322     LayoutUnit borderAndPadding = isHorizontalFlow() ? child.horizontalBorderAndPaddingExtent() : child.verticalBorderAndPaddingExtent();
    1323     LayoutUnit childInnerFlexBaseSize = computeInnerFlexBaseSizeForChild(child, borderAndPadding);
    1324     LayoutUnit childMinMaxAppliedMainAxisExtent = adjustChildSizeForMinAndMax(child, childInnerFlexBaseSize);
    1325     LayoutUnit margin = isHorizontalFlow() ? child.horizontalMarginExtent() : child.verticalMarginExtent();
    1326     return FlexItem(child, childInnerFlexBaseSize, childMinMaxAppliedMainAxisExtent, borderAndPadding, margin);
     1278Vector<FlexItem> RenderFlexibleBox::constructFlexItems(bool relayoutChildren)
     1279{
     1280    SetForScope<bool> inFlexItemConstruction(m_inFlexItemConstruction, true);
     1281
     1282    Vector<FlexItem> flexItems;
     1283    for (auto* child = m_orderIterator.first(); child; child = m_orderIterator.next()) {
     1284        if (m_orderIterator.shouldSkipChild(*child)) {
     1285            // Out-of-flow children are not flex items, so we skip them here.
     1286            if (child->isOutOfFlowPositioned())
     1287                prepareChildForPositionedLayout(*child);
     1288            continue;
     1289        }
     1290
     1291        auto constructFlexItemForChildBox = [&](auto& childBox) {
     1292            childBox.clearOverridingContentSize();
     1293            if (childHasIntrinsicMainAxisSize(childBox)) {
     1294                // If this condition is true, then computeMainAxisExtentForChild will call
     1295                // child.intrinsicContentLogicalHeight() and child.scrollbarLogicalHeight(),
     1296                // so if the child has intrinsic min/max/preferred size, run layout on it now to make sure
     1297                // its logical height and scroll bars are up to date.
     1298                updateBlockChildDirtyBitsBeforeLayout(relayoutChildren, childBox);
     1299                // Don't resolve percentages in children. This is especially important for the min-height calculation,
     1300                // where we want percentages to be treated as auto. For flex-basis itself, this is not a problem because
     1301                // by definition we have an indefinite flex basis here and thus percentages should not resolve.
     1302                if (childBox.needsLayout() || !m_intrinsicSizeAlongMainAxis.contains(&childBox)) {
     1303                    if (isHorizontalWritingMode() == childBox.isHorizontalWritingMode())
     1304                        childBox.setOverridingContainingBlockContentLogicalHeight(WTF::nullopt);
     1305                    else
     1306                        childBox.setOverridingContainingBlockContentLogicalWidth(WTF::nullopt);
     1307                    childBox.clearOverridingContentSize();
     1308                    childBox.setChildNeedsLayout(MarkOnlyThis);
     1309                    childBox.layoutIfNeeded();
     1310                    cacheChildMainSize(childBox);
     1311                    childBox.clearOverridingContainingBlockContentSize();
     1312                }
     1313            }
     1314
     1315            auto borderAndPadding = isHorizontalFlow() ? childBox.horizontalBorderAndPaddingExtent() : childBox.verticalBorderAndPaddingExtent();
     1316            auto childInnerFlexBaseSize = computeInnerFlexBaseSizeForChild(childBox, borderAndPadding);
     1317            auto childMinMaxAppliedMainAxisExtent = adjustChildSizeForMinAndMax(childBox, childInnerFlexBaseSize);
     1318            auto margin = isHorizontalFlow() ? childBox.horizontalMarginExtent() : childBox.verticalMarginExtent();
     1319            return FlexItem(childBox, childInnerFlexBaseSize, childMinMaxAppliedMainAxisExtent, borderAndPadding, margin);
     1320        };
     1321        flexItems.append(constructFlexItemForChildBox(*child));
     1322    }
     1323    return flexItems;
    13271324}
    13281325   
  • trunk/Source/WebCore/rendering/RenderFlexibleBox.h

    r276835 r277222  
    176176    LayoutUnit adjustChildSizeForMinAndMax(RenderBox& child, LayoutUnit childSize);
    177177    LayoutUnit adjustChildSizeForAspectRatioCrossAxisMinAndMax(const RenderBox& child, LayoutUnit childSize);
    178     FlexItem constructFlexItem(RenderBox&, bool relayoutChildren);
     178    Vector<FlexItem> constructFlexItems(bool relayoutChildren);
    179179   
    180180    void freezeInflexibleItems(FlexSign, Vector<FlexItem>& children, LayoutUnit& remainingFreeSpace, double& totalFlexGrow, double& totalFlexShrink, double& totalWeightedFlexShrink);
     
    221221    // This is SizeIsUnknown outside of layoutBlock()
    222222    SizeDefiniteness m_hasDefiniteHeight { SizeDefiniteness::Unknown };
    223     bool m_inLayout { false };
     223    bool m_inFlexItemConstruction { false };
    224224    bool m_shouldResetChildLogicalHeightBeforeLayout { false };
    225225};
Note: See TracChangeset for help on using the changeset viewer.