diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/rendering/RenderGrid.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/rendering/RenderGrid.cpp')
-rw-r--r-- | Source/WebCore/rendering/RenderGrid.cpp | 2065 |
1 files changed, 1358 insertions, 707 deletions
diff --git a/Source/WebCore/rendering/RenderGrid.cpp b/Source/WebCore/rendering/RenderGrid.cpp index 8f2ff4241..4d89feaba 100644 --- a/Source/WebCore/rendering/RenderGrid.cpp +++ b/Source/WebCore/rendering/RenderGrid.cpp @@ -1,6 +1,6 @@ /* * Copyright (C) 2011 Apple Inc. All rights reserved. - * Copyright (C) 2013 Igalia S.L. 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 @@ -14,7 +14,7 @@ * 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 COMPUTER, INC. OR + * 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 @@ -27,148 +27,203 @@ #include "config.h" #include "RenderGrid.h" -#include "GridCoordinate.h" +#include "GridArea.h" +#include "GridPositionsResolver.h" +#include "GridTrackSizingAlgorithm.h" #include "LayoutRepainter.h" -#include "NotImplemented.h" #include "RenderLayer.h" #include "RenderView.h" +#include <cstdlib> namespace WebCore { -static const int infinity = -1; +static constexpr ItemPosition selfAlignmentNormalBehavior = ItemPositionStretch; -class GridTrack { -public: - GridTrack() - : m_usedBreadth(0) - , m_maxBreadth(0) - { - } +enum TrackSizeRestriction { + AllowInfinity, + ForbidInfinity, +}; - void growUsedBreadth(LayoutUnit growth) - { - ASSERT(growth >= 0); - m_usedBreadth += growth; - } - LayoutUnit usedBreadth() const { return m_usedBreadth; } - - void growMaxBreadth(LayoutUnit growth) - { - if (m_maxBreadth == infinity) - m_maxBreadth = m_usedBreadth + growth; - else - m_maxBreadth += growth; - } - LayoutUnit maxBreadthIfNotInfinite() const - { - return (m_maxBreadth == infinity) ? m_usedBreadth : m_maxBreadth; - } +struct ContentAlignmentData { + WTF_MAKE_FAST_ALLOCATED; +public: + bool isValid() { return positionOffset >= 0 && distributionOffset >= 0; } + static ContentAlignmentData defaultOffsets() { return {-1, -1}; } - LayoutUnit m_usedBreadth; - LayoutUnit m_maxBreadth; + LayoutUnit positionOffset; + LayoutUnit distributionOffset; }; -struct GridTrackForNormalization { - GridTrackForNormalization(const GridTrack& track, double flex) - : m_track(&track) - , m_flex(flex) - , m_normalizedFlexValue(track.m_usedBreadth / flex) - { - } +RenderGrid::RenderGrid(Element& element, RenderStyle&& style) + : RenderBlock(element, WTFMove(style), 0) + , m_grid(*this) + , m_trackSizingAlgorithm(this, m_grid) +{ + // All of our children must be block level. + setChildrenInline(false); +} - const GridTrack* m_track; - double m_flex; - LayoutUnit m_normalizedFlexValue; -}; +RenderGrid::~RenderGrid() +{ +} -class RenderGrid::GridIterator { - WTF_MAKE_NONCOPYABLE(GridIterator); -public: - // |direction| is the direction that is fixed to |fixedTrackIndex| so e.g - // GridIterator(m_grid, ForColumns, 1) will walk over the rows of the 2nd column. - GridIterator(const Vector<Vector<Vector<RenderBox*, 1>>>& grid, TrackSizingDirection direction, size_t fixedTrackIndex) - : m_grid(grid) - , m_direction(direction) - , m_rowIndex((direction == ForColumns) ? 0 : fixedTrackIndex) - , m_columnIndex((direction == ForColumns) ? fixedTrackIndex : 0) - , m_childIndex(0) - { - ASSERT(m_rowIndex < m_grid.size()); - ASSERT(m_columnIndex < m_grid[0].size()); - } +static inline bool defaultAlignmentIsStretch(ItemPosition position) +{ + return position == ItemPositionStretch || position == ItemPositionAuto; +} - RenderBox* nextGridItem() - { - if (!m_grid.size()) - return 0; +static inline bool defaultAlignmentChangedToStretchInRowAxis(const RenderStyle& oldStyle, const RenderStyle& newStyle) +{ + return !defaultAlignmentIsStretch(oldStyle.justifyItems().position()) && defaultAlignmentIsStretch(newStyle.justifyItems().position()); +} - size_t& varyingTrackIndex = (m_direction == ForColumns) ? m_rowIndex : m_columnIndex; - const size_t endOfVaryingTrackIndex = (m_direction == ForColumns) ? m_grid.size() : m_grid[0].size(); - for (; varyingTrackIndex < endOfVaryingTrackIndex; ++varyingTrackIndex) { - const Vector<RenderBox*>& children = m_grid[m_rowIndex][m_columnIndex]; - if (m_childIndex < children.size()) - return children[m_childIndex++]; +static inline bool defaultAlignmentChangedFromStretchInRowAxis(const RenderStyle& oldStyle, const RenderStyle& newStyle) +{ + return defaultAlignmentIsStretch(oldStyle.justifyItems().position()) && !defaultAlignmentIsStretch(newStyle.justifyItems().position()); +} - m_childIndex = 0; - } - return 0; - } +static inline bool defaultAlignmentChangedFromStretchInColumnAxis(const RenderStyle& oldStyle, const RenderStyle& newStyle) +{ + return defaultAlignmentIsStretch(oldStyle.alignItems().position()) && !defaultAlignmentIsStretch(newStyle.alignItems().position()); +} - PassOwnPtr<GridCoordinate> nextEmptyGridArea() - { - if (m_grid.isEmpty()) - return nullptr; - - size_t& varyingTrackIndex = (m_direction == ForColumns) ? m_rowIndex : m_columnIndex; - const size_t endOfVaryingTrackIndex = (m_direction == ForColumns) ? m_grid.size() : m_grid[0].size(); - for (; varyingTrackIndex < endOfVaryingTrackIndex; ++varyingTrackIndex) { - const Vector<RenderBox*>& children = m_grid[m_rowIndex][m_columnIndex]; - if (children.isEmpty()) { - OwnPtr<GridCoordinate> result = adoptPtr(new GridCoordinate(GridSpan(m_rowIndex, m_rowIndex), GridSpan(m_columnIndex, m_columnIndex))); - // Advance the iterator to avoid an infinite loop where we would return the same grid area over and over. - ++varyingTrackIndex; - return result.release(); +static inline bool selfAlignmentChangedToStretchInRowAxis(const RenderStyle& oldStyle, const RenderStyle& newStyle, const RenderStyle& childStyle) +{ + return childStyle.resolvedJustifySelf(oldStyle, selfAlignmentNormalBehavior).position() != ItemPositionStretch + && childStyle.resolvedJustifySelf(newStyle, selfAlignmentNormalBehavior).position() == ItemPositionStretch; +} + +static inline bool selfAlignmentChangedFromStretchInRowAxis(const RenderStyle& oldStyle, const RenderStyle& newStyle, const RenderStyle& childStyle) +{ + return childStyle.resolvedJustifySelf(oldStyle, selfAlignmentNormalBehavior).position() == ItemPositionStretch + && childStyle.resolvedJustifySelf(newStyle, selfAlignmentNormalBehavior).position() != ItemPositionStretch; +} + +static inline bool selfAlignmentChangedFromStretchInColumnAxis(const RenderStyle& oldStyle, const RenderStyle& newStyle, const RenderStyle& childStyle) +{ + return childStyle.resolvedAlignSelf(oldStyle, selfAlignmentNormalBehavior).position() == ItemPositionStretch + && childStyle.resolvedAlignSelf(newStyle, selfAlignmentNormalBehavior).position() != ItemPositionStretch; +} + +void RenderGrid::addChild(RenderObject* newChild, RenderObject* beforeChild) +{ + RenderBlock::addChild(newChild, beforeChild); + + // Positioned grid items do not take up space or otherwise participate in the layout of the grid, + // for that reason we don't need to mark the grid as dirty when they are added. + if (newChild->isOutOfFlowPositioned()) + return; + + // The grid needs to be recomputed as it might contain auto-placed items that + // will change their position. + dirtyGrid(); +} + +void RenderGrid::removeChild(RenderObject& child) +{ + RenderBlock::removeChild(child); + + // Positioned grid items do not take up space or otherwise participate in the layout of the grid, + // for that reason we don't need to mark the grid as dirty when they are removed. + if (child.isOutOfFlowPositioned()) + return; + + // The grid needs to be recomputed as it might contain auto-placed items that + // will change their position. + dirtyGrid(); +} + +void RenderGrid::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderBlock::styleDidChange(diff, oldStyle); + if (!oldStyle || diff != StyleDifferenceLayout) + return; + + const RenderStyle& newStyle = style(); + if (defaultAlignmentChangedToStretchInRowAxis(*oldStyle, newStyle) || defaultAlignmentChangedFromStretchInRowAxis(*oldStyle, newStyle) + || defaultAlignmentChangedFromStretchInColumnAxis(*oldStyle, newStyle)) { + // Grid items that were not previously stretched in row-axis need to be relayed out so we can compute new available space. + // Grid items that were previously stretching in column-axis need to be relayed out so we can compute new available space. + // This is only necessary for stretching since other alignment values don't change the size of the box. + for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { + if (child->isOutOfFlowPositioned()) + continue; + if (selfAlignmentChangedToStretchInRowAxis(*oldStyle, newStyle, child->style()) || selfAlignmentChangedFromStretchInRowAxis(*oldStyle, newStyle, child->style()) + || selfAlignmentChangedFromStretchInColumnAxis(*oldStyle, newStyle, child->style())) { + child->setChildNeedsLayout(MarkOnlyThis); } } - return nullptr; } -private: - const Vector<Vector<Vector<RenderBox*, 1>>>& m_grid; - TrackSizingDirection m_direction; - size_t m_rowIndex; - size_t m_columnIndex; - size_t m_childIndex; -}; + if (explicitGridDidResize(*oldStyle) || namedGridLinesDefinitionDidChange(*oldStyle) || oldStyle->gridAutoFlow() != style().gridAutoFlow() + || (style().gridAutoRepeatColumns().size() || style().gridAutoRepeatRows().size())) + dirtyGrid(); +} -class RenderGrid::GridSizingData { - WTF_MAKE_NONCOPYABLE(GridSizingData); -public: - GridSizingData(size_t gridColumnCount, size_t gridRowCount) - : columnTracks(gridColumnCount) - , rowTracks(gridRowCount) - { - } +bool RenderGrid::explicitGridDidResize(const RenderStyle& oldStyle) const +{ + return oldStyle.gridColumns().size() != style().gridColumns().size() + || oldStyle.gridRows().size() != style().gridRows().size() + || oldStyle.namedGridAreaColumnCount() != style().namedGridAreaColumnCount() + || oldStyle.namedGridAreaRowCount() != style().namedGridAreaRowCount() + || oldStyle.gridAutoRepeatColumns().size() != style().gridAutoRepeatColumns().size() + || oldStyle.gridAutoRepeatRows().size() != style().gridAutoRepeatRows().size(); +} - Vector<GridTrack> columnTracks; - Vector<GridTrack> rowTracks; - Vector<size_t> contentSizedTracksIndex; +bool RenderGrid::namedGridLinesDefinitionDidChange(const RenderStyle& oldStyle) const +{ + return oldStyle.namedGridRowLines() != style().namedGridRowLines() + || oldStyle.namedGridColumnLines() != style().namedGridColumnLines(); +} - // Performance optimization: hold onto these Vectors until the end of Layout to avoid repeated malloc / free. - Vector<LayoutUnit> distributeTrackVector; - Vector<GridTrack*> filteredTracks; -}; +LayoutUnit RenderGrid::computeTrackBasedLogicalHeight() const +{ + LayoutUnit logicalHeight; -RenderGrid::RenderGrid(Element& element, PassRef<RenderStyle> style) - : RenderBlock(element, std::move(style), 0) - , m_orderIterator(*this) + auto& allRows = m_trackSizingAlgorithm.tracks(ForRows); + for (const auto& row : allRows) + logicalHeight += row.baseSize(); + + logicalHeight += guttersSize(m_grid, ForRows, 0, allRows.size()); + + return logicalHeight; +} + +void RenderGrid::computeTrackSizesForDefiniteSize(GridTrackSizingDirection direction, LayoutUnit availableSpace) { - // All of our children must be block level. - setChildrenInline(false); + LayoutUnit totalGuttersSize = guttersSize(m_grid, direction, 0, m_grid.numTracks(direction)); + LayoutUnit freeSpace = availableSpace - totalGuttersSize; + + m_trackSizingAlgorithm.setup(direction, numTracks(direction, m_grid), TrackSizing, availableSpace, freeSpace); + m_trackSizingAlgorithm.run(); + + ASSERT(m_trackSizingAlgorithm.tracksAreWiderThanMinTrackBreadth()); } -RenderGrid::~RenderGrid() +void RenderGrid::repeatTracksSizingIfNeeded(LayoutUnit availableSpaceForColumns, LayoutUnit availableSpaceForRows) { + // 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 child's min-content contribution has changed or not. + if (m_grid.hasAnyOrthogonalGridItem()) { + computeTrackSizesForDefiniteSize(ForColumns, availableSpaceForColumns); + computeTrackSizesForDefiniteSize(ForRows, availableSpaceForRows); + } +} + +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 (m_grid.needsItemsPlacement() && posChildNeedsLayout()) + return false; + + return RenderBlock::canPerformSimplifiedLayout(); } void RenderGrid::layoutBlock(bool relayoutChildren, LayoutUnit) @@ -178,29 +233,88 @@ void RenderGrid::layoutBlock(bool relayoutChildren, LayoutUnit) if (!relayoutChildren && simplifiedLayout()) return; - // FIXME: Much of this method is boiler plate that matches RenderBox::layoutBlock and Render*FlexibleBox::layoutBlock. - // It would be nice to refactor some of the duplicate code. LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); LayoutStateMaintainer statePusher(view(), *this, locationOffset(), hasTransform() || hasReflection() || style().isFlippedBlocksWritingMode()); - prepareShapesAndPaginationBeforeBlockLayout(relayoutChildren); + preparePaginationBeforeBlockLayout(relayoutChildren); LayoutSize previousSize = size(); + // We need to clear both own and containingBlock override sizes of orthogonal items to ensure we get the + // same result when grid's intrinsic size is computed again in the updateLogicalWidth call bellow. + if (sizesLogicalWidthToFitContent(MaxSize) || style().logicalWidth().isIntrinsicOrAuto()) { + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) { + if (child->isOutOfFlowPositioned() || !isOrthogonalChild(*child)) + continue; + child->clearOverrideSize(); + child->clearContainingBlockOverrideSize(); + child->setNeedsLayout(); + child->layoutIfNeeded(); + } + } + setLogicalHeight(0); updateLogicalWidth(); - layoutGridItems(); + placeItemsOnGrid(m_grid, TrackSizing); + + // 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). + LayoutUnit availableSpaceForColumns = availableLogicalWidth(); + computeTrackSizesForDefiniteSize(ForColumns, availableSpaceForColumns); + + // FIXME: We should use RenderBlock::hasDefiniteLogicalHeight() but it does not work for positioned stuff. + // FIXME: Consider caching the hasDefiniteLogicalHeight value throughout the layout. + bool hasDefiniteLogicalHeight = hasOverrideLogicalContentHeight() || computeContentLogicalHeight(MainOrPreferredSize, style().logicalHeight(), std::nullopt); + if (!hasDefiniteLogicalHeight) { + m_minContentHeight = LayoutUnit(); + m_maxContentHeight = LayoutUnit(); + computeTrackSizesForIndefiniteSize(m_trackSizingAlgorithm, ForRows, m_grid, *m_minContentHeight, *m_maxContentHeight); + // FIXME: This should be really added to the intrinsic height in RenderBox::computeContentAndScrollbarLogicalHeightUsing(). + // Remove this when that is fixed. + ASSERT(m_minContentHeight); + ASSERT(m_maxContentHeight); + LayoutUnit scrollbarHeight = scrollbarLogicalHeight(); + *m_minContentHeight += scrollbarHeight; + *m_maxContentHeight += scrollbarHeight; + } else + computeTrackSizesForDefiniteSize(ForRows, availableLogicalHeight(ExcludeMarginBorderPadding)); + LayoutUnit trackBasedLogicalHeight = computeTrackBasedLogicalHeight() + borderAndPaddingLogicalHeight() + scrollbarLogicalHeight(); + setLogicalHeight(trackBasedLogicalHeight); LayoutUnit oldClientAfterEdge = clientLogicalBottom(); updateLogicalHeight(); + // Once grid's indefinite height is resolved, we can compute the + // available free space for Content Alignment. + if (!hasDefiniteLogicalHeight) + m_trackSizingAlgorithm.setFreeSpace(ForRows, logicalHeight() - trackBasedLogicalHeight); + + // 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, contentLogicalHeight()); + + // 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(true, isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes) + + scrollbarLogicalHeight(); + setLogicalHeight(std::max(logicalHeight(), minHeightForEmptyLine)); + } + + applyStretchAlignmentToTracksIfNeeded(ForColumns); + applyStretchAlignmentToTracksIfNeeded(ForRows); + + layoutGridItems(); + m_trackSizingAlgorithm.reset(); + if (size() != previousSize) relayoutChildren = true; - layoutPositionedObjects(relayoutChildren || isRoot()); - - updateShapesAfterBlockLayout(); + layoutPositionedObjects(relayoutChildren || isDocumentElementRenderer()); computeOverflow(oldClientAfterEdge); statePusher.pop(); @@ -216,551 +330,580 @@ void RenderGrid::layoutBlock(bool relayoutChildren, LayoutUnit) clearNeedsLayout(); } -void RenderGrid::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const +LayoutUnit RenderGrid::gridGapForDirection(GridTrackSizingDirection direction) const { - const_cast<RenderGrid*>(this)->placeItemsOnGrid(); - - GridSizingData sizingData(gridColumnCount(), gridRowCount()); - LayoutUnit availableLogicalSpace = 0; - const_cast<RenderGrid*>(this)->computedUsedBreadthOfGridTracks(ForColumns, sizingData, availableLogicalSpace); - - for (size_t i = 0; i < sizingData.columnTracks.size(); ++i) { - LayoutUnit minTrackBreadth = sizingData.columnTracks[i].m_usedBreadth; - LayoutUnit maxTrackBreadth = sizingData.columnTracks[i].m_maxBreadth; - maxTrackBreadth = std::max(maxTrackBreadth, minTrackBreadth); + return valueForLength(direction == ForColumns ? style().gridColumnGap() : style().gridRowGap(), LayoutUnit()); +} - minLogicalWidth += minTrackBreadth; - maxLogicalWidth += maxTrackBreadth; +LayoutUnit RenderGrid::guttersSize(const Grid& grid, GridTrackSizingDirection direction, unsigned startLine, unsigned span) const +{ + if (span <= 1) + return { }; - // FIXME: This should add in the scrollbarWidth (e.g. see RenderFlexibleBox). - } + LayoutUnit gap = gridGapForDirection(direction); - const_cast<RenderGrid*>(this)->clearGrid(); -} + // Fast path, no collapsing tracks. + if (!grid.hasAutoRepeatEmptyTracks(direction)) + return gap * (span - 1); -void RenderGrid::computePreferredLogicalWidths() -{ - ASSERT(preferredLogicalWidthsDirty()); + // 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. - m_minPreferredLogicalWidth = 0; - m_maxPreferredLogicalWidth = 0; + LayoutUnit gapAccumulator; + unsigned endLine = startLine + span; - // FIXME: We don't take our own logical width into account. Once we do, we need to make sure - // we apply (and test the interaction with) min-width / max-width. + for (unsigned line = startLine; line < endLine - 1; ++line) { + if (!grid.isEmptyAutoRepeatTrack(direction, line)) + gapAccumulator += gap; + } - computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth); + // The above loop adds one extra gap for trailing collapsed tracks. + if (gapAccumulator && grid.isEmptyAutoRepeatTrack(direction, endLine - 1)) { + ASSERT(gapAccumulator >= gap); + gapAccumulator -= gap; + } - LayoutUnit borderAndPaddingInInlineDirection = borderAndPaddingLogicalWidth(); - m_minPreferredLogicalWidth += borderAndPaddingInInlineDirection; - m_maxPreferredLogicalWidth += borderAndPaddingInInlineDirection; + // 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. + if (startLine && grid.isEmptyAutoRepeatTrack(direction, startLine)) { + unsigned nonEmptyTracksBeforeStartLine = startLine; + auto begin = grid.autoRepeatEmptyTracks(direction)->begin(); + for (auto it = begin; *it != startLine; ++it) { + ASSERT(nonEmptyTracksBeforeStartLine); + --nonEmptyTracksBeforeStartLine; + } + if (nonEmptyTracksBeforeStartLine) + gapAccumulator += gap; + } - setPreferredLogicalWidthsDirty(false); -} + // 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 (grid.isEmptyAutoRepeatTrack(direction, endLine - 1)) { + unsigned nonEmptyTracksAfterEndLine = grid.numTracks(direction) - endLine; + auto currentEmptyTrack = grid.autoRepeatEmptyTracks(direction)->find(endLine - 1); + auto endEmptyTrack = grid.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) + gapAccumulator += gap; + } -void RenderGrid::computedUsedBreadthOfGridTracks(TrackSizingDirection direction, GridSizingData& sizingData) -{ - LayoutUnit availableLogicalSpace = (direction == ForColumns) ? availableLogicalWidth() : availableLogicalHeight(IncludeMarginBorderPadding); - computedUsedBreadthOfGridTracks(direction, sizingData, availableLogicalSpace); + return gapAccumulator; } -void RenderGrid::computedUsedBreadthOfGridTracks(TrackSizingDirection direction, GridSizingData& sizingData, LayoutUnit& availableLogicalSpace) +void RenderGrid::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const { - Vector<GridTrack>& tracks = (direction == ForColumns) ? sizingData.columnTracks : sizingData.rowTracks; - sizingData.contentSizedTracksIndex.shrink(0); - for (size_t i = 0; i < tracks.size(); ++i) { - GridTrack& track = tracks[i]; - const GridTrackSize& trackSize = gridTrackSize(direction, i); - const GridLength& minTrackBreadth = trackSize.minTrackBreadth(); - const GridLength& maxTrackBreadth = trackSize.maxTrackBreadth(); + Grid grid(const_cast<RenderGrid&>(*this)); + placeItemsOnGrid(grid, IntrinsicSizeComputation); - track.m_usedBreadth = computeUsedBreadthOfMinLength(direction, minTrackBreadth); - track.m_maxBreadth = computeUsedBreadthOfMaxLength(direction, maxTrackBreadth, track.m_usedBreadth); + GridTrackSizingAlgorithm algorithm(this, grid); + computeTrackSizesForIndefiniteSize(algorithm, ForColumns, grid, minLogicalWidth, maxLogicalWidth); - track.m_maxBreadth = std::max(track.m_maxBreadth, track.m_usedBreadth); - - if (trackSize.isContentSized()) - sizingData.contentSizedTracksIndex.append(i); - } + LayoutUnit scrollbarWidth = intrinsicScrollbarLogicalWidth(); + minLogicalWidth += scrollbarWidth; + maxLogicalWidth += scrollbarWidth; +} - if (!sizingData.contentSizedTracksIndex.isEmpty()) - resolveContentBasedTrackSizingFunctions(direction, sizingData); +void RenderGrid::computeTrackSizesForIndefiniteSize(GridTrackSizingAlgorithm& algorithm, GridTrackSizingDirection direction, Grid& grid, LayoutUnit& minIntrinsicSize, LayoutUnit& maxIntrinsicSize) const +{ + algorithm.setup(direction, numTracks(direction, grid), IntrinsicSizeComputation, std::nullopt, std::nullopt); + algorithm.run(); - for (size_t i = 0; i < tracks.size(); ++i) { - ASSERT(tracks[i].m_maxBreadth != infinity); - availableLogicalSpace -= tracks[i].m_usedBreadth; - } + size_t numberOfTracks = algorithm.tracks(direction).size(); + LayoutUnit totalGuttersSize = guttersSize(grid, direction, 0, numberOfTracks); - if (availableLogicalSpace <= 0) - return; + minIntrinsicSize = algorithm.minContentSize() + totalGuttersSize; + maxIntrinsicSize = algorithm.maxContentSize() + totalGuttersSize; - const size_t tracksSize = tracks.size(); - Vector<GridTrack*> tracksForDistribution(tracksSize); - for (size_t i = 0; i < tracksSize; ++i) - tracksForDistribution[i] = tracks.data() + i; + ASSERT(algorithm.tracksAreWiderThanMinTrackBreadth()); +} - distributeSpaceToTracks(tracksForDistribution, 0, &GridTrack::usedBreadth, &GridTrack::growUsedBreadth, sizingData, availableLogicalSpace); +std::optional<LayoutUnit> RenderGrid::computeIntrinsicLogicalContentHeightUsing(Length logicalHeightLength, std::optional<LayoutUnit> intrinsicLogicalHeight, LayoutUnit borderAndPadding) const +{ + if (!intrinsicLogicalHeight) + return std::nullopt; - // 4. Grow all Grid tracks having a fraction as the MaxTrackSizingFunction. + if (logicalHeightLength.isMinContent()) + return m_minContentHeight; - // FIXME: Handle the case where RemainingSpace is not defined. - double normalizedFractionBreadth = computeNormalizedFractionBreadth(tracks, direction, availableLogicalSpace); - for (size_t i = 0; i < tracksSize; ++i) { - const GridTrackSize& trackSize = gridTrackSize(direction, i); - if (!trackSize.maxTrackBreadth().isFlex()) - continue; + if (logicalHeightLength.isMaxContent()) + return m_maxContentHeight; - tracks[i].m_usedBreadth = std::max<LayoutUnit>(tracks[i].m_usedBreadth, normalizedFractionBreadth * trackSize.maxTrackBreadth().flex()); + if (logicalHeightLength.isFitContent()) { + LayoutUnit fillAvailableExtent = containingBlock()->availableLogicalHeight(ExcludeMarginBorderPadding); + return std::min(m_maxContentHeight.value_or(0), std::max(m_minContentHeight.value_or(0), fillAvailableExtent)); } + + if (logicalHeightLength.isFillAvailable()) + return containingBlock()->availableLogicalHeight(ExcludeMarginBorderPadding) - borderAndPadding; + ASSERT_NOT_REACHED(); + return std::nullopt; } -LayoutUnit RenderGrid::computeUsedBreadthOfMinLength(TrackSizingDirection direction, const GridLength& gridLength) const +static std::optional<LayoutUnit> overrideContainingBlockContentSizeForChild(const RenderBox& child, GridTrackSizingDirection direction) { - if (gridLength.isFlex()) - return 0; - - const Length& trackLength = gridLength.length(); - ASSERT(!trackLength.isAuto()); - if (trackLength.isFixed() || trackLength.isPercent() || trackLength.isViewportPercentage()) - return computeUsedBreadthOfSpecifiedLength(direction, trackLength); - - ASSERT(trackLength.isMinContent() || trackLength.isMaxContent()); - return 0; + return direction == ForColumns ? child.overrideContainingBlockContentLogicalWidth() : child.overrideContainingBlockContentLogicalHeight(); } -LayoutUnit RenderGrid::computeUsedBreadthOfMaxLength(TrackSizingDirection direction, const GridLength& gridLength, LayoutUnit usedBreadth) const +bool RenderGrid::isOrthogonalChild(const RenderBox& child) const { - if (gridLength.isFlex()) - return usedBreadth; - - const Length& trackLength = gridLength.length(); - ASSERT(!trackLength.isAuto()); - if (trackLength.isFixed() || trackLength.isPercent() || trackLength.isViewportPercentage()) { - LayoutUnit computedBreadth = computeUsedBreadthOfSpecifiedLength(direction, trackLength); - ASSERT(computedBreadth != infinity); - return computedBreadth; - } - - ASSERT(trackLength.isMinContent() || trackLength.isMaxContent()); - return infinity; + return child.isHorizontalWritingMode() != isHorizontalWritingMode(); } -LayoutUnit RenderGrid::computeUsedBreadthOfSpecifiedLength(TrackSizingDirection direction, const Length& trackLength) const +GridTrackSizingDirection RenderGrid::flowAwareDirectionForChild(const RenderBox& child, GridTrackSizingDirection direction) const { - // FIXME: We still need to support calc() here (https://webkit.org/b/103761). - ASSERT(trackLength.isFixed() || trackLength.isPercent() || trackLength.isViewportPercentage()); - return valueForLength(trackLength, direction == ForColumns ? logicalWidth() : computeContentLogicalHeight(style().logicalHeight())); + return !isOrthogonalChild(child) ? direction : (direction == ForColumns ? ForRows : ForColumns); } -double RenderGrid::computeNormalizedFractionBreadth(Vector<GridTrack>& tracks, TrackSizingDirection direction, LayoutUnit availableLogicalSpace) const +unsigned RenderGrid::computeAutoRepeatTracksCount(GridTrackSizingDirection direction, SizingOperation sizingOperation) const { - // |availableLogicalSpace| already accounts for the used breadths so no need to remove it here. + bool isRowAxis = direction == ForColumns; + const auto& autoRepeatTracks = isRowAxis ? style().gridAutoRepeatColumns() : style().gridAutoRepeatRows(); + unsigned autoRepeatTrackListLength = autoRepeatTracks.size(); - Vector<GridTrackForNormalization> tracksForNormalization; - for (size_t i = 0; i < tracks.size(); ++i) { - const GridTrackSize& trackSize = gridTrackSize(direction, i); - if (!trackSize.maxTrackBreadth().isFlex()) - continue; + if (!autoRepeatTrackListLength) + return 0; - tracksForNormalization.append(GridTrackForNormalization(tracks[i], trackSize.maxTrackBreadth().flex())); + std::optional<LayoutUnit> availableSize; + if (isRowAxis) { + if (sizingOperation != IntrinsicSizeComputation) + availableSize = availableLogicalWidth(); + } else { + availableSize = computeContentLogicalHeight(MainOrPreferredSize, style().logicalHeight(), std::nullopt); + if (!availableSize) { + const Length& maxLength = style().logicalMaxHeight(); + if (!maxLength.isUndefined()) + availableSize = computeContentLogicalHeight(MaxSize, maxLength, std::nullopt); + } + if (availableSize) + availableSize = constrainContentBoxLogicalHeightByMinMax(availableSize.value(), std::nullopt); } - // FIXME: Ideally we shouldn't come here without any <flex> grid track. - if (tracksForNormalization.isEmpty()) - return LayoutUnit(); - - std::sort(tracksForNormalization.begin(), tracksForNormalization.end(), - [](const GridTrackForNormalization& track1, const GridTrackForNormalization& track2) { - return track1.m_normalizedFlexValue < track2.m_normalizedFlexValue; - }); - - // These values work together: as we walk over our grid tracks, we increase fractionValueBasedOnGridItemsRatio - // to match a grid track's usedBreadth to <flex> ratio until the total fractions sized grid tracks wouldn't - // fit into availableLogicalSpaceIgnoringFractionTracks. - double accumulatedFractions = 0; - LayoutUnit fractionValueBasedOnGridItemsRatio = 0; - LayoutUnit availableLogicalSpaceIgnoringFractionTracks = availableLogicalSpace; - - for (size_t i = 0; i < tracksForNormalization.size(); ++i) { - const GridTrackForNormalization& track = tracksForNormalization[i]; - if (track.m_normalizedFlexValue > fractionValueBasedOnGridItemsRatio) { - // If the normalized flex value (we ordered |tracksForNormalization| by increasing normalized flex value) - // will make us overflow our container, then stop. We have the previous step's ratio is the best fit. - if (track.m_normalizedFlexValue * accumulatedFractions > availableLogicalSpaceIgnoringFractionTracks) - break; + bool needsToFulfillMinimumSize = false; + if (!availableSize) { + const Length& minSize = isRowAxis ? style().logicalMinWidth() : style().logicalMinHeight(); + if (!minSize.isSpecified()) + return autoRepeatTrackListLength; - fractionValueBasedOnGridItemsRatio = track.m_normalizedFlexValue; - } + LayoutUnit containingBlockAvailableSize = isRowAxis ? containingBlockLogicalWidthForContent() : containingBlockLogicalHeightForContent(ExcludeMarginBorderPadding); + availableSize = valueForLength(minSize, containingBlockAvailableSize); + needsToFulfillMinimumSize = true; + } - accumulatedFractions += track.m_flex; - // This item was processed so we re-add its used breadth to the available space to accurately count the remaining space. - availableLogicalSpaceIgnoringFractionTracks += track.m_track->m_usedBreadth; + LayoutUnit autoRepeatTracksSize; + for (auto& autoTrackSize : autoRepeatTracks) { + ASSERT(autoTrackSize.minTrackBreadth().isLength()); + ASSERT(!autoTrackSize.minTrackBreadth().isFlex()); + bool hasDefiniteMaxTrackSizingFunction = autoTrackSize.maxTrackBreadth().isLength() && !autoTrackSize.maxTrackBreadth().isContentSized(); + auto trackLength = hasDefiniteMaxTrackSizingFunction ? autoTrackSize.maxTrackBreadth().length() : autoTrackSize.minTrackBreadth().length(); + autoRepeatTracksSize += valueForLength(trackLength, availableSize.value()); + } + // 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>(LayoutUnit(1), 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 = isRowAxis ? style().gridColumns() : style().gridRows(); + + for (const auto& track : trackSizes) { + bool hasDefiniteMaxTrackBreadth = track.maxTrackBreadth().isLength() && !track.maxTrackBreadth().isContentSized(); + ASSERT(hasDefiniteMaxTrackBreadth || (track.minTrackBreadth().isLength() && !track.minTrackBreadth().isContentSized())); + tracksSize += valueForLength(hasDefiniteMaxTrackBreadth ? track.maxTrackBreadth().length() : track.minTrackBreadth().length(), availableSize.value()); } - return availableLogicalSpaceIgnoringFractionTracks / accumulatedFractions; -} + // Add gutters as if there where only 1 auto repeat track. Gaps between auto repeat tracks will be added later when + // computing the repetitions. + LayoutUnit gapSize = gridGapForDirection(direction); + tracksSize += gapSize * trackSizes.size(); -const GridTrackSize& RenderGrid::gridTrackSize(TrackSizingDirection direction, size_t i) const -{ - const Vector<GridTrackSize>& trackStyles = (direction == ForColumns) ? style().gridColumns() : style().gridRows(); - if (i >= trackStyles.size()) - return (direction == ForColumns) ? style().gridAutoColumns() : style().gridAutoRows(); + LayoutUnit freeSpace = availableSize.value() - tracksSize; + if (freeSpace <= 0) + return autoRepeatTrackListLength; - return trackStyles[i]; -} + unsigned repetitions = 1 + (freeSpace / (autoRepeatTracksSize + gapSize)).toInt(); -size_t RenderGrid::explicitGridColumnCount() const -{ - return style().gridColumns().size(); -} + // 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) + ++repetitions; -size_t RenderGrid::explicitGridRowCount() const -{ - return style().gridRows().size(); + return repetitions * autoRepeatTrackListLength; } -size_t RenderGrid::explicitGridSizeForSide(GridPositionSide side) const -{ - return (side == ColumnStartSide || side == ColumnEndSide) ? explicitGridColumnCount() : explicitGridRowCount(); -} -LayoutUnit RenderGrid::logicalContentHeightForChild(RenderBox* child, Vector<GridTrack>& columnTracks) +std::unique_ptr<OrderedTrackIndexSet> RenderGrid::computeEmptyTracksForAutoRepeat(Grid& grid, GridTrackSizingDirection direction) const { - if (child->style().logicalHeight().isPercent()) - child->setNeedsLayout(MarkOnlyThis); + bool isRowAxis = direction == ForColumns; + if ((isRowAxis && style().gridAutoRepeatColumnsType() != AutoFit) + || (!isRowAxis && style().gridAutoRepeatRowsType() != AutoFit)) + return nullptr; + + std::unique_ptr<OrderedTrackIndexSet> emptyTrackIndexes; + unsigned insertionPoint = isRowAxis ? style().gridAutoRepeatColumnsInsertionPoint() : style().gridAutoRepeatRowsInsertionPoint(); + unsigned firstAutoRepeatTrack = insertionPoint + std::abs(grid.smallestTrackStart(direction)); + unsigned lastAutoRepeatTrack = firstAutoRepeatTrack + grid.autoRepeatTracks(direction); - child->setOverrideContainingBlockContentLogicalWidth(gridAreaBreadthForChild(child, ForColumns, columnTracks)); - // If |child| has a percentage logical height, we shouldn't let it override its intrinsic height, which is - // what we are interested in here. Thus we need to set the override logical height to -1 (no possible resolution). - child->setOverrideContainingBlockContentLogicalHeight(-1); - child->layoutIfNeeded(); - return child->logicalHeight(); + if (!grid.hasGridItems()) { + emptyTrackIndexes = std::make_unique<OrderedTrackIndexSet>(); + for (unsigned trackIndex = firstAutoRepeatTrack; trackIndex < lastAutoRepeatTrack; ++trackIndex) + emptyTrackIndexes->add(trackIndex); + } else { + for (unsigned trackIndex = firstAutoRepeatTrack; trackIndex < lastAutoRepeatTrack; ++trackIndex) { + GridIterator iterator(grid, direction, trackIndex); + if (!iterator.nextGridItem()) { + if (!emptyTrackIndexes) + emptyTrackIndexes = std::make_unique<OrderedTrackIndexSet>(); + emptyTrackIndexes->add(trackIndex); + } + } + } + return emptyTrackIndexes; } -LayoutUnit RenderGrid::minContentForChild(RenderBox* child, TrackSizingDirection direction, Vector<GridTrack>& columnTracks) +void RenderGrid::placeItemsOnGrid(Grid& grid, SizingOperation sizingOperation) const { - bool hasOrthogonalWritingMode = child->isHorizontalWritingMode() != isHorizontalWritingMode(); - // FIXME: Properly support orthogonal writing mode. - if (hasOrthogonalWritingMode) - return 0; - - if (direction == ForColumns) { - // FIXME: It's unclear if we should return the intrinsic width or the preferred width. - // See http://lists.w3.org/Archives/Public/www-style/2013Jan/0245.html - return child->minPreferredLogicalWidth() + marginIntrinsicLogicalWidthForChild(*child); + unsigned autoRepeatColumns = computeAutoRepeatTracksCount(ForColumns, sizingOperation); + unsigned autoRepeatRows = computeAutoRepeatTracksCount(ForRows, sizingOperation); + if (autoRepeatColumns != grid.autoRepeatTracks(ForColumns) || autoRepeatRows != grid.autoRepeatTracks(ForRows)) { + grid.setNeedsItemsPlacement(true); + grid.setAutoRepeatTracks(autoRepeatRows, autoRepeatColumns); } - return logicalContentHeightForChild(child, columnTracks); -} + if (!grid.needsItemsPlacement()) + return; -LayoutUnit RenderGrid::maxContentForChild(RenderBox* child, TrackSizingDirection direction, Vector<GridTrack>& columnTracks) -{ - bool hasOrthogonalWritingMode = child->isHorizontalWritingMode() != isHorizontalWritingMode(); - // FIXME: Properly support orthogonal writing mode. - if (hasOrthogonalWritingMode) - return LayoutUnit(); + ASSERT(!grid.hasGridItems()); + populateExplicitGridAndOrderIterator(grid); - if (direction == ForColumns) { - // FIXME: It's unclear if we should return the intrinsic width or the preferred width. - // See http://lists.w3.org/Archives/Public/www-style/2013Jan/0245.html - return child->maxPreferredLogicalWidth() + marginIntrinsicLogicalWidthForChild(*child); - } + Vector<RenderBox*> autoMajorAxisAutoGridItems; + Vector<RenderBox*> specifiedMajorAxisAutoGridItems; + bool hasAnyOrthogonalGridItem = false; + for (auto* child = grid.orderIterator().first(); child; child = grid.orderIterator().next()) { + if (child->isOutOfFlowPositioned()) + continue; - return logicalContentHeightForChild(child, columnTracks); -} + hasAnyOrthogonalGridItem = hasAnyOrthogonalGridItem || isOrthogonalChild(*child); -void RenderGrid::resolveContentBasedTrackSizingFunctions(TrackSizingDirection direction, GridSizingData& sizingData) -{ - // FIXME: Split the grid tracks into groups that doesn't overlap a <flex> grid track. + GridArea area = grid.gridItemArea(*child); + if (!area.rows.isIndefinite()) + area.rows.translate(std::abs(grid.smallestTrackStart(ForRows))); + if (!area.columns.isIndefinite()) + area.columns.translate(std::abs(grid.smallestTrackStart(ForColumns))); - for (size_t i = 0; i < sizingData.contentSizedTracksIndex.size(); ++i) { - GridIterator iterator(m_grid, direction, sizingData.contentSizedTracksIndex[i]); - while (RenderBox* gridItem = iterator.nextGridItem()) { - resolveContentBasedTrackSizingFunctionsForItems(direction, sizingData, gridItem, &GridTrackSize::hasMinOrMaxContentMinTrackBreadth, &RenderGrid::minContentForChild, &GridTrack::usedBreadth, &GridTrack::growUsedBreadth); - resolveContentBasedTrackSizingFunctionsForItems(direction, sizingData, gridItem, &GridTrackSize::hasMaxContentMinTrackBreadth, &RenderGrid::maxContentForChild, &GridTrack::usedBreadth, &GridTrack::growUsedBreadth); - resolveContentBasedTrackSizingFunctionsForItems(direction, sizingData, gridItem, &GridTrackSize::hasMinOrMaxContentMaxTrackBreadth, &RenderGrid::minContentForChild, &GridTrack::maxBreadthIfNotInfinite, &GridTrack::growMaxBreadth); - resolveContentBasedTrackSizingFunctionsForItems(direction, sizingData, gridItem, &GridTrackSize::hasMaxContentMaxTrackBreadth, &RenderGrid::maxContentForChild, &GridTrack::maxBreadthIfNotInfinite, &GridTrack::growMaxBreadth); + if (area.rows.isIndefinite() || area.columns.isIndefinite()) { + grid.setGridItemArea(*child, area); + bool majorAxisDirectionIsForColumns = autoPlacementMajorAxisDirection() == ForColumns; + if ((majorAxisDirectionIsForColumns && area.columns.isIndefinite()) + || (!majorAxisDirectionIsForColumns && area.rows.isIndefinite())) + autoMajorAxisAutoGridItems.append(child); + else + specifiedMajorAxisAutoGridItems.append(child); + continue; } + grid.insert(*child, { area.rows, area.columns }); + } + grid.setHasAnyOrthogonalGridItem(hasAnyOrthogonalGridItem); - GridTrack& track = (direction == ForColumns) ? sizingData.columnTracks[i] : sizingData.rowTracks[i]; - if (track.m_maxBreadth == infinity) - track.m_maxBreadth = track.m_usedBreadth; +#if ENABLE(ASSERT) + if (grid.hasGridItems()) { + ASSERT(grid.numTracks(ForRows) >= GridPositionsResolver::explicitGridRowCount(style(), grid.autoRepeatTracks(ForRows))); + ASSERT(grid.numTracks(ForColumns) >= GridPositionsResolver::explicitGridColumnCount(style(), grid.autoRepeatTracks(ForColumns))); } -} +#endif -void RenderGrid::resolveContentBasedTrackSizingFunctionsForItems(TrackSizingDirection direction, GridSizingData& sizingData, RenderBox* gridItem, FilterFunction filterFunction, SizingFunction sizingFunction, AccumulatorGetter trackGetter, AccumulatorGrowFunction trackGrowthFunction) -{ - const GridCoordinate coordinate = cachedGridCoordinate(gridItem); - const size_t initialTrackIndex = (direction == ForColumns) ? coordinate.columns.initialPositionIndex : coordinate.rows.initialPositionIndex; - const size_t finalTrackIndex = (direction == ForColumns) ? coordinate.columns.finalPositionIndex : coordinate.rows.finalPositionIndex; + placeSpecifiedMajorAxisItemsOnGrid(grid, specifiedMajorAxisAutoGridItems); + placeAutoMajorAxisItemsOnGrid(grid, autoMajorAxisAutoGridItems); - sizingData.filteredTracks.shrink(0); - for (size_t trackIndex = initialTrackIndex; trackIndex <= finalTrackIndex; ++trackIndex) { - const GridTrackSize& trackSize = gridTrackSize(direction, trackIndex); - if (!(trackSize.*filterFunction)()) - continue; + // Compute collapsible tracks for auto-fit. + grid.setAutoRepeatEmptyColumns(computeEmptyTracksForAutoRepeat(grid, ForColumns)); + grid.setAutoRepeatEmptyRows(computeEmptyTracksForAutoRepeat(grid, ForRows)); - GridTrack& track = (direction == ForColumns) ? sizingData.columnTracks[trackIndex] : sizingData.rowTracks[trackIndex]; - sizingData.filteredTracks.append(&track); - } + grid.setNeedsItemsPlacement(false); - if (sizingData.filteredTracks.isEmpty()) - return; +#if ENABLE(ASSERT) + for (auto* child = grid.orderIterator().first(); child; child = grid.orderIterator().next()) { + if (child->isOutOfFlowPositioned()) + continue; - LayoutUnit additionalBreadthSpace = (this->*sizingFunction)(gridItem, direction, sizingData.columnTracks); - for (size_t trackIndexForSpace = initialTrackIndex; trackIndexForSpace <= finalTrackIndex; ++trackIndexForSpace) { - GridTrack& track = (direction == ForColumns) ? sizingData.columnTracks[trackIndexForSpace] : sizingData.rowTracks[trackIndexForSpace]; - additionalBreadthSpace -= (track.*trackGetter)(); + GridArea area = grid.gridItemArea(*child); + ASSERT(area.rows.isTranslatedDefinite() && area.columns.isTranslatedDefinite()); } - - // FIXME: We should pass different values for |tracksForGrowthAboveMaxBreadth|. - distributeSpaceToTracks(sizingData.filteredTracks, &sizingData.filteredTracks, trackGetter, trackGrowthFunction, sizingData, additionalBreadthSpace); +#endif } -static bool sortByGridTrackGrowthPotential(const GridTrack* track1, const GridTrack* track2) +void RenderGrid::populateExplicitGridAndOrderIterator(Grid& grid) const { - return (track1->m_maxBreadth - track1->m_usedBreadth) < (track2->m_maxBreadth - track2->m_usedBreadth); -} + OrderIteratorPopulator populator(grid.orderIterator()); + int smallestRowStart = 0; + int smallestColumnStart = 0; + unsigned autoRepeatRows = grid.autoRepeatTracks(ForRows); + unsigned autoRepeatColumns = grid.autoRepeatTracks(ForColumns); + unsigned maximumRowIndex = GridPositionsResolver::explicitGridRowCount(style(), autoRepeatRows); + unsigned maximumColumnIndex = GridPositionsResolver::explicitGridColumnCount(style(), autoRepeatColumns); -void RenderGrid::distributeSpaceToTracks(Vector<GridTrack*>& tracks, Vector<GridTrack*>* tracksForGrowthAboveMaxBreadth, AccumulatorGetter trackGetter, AccumulatorGrowFunction trackGrowthFunction, GridSizingData& sizingData, LayoutUnit& availableLogicalSpace) -{ - std::sort(tracks.begin(), tracks.end(), sortByGridTrackGrowthPotential); + for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { + if (child->isOutOfFlowPositioned()) + continue; - size_t tracksSize = tracks.size(); - sizingData.distributeTrackVector.resize(tracksSize); + populator.collectChild(*child); - for (size_t i = 0; i < tracksSize; ++i) { - GridTrack& track = *tracks[i]; - LayoutUnit availableLogicalSpaceShare = availableLogicalSpace / (tracksSize - i); - LayoutUnit trackBreadth = (tracks[i]->*trackGetter)(); - LayoutUnit growthShare = std::max(LayoutUnit(), std::min(availableLogicalSpaceShare, track.m_maxBreadth - trackBreadth)); - // We should never shrink any grid track or else we can't guarantee we abide by our min-sizing function. - sizingData.distributeTrackVector[i] = trackBreadth + growthShare; - availableLogicalSpace -= growthShare; - } + GridSpan rowPositions = GridPositionsResolver::resolveGridPositionsFromStyle(style(), *child, ForRows, autoRepeatRows); + if (!rowPositions.isIndefinite()) { + smallestRowStart = std::min(smallestRowStart, 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 = GridPositionsResolver::spanSizeForAutoPlacedItem(style(), *child, ForRows); + maximumRowIndex = std::max(maximumRowIndex, spanSize); + } - if (availableLogicalSpace > 0 && tracksForGrowthAboveMaxBreadth) { - tracksSize = tracksForGrowthAboveMaxBreadth->size(); - for (size_t i = 0; i < tracksSize; ++i) { - LayoutUnit growthShare = availableLogicalSpace / (tracksSize - i); - sizingData.distributeTrackVector[i] += growthShare; - availableLogicalSpace -= growthShare; + GridSpan columnPositions = GridPositionsResolver::resolveGridPositionsFromStyle(style(), *child, ForColumns, autoRepeatColumns); + if (!columnPositions.isIndefinite()) { + smallestColumnStart = std::min(smallestColumnStart, 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 = GridPositionsResolver::spanSizeForAutoPlacedItem(style(), *child, ForColumns); + maximumColumnIndex = std::max(maximumColumnIndex, spanSize); } - } - for (size_t i = 0; i < tracksSize; ++i) { - LayoutUnit growth = sizingData.distributeTrackVector[i] - (tracks[i]->*trackGetter)(); - if (growth >= 0) - (tracks[i]->*trackGrowthFunction)(growth); + grid.setGridItemArea(*child, { rowPositions, columnPositions }); } -} -#ifndef NDEBUG -bool RenderGrid::tracksAreWiderThanMinTrackBreadth(TrackSizingDirection direction, const Vector<GridTrack>& tracks) -{ - for (size_t i = 0; i < tracks.size(); ++i) { - const GridTrackSize& trackSize = gridTrackSize(direction, i); - const GridLength& minTrackBreadth = trackSize.minTrackBreadth(); - if (computeUsedBreadthOfMinLength(direction, minTrackBreadth) > tracks[i].m_usedBreadth) - return false; - } - return true; + grid.setSmallestTracksStart(smallestRowStart, smallestColumnStart); + grid.ensureGridSize(maximumRowIndex + std::abs(smallestRowStart), maximumColumnIndex + std::abs(smallestColumnStart)); } -#endif -void RenderGrid::growGrid(TrackSizingDirection direction) +std::unique_ptr<GridArea> RenderGrid::createEmptyGridAreaAtSpecifiedPositionsOutsideGrid(Grid& grid, const RenderBox& gridItem, GridTrackSizingDirection specifiedDirection, const GridSpan& specifiedPositions) const { - if (direction == ForColumns) { - const size_t oldColumnSize = m_grid[0].size(); - for (size_t row = 0; row < m_grid.size(); ++row) - m_grid[row].grow(oldColumnSize + 1); - } else { - const size_t oldRowSize = m_grid.size(); - m_grid.grow(oldRowSize + 1); - m_grid[oldRowSize].grow(m_grid[0].size()); - } + GridTrackSizingDirection crossDirection = specifiedDirection == ForColumns ? ForRows : ForColumns; + const unsigned endOfCrossDirection = grid.numTracks(crossDirection); + unsigned crossDirectionSpanSize = GridPositionsResolver::spanSizeForAutoPlacedItem(style(), gridItem, crossDirection); + GridSpan crossDirectionPositions = GridSpan::translatedDefiniteGridSpan(endOfCrossDirection, endOfCrossDirection + crossDirectionSpanSize); + return std::make_unique<GridArea>(specifiedDirection == ForColumns ? crossDirectionPositions : specifiedPositions, specifiedDirection == ForColumns ? specifiedPositions : crossDirectionPositions); } -void RenderGrid::insertItemIntoGrid(RenderBox* child, const GridCoordinate& coordinate) +void RenderGrid::placeSpecifiedMajorAxisItemsOnGrid(Grid& grid, const Vector<RenderBox*>& autoGridItems) const { - for (size_t row = coordinate.rows.initialPositionIndex; row <= coordinate.rows.finalPositionIndex; ++row) { - for (size_t column = coordinate.columns.initialPositionIndex; column <= coordinate.columns.finalPositionIndex; ++column) - m_grid[row][column].append(child); + bool isForColumns = autoPlacementMajorAxisDirection() == ForColumns; + bool isGridAutoFlowDense = style().isGridAutoFlowAlgorithmDense(); + + // Mapping between the major axis tracks (rows or columns) and the last auto-placed item's position inserted on + // that track. This is needed to implement "sparse" packing for items locked to a given track. + // See http://dev.w3.org/csswg/css-grid/#auto-placement-algorithm + HashMap<unsigned, unsigned, DefaultHash<unsigned>::Hash, WTF::UnsignedWithZeroKeyHashTraits<unsigned>> minorAxisCursors; + + for (auto& autoGridItem : autoGridItems) { + GridSpan majorAxisPositions = grid.gridItemSpan(*autoGridItem, autoPlacementMajorAxisDirection()); + ASSERT(majorAxisPositions.isTranslatedDefinite()); + ASSERT(grid.gridItemSpan(*autoGridItem, autoPlacementMinorAxisDirection()).isIndefinite()); + unsigned minorAxisSpanSize = GridPositionsResolver::spanSizeForAutoPlacedItem(style(), *autoGridItem, autoPlacementMinorAxisDirection()); + unsigned majorAxisInitialPosition = majorAxisPositions.startLine(); + + GridIterator iterator(grid, autoPlacementMajorAxisDirection(), majorAxisPositions.startLine(), isGridAutoFlowDense ? 0 : minorAxisCursors.get(majorAxisInitialPosition)); + std::unique_ptr<GridArea> emptyGridArea = iterator.nextEmptyGridArea(majorAxisPositions.integerSpan(), minorAxisSpanSize); + if (!emptyGridArea) + emptyGridArea = createEmptyGridAreaAtSpecifiedPositionsOutsideGrid(grid, *autoGridItem, autoPlacementMajorAxisDirection(), majorAxisPositions); + + grid.insert(*autoGridItem, *emptyGridArea); + + if (!isGridAutoFlowDense) + minorAxisCursors.set(majorAxisInitialPosition, isForColumns ? emptyGridArea->rows.startLine() : emptyGridArea->columns.startLine()); } - m_gridItemCoordinate.set(child, coordinate); } -void RenderGrid::insertItemIntoGrid(RenderBox* child, size_t rowTrack, size_t columnTrack) +void RenderGrid::placeAutoMajorAxisItemsOnGrid(Grid& grid, const Vector<RenderBox*>& autoGridItems) const { - const GridSpan& rowSpan = resolveGridPositionsFromAutoPlacementPosition(child, ForRows, rowTrack); - const GridSpan& columnSpan = resolveGridPositionsFromAutoPlacementPosition(child, ForColumns, columnTrack); - insertItemIntoGrid(child, GridCoordinate(rowSpan, columnSpan)); + AutoPlacementCursor autoPlacementCursor = {0, 0}; + bool isGridAutoFlowDense = style().isGridAutoFlowAlgorithmDense(); + + for (auto& autoGridItem : autoGridItems) { + placeAutoMajorAxisItemOnGrid(grid, *autoGridItem, autoPlacementCursor); + + if (isGridAutoFlowDense) { + autoPlacementCursor.first = 0; + autoPlacementCursor.second = 0; + } + } } -void RenderGrid::placeItemsOnGrid() +void RenderGrid::placeAutoMajorAxisItemOnGrid(Grid& grid, RenderBox& gridItem, AutoPlacementCursor& autoPlacementCursor) const { - ASSERT(!gridWasPopulated()); - ASSERT(m_gridItemCoordinate.isEmpty()); + ASSERT(grid.gridItemSpan(gridItem, autoPlacementMajorAxisDirection()).isIndefinite()); + unsigned majorAxisSpanSize = GridPositionsResolver::spanSizeForAutoPlacedItem(style(), gridItem, autoPlacementMajorAxisDirection()); + + const unsigned endOfMajorAxis = grid.numTracks(autoPlacementMajorAxisDirection()); + unsigned majorAxisAutoPlacementCursor = autoPlacementMajorAxisDirection() == ForColumns ? autoPlacementCursor.second : autoPlacementCursor.first; + unsigned minorAxisAutoPlacementCursor = autoPlacementMajorAxisDirection() == ForColumns ? autoPlacementCursor.first : autoPlacementCursor.second; + + std::unique_ptr<GridArea> emptyGridArea; + GridSpan minorAxisPositions = grid.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(grid, autoPlacementMinorAxisDirection(), minorAxisPositions.startLine(), majorAxisAutoPlacementCursor); + emptyGridArea = iterator.nextEmptyGridArea(minorAxisPositions.integerSpan(), majorAxisSpanSize); + } - populateExplicitGridAndOrderIterator(); + if (!emptyGridArea) + emptyGridArea = createEmptyGridAreaAtSpecifiedPositionsOutsideGrid(grid, gridItem, autoPlacementMinorAxisDirection(), minorAxisPositions); + } else { + unsigned minorAxisSpanSize = GridPositionsResolver::spanSizeForAutoPlacedItem(style(), gridItem, autoPlacementMinorAxisDirection()); + + for (unsigned majorAxisIndex = majorAxisAutoPlacementCursor; majorAxisIndex < endOfMajorAxis; ++majorAxisIndex) { + GridIterator iterator(grid, 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() == ForColumns ? emptyGridArea->columns.endLine() : emptyGridArea->rows.endLine(); + const unsigned endOfMinorAxis = grid.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 = nullptr; + } - Vector<RenderBox*> autoMajorAxisAutoGridItems; - Vector<RenderBox*> specifiedMajorAxisAutoGridItems; - GridAutoFlow autoFlow = style().gridAutoFlow(); - for (RenderBox* child = m_orderIterator.first(); child; child = m_orderIterator.next()) { - // FIXME: We never re-resolve positions if the grid is grown during auto-placement which may lead auto / <integer> - // positions to not match the author's intent. The specification is unclear on what should be done in this case. - OwnPtr<GridSpan> rowPositions = resolveGridPositionsFromStyle(child, ForRows); - OwnPtr<GridSpan> columnPositions = resolveGridPositionsFromStyle(child, ForColumns); - if (!rowPositions || !columnPositions) { - GridSpan* majorAxisPositions = (autoPlacementMajorAxisDirection() == ForColumns) ? columnPositions.get() : rowPositions.get(); - if (!majorAxisPositions) - autoMajorAxisAutoGridItems.append(child); - else - specifiedMajorAxisAutoGridItems.append(child); - continue; + // 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; } - insertItemIntoGrid(child, GridCoordinate(*rowPositions, *columnPositions)); - } - - ASSERT(gridRowCount() >= style().gridRows().size()); - ASSERT(gridColumnCount() >= style().gridColumns().size()); - if (autoFlow == AutoFlowNone) { - // If we did collect some grid items, they won't be placed thus never laid out. - ASSERT(!autoMajorAxisAutoGridItems.size()); - ASSERT(!specifiedMajorAxisAutoGridItems.size()); - return; + if (!emptyGridArea) + emptyGridArea = createEmptyGridAreaAtSpecifiedPositionsOutsideGrid(grid, gridItem, autoPlacementMinorAxisDirection(), GridSpan::translatedDefiniteGridSpan(0, minorAxisSpanSize)); } - placeSpecifiedMajorAxisItemsOnGrid(specifiedMajorAxisAutoGridItems); - placeAutoMajorAxisItemsOnGrid(autoMajorAxisAutoGridItems); + grid.insert(gridItem, *emptyGridArea); + autoPlacementCursor.first = emptyGridArea->rows.startLine(); + autoPlacementCursor.second = emptyGridArea->columns.startLine(); } -void RenderGrid::populateExplicitGridAndOrderIterator() +GridTrackSizingDirection RenderGrid::autoPlacementMajorAxisDirection() const { - // FIXME: We should find a way to share OrderValues's initialization code with RenderFlexibleBox. - OrderIterator::OrderValues orderValues; - size_t maximumRowIndex = std::max<size_t>(1, explicitGridRowCount()); - size_t maximumColumnIndex = std::max<size_t>(1, explicitGridColumnCount()); - - for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { - // Avoid growing the vector for the common-case default value of 0. This optimizes the most common case which is - // one or a few values with the default order 0 - int order = child->style().order(); - if (orderValues.isEmpty() || orderValues.last() != order) - orderValues.append(order); - - // This function bypasses the cache (cachedGridCoordinate()) as it is used to build it. - OwnPtr<GridSpan> rowPositions = resolveGridPositionsFromStyle(child, ForRows); - OwnPtr<GridSpan> columnPositions = resolveGridPositionsFromStyle(child, ForColumns); - - // |positions| is 0 if we need to run the auto-placement algorithm. Our estimation ignores - // this case as the auto-placement algorithm will grow the grid as needed. - if (rowPositions) - maximumRowIndex = std::max(maximumRowIndex, rowPositions->finalPositionIndex + 1); - if (columnPositions) - maximumColumnIndex = std::max(maximumColumnIndex, columnPositions->finalPositionIndex + 1); - } - - m_grid.grow(maximumRowIndex); - for (size_t i = 0; i < m_grid.size(); ++i) - m_grid[i].grow(maximumColumnIndex); - - m_orderIterator.setOrderValues(std::move(orderValues)); + return style().isGridAutoFlowDirectionColumn() ? ForColumns : ForRows; } -void RenderGrid::placeSpecifiedMajorAxisItemsOnGrid(Vector<RenderBox*> autoGridItems) +GridTrackSizingDirection RenderGrid::autoPlacementMinorAxisDirection() const { - for (size_t i = 0; i < autoGridItems.size(); ++i) { - OwnPtr<GridSpan> majorAxisPositions = resolveGridPositionsFromStyle(autoGridItems[i], autoPlacementMajorAxisDirection()); - GridIterator iterator(m_grid, autoPlacementMajorAxisDirection(), majorAxisPositions->initialPositionIndex); - if (OwnPtr<GridCoordinate> emptyGridArea = iterator.nextEmptyGridArea()) { - insertItemIntoGrid(autoGridItems[i], emptyGridArea->rows.initialPositionIndex, emptyGridArea->columns.initialPositionIndex); - continue; - } - - growGrid(autoPlacementMinorAxisDirection()); - OwnPtr<GridCoordinate> emptyGridArea = iterator.nextEmptyGridArea(); - ASSERT(emptyGridArea); - insertItemIntoGrid(autoGridItems[i], emptyGridArea->rows.initialPositionIndex, emptyGridArea->columns.initialPositionIndex); - } + return style().isGridAutoFlowDirectionColumn() ? ForRows : ForColumns; } -void RenderGrid::placeAutoMajorAxisItemsOnGrid(Vector<RenderBox*> autoGridItems) +void RenderGrid::dirtyGrid() { - for (size_t i = 0; i < autoGridItems.size(); ++i) - placeAutoMajorAxisItemOnGrid(autoGridItems[i]); + if (m_grid.needsItemsPlacement()) + return; + + m_grid.setNeedsItemsPlacement(true); } -void RenderGrid::placeAutoMajorAxisItemOnGrid(RenderBox* gridItem) +Vector<LayoutUnit> RenderGrid::trackSizesForComputedStyle(GridTrackSizingDirection direction) const { - OwnPtr<GridSpan> minorAxisPositions = resolveGridPositionsFromStyle(gridItem, autoPlacementMinorAxisDirection()); - ASSERT(!resolveGridPositionsFromStyle(gridItem, autoPlacementMajorAxisDirection())); - size_t minorAxisIndex = 0; - if (minorAxisPositions) { - minorAxisIndex = minorAxisPositions->initialPositionIndex; - GridIterator iterator(m_grid, autoPlacementMinorAxisDirection(), minorAxisIndex); - if (OwnPtr<GridCoordinate> emptyGridArea = iterator.nextEmptyGridArea()) { - insertItemIntoGrid(gridItem, emptyGridArea->rows.initialPositionIndex, emptyGridArea->columns.initialPositionIndex); - return; - } - } else { - const size_t endOfMajorAxis = (autoPlacementMajorAxisDirection() == ForColumns) ? gridColumnCount() : gridRowCount(); - for (size_t majorAxisIndex = 0; majorAxisIndex < endOfMajorAxis; ++majorAxisIndex) { - GridIterator iterator(m_grid, autoPlacementMajorAxisDirection(), majorAxisIndex); - if (OwnPtr<GridCoordinate> emptyGridArea = iterator.nextEmptyGridArea()) { - insertItemIntoGrid(gridItem, emptyGridArea->rows.initialPositionIndex, emptyGridArea->columns.initialPositionIndex); - return; - } + bool isRowAxis = direction == ForColumns; + auto& positions = isRowAxis ? m_columnPositions : m_rowPositions; + size_t numPositions = positions.size(); + LayoutUnit offsetBetweenTracks = isRowAxis ? m_offsetBetweenColumns : m_offsetBetweenRows; + + Vector<LayoutUnit> tracks; + if (numPositions < 2) + return tracks; + + ASSERT(!m_grid.needsItemsPlacement()); + bool hasCollapsedTracks = m_grid.hasAutoRepeatEmptyTracks(direction); + LayoutUnit gap = !hasCollapsedTracks ? gridGapForDirection(direction) : LayoutUnit(); + tracks.reserveCapacity(numPositions - 1); + for (size_t i = 0; i < numPositions - 2; ++i) + tracks.append(positions[i + 1] - positions[i] - offsetBetweenTracks - gap); + tracks.append(positions[numPositions - 1] - positions[numPositions - 2]); + + if (!hasCollapsedTracks) + return tracks; + + size_t remainingEmptyTracks = m_grid.autoRepeatEmptyTracks(direction)->size(); + size_t lastLine = tracks.size(); + gap = gridGapForDirection(direction); + for (size_t i = 1; i < lastLine; ++i) { + if (m_grid.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 || !m_grid.isEmptyAutoRepeatTrack(direction, i)) + tracks[i - 1] -= gap; } } - // We didn't find an empty grid area so we need to create an extra major axis line and insert our gridItem in it. - const size_t columnIndex = (autoPlacementMajorAxisDirection() == ForColumns) ? m_grid[0].size() : minorAxisIndex; - const size_t rowIndex = (autoPlacementMajorAxisDirection() == ForColumns) ? minorAxisIndex : m_grid.size(); - growGrid(autoPlacementMajorAxisDirection()); - insertItemIntoGrid(gridItem, rowIndex, columnIndex); + return tracks; } -RenderGrid::TrackSizingDirection RenderGrid::autoPlacementMajorAxisDirection() const +static const StyleContentAlignmentData& contentAlignmentNormalBehaviorGrid() { - GridAutoFlow flow = style().gridAutoFlow(); - ASSERT(flow != AutoFlowNone); - return (flow == AutoFlowColumn) ? ForColumns : ForRows; + static const StyleContentAlignmentData normalBehavior = {ContentPositionNormal, ContentDistributionStretch}; + return normalBehavior; } -RenderGrid::TrackSizingDirection RenderGrid::autoPlacementMinorAxisDirection() const +void RenderGrid::applyStretchAlignmentToTracksIfNeeded(GridTrackSizingDirection direction) { - GridAutoFlow flow = style().gridAutoFlow(); - ASSERT(flow != AutoFlowNone); - return (flow == AutoFlowColumn) ? ForRows : ForColumns; -} + std::optional<LayoutUnit> freeSpace = m_trackSizingAlgorithm.freeSpace(direction); + if (!freeSpace + || freeSpace.value() <= 0 + || (direction == ForColumns && style().resolvedJustifyContentDistribution(contentAlignmentNormalBehaviorGrid()) != ContentDistributionStretch) + || (direction == ForRows && style().resolvedAlignContentDistribution(contentAlignmentNormalBehaviorGrid()) != ContentDistributionStretch)) + return; -void RenderGrid::clearGrid() -{ - m_grid.clear(); - m_gridItemCoordinate.clear(); + // Spec defines auto-sized tracks as the ones with an 'auto' max-sizing function. + Vector<GridTrack>& allTracks = m_trackSizingAlgorithm.tracks(direction); + Vector<unsigned> autoSizedTracksIndex; + for (unsigned i = 0; i < allTracks.size(); ++i) { + const GridTrackSize& trackSize = m_trackSizingAlgorithm.gridTrackSize(direction, i, TrackSizing); + if (trackSize.hasAutoMaxTrackBreadth()) + autoSizedTracksIndex.append(i); + } + + unsigned numberOfAutoSizedTracks = autoSizedTracksIndex.size(); + if (numberOfAutoSizedTracks < 1) + return; + + LayoutUnit sizeToIncrease = freeSpace.value() / numberOfAutoSizedTracks; + for (const auto& trackIndex : autoSizedTracksIndex) { + auto& track = allTracks[trackIndex]; + track.setBaseSize(track.baseSize() + sizeToIncrease); + } + m_trackSizingAlgorithm.setFreeSpace(direction, LayoutUnit()); } void RenderGrid::layoutGridItems() { - placeItemsOnGrid(); - - GridSizingData sizingData(gridColumnCount(), gridRowCount()); - computedUsedBreadthOfGridTracks(ForColumns, sizingData); - ASSERT(tracksAreWiderThanMinTrackBreadth(ForColumns, sizingData.columnTracks)); - computedUsedBreadthOfGridTracks(ForRows, sizingData); - ASSERT(tracksAreWiderThanMinTrackBreadth(ForRows, sizingData.rowTracks)); + populateGridPositionsForDirection(ForColumns); + populateGridPositionsForDirection(ForRows); for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { + if (child->isOutOfFlowPositioned()) { + prepareChildForPositionedLayout(*child); + continue; + } + // Because the grid area cannot be styled, we don't need to adjust // the grid breadth to account for 'box-sizing'. - LayoutUnit oldOverrideContainingBlockContentLogicalWidth = child->hasOverrideContainingBlockLogicalWidth() ? child->overrideContainingBlockContentLogicalWidth() : LayoutUnit(); - LayoutUnit oldOverrideContainingBlockContentLogicalHeight = child->hasOverrideContainingBlockLogicalHeight() ? child->overrideContainingBlockContentLogicalHeight() : LayoutUnit(); - - LayoutUnit overrideContainingBlockContentLogicalWidth = gridAreaBreadthForChild(child, ForColumns, sizingData.columnTracks); - LayoutUnit overrideContainingBlockContentLogicalHeight = gridAreaBreadthForChild(child, ForRows, sizingData.rowTracks); - if (oldOverrideContainingBlockContentLogicalWidth != overrideContainingBlockContentLogicalWidth || (oldOverrideContainingBlockContentLogicalHeight != overrideContainingBlockContentLogicalHeight && (child->hasRelativeLogicalHeight() || child->hasViewportPercentageLogicalHeight()))) + std::optional<LayoutUnit> oldOverrideContainingBlockContentLogicalWidth = child->hasOverrideContainingBlockLogicalWidth() ? child->overrideContainingBlockContentLogicalWidth() : LayoutUnit(); + std::optional<LayoutUnit> oldOverrideContainingBlockContentLogicalHeight = child->hasOverrideContainingBlockLogicalHeight() ? child->overrideContainingBlockContentLogicalHeight() : LayoutUnit(); + + LayoutUnit overrideContainingBlockContentLogicalWidth = gridAreaBreadthForChildIncludingAlignmentOffsets(*child, ForColumns); + LayoutUnit overrideContainingBlockContentLogicalHeight = gridAreaBreadthForChildIncludingAlignmentOffsets(*child, ForRows); + if (!oldOverrideContainingBlockContentLogicalWidth || oldOverrideContainingBlockContentLogicalWidth.value() != overrideContainingBlockContentLogicalWidth + || ((!oldOverrideContainingBlockContentLogicalHeight || oldOverrideContainingBlockContentLogicalHeight.value() != overrideContainingBlockContentLogicalHeight) + && child->hasRelativeLogicalHeight())) child->setNeedsLayout(MarkOnlyThis); child->setOverrideContainingBlockContentLogicalWidth(overrideContainingBlockContentLogicalWidth); @@ -768,12 +911,18 @@ void RenderGrid::layoutGridItems() LayoutRect oldChildRect = child->frameRect(); - // FIXME: Grid items should stretch to fill their cells. Once we - // implement grid-{column,row}-align, we can also shrink to fit. For - // now, just size as if we were a regular child. + // Stretching logic might force a child layout, so we need to run it before the layoutIfNeeded + // call to avoid unnecessary relayouts. This might imply that child margins, needed to correctly + // determine the available space before stretching, are not set yet. + applyStretchAlignmentToChildIfNeeded(*child); + child->layoutIfNeeded(); - child->setLogicalLocation(findChildLogicalPosition(child, sizingData)); + // We need pending layouts to be done in order to compute auto-margins properly. + updateAutoMarginsInColumnAxisIfNeeded(*child); + updateAutoMarginsInRowAxisIfNeeded(*child); + + child->setLogicalLocation(findChildLogicalPosition(*child)); // If the child moved, we have to repaint it as well as any floating/positioned // descendants. An exception is if we need a layout. In this case, we know we're going to @@ -781,257 +930,759 @@ void RenderGrid::layoutGridItems() if (!selfNeedsLayout() && child->checkForRepaintDuringLayout()) child->repaintDuringLayoutIfMoved(oldChildRect); } +} - for (size_t i = 0; i < sizingData.rowTracks.size(); ++i) - setLogicalHeight(logicalHeight() + sizingData.rowTracks[i].m_usedBreadth); - - // FIXME: We should handle min / max logical height. +void RenderGrid::prepareChildForPositionedLayout(RenderBox& child) +{ + ASSERT(child.isOutOfFlowPositioned()); + child.containingBlock()->insertPositionedObject(child); - setLogicalHeight(logicalHeight() + borderAndPaddingLogicalHeight()); - clearGrid(); + RenderLayer* childLayer = child.layer(); + childLayer->setStaticInlinePosition(borderAndPaddingStart()); + childLayer->setStaticBlockPosition(borderAndPaddingBefore()); } -GridCoordinate RenderGrid::cachedGridCoordinate(const RenderBox* gridItem) const +void RenderGrid::layoutPositionedObject(RenderBox& child, bool relayoutChildren, bool fixedPositionObjectsOnly) { - ASSERT(m_gridItemCoordinate.contains(gridItem)); - return m_gridItemCoordinate.get(gridItem); + // FIXME: Properly support orthogonal writing mode. + if (!isOrthogonalChild(child)) { + LayoutUnit columnOffset = LayoutUnit(); + LayoutUnit columnBreadth = LayoutUnit(); + offsetAndBreadthForPositionedChild(child, ForColumns, columnOffset, columnBreadth); + LayoutUnit rowOffset = LayoutUnit(); + LayoutUnit rowBreadth = LayoutUnit(); + offsetAndBreadthForPositionedChild(child, ForRows, rowOffset, rowBreadth); + + child.setOverrideContainingBlockContentLogicalWidth(columnBreadth); + child.setOverrideContainingBlockContentLogicalHeight(rowBreadth); + child.setExtraInlineOffset(columnOffset); + child.setExtraBlockOffset(rowOffset); + + if (child.parent() == this) { + auto& childLayer = *child.layer(); + childLayer.setStaticInlinePosition(borderStart() + columnOffset); + childLayer.setStaticBlockPosition(borderBefore() + rowOffset); + } + } + + RenderBlock::layoutPositionedObject(child, relayoutChildren, fixedPositionObjectsOnly); } -GridSpan RenderGrid::resolveGridPositionsFromAutoPlacementPosition(const RenderBox*, TrackSizingDirection, size_t initialPosition) const +void RenderGrid::offsetAndBreadthForPositionedChild(const RenderBox& child, GridTrackSizingDirection direction, LayoutUnit& offset, LayoutUnit& breadth) { - // FIXME: We don't support spanning with auto positions yet. Once we do, this is wrong. Also we should make - // sure the grid can accomodate the new item as we only grow 1 position in a given direction. - return GridSpan(initialPosition, initialPosition); + ASSERT(!isOrthogonalChild(child)); + bool isRowAxis = direction == ForColumns; + + unsigned autoRepeatCount = m_grid.autoRepeatTracks(direction); + GridSpan positions = GridPositionsResolver::resolveGridPositionsFromStyle(style(), child, direction, autoRepeatCount); + if (positions.isIndefinite()) { + offset = LayoutUnit(); + breadth = isRowAxis ? clientLogicalWidth() : clientLogicalHeight(); + return; + } + + // For positioned items we cannot use GridSpan::translate() because we could end up with negative values, as the positioned items do not create implicit tracks per spec. + int smallestStart = std::abs(m_grid.smallestTrackStart(direction)); + int startLine = positions.untranslatedStartLine() + smallestStart; + int endLine = positions.untranslatedEndLine() + smallestStart; + + GridPosition startPosition = isRowAxis ? child.style().gridItemColumnStart() : child.style().gridItemRowStart(); + GridPosition endPosition = isRowAxis ? child.style().gridItemColumnEnd() : child.style().gridItemRowEnd(); + int lastLine = numTracks(direction, m_grid); + + bool startIsAuto = startPosition.isAuto() + || (startPosition.isNamedGridArea() && !NamedLineCollection::isValidNamedLineOrArea(startPosition.namedGridLine(), style(), (direction == ForColumns) ? ColumnStartSide : RowStartSide)) + || (startLine < 0) + || (startLine > lastLine); + bool endIsAuto = endPosition.isAuto() + || (endPosition.isNamedGridArea() && !NamedLineCollection::isValidNamedLineOrArea(endPosition.namedGridLine(), style(), (direction == ForColumns) ? ColumnEndSide : RowEndSide)) + || (endLine < 0) + || (endLine > lastLine); + + // We're normalizing the positions to avoid issues with RTL (as they're stored in the same order than LTR but adding an offset). + LayoutUnit start; + if (!startIsAuto) { + if (isRowAxis) { + if (style().isLeftToRightDirection()) + start = m_columnPositions[startLine] - borderLogicalLeft(); + else + start = logicalWidth() - translateRTLCoordinate(m_columnPositions[startLine]) - borderLogicalRight(); + } else + start = m_rowPositions[startLine] - borderBefore(); + } + + LayoutUnit end = isRowAxis ? clientLogicalWidth() : clientLogicalHeight(); + if (!endIsAuto) { + if (isRowAxis) { + if (style().isLeftToRightDirection()) + end = m_columnPositions[endLine] - borderLogicalLeft(); + else + end = logicalWidth() - translateRTLCoordinate(m_columnPositions[endLine]) - borderLogicalRight(); + } else + end = m_rowPositions[endLine] - borderBefore(); + + // These vectors store line positions including gaps, but we shouldn't consider them for the edges of the grid. + if (endLine > 0 && endLine < lastLine) { + ASSERT(!m_grid.needsItemsPlacement()); + end -= guttersSize(m_grid, direction, endLine - 1, 2); + end -= isRowAxis ? m_offsetBetweenColumns : m_offsetBetweenRows; + } + } + + breadth = end - start; + offset = start; + + if (isRowAxis && !style().isLeftToRightDirection() && !child.style().hasStaticInlinePosition(child.isHorizontalWritingMode())) { + // If the child doesn't have a static inline position (i.e. "left" and/or "right" aren't "auto", + // we need to calculate the offset from the left (even if we're in RTL). + if (endIsAuto) + offset = LayoutUnit(); + else { + offset = translateRTLCoordinate(m_columnPositions[endLine]) - borderLogicalLeft(); + + if (endLine > 0 && endLine < lastLine) { + ASSERT(!m_grid.needsItemsPlacement()); + offset += guttersSize(m_grid, direction, endLine - 1, 2); + offset += isRowAxis ? m_offsetBetweenColumns : m_offsetBetweenRows; + } + } + } } -PassOwnPtr<GridSpan> RenderGrid::resolveGridPositionsFromStyle(const RenderBox* gridItem, TrackSizingDirection direction) const +LayoutUnit RenderGrid::gridAreaBreadthForChildIncludingAlignmentOffsets(const RenderBox& child, GridTrackSizingDirection direction) const { - const GridPosition& initialPosition = (direction == ForColumns) ? gridItem->style().gridItemColumnStart() : gridItem->style().gridItemRowStart(); - const GridPositionSide initialPositionSide = (direction == ForColumns) ? ColumnStartSide : RowStartSide; - const GridPosition& finalPosition = (direction == ForColumns) ? gridItem->style().gridItemColumnEnd() : gridItem->style().gridItemRowEnd(); - const GridPositionSide finalPositionSide = (direction == ForColumns) ? ColumnEndSide : RowEndSide; + // 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 = m_grid.gridItemSpan(child, direction); + const auto& linePositions = (direction == ForColumns) ? m_columnPositions : m_rowPositions; - // We should NEVER see both spans as they should have been handled during style resolve. - ASSERT(!initialPosition.isSpan() || !finalPosition.isSpan()); + LayoutUnit initialTrackPosition = linePositions[span.startLine()]; + LayoutUnit finalTrackPosition = linePositions[span.endLine() - 1]; - if (initialPosition.shouldBeResolvedAgainstOppositePosition() && finalPosition.shouldBeResolvedAgainstOppositePosition()) { - if (style().gridAutoFlow() == AutoFlowNone) - return adoptPtr(new GridSpan(0, 0)); + // 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(); +} - // We can't get our grid positions without running the auto placement algorithm. - return nullptr; +void RenderGrid::populateGridPositionsForDirection(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 == ForColumns; + auto& tracks = m_trackSizingAlgorithm.tracks(direction); + unsigned numberOfTracks = tracks.size(); + unsigned numberOfLines = numberOfTracks + 1; + unsigned lastLine = numberOfLines - 1; + + ContentAlignmentData offset = computeContentPositionAndDistributionOffset(direction, m_trackSizingAlgorithm.freeSpace(direction).value(), numberOfTracks); + auto& positions = isRowAxis ? m_columnPositions : m_rowPositions; + positions.resize(numberOfLines); + auto borderAndPadding = isRowAxis ? borderAndPaddingLogicalLeft() : borderAndPaddingBefore(); + positions[0] = borderAndPadding + offset.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. + bool hasCollapsedTracks = m_grid.hasAutoRepeatEmptyTracks(direction); + LayoutUnit gap = !hasCollapsedTracks ? gridGapForDirection(direction) : LayoutUnit(); + unsigned nextToLastLine = numberOfLines - 2; + for (unsigned i = 0; i < nextToLastLine; ++i) + positions[i + 1] = positions[i] + offset.distributionOffset + tracks[i].baseSize() + gap; + positions[lastLine] = positions[nextToLastLine] + tracks[nextToLastLine].baseSize(); + + // 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 = gridGapForDirection(direction); + unsigned remainingEmptyTracks = m_grid.autoRepeatEmptyTracks(direction)->size(); + LayoutUnit gapAccumulator; + for (unsigned i = 1; i < lastLine; ++i) { + if (m_grid.isEmptyAutoRepeatTrack(direction, i - 1)) + --remainingEmptyTracks; + 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 || !m_grid.isEmptyAutoRepeatTrack(direction, i)) + gapAccumulator += gap; + } + positions[i] += gapAccumulator; + } + positions[lastLine] += gapAccumulator; + } } + auto& offsetBetweenTracks = isRowAxis ? m_offsetBetweenColumns : m_offsetBetweenRows; + offsetBetweenTracks = offset.distributionOffset; +} - if (initialPosition.shouldBeResolvedAgainstOppositePosition()) { - // Infer the position from the final position ('auto / 1' or 'span 2 / 3' case). - const size_t finalResolvedPosition = resolveGridPositionFromStyle(finalPosition, finalPositionSide); - return resolveGridPositionAgainstOppositePosition(finalResolvedPosition, initialPosition, initialPositionSide); +static LayoutUnit computeOverflowAlignmentOffset(OverflowAlignment overflow, LayoutUnit trackSize, LayoutUnit childSize) +{ + LayoutUnit offset = trackSize - childSize; + switch (overflow) { + case OverflowAlignmentSafe: + // 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 OverflowAlignmentUnsafe: + case OverflowAlignmentDefault: + // 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; } - if (finalPosition.shouldBeResolvedAgainstOppositePosition()) { - // Infer our position from the initial position ('1 / auto' or '3 / span 2' case). - const size_t initialResolvedPosition = resolveGridPositionFromStyle(initialPosition, initialPositionSide); - return resolveGridPositionAgainstOppositePosition(initialResolvedPosition, finalPosition, finalPositionSide); - } + ASSERT_NOT_REACHED(); + return 0; +} - size_t resolvedInitialPosition = resolveGridPositionFromStyle(initialPosition, initialPositionSide); - size_t resolvedFinalPosition = resolveGridPositionFromStyle(finalPosition, finalPositionSide); +// FIXME: This logic is shared by RenderFlexibleBox, so it should be moved to RenderBox. +bool RenderGrid::needToStretchChildLogicalHeight(const RenderBox& child) const +{ + if (child.style().resolvedAlignSelf(style(), selfAlignmentNormalBehavior).position() != ItemPositionStretch) + return false; - // If 'grid-row-end' specifies a line at or before that specified by 'grid-row-start', it computes to 'span 1'. - if (resolvedFinalPosition < resolvedInitialPosition) - resolvedFinalPosition = resolvedInitialPosition; + return isHorizontalWritingMode() && child.style().height().isAuto(); +} - return adoptPtr(new GridSpan(resolvedInitialPosition, resolvedFinalPosition)); +// FIXME: This logic is shared by RenderFlexibleBox, so it should be moved to RenderBox. +LayoutUnit RenderGrid::marginLogicalHeightForChild(const RenderBox& child) const +{ + return isHorizontalWritingMode() ? child.verticalMarginExtent() : child.horizontalMarginExtent(); } -inline static size_t adjustGridPositionForRowEndColumnEndSide(size_t resolvedPosition) +LayoutUnit RenderGrid::computeMarginLogicalSizeForChild(GridTrackSizingDirection direction, const RenderBox& child) const { - return resolvedPosition ? resolvedPosition - 1 : 0; + if (!child.style().hasMargin()) + return 0; + + LayoutUnit marginStart; + LayoutUnit marginEnd; + if (direction == ForColumns) + child.computeInlineDirectionMargins(*this, child.containingBlockLogicalWidthForContentInRegion(nullptr), child.logicalWidth(), marginStart, marginEnd); + else + child.computeBlockDirectionMargins(*this, marginStart, marginEnd); + + return marginStart + marginEnd; } -static size_t adjustGridPositionForSide(size_t resolvedPosition, RenderGrid::GridPositionSide side) +LayoutUnit RenderGrid::availableAlignmentSpaceForChildBeforeStretching(LayoutUnit gridAreaBreadthForChild, const RenderBox& child) const { - // An item finishing on the N-th line belongs to the N-1-th cell. - if (side == RenderGrid::ColumnEndSide || side == RenderGrid::RowEndSide) - return adjustGridPositionForRowEndColumnEndSide(resolvedPosition); + // Because we want to avoid multiple layouts, stretching logic might be performed before + // children are laid out, so we can't use the child cached values. Hence, we need to + // compute margins in order to determine the available height before stretching. + return gridAreaBreadthForChild - (child.needsLayout() ? computeMarginLogicalSizeForChild(ForRows, child) : marginLogicalHeightForChild(child)); +} - return resolvedPosition; +StyleSelfAlignmentData RenderGrid::alignSelfForChild(const RenderBox& child) const +{ + return child.style().resolvedAlignSelf(style(), selfAlignmentNormalBehavior); } -size_t RenderGrid::resolveNamedGridLinePositionFromStyle(const GridPosition& position, GridPositionSide side) const +StyleSelfAlignmentData RenderGrid::justifySelfForChild(const RenderBox& child) const { - ASSERT(!position.namedGridLine().isNull()); + return child.style().resolvedJustifySelf(style(), selfAlignmentNormalBehavior); +} - const NamedGridLinesMap& gridLinesNames = (side == ColumnStartSide || side == ColumnEndSide) ? style().namedGridColumnLines() : style().namedGridRowLines(); - NamedGridLinesMap::const_iterator it = gridLinesNames.find(position.namedGridLine()); - if (it == gridLinesNames.end()) { - if (position.isPositive()) - return 0; - const size_t lastLine = explicitGridSizeForSide(side); - return adjustGridPositionForSide(lastLine, side); +// FIXME: This logic is shared by RenderFlexibleBox, so it should be moved to RenderBox. +void RenderGrid::applyStretchAlignmentToChildIfNeeded(RenderBox& child) +{ + ASSERT(child.overrideContainingBlockContentLogicalHeight()); + + // We clear height 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. + child.clearOverrideLogicalContentHeight(); + + GridTrackSizingDirection childBlockDirection = flowAwareDirectionForChild(child, ForRows); + bool blockFlowIsColumnAxis = childBlockDirection == ForRows; + bool allowedToStretchChildBlockSize = blockFlowIsColumnAxis ? allowedToStretchChildAlongColumnAxis(child) : allowedToStretchChildAlongRowAxis(child); + if (allowedToStretchChildBlockSize) { + LayoutUnit stretchedLogicalHeight = availableAlignmentSpaceForChildBeforeStretching(overrideContainingBlockContentSizeForChild(child, childBlockDirection).value(), child); + LayoutUnit desiredLogicalHeight = child.constrainLogicalHeightByMinMax(stretchedLogicalHeight, LayoutUnit(-1)); + child.setOverrideLogicalContentHeight(desiredLogicalHeight - child.borderAndPaddingLogicalHeight()); + if (desiredLogicalHeight != child.logicalHeight()) { + // FIXME: Can avoid laying out here in some cases. See https://webkit.org/b/87905. + child.setLogicalHeight(LayoutUnit()); + child.setNeedsLayout(); + } } +} - size_t namedGridLineIndex; - if (position.isPositive()) - namedGridLineIndex = std::min<size_t>(position.integerPosition(), it->value.size()) - 1; - else - namedGridLineIndex = std::max<int>(it->value.size() - abs(position.integerPosition()), 0); - return adjustGridPositionForSide(it->value[namedGridLineIndex], side); +// FIXME: This logic is shared by RenderFlexibleBox, so it should be moved to RenderBox. +bool RenderGrid::hasAutoMarginsInColumnAxis(const RenderBox& child) const +{ + if (isHorizontalWritingMode()) + return child.style().marginTop().isAuto() || child.style().marginBottom().isAuto(); + return child.style().marginLeft().isAuto() || child.style().marginRight().isAuto(); } -size_t RenderGrid::resolveGridPositionFromStyle(const GridPosition& position, GridPositionSide side) const +// FIXME: This logic is shared by RenderFlexibleBox, so it should be moved to RenderBox. +bool RenderGrid::hasAutoMarginsInRowAxis(const RenderBox& child) const { - switch (position.type()) { - case ExplicitPosition: { - ASSERT(position.integerPosition()); + if (isHorizontalWritingMode()) + return child.style().marginLeft().isAuto() || child.style().marginRight().isAuto(); + return child.style().marginTop().isAuto() || child.style().marginBottom().isAuto(); +} - if (!position.namedGridLine().isNull()) - return resolveNamedGridLinePositionFromStyle(position, side); +// FIXME: This logic is shared by RenderFlexibleBox, so it should be moved to RenderBox. +void RenderGrid::updateAutoMarginsInRowAxisIfNeeded(RenderBox& child) +{ + ASSERT(!child.isOutOfFlowPositioned()); - // Handle <integer> explicit position. - if (position.isPositive()) - return adjustGridPositionForSide(position.integerPosition() - 1, side); + LayoutUnit availableAlignmentSpace = child.overrideContainingBlockContentLogicalWidth().value() - child.logicalWidth() - child.marginLogicalWidth(); + if (availableAlignmentSpace <= 0) + return; - size_t resolvedPosition = abs(position.integerPosition()) - 1; - const size_t endOfTrack = explicitGridSizeForSide(side); + const RenderStyle& parentStyle = style(); + Length marginStart = child.style().marginStartUsing(&parentStyle); + Length marginEnd = child.style().marginEndUsing(&parentStyle); + if (marginStart.isAuto() && marginEnd.isAuto()) { + child.setMarginStart(availableAlignmentSpace / 2, &parentStyle); + child.setMarginEnd(availableAlignmentSpace / 2, &parentStyle); + } else if (marginStart.isAuto()) { + child.setMarginStart(availableAlignmentSpace, &parentStyle); + } else if (marginEnd.isAuto()) { + child.setMarginEnd(availableAlignmentSpace, &parentStyle); + } +} - // Per http://lists.w3.org/Archives/Public/www-style/2013Mar/0589.html, we clamp negative value to the first line. - if (endOfTrack < resolvedPosition) - return 0; +// FIXME: This logic is shared by RenderFlexibleBox, so it should be moved to RenderBox. +void RenderGrid::updateAutoMarginsInColumnAxisIfNeeded(RenderBox& child) +{ + ASSERT(!child.isOutOfFlowPositioned()); - return adjustGridPositionForSide(endOfTrack - resolvedPosition, side); + LayoutUnit availableAlignmentSpace = child.overrideContainingBlockContentLogicalHeight().value() - child.logicalHeight() - child.marginLogicalHeight(); + if (availableAlignmentSpace <= 0) + return; + + const RenderStyle& parentStyle = style(); + Length marginBefore = child.style().marginBeforeUsing(&parentStyle); + Length marginAfter = child.style().marginAfterUsing(&parentStyle); + if (marginBefore.isAuto() && marginAfter.isAuto()) { + child.setMarginBefore(availableAlignmentSpace / 2, &parentStyle); + child.setMarginAfter(availableAlignmentSpace / 2, &parentStyle); + } else if (marginBefore.isAuto()) { + child.setMarginBefore(availableAlignmentSpace, &parentStyle); + } else if (marginAfter.isAuto()) { + child.setMarginAfter(availableAlignmentSpace, &parentStyle); } - case NamedGridAreaPosition: - { - NamedGridAreaMap::const_iterator it = style().namedGridArea().find(position.namedGridLine()); - // Unknown grid area should have been computed to 'auto' by now. - ASSERT(it != style().namedGridArea().end()); - const GridCoordinate& gridAreaCoordinate = it->value; - switch (side) { - case ColumnStartSide: - return gridAreaCoordinate.columns.initialPositionIndex; - case ColumnEndSide: - return gridAreaCoordinate.columns.finalPositionIndex; - case RowStartSide: - return gridAreaCoordinate.rows.initialPositionIndex; - case RowEndSide: - return gridAreaCoordinate.rows.finalPositionIndex; +} + +// FIXME: This logic could be refactored somehow and defined in RenderBox. +static int synthesizedBaselineFromBorderBox(const RenderBox& box, LineDirectionMode direction) +{ + return (direction == HorizontalLine ? box.size().height() : box.size().width()).toInt(); +} + +bool RenderGrid::isInlineBaselineAlignedChild(const RenderBox& child) const +{ + return alignSelfForChild(child).position() == ItemPositionBaseline && !isOrthogonalChild(child) && !hasAutoMarginsInColumnAxis(child); +} + +// FIXME: This logic is shared by RenderFlexibleBox, so it might be refactored somehow. +int RenderGrid::baselinePosition(FontBaseline, bool, LineDirectionMode direction, LinePositionMode mode) const +{ +#if ENABLE(ASSERT) + ASSERT(mode == PositionOnContainingLine); +#else + UNUSED_PARAM(mode); +#endif + int baseline = firstLineBaseline().value_or(synthesizedBaselineFromBorderBox(*this, direction)); + + int marginSize = direction == HorizontalLine ? verticalMarginExtent() : horizontalMarginExtent(); + return baseline + marginSize; +} + +std::optional<int> RenderGrid::firstLineBaseline() const +{ + if (isWritingModeRoot() || !m_grid.hasGridItems()) + return std::nullopt; + + const RenderBox* baselineChild = nullptr; + // Finding the first grid item in grid order. + unsigned numColumns = m_grid.numTracks(ForColumns); + for (size_t column = 0; column < numColumns; column++) { + for (const auto* child : m_grid.cell(0, column)) { + // If an item participates in baseline alignment, we select such item. + if (isInlineBaselineAlignedChild(*child)) { + // FIXME: self-baseline and content-baseline alignment not implemented yet. + baselineChild = child; + break; + } + if (!baselineChild) + baselineChild = child; } - ASSERT_NOT_REACHED(); - return 0; } - case AutoPosition: - case SpanPosition: - // 'auto' and span depend on the opposite position for resolution (e.g. grid-row: auto / 1 or grid-column: span 3 / "myHeader"). - ASSERT_NOT_REACHED(); - return 0; + + if (!baselineChild) + return std::nullopt; + + auto baseline = isOrthogonalChild(*baselineChild) ? std::nullopt : baselineChild->firstLineBaseline(); + // We take border-box's bottom if no valid baseline. + if (!baseline) { + // FIXME: We should pass |direction| into firstLineBaseline and stop bailing out if we're a writing + // mode root. This would also fix some cases where the grid is orthogonal to its container. + LineDirectionMode direction = isHorizontalWritingMode() ? HorizontalLine : VerticalLine; + return synthesizedBaselineFromBorderBox(*baselineChild, direction) + baselineChild->logicalTop().toInt(); } - ASSERT_NOT_REACHED(); - return 0; + return baseline.value() + baselineChild->logicalTop().toInt(); } -PassOwnPtr<GridSpan> RenderGrid::resolveGridPositionAgainstOppositePosition(size_t resolvedOppositePosition, const GridPosition& position, GridPositionSide side) const +std::optional<int> RenderGrid::inlineBlockBaseline(LineDirectionMode direction) const { - if (position.isAuto()) - return GridSpan::create(resolvedOppositePosition, resolvedOppositePosition); + if (std::optional<int> baseline = firstLineBaseline()) + return baseline; - ASSERT(position.isSpan()); - ASSERT(position.spanPosition() > 0); + int marginAscent = direction == HorizontalLine ? marginTop() : marginRight(); + return synthesizedBaselineFromBorderBox(*this, direction) + marginAscent; +} - if (!position.namedGridLine().isNull()) { - // span 2 'c' -> we need to find the appropriate grid line before / after our opposite position. - return resolveNamedGridLinePositionAgainstOppositePosition(resolvedOppositePosition, position, side); +GridAxisPosition RenderGrid::columnAxisPositionForChild(const RenderBox& child) const +{ + bool hasSameWritingMode = child.style().writingMode() == style().writingMode(); + bool childIsLTR = child.style().isLeftToRightDirection(); + + switch (child.style().resolvedAlignSelf(style(), selfAlignmentNormalBehavior).position()) { + case ItemPositionSelfStart: + // FIXME: Should we implement this logic in a generic utility function ? + // Aligns the alignment subject to be flush with the edge of the alignment container + // corresponding to the alignment subject's 'start' side in the column axis. + if (isOrthogonalChild(child)) { + // If orthogonal writing-modes, self-start will be based on the child's inline-axis + // direction (inline-start), because it's the one parallel to the column axis. + if (style().isFlippedBlocksWritingMode()) + return childIsLTR ? GridAxisEnd : GridAxisStart; + return childIsLTR ? GridAxisStart : GridAxisEnd; + } + // self-start is based on the child's block-flow direction. That's why we need to check against the grid container's block-flow direction. + return hasSameWritingMode ? GridAxisStart : GridAxisEnd; + case ItemPositionSelfEnd: + // FIXME: Should we implement this logic in a generic utility function ? + // Aligns the alignment subject to be flush with the edge of the alignment container + // corresponding to the alignment subject's 'end' side in the column axis. + if (isOrthogonalChild(child)) { + // If orthogonal writing-modes, self-end will be based on the child's inline-axis + // direction, (inline-end) because it's the one parallel to the column axis. + if (style().isFlippedBlocksWritingMode()) + return childIsLTR ? GridAxisStart : GridAxisEnd; + return childIsLTR ? GridAxisEnd : GridAxisStart; + } + // self-end is based on the child's block-flow direction. That's why we need to check against the grid container's block-flow direction. + return hasSameWritingMode ? GridAxisEnd : GridAxisStart; + case ItemPositionLeft: + // 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 GridAxisStart; + case ItemPositionRight: + // 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 GridAxisStart; + case ItemPositionCenter: + return GridAxisCenter; + case ItemPositionFlexStart: // Only used in flex layout, otherwise equivalent to 'start'. + // Aligns the alignment subject to be flush with the alignment container's 'start' edge (block-start) in the column axis. + case ItemPositionStart: + return GridAxisStart; + case ItemPositionFlexEnd: // Only used in flex layout, otherwise equivalent to 'end'. + // Aligns the alignment subject to be flush with the alignment container's 'end' edge (block-end) in the column axis. + case ItemPositionEnd: + return GridAxisEnd; + case ItemPositionStretch: + return GridAxisStart; + case ItemPositionBaseline: + case ItemPositionLastBaseline: + // FIXME: Implement the previous values. For now, we always 'start' align the child. + return GridAxisStart; + case ItemPositionAuto: + case ItemPositionNormal: + break; } - // 'span 1' is contained inside a single grid track regardless of the direction. - // That's why the CSS span value is one more than the offset we apply. - size_t positionOffset = position.spanPosition() - 1; - if (side == ColumnStartSide || side == RowStartSide) { - size_t initialResolvedPosition = std::max<int>(0, resolvedOppositePosition - positionOffset); - return GridSpan::create(initialResolvedPosition, resolvedOppositePosition); + ASSERT_NOT_REACHED(); + return GridAxisStart; +} + +GridAxisPosition RenderGrid::rowAxisPositionForChild(const RenderBox& child) const +{ + bool hasSameDirection = child.style().direction() == style().direction(); + bool gridIsLTR = style().isLeftToRightDirection(); + + switch (child.style().resolvedJustifySelf(style(), selfAlignmentNormalBehavior).position()) { + case ItemPositionSelfStart: + // FIXME: Should we implement this logic in a generic utility function ? + // Aligns the alignment subject to be flush with the edge of the alignment container + // corresponding to the alignment subject's 'start' side in the row axis. + if (isOrthogonalChild(child)) { + // If orthogonal writing-modes, self-start will be based on the child's block-axis + // direction, because it's the one parallel to the row axis. + if (child.style().isFlippedBlocksWritingMode()) + return gridIsLTR ? GridAxisEnd : GridAxisStart; + return gridIsLTR ? GridAxisStart : GridAxisEnd; + } + // self-start is based on the child's inline-flow direction. That's why we need to check against the grid container's direction. + return hasSameDirection ? GridAxisStart : GridAxisEnd; + case ItemPositionSelfEnd: + // FIXME: Should we implement this logic in a generic utility function ? + // Aligns the alignment subject to be flush with the edge of the alignment container + // corresponding to the alignment subject's 'end' side in the row axis. + if (isOrthogonalChild(child)) { + // If orthogonal writing-modes, self-end will be based on the child's block-axis + // direction, because it's the one parallel to the row axis. + if (child.style().isFlippedBlocksWritingMode()) + return gridIsLTR ? GridAxisStart : GridAxisEnd; + return gridIsLTR ? GridAxisEnd : GridAxisStart; + } + // self-end is based on the child's inline-flow direction. That's why we need to check against the grid container's direction. + return hasSameDirection ? GridAxisEnd : GridAxisStart; + case ItemPositionLeft: + // 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 gridIsLTR ? GridAxisStart : GridAxisEnd; + case ItemPositionRight: + // 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 gridIsLTR ? GridAxisEnd : GridAxisStart; + case ItemPositionCenter: + return GridAxisCenter; + case ItemPositionFlexStart: // 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 ItemPositionStart: + return GridAxisStart; + case ItemPositionFlexEnd: // 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 ItemPositionEnd: + return GridAxisEnd; + case ItemPositionStretch: + return GridAxisStart; + case ItemPositionBaseline: + case ItemPositionLastBaseline: + // FIXME: Implement the previous values. For now, we always 'start' align the child. + return GridAxisStart; + case ItemPositionAuto: + case ItemPositionNormal: + break; } - return GridSpan::create(resolvedOppositePosition, resolvedOppositePosition + positionOffset); + ASSERT_NOT_REACHED(); + return GridAxisStart; } -PassOwnPtr<GridSpan> RenderGrid::resolveNamedGridLinePositionAgainstOppositePosition(size_t resolvedOppositePosition, const GridPosition& position, GridPositionSide side) const +LayoutUnit RenderGrid::columnAxisOffsetForChild(const RenderBox& child) const { - ASSERT(position.isSpan()); - ASSERT(!position.namedGridLine().isNull()); - // Negative positions are not allowed per the specification and should have been handled during parsing. - ASSERT(position.spanPosition() > 0); + const GridSpan& rowsSpan = m_grid.gridItemSpan(child, ForRows); + unsigned childStartLine = rowsSpan.startLine(); + LayoutUnit startOfRow = m_rowPositions[childStartLine]; + LayoutUnit startPosition = startOfRow + marginBeforeForChild(child); + if (hasAutoMarginsInColumnAxis(child)) + return startPosition; + GridAxisPosition axisPosition = columnAxisPositionForChild(child); + switch (axisPosition) { + case GridAxisStart: + return startPosition; + case GridAxisEnd: + case GridAxisCenter: { + unsigned childEndLine = rowsSpan.endLine(); + LayoutUnit endOfRow = m_rowPositions[childEndLine]; + // m_rowPositions include distribution offset (because of content alignment) and gutters + // so we need to subtract them to get the actual end position for a given row + // (this does not have to be done for the last track as there are no more m_rowPositions after it). + if (childEndLine < m_rowPositions.size() - 1) + endOfRow -= gridGapForDirection(ForRows) + m_offsetBetweenRows; + LayoutUnit columnAxisChildSize = isOrthogonalChild(child) ? child.logicalWidth() + child.marginLogicalWidth() : child.logicalHeight() + child.marginLogicalHeight(); + auto overflow = child.style().resolvedAlignSelf(style(), selfAlignmentNormalBehavior).overflow(); + LayoutUnit offsetFromStartPosition = computeOverflowAlignmentOffset(overflow, endOfRow - startOfRow, columnAxisChildSize); + return startPosition + (axisPosition == GridAxisEnd ? offsetFromStartPosition : offsetFromStartPosition / 2); + } + } - const NamedGridLinesMap& gridLinesNames = (side == ColumnStartSide || side == ColumnEndSide) ? style().namedGridColumnLines() : style().namedGridRowLines(); - NamedGridLinesMap::const_iterator it = gridLinesNames.find(position.namedGridLine()); + ASSERT_NOT_REACHED(); + return 0; +} - // If there is no named grid line of that name, we resolve the position to 'auto' (which is equivalent to 'span 1' in this case). - // See http://lists.w3.org/Archives/Public/www-style/2013Jun/0394.html. - if (it == gridLinesNames.end()) - return GridSpan::create(resolvedOppositePosition, resolvedOppositePosition); - if (side == RowStartSide || side == ColumnStartSide) - return resolveRowStartColumnStartNamedGridLinePositionAgainstOppositePosition(resolvedOppositePosition, position, it->value); +LayoutUnit RenderGrid::rowAxisOffsetForChild(const RenderBox& child) const +{ + const GridSpan& columnsSpan = m_grid.gridItemSpan(child, ForColumns); + unsigned childStartLine = columnsSpan.startLine(); + LayoutUnit startOfColumn = m_columnPositions[childStartLine]; + LayoutUnit startPosition = startOfColumn + marginStartForChild(child); + if (hasAutoMarginsInRowAxis(child)) + return startPosition; + GridAxisPosition axisPosition = rowAxisPositionForChild(child); + switch (axisPosition) { + case GridAxisStart: + return startPosition; + case GridAxisEnd: + case GridAxisCenter: { + unsigned childEndLine = columnsSpan.endLine(); + LayoutUnit endOfColumn = m_columnPositions[childEndLine]; + // m_columnPositions include distribution offset (because of content alignment) and gutters + // so we need to subtract them to get the actual end position for a given column + // (this does not have to be done for the last track as there are no more m_columnPositions after it). + if (childEndLine < m_columnPositions.size() - 1) + endOfColumn -= gridGapForDirection(ForColumns) + m_offsetBetweenColumns; + LayoutUnit rowAxisChildSize = isOrthogonalChild(child) ? child.logicalHeight() + child.marginLogicalHeight() : child.logicalWidth() + child.marginLogicalWidth(); + auto overflow = child.style().resolvedJustifySelf(style(), selfAlignmentNormalBehavior).overflow(); + LayoutUnit offsetFromStartPosition = computeOverflowAlignmentOffset(overflow, endOfColumn - startOfColumn, rowAxisChildSize); + return startPosition + (axisPosition == GridAxisEnd ? offsetFromStartPosition : offsetFromStartPosition / 2); + } + } - return resolveRowEndColumnEndNamedGridLinePositionAgainstOppositePosition(resolvedOppositePosition, position, it->value); + ASSERT_NOT_REACHED(); + return 0; } -PassOwnPtr<GridSpan> RenderGrid::resolveRowStartColumnStartNamedGridLinePositionAgainstOppositePosition(size_t resolvedOppositePosition, const GridPosition& position, const Vector<size_t>& gridLines) const +ContentPosition static resolveContentDistributionFallback(ContentDistributionType distribution) { - // The grid line inequality needs to be strict (which doesn't match the after / end case) because |resolvedOppositePosition| - // is already converted to an index in our grid representation (ie one was removed from the grid line to account for the side). - // FIXME: This could be a binary search as |gridLines| is ordered. - int firstLineBeforeOppositePositionIndex = gridLines.size() - 1; - for (; firstLineBeforeOppositePositionIndex >= 0 && gridLines[firstLineBeforeOppositePositionIndex] > resolvedOppositePosition; --firstLineBeforeOppositePositionIndex) { } + switch (distribution) { + case ContentDistributionSpaceBetween: + return ContentPositionStart; + case ContentDistributionSpaceAround: + return ContentPositionCenter; + case ContentDistributionSpaceEvenly: + return ContentPositionCenter; + case ContentDistributionStretch: + return ContentPositionStart; + case ContentDistributionDefault: + return ContentPositionNormal; + } - size_t gridLineIndex = std::max<int>(0, firstLineBeforeOppositePositionIndex - position.spanPosition() + 1); - size_t resolvedGridLinePosition = gridLines[gridLineIndex]; - if (resolvedGridLinePosition > resolvedOppositePosition) - resolvedGridLinePosition = resolvedOppositePosition; - return GridSpan::create(resolvedGridLinePosition, resolvedOppositePosition); + ASSERT_NOT_REACHED(); + return ContentPositionNormal; } -PassOwnPtr<GridSpan> RenderGrid::resolveRowEndColumnEndNamedGridLinePositionAgainstOppositePosition(size_t resolvedOppositePosition, const GridPosition& position, const Vector<size_t>& gridLines) const +static ContentAlignmentData contentDistributionOffset(const LayoutUnit& availableFreeSpace, ContentPosition& fallbackPosition, ContentDistributionType distribution, unsigned numberOfGridTracks) { - // FIXME: This could be a binary search as |gridLines| is ordered. - size_t firstLineAfterOppositePositionIndex = 0; - for (; firstLineAfterOppositePositionIndex < gridLines.size() && gridLines[firstLineAfterOppositePositionIndex] <= resolvedOppositePosition; ++firstLineAfterOppositePositionIndex) { } + if (distribution != ContentDistributionDefault && fallbackPosition == ContentPositionNormal) + fallbackPosition = resolveContentDistributionFallback(distribution); + + if (availableFreeSpace <= 0) + return ContentAlignmentData::defaultOffsets(); + + LayoutUnit distributionOffset; + switch (distribution) { + case ContentDistributionSpaceBetween: + if (numberOfGridTracks < 2) + return ContentAlignmentData::defaultOffsets(); + return {0, availableFreeSpace / (numberOfGridTracks - 1)}; + case ContentDistributionSpaceAround: + if (numberOfGridTracks < 1) + return ContentAlignmentData::defaultOffsets(); + distributionOffset = availableFreeSpace / numberOfGridTracks; + return {distributionOffset / 2, distributionOffset}; + case ContentDistributionSpaceEvenly: + distributionOffset = availableFreeSpace / (numberOfGridTracks + 1); + return {distributionOffset, distributionOffset}; + case ContentDistributionStretch: + case ContentDistributionDefault: + return ContentAlignmentData::defaultOffsets(); + } - size_t gridLineIndex = std::min(gridLines.size() - 1, firstLineAfterOppositePositionIndex + position.spanPosition() - 1); - size_t resolvedGridLinePosition = adjustGridPositionForRowEndColumnEndSide(gridLines[gridLineIndex]); - if (resolvedGridLinePosition < resolvedOppositePosition) - resolvedGridLinePosition = resolvedOppositePosition; - return GridSpan::create(resolvedOppositePosition, resolvedGridLinePosition); + ASSERT_NOT_REACHED(); + return ContentAlignmentData::defaultOffsets(); } -LayoutUnit RenderGrid::gridAreaBreadthForChild(const RenderBox* child, TrackSizingDirection direction, const Vector<GridTrack>& tracks) const +ContentAlignmentData RenderGrid::computeContentPositionAndDistributionOffset(GridTrackSizingDirection direction, const LayoutUnit& availableFreeSpace, unsigned numberOfGridTracks) const { - const GridCoordinate& coordinate = cachedGridCoordinate(child); - const GridSpan& span = (direction == ForColumns) ? coordinate.columns : coordinate.rows; - LayoutUnit gridAreaBreadth = 0; - for (size_t trackIndex = span.initialPositionIndex; trackIndex <= span.finalPositionIndex; ++trackIndex) - gridAreaBreadth += tracks[trackIndex].m_usedBreadth; - return gridAreaBreadth; + bool isRowAxis = direction == ForColumns; + auto position = isRowAxis ? style().resolvedJustifyContentPosition(contentAlignmentNormalBehaviorGrid()) : style().resolvedAlignContentPosition(contentAlignmentNormalBehaviorGrid()); + auto distribution = isRowAxis ? style().resolvedJustifyContentDistribution(contentAlignmentNormalBehaviorGrid()) : style().resolvedAlignContentDistribution(contentAlignmentNormalBehaviorGrid()); + // If <content-distribution> value can't be applied, 'position' will become the associated + // <content-position> fallback value. + auto contentAlignment = contentDistributionOffset(availableFreeSpace, position, distribution, numberOfGridTracks); + if (contentAlignment.isValid()) + return contentAlignment; + + auto overflow = (isRowAxis ? style().justifyContent() : style().alignContent()).overflow(); + if (availableFreeSpace <= 0 && overflow == OverflowAlignmentSafe) + return {0, 0}; + + switch (position) { + case ContentPositionLeft: + // The align-content's axis is always orthogonal to the inline-axis. + return {0, 0}; + case ContentPositionRight: + if (isRowAxis) + return {availableFreeSpace, 0}; + // The align-content's axis is always orthogonal to the inline-axis. + return {0, 0}; + case ContentPositionCenter: + return {availableFreeSpace / 2, 0}; + case ContentPositionFlexEnd: // Only used in flex layout, for other layout, it's equivalent to 'end'. + case ContentPositionEnd: + if (isRowAxis) + return {style().isLeftToRightDirection() ? availableFreeSpace : LayoutUnit(), LayoutUnit()}; + return {availableFreeSpace, 0}; + case ContentPositionFlexStart: // Only used in flex layout, for other layout, it's equivalent to 'start'. + case ContentPositionStart: + if (isRowAxis) + return {style().isLeftToRightDirection() ? LayoutUnit() : availableFreeSpace, LayoutUnit()}; + return {0, 0}; + case ContentPositionBaseline: + case ContentPositionLastBaseline: + // FIXME: Implement the previous values. For now, we always 'start' align. + // http://webkit.org/b/145566 + if (isRowAxis) + return {style().isLeftToRightDirection() ? LayoutUnit() : availableFreeSpace, LayoutUnit()}; + return {0, 0}; + case ContentPositionNormal: + break; + } + + ASSERT_NOT_REACHED(); + return {0, 0}; } -LayoutPoint RenderGrid::findChildLogicalPosition(RenderBox* child, const GridSizingData& sizingData) +LayoutUnit RenderGrid::translateRTLCoordinate(LayoutUnit coordinate) const { - const GridCoordinate& coordinate = cachedGridCoordinate(child); + ASSERT(!style().isLeftToRightDirection()); - // The grid items should be inside the grid container's border box, that's why they need to be shifted. - LayoutPoint offset(borderAndPaddingStart() + marginStartForChild(*child), borderAndPaddingBefore() + marginBeforeForChild(*child)); - // FIXME: |columnTrack| and |rowTrack| should be smaller than our column / row count. - for (size_t i = 0; i < coordinate.columns.initialPositionIndex && i < sizingData.columnTracks.size(); ++i) - offset.setX(offset.x() + sizingData.columnTracks[i].m_usedBreadth); - for (size_t i = 0; i < coordinate.rows.initialPositionIndex && i < sizingData.rowTracks.size(); ++i) - offset.setY(offset.y() + sizingData.rowTracks[i].m_usedBreadth); + LayoutUnit alignmentOffset = m_columnPositions[0]; + LayoutUnit rightGridEdgePosition = m_columnPositions[m_columnPositions.size() - 1]; + return rightGridEdgePosition + alignmentOffset - coordinate; +} - return offset; +LayoutPoint RenderGrid::findChildLogicalPosition(const RenderBox& child) const +{ + LayoutUnit columnAxisOffset = columnAxisOffsetForChild(child); + LayoutUnit rowAxisOffset = rowAxisOffsetForChild(child); + + // 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 (!style().isLeftToRightDirection()) + rowAxisOffset = translateRTLCoordinate(rowAxisOffset) - (isOrthogonalChild(child) ? child.logicalHeight() : child.logicalWidth()); + + // "In the positioning phase [...] calculations are performed according to the writing mode + // of the containing block of the box establishing the orthogonal flow." However, the + // resulting LayoutPoint will be used in 'setLogicalPosition' in order to set the child's + // logical position, which will only take into account the child's writing-mode. + LayoutPoint childLocation(rowAxisOffset, columnAxisOffset); + return isOrthogonalChild(child) ? childLocation.transposedPoint() : childLocation; +} + +unsigned RenderGrid::numTracks(GridTrackSizingDirection direction, const Grid& grid) const +{ + // Due to limitations in our internal representation, we cannot know the number of columns from + // m_grid *if* there is no row (because m_grid 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 are't any implicit tracks, + // because not having rows implies that there are no "normal" children (out-of-flow children are + // not stored in m_grid). + ASSERT(!grid.needsItemsPlacement()); + if (direction == ForRows) + return grid.numTracks(ForRows); + + // FIXME: This still requires knowledge about m_grid internals. + return grid.numTracks(ForRows) ? grid.numTracks(ForColumns) : GridPositionsResolver::explicitGridColumnCount(style(), grid.autoRepeatTracks(ForColumns)); } void RenderGrid::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset, PaintInfo& forChild, bool usePrintRect) { - for (RenderBox* child = m_orderIterator.first(); child; child = m_orderIterator.next()) - paintChild(*child, paintInfo, paintOffset, forChild, usePrintRect); + ASSERT(!m_grid.needsItemsPlacement()); + for (RenderBox* child = m_grid.orderIterator().first(); child; child = m_grid.orderIterator().next()) + paintChild(*child, paintInfo, paintOffset, forChild, usePrintRect, PaintAsInlineBlock); } const char* RenderGrid::renderName() const |