blob: 06a64737feb2c50b4eaf658c31955c7190264d5a [file] [log] [blame]
/*
* Copyright (C) 2011, 2022 Apple Inc. All rights reserved.
* Copyright (C) 2013-2017 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "RenderGrid.h"
#include "GridArea.h"
#include "GridLayoutFunctions.h"
#include "GridMasonryLayout.h"
#include "GridTrackSizingAlgorithm.h"
#include "HitTestLocation.h"
#include "LayoutRepainter.h"
#include "RenderChildIterator.h"
#include "RenderElementInlines.h"
#include "RenderLayer.h"
#include "RenderLayoutState.h"
#include "RenderObjectInlines.h"
#include "RenderTreeBuilder.h"
#include "RenderView.h"
#include "StyleGridPositionsResolver.h"
#include "StylePrimitiveNumericTypes+Evaluation.h"
#include <wtf/Range.h>
#include <wtf/Scope.h>
#include <wtf/SetForScope.h>
#include <wtf/TZoneMallocInlines.h>
namespace WebCore {
WTF_MAKE_TZONE_OR_ISO_ALLOCATED_IMPL(RenderGrid);
enum class TrackSizeRestriction : uint8_t {
AllowInfinity,
ForbidInfinity,
};
RenderGrid::RenderGrid(Element& element, RenderStyle&& style)
: RenderBlock(Type::Grid, element, WTFMove(style), { })
, m_grid(*this)
, m_trackSizingAlgorithm(this, currentGrid())
, m_masonryLayout(*this)
{
ASSERT(isRenderGrid());
// All of our children must be block level.
setChildrenInline(false);
}
RenderGrid::~RenderGrid() = default;
bool RenderGrid::isExtrinsicallySized() const
{
auto& gridStyle = style();
auto allTracksAreExtrinsicallySized = [&] {
for (auto& column : gridStyle.gridTemplateColumns().sizes) {
if (column.isContentSized())
return false;
}
for (auto& row : gridStyle.gridTemplateRows().sizes) {
if (row.isContentSized())
return false;
}
return true;
};
// Since we currently only check if the grid's logical width is auto, it being
// extrinsically sized in this regard depends on the formatting context it
// participates in. For now we only check and allow if it participates in block
// layout since that is simple.
auto participatesInBlockLayout = [&] {
auto* containingBlock = this->containingBlock();
return containingBlock && containingBlock->isBlockContainer() && !containingBlock->childrenInline();
};
if (!gridStyle.logicalWidth().isAuto()
|| !participatesInBlockLayout()
|| !gridStyle.logicalHeight().isFixed()
|| !allTracksAreExtrinsicallySized()
|| gridStyle.hasAspectRatio()
|| isSubgrid()
|| isMasonry())
return false;
for (auto& gridItem : childrenOfType<RenderBox>(*this)) {
// FIXME: If we do not need to perform item placement we should be able
// to check any implicitly created tracks as well.
if (!isPlacedWithinExtrinsicallySizedExplicitTracks(gridItem))
return false;
}
return true;
}
StyleSelfAlignmentData RenderGrid::selfAlignmentForGridItem(Style::GridTrackSizingDirection alignmentContextType, const RenderBox& gridItem, const RenderStyle* gridStyle) const
{
return alignmentContextType == Style::GridTrackSizingDirection::Columns ? justifySelfForGridItem(gridItem, StretchingMode::Any, gridStyle) : alignSelfForGridItem(gridItem, StretchingMode::Any, gridStyle);
}
bool RenderGrid::selfAlignmentChangedToStretch(Style::GridTrackSizingDirection alignmentContextType, const RenderStyle& oldStyle, const RenderStyle& newStyle, const RenderBox& gridItem) const
{
return selfAlignmentForGridItem(alignmentContextType, gridItem, &oldStyle).position() != ItemPosition::Stretch
&& selfAlignmentForGridItem(alignmentContextType, gridItem, &newStyle).position() == ItemPosition::Stretch;
}
bool RenderGrid::selfAlignmentChangedFromStretch(Style::GridTrackSizingDirection alignmentContextType, const RenderStyle& oldStyle, const RenderStyle& newStyle, const RenderBox& gridItem) const
{
return selfAlignmentForGridItem(alignmentContextType, gridItem, &oldStyle).position() == ItemPosition::Stretch
&& selfAlignmentForGridItem(alignmentContextType, gridItem, &newStyle).position() != ItemPosition::Stretch;
}
void RenderGrid::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
{
RenderBlock::styleDidChange(diff, oldStyle);
if (!oldStyle || diff != StyleDifference::Layout)
return;
m_intrinsicLogicalHeightsForRowSizingFirstPass.reset();
const RenderStyle& newStyle = this->style();
auto hasDifferentTrackSizes = [&newStyle, &oldStyle](Style::GridTrackSizingDirection direction) {
return newStyle.gridTemplateList(direction).sizes != oldStyle->gridTemplateList(direction).sizes;
};
if (hasDifferentTrackSizes(Style::GridTrackSizingDirection::Columns) || hasDifferentTrackSizes(Style::GridTrackSizingDirection::Rows)) {
for (auto& gridItem : childrenOfType<RenderBox>(*this))
gridItem.setChildNeedsLayout();
}
if (oldStyle->resolvedAlignItems(selfAlignmentNormalBehavior(this)).position() == ItemPosition::Stretch) {
// Style changes on the grid container implying stretching (to-stretch) or
// shrinking (from-stretch) require the affected items to be laid out again.
// These logic only applies to 'stretch' since the rest of the alignment
// values don't change the size of the box.
// In any case, the items' overrideSize will be cleared and recomputed (if
// necessary) as part of the Grid layout logic, triggered by this style
// change.
for (auto& gridItem : childrenOfType<RenderBox>(*this)) {
if (gridItem.isOutOfFlowPositioned())
continue;
if (selfAlignmentChangedToStretch(Style::GridTrackSizingDirection::Columns, *oldStyle, newStyle, gridItem)
|| selfAlignmentChangedFromStretch(Style::GridTrackSizingDirection::Columns, *oldStyle, newStyle, gridItem)
|| selfAlignmentChangedToStretch(Style::GridTrackSizingDirection::Rows, *oldStyle, newStyle, gridItem)
|| selfAlignmentChangedFromStretch(Style::GridTrackSizingDirection::Rows, *oldStyle, newStyle, gridItem)) {
gridItem.setNeedsLayout();
}
}
}
auto subgridDidChange = this->subgridDidChange(*oldStyle);
auto isSubgridWithIndependentFormattingContextChange = [&] {
if (newStyle.gridTemplateRows().subgrid || newStyle.gridTemplateColumns().subgrid)
return establishesIndependentFormattingContextIgnoringDisplayType(*oldStyle) != establishesIndependentFormattingContextIgnoringDisplayType(style());
return false;
};
if (explicitGridDidResize(*oldStyle)
|| namedGridLinesDefinitionDidChange(*oldStyle)
|| implicitGridLinesDefinitionDidChange(*oldStyle)
|| oldStyle->gridAutoFlow() != style().gridAutoFlow()
|| style().gridTemplateColumns().autoRepeatSizes.size()
|| style().gridTemplateRows().autoRepeatSizes.size()
|| subgridDidChange == SubgridDidChange::Yes
|| isSubgridWithIndependentFormattingContextChange())
setNeedsItemPlacement(subgridDidChange);
}
SubgridDidChange RenderGrid::subgridDidChange(const RenderStyle& oldStyle) const
{
if (oldStyle.gridTemplateRows().subgrid != style().gridTemplateRows().subgrid
|| oldStyle.gridTemplateColumns().subgrid != style().gridTemplateColumns().subgrid)
return SubgridDidChange::Yes;
return SubgridDidChange::No;
}
bool RenderGrid::explicitGridDidResize(const RenderStyle& oldStyle) const
{
auto& oldGridTemplateColumns = oldStyle.gridTemplateColumns();
auto& oldGridTemplateRows = oldStyle.gridTemplateRows();
auto& oldGridTemplateAreas = oldStyle.gridTemplateAreas();
auto& newGridTemplateColumns = style().gridTemplateColumns();
auto& newGridTemplateRows = style().gridTemplateRows();
auto& newGridTemplateAreas = style().gridTemplateAreas();
return oldGridTemplateColumns.sizes.size() != newGridTemplateColumns.sizes.size()
|| oldGridTemplateColumns.autoRepeatSizes.size() != newGridTemplateColumns.autoRepeatSizes.size()
|| oldGridTemplateRows.sizes.size() != newGridTemplateRows.sizes.size()
|| oldGridTemplateRows.autoRepeatSizes.size() != newGridTemplateRows.autoRepeatSizes.size()
|| oldGridTemplateAreas.map.columnCount != newGridTemplateAreas.map.columnCount
|| oldGridTemplateAreas.map.rowCount != newGridTemplateAreas.map.rowCount;
}
bool RenderGrid::namedGridLinesDefinitionDidChange(const RenderStyle& oldStyle) const
{
return oldStyle.gridTemplateRows().namedLines.map != style().gridTemplateRows().namedLines.map
|| oldStyle.gridTemplateColumns().namedLines.map != style().gridTemplateColumns().namedLines.map;
}
bool RenderGrid::implicitGridLinesDefinitionDidChange(const RenderStyle& oldStyle) const
{
auto& oldGridTemplateAreas = oldStyle.gridTemplateAreas();
auto& newGridTemplateAreas = style().gridTemplateAreas();
return oldGridTemplateAreas.implicitNamedGridRowLines != newGridTemplateAreas.implicitNamedGridRowLines
|| oldGridTemplateAreas.implicitNamedGridColumnLines != newGridTemplateAreas.implicitNamedGridColumnLines;
}
// This method optimizes the gutters computation by skipping the available size
// call if gaps are fixed size (it's only needed for percentages).
std::optional<LayoutUnit> RenderGrid::availableSpaceForGutters(Style::GridTrackSizingDirection direction) const
{
if (!style().gap(direction).isPercentOrCalculated())
return std::nullopt;
return direction == Style::GridTrackSizingDirection::Columns ? contentBoxLogicalWidth() : contentBoxLogicalHeight();
}
void RenderGrid::computeTrackSizesForDefiniteSize(Style::GridTrackSizingDirection direction, LayoutUnit availableSpace, GridLayoutState& gridLayoutState)
{
auto autoMarginResolutionScope = SetForScope(m_isComputingTrackSizes, true);
m_trackSizingAlgorithm.run(direction, numTracks(direction), SizingOperation::TrackSizing, availableSpace, gridLayoutState);
ASSERT(m_trackSizingAlgorithm.tracksAreWiderThanMinTrackBreadth());
}
void RenderGrid::repeatTracksSizingIfNeeded(LayoutUnit availableSpaceForColumns, LayoutUnit availableSpaceForRows, GridLayoutState& gridLayoutState)
{
// In orthogonal flow cases column track's size is determined by using the computed
// row track's size, which it was estimated during the first cycle of the sizing
// algorithm. Hence we need to repeat computeUsedBreadthOfGridTracks for both,
// columns and rows, to determine the final values.
// TODO (lajava): orthogonal flows is just one of the cases which may require
// a new cycle of the sizing algorithm; there may be more. In addition, not all the
// cases with orthogonal flows require this extra cycle; we need a more specific
// condition to detect whether grid item's min-content contribution has changed or not.
// The complication with repeating the track sizing algorithm for flex max-sizing is that
// it might change a grid item's status of participating in Baseline Alignment for
// a cyclic sizing dependency case, which should be definitively excluded. See
// https://github.com/w3c/csswg-drafts/issues/3046 for details.
// FIXME: we are avoiding repeating the track sizing algorithm for grid item with baseline alignment
// here in the case of using flex max-sizing functions. We probably also need to investigate whether
// it is applicable for the case of percent-sized rows with indefinite height as well.
if (gridLayoutState.needsSecondTrackSizingPass() || m_trackSizingAlgorithm.hasAnyPercentSizedRowsIndefiniteHeight() || (m_trackSizingAlgorithm.hasAnyFlexibleMaxTrackBreadth() && !m_trackSizingAlgorithm.hasAnyBaselineAlignmentItem()) || gridLayoutState.hasAspectRatioBlockSizeDependentItem()) {
populateGridPositionsForDirection(m_trackSizingAlgorithm, Style::GridTrackSizingDirection::Rows);
computeTrackSizesForDefiniteSize(Style::GridTrackSizingDirection::Columns, availableSpaceForColumns, gridLayoutState);
m_offsetBetweenColumns = computeContentPositionAndDistributionOffset(Style::GridTrackSizingDirection::Columns, m_trackSizingAlgorithm.freeSpace(Style::GridTrackSizingDirection::Columns).value(), nonCollapsedTracks(Style::GridTrackSizingDirection::Columns));
computeTrackSizesForDefiniteSize(Style::GridTrackSizingDirection::Rows, availableSpaceForRows, gridLayoutState);
m_offsetBetweenRows = computeContentPositionAndDistributionOffset(Style::GridTrackSizingDirection::Rows, m_trackSizingAlgorithm.freeSpace(Style::GridTrackSizingDirection::Rows).value(), nonCollapsedTracks(Style::GridTrackSizingDirection::Rows));
}
}
bool RenderGrid::canPerformSimplifiedLayout() const
{
// We cannot perform a simplified layout if we need to position the items and we have some
// positioned items to be laid out.
if (currentGrid().needsItemsPlacement() && outOfFlowChildNeedsLayout())
return false;
return RenderBlock::canPerformSimplifiedLayout();
}
enum class AlignmentContextTypes : uint8_t {
Columns = 1 << 0,
Rows = 1 << 1
};
template<typename F>
static void cacheBaselineAlignedGridItems(const RenderGrid& grid, GridTrackSizingAlgorithm& algorithm, OptionSet<AlignmentContextTypes> alignmentContextTypes, F& callback, bool cachingRowSubgridsForRootGrid)
{
ASSERT_IMPLIES(cachingRowSubgridsForRootGrid, !algorithm.renderGrid()->isSubgridRows() && (algorithm.renderGrid() == &grid || grid.isSubgridOf(GridLayoutFunctions::flowAwareDirectionForGridItem(*algorithm.renderGrid(), grid, Style::GridTrackSizingDirection::Rows), *algorithm.renderGrid())));
for (auto& gridItem : childrenOfType<RenderBox>(grid)) {
if (gridItem.isOutOfFlowPositioned() || gridItem.isLegend())
continue;
callback(const_cast<RenderBox*>(&gridItem));
// We keep a cache of items with baseline as alignment values so that we only compute the baseline shims for
// such items. This cache is needed for performance related reasons due to the cost of evaluating the item's
// participation in a baseline context during the track sizing algorithm.
OptionSet<AlignmentContextTypes> innerAlignmentContextTypes = { };
CheckedPtr inner = dynamicDowncast<RenderGrid>(gridItem);
if (alignmentContextTypes.contains(AlignmentContextTypes::Rows)) {
if (inner && inner->isSubgridInParentDirection(Style::GridTrackSizingDirection::Rows))
innerAlignmentContextTypes.add(GridLayoutFunctions::isOrthogonalGridItem(grid, gridItem) ? AlignmentContextTypes::Columns : AlignmentContextTypes::Rows);
else if (grid.isBaselineAlignmentForGridItem(gridItem, Style::GridTrackSizingDirection::Rows))
algorithm.cacheBaselineAlignedItem(gridItem, Style::GridTrackSizingDirection::Rows, cachingRowSubgridsForRootGrid);
}
if (alignmentContextTypes.contains(AlignmentContextTypes::Columns)) {
if (inner && inner->isSubgridInParentDirection(Style::GridTrackSizingDirection::Columns))
innerAlignmentContextTypes.add(GridLayoutFunctions::isOrthogonalGridItem(grid, gridItem) ? AlignmentContextTypes::Rows : AlignmentContextTypes::Columns);
else if (grid.isBaselineAlignmentForGridItem(gridItem, Style::GridTrackSizingDirection::Columns))
algorithm.cacheBaselineAlignedItem(gridItem, Style::GridTrackSizingDirection::Columns, cachingRowSubgridsForRootGrid);
}
if (inner && cachingRowSubgridsForRootGrid)
cachingRowSubgridsForRootGrid = GridLayoutFunctions::isOrthogonalGridItem(*algorithm.renderGrid(), *inner) ? inner->isSubgridColumns() : inner->isSubgridRows();
if (innerAlignmentContextTypes)
cacheBaselineAlignedGridItems(*inner, algorithm, innerAlignmentContextTypes, callback, cachingRowSubgridsForRootGrid);
}
}
Vector<RenderBox*> RenderGrid::computeAspectRatioDependentAndBaselineItems(GridLayoutState& gridLayoutState)
{
Vector<RenderBox*> dependentGridItems;
m_baselineItemsCached = true;
auto computeOrthogonalAndDependentItems = [&](RenderBox* gridItem) {
// For a grid item that has an aspect-ratio and block-constraints such as the relative logical height,
// when the grid width is auto, we may need get the real grid width before laying out the item.
if (GridLayoutFunctions::isAspectRatioBlockSizeDependentGridItem(*gridItem) && (style().logicalWidth().isAuto() || style().logicalWidth().isMinContent() || style().logicalWidth().isMaxContent())) {
dependentGridItems.append(gridItem);
gridLayoutState.setHasAspectRatioBlockSizeDependentItem();
}
};
cacheBaselineAlignedGridItems(*this, m_trackSizingAlgorithm, { AlignmentContextTypes::Columns, AlignmentContextTypes::Rows }, computeOrthogonalAndDependentItems, !isSubgridRows());
return dependentGridItems;
}
bool RenderGrid::canSetColumnAxisStretchRequirementForItem(const RenderBox& gridItem) const
{
auto gridItemBlockFlowDirection = GridLayoutFunctions::flowAwareDirectionForGridItem(*this, gridItem, Style::GridTrackSizingDirection::Rows);
return gridItemBlockFlowDirection == Style::GridTrackSizingDirection::Rows && allowedToStretchGridItemAlongColumnAxis(gridItem);
}
void RenderGrid::computeLayoutRequirementsForItemsBeforeLayout(GridLayoutState& gridLayoutState) const
{
for (auto& gridItem : childrenOfType<RenderBox>(*this)) {
auto gridItemAlignSelf = alignSelfForGridItem(gridItem).position();
if (GridLayoutFunctions::isGridItemInlineSizeDependentOnBlockConstraints(gridItem, *this, gridItemAlignSelf)) {
gridLayoutState.setNeedsSecondTrackSizingPass();
gridLayoutState.setLayoutRequirementForGridItem(gridItem, ItemLayoutRequirement::MinContentContributionForSecondColumnPass);
}
if (!gridItem.needsLayout() || gridItem.isOutOfFlowPositioned() || gridItem.isExcludedFromNormalLayout())
continue;
if (canSetColumnAxisStretchRequirementForItem(gridItem))
gridLayoutState.setLayoutRequirementForGridItem(gridItem, ItemLayoutRequirement::NeedsColumnAxisStretchAlignment);
}
}
void RenderGrid::layoutBlock(RelayoutChildren relayoutChildren, LayoutUnit)
{
ASSERT(needsLayout());
auto postLayoutTasks = WTF::makeScopeExit([&] {
m_outOfFlowItemColumn.clear();
m_outOfFlowItemRow.clear();
});
if (relayoutChildren ==RelayoutChildren::No && simplifiedLayout())
return;
// The layoutBlock was handling the layout of both the grid and masonry implementations.
// This caused a huge amount of branching code to handle masonry specific cases. Splitting up the code
// to layout will simplify both implementations.
if (!isMasonry())
layoutGrid(relayoutChildren);
else
layoutMasonry(relayoutChildren);
}
static void clearGridItemOverridingSizesBeforeLayout(RenderGrid& renderGrid)
{
// Grid's layout logic controls the grid item's override content size, hence we need to
// clear any override set previously, so it doesn't interfere in current layout
// execution.
for (auto& gridItem : childrenOfType<RenderBox>(renderGrid)) {
if (gridItem.isOutOfFlowPositioned() || gridItem.isLegend())
continue;
gridItem.clearOverridingSize();
}
}
bool RenderGrid::hasDefiniteLogicalHeight() const
{
// FIXME: We should use RenderBlock::hasDefiniteLogicalHeight() only but it does not work for out of flow content.
return RenderBlock::hasDefiniteLogicalHeight() || overridingBorderBoxLogicalHeight() || computeContentLogicalHeight(style().logicalHeight(), std::nullopt) || shouldComputeLogicalHeightFromAspectRatio();
}
const std::optional<LayoutUnit> RenderGrid::availableLogicalHeightForContentBox() const
{
if (!hasDefiniteLogicalHeight())
return { };
if (auto overridingLogicalHeight = this->overridingBorderBoxLogicalHeight())
return constrainContentBoxLogicalHeightByMinMax(*overridingLogicalHeight - borderAndPaddingLogicalHeight(), { });
return availableLogicalHeight(AvailableLogicalHeightType::ExcludeMarginBorderPadding);
}
void RenderGrid::layoutGrid(RelayoutChildren relayoutChildren)
{
LayoutRepainter repainter(*this);
{
LayoutStateMaintainer statePusher(*this, locationOffset(), isTransformed() || hasReflection() || writingMode().isBlockFlipped());
GridLayoutState gridLayoutState;
updateIntrinsicLogicalHeightsForRowSizingFirstPassCacheAvailability();
clearGridItemOverridingSizesBeforeLayout(*this);
computeLayoutRequirementsForItemsBeforeLayout(gridLayoutState);
preparePaginationBeforeBlockLayout(relayoutChildren);
beginUpdateScrollInfoAfterLayoutTransaction();
LayoutSize previousSize = size();
auto aspectRatioBlockSizeDependentGridItems = computeAspectRatioDependentAndBaselineItems(gridLayoutState);
resetLogicalHeightBeforeLayoutIfNeeded();
updateLogicalWidth();
// 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);
LayoutUnit availableSpaceForColumns = contentBoxLogicalWidth();
placeItemsOnGrid(availableSpaceForColumns);
m_trackSizingAlgorithm.setAvailableSpace(Style::GridTrackSizingDirection::Columns, availableSpaceForColumns);
performPreLayoutForGridItems(m_trackSizingAlgorithm, ShouldUpdateGridAreaLogicalSize::Yes);
// 1. First, the track sizing algorithm is used to resolve the sizes of the grid columns. At this point the
// logical width is always definite as the above call to updateLogicalWidth() properly resolves intrinsic
// sizes. We cannot do the same for heights though because many code paths inside updateLogicalHeight() require
// a previous call to setLogicalHeight() to resolve heights properly (like for positioned items for example).
computeTrackSizesForDefiniteSize(Style::GridTrackSizingDirection::Columns, availableSpaceForColumns, gridLayoutState);
// 1.5. Compute Content Distribution offsets for column tracks
m_offsetBetweenColumns = computeContentPositionAndDistributionOffset(Style::GridTrackSizingDirection::Columns, m_trackSizingAlgorithm.freeSpace(Style::GridTrackSizingDirection::Columns).value(), nonCollapsedTracks(Style::GridTrackSizingDirection::Columns));
// 2. Next, the track sizing algorithm resolves the sizes of the grid rows,
// using the grid column sizes calculated in the previous step.
auto availableLogicalHeightForContentBox = this->availableLogicalHeightForContentBox();
bool shouldRecomputeHeight = false;
if (!availableLogicalHeightForContentBox) {
computeTrackSizesForIndefiniteSize(m_trackSizingAlgorithm, Style::GridTrackSizingDirection::Rows, gridLayoutState);
if (shouldApplySizeContainment())
shouldRecomputeHeight = true;
} else
computeTrackSizesForDefiniteSize(Style::GridTrackSizingDirection::Rows, *availableLogicalHeightForContentBox, gridLayoutState);
LayoutUnit trackBasedLogicalHeight = borderAndPaddingLogicalHeight() + scrollbarLogicalHeight();
if (auto size = explicitIntrinsicInnerLogicalSize(Style::GridTrackSizingDirection::Rows))
trackBasedLogicalHeight += size.value();
else
trackBasedLogicalHeight += m_trackSizingAlgorithm.computeTrackBasedSize();
if (shouldRecomputeHeight)
computeTrackSizesForDefiniteSize(Style::GridTrackSizingDirection::Rows, trackBasedLogicalHeight, gridLayoutState);
setLogicalHeight(trackBasedLogicalHeight);
updateLogicalHeight();
// Once grid's indefinite height is resolved, we can compute the
// available free space for Content Alignment.
if (!availableLogicalHeightForContentBox)
m_trackSizingAlgorithm.setFreeSpace(Style::GridTrackSizingDirection::Rows, logicalHeight() - trackBasedLogicalHeight);
// 2.5. Compute Content Distribution offsets for rows tracks
m_offsetBetweenRows = computeContentPositionAndDistributionOffset(Style::GridTrackSizingDirection::Rows, m_trackSizingAlgorithm.freeSpace(Style::GridTrackSizingDirection::Rows).value(), nonCollapsedTracks(Style::GridTrackSizingDirection::Rows));
if (!aspectRatioBlockSizeDependentGridItems.isEmpty()) {
updateGridAreaForAspectRatioItems(aspectRatioBlockSizeDependentGridItems, gridLayoutState);
updateLogicalWidth();
}
// 3. If the min-content contribution of any grid items have changed based on the row
// sizes calculated in step 2, steps 1 and 2 are repeated with the new min-content
// contribution (once only).
repeatTracksSizingIfNeeded(availableSpaceForColumns, contentBoxLogicalHeight(), gridLayoutState);
// Grid container should have the minimum height of a line if it's editable. That does not affect track sizing though.
if (hasLineIfEmpty()) {
LayoutUnit minHeightForEmptyLine = borderAndPaddingLogicalHeight()
+ lineHeight()
+ scrollbarLogicalHeight();
setLogicalHeight(std::max(logicalHeight(), minHeightForEmptyLine));
}
layoutGridItems(gridLayoutState);
endAndCommitUpdateScrollInfoAfterLayoutTransaction();
if (size() != previousSize)
relayoutChildren = RelayoutChildren::Yes;
if (isDocumentElementRenderer())
layoutOutOfFlowBoxes(RelayoutChildren::Yes);
else
layoutOutOfFlowBoxes(relayoutChildren);
m_trackSizingAlgorithm.reset();
computeOverflow(layoutOverflowLogicalBottom(*this));
updateDescendantTransformsAfterLayout();
}
updateLayerTransform();
// Update our scroll information if we're overflow:auto/scroll/hidden now that we know if
// we overflow or not.
updateScrollInfoAfterLayout();
repainter.repaintAfterLayout();
clearNeedsLayout();
m_trackSizingAlgorithm.clearBaselineItemsCache();
m_baselineItemsCached = false;
}
void RenderGrid::layoutMasonry(RelayoutChildren relayoutChildren)
{
LayoutRepainter repainter(*this);
{
LayoutStateMaintainer statePusher(*this, locationOffset(), isTransformed() || hasReflection() || writingMode().isBlockFlipped());
GridLayoutState gridLayoutState;
clearGridItemOverridingSizesBeforeLayout(*this);
preparePaginationBeforeBlockLayout(relayoutChildren);
beginUpdateScrollInfoAfterLayoutTransaction();
LayoutSize previousSize = size();
// FIXME: We should use RenderBlock::hasDefiniteLogicalHeight() only but it does not work for positioned stuff.
// FIXME: Consider caching the hasDefiniteLogicalHeight value throughout the layout.
// FIXME: We might need to cache the hasDefiniteLogicalHeight if the call of RenderBlock::hasDefiniteLogicalHeight() causes a relevant performance regression.
bool hasDefiniteLogicalHeight = RenderBlock::hasDefiniteLogicalHeight() || overridingBorderBoxLogicalHeight() || computeContentLogicalHeight(style().logicalHeight(), std::nullopt);
auto aspectRatioBlockSizeDependentGridItems = computeAspectRatioDependentAndBaselineItems(gridLayoutState);
resetLogicalHeightBeforeLayoutIfNeeded();
// 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);
updateLogicalWidth();
LayoutUnit availableSpaceForColumns = contentBoxLogicalWidth();
placeItemsOnGrid(availableSpaceForColumns);
m_trackSizingAlgorithm.setAvailableSpace(Style::GridTrackSizingDirection::Columns, availableSpaceForColumns);
performPreLayoutForGridItems(m_trackSizingAlgorithm, ShouldUpdateGridAreaLogicalSize::Yes);
// 1. First, the track sizing algorithm is used to resolve the sizes of the grid columns. At this point the
// logical width is always definite as the above call to updateLogicalWidth() properly resolves intrinsic
// sizes. We cannot do the same for heights though because many code paths inside updateLogicalHeight() require
// a previous call to setLogicalHeight() to resolve heights properly (like for positioned items for example).
computeTrackSizesForDefiniteSize(Style::GridTrackSizingDirection::Columns, availableSpaceForColumns, gridLayoutState);
// 1.5. Compute Content Distribution offsets for column tracks
m_offsetBetweenColumns = computeContentPositionAndDistributionOffset(Style::GridTrackSizingDirection::Columns, m_trackSizingAlgorithm.freeSpace(Style::GridTrackSizingDirection::Columns).value(), nonCollapsedTracks(Style::GridTrackSizingDirection::Columns));
// 2. Next, the track sizing algorithm resolves the sizes of the grid rows,
// using the grid column sizes calculated in the previous step.
bool shouldRecomputeHeight = false;
if (!hasDefiniteLogicalHeight) {
computeTrackSizesForIndefiniteSize(m_trackSizingAlgorithm, Style::GridTrackSizingDirection::Rows, gridLayoutState);
if (shouldApplySizeContainment())
shouldRecomputeHeight = true;
} else
computeTrackSizesForDefiniteSize(Style::GridTrackSizingDirection::Rows, availableLogicalHeight(AvailableLogicalHeightType::ExcludeMarginBorderPadding), gridLayoutState);
auto performMasonryPlacement = [&](const Style::GridTrackSizingDirection masonryAxisDirection) {
auto gridAxisDirection = masonryAxisDirection == Style::GridTrackSizingDirection::Rows ? Style::GridTrackSizingDirection::Columns : Style::GridTrackSizingDirection::Rows;
unsigned gridAxisTracksBeforeAutoPlacement = currentGrid().numTracks(gridAxisDirection);
m_masonryLayout.performMasonryPlacement(m_trackSizingAlgorithm, gridAxisTracksBeforeAutoPlacement, masonryAxisDirection, GridMasonryLayout::MasonryLayoutPhase::LayoutPhase);
};
if (areMasonryRows())
performMasonryPlacement(Style::GridTrackSizingDirection::Rows);
else if (areMasonryColumns())
performMasonryPlacement(Style::GridTrackSizingDirection::Columns);
LayoutUnit trackBasedLogicalHeight = borderAndPaddingLogicalHeight() + scrollbarLogicalHeight();
if (auto size = explicitIntrinsicInnerLogicalSize(Style::GridTrackSizingDirection::Rows))
trackBasedLogicalHeight += size.value();
else {
if (areMasonryRows())
trackBasedLogicalHeight += m_masonryLayout.gridContentSize();
else
trackBasedLogicalHeight += m_trackSizingAlgorithm.computeTrackBasedSize();
}
if (shouldRecomputeHeight)
computeTrackSizesForDefiniteSize(Style::GridTrackSizingDirection::Rows, trackBasedLogicalHeight, gridLayoutState);
setLogicalHeight(trackBasedLogicalHeight);
updateLogicalHeight();
// Once grid's indefinite height is resolved, we can compute the
// available free space for Content Alignment.
if (!hasDefiniteLogicalHeight || areMasonryRows())
m_trackSizingAlgorithm.setFreeSpace(Style::GridTrackSizingDirection::Rows, logicalHeight() - trackBasedLogicalHeight);
// 2.5. Compute Content Distribution offsets for rows tracks
m_offsetBetweenRows = computeContentPositionAndDistributionOffset(Style::GridTrackSizingDirection::Rows, m_trackSizingAlgorithm.freeSpace(Style::GridTrackSizingDirection::Rows).value(), nonCollapsedTracks(Style::GridTrackSizingDirection::Rows));
if (!aspectRatioBlockSizeDependentGridItems.isEmpty()) {
updateGridAreaForAspectRatioItems(aspectRatioBlockSizeDependentGridItems, gridLayoutState);
updateLogicalWidth();
}
// Grid container should have the minimum height of a line if it's editable. That does not affect track sizing though.
if (hasLineIfEmpty()) {
LayoutUnit minHeightForEmptyLine = borderAndPaddingLogicalHeight()
+ lineHeight()
+ scrollbarLogicalHeight();
setLogicalHeight(std::max(logicalHeight(), minHeightForEmptyLine));
}
layoutMasonryItems(gridLayoutState);
endAndCommitUpdateScrollInfoAfterLayoutTransaction();
if (size() != previousSize)
relayoutChildren = RelayoutChildren::Yes;
if (isDocumentElementRenderer())
layoutOutOfFlowBoxes(RelayoutChildren::Yes);
else
layoutOutOfFlowBoxes(relayoutChildren);
m_trackSizingAlgorithm.reset();
computeOverflow(layoutOverflowLogicalBottom(*this));
updateDescendantTransformsAfterLayout();
}
updateLayerTransform();
// Update our scroll information if we're overflow:auto/scroll/hidden now that we know if
// we overflow or not.
updateScrollInfoAfterLayout();
repainter.repaintAfterLayout();
clearNeedsLayout();
m_trackSizingAlgorithm.clearBaselineItemsCache();
m_baselineItemsCached = false;
}
LayoutUnit RenderGrid::gridGap(Style::GridTrackSizingDirection direction, std::optional<LayoutUnit> availableSize) const
{
ASSERT(!availableSize || *availableSize >= 0);
auto& gap = style().gap(direction);
if (gap.isNormal()) {
if (!isSubgrid(direction))
return 0_lu;
auto parentDirection = GridLayoutFunctions::flowAwareDirectionForParent(*this, *parent(), direction);
if (!availableSize)
return downcast<RenderGrid>(parent())->gridGap(parentDirection, std::nullopt);
return downcast<RenderGrid>(parent())->gridGap(parentDirection);
}
return Style::evaluate(gap, availableSize.value_or(0_lu));
}
LayoutUnit RenderGrid::gridGap(Style::GridTrackSizingDirection direction) const
{
return gridGap(direction, availableSpaceForGutters(direction));
}
LayoutUnit RenderGrid::gridItemOffset(Style::GridTrackSizingDirection direction) const
{
return direction == Style::GridTrackSizingDirection::Rows ? m_offsetBetweenRows.distributionOffset : m_offsetBetweenColumns.distributionOffset;
}
LayoutUnit RenderGrid::guttersSize(Style::GridTrackSizingDirection direction, unsigned startLine, unsigned span, std::optional<LayoutUnit> availableSize) const
{
if (span <= 1)
return { };
LayoutUnit gap = gridGap(direction, availableSize);
// Fast path, no collapsing tracks.
if (!currentGrid().hasAutoRepeatEmptyTracks(direction))
return gap * (span - 1);
// If there are collapsing tracks we need to be sure that gutters are properly collapsed. Apart
// from that, if we have a collapsed track in the edges of the span we're considering, we need
// to move forward (or backwards) in order to know whether the collapsed tracks reach the end of
// the grid (so the gap becomes 0) or there is a non empty track before that.
LayoutUnit gapAccumulator;
unsigned endLine = startLine + span;
for (unsigned line = startLine; line < endLine - 1; ++line) {
if (!currentGrid().isEmptyAutoRepeatTrack(direction, line))
gapAccumulator += gap;
}
// The above loop adds one extra gap for trailing collapsed tracks.
if (gapAccumulator && currentGrid().isEmptyAutoRepeatTrack(direction, endLine - 1)) {
ASSERT(gapAccumulator >= gap);
gapAccumulator -= gap;
}
// If the startLine is the start line of a collapsed track we need to go backwards till we reach
// a non collapsed track. If we find a non collapsed track we need to add that gap.
size_t nonEmptyTracksBeforeStartLine = 0;
if (startLine && currentGrid().isEmptyAutoRepeatTrack(direction, startLine)) {
nonEmptyTracksBeforeStartLine = startLine;
auto begin = currentGrid().autoRepeatEmptyTracks(direction)->begin();
for (auto it = begin; *it != startLine; ++it) {
ASSERT(nonEmptyTracksBeforeStartLine);
--nonEmptyTracksBeforeStartLine;
}
if (nonEmptyTracksBeforeStartLine)
gapAccumulator += gap;
}
// If the endLine is the end line of a collapsed track we need to go forward till we reach a non
// collapsed track. If we find a non collapsed track we need to add that gap.
if (currentGrid().isEmptyAutoRepeatTrack(direction, endLine - 1)) {
unsigned nonEmptyTracksAfterEndLine = currentGrid().numTracks(direction) - endLine;
auto currentEmptyTrack = currentGrid().autoRepeatEmptyTracks(direction)->find(endLine - 1);
auto endEmptyTrack = currentGrid().autoRepeatEmptyTracks(direction)->end();
// HashSet iterators do not implement operator- so we have to manually iterate to know the number of remaining empty tracks.
for (auto it = ++currentEmptyTrack; it != endEmptyTrack; ++it) {
ASSERT(nonEmptyTracksAfterEndLine >= 1);
--nonEmptyTracksAfterEndLine;
}
if (nonEmptyTracksAfterEndLine) {
// We shouldn't count the gap twice if the span starts and ends in a collapsed track between two non-empty tracks.
if (!nonEmptyTracksBeforeStartLine)
gapAccumulator += gap;
} else if (nonEmptyTracksBeforeStartLine) {
// We shouldn't count the gap if the span starts and ends in a collapsed but there isn't non-empty tracks afterwards (it's at the end of the grid).
gapAccumulator -= gap;
}
}
return gapAccumulator;
}
void RenderGrid::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
{
GridLayoutState gridLayoutState;
LayoutUnit gridItemMinWidth;
LayoutUnit gridItemMaxWidth;
bool hadExcludedChildren = computePreferredWidthsForExcludedChildren(gridItemMinWidth, gridItemMaxWidth);
Grid grid(const_cast<RenderGrid&>(*this));
m_grid.m_currentGrid = std::ref(grid);
GridTrackSizingAlgorithm algorithm(this, grid);
// placeItemsOnGrid isn't const since it mutates our grid, but it's safe to do
// so here since we've overridden m_currentGrid with a stack based temporary.
const_cast<RenderGrid&>(*this).placeItemsOnGrid(std::nullopt);
performPreLayoutForGridItems(algorithm, ShouldUpdateGridAreaLogicalSize::No);
if (m_baselineItemsCached)
algorithm.copyBaselineItemsCache(m_trackSizingAlgorithm, Style::GridTrackSizingDirection::Columns);
else {
auto emptyCallback = [](RenderBox*) { };
cacheBaselineAlignedGridItems(*this, algorithm, { AlignmentContextTypes::Columns }, emptyCallback, !isSubgridRows());
}
computeTrackSizesForIndefiniteSize(algorithm, Style::GridTrackSizingDirection::Columns, gridLayoutState, &minLogicalWidth, &maxLogicalWidth);
if (isMasonry(Style::GridTrackSizingDirection::Columns)) {
// The track sizing algorithm will only be run once in this case, since track sizing will not run in the masonry direction.
computeTrackSizesForIndefiniteSize(algorithm, Style::GridTrackSizingDirection::Rows, gridLayoutState, &minLogicalWidth, &maxLogicalWidth);
auto gridAxisTracksCountBeforeAutoPlacement = currentGrid().numTracks(Style::GridTrackSizingDirection::Rows);
// To determine the width of the grid when we have a masonry layout in the column direction we need to perform a layout with the min and max
// content sizes. We will override the grid items widths to accomplish this and then calculate the final grid content size after placement.
m_masonryLayout.performMasonryPlacement(algorithm, gridAxisTracksCountBeforeAutoPlacement, Style::GridTrackSizingDirection::Columns, GridMasonryLayout::MasonryLayoutPhase::MinContentPhase);
minLogicalWidth = m_masonryLayout.gridContentSize();
m_masonryLayout.performMasonryPlacement(algorithm, gridAxisTracksCountBeforeAutoPlacement, Style::GridTrackSizingDirection::Columns, GridMasonryLayout::MasonryLayoutPhase::MaxContentPhase);
maxLogicalWidth = m_masonryLayout.gridContentSize();
}
m_grid.resetCurrentGrid();
if (hadExcludedChildren) {
minLogicalWidth = std::max(minLogicalWidth, gridItemMinWidth);
maxLogicalWidth = std::max(maxLogicalWidth, gridItemMaxWidth);
}
LayoutUnit scrollbarWidth = intrinsicScrollbarLogicalWidthIncludingGutter();
minLogicalWidth += scrollbarWidth;
maxLogicalWidth += scrollbarWidth;
}
void RenderGrid::computeTrackSizesForIndefiniteSize(GridTrackSizingAlgorithm& algorithm, Style::GridTrackSizingDirection direction, GridLayoutState& gridLayoutState, LayoutUnit* minIntrinsicSize, LayoutUnit* maxIntrinsicSize) const
{
auto autoMarginResolutionScope = SetForScope(m_isComputingTrackSizes, true);
algorithm.run(direction, numTracks(direction), SizingOperation::IntrinsicSizeComputation, std::nullopt, gridLayoutState);
size_t numberOfTracks = algorithm.tracks(direction).size();
LayoutUnit totalGuttersSize = direction == Style::GridTrackSizingDirection::Columns && explicitIntrinsicInnerLogicalSize(direction).has_value() ? 0_lu : guttersSize(direction, 0, numberOfTracks, std::nullopt);
if (minIntrinsicSize)
*minIntrinsicSize = algorithm.minContentSize() + totalGuttersSize;
if (maxIntrinsicSize)
*maxIntrinsicSize = algorithm.maxContentSize() + totalGuttersSize;
ASSERT(algorithm.tracksAreWiderThanMinTrackBreadth());
}
bool RenderGrid::shouldCheckExplicitIntrinsicInnerLogicalSize(Style::GridTrackSizingDirection direction) const
{
return direction == Style::GridTrackSizingDirection::Columns ? shouldApplySizeOrInlineSizeContainment() : shouldApplySizeContainment();
}
std::optional<LayoutUnit> RenderGrid::explicitIntrinsicInnerLogicalSize(Style::GridTrackSizingDirection direction) const
{
if (!shouldCheckExplicitIntrinsicInnerLogicalSize(direction))
return std::nullopt;
if (direction == Style::GridTrackSizingDirection::Columns)
return explicitIntrinsicInnerLogicalWidth();
return explicitIntrinsicInnerLogicalHeight();
}
unsigned RenderGrid::computeAutoRepeatTracksCount(Style::GridTrackSizingDirection direction, std::optional<LayoutUnit> availableSize) const
{
ASSERT(!availableSize || availableSize.value() != -1);
bool isRowAxis = direction == Style::GridTrackSizingDirection::Columns;
if (isSubgrid(direction))
return 0;
const auto& tracks = style().gridTemplateList(direction);
const auto& autoRepeatTracks = tracks.autoRepeatSizes;
unsigned autoRepeatTrackListLength = autoRepeatTracks.size();
if (!autoRepeatTrackListLength)
return 0;
bool needsToFulfillMinimumSize = false;
if (!availableSize) {
// Both min-width/height and max-width/height calculations may need the containing block size, so it is cached if lazily computed.
std::optional<LayoutUnit> cachedContainingBlockAvailableSize;
auto containingBlockAvailableSize = [&] -> LayoutUnit {
if (cachedContainingBlockAvailableSize)
return *cachedContainingBlockAvailableSize;
cachedContainingBlockAvailableSize = isRowAxis
? containingBlockLogicalWidthForContent()
: containingBlockLogicalHeightForContent(AvailableLogicalHeightType::ExcludeMarginBorderPadding);
return *cachedContainingBlockAvailableSize;
};
auto& maxSize = isRowAxis ? style().logicalMaxWidth() : style().logicalMaxHeight();
auto availableMaxSize = WTF::switchOn(maxSize,
[&](const Style::MaximumSize::Fixed& fixedMaxSize) -> std::optional<LayoutUnit> {
auto maxSizeValue = LayoutUnit { fixedMaxSize.value };
return isRowAxis
? adjustContentBoxLogicalWidthForBoxSizing(maxSizeValue)
: adjustContentBoxLogicalHeightForBoxSizing(maxSizeValue);
},
[&](const Style::MaximumSize::Percentage& percentageMaxSize) -> std::optional<LayoutUnit> {
auto maxSizeValue = Style::evaluate(percentageMaxSize, containingBlockAvailableSize());
return isRowAxis
? adjustContentBoxLogicalWidthForBoxSizing(maxSizeValue)
: adjustContentBoxLogicalHeightForBoxSizing(maxSizeValue);
},
[&](const Style::MaximumSize::Calc& calcMaxSize) -> std::optional<LayoutUnit> {
auto maxSizeValue = Style::evaluate(calcMaxSize, containingBlockAvailableSize());
return isRowAxis
? adjustContentBoxLogicalWidthForBoxSizing(maxSizeValue)
: adjustContentBoxLogicalHeightForBoxSizing(maxSizeValue);
},
[&](const auto&) -> std::optional<LayoutUnit> {
return { };
}
);
auto& minSize = isRowAxis ? style().logicalMinWidth() : style().logicalMinHeight();
auto& minSizeForOrthogonalAxis = isRowAxis ? style().logicalMinHeight() : style().logicalMinWidth();
bool shouldComputeMinSizeFromAspectRatio = minSizeForOrthogonalAxis.isSpecified() && !shouldIgnoreAspectRatio();
auto explicitIntrinsicInnerSize = explicitIntrinsicInnerLogicalSize(direction);
if (!availableMaxSize && !minSize.isSpecified() && !shouldComputeMinSizeFromAspectRatio && !explicitIntrinsicInnerSize)
return autoRepeatTrackListLength;
auto availableMinSize = WTF::switchOn(minSize,
[&](const Style::MinimumSize::Fixed& fixedMinSize) -> std::optional<LayoutUnit> {
auto minSizeValue = LayoutUnit { fixedMinSize.value };
return isRowAxis
? adjustContentBoxLogicalWidthForBoxSizing(minSizeValue)
: adjustContentBoxLogicalHeightForBoxSizing(minSizeValue);
},
[&](const Style::MinimumSize::Percentage& percentageMinSize) -> std::optional<LayoutUnit> {
auto minSizeValue = Style::evaluate(percentageMinSize, containingBlockAvailableSize());
return isRowAxis
? adjustContentBoxLogicalWidthForBoxSizing(minSizeValue)
: adjustContentBoxLogicalHeightForBoxSizing(minSizeValue);
},
[&](const Style::MinimumSize::Calc& calcMinSize) -> std::optional<LayoutUnit> {
auto minSizeValue = Style::evaluate(calcMinSize, containingBlockAvailableSize());
return isRowAxis
? adjustContentBoxLogicalWidthForBoxSizing(minSizeValue)
: adjustContentBoxLogicalHeightForBoxSizing(minSizeValue);
},
[&](const auto&) -> std::optional<LayoutUnit> {
if (!shouldComputeMinSizeFromAspectRatio)
return { };
auto [logicalMinWidth, logicalMaxWidth] = computeMinMaxLogicalWidthFromAspectRatio();
return logicalMinWidth;
}
);
if (!maxSize.isSpecified() || explicitIntrinsicInnerSize)
needsToFulfillMinimumSize = true;
availableSize = std::max(std::max(valueOrDefault(availableMinSize), valueOrDefault(availableMaxSize)), valueOrDefault(explicitIntrinsicInnerSize));
if (maxSize.isSpecified() && availableMaxSize < availableSize)
availableSize = std::max(availableMinSize, availableMaxSize);
}
LayoutUnit autoRepeatTracksSize;
for (auto& autoTrackSize : autoRepeatTracks) {
ASSERT(autoTrackSize.minTrackBreadth().isLength());
ASSERT(!autoTrackSize.minTrackBreadth().isFlex());
auto& minTrackSizingFunction = autoTrackSize.minTrackBreadth();
auto& maxTrackSizingFunction = autoTrackSize.maxTrackBreadth();
bool hasDefiniteMaxTrackSizingFunction = maxTrackSizingFunction.isLength() && !maxTrackSizingFunction.isContentSized();
bool hasDefiniteMinTrackSizingFunction = minTrackSizingFunction.isLength() && !minTrackSizingFunction.isContentSized();
auto contributingTrackSize = [&] {
if (hasDefiniteMaxTrackSizingFunction && hasDefiniteMinTrackSizingFunction)
return std::max(Style::evaluate(minTrackSizingFunction.length(), *availableSize), Style::evaluate(maxTrackSizingFunction.length(), *availableSize));
return hasDefiniteMaxTrackSizingFunction ? Style::evaluate(maxTrackSizingFunction.length(), *availableSize) : Style::evaluate(minTrackSizingFunction.length(), *availableSize);
};
autoRepeatTracksSize += contributingTrackSize();
}
// For the purpose of finding the number of auto-repeated tracks, the UA must floor the track size to a UA-specified
// value to avoid division by zero. It is suggested that this floor be 1px.
autoRepeatTracksSize = std::max<LayoutUnit>(1_lu, autoRepeatTracksSize);
// There will be always at least 1 auto-repeat track, so take it already into account when computing the total track size.
LayoutUnit tracksSize = autoRepeatTracksSize;
auto& trackSizes = tracks.sizes;
for (const auto& track : trackSizes) {
bool hasDefiniteMaxTrackBreadth = track.maxTrackBreadth().isLength() && !track.maxTrackBreadth().isContentSized();
ASSERT(hasDefiniteMaxTrackBreadth || (track.minTrackBreadth().isLength() && !track.minTrackBreadth().isContentSized()));
tracksSize += Style::evaluate(hasDefiniteMaxTrackBreadth ? track.maxTrackBreadth().length() : track.minTrackBreadth().length(), availableSize.value());
}
// Add gutters as if auto repeat tracks were only repeated once. Gaps between different repetitions will be added later when
// computing the number of repetitions of the auto repeat().
LayoutUnit gapSize = gridGap(direction, availableSize);
tracksSize += gapSize * (trackSizes.size() + autoRepeatTrackListLength - 1);
LayoutUnit freeSpace = availableSize.value() - tracksSize;
if (freeSpace <= 0)
return autoRepeatTrackListLength;
LayoutUnit autoRepeatSizeWithGap = autoRepeatTracksSize + gapSize * autoRepeatTrackListLength;
unsigned repetitions = 1 + (freeSpace / autoRepeatSizeWithGap).toUnsigned();
freeSpace -= autoRepeatSizeWithGap * (repetitions - 1);
ASSERT(freeSpace >= 0);
// Provided the grid container does not have a definite size or max-size in the relevant axis,
// if the min size is definite then the number of repetitions is the largest possible positive
// integer that fulfills that minimum requirement.
if (needsToFulfillMinimumSize && freeSpace)
++repetitions;
return repetitions * autoRepeatTrackListLength;
}
WTF::Range<size_t> RenderGrid::autoRepeatTracksRange(Style::GridTrackSizingDirection direction) const
{
auto insertionPoint = style().gridTemplateList(direction).autoRepeatInsertionPoint;
auto firstAutoRepeatTrack = insertionPoint + currentGrid().explicitGridStart(direction);
auto lastAutoRepeatTrack = firstAutoRepeatTrack + currentGrid().autoRepeatTracks(direction);
return { firstAutoRepeatTrack, lastAutoRepeatTrack };
}
std::unique_ptr<OrderedTrackIndexSet> RenderGrid::computeEmptyTracksForAutoRepeat(Style::GridTrackSizingDirection direction) const
{
if (autoRepeatType(direction) != AutoRepeatType::Fit)
return nullptr;
std::unique_ptr<OrderedTrackIndexSet> emptyTrackIndexes;
auto autoRepeatTracksRange = this->autoRepeatTracksRange(direction);
if (!currentGrid().hasGridItems()) {
emptyTrackIndexes = makeUnique<OrderedTrackIndexSet>();
for (unsigned trackIndex = autoRepeatTracksRange.begin(); trackIndex < autoRepeatTracksRange.end(); ++trackIndex)
emptyTrackIndexes->add(trackIndex);
} else {
for (unsigned trackIndex = autoRepeatTracksRange.begin(); trackIndex < autoRepeatTracksRange.end(); ++trackIndex) {
GridIterator iterator(currentGrid(), direction, trackIndex);
if (!iterator.nextGridItem()) {
if (!emptyTrackIndexes)
emptyTrackIndexes = makeUnique<OrderedTrackIndexSet>();
emptyTrackIndexes->add(trackIndex);
}
}
}
return emptyTrackIndexes;
}
unsigned RenderGrid::clampAutoRepeatTracks(Style::GridTrackSizingDirection direction, unsigned autoRepeatTracks) const
{
if (!autoRepeatTracks)
return 0;
unsigned insertionPoint = style().gridTemplateList(direction).autoRepeatInsertionPoint;
unsigned maxTracks = static_cast<unsigned>(Style::GridPosition::max());
if (!insertionPoint)
return std::min(autoRepeatTracks, maxTracks);
if (insertionPoint >= maxTracks)
return 0;
return std::min(autoRepeatTracks, maxTracks - insertionPoint);
}
void RenderGrid::placeItems()
{
updateLogicalWidth();
LayoutUnit availableSpaceForColumns = contentBoxLogicalWidth();
placeItemsOnGrid(availableSpaceForColumns);
}
static GridArea insertIntoGrid(Grid& grid, RenderBox& gridItem, const GridArea& area)
{
GridArea clamped = grid.insert(gridItem, area);
CheckedPtr renderGrid = dynamicDowncast<RenderGrid>(gridItem);
if (!renderGrid)
return clamped;
if (renderGrid->isSubgridRows() || renderGrid->isSubgridColumns())
renderGrid->placeItems();
return clamped;
}
bool RenderGrid::isMasonry() const
{
return areMasonryRows() || areMasonryColumns();
}
// Masonry Spec Section 2
// "If masonry is specified for both grid-template-columns and grid-template-rows, then the used value for grid-template-columns is none,
// and thus the inline axis will be the grid axis."
bool RenderGrid::isMasonry(Style::GridTrackSizingDirection direction) const
{
// isSubgrid will return false if the masonry axis matches. Need to check style if we are a subgrid
auto& tracks = style().gridTemplateList(direction);
if (auto* parentGrid = dynamicDowncast<RenderGrid>(parent()); parentGrid && tracks.subgrid)
return parentGrid->isMasonry(direction);
return tracks.masonry;
}
// Masonry Spec Section 2.3.1 repeat(auto-fit)
// "repeat(auto-fit) behaves as repeat(auto-fill) when the other axis is a masonry axis."
// We need to lie here that we are really an auto-fill instead of an auto-fit.
AutoRepeatType RenderGrid::autoRepeatType(Style::GridTrackSizingDirection direction) const
{
auto autoRepeatType = style().gridTemplateList(direction).autoRepeatType;
if (isMasonry(orthogonalDirection(direction)) && autoRepeatType == AutoRepeatType::Fit)
return AutoRepeatType::Fill;
return autoRepeatType;
}
// FIXME: We shouldn't have to pass the available logical width as argument. The problem is that
// contentBoxLogicalWidth() does always return a value even if we cannot resolve it like when
// computing the intrinsic size (preferred widths). That's why we pass the responsibility to the
// caller who does know whether the available logical width is indefinite or not.
void RenderGrid::placeItemsOnGrid(std::optional<LayoutUnit> availableLogicalWidth)
{
unsigned autoRepeatColumns = computeAutoRepeatTracksCount(Style::GridTrackSizingDirection::Columns, availableLogicalWidth);
unsigned autoRepeatRows = computeAutoRepeatTracksCount(Style::GridTrackSizingDirection::Rows, availableLogicalHeightForPercentageComputation());
autoRepeatRows = clampAutoRepeatTracks(Style::GridTrackSizingDirection::Rows, autoRepeatRows);
autoRepeatColumns = clampAutoRepeatTracks(Style::GridTrackSizingDirection::Columns, autoRepeatColumns);
if (isSubgridInParentDirection(Style::GridTrackSizingDirection::Columns) || isSubgridInParentDirection(Style::GridTrackSizingDirection::Rows)) {
auto* parent = dynamicDowncast<RenderGrid>(this->parent());
if (parent && parent->currentGrid().needsItemsPlacement())
currentGrid().setNeedsItemsPlacement(true);
}
if (autoRepeatColumns != currentGrid().autoRepeatTracks(Style::GridTrackSizingDirection::Columns)
|| autoRepeatRows != currentGrid().autoRepeatTracks(Style::GridTrackSizingDirection::Rows)
|| isMasonry()) {
currentGrid().setNeedsItemsPlacement(true);
currentGrid().setAutoRepeatTracks(autoRepeatRows, autoRepeatColumns);
}
if (!currentGrid().needsItemsPlacement())
return;
ASSERT(!currentGrid().hasGridItems());
populateExplicitGridAndOrderIterator();
Vector<RenderBox*> autoMajorAxisAutoGridItems;
Vector<RenderBox*> specifiedMajorAxisAutoGridItems;
for (auto* gridItem = currentGrid().orderIterator().first(); gridItem; gridItem = currentGrid().orderIterator().next()) {
if (currentGrid().orderIterator().shouldSkipChild(*gridItem))
continue;
// Grid items should use the grid area sizes instead of the containing block (grid container)
// sizes, we initialize the overrides here if needed to ensure it.
if (!gridItem->gridAreaContentLogicalWidth())
gridItem->setGridAreaContentLogicalWidth(0_lu);
if (!gridItem->gridAreaContentLogicalHeight())
gridItem->setGridAreaContentLogicalHeight(std::nullopt);
GridArea area = currentGrid().gridItemArea(*gridItem);
currentGrid().clampAreaToSubgridIfNeeded(area);
if (!area.rows.isIndefinite())
area.rows.translate(currentGrid().explicitGridStart(Style::GridTrackSizingDirection::Rows));
if (!area.columns.isIndefinite())
area.columns.translate(currentGrid().explicitGridStart(Style::GridTrackSizingDirection::Columns));
if (area.rows.isIndefinite() || area.columns.isIndefinite()) {
currentGrid().setGridItemArea(*gridItem, area);
bool majorAxisDirectionIsForColumns = autoPlacementMajorAxisDirection() == Style::GridTrackSizingDirection::Columns;
if ((majorAxisDirectionIsForColumns && area.columns.isIndefinite())
|| (!majorAxisDirectionIsForColumns && area.rows.isIndefinite()))
autoMajorAxisAutoGridItems.append(gridItem);
else
specifiedMajorAxisAutoGridItems.append(gridItem);
continue;
}
insertIntoGrid(currentGrid(), *gridItem, { area.rows, area.columns });
}
#if ASSERT_ENABLED
if (currentGrid().hasGridItems()) {
ASSERT(currentGrid().numTracks(Style::GridTrackSizingDirection::Rows) >= Style::GridPositionsResolver::explicitGridCount(*this, Style::GridTrackSizingDirection::Rows));
ASSERT(currentGrid().numTracks(Style::GridTrackSizingDirection::Columns) >= Style::GridPositionsResolver::explicitGridCount(*this, Style::GridTrackSizingDirection::Columns));
}
#endif
auto performAutoPlacement = [&]() {
placeSpecifiedMajorAxisItemsOnGrid(specifiedMajorAxisAutoGridItems);
placeAutoMajorAxisItemsOnGrid(autoMajorAxisAutoGridItems);
// Compute collapsible tracks for auto-fit.
currentGrid().setAutoRepeatEmptyColumns(computeEmptyTracksForAutoRepeat(Style::GridTrackSizingDirection::Columns));
currentGrid().setAutoRepeatEmptyRows(computeEmptyTracksForAutoRepeat(Style::GridTrackSizingDirection::Rows));
currentGrid().setNeedsItemsPlacement(false);
};
performAutoPlacement();
#if ASSERT_ENABLED
for (auto* gridItem = currentGrid().orderIterator().first(); gridItem; gridItem = currentGrid().orderIterator().next()) {
if (currentGrid().orderIterator().shouldSkipChild(*gridItem))
continue;
GridArea area = currentGrid().gridItemArea(*gridItem);
ASSERT(area.rows.isTranslatedDefinite() && area.columns.isTranslatedDefinite());
}
#endif
}
LayoutUnit RenderGrid::masonryContentSize() const
{
return m_masonryLayout.gridContentSize();
}
void RenderGrid::performPreLayoutForGridItems(const GridTrackSizingAlgorithm& algorithm, const ShouldUpdateGridAreaLogicalSize shouldUpdateGridAreaLogicalSize) const
{
ASSERT(!algorithm.grid().needsItemsPlacement());
// FIXME: We need a way when we are calling this during intrinsic size computation before performing
// the layout. Maybe using the PreLayout phase ?
for (auto* gridItem = firstChildBox(); gridItem; gridItem = gridItem->nextSiblingBox()) {
if (gridItem->isOutOfFlowPositioned())
continue;
// Orthogonal items should be laid out in order to properly compute content-sized tracks that may depend on item's intrinsic size.
// We also need to properly estimate its grid area size, since it may affect to the baseline shims if such item participates in baseline alignment.
if (GridLayoutFunctions::isOrthogonalGridItem(*this, *gridItem)) {
updateGridAreaLogicalSize(*gridItem, algorithm.estimatedGridAreaBreadthForGridItem(*gridItem, Style::GridTrackSizingDirection::Columns), algorithm.estimatedGridAreaBreadthForGridItem(*gridItem, Style::GridTrackSizingDirection::Rows));
gridItem->layoutIfNeeded();
continue;
}
// We need to layout the item to know whether it must synthesize its
// baseline or not, which may imply a cyclic sizing dependency.
// FIXME: Can we avoid it ?
// FIXME: We also want to layout baseline aligned items within subgrids, but
// we don't currently have a way to do that here.
if (isBaselineAlignmentForGridItem(*gridItem)) {
// FIXME: Hack to fix nested grid text size overflow during re-layouts.
if (shouldUpdateGridAreaLogicalSize == ShouldUpdateGridAreaLogicalSize::Yes)
updateGridAreaLogicalSize(*gridItem, algorithm.estimatedGridAreaBreadthForGridItem(*gridItem, Style::GridTrackSizingDirection::Columns), algorithm.estimatedGridAreaBreadthForGridItem(*gridItem, Style::GridTrackSizingDirection::Rows));
gridItem->layoutIfNeeded();
}
}
}
void RenderGrid::populateExplicitGridAndOrderIterator()
{
OrderIteratorPopulator populator(currentGrid().orderIterator());
unsigned explicitRowStart = 0;
unsigned explicitColumnStart = 0;
unsigned maximumRowIndex = Style::GridPositionsResolver::explicitGridCount(*this, Style::GridTrackSizingDirection::Rows);
unsigned maximumColumnIndex = Style::GridPositionsResolver::explicitGridCount(*this, Style::GridTrackSizingDirection::Columns);
for (auto& gridItem : childrenOfType<RenderBox>(*this)) {
if (!populator.collectChild(gridItem))
continue;
auto rowPositions = Style::GridPositionsResolver::resolveGridPositionsFromStyle(*this, gridItem, Style::GridTrackSizingDirection::Rows);
if (!isSubgridRows()) {
if (!rowPositions.isIndefinite()) {
explicitRowStart = std::max<int>(explicitRowStart, -rowPositions.untranslatedStartLine());
maximumRowIndex = std::max<int>(maximumRowIndex, rowPositions.untranslatedEndLine());
} else {
// Grow the grid for items with a definite row span, getting the largest such span.
unsigned spanSize = Style::GridPositionsResolver::spanSizeForAutoPlacedItem(gridItem, Style::GridTrackSizingDirection::Rows);
maximumRowIndex = std::max(maximumRowIndex, spanSize);
}
}
auto columnPositions = Style::GridPositionsResolver::resolveGridPositionsFromStyle(*this, gridItem, Style::GridTrackSizingDirection::Columns);
if (!isSubgridColumns()) {
if (!columnPositions.isIndefinite()) {
explicitColumnStart = std::max<int>(explicitColumnStart, -columnPositions.untranslatedStartLine());
maximumColumnIndex = std::max<int>(maximumColumnIndex, columnPositions.untranslatedEndLine());
} else {
// Grow the grid for items with a definite column span, getting the largest such span.
unsigned spanSize = Style::GridPositionsResolver::spanSizeForAutoPlacedItem(gridItem, Style::GridTrackSizingDirection::Columns);
maximumColumnIndex = std::max(maximumColumnIndex, spanSize);
}
}
currentGrid().setGridItemArea(gridItem, { rowPositions, columnPositions });
}
currentGrid().setExplicitGridStart(explicitRowStart, explicitColumnStart);
currentGrid().ensureGridSize(maximumRowIndex + explicitRowStart, maximumColumnIndex + explicitColumnStart);
currentGrid().setClampingForSubgrid(isSubgridRows() ? maximumRowIndex : 0, isSubgridColumns() ? maximumColumnIndex : 0);
}
GridArea RenderGrid::createEmptyGridAreaAtSpecifiedPositionsOutsideGrid(const RenderBox& gridItem, Style::GridTrackSizingDirection specifiedDirection, const GridSpan& specifiedPositions) const
{
auto crossDirection = orthogonalDirection(specifiedDirection);
auto endOfCrossDirection = currentGrid().numTracks(crossDirection);
auto crossDirectionSpanSize = Style::GridPositionsResolver::spanSizeForAutoPlacedItem(gridItem, crossDirection);
auto crossDirectionPositions = GridSpan::translatedDefiniteGridSpan(endOfCrossDirection, endOfCrossDirection + crossDirectionSpanSize);
return specifiedDirection == Style::GridTrackSizingDirection::Columns
? GridArea { crossDirectionPositions, specifiedPositions }
: GridArea { specifiedPositions, crossDirectionPositions };
}
bool RenderGrid::isPlacedWithinExtrinsicallySizedExplicitTracks(const RenderBox& gridItem) const
{
auto& currentGrid = this->currentGrid();
if (currentGrid.needsItemsPlacement())
return false;
auto& gridStyle = style();
auto gridItemArea = currentGrid.gridItemArea(gridItem);
auto& gridColumnSizes = gridStyle.gridTemplateColumns().sizes;
if (gridItemArea.columns.endLine() > gridColumnSizes.size())
return false;
for (auto columnIndex : gridItemArea.columns) {
if (gridColumnSizes[columnIndex].isContentSized())
return false;
}
auto& gridRowSizes = gridStyle.gridTemplateRows().sizes;
if (gridItemArea.rows.endLine() > gridRowSizes.size())
return false;
for (auto rowIndex : gridItemArea.rows) {
if (gridRowSizes[rowIndex].isContentSized())
return false;
}
return true;
}
void RenderGrid::placeSpecifiedMajorAxisItemsOnGrid(const Vector<RenderBox*>& autoGridItems)
{
bool isForColumns = autoPlacementMajorAxisDirection() == Style::GridTrackSizingDirection::Columns;
bool isGridAutoFlowDense = style().isGridAutoFlowAlgorithmDense();
// Mapping between the major axis tracks (rows or columns) and the last auto-placed item's position inserted on
// that track. This is needed to implement "sparse" packing for items locked to a given track.
// See https://drafts.csswg.org/css-grid-2/#auto-placement-algo
HashMap<unsigned, unsigned, DefaultHash<unsigned>, WTF::UnsignedWithZeroKeyHashTraits<unsigned>> minorAxisCursors;
for (auto& autoGridItem : autoGridItems) {
GridSpan majorAxisPositions = currentGrid().gridItemSpan(*autoGridItem, autoPlacementMajorAxisDirection());
ASSERT(majorAxisPositions.isTranslatedDefinite());
ASSERT(currentGrid().gridItemSpan(*autoGridItem, autoPlacementMinorAxisDirection()).isIndefinite());
unsigned minorAxisSpanSize = Style::GridPositionsResolver::spanSizeForAutoPlacedItem(*autoGridItem, autoPlacementMinorAxisDirection());
unsigned majorAxisInitialPosition = majorAxisPositions.startLine();
GridIterator iterator(currentGrid(), autoPlacementMajorAxisDirection(), majorAxisPositions.startLine(), isGridAutoFlowDense ? 0 : minorAxisCursors.get(majorAxisInitialPosition));
auto emptyGridArea = iterator.nextEmptyGridArea(majorAxisPositions.integerSpan(), minorAxisSpanSize);
if (!emptyGridArea)
emptyGridArea = createEmptyGridAreaAtSpecifiedPositionsOutsideGrid(*autoGridItem, autoPlacementMajorAxisDirection(), majorAxisPositions);
emptyGridArea = insertIntoGrid(currentGrid(), *autoGridItem, *emptyGridArea);
if (!isGridAutoFlowDense)
minorAxisCursors.set(majorAxisInitialPosition, isForColumns ? emptyGridArea->rows.startLine() : emptyGridArea->columns.startLine());
}
}
void RenderGrid::placeAutoMajorAxisItemsOnGrid(const Vector<RenderBox*>& autoGridItems)
{
AutoPlacementCursor autoPlacementCursor = {0, 0};
bool isGridAutoFlowDense = style().isGridAutoFlowAlgorithmDense();
for (auto& autoGridItem : autoGridItems) {
placeAutoMajorAxisItemOnGrid(*autoGridItem, autoPlacementCursor);
if (isGridAutoFlowDense) {
autoPlacementCursor.first = 0;
autoPlacementCursor.second = 0;
}
}
}
void RenderGrid::placeAutoMajorAxisItemOnGrid(RenderBox& gridItem, AutoPlacementCursor& autoPlacementCursor)
{
ASSERT(currentGrid().gridItemSpan(gridItem, autoPlacementMajorAxisDirection()).isIndefinite());
unsigned majorAxisSpanSize = Style::GridPositionsResolver::spanSizeForAutoPlacedItem(gridItem, autoPlacementMajorAxisDirection());
const unsigned endOfMajorAxis = currentGrid().numTracks(autoPlacementMajorAxisDirection());
unsigned majorAxisAutoPlacementCursor = autoPlacementMajorAxisDirection() == Style::GridTrackSizingDirection::Columns ? autoPlacementCursor.second : autoPlacementCursor.first;
unsigned minorAxisAutoPlacementCursor = autoPlacementMajorAxisDirection() == Style::GridTrackSizingDirection::Columns ? autoPlacementCursor.first : autoPlacementCursor.second;
auto emptyGridArea = std::optional<GridArea> { };
GridSpan minorAxisPositions = currentGrid().gridItemSpan(gridItem, autoPlacementMinorAxisDirection());
if (minorAxisPositions.isTranslatedDefinite()) {
// Move to the next track in major axis if initial position in minor axis is before auto-placement cursor.
if (minorAxisPositions.startLine() < minorAxisAutoPlacementCursor)
majorAxisAutoPlacementCursor++;
if (majorAxisAutoPlacementCursor < endOfMajorAxis) {
GridIterator iterator(currentGrid(), autoPlacementMinorAxisDirection(), minorAxisPositions.startLine(), majorAxisAutoPlacementCursor);
emptyGridArea = iterator.nextEmptyGridArea(minorAxisPositions.integerSpan(), majorAxisSpanSize);
}
if (!emptyGridArea)
emptyGridArea = createEmptyGridAreaAtSpecifiedPositionsOutsideGrid(gridItem, autoPlacementMinorAxisDirection(), minorAxisPositions);
} else {
unsigned minorAxisSpanSize = Style::GridPositionsResolver::spanSizeForAutoPlacedItem(gridItem, autoPlacementMinorAxisDirection());
for (unsigned majorAxisIndex = majorAxisAutoPlacementCursor; majorAxisIndex < endOfMajorAxis; ++majorAxisIndex) {
GridIterator iterator(currentGrid(), autoPlacementMajorAxisDirection(), majorAxisIndex, minorAxisAutoPlacementCursor);
emptyGridArea = iterator.nextEmptyGridArea(majorAxisSpanSize, minorAxisSpanSize);
if (emptyGridArea) {
// Check that it fits in the minor axis direction, as we shouldn't grow in that direction here (it was already managed in populateExplicitGridAndOrderIterator()).
unsigned minorAxisFinalPositionIndex = autoPlacementMinorAxisDirection() == Style::GridTrackSizingDirection::Columns ? emptyGridArea->columns.endLine() : emptyGridArea->rows.endLine();
const unsigned endOfMinorAxis = currentGrid().numTracks(autoPlacementMinorAxisDirection());
if (minorAxisFinalPositionIndex <= endOfMinorAxis)
break;
// Discard empty grid area as it does not fit in the minor axis direction.
// We don't need to create a new empty grid area yet as we might find a valid one in the next iteration.
emptyGridArea = { };
}
// As we're moving to the next track in the major axis we should reset the auto-placement cursor in the minor axis.
minorAxisAutoPlacementCursor = 0;
}
if (!emptyGridArea)
emptyGridArea = createEmptyGridAreaAtSpecifiedPositionsOutsideGrid(gridItem, autoPlacementMinorAxisDirection(), GridSpan::translatedDefiniteGridSpan(0, minorAxisSpanSize));
}
emptyGridArea = insertIntoGrid(currentGrid(), gridItem, *emptyGridArea);
autoPlacementCursor.first = emptyGridArea->rows.startLine();
autoPlacementCursor.second = emptyGridArea->columns.startLine();
}
Style::GridTrackSizingDirection RenderGrid::autoPlacementMajorAxisDirection() const
{
if (areMasonryColumns())
return Style::GridTrackSizingDirection::Columns;
if (areMasonryRows())
return Style::GridTrackSizingDirection::Rows;
return style().isGridAutoFlowDirectionColumn() ? Style::GridTrackSizingDirection::Columns : Style::GridTrackSizingDirection::Rows;
}
Style::GridTrackSizingDirection RenderGrid::autoPlacementMinorAxisDirection() const
{
return orthogonalDirection(autoPlacementMajorAxisDirection());
}
void RenderGrid::setNeedsItemPlacement(SubgridDidChange subgridDidChange)
{
if (currentGrid().needsItemsPlacement())
return;
currentGrid().setNeedsItemsPlacement(true);
auto currentChild = this;
while (currentChild && (subgridDidChange == SubgridDidChange::Yes || currentChild->isSubgridRows() || currentChild->isSubgridColumns())) {
currentChild = dynamicDowncast<RenderGrid>(currentChild->parent());
if (currentChild)
currentChild->currentGrid().setNeedsItemsPlacement(true);
subgridDidChange = SubgridDidChange::No;
}
}
Vector<LayoutUnit> RenderGrid::trackSizesForComputedStyle(Style::GridTrackSizingDirection direction) const
{
const auto& positions = this->positions(direction);
auto numPositions = positions.size();
Vector<LayoutUnit> tracks;
if (numPositions < 2)
return tracks;
ASSERT(!currentGrid().needsItemsPlacement());
auto offsetBetweenTracks = this->offsetBetweenTracks(direction).distributionOffset;
bool hasCollapsedTracks = currentGrid().hasAutoRepeatEmptyTracks(direction);
auto gap = !hasCollapsedTracks ? gridGap(direction) : 0_lu;
tracks.reserveInitialCapacity(numPositions - 1);
tracks.appendUsingFunctor(numPositions - 2, [&](size_t i) {
return positions[i + 1] - positions[i] - offsetBetweenTracks - gap;
});
tracks.append(positions[numPositions - 1] - positions[numPositions - 2]);
if (!hasCollapsedTracks)
return tracks;
size_t remainingEmptyTracks = currentGrid().autoRepeatEmptyTracks(direction)->size();
size_t lastLine = tracks.size();
gap = gridGap(direction);
for (size_t i = 1; i < lastLine; ++i) {
if (currentGrid().isEmptyAutoRepeatTrack(direction, i - 1))
--remainingEmptyTracks;
else {
// Remove the gap between consecutive non empty tracks. Remove it also just once for an
// arbitrary number of empty tracks between two non empty ones.
bool allRemainingTracksAreEmpty = remainingEmptyTracks == (lastLine - i);
if (!allRemainingTracksAreEmpty || !currentGrid().isEmptyAutoRepeatTrack(direction, i))
tracks[i - 1] -= gap;
}
}
return tracks;
}
static const StyleContentAlignmentData& contentAlignmentNormalBehaviorGrid()
{
static const StyleContentAlignmentData normalBehavior = {ContentPosition::Normal, ContentDistribution::Stretch};
return normalBehavior;
}
static bool overrideSizeChanged(const RenderBox& gridItem, Style::GridTrackSizingDirection direction, std::optional<LayoutUnit> width, std::optional<LayoutUnit> height)
{
if (direction == Style::GridTrackSizingDirection::Columns) {
if (auto gridAreaContentLogicalWidth = gridItem.gridAreaContentLogicalWidth())
return *gridAreaContentLogicalWidth != width;
return true;
}
if (auto gridAreaContentLogicalHeight = gridItem.gridAreaContentLogicalHeight())
return *gridAreaContentLogicalHeight != height;
return true;
}
static bool hasRelativeBlockAxisSize(const RenderGrid& grid, const RenderBox& gridItem)
{
return GridLayoutFunctions::isOrthogonalGridItem(grid, gridItem) ? gridItem.hasRelativeLogicalWidth() || gridItem.style().logicalWidth().isAuto() : gridItem.hasRelativeLogicalHeight();
}
void RenderGrid::updateGridAreaLogicalSize(RenderBox& gridItem, std::optional<LayoutUnit> width, std::optional<LayoutUnit> height) const
{
// Because the grid area cannot be styled, we don't need to adjust
// the grid breadth to account for 'box-sizing'.
bool gridAreaWidthChanged = overrideSizeChanged(gridItem, Style::GridTrackSizingDirection::Columns, width, height);
bool gridAreaHeightChanged = overrideSizeChanged(gridItem, Style::GridTrackSizingDirection::Rows, width, height);
if (gridAreaWidthChanged || (gridAreaHeightChanged && hasRelativeBlockAxisSize(*this, gridItem)))
gridItem.setNeedsLayout(MarkOnlyThis);
gridItem.setGridAreaContentLogicalWidth(width);
gridItem.setGridAreaContentLogicalHeight(height);
}
void RenderGrid::updateGridAreaForAspectRatioItems(const Vector<RenderBox*>& autoGridItems, GridLayoutState& gridLayoutState)
{
populateGridPositionsForDirection(m_trackSizingAlgorithm, Style::GridTrackSizingDirection::Columns);
populateGridPositionsForDirection(m_trackSizingAlgorithm, Style::GridTrackSizingDirection::Rows);
for (auto& autoGridItem : autoGridItems) {
updateGridAreaLogicalSize(*autoGridItem, gridAreaBreadthForGridItemIncludingAlignmentOffsets(*autoGridItem, Style::GridTrackSizingDirection::Columns), gridAreaBreadthForGridItemIncludingAlignmentOffsets(*autoGridItem, Style::GridTrackSizingDirection::Rows));
// For an item with aspect-ratio, if it has stretch alignment that stretches to the definite row, we also need to transfer the size before laying out the grid item.
if (autoGridItem->hasStretchedLogicalHeight())
applyStretchAlignmentToGridItemIfNeeded(*autoGridItem, gridLayoutState);
}
}
void RenderGrid::layoutGridItems(GridLayoutState& gridLayoutState)
{
populateGridPositionsForDirection(m_trackSizingAlgorithm, Style::GridTrackSizingDirection::Columns);
populateGridPositionsForDirection(m_trackSizingAlgorithm, Style::GridTrackSizingDirection::Rows);
for (auto& gridItem : childrenOfType<RenderBox>(*this)) {
if (currentGrid().orderIterator().shouldSkipChild(gridItem)) {
if (gridItem.isOutOfFlowPositioned())
prepareGridItemForPositionedLayout(gridItem);
continue;
}
auto* renderGrid = dynamicDowncast<RenderGrid>(gridItem);
if (renderGrid && (renderGrid->isSubgridColumns() || renderGrid->isSubgridRows()))
gridItem.setNeedsLayout(MarkOnlyThis);
// Setting the definite grid area's sizes. It may imply that the
// item must perform a layout if its area differs from the one
// used during the track sizing algorithm.
updateGridAreaLogicalSize(gridItem, gridAreaBreadthForGridItemIncludingAlignmentOffsets(gridItem, Style::GridTrackSizingDirection::Columns), gridAreaBreadthForGridItemIncludingAlignmentOffsets(gridItem, Style::GridTrackSizingDirection::Rows));
LayoutRect oldGridItemRect = gridItem.frameRect();
// Stretching logic might force a grid item layout, so we need to run it before the layoutIfNeeded
// call to avoid unnecessary relayouts. This might imply that grid item margins, needed to correctly
// determine the available space before stretching, are not set yet.
applyStretchAlignmentToGridItemIfNeeded(gridItem, gridLayoutState);
applySubgridStretchAlignmentToGridItemIfNeeded(gridItem);
gridItem.layoutIfNeeded();
// We need pending layouts to be done in order to compute auto-margins properly.
updateAutoMarginsInColumnAxisIfNeeded(gridItem);
updateAutoMarginsInRowAxisIfNeeded(gridItem);
setLogicalPositionForGridItem(gridItem);
// If the grid item 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 grid item) anyway.
if (!selfNeedsLayout() && gridItem.checkForRepaintDuringLayout())
gridItem.repaintDuringLayoutIfMoved(oldGridItemRect);
}
}
void RenderGrid::layoutMasonryItems(GridLayoutState& gridLayoutState)
{
populateGridPositionsForDirection(m_trackSizingAlgorithm, Style::GridTrackSizingDirection::Columns);
populateGridPositionsForDirection(m_trackSizingAlgorithm, Style::GridTrackSizingDirection::Rows);
for (auto& gridItem : childrenOfType<RenderBox>(*this)) {
if (currentGrid().orderIterator().shouldSkipChild(gridItem)) {
if (gridItem.isOutOfFlowPositioned())
prepareGridItemForPositionedLayout(gridItem);
continue;
}
auto* renderGrid = dynamicDowncast<RenderGrid>(gridItem);
if (renderGrid && (renderGrid->isSubgridColumns() || renderGrid->isSubgridRows()))
gridItem.setNeedsLayout(MarkOnlyThis);
// Setting the definite grid area's sizes. It may imply that the
// item must perform a layout if its area differs from the one
// used during the track sizing algorithm.
updateGridAreaLogicalSize(gridItem, gridAreaBreadthForGridItemIncludingAlignmentOffsets(gridItem, Style::GridTrackSizingDirection::Columns), gridAreaBreadthForGridItemIncludingAlignmentOffsets(gridItem, Style::GridTrackSizingDirection::Rows));
LayoutRect oldGridItemRect = gridItem.frameRect();
// Stretching logic might force a grid item layout, so we need to run it before the layoutIfNeeded
// call to avoid unnecessary relayouts. This might imply that grid item margins, needed to correctly
// determine the available space before stretching, are not set yet.
applyStretchAlignmentToGridItemIfNeeded(gridItem, gridLayoutState);
applySubgridStretchAlignmentToGridItemIfNeeded(gridItem);
gridItem.layoutIfNeeded();
// We need pending layouts to be done in order to compute auto-margins properly.
updateAutoMarginsInColumnAxisIfNeeded(gridItem);
updateAutoMarginsInRowAxisIfNeeded(gridItem);
setLogicalPositionForGridItem(gridItem);
// If the grid item 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 grid item) anyway.
if (!selfNeedsLayout() && gridItem.checkForRepaintDuringLayout())
gridItem.repaintDuringLayoutIfMoved(oldGridItemRect);
}
}
void RenderGrid::prepareGridItemForPositionedLayout(RenderBox& gridItem)
{
ASSERT(gridItem.isOutOfFlowPositioned());
gridItem.containingBlock()->addOutOfFlowBox(gridItem);
RenderLayer* gridItemLayer = gridItem.layer();
// Static position of a positioned grid item should use the content-box (https://drafts.csswg.org/css-grid/#static-position).
gridItemLayer->setStaticInlinePosition(borderAndPaddingStart());
gridItemLayer->setStaticBlockPosition(borderAndPaddingBefore());
}
bool RenderGrid::hasStaticPositionForGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction) const
{
return direction == Style::GridTrackSizingDirection::Columns ? gridItem.style().hasStaticInlinePosition(isHorizontalWritingMode()) : gridItem.style().hasStaticBlockPosition(isHorizontalWritingMode());
}
void RenderGrid::layoutOutOfFlowBox(RenderBox& gridItem, RelayoutChildren relayoutChildren, bool fixedPositionObjectsOnly)
{
if (layoutContext().isSkippedContentRootForLayout(*this)) {
gridItem.clearNeedsLayoutForSkippedContent();
return;
}
LayoutUnit columnBreadth = gridAreaBreadthForOutOfFlowGridItem(gridItem, Style::GridTrackSizingDirection::Columns);
LayoutUnit rowBreadth = gridAreaBreadthForOutOfFlowGridItem(gridItem, Style::GridTrackSizingDirection::Rows);
gridItem.setGridAreaContentLogicalWidth(columnBreadth);
gridItem.setGridAreaContentLogicalHeight(rowBreadth);
// Mark for layout as we're resetting the position before and we relay in generic layout logic
// for positioned items in order to get the offsets properly resolved.
gridItem.setChildNeedsLayout(MarkOnlyThis);
RenderBlock::layoutOutOfFlowBox(gridItem, relayoutChildren, fixedPositionObjectsOnly);
}
LayoutUnit RenderGrid::gridAreaBreadthForGridItemIncludingAlignmentOffsets(const RenderBox& gridItem, Style::GridTrackSizingDirection direction) const
{
if (direction == Style::GridTrackSizingDirection::Rows) {
if (areMasonryRows())
return isHorizontalWritingMode() ? gridItem.height() + gridItem.verticalMarginExtent() : gridItem.width() + gridItem.horizontalMarginExtent();
} else if (areMasonryColumns())
return isHorizontalWritingMode() ? gridItem.width() + gridItem.horizontalMarginExtent() : gridItem.height() + gridItem.verticalMarginExtent();
// We need the cached value when available because Content Distribution alignment properties
// may have some influence in the final grid area breadth.
const auto& tracks = m_trackSizingAlgorithm.tracks(direction);
const auto& span = currentGrid().gridItemSpan(gridItem, direction);
const auto& positions = this->positions(direction);
auto initialTrackPosition = positions[span.startLine()];
auto finalTrackPosition = positions[span.endLine() - 1];
// Track Positions vector stores the 'start' grid line of each track, so we have to add last track's baseSize.
return finalTrackPosition - initialTrackPosition + tracks[span.endLine() - 1].baseSize();
}
void RenderGrid::populateGridPositionsForDirection(const GridTrackSizingAlgorithm& algorithm, Style::GridTrackSizingDirection direction)
{
// Since we add alignment offsets and track gutters, grid lines are not always adjacent. Hence, we will have to
// assume from now on that we just store positions of the initial grid lines of each track,
// except the last one, which is the only one considered as a final grid line of a track.
// The grid container's frame elements (border, padding and <content-position> offset) are sensible to the
// inline-axis flow direction. However, column lines positions are 'direction' unaware. This simplification
// allows us to use the same indexes to identify the columns independently on the inline-axis direction.
bool isRowAxis = direction == Style::GridTrackSizingDirection::Columns;
auto& tracks = algorithm.tracks(direction);
unsigned numberOfTracks = tracks.size();
unsigned numberOfLines = numberOfTracks + 1;
unsigned lastLine = numberOfLines - 1;
bool hasCollapsedTracks = currentGrid().hasAutoRepeatEmptyTracks(direction);
size_t numberOfCollapsedTracks = hasCollapsedTracks ? currentGrid().autoRepeatEmptyTracks(direction)->size() : 0;
const auto& offsetBetweenTracks = this->offsetBetweenTracks(direction);
auto& positions = this->positions(direction);
positions.resize(numberOfLines);
auto borderAndPadding = isRowAxis ? borderAndPaddingStart() : borderAndPaddingBefore();
positions[0] = borderAndPadding + offsetBetweenTracks.positionOffset;
if (numberOfLines > 1) {
// If we have collapsed tracks we just ignore gaps here and add them later as we might not
// compute the gap between two consecutive tracks without examining the surrounding ones.
LayoutUnit gap = !hasCollapsedTracks ? gridGap(direction) : 0_lu;
unsigned nextToLastLine = numberOfLines - 2;
for (unsigned i = 0; i < nextToLastLine; ++i)
positions[i + 1] = positions[i] + offsetBetweenTracks.distributionOffset + tracks[i].unclampedBaseSize() + gap;
positions[lastLine] = positions[nextToLastLine] + tracks[nextToLastLine].unclampedBaseSize();
if (isMasonry(direction))
positions[lastLine] = m_masonryLayout.gridContentSize() + positions[0];
// Adjust collapsed gaps. Collapsed tracks cause the surrounding gutters to collapse (they
// coincide exactly) except on the edges of the grid where they become 0.
if (hasCollapsedTracks) {
gap = gridGap(direction);
unsigned remainingEmptyTracks = numberOfCollapsedTracks;
LayoutUnit offsetAccumulator;
LayoutUnit gapAccumulator;
for (unsigned i = 1; i < lastLine; ++i) {
if (currentGrid().isEmptyAutoRepeatTrack(direction, i - 1)) {
--remainingEmptyTracks;
offsetAccumulator += offsetBetweenTracks.distributionOffset;
} else {
// Add gap between consecutive non empty tracks. Add it also just once for an
// arbitrary number of empty tracks between two non empty ones.
bool allRemainingTracksAreEmpty = remainingEmptyTracks == (lastLine - i);
if (!allRemainingTracksAreEmpty || !currentGrid().isEmptyAutoRepeatTrack(direction, i))
gapAccumulator += gap;
}
positions[i] += gapAccumulator - offsetAccumulator;
}
positions[lastLine] += gapAccumulator - offsetAccumulator;
}
}
}
static LayoutUnit computeOverflowAlignmentOffset(OverflowAlignment overflow, LayoutUnit trackSize, LayoutUnit gridItemSize)
{
LayoutUnit offset = trackSize - gridItemSize;
switch (overflow) {
case OverflowAlignment::Safe:
// If overflow is 'safe', we have to make sure we don't overflow the 'start'
// edge (potentially cause some data loss as the overflow is unreachable).
return std::max<LayoutUnit>(0, offset);
case OverflowAlignment::Unsafe:
case OverflowAlignment::Default:
// If we overflow our alignment container and overflow is 'true' (default), we
// ignore the overflow and just return the value regardless (which may cause data
// loss as we overflow the 'start' edge).
return offset;
}
ASSERT_NOT_REACHED();
return 0;
}
LayoutUnit RenderGrid::availableAlignmentSpaceForGridItemBeforeStretching(LayoutUnit gridAreaBreadthForGridItem, const RenderBox& gridItem, Style::GridTrackSizingDirection direction) const
{
// Because we want to avoid multiple layouts, stretching logic might be performed before
// grid items are laid out, so we can't use the grid item cached values. Hence, we need to
// compute margins in order to determine the available height before stretching.
auto gridItemFlowDirection = GridLayoutFunctions::flowAwareDirectionForGridItem(*this, gridItem, direction);
return std::max(0_lu, gridAreaBreadthForGridItem - GridLayoutFunctions::marginLogicalSizeForGridItem(*this, gridItemFlowDirection, gridItem));
}
StyleSelfAlignmentData RenderGrid::alignSelfForGridItem(const RenderBox& gridItem, StretchingMode stretchingMode, const RenderStyle* gridStyle) const
{
CheckedPtr renderGrid = dynamicDowncast<RenderGrid>(gridItem);
if (renderGrid && renderGrid->isSubgridInParentDirection(Style::GridTrackSizingDirection::Rows))
return { ItemPosition::Stretch, OverflowAlignment::Default };
if (!gridStyle)
gridStyle = &style();
auto normalBehavior = stretchingMode == StretchingMode::Any ? selfAlignmentNormalBehavior(&gridItem) : ItemPosition::Normal;
return gridItem.style().resolvedAlignSelf(gridStyle, normalBehavior);
}
StyleSelfAlignmentData RenderGrid::justifySelfForGridItem(const RenderBox& gridItem, StretchingMode stretchingMode, const RenderStyle* gridStyle) const
{
CheckedPtr renderGrid = dynamicDowncast<RenderGrid>(gridItem);
if (renderGrid && renderGrid->isSubgridInParentDirection(Style::GridTrackSizingDirection::Columns))
return { ItemPosition::Stretch, OverflowAlignment::Default };
if (!gridStyle)
gridStyle = &style();
auto normalBehavior = stretchingMode == StretchingMode::Any ? selfAlignmentNormalBehavior(&gridItem) : ItemPosition::Normal;
return gridItem.style().resolvedJustifySelf(gridStyle, normalBehavior);
}
bool RenderGrid::aspectRatioPrefersInline(const RenderBox& gridItem, bool blockFlowIsColumnAxis)
{
if (!gridItem.style().hasAspectRatio())
return false;
bool hasExplicitInlineStretch = justifySelfForGridItem(gridItem, StretchingMode::Explicit).position() == ItemPosition::Stretch;
bool hasExplicitBlockStretch = alignSelfForGridItem(gridItem, StretchingMode::Explicit).position() == ItemPosition::Stretch;
if (!blockFlowIsColumnAxis)
std::swap(hasExplicitInlineStretch, hasExplicitBlockStretch);
return !hasExplicitBlockStretch;
}
inline bool RenderGrid::allowedToStretchGridItemAlongColumnAxis(const RenderBox& gridItem) const
{
return alignSelfForGridItem(gridItem).position() == ItemPosition::Stretch && hasAutoSizeInColumnAxis(gridItem) && !hasAutoMarginsInColumnAxis(gridItem);
}
inline bool RenderGrid::allowedToStretchGridItemAlongRowAxis(const RenderBox& gridItem) const
{
return justifySelfForGridItem(gridItem).position() == ItemPosition::Stretch && hasAutoSizeInRowAxis(gridItem) && !hasAutoMarginsInRowAxis(gridItem);
}
// FIXME: This logic is shared by RenderFlexibleBox, so it should be moved to RenderBox.
void RenderGrid::applyStretchAlignmentToGridItemIfNeeded(RenderBox& gridItem, GridLayoutState& gridLayoutState)
{
ASSERT(gridItem.gridAreaContentLogicalHeight());
ASSERT(gridItem.gridAreaContentLogicalWidth());
// We clear height and width override values because we will decide now whether it's allowed or
// not, evaluating the conditions which might have changed since the old values were set.
gridItem.clearOverridingSize();
auto gridItemBlockDirection = GridLayoutFunctions::flowAwareDirectionForGridItem(*this, gridItem, Style::GridTrackSizingDirection::Rows);
auto gridItemInlineDirection = GridLayoutFunctions::flowAwareDirectionForGridItem(*this, gridItem, Style::GridTrackSizingDirection::Columns);
bool blockFlowIsColumnAxis = gridItemBlockDirection == Style::GridTrackSizingDirection::Rows;
bool allowedToStretchgridItemBlockSize = blockFlowIsColumnAxis ? allowedToStretchGridItemAlongColumnAxis(gridItem) : allowedToStretchGridItemAlongRowAxis(gridItem);
if (allowedToStretchgridItemBlockSize && !aspectRatioPrefersInline(gridItem, blockFlowIsColumnAxis)) {
auto overridingContainingBlockContentSizeForGridItem = GridLayoutFunctions::overridingContainingBlockContentSizeForGridItem(gridItem, gridItemBlockDirection);
ASSERT(overridingContainingBlockContentSizeForGridItem && *overridingContainingBlockContentSizeForGridItem);
LayoutUnit stretchedLogicalHeight = availableAlignmentSpaceForGridItemBeforeStretching(overridingContainingBlockContentSizeForGridItem->value(), gridItem, Style::GridTrackSizingDirection::Rows);
LayoutUnit desiredLogicalHeight = gridItem.constrainLogicalHeightByMinMax(stretchedLogicalHeight, std::nullopt);
gridItem.setOverridingBorderBoxLogicalHeight(desiredLogicalHeight);
auto itemNeedsRelayoutForStretchAlignment = [&]() {
if (desiredLogicalHeight != gridItem.logicalHeight())
return true;
if (canSetColumnAxisStretchRequirementForItem(gridItem))
return gridLayoutState.containsLayoutRequirementForGridItem(gridItem, ItemLayoutRequirement::NeedsColumnAxisStretchAlignment);
return is<RenderBlock>(gridItem) && downcast<RenderBlock>(gridItem).hasPercentHeightDescendants();
}();
// Checking the logical-height of a grid item isn't enough. Setting an override logical-height
// changes the definiteness, resulting in percentages to resolve differently.
//
// FIXME: Can avoid laying out here in some cases. See https://webkit.org/b/87905.
if (itemNeedsRelayoutForStretchAlignment) {
gridItem.setLogicalHeight(0_lu);
gridItem.setNeedsLayout(MarkOnlyThis);
}
} else if (!allowedToStretchgridItemBlockSize && allowedToStretchGridItemAlongRowAxis(gridItem)) {
auto overridingContainingBlockContentSizeForGridItem = GridLayoutFunctions::overridingContainingBlockContentSizeForGridItem(gridItem, gridItemInlineDirection);
ASSERT(overridingContainingBlockContentSizeForGridItem && *overridingContainingBlockContentSizeForGridItem);
LayoutUnit stretchedLogicalWidth = availableAlignmentSpaceForGridItemBeforeStretching(overridingContainingBlockContentSizeForGridItem->value(), gridItem, Style::GridTrackSizingDirection::Columns);
LayoutUnit desiredLogicalWidth = gridItem.constrainLogicalWidthByMinMax(stretchedLogicalWidth, contentBoxWidth(), *this);
gridItem.setOverridingBorderBoxLogicalWidth(desiredLogicalWidth);
if (desiredLogicalWidth != gridItem.logicalWidth())
gridItem.setNeedsLayout(MarkOnlyThis);
}
}
void RenderGrid::applySubgridStretchAlignmentToGridItemIfNeeded(RenderBox& gridItem)
{
CheckedPtr renderGrid = dynamicDowncast<RenderGrid>(gridItem);
if (!renderGrid)
return;
if (renderGrid->isSubgrid(Style::GridTrackSizingDirection::Rows)) {
auto gridItemBlockDirection = GridLayoutFunctions::flowAwareDirectionForGridItem(*this, gridItem, Style::GridTrackSizingDirection::Rows);
auto overridingContainingBlockContentSizeForGridItem = GridLayoutFunctions::overridingContainingBlockContentSizeForGridItem(gridItem, gridItemBlockDirection);
ASSERT(overridingContainingBlockContentSizeForGridItem && *overridingContainingBlockContentSizeForGridItem);
auto stretchedLogicalHeight = availableAlignmentSpaceForGridItemBeforeStretching(overridingContainingBlockContentSizeForGridItem->value(), gridItem, Style::GridTrackSizingDirection::Rows);
gridItem.setOverridingBorderBoxLogicalHeight(stretchedLogicalHeight);
}
if (renderGrid->isSubgrid(Style::GridTrackSizingDirection::Columns)) {
auto gridItemInlineDirection = GridLayoutFunctions::flowAwareDirectionForGridItem(*this, gridItem, Style::GridTrackSizingDirection::Columns);
auto overridingContainingBlockContentSizeForGridItem = GridLayoutFunctions::overridingContainingBlockContentSizeForGridItem(gridItem, gridItemInlineDirection);
ASSERT(overridingContainingBlockContentSizeForGridItem && *overridingContainingBlockContentSizeForGridItem);
auto stretchedLogicalWidth = availableAlignmentSpaceForGridItemBeforeStretching(overridingContainingBlockContentSizeForGridItem->value(), gridItem, Style::GridTrackSizingDirection::Columns);
gridItem.setOverridingBorderBoxLogicalWidth(stretchedLogicalWidth);
}
}
// FIXME: This logic is shared by RenderFlexibleBox, so it should be moved to RenderBox.
bool RenderGrid::hasAutoMarginsInColumnAxis(const RenderBox& gridItem) const
{
if (isHorizontalWritingMode())
return gridItem.style().marginTop().isAuto() || gridItem.style().marginBottom().isAuto();
return gridItem.style().marginLeft().isAuto() || gridItem.style().marginRight().isAuto();
}
// FIXME: This logic is shared by RenderFlexibleBox, so it should be moved to RenderBox.
bool RenderGrid::hasAutoMarginsInRowAxis(const RenderBox& gridItem) const
{
if (isHorizontalWritingMode())
return gridItem.style().marginLeft().isAuto() || gridItem.style().marginRight().isAuto();
return gridItem.style().marginTop().isAuto() || gridItem.style().marginBottom().isAuto();
}
// FIXME: This logic is shared by RenderFlexibleBox, so it should be moved to RenderBox.
void RenderGrid::updateAutoMarginsInRowAxisIfNeeded(RenderBox& gridItem)
{
ASSERT(!gridItem.isOutOfFlowPositioned());
auto& marginStart = gridItem.style().marginStart(writingMode());
auto& marginEnd = gridItem.style().marginEnd(writingMode());
LayoutUnit marginLogicalWidth;
// We should only consider computed margins if their specified value isn't
// 'auto', since such computed value may come from a previous layout and may
// be incorrect now.
if (!marginStart.isAuto())
marginLogicalWidth += gridItem.marginStart();
if (!marginEnd.isAuto())
marginLogicalWidth += gridItem.marginEnd();
auto availableAlignmentSpace = gridItem.gridAreaContentLogicalWidth()->value() - gridItem.logicalWidth() - marginLogicalWidth;
if (availableAlignmentSpace <= 0)
return;
if (marginStart.isAuto() && marginEnd.isAuto()) {
gridItem.setMarginStart(availableAlignmentSpace / 2, writingMode());
gridItem.setMarginEnd(availableAlignmentSpace / 2, writingMode());
} else if (marginStart.isAuto()) {
gridItem.setMarginStart(availableAlignmentSpace, writingMode());
} else if (marginEnd.isAuto()) {
gridItem.setMarginEnd(availableAlignmentSpace, writingMode());
}
}
// FIXME: This logic is shared by RenderFlexibleBox, so it should be moved to RenderBox.
void RenderGrid::updateAutoMarginsInColumnAxisIfNeeded(RenderBox& gridItem)
{
ASSERT(!gridItem.isOutOfFlowPositioned());
auto& marginBefore = gridItem.style().marginBefore(writingMode());
auto& marginAfter = gridItem.style().marginAfter(writingMode());
LayoutUnit marginLogicalHeight;
// We should only consider computed margins if their specified value isn't
// 'auto', since such computed value may come from a previous layout and may
// be incorrect now.
if (!marginBefore.isAuto())
marginLogicalHeight += gridItem.marginBefore();
if (!marginAfter.isAuto())
marginLogicalHeight += gridItem.marginAfter();
auto availableAlignmentSpace = gridItem.gridAreaContentLogicalHeight()->value() - gridItem.logicalHeight() - marginLogicalHeight;
if (availableAlignmentSpace <= 0)
return;
if (marginBefore.isAuto() && marginAfter.isAuto()) {
gridItem.setMarginBefore(availableAlignmentSpace / 2, writingMode());
gridItem.setMarginAfter(availableAlignmentSpace / 2, writingMode());
} else if (marginBefore.isAuto()) {
gridItem.setMarginBefore(availableAlignmentSpace, writingMode());
} else if (marginAfter.isAuto()) {
gridItem.setMarginAfter(availableAlignmentSpace, writingMode());
}
}
bool RenderGrid::isChildEligibleForMarginTrim(MarginTrimType marginTrimType, const RenderBox& gridItem) const
{
ASSERT(style().marginTrim().contains(marginTrimType));
auto isTrimmingBlockDirection = marginTrimType == MarginTrimType::BlockStart || marginTrimType == MarginTrimType::BlockEnd;
auto itemGridSpan = isTrimmingBlockDirection ? currentGrid().gridItemSpanIgnoringCollapsedTracks(gridItem, Style::GridTrackSizingDirection::Rows) : currentGrid().gridItemSpanIgnoringCollapsedTracks(gridItem, Style::GridTrackSizingDirection::Columns);
switch (marginTrimType) {
case MarginTrimType::BlockStart:
case MarginTrimType::InlineStart:
return !itemGridSpan.startLine();
case MarginTrimType::BlockEnd:
return itemGridSpan.endLine() == currentGrid().numTracks(Style::GridTrackSizingDirection::Rows);
case MarginTrimType::InlineEnd:
return itemGridSpan.endLine() == currentGrid().numTracks(Style::GridTrackSizingDirection::Columns);
}
ASSERT_NOT_REACHED();
return false;
}
bool RenderGrid::isBaselineAlignmentForGridItem(const RenderBox& gridItem) const
{
return isBaselineAlignmentForGridItem(gridItem, Style::GridTrackSizingDirection::Columns) || isBaselineAlignmentForGridItem(gridItem, Style::GridTrackSizingDirection::Rows);
}
bool RenderGrid::isBaselineAlignmentForGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection alignmentContextType) const
{
if (gridItem.isOutOfFlowPositioned())
return false;
auto align = selfAlignmentForGridItem(alignmentContextType, gridItem).position();
bool hasAutoMargins = alignmentContextType == Style::GridTrackSizingDirection::Rows ? hasAutoMarginsInColumnAxis(gridItem) : hasAutoMarginsInRowAxis(gridItem);
return isBaselinePosition(align) && !hasAutoMargins;
}
std::optional<LayoutUnit> RenderGrid::firstLineBaseline() const
{
if ((isWritingModeRoot() && !isFlexItem()) || !currentGrid().hasGridItems() || shouldApplyLayoutContainment())
return std::nullopt;
// Finding the first grid item in grid order.
auto baselineGridItem = getBaselineGridItem(ItemPosition::Baseline);
if (!baselineGridItem)
return std::nullopt;
auto baseline = GridLayoutFunctions::isOrthogonalGridItem(*this, *baselineGridItem) ? std::nullopt : baselineGridItem->firstLineBaseline();
// We take border-box's bottom if no valid baseline.
if (!baseline) {
// FIXME: We should pass |direction| into firstLineBaseline and stop bailing out if we're a writing
// mode root. This would also fix some cases where the grid is orthogonal to its container.
auto gridWritingMode = style().writingMode();
auto dominantBaseline = BaselineAlignmentState::dominantBaseline(gridWritingMode);
auto direction = isHorizontalWritingMode() ? LineDirection::Horizontal : LineDirection::Vertical;
return BaselineAlignmentState::synthesizedBaseline(*baselineGridItem, dominantBaseline, gridWritingMode, direction, BaselineSynthesisEdge::BorderBox) + logicalTopForChild(*baselineGridItem);
}
return baseline.value() + baselineGridItem->logicalTop().toInt();
}
std::optional<LayoutUnit> RenderGrid::lastLineBaseline() const
{
if (isWritingModeRoot() || !currentGrid().hasGridItems() || shouldApplyLayoutContainment())
return std::nullopt;
auto baselineGridItem = getBaselineGridItem(ItemPosition::LastBaseline);
if (!baselineGridItem)
return std::nullopt;
auto baseline = GridLayoutFunctions::isOrthogonalGridItem(*this, *baselineGridItem) ? std::nullopt : baselineGridItem->lastLineBaseline();
if (!baseline) {
auto direction = isHorizontalWritingMode() ? LineDirection::Horizontal : LineDirection::Vertical;
auto gridWritingMode = style().writingMode();
auto dominantBaseline = BaselineAlignmentState::dominantBaseline(gridWritingMode);
return BaselineAlignmentState::synthesizedBaseline(*baselineGridItem, dominantBaseline, gridWritingMode, direction, BaselineSynthesisEdge::BorderBox) + logicalTopForChild(*baselineGridItem);
}
return baseline.value() + baselineGridItem->logicalTop().toInt();
}
SingleThreadWeakPtr<RenderBox> RenderGrid::getBaselineGridItem(ItemPosition alignment) const
{
ASSERT(alignment == ItemPosition::Baseline || alignment == ItemPosition::LastBaseline);
const RenderBox* baselineGridItem = nullptr;
unsigned numColumns = currentGrid().numTracks(Style::GridTrackSizingDirection::Columns);
auto rowIndexDeterminingBaseline = alignment == ItemPosition::Baseline ? 0 : currentGrid().numTracks(Style::GridTrackSizingDirection::Rows) - 1;
for (size_t column = 0; column < numColumns; column++) {
auto cell = currentGrid().cell(rowIndexDeterminingBaseline, alignment == ItemPosition::Baseline ? column : numColumns - column - 1);
for (auto& gridItem : cell) {
ASSERT(gridItem.get());
// If an item participates in baseline alignment, we select such item.
if (isBaselineAlignmentForGridItem(*gridItem, Style::GridTrackSizingDirection::Rows)) {
auto gridItemAlignment = selfAlignmentForGridItem(Style::GridTrackSizingDirection::Rows, *gridItem).position();
if (rowIndexDeterminingBaseline == GridLayoutFunctions::alignmentContextForBaselineAlignment(gridSpanForGridItem(*gridItem, Style::GridTrackSizingDirection::Rows), gridItemAlignment)) {
// FIXME: self-baseline and content-baseline alignment not implemented yet.
baselineGridItem = gridItem.get();
break;
}
}
if (!baselineGridItem)
baselineGridItem = gridItem.get();
}
}
return baselineGridItem;
}
LayoutUnit RenderGrid::columnAxisBaselineOffsetForGridItem(const RenderBox& gridItem) const
{
// FIXME : CSS Masonry does not properly handle baseline calculations currently.
// We will just skip this running this step if we detect the RenderGrid is Masonry for now.
if (isMasonry())
return LayoutUnit { };
if (isSubgridRows()) {
RenderGrid* outer = downcast<RenderGrid>(parent());
if (GridLayoutFunctions::isOrthogonalGridItem(*outer, *this))
return outer->rowAxisBaselineOffsetForGridItem(gridItem);
return outer->columnAxisBaselineOffsetForGridItem(gridItem);
}
return m_trackSizingAlgorithm.baselineOffsetForGridItem(gridItem, Style::GridTrackSizingDirection::Rows);
}
LayoutUnit RenderGrid::rowAxisBaselineOffsetForGridItem(const RenderBox& gridItem) const
{
// FIXME : CSS Masonry does not properly handle baseline calculations currently.
// We will just skip this running this step if we detect the RenderGrid is Masonry for now.
if (isMasonry())
return LayoutUnit { };
if (isSubgridColumns()) {
RenderGrid* outer = downcast<RenderGrid>(parent());
if (GridLayoutFunctions::isOrthogonalGridItem(*outer, *this))
return outer->columnAxisBaselineOffsetForGridItem(gridItem);
return outer->rowAxisBaselineOffsetForGridItem(gridItem);
}
return m_trackSizingAlgorithm.baselineOffsetForGridItem(gridItem, Style::GridTrackSizingDirection::Columns);
}
GridAxisPosition RenderGrid::columnAxisPositionForGridItem(const RenderBox& gridItem) const
{
if (gridItem.isOutOfFlowPositioned() && !hasStaticPositionForGridItem(gridItem, Style::GridTrackSizingDirection::Rows))
return GridAxisPosition::GridAxisStart;
bool hasSameDirection = isHorizontalWritingMode()
? writingMode().isBlockTopToBottom() == gridItem.writingMode().isAnyTopToBottom()
: writingMode().isBlockLeftToRight() == gridItem.writingMode().isAnyLeftToRight();
switch (const auto gridItemAlignSelf = alignSelfForGridItem(gridItem).position()) {
case ItemPosition::SelfStart:
// self-start is based on the grid item's block-flow direction.
return hasSameDirection ? GridAxisPosition::GridAxisStart : GridAxisPosition::GridAxisEnd;
case ItemPosition::SelfEnd:
// self-end is based on the grid item's block-flow direction.
return hasSameDirection ? GridAxisPosition::GridAxisEnd : GridAxisPosition::GridAxisStart;
case ItemPosition::Left:
// Aligns the alignment subject to be flush with the alignment container's 'line-left' edge.
// The alignment axis (column axis) is always orthogonal to the inline axis, hence this value behaves as 'start'.
return GridAxisPosition::GridAxisStart;
case ItemPosition::Right:
// Aligns the alignment subject to be flush with the alignment container's 'line-right' edge.
// The alignment axis (column axis) is always orthogonal to the inline axis, hence this value behaves as 'start'.
return GridAxisPosition::GridAxisStart;
case ItemPosition::Center:
case ItemPosition::AnchorCenter:
return GridAxisPosition::GridAxisCenter;
case ItemPosition::FlexStart: // Only used in flex layout, otherwise equivalent to 'start'.
// Aligns the alignment subject to be flush with the alignment container's 'start' edge (block-start) in the column axis.
case ItemPosition::Start:
return GridAxisPosition::GridAxisStart;
case ItemPosition::FlexEnd: // Only used in flex layout, otherwise equivalent to 'end'.
// Aligns the alignment subject to be flush with the alignment container's 'end' edge (block-end) in the column axis.
case ItemPosition::End:
return GridAxisPosition::GridAxisEnd;
case ItemPosition::Stretch:
return GridAxisPosition::GridAxisStart;
case ItemPosition::Baseline:
case ItemPosition::LastBaseline: {
auto fallbackAlignment = [&] {
if (gridItemAlignSelf == ItemPosition::Baseline)
return hasSameDirection ? GridAxisPosition::GridAxisStart : GridAxisPosition::GridAxisEnd;
return hasSameDirection ? GridAxisPosition::GridAxisEnd : GridAxisPosition::GridAxisStart;
};
if (GridLayoutFunctions::isOrthogonalGridItem(*this, gridItem))
return gridItemAlignSelf == ItemPosition::Baseline ? GridAxisPosition::GridAxisStart : GridAxisPosition::GridAxisEnd;
return fallbackAlignment();
}
case ItemPosition::Legacy:
case ItemPosition::Auto:
case ItemPosition::Normal:
break;
}
ASSERT_NOT_REACHED();
return GridAxisPosition::GridAxisStart;
}
GridAxisPosition RenderGrid::rowAxisPositionForGridItem(const RenderBox& gridItem) const
{
if (gridItem.isOutOfFlowPositioned() && !hasStaticPositionForGridItem(gridItem, Style::GridTrackSizingDirection::Columns))
return GridAxisPosition::GridAxisStart;
bool hasSameDirection = isHorizontalWritingMode()
? writingMode().isInlineLeftToRight() == gridItem.writingMode().isAnyLeftToRight()
: writingMode().isInlineTopToBottom() == gridItem.writingMode().isAnyTopToBottom();
switch (justifySelfForGridItem(gridItem).position()) {
case ItemPosition::SelfStart:
// self-start is based on the grid item's inline-flow direction.
return hasSameDirection ? GridAxisPosition::GridAxisStart : GridAxisPosition::GridAxisEnd;
case ItemPosition::SelfEnd:
// self-end is based on the grid item's inline-flow direction.
return hasSameDirection ? GridAxisPosition::GridAxisEnd : GridAxisPosition::GridAxisStart;
case ItemPosition::Left:
// Aligns the alignment subject to be flush with the alignment container's 'line-left' edge.
// We want the physical 'left' side, so we have to take account, container's inline-flow direction.
return writingMode().isBidiLTR() ? GridAxisPosition::GridAxisStart : GridAxisPosition::GridAxisEnd;
case ItemPosition::Right:
// Aligns the alignment subject to be flush with the alignment container's 'line-right' edge.
// We want the physical 'right' side, so we have to take account, container's inline-flow direction.
return writingMode().isBidiLTR() ? GridAxisPosition::GridAxisEnd : GridAxisPosition::GridAxisStart;
case ItemPosition::Center:
case ItemPosition::AnchorCenter:
return GridAxisPosition::GridAxisCenter;
case ItemPosition::FlexStart: // Only used in flex layout, otherwise equivalent to 'start'.
// Aligns the alignment subject to be flush with the alignment container's 'start' edge (inline-start) in the row axis.
case ItemPosition::Start:
return GridAxisPosition::GridAxisStart;
case ItemPosition::FlexEnd: // Only used in flex layout, otherwise equivalent to 'end'.
// Aligns the alignment subject to be flush with the alignment container's 'end' edge (inline-end) in the row axis.
case ItemPosition::End:
return GridAxisPosition::GridAxisEnd;
case ItemPosition::Stretch:
return GridAxisPosition::GridAxisStart;
case ItemPosition::Baseline:
case ItemPosition::LastBaseline:
// FIXME: Implement the previous values. For now, we always 'start' align the grid item.
return GridAxisPosition::GridAxisStart;
case ItemPosition::Legacy:
case ItemPosition::Auto:
case ItemPosition::Normal:
break;
}
ASSERT_NOT_REACHED();
return GridAxisPosition::GridAxisStart;
}
LayoutUnit RenderGrid::columnAxisOffsetForGridItem(const RenderBox& gridItem) const
{
auto [startOfRow, endOfRow] = gridAreaPositionForGridItem(gridItem, Style::GridTrackSizingDirection::Rows);
LayoutUnit startPosition = startOfRow + marginBeforeForChild(gridItem);
LayoutUnit columnAxisGridItemSize = GridLayoutFunctions::isOrthogonalGridItem(*this, gridItem) ? gridItem.logicalWidth() + gridItem.marginLogicalWidth() : gridItem.logicalHeight() + gridItem.marginLogicalHeight();
LayoutUnit masonryOffset = areMasonryRows() ? m_masonryLayout.offsetForGridItem(gridItem) : 0_lu;
auto overflow = alignSelfForGridItem(gridItem).overflow();
LayoutUnit offsetFromStartPosition = computeOverflowAlignmentOffset(overflow, endOfRow - startOfRow, columnAxisGridItemSize);
if (hasAutoMarginsInColumnAxis(gridItem))
return startPosition;
GridAxisPosition axisPosition = columnAxisPositionForGridItem(gridItem);
switch (axisPosition) {
case GridAxisPosition::GridAxisStart:
return startPosition + columnAxisBaselineOffsetForGridItem(gridItem) + masonryOffset;
case GridAxisPosition::GridAxisEnd:
return (startPosition + offsetFromStartPosition) - columnAxisBaselineOffsetForGridItem(gridItem);
case GridAxisPosition::GridAxisCenter:
return startPosition + (offsetFromStartPosition / 2);
}
ASSERT_NOT_REACHED();
return 0;
}
LayoutUnit RenderGrid::rowAxisOffsetForGridItem(const RenderBox& gridItem) const
{
auto [startOfColumn, endOfColumn] = gridAreaPositionForGridItem(gridItem, Style::GridTrackSizingDirection::Columns);
LayoutUnit startPosition = startOfColumn + marginStartForChild(gridItem);
LayoutUnit masonryOffset = areMasonryColumns() ? m_masonryLayout.offsetForGridItem(gridItem) : 0_lu;
if (hasAutoMarginsInRowAxis(gridItem))
return startPosition;
GridAxisPosition axisPosition = rowAxisPositionForGridItem(gridItem);
switch (axisPosition) {
case GridAxisPosition::GridAxisStart:
return startPosition + rowAxisBaselineOffsetForGridItem(gridItem) + masonryOffset;
case GridAxisPosition::GridAxisEnd:
case GridAxisPosition::GridAxisCenter: {
LayoutUnit rowAxisGridItemSize = GridLayoutFunctions::isOrthogonalGridItem(*this, gridItem) ? gridItem.logicalHeight() + gridItem.marginLogicalHeight() : gridItem.logicalWidth() + gridItem.marginLogicalWidth();
auto overflow = justifySelfForGridItem(gridItem).overflow();
LayoutUnit offsetFromStartPosition = computeOverflowAlignmentOffset(overflow, endOfColumn - startOfColumn, rowAxisGridItemSize);
return startPosition + (axisPosition == GridAxisPosition::GridAxisEnd ? offsetFromStartPosition : offsetFromStartPosition / 2);
}
}
ASSERT_NOT_REACHED();
return 0;
}
bool RenderGrid::isSubgrid() const
{
return isSubgrid(Style::GridTrackSizingDirection::Rows) || isSubgrid(Style::GridTrackSizingDirection::Columns);
}
bool RenderGrid::isSubgrid(Style::GridTrackSizingDirection direction) const
{
// If the grid container is forced to establish an independent formatting
// context (like contain layout, or position:absolute), then the used value
// of grid-template-rows/columns is 'none' and the container is not a subgrid.
// https://drafts.csswg.org/css-grid-2/#subgrid-listing
if (establishesIndependentFormattingContextIgnoringDisplayType(style()))
return false;
if (!style().gridTemplateList(direction).subgrid)
return false;
auto* renderGrid = dynamicDowncast<RenderGrid>(parent());
if (!renderGrid)
return false;
return !renderGrid->isMasonry(direction);
}
bool RenderGrid::isSubgridInParentDirection(Style::GridTrackSizingDirection parentDirection) const
{
auto* renderGrid = dynamicDowncast<RenderGrid>(parent());
if (!renderGrid)
return false;
auto direction = GridLayoutFunctions::flowAwareDirectionForGridItem(*renderGrid, *this, parentDirection);
return isSubgrid(direction);
}
bool RenderGrid::isSubgridOf(Style::GridTrackSizingDirection direction, const RenderGrid& ancestor) const
{
if (!isSubgrid(direction))
return false;
if (parent() == &ancestor)
return true;
auto& parentGrid = *downcast<RenderGrid>(parent());
auto parentDirection = GridLayoutFunctions::flowAwareDirectionForParent(parentGrid, *this, direction);
return parentGrid.isSubgridOf(parentDirection, ancestor);
}
const Grid& RenderGrid::currentGrid() const
{
return m_grid.m_currentGrid;
}
Grid& RenderGrid::currentGrid()
{
return m_grid.m_currentGrid;
}
LayoutUnit RenderGrid::gridAreaBreadthForOutOfFlowGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction)
{
ASSERT(gridItem.isOutOfFlowPositioned());
bool isRowAxis = direction == Style::GridTrackSizingDirection::Columns;
int lastLine = numTracks(direction);
int startLine, endLine;
bool startIsAuto, endIsAuto;
if (!computeGridPositionsForOutOfFlowGridItem(gridItem, direction, startLine, startIsAuto, endLine, endIsAuto))
return isRowAxis ? clientLogicalWidth() : clientLogicalHeight();
if (startIsAuto && endIsAuto)
return isRowAxis ? clientLogicalWidth() : clientLogicalHeight();
LayoutUnit start;
LayoutUnit end;
auto& positions = this->positions(direction);
auto& outOfFlowItemLine = outOfFlowItem(direction);
LayoutUnit borderEdge = isRowAxis ? borderStart() : borderBefore();
if (startIsAuto)
start = borderEdge;
else {
outOfFlowItemLine.set(gridItem, startLine);
start = positions[startLine];
}
if (endIsAuto)
end = ((direction == Style::GridTrackSizingDirection::Rows) ? clientLogicalHeight() : clientLogicalWidth()) + borderEdge;
else {
end = positions[endLine];
// These vectors store line positions including gaps, but we shouldn't consider them for the edges of the grid.
std::optional<LayoutUnit> availableSizeForGutters = availableSpaceForGutters(direction);
if (endLine > 0 && endLine < lastLine) {
ASSERT(!currentGrid().needsItemsPlacement());
end -= guttersSize(direction, endLine - 1, 2, availableSizeForGutters);
end -= isRowAxis ? m_offsetBetweenColumns.distributionOffset : m_offsetBetweenRows.distributionOffset;
}
}
return std::max(end - start, 0_lu);
}
LayoutUnit RenderGrid::logicalOffsetForOutOfFlowGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction, LayoutUnit trackBreadth) const
{
ASSERT(gridItem.isOutOfFlowPositioned());
if (hasStaticPositionForGridItem(gridItem, direction))
return 0_lu;
bool isRowAxis = direction == Style::GridTrackSizingDirection::Columns;
bool isFlowAwareRowAxis = GridLayoutFunctions::flowAwareDirectionForGridItem(*this, gridItem, direction) == Style::GridTrackSizingDirection::Columns;
LayoutUnit gridItemPosition = isFlowAwareRowAxis ? gridItem.logicalLeft() : gridItem.logicalTop();
LayoutUnit gridBorder = isRowAxis ? borderLogicalLeft() : borderBefore();
LayoutUnit gridItemMargin = isRowAxis ? gridItem.marginLogicalLeft(writingMode()) : gridItem.marginBefore(writingMode());
LayoutUnit offset = gridItemPosition - gridBorder - gridItemMargin;
if (!isRowAxis || writingMode().isLogicalLeftInlineStart())
return offset;
LayoutUnit gridItemBreadth = isFlowAwareRowAxis ? gridItem.logicalWidth() + gridItem.marginLogicalWidth() : gridItem.logicalHeight() + gridItem.marginLogicalHeight();
return trackBreadth - offset - gridItemBreadth;
}
std::optional<LayoutRange> RenderGrid::gridAreaRowRangeForOutOfFlow(const RenderBox& gridItem) const
{
ASSERT(gridItem.isOutOfFlowPositioned());
auto areaSize = GridLayoutFunctions::overridingContainingBlockContentSizeForGridItem(gridItem, Style::GridTrackSizingDirection::Rows);
if (!areaSize)
return std::nullopt;
LayoutRange range(borderBefore(), areaSize->value());
if (auto line = m_outOfFlowItemRow.get(gridItem))
range.moveTo(m_rowPositions[line.value()]);
return range;
}
std::optional<LayoutRange> RenderGrid::gridAreaColumnRangeForOutOfFlow(const RenderBox& gridItem) const
{
ASSERT(gridItem.isOutOfFlowPositioned());
auto areaSize = GridLayoutFunctions::overridingContainingBlockContentSizeForGridItem(gridItem, Style::GridTrackSizingDirection::Columns);
if (!areaSize)
return std::nullopt;
LayoutRange range(borderStart(), areaSize->value());
if (auto line = m_outOfFlowItemColumn.get(gridItem))
range.moveTo(m_columnPositions[line.value()]);
return range;
}
std::pair<LayoutUnit, LayoutUnit> RenderGrid::gridAreaPositionForOutOfFlowGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction) const
{
std::optional<LayoutRange> range = direction == Style::GridTrackSizingDirection::Columns
? gridAreaColumnRangeForOutOfFlow(gridItem)
: gridAreaRowRangeForOutOfFlow(gridItem);
ASSERT(range);
range->moveBy(logicalOffsetForOutOfFlowGridItem(gridItem, direction, range->size()));
return { range->min(), range->max() };
}
std::pair<LayoutUnit, LayoutUnit> RenderGrid::gridAreaPositionForInFlowGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction) const
{
ASSERT(!gridItem.isOutOfFlowPositioned());
const auto& span = currentGrid().gridItemSpan(gridItem, direction);
const auto& positions = this->positions(direction);
auto start = positions[span.startLine()];
auto end = positions[span.endLine()];
// The 'positions' vector includes distribution offset (because of content
// alignment) and gutters, so we need to subtract them to get the actual
// end position for a given track (this does not have to be done for the
// last track as there are no more positions' elements after it, nor for
// collapsed tracks).
if (span.endLine() < positions.size() - 1
&& !(currentGrid().hasAutoRepeatEmptyTracks(direction)
&& currentGrid().isEmptyAutoRepeatTrack(direction, span.endLine()))) {
end -= gridGap(direction) + gridItemOffset(direction);
}
return { start, end };
}
std::pair<LayoutUnit, LayoutUnit> RenderGrid::gridAreaPositionForGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction) const
{
if (gridItem.isOutOfFlowPositioned())
return gridAreaPositionForOutOfFlowGridItem(gridItem, direction);
return gridAreaPositionForInFlowGridItem(gridItem, direction);
}
std::pair<OverflowAlignment, ContentPosition> static resolveContentDistributionFallback(ContentDistribution distribution)
{
switch (distribution) {
case ContentDistribution::SpaceBetween:
return { OverflowAlignment::Default, ContentPosition::Start };
case ContentDistribution::SpaceAround:
return { OverflowAlignment::Safe, ContentPosition::Center };
case ContentDistribution::SpaceEvenly:
return { OverflowAlignment::Safe, ContentPosition::Center };
case ContentDistribution::Stretch:
return { OverflowAlignment::Default, ContentPosition::Start };
case ContentDistribution::Default:
return { OverflowAlignment::Default, ContentPosition::Normal };
}
ASSERT_NOT_REACHED();
return { OverflowAlignment::Default, ContentPosition::Normal };
}
StyleContentAlignmentData RenderGrid::contentAlignment(Style::GridTrackSizingDirection direction) const
{
return direction == Style::GridTrackSizingDirection::Columns ? style().resolvedJustifyContent(contentAlignmentNormalBehaviorGrid()) : style().resolvedAlignContent(contentAlignmentNormalBehaviorGrid());
}
ContentAlignmentData RenderGrid::computeContentPositionAndDistributionOffset(Style::GridTrackSizingDirection direction, const LayoutUnit& availableFreeSpace, unsigned numberOfGridTracks) const
{
if (isSubgrid(direction))
return { };
auto contentAlignmentData = contentAlignment(direction);
auto contentAlignmentDistribution = contentAlignmentData.distribution();
// Apply <content-distribution> and return, or continue to fallback positioning if we can't distribute.
if (contentAlignmentDistribution != ContentDistribution::Default) {
if (availableFreeSpace > 0) {
switch (contentAlignmentDistribution) {
case ContentDistribution::SpaceBetween:
if (numberOfGridTracks < 2)
break;
return { 0_lu, availableFreeSpace / (numberOfGridTracks - 1) };
case ContentDistribution::SpaceAround: {
if (numberOfGridTracks < 1)
break;
auto spaceBetweenTracks = availableFreeSpace / numberOfGridTracks;
return { spaceBetweenTracks / 2, spaceBetweenTracks };
}
case ContentDistribution::SpaceEvenly: {
auto spaceEvenlyDistribution = availableFreeSpace / (numberOfGridTracks + 1);
return { spaceEvenlyDistribution, spaceEvenlyDistribution };
}
case ContentDistribution::Stretch:
break;
default:
ASSERT_NOT_REACHED();
}
}
}
auto [fallbackOverflow, fallbackContentPosition] = resolveContentDistributionFallback(contentAlignmentDistribution);
auto contentAlignmentOverflow = contentAlignmentData.overflow();
// Apply alignment safety.
if (availableFreeSpace <= 0 && (contentAlignmentOverflow == OverflowAlignment::Safe || fallbackOverflow == OverflowAlignment::Safe))
return { };
auto usedContentPosition = contentAlignmentDistribution == ContentDistribution::Default ? contentAlignmentData.position() : fallbackContentPosition;
// Apply <content-position> / fallback positioning.
switch (usedContentPosition) {
case ContentPosition::Left:
ASSERT(direction == Style::GridTrackSizingDirection::Columns);
if (!writingMode().isBidiLTR())
return { availableFreeSpace, 0_lu };
return { };
case ContentPosition::Right:
ASSERT(direction == Style::GridTrackSizingDirection::Columns);
if (writingMode().isBidiLTR())
return { availableFreeSpace, 0_lu };
return { };
case ContentPosition::Center:
return { availableFreeSpace / 2, 0_lu };
case ContentPosition::FlexEnd: // Only used in flex layout, for other layout, it's equivalent to 'end'.
case ContentPosition::End:
return { availableFreeSpace, 0_lu };
case ContentPosition::FlexStart: // Only used in flex layout, for other layout, it's equivalent to 'start'.
case ContentPosition::Start:
case ContentPosition::Baseline:
case ContentPosition::LastBaseline:
// FIXME: Implement the baseline values. For now, we always 'start' align.
// http://webkit.org/b/145566
return { };
case ContentPosition::Normal:
default:
ASSERT_NOT_REACHED();
return { };
}
}
LayoutOptionalOutsets RenderGrid::allowedLayoutOverflow() const
{
LayoutOptionalOutsets allowance = RenderBox::allowedLayoutOverflow();
if (m_offsetBetweenColumns.positionOffset < 0)
allowance.setStart(-m_offsetBetweenColumns.positionOffset, writingMode());
if (m_offsetBetweenRows.positionOffset < 0) {
if (isHorizontalWritingMode())
allowance.setTop(-m_offsetBetweenRows.positionOffset);
else
allowance.setLeft(-m_offsetBetweenRows.positionOffset);
}
return allowance;
}
LayoutUnit RenderGrid::translateRTLCoordinate(LayoutUnit coordinate) const
{
LayoutUnit width = borderLogicalLeft() + borderLogicalRight() + clientLogicalWidth();
#if !PLATFORM(IOS_FAMILY)
// FIXME: Ideally scrollbarLogicalWidth() should return zero in iOS so we don't need this
// (see bug https://webkit.org/b/191857).
// If we are in horizontal writing mode and RTL direction the scrollbar is painted on the left,
// so we need to take into account when computing the position of the columns.
if (writingMode().isHorizontal())
width += scrollbarLogicalWidth();
#endif
return width - coordinate;
}
// FIXME: SetLogicalPositionForGridItem has only one caller, consider its refactoring in the future.
void RenderGrid::setLogicalPositionForGridItem(RenderBox& gridItem) const
{
// "In the positioning phase [...] calculations are performed according to the writing mode of the containing block of the box establishing the
// orthogonal flow." However, 'setLogicalLocation' will only take into account the grid item's writing-mode, so the position may need to be transposed.
LayoutPoint gridItemLocation(logicalOffsetForGridItem(gridItem, Style::GridTrackSizingDirection::Columns), logicalOffsetForGridItem(gridItem, Style::GridTrackSizingDirection::Rows));
gridItem.setLogicalLocation(GridLayoutFunctions::isOrthogonalGridItem(*this, gridItem) ? gridItemLocation.transposedPoint() : gridItemLocation);
}
LayoutUnit RenderGrid::logicalOffsetForGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction) const
{
if (direction == Style::GridTrackSizingDirection::Rows)
return columnAxisOffsetForGridItem(gridItem);
LayoutUnit rowAxisOffset = rowAxisOffsetForGridItem(gridItem);
// We stored m_columnPositions's data ignoring the direction, hence we might need now
// to translate positions from RTL to LTR, as it's more convenient for painting.
if (writingMode().isInlineFlipped())
rowAxisOffset = translateRTLCoordinate(rowAxisOffset) - (GridLayoutFunctions::isOrthogonalGridItem(*this, gridItem) ? gridItem.logicalHeight() : gridItem.logicalWidth());
return rowAxisOffset;
}
unsigned RenderGrid::nonCollapsedTracks(Style::GridTrackSizingDirection direction) const
{
auto& tracks = m_trackSizingAlgorithm.tracks(direction);
size_t numberOfTracks = tracks.size();
bool hasCollapsedTracks = currentGrid().hasAutoRepeatEmptyTracks(direction);
size_t numberOfCollapsedTracks = hasCollapsedTracks ? currentGrid().autoRepeatEmptyTracks(direction)->size() : 0;
return numberOfTracks - numberOfCollapsedTracks;
}
unsigned RenderGrid::numTracks(Style::GridTrackSizingDirection direction) const
{
// Due to limitations in our internal representation, we cannot know the number of columns from
// currentGrid *if* there is no row (because currentGrid would be empty). That's why in that case we need
// to get it from the style. Note that we know for sure that there aren't any implicit tracks,
// because not having rows implies that there are no "normal" grid items (out-of-flow grid items are
// not stored in currentGrid).
ASSERT(!currentGrid().needsItemsPlacement());
if (direction == Style::GridTrackSizingDirection::Rows)
return currentGrid().numTracks(Style::GridTrackSizingDirection::Rows);
// FIXME: This still requires knowledge about currentGrid internals.
return currentGrid().numTracks(Style::GridTrackSizingDirection::Rows)
? currentGrid().numTracks(Style::GridTrackSizingDirection::Columns)
: Style::GridPositionsResolver::explicitGridCount(*this, Style::GridTrackSizingDirection::Columns);
}
void RenderGrid::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset, PaintInfo& forChild, bool usePrintRect)
{
ASSERT(!currentGrid().needsItemsPlacement());
for (RenderBox* gridItem = currentGrid().orderIterator().first(); gridItem; gridItem = currentGrid().orderIterator().next())
paintChild(*gridItem, paintInfo, paintOffset, forChild, usePrintRect, PaintAsInlineBlock);
}
bool RenderGrid::hitTestChildren(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& adjustedLocation, HitTestAction hitTestAction)
{
if (hitTestAction != HitTestForeground)
return false;
LayoutPoint scrolledOffset = hasNonVisibleOverflow() ? adjustedLocation - toLayoutSize(scrollPosition()) : adjustedLocation;
Vector<RenderBox*> reversedOrderIteratorForHitTesting;
for (auto* gridItem = currentGrid().orderIterator().first(); gridItem; gridItem = currentGrid().orderIterator().next()) {
if (gridItem->isOutOfFlowPositioned())
continue;
reversedOrderIteratorForHitTesting.append(gridItem);
}
reversedOrderIteratorForHitTesting.reverse();
for (auto* gridItem : reversedOrderIteratorForHitTesting) {
if (gridItem->hasSelfPaintingLayer())
continue;
auto location = flipForWritingModeForChild(*gridItem, scrolledOffset);
if (gridItem->hitTest(request, result, locationInContainer, location)) {
updateHitTestResult(result, flipForWritingMode(toLayoutPoint(locationInContainer.point() - adjustedLocation)));
return true;
}
}
return false;
}
ASCIILiteral RenderGrid::renderName() const
{
if (isFloating())
return "RenderGrid (floating)"_s;
if (isOutOfFlowPositioned())
return "RenderGrid (positioned)"_s;
if (isAnonymous())
return "RenderGrid (generated)"_s;
if (isRelativelyPositioned())
return "RenderGrid (relative positioned)"_s;
return "RenderGrid"_s;
}
bool RenderGrid::hasAutoSizeInColumnAxis(const RenderBox& gridItem) const
{
if (gridItem.style().hasAspectRatio()) {
// FIXME: should align-items + align-self: auto/justify-items + justify-self: auto be taken into account?
if (isHorizontalWritingMode() == gridItem.isHorizontalWritingMode() && gridItem.style().alignSelf().position() != ItemPosition::Stretch) {
// A non-auto inline size means the same for block size (column axis size) because of the aspect ratio.
if (!gridItem.style().logicalWidth().isAuto())
return false;
} else if (gridItem.style().justifySelf().position() != ItemPosition::Stretch) {
auto& logicalHeight = gridItem.style().logicalHeight();
if (logicalHeight.isFixed() || (logicalHeight.isPercentOrCalculated() && gridItem.percentageLogicalHeightIsResolvable()))
return false;
}
}
return isHorizontalWritingMode() ? gridItem.style().height().isAuto() : gridItem.style().width().isAuto();
}
bool RenderGrid::hasAutoSizeInRowAxis(const RenderBox& gridItem) const
{
if (gridItem.style().hasAspectRatio()) {
// FIXME: should align-items + align-self: auto/justify-items + justify-self: auto be taken into account?
if (isHorizontalWritingMode() == gridItem.isHorizontalWritingMode() && gridItem.style().justifySelf().position() != ItemPosition::Stretch) {
// A non-auto block size means the same for inline size (row axis size) because of the aspect ratio.
auto& logicalHeight = gridItem.style().logicalHeight();
if (logicalHeight.isFixed() || (logicalHeight.isPercentOrCalculated() && gridItem.percentageLogicalHeightIsResolvable()))
return false;
} else if (gridItem.style().alignSelf().position() != ItemPosition::Stretch) {
if (!gridItem.style().logicalWidth().isAuto())
return false;
}
}
return isHorizontalWritingMode() ? gridItem.style().width().isAuto() : gridItem.style().height().isAuto();
}
bool RenderGrid::computeGridPositionsForOutOfFlowGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction, int& startLine, bool& startIsAuto, int& endLine, bool& endIsAuto) const
{
ASSERT(gridItem.isOutOfFlowPositioned());
int lastLine = numTracks(direction);
auto span = Style::GridPositionsResolver::resolveGridPositionsFromStyle(*this, gridItem, direction);
if (span.isIndefinite())
return false;
unsigned explicitStart = currentGrid().explicitGridStart(direction);
startLine = span.untranslatedStartLine() + explicitStart;
endLine = span.untranslatedEndLine() + explicitStart;
startIsAuto = gridItem.style().gridItemStart(direction).isAuto() || startLine < 0 || startLine > lastLine;
endIsAuto = gridItem.style().gridItemEnd(direction).isAuto() || endLine < 0 || endLine > lastLine;
return true;
}
GridSpan RenderGrid::gridSpanForOutOfFlowGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction) const
{
int lastLine = numTracks(direction);
int startLine, endLine;
bool startIsAuto, endIsAuto;
if (!computeGridPositionsForOutOfFlowGridItem(gridItem, direction, startLine, startIsAuto, endLine, endIsAuto))
return GridSpan::translatedDefiniteGridSpan(0, lastLine);
return GridSpan::translatedDefiniteGridSpan(startIsAuto ? 0 : startLine, endIsAuto ? lastLine : endLine);
}
GridSpan RenderGrid::gridSpanForGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction) const
{
RenderGrid* renderGrid = downcast<RenderGrid>(gridItem.parent());
// |direction| is specified relative to this grid, switch it if |gridItem|'s direct parent grid
// is using a different writing mode.
direction = GridLayoutFunctions::flowAwareDirectionForGridItem(*this, *renderGrid, direction);
GridSpan span = gridItem.isOutOfFlowPositioned() ? renderGrid->gridSpanForOutOfFlowGridItem(gridItem, direction) : renderGrid->currentGrid().gridItemSpan(gridItem, direction);
while (renderGrid != this) {
RenderGrid* parent = downcast<RenderGrid>(renderGrid->parent());
bool isSubgrid = renderGrid->isSubgrid(direction);
direction = GridLayoutFunctions::flowAwareDirectionForGridItem(*parent, *renderGrid, direction);
GridSpan parentSpan = renderGrid->isOutOfFlowPositioned() ? parent->gridSpanForOutOfFlowGridItem(*renderGrid, direction) : parent->currentGrid().gridItemSpan(*renderGrid, direction);
if (isSubgrid)
span.translateTo(parentSpan, GridLayoutFunctions::isSubgridReversedDirection(*parent, direction, *renderGrid));
else
span = parentSpan;
renderGrid = parent;
}
return span;
}
RenderGrid::GridWrapper::GridWrapper(RenderGrid& renderGrid)
: m_layoutGrid(renderGrid)
{ }
void RenderGrid::GridWrapper::resetCurrentGrid() const
{
m_currentGrid = std::ref(const_cast<Grid&>(m_layoutGrid));
}
void RenderGrid::computeOverflow(LayoutUnit oldClientAfterEdge, bool recomputeFloats)
{
RenderBlock::computeOverflow(oldClientAfterEdge, recomputeFloats);
if (!hasPotentiallyScrollableOverflow() || isMasonry() || isSubgridRows() || isSubgridColumns())
return;
// FIXME: We should handle RTL and other writing modes also.
if (writingMode().isBidiLTR() && isHorizontalWritingMode()) {
auto gridAreaSize = LayoutSize { m_columnPositions.last(), m_rowPositions.last() };
gridAreaSize += { paddingEnd(), paddingAfter() };
addLayoutOverflow({ { }, gridAreaSize });
}
}
void RenderGrid::updateIntrinsicLogicalHeightsForRowSizingFirstPassCacheAvailability()
{
auto canCreateIntrinsicLogicalHeightsCacheForRowSizingFirstPass = this->canCreateIntrinsicLogicalHeightsForRowSizingFirstPassCache();
if (canCreateIntrinsicLogicalHeightsCacheForRowSizingFirstPass && m_intrinsicLogicalHeightsForRowSizingFirstPass) {
for (auto& gridItem : childrenOfType<RenderBox>(*this)) {
if (gridItem.needsLayout())
m_intrinsicLogicalHeightsForRowSizingFirstPass->invalidateSizeForItem(gridItem);
}
} else if (canCreateIntrinsicLogicalHeightsCacheForRowSizingFirstPass)
m_intrinsicLogicalHeightsForRowSizingFirstPass.emplace();
else
m_intrinsicLogicalHeightsForRowSizingFirstPass.reset();
}
std::optional<GridItemSizeCache>& RenderGrid::intrinsicLogicalHeightsForRowSizingFirstPass() const
{
ASSERT_IMPLIES(m_intrinsicLogicalHeightsForRowSizingFirstPass, canCreateIntrinsicLogicalHeightsForRowSizingFirstPassCache());
return m_intrinsicLogicalHeightsForRowSizingFirstPass;
}
bool RenderGrid::canCreateIntrinsicLogicalHeightsForRowSizingFirstPassCache() const
{
if (isMasonry())
return false;
if (isSubgridRows())
return false;
if (enclosingFragmentedFlow())
return false;
for (auto& gridItem : childrenOfType<RenderBox>(*this)) {
if (auto* renderGrid = dynamicDowncast<RenderGrid>(gridItem)) {
if (renderGrid->isSubgridRows())
return false;
if (renderGrid->isSubgridColumns() && GridLayoutFunctions::isOrthogonalGridItem(*this, *renderGrid))
return false;
}
if (isBaselineAlignmentForGridItem(gridItem))
return false;
}
return true;
}
void GridItemSizeCache::setSizeForGridItem(const RenderBox& gridItem, LayoutUnit size)
{
m_sizes.set(gridItem, size);
}
std::optional<LayoutUnit> GridItemSizeCache::sizeForItem(const RenderBox& gridItem) const
{
return m_sizes.get(gridItem);
}
void GridItemSizeCache::invalidateSizeForItem(const RenderBox& gridItem)
{
m_sizes.remove(gridItem);
}
} // namespace WebCore