| /* |
| * Copyright (C) 1999 Lars Knoll ([email protected]) |
| * (C) 1999 Antti Koivisto ([email protected]) |
| * Copyright (C) 2003-2025 Apple Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "config.h" |
| #include "RenderDeprecatedFlexibleBox.h" |
| |
| #include "FontCascade.h" |
| #include "InlineIteratorLineBox.h" |
| #include "LayoutIntegrationLineLayout.h" |
| #include "LayoutRepainter.h" |
| #include "RenderBoxInlines.h" |
| #include "RenderBoxModelObjectInlines.h" |
| #include "RenderDescendantIterator.h" |
| #include "RenderElementStyleInlines.h" |
| #include "RenderElementInlines.h" |
| #include "RenderIterator.h" |
| #include "RenderLayer.h" |
| #include "RenderLayoutState.h" |
| #include "RenderObjectInlines.h" |
| #include "RenderStyle+GettersInlines.h" |
| #include "RenderView.h" |
| #include "StyleComputedStyle+InitialInlines.h" |
| #include <ranges> |
| #include <wtf/Scope.h> |
| #include <wtf/StdLibExtras.h> |
| #include <wtf/TZoneMallocInlines.h> |
| #include <wtf/unicode/CharacterNames.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_TZONE_ALLOCATED_IMPL(RenderDeprecatedFlexibleBox); |
| |
| class FlexBoxIterator { |
| public: |
| FlexBoxIterator(RenderDeprecatedFlexibleBox* parent) |
| : m_box(parent) |
| , m_largestOrdinal(1) |
| { |
| if (m_box->style().boxOrient() == BoxOrient::Horizontal && !m_box->style().isLeftToRightDirection()) |
| m_forward = m_box->style().boxDirection() != BoxDirection::Normal; |
| else |
| m_forward = m_box->style().boxDirection() == BoxDirection::Normal; |
| if (!m_forward) { |
| // No choice, since we're going backwards, we have to find out the highest ordinal up front. |
| RenderBox* child = m_box->firstChildBox(); |
| while (child) { |
| if (child->style().boxOrdinalGroup() > m_largestOrdinal) |
| m_largestOrdinal = child->style().boxOrdinalGroup().value; |
| child = child->nextSiblingBox(); |
| } |
| } |
| |
| reset(); |
| } |
| |
| void reset() |
| { |
| m_currentChild = nullptr; |
| m_ordinalIteration = std::numeric_limits<unsigned>::max(); |
| } |
| |
| RenderBox* first() |
| { |
| reset(); |
| return next(); |
| } |
| |
| RenderBox* next() |
| { |
| do { |
| if (!m_currentChild) { |
| ++m_ordinalIteration; |
| |
| if (!m_ordinalIteration) |
| m_currentOrdinal = m_forward ? 1 : m_largestOrdinal; |
| else { |
| if (m_ordinalIteration > m_ordinalValues.size()) |
| return nullptr; |
| |
| // Only copy+sort the values once per layout even if the iterator is reset. |
| if (static_cast<size_t>(m_ordinalValues.size()) != m_sortedOrdinalValues.size()) { |
| m_sortedOrdinalValues = copyToVector(m_ordinalValues); |
| std::ranges::sort(m_sortedOrdinalValues); |
| } |
| m_currentOrdinal = m_forward ? m_sortedOrdinalValues[m_ordinalIteration - 1] : m_sortedOrdinalValues[m_sortedOrdinalValues.size() - m_ordinalIteration]; |
| } |
| |
| m_currentChild = m_forward ? m_box->firstChildBox() : m_box->lastChildBox(); |
| } else |
| m_currentChild = m_forward ? m_currentChild->nextSiblingBox() : m_currentChild->previousSiblingBox(); |
| |
| if (m_currentChild && notFirstOrdinalValue()) |
| m_ordinalValues.add(m_currentChild->style().boxOrdinalGroup().value); |
| } while (!m_currentChild || m_currentChild->isExcludedFromNormalLayout() || (!m_currentChild->isAnonymous() |
| && m_currentChild->style().boxOrdinalGroup() != m_currentOrdinal)); |
| return m_currentChild; |
| } |
| |
| private: |
| bool notFirstOrdinalValue() |
| { |
| unsigned int firstOrdinalValue = m_forward ? 1 : m_largestOrdinal; |
| return m_currentOrdinal == firstOrdinalValue && m_currentChild->style().boxOrdinalGroup() != firstOrdinalValue; |
| } |
| |
| RenderDeprecatedFlexibleBox* m_box; |
| RenderBox* m_currentChild; |
| bool m_forward; |
| unsigned m_currentOrdinal; |
| unsigned m_largestOrdinal; |
| HashSet<unsigned> m_ordinalValues; |
| Vector<unsigned> m_sortedOrdinalValues; |
| unsigned m_ordinalIteration; |
| }; |
| |
| RenderDeprecatedFlexibleBox::RenderDeprecatedFlexibleBox(Element& element, RenderStyle&& style) |
| : RenderBlock(RenderObject::Type::DeprecatedFlexibleBox, element, WTF::move(style), { }) |
| { |
| setChildrenInline(false); // All of our children must be block-level |
| m_stretchingChildren = false; |
| } |
| |
| RenderDeprecatedFlexibleBox::~RenderDeprecatedFlexibleBox() = default; |
| |
| static LayoutUnit marginWidthForChild(RenderBox* child) |
| { |
| // A margin basically has three types: fixed, percentage, and auto (variable). |
| // Auto and percentage margins simply become 0 when computing min/max width. |
| // Fixed margins can be added in as is. |
| const auto& zoomFactor = child->style().usedZoomForLength(); |
| LayoutUnit margin; |
| if (auto fixedMarginLeft = child->style().marginLeft().tryFixed()) |
| margin += fixedMarginLeft->resolveZoom(zoomFactor); |
| if (auto fixedMarginRight = child->style().marginRight().tryFixed()) |
| margin += fixedMarginRight->resolveZoom(zoomFactor); |
| return margin; |
| } |
| |
| static bool childDoesNotAffectWidthOrFlexing(RenderObject* child) |
| { |
| // Positioned children don't affect the min/max width. |
| return child->isOutOfFlowPositioned(); |
| } |
| |
| static LayoutUnit widthForChild(RenderBox* child) |
| { |
| if (auto overridingLogicalWidth = child->overridingBorderBoxLogicalWidth()) |
| return *overridingLogicalWidth; |
| return child->logicalWidth(); |
| } |
| |
| static LayoutUnit heightForChild(RenderBox* child) |
| { |
| if (auto overridingLogicalHeight = child->overridingBorderBoxLogicalHeight()) |
| return *overridingLogicalHeight; |
| return child->logicalHeight(); |
| } |
| |
| static LayoutUnit contentWidthForChild(RenderBox* child) |
| { |
| return std::max<LayoutUnit>(0, widthForChild(child) - child->borderAndPaddingLogicalWidth()); |
| } |
| |
| static LayoutUnit contentHeightForChild(RenderBox* child) |
| { |
| return std::max<LayoutUnit>(0, heightForChild(child) - child->borderAndPaddingLogicalHeight()); |
| } |
| |
| void RenderDeprecatedFlexibleBox::styleWillChange(Style::Difference diff, const RenderStyle& newStyle) |
| { |
| auto shouldClearLineClamp = [&] { |
| auto* oldStyle = hasInitializedStyle() ? &style() : nullptr; |
| if (!oldStyle || oldStyle->lineClamp().isNone()) |
| return false; |
| if (newStyle.lineClamp().isNone()) |
| return true; |
| return newStyle.boxOrient() == BoxOrient::Horizontal; |
| }; |
| if (shouldClearLineClamp()) |
| clearLineClamp(); |
| RenderBlock::styleWillChange(diff, newStyle); |
| } |
| |
| void RenderDeprecatedFlexibleBox::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const |
| { |
| auto addScrollbarWidth = [&]() { |
| LayoutUnit scrollbarWidth = intrinsicScrollbarLogicalWidthIncludingGutter(); |
| maxLogicalWidth += scrollbarWidth; |
| minLogicalWidth += scrollbarWidth; |
| }; |
| |
| if (shouldApplySizeOrInlineSizeContainment()) { |
| if (auto width = explicitIntrinsicInnerLogicalWidth()) { |
| minLogicalWidth = width.value(); |
| maxLogicalWidth = width.value(); |
| } |
| addScrollbarWidth(); |
| return; |
| } |
| |
| if (hasMultipleLines() || isVertical()) { |
| for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| LayoutUnit margin = marginWidthForChild(child); |
| LayoutUnit width = child->minPreferredLogicalWidth() + margin; |
| minLogicalWidth = std::max(width, minLogicalWidth); |
| |
| width = child->maxPreferredLogicalWidth() + margin; |
| maxLogicalWidth = std::max(width, maxLogicalWidth); |
| } |
| } else { |
| for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| LayoutUnit margin = marginWidthForChild(child); |
| minLogicalWidth += child->minPreferredLogicalWidth() + margin; |
| maxLogicalWidth += child->maxPreferredLogicalWidth() + margin; |
| } |
| } |
| |
| maxLogicalWidth = std::max(minLogicalWidth, maxLogicalWidth); |
| addScrollbarWidth(); |
| } |
| |
| void RenderDeprecatedFlexibleBox::computePreferredLogicalWidths() |
| { |
| ASSERT(needsPreferredLogicalWidthsUpdate()); |
| |
| m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0; |
| if (auto fixedWidth = style().width().tryFixed(); fixedWidth && fixedWidth->isPositive()) |
| m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(*fixedWidth); |
| else |
| computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth); |
| |
| RenderBox::computePreferredLogicalWidths(style().minWidth(), style().maxWidth(), borderAndPaddingLogicalWidth()); |
| |
| clearNeedsPreferredWidthsUpdate(); |
| } |
| |
| // Use an inline capacity of 8, since flexbox containers usually have less than 8 children. |
| typedef Vector<LayoutRect, 8> ChildFrameRects; |
| typedef Vector<LayoutSize, 8> ChildLayoutDeltas; |
| |
| static void appendChildFrameRects(RenderDeprecatedFlexibleBox* box, ChildFrameRects& childFrameRects) |
| { |
| FlexBoxIterator iterator(box); |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (!child->isOutOfFlowPositioned()) |
| childFrameRects.append(child->frameRect()); |
| } |
| } |
| |
| static void appendChildLayoutDeltas(RenderDeprecatedFlexibleBox* box, ChildLayoutDeltas& childLayoutDeltas) |
| { |
| FlexBoxIterator iterator(box); |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (!child->isOutOfFlowPositioned()) |
| childLayoutDeltas.append(LayoutSize()); |
| } |
| } |
| |
| static void repaintChildrenDuringLayoutIfMoved(RenderDeprecatedFlexibleBox* box, const ChildFrameRects& oldChildRects) |
| { |
| size_t childIndex = 0; |
| FlexBoxIterator iterator(box); |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (child->isOutOfFlowPositioned()) |
| continue; |
| |
| // If the child moved, we have to repaint it as well as any floating/positioned |
| // descendants. An exception is if we need a layout. In this case, we know we're going to |
| // repaint ourselves (and the child) anyway. |
| if (!box->selfNeedsLayout() && child->checkForRepaintDuringLayout()) |
| child->repaintDuringLayoutIfMoved(oldChildRects[childIndex]); |
| |
| ++childIndex; |
| } |
| ASSERT(childIndex == oldChildRects.size()); |
| } |
| |
| bool RenderDeprecatedFlexibleBox::hasClampingAndNoFlexing() const |
| { |
| if (isHorizontal()) |
| return false; |
| auto* firstChildBox = this->firstChildBox(); |
| if (!firstChildBox || firstChildBox != lastChildBox() || firstChildBox->firstChild() != firstChildBox->lastChild() || !is<RenderText>(firstChildBox->firstChild())) |
| return false; |
| if (firstChildBox->style().hasOutOfFlowPosition()) |
| return false; |
| |
| auto& style = this->style(); |
| if (style.lineClamp().isNone() || style.lineClamp().isPercentage()) |
| return false; |
| if (!style.logicalHeight().isAuto() || !firstChildBox->style().logicalHeight().isAuto()) |
| return false; |
| if (style.overflowX() != Overflow::Hidden || style.overflowY() != Overflow::Hidden) |
| return false; |
| if (style.boxAlign() != Style::ComputedStyle::initialBoxAlign() || !style.isLeftToRightDirection()) |
| return false; |
| return true; |
| } |
| |
| void RenderDeprecatedFlexibleBox::layoutBlock(RelayoutChildren relayoutChildren, LayoutUnit) |
| { |
| ASSERT(needsLayout()); |
| |
| if (relayoutChildren == RelayoutChildren::No && simplifiedLayout()) |
| return; |
| |
| if (hasClampingAndNoFlexing()) |
| return layoutSingleClampedFlexItem(); |
| |
| LayoutRepainter repainter(*this); |
| { |
| LayoutStateMaintainer statePusher(*this, locationOffset(), isTransformed() || hasReflection() || writingMode().isBlockFlipped()); |
| |
| resetLogicalHeightBeforeLayoutIfNeeded(); |
| preparePaginationBeforeBlockLayout(relayoutChildren); |
| |
| LayoutSize previousSize = size(); |
| |
| updateLogicalWidth(); |
| updateLogicalHeight(); |
| |
| if (previousSize != size() |
| || (parent()->isRenderDeprecatedFlexibleBox() && parent()->style().boxOrient() == BoxOrient::Horizontal |
| && parent()->style().boxAlign() == BoxAlignment::Stretch)) |
| relayoutChildren = RelayoutChildren::Yes; |
| |
| setHeight(0); |
| |
| m_stretchingChildren = false; |
| |
| #if ASSERT_ENABLED |
| LayoutSize oldLayoutDelta = view().frameView().layoutContext().layoutDelta(); |
| #endif |
| |
| // Fieldsets need to find their legend and position it inside the border of the object. |
| // The legend then gets skipped during normal layout. The same is true for ruby text. |
| // It doesn't get included in the normal layout process but is instead skipped. |
| layoutExcludedChildren(relayoutChildren); |
| |
| ChildFrameRects oldChildRects; |
| appendChildFrameRects(this, oldChildRects); |
| |
| if (isHorizontal()) |
| layoutHorizontalBox(relayoutChildren); |
| else |
| layoutVerticalBox(relayoutChildren); |
| |
| repaintChildrenDuringLayoutIfMoved(this, oldChildRects); |
| ASSERT(view().frameView().layoutContext().layoutDeltaMatches(oldLayoutDelta)); |
| |
| auto contentArea = flippedContentBoxRect(); |
| updateLogicalHeight(); |
| |
| if (previousSize.height() != height()) |
| relayoutChildren = RelayoutChildren::Yes; |
| |
| if (isDocumentElementRenderer()) |
| layoutOutOfFlowBoxes(RelayoutChildren::Yes); |
| else |
| layoutOutOfFlowBoxes(relayoutChildren); |
| |
| updateDescendantTransformsAfterLayout(); |
| |
| computeOverflow(contentArea); |
| } |
| |
| updateLayerTransform(); |
| |
| auto* layoutState = view().frameView().layoutContext().layoutState(); |
| if (layoutState && layoutState->pageLogicalHeight()) |
| setPageLogicalOffset(layoutState->pageLogicalOffset(this, logicalTop())); |
| |
| // Repaint with our new bounds if they are different from our old bounds. |
| repainter.repaintAfterLayout(); |
| } |
| |
| // The first walk over our kids is to find out if we have any flexible children. |
| static void gatherFlexChildrenInfo(FlexBoxIterator& iterator, RelayoutChildren relayoutChildren, unsigned& highestFlexGroup, unsigned& lowestFlexGroup, bool& haveFlex) |
| { |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| // Check to see if this child flexes. |
| if (!childDoesNotAffectWidthOrFlexing(child) && child->style().boxFlex() > 0.0f) { |
| // We always have to lay out flexible objects again, since the flex distribution |
| // may have changed, and we need to reallocate space. |
| child->clearOverridingSize(); |
| if (relayoutChildren == RelayoutChildren::No) |
| child->setChildNeedsLayout(MarkOnlyThis); |
| haveFlex = true; |
| unsigned flexGroup = child->style().boxFlexGroup().value; |
| if (lowestFlexGroup == 0) |
| lowestFlexGroup = flexGroup; |
| if (flexGroup < lowestFlexGroup) |
| lowestFlexGroup = flexGroup; |
| if (flexGroup > highestFlexGroup) |
| highestFlexGroup = flexGroup; |
| } |
| } |
| } |
| |
| static void issueFullRepaintOnFirstLayout(RenderBox& flexItem, bool everHadLayout) |
| { |
| if (everHadLayout || !flexItem.checkForRepaintDuringLayout()) |
| return; |
| |
| flexItem.repaint(); |
| flexItem.repaintOverhangingFloats(true); |
| } |
| |
| static void layoutChildIfNeededApplyingDelta(RenderBox* child, const LayoutSize& layoutDelta) |
| { |
| if (!child->needsLayout()) |
| return; |
| |
| auto everHadLayout = child->everHadLayout(); |
| child->view().frameView().layoutContext().addLayoutDelta(layoutDelta); |
| child->layoutIfNeeded(); |
| child->view().frameView().layoutContext().addLayoutDelta(-layoutDelta); |
| issueFullRepaintOnFirstLayout(*child, everHadLayout); |
| } |
| |
| void RenderDeprecatedFlexibleBox::layoutHorizontalBox(RelayoutChildren relayoutChildren) |
| { |
| LayoutUnit toAdd = borderBottom() + paddingBottom() + horizontalScrollbarHeight(); |
| LayoutUnit yPos = borderTop() + paddingTop(); |
| LayoutUnit xPos = borderLeft() + paddingLeft(); |
| bool heightSpecified = false; |
| LayoutUnit oldHeight; |
| |
| LayoutUnit remainingSpace; |
| |
| FlexBoxIterator iterator(this); |
| unsigned int highestFlexGroup = 0; |
| unsigned int lowestFlexGroup = 0; |
| bool haveFlex = false, flexingChildren = false; |
| gatherFlexChildrenInfo(iterator, relayoutChildren, highestFlexGroup, lowestFlexGroup, haveFlex); |
| |
| beginUpdateScrollInfoAfterLayoutTransaction(); |
| |
| ChildLayoutDeltas childLayoutDeltas; |
| appendChildLayoutDeltas(this, childLayoutDeltas); |
| |
| // We do 2 passes. The first pass is simply to lay everyone out at |
| // their preferred widths. The subsequent passes handle flexing the children. |
| // The first pass skips flexible objects completely. |
| do { |
| // Reset our height. |
| setHeight(yPos); |
| |
| xPos = borderLeft() + paddingLeft(); |
| |
| size_t childIndex = 0; |
| |
| // Our first pass is done without flexing. We simply lay the children |
| // out within the box. We have to do a layout first in order to determine |
| // our box's intrinsic height. |
| LayoutUnit maxAscent, maxDescent; |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (relayoutChildren == RelayoutChildren::Yes) |
| child->setChildNeedsLayout(MarkOnlyThis); |
| |
| if (child->isOutOfFlowPositioned()) |
| continue; |
| |
| LayoutSize& childLayoutDelta = childLayoutDeltas[childIndex++]; |
| |
| // Compute the child's vertical margins. |
| child->computeAndSetBlockDirectionMargins(*this); |
| |
| child->markForPaginationRelayoutIfNeeded(); |
| |
| // Apply the child's current layout delta. |
| layoutChildIfNeededApplyingDelta(child, childLayoutDelta); |
| |
| // Update our height and overflow height. |
| if (style().boxAlign() == BoxAlignment::Baseline) { |
| LayoutUnit ascent = child->firstLineBaseline().value_or(child->height() + child->marginBottom()); |
| ascent += child->marginTop(); |
| LayoutUnit descent = (child->height() + child->verticalMarginExtent()) - ascent; |
| |
| // Update our maximum ascent. |
| maxAscent = std::max(maxAscent, ascent); |
| |
| // Update our maximum descent. |
| maxDescent = std::max(maxDescent, descent); |
| |
| // Now update our height. |
| setHeight(std::max(yPos + maxAscent + maxDescent, height())); |
| } |
| else |
| setHeight(std::max(height(), yPos + child->height() + child->verticalMarginExtent())); |
| } |
| ASSERT(childIndex == childLayoutDeltas.size()); |
| |
| if (!iterator.first() && hasLineIfEmpty()) |
| setHeight(height() + lineHeight()); |
| |
| setHeight(height() + toAdd); |
| |
| oldHeight = height(); |
| updateLogicalHeight(); |
| |
| relayoutChildren = RelayoutChildren::No; |
| if (oldHeight != height()) |
| heightSpecified = true; |
| |
| // Now that our height is actually known, we can place our boxes. |
| childIndex = 0; |
| m_stretchingChildren = (style().boxAlign() == BoxAlignment::Stretch); |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (child->isOutOfFlowPositioned()) { |
| child->containingBlock()->addOutOfFlowBox(*child); |
| CheckedPtr childLayer = child->layer(); |
| childLayer->setStaticInlinePosition(xPos); // FIXME: Not right for regions. |
| if (childLayer->staticBlockPosition() != yPos) { |
| childLayer->setStaticBlockPosition(yPos); |
| if (child->style().hasStaticBlockPosition(writingMode().isHorizontal())) |
| child->setChildNeedsLayout(MarkOnlyThis); |
| } |
| continue; |
| } |
| |
| LayoutSize& childLayoutDelta = childLayoutDeltas[childIndex++]; |
| |
| // We need to see if this child's height has changed, since we make block elements |
| // fill the height of a containing box by default. |
| // Now do a layout. |
| LayoutUnit oldChildHeight = child->height(); |
| child->updateLogicalHeight(); |
| if (oldChildHeight != child->height()) |
| child->setChildNeedsLayout(MarkOnlyThis); |
| |
| child->markForPaginationRelayoutIfNeeded(); |
| |
| layoutChildIfNeededApplyingDelta(child, childLayoutDelta); |
| |
| // We can place the child now, using our value of box-align. |
| xPos += child->marginLeft(); |
| LayoutUnit childY = yPos; |
| switch (style().boxAlign()) { |
| case BoxAlignment::Center: |
| childY += child->marginTop() + std::max<LayoutUnit>(0, (contentBoxHeight() - (child->height() + child->verticalMarginExtent())) / 2); |
| break; |
| case BoxAlignment::Baseline: { |
| LayoutUnit ascent = child->firstLineBaseline().value_or(child->height() + child->marginBottom()); |
| ascent += child->marginTop(); |
| childY += child->marginTop() + (maxAscent - ascent); |
| break; |
| } |
| case BoxAlignment::End: |
| childY += contentBoxHeight() - child->marginBottom() - child->height(); |
| break; |
| default: // BoxAlignment::Start |
| childY += child->marginTop(); |
| break; |
| } |
| |
| placeChild(child, LayoutPoint(xPos, childY), &childLayoutDelta); |
| |
| xPos += child->width() + child->marginRight(); |
| } |
| ASSERT(childIndex == childLayoutDeltas.size()); |
| |
| remainingSpace = borderLeft() + paddingLeft() + contentBoxWidth() - xPos; |
| |
| m_stretchingChildren = false; |
| if (flexingChildren) |
| haveFlex = false; // We're done. |
| else if (haveFlex) { |
| // We have some flexible objects. See if we need to grow/shrink them at all. |
| if (!remainingSpace) |
| break; |
| |
| // Allocate the remaining space among the flexible objects. If we are trying to |
| // grow, then we go from the lowest flex group to the highest flex group. For shrinking, |
| // we go from the highest flex group to the lowest group. |
| bool expanding = remainingSpace > 0; |
| unsigned int start = expanding ? lowestFlexGroup : highestFlexGroup; |
| unsigned int end = expanding? highestFlexGroup : lowestFlexGroup; |
| for (unsigned int i = start; i <= end && remainingSpace; i++) { |
| // Always start off by assuming the group can get all the remaining space. |
| LayoutUnit groupRemainingSpace = remainingSpace; |
| do { |
| // Flexing consists of multiple passes, since we have to change ratios every time an object hits its max/min-width |
| // For a given pass, we always start off by computing the totalFlex of all objects that can grow/shrink at all, and |
| // computing the allowed growth before an object hits its min/max width (and thus |
| // forces a totalFlex recomputation). |
| LayoutUnit groupRemainingSpaceAtBeginning = groupRemainingSpace; |
| float totalFlex = 0.0f; |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (allowedChildFlex(child, expanding, i)) |
| totalFlex += child->style().boxFlex().value; |
| } |
| LayoutUnit spaceAvailableThisPass = groupRemainingSpace; |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| LayoutUnit allowedFlex = allowedChildFlex(child, expanding, i); |
| if (allowedFlex) { |
| LayoutUnit projectedFlex = (allowedFlex == LayoutUnit::max()) ? allowedFlex : LayoutUnit(allowedFlex * (totalFlex / child->style().boxFlex().value)); |
| spaceAvailableThisPass = expanding ? std::min(spaceAvailableThisPass, projectedFlex) : std::max(spaceAvailableThisPass, projectedFlex); |
| } |
| } |
| |
| // The flex groups may not have any flexible objects this time around. |
| if (!spaceAvailableThisPass || totalFlex == 0.0f) { |
| // If we just couldn't grow/shrink any more, then it's time to transition to the next flex group. |
| groupRemainingSpace = 0; |
| continue; |
| } |
| |
| // Now distribute the space to objects. |
| for (RenderBox* child = iterator.first(); child && spaceAvailableThisPass && totalFlex; child = iterator.next()) { |
| if (allowedChildFlex(child, expanding, i)) { |
| LayoutUnit spaceAdd = LayoutUnit(spaceAvailableThisPass * (child->style().boxFlex().value / totalFlex)); |
| if (spaceAdd) { |
| child->setOverridingBorderBoxLogicalWidth(widthForChild(child) + spaceAdd); |
| flexingChildren = true; |
| relayoutChildren = RelayoutChildren::Yes; |
| } |
| |
| spaceAvailableThisPass -= spaceAdd; |
| remainingSpace -= spaceAdd; |
| groupRemainingSpace -= spaceAdd; |
| |
| totalFlex -= child->style().boxFlex().value; |
| } |
| } |
| if (groupRemainingSpace == groupRemainingSpaceAtBeginning) { |
| // This is not advancing, avoid getting stuck by distributing the remaining pixels. |
| LayoutUnit spaceAdd = groupRemainingSpace > 0 ? 1 : -1; |
| for (RenderBox* child = iterator.first(); child && groupRemainingSpace; child = iterator.next()) { |
| if (allowedChildFlex(child, expanding, i)) { |
| child->setOverridingBorderBoxLogicalWidth(widthForChild(child) + spaceAdd); |
| flexingChildren = true; |
| relayoutChildren = RelayoutChildren::Yes; |
| remainingSpace -= spaceAdd; |
| groupRemainingSpace -= spaceAdd; |
| } |
| } |
| } |
| } while (absoluteValue(groupRemainingSpace) >= 1); |
| } |
| |
| // We didn't find any children that could grow. |
| if (haveFlex && !flexingChildren) |
| haveFlex = false; |
| } |
| } while (haveFlex); |
| |
| endAndCommitUpdateScrollInfoAfterLayoutTransaction(); |
| |
| if (remainingSpace > 0 && ((style().isLeftToRightDirection() && style().boxPack() != BoxPack::Start) |
| || (!style().isLeftToRightDirection() && style().boxPack() != BoxPack::End))) { |
| // Children must be repositioned. |
| LayoutUnit offset; |
| if (style().boxPack() == BoxPack::Justify) { |
| // Determine the total number of children. |
| int totalChildren = 0; |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| ++totalChildren; |
| } |
| |
| // Iterate over the children and space them out according to the |
| // justification level. |
| if (totalChildren > 1) { |
| --totalChildren; |
| bool firstChild = true; |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| if (firstChild) { |
| firstChild = false; |
| continue; |
| } |
| |
| offset += remainingSpace/totalChildren; |
| remainingSpace -= (remainingSpace/totalChildren); |
| --totalChildren; |
| |
| placeChild(child, child->location() + LayoutSize(offset, 0_lu)); |
| } |
| } |
| } else { |
| if (style().boxPack() == BoxPack::Center) |
| offset += remainingSpace / 2; |
| else // BoxPack::End for LTR, BoxPack::Start for RTL |
| offset += remainingSpace; |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| placeChild(child, child->location() + LayoutSize(offset, 0_lu)); |
| } |
| } |
| } |
| |
| // So that the computeLogicalHeight in layoutBlock() knows to relayout positioned objects because of |
| // a height change, we revert our height back to the intrinsic height before returning. |
| if (heightSpecified) |
| setHeight(oldHeight); |
| } |
| |
| void RenderDeprecatedFlexibleBox::layoutSingleClampedFlexItem() |
| { |
| auto repainter = LayoutRepainter { *this }; |
| |
| updateLogicalWidth(); |
| updateLogicalHeight(); |
| |
| beginUpdateScrollInfoAfterLayoutTransaction(); |
| |
| auto& clampedRendererCandidate = *firstChildBox(); |
| auto everHadLayout = clampedRendererCandidate.everHadLayout(); |
| clampedRendererCandidate.setLocation({ borderLeft() + paddingLeft(), borderTop() + paddingTop() }); |
| |
| auto iterator = FlexBoxIterator { this }; |
| auto clampedContent = applyLineClamp(iterator, { }); |
| issueFullRepaintOnFirstLayout(clampedRendererCandidate, everHadLayout); |
| |
| clampedRendererCandidate.move(clampedRendererCandidate.marginLeft(), clampedRendererCandidate.marginTop()); |
| auto childBoxBottom = clampedRendererCandidate.logicalTop() + clampedRendererCandidate.borderAndPaddingBefore() + clampedRendererCandidate.borderAndPaddingAfter(); |
| if (clampedContent.renderer) { |
| ASSERT(&clampedRendererCandidate == clampedContent.renderer.get()); |
| childBoxBottom += clampedContent.contentHeight; |
| } else |
| childBoxBottom += clampedRendererCandidate.contentBoxRect().height() + clampedRendererCandidate.marginBottom(); |
| |
| setHeight(childBoxBottom + paddingBottom() + borderBottom()); |
| updateLogicalHeight(); |
| |
| computeOverflow(flippedContentBoxRect()); |
| |
| endAndCommitUpdateScrollInfoAfterLayoutTransaction(); |
| |
| updateLayerTransform(); |
| |
| repainter.repaintAfterLayout(); |
| } |
| |
| void RenderDeprecatedFlexibleBox::layoutVerticalBox(RelayoutChildren relayoutChildren) |
| { |
| LayoutUnit yPos = borderTop() + paddingTop(); |
| LayoutUnit toAdd = borderBottom() + paddingBottom() + horizontalScrollbarHeight(); |
| bool heightSpecified = false; |
| LayoutUnit oldHeight; |
| |
| LayoutUnit remainingSpace; |
| |
| FlexBoxIterator iterator(this); |
| unsigned int highestFlexGroup = 0; |
| unsigned int lowestFlexGroup = 0; |
| bool haveFlex = false, flexingChildren = false; |
| gatherFlexChildrenInfo(iterator, relayoutChildren, highestFlexGroup, lowestFlexGroup, haveFlex); |
| |
| // We confine the line clamp ugliness to vertical flexible boxes (thus keeping it out of |
| // mainstream block layout); this is not really part of the XUL box model. |
| bool haveLineClamp = !style().lineClamp().isNone(); |
| auto clampedContent = ClampedContent { }; |
| if (haveLineClamp) |
| clampedContent = applyLineClamp(iterator, relayoutChildren); |
| |
| beginUpdateScrollInfoAfterLayoutTransaction(); |
| |
| // We do 2 passes. The first pass is simply to lay everyone out at |
| // their preferred widths. The second pass handles flexing the children. |
| // Our first pass is done without flexing. We simply lay the children |
| // out within the box. |
| do { |
| setHeight(borderTop() + paddingTop()); |
| LayoutUnit minHeight = height() + toAdd; |
| |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| // Make sure we relayout children if we need it. |
| if (!haveLineClamp && relayoutChildren == RelayoutChildren::Yes) |
| child->setChildNeedsLayout(MarkOnlyThis); |
| |
| if (child->isOutOfFlowPositioned()) { |
| child->containingBlock()->addOutOfFlowBox(*child); |
| CheckedPtr childLayer = child->layer(); |
| childLayer->setStaticInlinePosition(borderAndPaddingStart()); // FIXME: Not right for regions. |
| if (childLayer->staticBlockPosition() != height()) { |
| childLayer->setStaticBlockPosition(height()); |
| if (child->style().hasStaticBlockPosition(writingMode().isHorizontal())) |
| child->setChildNeedsLayout(MarkOnlyThis); |
| } |
| continue; |
| } |
| |
| // Compute the child's vertical margins. |
| child->computeAndSetBlockDirectionMargins(*this); |
| |
| // Add in the child's marginTop to our height. |
| setHeight(height() + child->marginTop()); |
| |
| if (!haveLineClamp) |
| child->markForPaginationRelayoutIfNeeded(); |
| |
| // Now do a layout. |
| auto everHadLayout = child->everHadLayout(); |
| child->layoutIfNeeded(); |
| issueFullRepaintOnFirstLayout(*child, everHadLayout); |
| |
| // We can place the child now, using our value of box-align. |
| LayoutUnit childX = borderLeft() + paddingLeft(); |
| switch (style().boxAlign()) { |
| case BoxAlignment::Center: |
| case BoxAlignment::Baseline: // Baseline just maps to center for vertical boxes |
| childX += child->marginLeft() + std::max<LayoutUnit>(0, (contentBoxWidth() - (child->width() + child->horizontalMarginExtent())) / 2); |
| break; |
| case BoxAlignment::End: |
| if (!style().isLeftToRightDirection()) |
| childX += child->marginLeft(); |
| else |
| childX += contentBoxWidth() - child->marginRight() - child->width(); |
| break; |
| default: // BoxAlignment::Start/BoxAlignment::Stretch |
| if (style().isLeftToRightDirection()) |
| childX += child->marginLeft(); |
| else |
| childX += contentBoxWidth() - child->marginRight() - child->width(); |
| break; |
| } |
| |
| // Place the child. |
| placeChild(child, LayoutPoint(childX, height())); |
| setHeight(height() + child->height() + child->marginBottom()); |
| } |
| |
| yPos = height(); |
| |
| if (!iterator.first() && hasLineIfEmpty()) |
| setHeight(height() + lineHeight()); |
| |
| setHeight(height() + toAdd); |
| |
| // Negative margins can cause our height to shrink below our minimal height (border/padding). |
| // If this happens, ensure that the computed height is increased to the minimal height. |
| if (height() < minHeight) |
| setHeight(minHeight); |
| |
| // Now we have to calc our height, so we know how much space we have remaining. |
| oldHeight = height(); |
| updateLogicalHeight(); |
| if (oldHeight != height()) |
| heightSpecified = true; |
| |
| remainingSpace = borderTop() + paddingTop() + contentBoxHeight() - yPos; |
| |
| if (flexingChildren) |
| haveFlex = false; // We're done. |
| else if (haveFlex) { |
| // We have some flexible objects. See if we need to grow/shrink them at all. |
| if (!remainingSpace) |
| break; |
| |
| // Allocate the remaining space among the flexible objects. If we are trying to |
| // grow, then we go from the lowest flex group to the highest flex group. For shrinking, |
| // we go from the highest flex group to the lowest group. |
| bool expanding = remainingSpace > 0; |
| unsigned int start = expanding ? lowestFlexGroup : highestFlexGroup; |
| unsigned int end = expanding? highestFlexGroup : lowestFlexGroup; |
| for (unsigned int i = start; i <= end && remainingSpace; i++) { |
| // Always start off by assuming the group can get all the remaining space. |
| LayoutUnit groupRemainingSpace = remainingSpace; |
| do { |
| // Flexing consists of multiple passes, since we have to change ratios every time an object hits its max/min-width |
| // For a given pass, we always start off by computing the totalFlex of all objects that can grow/shrink at all, and |
| // computing the allowed growth before an object hits its min/max width (and thus |
| // forces a totalFlex recomputation). |
| LayoutUnit groupRemainingSpaceAtBeginning = groupRemainingSpace; |
| float totalFlex = 0.0f; |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (allowedChildFlex(child, expanding, i)) |
| totalFlex += child->style().boxFlex().value; |
| } |
| LayoutUnit spaceAvailableThisPass = groupRemainingSpace; |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| LayoutUnit allowedFlex = allowedChildFlex(child, expanding, i); |
| if (allowedFlex) { |
| LayoutUnit projectedFlex = (allowedFlex == LayoutUnit::max()) ? allowedFlex : LayoutUnit(allowedFlex * (totalFlex / child->style().boxFlex().value)); |
| spaceAvailableThisPass = expanding ? std::min(spaceAvailableThisPass, projectedFlex) : std::max(spaceAvailableThisPass, projectedFlex); |
| } |
| } |
| |
| // The flex groups may not have any flexible objects this time around. |
| if (!spaceAvailableThisPass || totalFlex == 0.0f) { |
| // If we just couldn't grow/shrink any more, then it's time to transition to the next flex group. |
| groupRemainingSpace = 0; |
| continue; |
| } |
| |
| // Now distribute the space to objects. |
| for (RenderBox* child = iterator.first(); child && spaceAvailableThisPass && totalFlex; child = iterator.next()) { |
| if (allowedChildFlex(child, expanding, i)) { |
| LayoutUnit spaceAdd { spaceAvailableThisPass * (child->style().boxFlex().value / totalFlex) }; |
| if (spaceAdd) { |
| child->setOverridingBorderBoxLogicalHeight(heightForChild(child) + spaceAdd); |
| flexingChildren = true; |
| relayoutChildren = RelayoutChildren::Yes; |
| } |
| |
| spaceAvailableThisPass -= spaceAdd; |
| remainingSpace -= spaceAdd; |
| groupRemainingSpace -= spaceAdd; |
| |
| totalFlex -= child->style().boxFlex().value; |
| } |
| } |
| if (groupRemainingSpace == groupRemainingSpaceAtBeginning) { |
| // This is not advancing, avoid getting stuck by distributing the remaining pixels. |
| LayoutUnit spaceAdd = groupRemainingSpace > 0 ? 1 : -1; |
| for (RenderBox* child = iterator.first(); child && groupRemainingSpace; child = iterator.next()) { |
| if (allowedChildFlex(child, expanding, i)) { |
| child->setOverridingBorderBoxLogicalHeight(heightForChild(child) + spaceAdd); |
| flexingChildren = true; |
| relayoutChildren = RelayoutChildren::Yes; |
| remainingSpace -= spaceAdd; |
| groupRemainingSpace -= spaceAdd; |
| } |
| } |
| } |
| } while (absoluteValue(groupRemainingSpace) >= 1); |
| } |
| |
| // We didn't find any children that could grow. |
| if (haveFlex && !flexingChildren) |
| haveFlex = false; |
| } |
| } while (haveFlex); |
| |
| endAndCommitUpdateScrollInfoAfterLayoutTransaction(); |
| |
| if (style().boxPack() != BoxPack::Start && remainingSpace > 0) { |
| // Children must be repositioned. |
| LayoutUnit offset; |
| if (style().boxPack() == BoxPack::Justify) { |
| // Determine the total number of children. |
| int totalChildren = 0; |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| ++totalChildren; |
| } |
| |
| // Iterate over the children and space them out according to the |
| // justification level. |
| if (totalChildren > 1) { |
| --totalChildren; |
| bool firstChild = true; |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| if (firstChild) { |
| firstChild = false; |
| continue; |
| } |
| |
| offset += remainingSpace/totalChildren; |
| remainingSpace -= (remainingSpace/totalChildren); |
| --totalChildren; |
| placeChild(child, child->location() + LayoutSize(0_lu, offset)); |
| } |
| } |
| } else { |
| if (style().boxPack() == BoxPack::Center) |
| offset += remainingSpace / 2; |
| else // BoxPack::End |
| offset += remainingSpace; |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| placeChild(child, child->location() + LayoutSize(0_lu, offset)); |
| } |
| } |
| } |
| |
| // So that the computeLogicalHeight in layoutBlock() knows to relayout positioned objects because of |
| // a height change, we revert our height back to the intrinsic height before returning. |
| if (haveLineClamp && clampedContent.renderer) { |
| auto contentOffset = [&] { |
| auto* clampedRenderer = clampedContent.renderer.get(); |
| auto contentLogicalTop = clampedRenderer->logicalTop() + clampedRenderer->contentBoxLocation().y(); |
| for (auto* ancestor = clampedRenderer->containingBlock(); ancestor; ancestor = ancestor->containingBlock()) { |
| if (ancestor == this) |
| return contentLogicalTop; |
| contentLogicalTop += ancestor->logicalTop(); |
| } |
| ASSERT_NOT_REACHED(); |
| return contentBoxLocation().y(); |
| }; |
| auto usedHeight = height(); |
| auto clampedHeight = contentOffset() + clampedContent.contentHeight + borderBottom() + paddingBottom(); |
| setHeight(clampedHeight); |
| updateLogicalHeight(); |
| if (clampedHeight != height()) |
| setHeight(heightSpecified ? oldHeight : usedHeight); |
| } else if (heightSpecified) |
| setHeight(oldHeight); |
| } |
| |
| static size_t lineCountFor(const RenderBlockFlow& blockFlow) |
| { |
| if (blockFlow.childrenInline()) |
| return blockFlow.lineCount(); |
| |
| size_t count = 0; |
| for (auto& child : childrenOfType<RenderBlockFlow>(blockFlow)) { |
| if (blockFlow.isFloatingOrOutOfFlowPositioned() || !blockFlow.style().height().isAuto()) |
| continue; |
| count += lineCountFor(child); |
| } |
| return count; |
| } |
| |
| static RenderBlockFlow* blockContainerForLastFormattedLine(const RenderBlock& enclosingBlockContainer) |
| { |
| for (auto* child = enclosingBlockContainer.lastChild(); child; child = child->previousSibling()) { |
| CheckedPtr blockContainer = dynamicDowncast<RenderBlock>(*child); |
| if (!blockContainer) |
| continue; |
| if (auto* descendantRoot = blockContainerForLastFormattedLine(*blockContainer)) |
| return descendantRoot; |
| if (CheckedPtr blockFlow = dynamicDowncast<RenderBlockFlow>(*blockContainer); blockFlow && blockFlow->hasContentfulInlineLine()) |
| return blockFlow.unsafeGet(); |
| } |
| return { }; |
| } |
| |
| RenderDeprecatedFlexibleBox::ClampedContent RenderDeprecatedFlexibleBox::applyLineClamp(FlexBoxIterator& iterator, RelayoutChildren relayoutChildren) |
| { |
| auto initialize = [&] { |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| child->clearOverridingSize(); |
| if (relayoutChildren == RelayoutChildren::Yes || (child->isBlockLevelReplacedOrAtomicInline() && (child->style().width().isPercentOrCalculated() || child->style().height().isPercentOrCalculated())) |
| || (child->style().height().isAuto() && is<RenderBlockFlow>(*child))) { |
| child->setChildNeedsLayout(MarkOnlyThis); |
| |
| // Dirty all the positioned objects. |
| if (CheckedPtr blockFlow = dynamicDowncast<RenderBlockFlow>(*child)) |
| blockFlow->markOutOfFlowBoxesForLayout(); |
| } |
| } |
| }; |
| initialize(); |
| |
| auto& layoutState = *view().frameView().layoutContext().layoutState(); |
| auto ancestorLineClamp = layoutState.legacyLineClamp(); |
| auto restoreAncestorLineClamp = makeScopeExit([&] { |
| layoutState.setLegacyLineClamp(ancestorLineClamp); |
| }); |
| |
| auto lineCountForLineClamp = WTF::switchOn(style().lineClamp(), |
| [](const CSS::Keyword::None&) -> size_t { |
| ASSERT_NOT_REACHED(); |
| return 1; |
| }, |
| [](const Style::WebkitLineClamp::Integer& integer) -> size_t { |
| return integer.value; |
| }, |
| [&](const Style::WebkitLineClamp::Percentage& percentage) -> size_t { |
| size_t numberOfLines = 0; |
| for (auto* child = iterator.first(); child; child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| child->layoutIfNeeded(); |
| if (auto* blockFlow = dynamicDowncast<RenderBlockFlow>(*child)) |
| numberOfLines += lineCountFor(*blockFlow); |
| // FIXME: This should be turned into a partial damage. |
| child->setChildNeedsLayout(MarkOnlyThis); |
| } |
| return std::max<size_t>(1, (numberOfLines + 1) * percentage.value / 100.f); |
| } |
| ); |
| |
| layoutState.setLegacyLineClamp(RenderLayoutState::LegacyLineClamp { lineCountForLineClamp, { }, { }, { } }); |
| for (auto* child = iterator.first(); child; child = iterator.next()) { |
| if (child->isOutOfFlowPositioned()) |
| continue; |
| |
| child->markForPaginationRelayoutIfNeeded(); |
| child->layoutIfNeeded(); |
| } |
| if (auto* lastRoot = blockContainerForLastFormattedLine(*this)) { |
| if (auto* inlineLayout = lastRoot->inlineLayout(); inlineLayout && inlineLayout->hasEllipsisInBlockDirectionOnLastFormattedLine()) { |
| auto currentLineClamp = layoutState.legacyLineClamp(); |
| |
| // Let line-clamp logic run but make sure no clamping happens (it's needed to make sure certain features are disabled like ellipsis in inline direction). |
| layoutState.setLegacyLineClamp(RenderLayoutState::LegacyLineClamp { inlineLayout->lineCount() + 1, { }, { }, { } }); |
| lastRoot->setChildNeedsLayout(MarkOnlyThis); |
| lastRoot->layoutIfNeeded(); |
| |
| layoutState.setLegacyLineClamp(currentLineClamp); |
| } |
| } |
| |
| auto lineClamp = *layoutState.legacyLineClamp(); |
| if (!lineClamp.clampedContentLogicalHeight) { |
| // We've managed to run line clamping but it came back with no clamped content (i.e. there are fewer lines than the line-clamp limit). |
| return { }; |
| } |
| return { *lineClamp.clampedContentLogicalHeight, lineClamp.clampedRenderer }; |
| } |
| |
| void RenderDeprecatedFlexibleBox::clearLineClamp() |
| { |
| FlexBoxIterator iterator(this); |
| for (RenderBox* child = iterator.first(); child; child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| child->clearOverridingSize(); |
| if ((child->isBlockLevelReplacedOrAtomicInline() && (child->style().width().isPercentOrCalculated() || child->style().height().isPercentOrCalculated())) |
| || (child->style().height().isAuto() && is<RenderBlockFlow>(*child))) { |
| child->setChildNeedsLayout(); |
| |
| if (CheckedPtr blockFlow = dynamicDowncast<RenderBlockFlow>(*child)) |
| blockFlow->markOutOfFlowBoxesForLayout(); |
| } |
| } |
| } |
| |
| void RenderDeprecatedFlexibleBox::placeChild(RenderBox* child, const LayoutPoint& location, LayoutSize* childLayoutDelta) |
| { |
| // Place the child and track the layout delta so we can apply it if we do another layout. |
| if (childLayoutDelta) |
| *childLayoutDelta += LayoutSize(child->x() - location.x(), child->y() - location.y()); |
| child->setLocation(location); |
| } |
| |
| LayoutUnit RenderDeprecatedFlexibleBox::allowedChildFlex(RenderBox* child, bool expanding, unsigned int group) |
| { |
| if (childDoesNotAffectWidthOrFlexing(child) || child->style().boxFlex() == 0.0f || child->style().boxFlexGroup() != group) |
| return 0; |
| |
| if (expanding) { |
| if (isHorizontal()) { |
| // FIXME: For now just handle fixed values. |
| LayoutUnit maxWidth = LayoutUnit::max(); |
| LayoutUnit width = contentWidthForChild(child); |
| if (auto fixedMaxWidth = child->style().maxWidth().tryFixed()) |
| maxWidth = fixedMaxWidth->resolveZoom(child->style().usedZoomForLength()); |
| else if (child->style().maxWidth().isIntrinsicKeyword()) |
| maxWidth = child->maxPreferredLogicalWidth(); |
| else if (child->style().maxWidth().isMinIntrinsic()) |
| maxWidth = child->minPreferredLogicalWidth(); |
| if (maxWidth == LayoutUnit::max()) |
| return maxWidth; |
| return std::max<LayoutUnit>(0, maxWidth - width); |
| } else { |
| // FIXME: For now just handle fixed values. |
| LayoutUnit maxHeight = LayoutUnit::max(); |
| LayoutUnit height = contentHeightForChild(child); |
| if (auto fixedMaxHeight = child->style().maxHeight().tryFixed()) |
| maxHeight = fixedMaxHeight->resolveZoom(child->style().usedZoomForLength()); |
| if (maxHeight == LayoutUnit::max()) |
| return maxHeight; |
| return std::max<LayoutUnit>(0, maxHeight - height); |
| } |
| } |
| |
| // FIXME: For now just handle fixed values. |
| if (isHorizontal()) { |
| LayoutUnit minWidth = child->minPreferredLogicalWidth(); |
| LayoutUnit width = contentWidthForChild(child); |
| if (auto fixedMinWidth = child->style().minWidth().tryFixed()) |
| minWidth = fixedMinWidth->resolveZoom(child->style().usedZoomForLength()); |
| else if (child->style().minWidth().isIntrinsicKeyword()) |
| minWidth = child->maxPreferredLogicalWidth(); |
| else if (child->style().minWidth().isMinIntrinsic()) |
| minWidth = child->minPreferredLogicalWidth(); |
| else if (child->style().minWidth().isAuto()) |
| minWidth = 0; |
| |
| LayoutUnit allowedShrinkage = std::min<LayoutUnit>(0, minWidth - width); |
| return allowedShrinkage; |
| } else { |
| auto& minHeight = child->style().minHeight(); |
| if (auto fixedMinHeight = minHeight.tryFixed()) { |
| LayoutUnit minHeight { fixedMinHeight->resolveZoom(child->style().usedZoomForLength()) }; |
| LayoutUnit height = contentHeightForChild(child); |
| LayoutUnit allowedShrinkage = std::min<LayoutUnit>(0, minHeight - height); |
| return allowedShrinkage; |
| } |
| if (minHeight.isAuto()) { |
| LayoutUnit minHeight { 0 }; |
| LayoutUnit height = contentHeightForChild(child); |
| LayoutUnit allowedShrinkage = std::min<LayoutUnit>(0, minHeight - height); |
| return allowedShrinkage; |
| } |
| } |
| |
| return 0; |
| } |
| |
| ASCIILiteral RenderDeprecatedFlexibleBox::renderName() const |
| { |
| if (isFloating()) |
| return "RenderDeprecatedFlexibleBox (floating)"_s; |
| if (isOutOfFlowPositioned()) |
| return "RenderDeprecatedFlexibleBox (positioned)"_s; |
| // FIXME: Temporary hack while the new generated content system is being implemented. |
| if (isPseudoElement()) |
| return "RenderDeprecatedFlexibleBox (generated)"_s; |
| if (isAnonymous()) |
| return "RenderDeprecatedFlexibleBox (generated)"_s; |
| if (isRelativelyPositioned()) |
| return "RenderDeprecatedFlexibleBox (relative positioned)"_s; |
| return "RenderDeprecatedFlexibleBox"_s; |
| } |
| |
| } // namespace WebCore |