blob: a5be6c1c7cdc822a625c5e74e7e2c46989a05a88 [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 "BaselineAlignmentInlines.h"
#include "GridArea.h"
#include "GridLayoutFunctions.h"
#include "GridMasonryLayout.h"
#include "GridTrackSizingAlgorithm.h"
#include "HitTestLocation.h"
#include "LayoutIntegrationGridCoverage.h"
#include "LayoutIntegrationGridLayout.h"
#include "LayoutRange.h"
#include "LayoutRepainter.h"
#include "RenderChildIterator.h"
#include "RenderElementInlines.h"
#include "RenderElementStyleInlines.h"
#include "RenderLayer.h"
#include "RenderLayoutState.h"
#include "RenderObjectInlines.h"
#include "RenderTreeBuilder.h"
#include "RenderView.h"
#include "StyleGridPositionsResolver.h"
#include "StylePrimitiveNumericTypes+Evaluation.h"
#include <ranges>
#include <wtf/Range.h>
#include <wtf/Scope.h>
#include <wtf/SetForScope.h>
#include <wtf/TZoneMallocInlines.h>
namespace WebCore {
WTF_MAKE_TZONE_ALLOCATED_IMPL(RenderGrid);
enum class TrackSizeRestriction : uint8_t {
AllowInfinity,
ForbidInfinity,
};
RenderGrid::RenderGrid(Element& element, RenderStyle&& style)
: RenderBlock(Type::Grid, element, WTF::move(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(const RenderBox& gridItem, LogicalBoxAxis containingAxis, StretchingMode stretchingMode, const RenderStyle* gridStyle) const
{
if (isMasonry(containingAxis))
return { ItemPosition::Start };
if (CheckedPtr renderGrid = dynamicDowncast<RenderGrid>(gridItem); renderGrid && renderGrid->isSubgridInParentDirection(Style::gridTrackSizingDirection(containingAxis)))
return { ItemPosition::Stretch, OverflowAlignment::Default };
if (!gridStyle)
gridStyle = &style();
auto alignment = containingAxis == LogicalBoxAxis::Inline
? gridItem.style().justifySelf().resolve(gridStyle) : gridItem.style().alignSelf().resolve(gridStyle);
if (alignment.isNormal() && StretchingMode::Explicit != stretchingMode)
alignment.setPosition(gridItem.isRenderReplaced() ? ItemPosition::Start : ItemPosition::Stretch);
return alignment;
}
bool RenderGrid::selfAlignmentChangedToStretch(LogicalBoxAxis containingAxis, const RenderStyle& oldStyle, const RenderStyle& newStyle, const RenderBox& gridItem) const
{
return selfAlignmentForGridItem(gridItem, containingAxis, StretchingMode::Normal, &oldStyle).position() != ItemPosition::Stretch
&& selfAlignmentForGridItem(gridItem, containingAxis, StretchingMode::Normal, &newStyle).position() == ItemPosition::Stretch;
}
bool RenderGrid::selfAlignmentChangedFromStretch(LogicalBoxAxis containingAxis, const RenderStyle& oldStyle, const RenderStyle& newStyle, const RenderBox& gridItem) const
{
return selfAlignmentForGridItem(gridItem, containingAxis, StretchingMode::Normal, &oldStyle).position() == ItemPosition::Stretch
&& selfAlignmentForGridItem(gridItem, containingAxis, StretchingMode::Normal, &newStyle).position() != ItemPosition::Stretch;
}
void RenderGrid::styleDidChange(Style::Difference diff, const RenderStyle* oldStyle)
{
RenderBlock::styleDidChange(diff, oldStyle);
if (!oldStyle || diff != Style::DifferenceResult::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();
}
bool alignItemsStretchChanged = [&]() {
auto oldAlignItems = oldStyle->alignItems().resolve().position();
auto newAlignItems = style().alignItems().resolve().position();
return oldAlignItems != newAlignItems && (oldAlignItems == ItemPosition::Normal || newAlignItems == ItemPosition::Normal || oldAlignItems == ItemPosition::Stretch || newAlignItems == ItemPosition::Stretch);
}();
bool justifyItemsStretchChanged = [&]() {
auto oldJustifyItems = oldStyle->justifyItems().resolve().position();
auto newJustifyItems = style().justifyItems().resolve().position();
return oldJustifyItems != newJustifyItems && (oldJustifyItems == ItemPosition::Normal || newJustifyItems == ItemPosition::Normal || oldJustifyItems == ItemPosition::Stretch || newJustifyItems == ItemPosition::Stretch);
}();
if (alignItemsStretchChanged || justifyItemsStretchChanged) {
// 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(LogicalBoxAxis::Block, *oldStyle, newStyle, gridItem)
|| selfAlignmentChangedFromStretch(LogicalBoxAxis::Block, *oldStyle, newStyle, gridItem)
|| selfAlignmentChangedToStretch(LogicalBoxAxis::Inline, *oldStyle, newStyle, gridItem)
|| selfAlignmentChangedFromStretch(LogicalBoxAxis::Inline, *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 && willStretchItem(gridItem, LogicalBoxAxis::Block);
}
void RenderGrid::computeLayoutRequirementsForItemsBeforeLayout(GridLayoutState& gridLayoutState) const
{
for (auto& gridItem : childrenOfType<RenderBox>(*this)) {
auto gridItemAlignSelf = selfAlignmentForGridItem(gridItem, LogicalBoxAxis::Block).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());
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();
if (layoutUsingGridFormattingContext())
return;
// 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(contentOverflowRect(), ComputeOverflowOptions::MarginsExtendLayoutOverflow);
updateDescendantTransformsAfterLayout();
}
updateLayerTransform();
repainter.repaintAfterLayout();
m_trackSizingAlgorithm.clearBaselineItemsCache();
m_baselineItemsCached = false;
}
bool RenderGrid::layoutUsingGridFormattingContext()
{
if (!m_hasGridFormattingContextLayout.has_value())
m_hasGridFormattingContextLayout = LayoutIntegration::canUseForGridLayout(*this);
if (!*m_hasGridFormattingContextLayout)
return false;
auto gridLayout = LayoutIntegration::GridLayout { *this };
gridLayout.updateFormattingContextGeometries();
gridLayout.layout();
updateLogicalHeight();
return true;
}
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(contentOverflowRect());
updateDescendantTransformsAfterLayout();
}
updateLayerTransform();
repainter.repaintAfterLayout();
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<LayoutUnit>(gap, availableSize.value_or(0_lu), Style::ZoomNeeded { });
}
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.resolveZoom(style().usedZoomForLength()) };
return isRowAxis
? adjustContentBoxLogicalWidthForBoxSizing(maxSizeValue)
: adjustContentBoxLogicalHeightForBoxSizing(maxSizeValue);
},
[&](const Style::MaximumSize::Percentage& percentageMaxSize) -> std::optional<LayoutUnit> {
auto maxSizeValue = Style::evaluate<LayoutUnit>(percentageMaxSize, containingBlockAvailableSize());
return isRowAxis
? adjustContentBoxLogicalWidthForBoxSizing(maxSizeValue)
: adjustContentBoxLogicalHeightForBoxSizing(maxSizeValue);
},
[&](const Style::MaximumSize::Calc& calcMaxSize) -> std::optional<LayoutUnit> {
auto maxSizeValue = Style::evaluate<LayoutUnit>(calcMaxSize, containingBlockAvailableSize(), style().usedZoomForLength());
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.resolveZoom(style().usedZoomForLength()) };
return isRowAxis
? adjustContentBoxLogicalWidthForBoxSizing(minSizeValue)
: adjustContentBoxLogicalHeightForBoxSizing(minSizeValue);
},
[&](const Style::MinimumSize::Percentage& percentageMinSize) -> std::optional<LayoutUnit> {
auto minSizeValue = Style::evaluate<LayoutUnit>(percentageMinSize, containingBlockAvailableSize());
return isRowAxis
? adjustContentBoxLogicalWidthForBoxSizing(minSizeValue)
: adjustContentBoxLogicalHeightForBoxSizing(minSizeValue);
},
[&](const Style::MinimumSize::Calc& calcMinSize) -> std::optional<LayoutUnit> {
auto minSizeValue = Style::evaluate<LayoutUnit>(calcMinSize, containingBlockAvailableSize(), style().usedZoomForLength());
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<LayoutUnit>(minTrackSizingFunction.length(), *availableSize, Style::ZoomNeeded { }), Style::evaluate<LayoutUnit>(maxTrackSizingFunction.length(), *availableSize, Style::ZoomNeeded { }));
return hasDefiniteMaxTrackSizingFunction ? Style::evaluate<LayoutUnit>(maxTrackSizingFunction.length(), *availableSize, Style::ZoomNeeded { }) : Style::evaluate<LayoutUnit>(minTrackSizingFunction.length(), *availableSize, Style::ZoomNeeded { });
};
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<LayoutUnit>(hasDefiniteMaxTrackBreadth ? track.maxTrackBreadth().length() : track.minTrackBreadth().length(), availableSize.value(), Style::ZoomNeeded { });
}
// 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 (auto trackIndex : std::views::iota(autoRepeatTracksRange.begin(), autoRepeatTracksRange.end()))
emptyTrackIndexes->add(trackIndex);
} else {
for (auto trackIndex : std::views::iota(autoRepeatTracksRange.begin(), autoRepeatTracksRange.end())) {
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);
if (style().display() != DisplayType::GridLanes && style().display() != DisplayType::InlineGridLanes)
return false;
return (direction == Style::GridTrackSizingDirection::Columns) == style().gridAutoFlow().isColumn();
}
// 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)) {
updateGridAreaWithEstimate(*gridItem, algorithm);
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)
updateGridAreaWithEstimate(*gridItem, algorithm);
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().gridAutoFlow().isDense();
// 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().gridAutoFlow().isDense();
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
{
return style().gridAutoFlow().isColumn() ? 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::updateGridAreaWithEstimate(RenderBox& gridItem, const GridTrackSizingAlgorithm& algorithm) const
{
auto logicalWidth = isMasonry(Style::GridTrackSizingDirection::Columns)
? contentBoxLogicalWidth()
: algorithm.estimatedGridAreaBreadthForGridItem(gridItem, Style::GridTrackSizingDirection::Columns);
auto logicalHeight = isMasonry(Style::GridTrackSizingDirection::Rows)
? availableLogicalHeightForContentBox()
: algorithm.estimatedGridAreaBreadthForGridItem(gridItem, Style::GridTrackSizingDirection::Rows);
updateGridAreaLogicalSize(gridItem, logicalWidth, logicalHeight);
}
void RenderGrid::updateGridAreaIncludingAlignment(RenderBox& gridItem) const
{
auto logicalWidth = isMasonry(Style::GridTrackSizingDirection::Columns)
? contentBoxLogicalWidth()
: gridAreaBreadthForGridItemIncludingAlignmentOffsets(gridItem, Style::GridTrackSizingDirection::Columns);
auto logicalHeight = isMasonry(Style::GridTrackSizingDirection::Rows)
? contentBoxLogicalHeight()
: gridAreaBreadthForGridItemIncludingAlignmentOffsets(gridItem, Style::GridTrackSizingDirection::Rows);
updateGridAreaLogicalSize(gridItem, logicalWidth, logicalHeight);
}
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) {
updateGridAreaIncludingAlignment(*autoGridItem);
// 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.
updateGridAreaIncludingAlignment(gridItem);
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.
GridLayoutFunctions::updateAutoMarginsIfNeeded(gridItem, writingMode());
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)
{
layoutGridItems(gridLayoutState);
}
void RenderGrid::prepareGridItemForPositionedLayout(RenderBox& gridItem)
{
ASSERT(gridItem.isOutOfFlowPositioned());
gridItem.containingBlock()->addOutOfFlowBox(gridItem);
CheckedPtr 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;
}
LayoutRange columnBreadth = gridAreaRangeForOutOfFlow(gridItem, Style::GridTrackSizingDirection::Columns);
LayoutRange rowBreadth = gridAreaRangeForOutOfFlow(gridItem, Style::GridTrackSizingDirection::Rows);
gridItem.setGridAreaContentLogicalWidth(columnBreadth.size());
gridItem.setGridAreaContentLogicalHeight(rowBreadth.size());
// 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
{
// 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;
}
bool RenderGrid::willStretchItem(const RenderBox& item, LogicalBoxAxis containingAxis, StretchingMode mode) const
{
auto canStretch = [&]() {
return containingAxis == LogicalBoxAxis::Block
? GridLayoutFunctions::hasStretchableSizeInColumnAxis(item, *this) && !GridLayoutFunctions::hasAutoMarginsInColumnAxis(item, writingMode())
: GridLayoutFunctions::hasStretchableSizeInRowAxis(item, *this) && !GridLayoutFunctions::hasAutoMarginsInRowAxis(item, writingMode());
};
// Beware: the order and short-circuiting here is necessary to prevent infinite loops.
return selfAlignmentForGridItem(item, containingAxis, mode).isStretch() && canStretch();
}
bool RenderGrid::aspectRatioPrefersInline(const RenderBox& gridItem, bool blockFlowIsColumnAxis)
{
if (!gridItem.style().hasAspectRatio())
return false;
LogicalBoxAxis containingAxis = blockFlowIsColumnAxis ? LogicalBoxAxis::Block : LogicalBoxAxis::Inline;
return !selfAlignmentForGridItem(gridItem, containingAxis, StretchingMode::Explicit).isStretch();
}
// 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);
bool blockFlowIsColumnAxis = gridItemBlockDirection == Style::GridTrackSizingDirection::Rows;
bool willStretchBlockSize = blockFlowIsColumnAxis
? willStretchItem(gridItem, LogicalBoxAxis::Block) : willStretchItem(gridItem, LogicalBoxAxis::Inline);
if (willStretchBlockSize && !aspectRatioPrefersInline(gridItem, blockFlowIsColumnAxis)) {
auto overridingContainingBlockContentSizeForGridItem = GridLayoutFunctions::overridingContainingBlockContentSizeForGridItem(gridItem, gridItemBlockDirection);
ASSERT(overridingContainingBlockContentSizeForGridItem && *overridingContainingBlockContentSizeForGridItem);
LayoutUnit stretchedLogicalHeight = GridLayoutFunctions::availableAlignmentSpaceForGridItemBeforeStretching(*this, 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 (!willStretchBlockSize && willStretchItem(gridItem, LogicalBoxAxis::Inline)) {
auto gridItemInlineDirection = Style::orthogonalDirection(gridItemBlockDirection);
auto overridingContainingBlockContentSizeForGridItem = GridLayoutFunctions::overridingContainingBlockContentSizeForGridItem(gridItem, gridItemInlineDirection);
ASSERT(overridingContainingBlockContentSizeForGridItem && *overridingContainingBlockContentSizeForGridItem);
LayoutUnit stretchedLogicalWidth = GridLayoutFunctions::availableAlignmentSpaceForGridItemBeforeStretching(*this, 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 = GridLayoutFunctions::availableAlignmentSpaceForGridItemBeforeStretching(*this, 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 = GridLayoutFunctions::availableAlignmentSpaceForGridItemBeforeStretching(*this, overridingContainingBlockContentSizeForGridItem->value(), gridItem, Style::GridTrackSizingDirection::Columns);
gridItem.setOverridingBorderBoxLogicalWidth(stretchedLogicalWidth);
}
}
bool RenderGrid::isChildEligibleForMarginTrim(Style::MarginTrimSide marginTrimSide, const RenderBox& gridItem) const
{
ASSERT(style().marginTrim().contains(marginTrimSide));
auto isTrimmingBlockDirection = marginTrimSide == Style::MarginTrimSide::BlockStart || marginTrimSide == Style::MarginTrimSide::BlockEnd;
auto itemGridSpan = isTrimmingBlockDirection ? currentGrid().gridItemSpanIgnoringCollapsedTracks(gridItem, Style::GridTrackSizingDirection::Rows) : currentGrid().gridItemSpanIgnoringCollapsedTracks(gridItem, Style::GridTrackSizingDirection::Columns);
switch (marginTrimSide) {
case Style::MarginTrimSide::BlockStart:
case Style::MarginTrimSide::InlineStart:
return !itemGridSpan.startLine();
case Style::MarginTrimSide::BlockEnd:
return itemGridSpan.endLine() == currentGrid().numTracks(Style::GridTrackSizingDirection::Rows);
case Style::MarginTrimSide::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(gridItem, logicalAxis(alignmentContextType)).position();
if (!isBaselinePosition(align))
return false;
return alignmentContextType == Style::GridTrackSizingDirection::Rows
? !GridLayoutFunctions::hasAutoMarginsInColumnAxis(gridItem, writingMode())
: !GridLayoutFunctions::hasAutoMarginsInRowAxis(gridItem, writingMode());
}
std::optional<LayoutUnit> RenderGrid::firstLineBaseline() const
{
if ((isWritingModeRoot() && !isFlexItem()) || !currentGrid().hasGridItems() || shouldApplyLayoutContainment())
return { };
// Finding the first grid item in grid order.
CheckedPtr baselineGridItem = this->baselineGridItem(ItemPosition::Baseline);
if (!baselineGridItem)
return { };
auto baseline = std::optional<LayoutUnit> { };
if (!GridLayoutFunctions::isOrthogonalGridItem(*this, *baselineGridItem))
baseline = baselineGridItem->firstLineBaseline();
if (!baseline) {
// We take border-box's bottom if no valid 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;
baseline = BaselineAlignmentState::synthesizedBaseline(*baselineGridItem, dominantBaseline, gridWritingMode, direction, BaselineSynthesisEdge::BorderBox);
}
return (settings().subpixelInlineLayoutEnabled() ? LayoutUnit(logicalTopForChild(*baselineGridItem)) : LayoutUnit(logicalTopForChild(*baselineGridItem).toInt())) + *baseline;
}
std::optional<LayoutUnit> RenderGrid::lastLineBaseline() const
{
if (isWritingModeRoot() || !currentGrid().hasGridItems() || shouldApplyLayoutContainment())
return { };
CheckedPtr baselineGridItem = this->baselineGridItem(ItemPosition::LastBaseline);
if (!baselineGridItem)
return { };
auto baseline = std::optional<LayoutUnit> { };
if (!GridLayoutFunctions::isOrthogonalGridItem(*this, *baselineGridItem))
baseline = baselineGridItem->lastLineBaseline();
if (!baseline) {
auto direction = isHorizontalWritingMode() ? LineDirection::Horizontal : LineDirection::Vertical;
auto gridWritingMode = style().writingMode();
auto dominantBaseline = BaselineAlignmentState::dominantBaseline(gridWritingMode);
baseline = BaselineAlignmentState::synthesizedBaseline(*baselineGridItem, dominantBaseline, gridWritingMode, direction, BaselineSynthesisEdge::BorderBox);
}
return (settings().subpixelInlineLayoutEnabled() ? LayoutUnit(logicalTopForChild(*baselineGridItem)) : LayoutUnit(logicalTopForChild(*baselineGridItem).toInt())) + *baseline;
}
const RenderBox* RenderGrid::baselineGridItem(ItemPosition alignment) const
{
ASSERT(alignment == ItemPosition::Baseline || alignment == ItemPosition::LastBaseline);
const RenderBox* baselineGridItem = { };
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(*gridItem, LogicalBoxAxis::Block).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 = selfAlignmentForGridItem(gridItem, LogicalBoxAxis::Block).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'.
case ItemPosition::Start:
// Aligns the alignment subject to be flush with the alignment container's 'start' edge (block-start) in the column axis.
return GridAxisPosition::GridAxisStart;
case ItemPosition::FlexEnd: // Only used in flex layout, otherwise equivalent to 'end'.
case ItemPosition::End:
// Aligns the alignment subject to be flush with the alignment container's 'end' edge (block-end) in the column axis.
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 (selfAlignmentForGridItem(gridItem, LogicalBoxAxis::Inline).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:
// FIXME: Handle non-inline matching orthogonal grid items properly.
if (GridLayoutFunctions::isOrthogonalGridItem(*this, gridItem) && !WritingMode().isInlineMatchingAny(gridItem.writingMode()))
return GridAxisPosition::GridAxisStart;
return hasSameDirection ? GridAxisPosition::GridAxisStart : GridAxisPosition::GridAxisEnd;
case ItemPosition::LastBaseline:
// FIXME: Handle non-inline matching orthogonal grid items properly.
if (GridLayoutFunctions::isOrthogonalGridItem(*this, gridItem) && !WritingMode().isInlineMatchingAny(gridItem.writingMode()))
return GridAxisPosition::GridAxisStart;
return hasSameDirection ? GridAxisPosition::GridAxisEnd : 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] = gridAreaPositionForInFlowGridItem(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 = selfAlignmentForGridItem(gridItem, LogicalBoxAxis::Block).overflow();
LayoutUnit offsetFromStartPosition = computeOverflowAlignmentOffset(overflow, endOfRow - startOfRow, columnAxisGridItemSize);
if (GridLayoutFunctions::hasAutoMarginsInColumnAxis(gridItem, writingMode()))
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] = gridAreaPositionForInFlowGridItem(gridItem, Style::GridTrackSizingDirection::Columns);
LayoutUnit startPosition = startOfColumn + marginStartForChild(gridItem);
LayoutUnit masonryOffset = areMasonryColumns() ? m_masonryLayout.offsetForGridItem(gridItem) : 0_lu;
if (GridLayoutFunctions::hasAutoMarginsInRowAxis(gridItem, writingMode()))
return startPosition;
LayoutUnit rowAxisGridItemSize = GridLayoutFunctions::isOrthogonalGridItem(*this, gridItem) ? gridItem.logicalHeight() + gridItem.marginLogicalHeight() : gridItem.logicalWidth() + gridItem.marginLogicalWidth();
auto overflow = selfAlignmentForGridItem(gridItem, LogicalBoxAxis::Inline).overflow();
auto rowAxisBaselineOffset = rowAxisBaselineOffsetForGridItem(gridItem);
LayoutUnit offsetFromStartPosition = computeOverflowAlignmentOffset(overflow, endOfColumn - startOfColumn, rowAxisGridItemSize);
GridAxisPosition axisPosition = rowAxisPositionForGridItem(gridItem);
switch (axisPosition) {
case GridAxisPosition::GridAxisStart:
return startPosition + rowAxisBaselineOffset + masonryOffset;
case GridAxisPosition::GridAxisEnd:
return startPosition + offsetFromStartPosition - rowAxisBaselineOffset;
case GridAxisPosition::GridAxisCenter:
return startPosition + 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;
}
LayoutRange RenderGrid::gridAreaRangeForOutOfFlow(const RenderBox& gridItem, Style::GridTrackSizingDirection direction) const
{
ASSERT(gridItem.isOutOfFlowPositioned());
bool isRowAxis = direction == Style::GridTrackSizingDirection::Columns;
auto borderEdge = [&]() {
if (isRowAxis) {
if (writingMode().isHorizontal() && (writingMode().isInlineLeftToRight() == shouldPlaceVerticalScrollbarOnLeft()))
return borderStart() + scrollbarLogicalWidth();
return borderStart();
}
return borderBefore();
}();
if (currentGrid().needsItemsPlacement()) {
// Haven't completed in-flow placement and grid sizing yet.
// Return something basic that doesn't access unbuilt data structures.
return LayoutRange(borderEdge, isRowAxis ? clientLogicalWidth() : clientLogicalHeight());
}
int startLine, endLine;
bool startIsAuto, endIsAuto;
if (!computeGridPositionsForOutOfFlowGridItem(gridItem, direction, startLine, startIsAuto, endLine, endIsAuto) || (startIsAuto && endIsAuto))
return LayoutRange(borderEdge, isRowAxis ? clientLogicalWidth() : clientLogicalHeight());
LayoutUnit start;
LayoutUnit end;
auto& positions = this->positions(direction);
if (positions.isEmpty()) {
ASSERT_WITH_SECURITY_IMPLICATION(!positions.isEmpty());
return LayoutRange(borderEdge, isRowAxis ? clientLogicalWidth() : clientLogicalHeight());
}
if (startIsAuto)
start = borderEdge;
else {
start = positions[startLine];
}
if (endIsAuto)
end = (isRowAxis ? clientLogicalWidth() : clientLogicalHeight()) + 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);
int lastLine = numTracks(direction);
if (endLine > 0 && endLine < lastLine) {
end -= guttersSize(direction, endLine - 1, 2, availableSizeForGutters);
end -= isRowAxis ? m_offsetBetweenColumns.distributionOffset : m_offsetBetweenRows.distributionOffset;
}
}
return LayoutRange(start, std::max(end - start, 0_lu));
}
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<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().justifyContent().resolve(contentAlignmentNormalBehaviorGrid())
: style().alignContent().resolve(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 { };
}
}
LayoutRect RenderGrid::contentOverflowRect() const
{
// FIXME: Handle subgrids and masonry.
if (!hasPotentiallyScrollableOverflow() || isMasonry() || isSubgridRows() || isSubgridColumns())
return flippedContentBoxRect();
// Get the grid rectangle.
LayoutRect contentArea;
if (writingMode().isInlineFlipped()) {
contentArea.shiftEdgesTo(
translateRTLCoordinate(m_columnPositions.last()),
m_rowPositions.first(),
translateRTLCoordinate(m_columnPositions.first()),
m_rowPositions.last());
} else {
contentArea.shiftEdgesTo(
m_columnPositions.first(),
m_rowPositions.first(),
m_columnPositions.last(),
m_rowPositions.last());
if (writingMode().isHorizontal() && shouldPlaceVerticalScrollbarOnLeft())
contentArea.move(verticalScrollbarWidth(), 0);
}
if (writingMode().isVertical())
return contentArea.transposedRect();
return contentArea;
}
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 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 (isHorizontalWritingMode() && shouldPlaceVerticalScrollbarOnLeft())
width += verticalScrollbarWidth();
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());
else if (writingMode().isHorizontal() && shouldPlaceVerticalScrollbarOnLeft())
rowAxisOffset += verticalScrollbarWidth();
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::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::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