blob: b763595185b9dd24ea3e0e9495fcb2384daabd01 [file]
/*
* Copyright (C) 2017 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "GridLayoutFunctions.h"
#include "AncestorSubgridIterator.h"
#include "RenderBoxInlines.h"
#include "RenderBoxModelObjectInlines.h"
#include "RenderChildIterator.h"
#include "RenderGrid.h"
#include "RenderStyleConstants.h"
#include "StyleComputedStyle+GettersInlines.h"
#include "StyleGridTrackSizingDirection.h"
namespace WebCore {
namespace GridLayoutFunctions {
static inline bool NODELETE marginStartIsAuto(const RenderBox& gridItem, Style::GridTrackSizingDirection direction)
{
return direction == Style::GridTrackSizingDirection::Columns ? gridItem.style().marginStart().isAuto() : gridItem.style().marginBefore().isAuto();
}
static inline bool NODELETE marginEndIsAuto(const RenderBox& gridItem, Style::GridTrackSizingDirection direction)
{
return direction == Style::GridTrackSizingDirection::Columns ? gridItem.style().marginEnd().isAuto() : gridItem.style().marginAfter().isAuto();
}
static bool gridItemHasMargin(const RenderBox& gridItem, Style::GridTrackSizingDirection direction)
{
auto hasMarginEdge = [](auto& edge) {
return !edge.isKnownZero() && !edge.isAuto();
};
auto& itemStyle = gridItem.style();
if (direction == Style::GridTrackSizingDirection::Columns)
return hasMarginEdge(itemStyle.marginStart()) || hasMarginEdge(itemStyle.marginEnd());
return hasMarginEdge(itemStyle.marginBefore()) || hasMarginEdge(itemStyle.marginAfter());
}
LayoutUnit computeMarginLogicalSizeForGridItem(const RenderGrid& grid, Style::GridTrackSizingDirection direction, const RenderBox& gridItem)
{
auto flowAwareDirection = flowAwareDirectionForGridItem(grid, gridItem, direction);
if (!gridItemHasMargin(gridItem, flowAwareDirection))
return 0;
LayoutUnit marginStart;
LayoutUnit marginEnd;
if (direction == Style::GridTrackSizingDirection::Columns)
gridItem.computeInlineDirectionMargins(grid, gridItem.containingBlockLogicalWidthForContent(), { }, gridItem.logicalWidth(), marginStart, marginEnd);
else
gridItem.computeBlockDirectionMargins(grid, marginStart, marginEnd);
return marginStartIsAuto(gridItem, flowAwareDirection) ? marginEnd : marginEndIsAuto(gridItem, flowAwareDirection) ? marginStart : marginStart + marginEnd;
}
bool hasRelativeOrKeywordOrAutoSizeForGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction)
{
if (direction == Style::GridTrackSizingDirection::Columns)
return gridItem.hasRelativeLogicalWidth() || gridItem.style().logicalWidth().isSizingKeywordOrAuto();
return gridItem.hasRelativeLogicalHeight() || gridItem.style().logicalHeight().isSizingKeywordOrAuto();
}
static ExtraMarginsFromSubgrids extraMarginForSubgrid(const RenderGrid& parent, unsigned startLine, unsigned endLine, Style::GridTrackSizingDirection direction)
{
unsigned numTracks = parent.numTracks(direction);
if (!numTracks || !parent.isSubgrid(direction))
return { };
std::optional<LayoutUnit> availableSpace;
if (!hasRelativeOrKeywordOrAutoSizeForGridItem(parent, direction))
availableSpace = parent.availableSpaceForGutters(direction);
RenderGrid& grandParent = downcast<RenderGrid>(*parent.parent());
ExtraMarginsFromSubgrids extraMargins;
if (!startLine)
extraMargins.addTrackStartMargin((direction == Style::GridTrackSizingDirection::Columns) ? parent.marginAndBorderAndPaddingStart() : parent.marginAndBorderAndPaddingBefore());
else
extraMargins.addTrackStartMargin((parent.gridGap(direction, availableSpace) - grandParent.gridGap(direction)) / 2);
if (endLine == numTracks)
extraMargins.addTrackEndMargin((direction == Style::GridTrackSizingDirection::Columns) ? parent.marginAndBorderAndPaddingEnd() : parent.marginAndBorderAndPaddingAfter());
else
extraMargins.addTrackEndMargin((parent.gridGap(direction, availableSpace) - grandParent.gridGap(direction)) / 2);
return extraMargins;
}
ExtraMarginsFromSubgrids extraMarginForSubgridAncestors(Style::GridTrackSizingDirection direction, const RenderBox& gridItem)
{
ExtraMarginsFromSubgrids extraMargins;
for (auto& currentAncestorSubgrid : ancestorSubgridsOfGridItem(gridItem, direction)) {
GridSpan span = currentAncestorSubgrid.gridSpanForGridItem(gridItem, direction);
extraMargins += extraMarginForSubgrid(currentAncestorSubgrid, span.startLine(), span.endLine(), direction);
}
return extraMargins;
}
LayoutUnit marginLogicalSizeForGridItem(const RenderGrid& grid, Style::GridTrackSizingDirection direction, const RenderBox& gridItem)
{
auto margin = computeMarginLogicalSizeForGridItem(grid, direction, gridItem);
if (&grid != gridItem.parent()) {
auto subgridDirection = flowAwareDirectionForGridItem(grid, *downcast<RenderGrid>(gridItem.parent()), direction);
margin += extraMarginForSubgridAncestors(subgridDirection, gridItem).extraTotalMargin();
}
return margin;
}
bool isOrthogonalGridItem(const RenderGrid& grid, const RenderBox& gridItem)
{
return gridItem.isHorizontalWritingMode() != grid.isHorizontalWritingMode();
}
bool isOrthogonalParent(const RenderGrid& grid, const RenderElement& parent)
{
return parent.isHorizontalWritingMode() != grid.isHorizontalWritingMode();
}
bool isAspectRatioBlockSizeDependentGridItem(const RenderBox& gridItem)
{
return (gridItem.style().aspectRatio().hasRatio() || gridItem.hasIntrinsicAspectRatio())
&& (gridItem.hasRelativeLogicalHeight() || gridItem.hasStretchedLogicalHeight());
}
bool isGridItemInlineSizeDependentOnBlockConstraints(const RenderBox& gridItem, const RenderGrid& parentGrid, ItemPosition gridItemAlignSelf)
{
ASSERT(gridItem.parent() == &parentGrid);
if (isOrthogonalGridItem(parentGrid, gridItem))
return true;
auto& gridItemStyle = gridItem.style();
if (gridItem.isRenderFlexibleBox() && gridItemStyle.isColumnFlexDirection() && gridItemStyle.flexWrap().isMultiline())
return true;
if (gridItem.isRenderMultiColumnFlow())
return true;
if (isAspectRatioBlockSizeDependentGridItem(gridItem))
return true;
// Stretch alignment allows the grid item content to resolve against the stretched size.
if (gridItemAlignSelf != ItemPosition::Stretch)
return false;
auto hasAspectRatioAndInlineSizeDependsOnBlockSize = [](auto& renderer) {
auto& rendererStyle = renderer.style();
bool rendererHasAspectRatio = renderer.hasIntrinsicAspectRatio() || rendererStyle.aspectRatio().hasRatio();
return rendererHasAspectRatio && rendererStyle.logicalWidth().isAuto() && !rendererStyle.logicalHeight().isSizingKeywordOrAuto();
};
for (auto& gridItemChild : childrenOfType<RenderBox>(gridItem)) {
if (hasAspectRatioAndInlineSizeDependsOnBlockSize(gridItemChild))
return true;
}
return false;
}
Style::GridTrackSizingDirection flowAwareDirectionForGridItem(const RenderGrid& grid, const RenderBox& gridItem, Style::GridTrackSizingDirection direction)
{
return !isOrthogonalGridItem(grid, gridItem) ? direction : orthogonalDirection(direction);
}
Style::GridTrackSizingDirection flowAwareDirectionForParent(const RenderGrid& grid, const RenderElement& parent, Style::GridTrackSizingDirection direction)
{
return isOrthogonalParent(grid, parent) ? orthogonalDirection(direction) : direction;
}
std::optional<RenderBox::GridAreaSize> overridingContainingBlockContentSizeForGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction)
{
return direction == Style::GridTrackSizingDirection::Columns ? gridItem.gridAreaContentLogicalWidth() : gridItem.gridAreaContentLogicalHeight();
}
bool isFlippedDirection(const RenderGrid& grid, Style::GridTrackSizingDirection direction)
{
if (direction == Style::GridTrackSizingDirection::Columns)
return grid.writingMode().isBidiRTL();
return grid.writingMode().isBlockFlipped();
}
bool isSubgridReversedDirection(const RenderGrid& grid, Style::GridTrackSizingDirection outerDirection, const RenderGrid& subgrid)
{
auto subgridDirection = flowAwareDirectionForGridItem(grid, subgrid, outerDirection);
ASSERT(subgrid.isSubgrid(subgridDirection));
return isFlippedDirection(grid, outerDirection) != isFlippedDirection(subgrid, subgridDirection);
}
unsigned alignmentContextForBaselineAlignment(const GridSpan& span, const ItemPosition& alignment)
{
ASSERT(alignment == ItemPosition::Baseline || alignment == ItemPosition::LastBaseline);
if (alignment == ItemPosition::Baseline)
return span.startLine();
return span.endLine() - 1;
}
void setOverridingContentSizeForGridItem(const RenderGrid& renderGrid, RenderBox& gridItem, LayoutUnit logicalSize, Style::GridTrackSizingDirection direction)
{
if (!isOrthogonalGridItem(renderGrid, gridItem))
direction == Style::GridTrackSizingDirection::Columns ? gridItem.setOverridingBorderBoxLogicalWidth(logicalSize) : gridItem.setOverridingBorderBoxLogicalHeight(logicalSize);
else
direction == Style::GridTrackSizingDirection::Columns ? gridItem.setOverridingBorderBoxLogicalHeight(logicalSize) : gridItem.setOverridingBorderBoxLogicalWidth(logicalSize);
}
void clearOverridingContentSizeForGridItem(const RenderGrid& renderGrid, RenderBox &gridItem, Style::GridTrackSizingDirection direction)
{
if (!isOrthogonalGridItem(renderGrid, gridItem))
direction == Style::GridTrackSizingDirection::Columns ? gridItem.clearOverridingBorderBoxLogicalWidth() : gridItem.clearOverridingBorderBoxLogicalHeight();
else
direction == Style::GridTrackSizingDirection::Columns ? gridItem.clearOverridingBorderBoxLogicalHeight() : gridItem.clearOverridingBorderBoxLogicalWidth();
}
bool hasAutoMarginsInColumnAxis(const RenderBox& gridItem, WritingMode parentWritingMode)
{
if (parentWritingMode.isHorizontal())
return gridItem.style().marginTop().isAuto() || gridItem.style().marginBottom().isAuto();
return gridItem.style().marginLeft().isAuto() || gridItem.style().marginRight().isAuto();
}
bool hasAutoMarginsInRowAxis(const RenderBox& gridItem, WritingMode parentWritingMode)
{
if (parentWritingMode.isHorizontal())
return gridItem.style().marginLeft().isAuto() || gridItem.style().marginRight().isAuto();
return gridItem.style().marginTop().isAuto() || gridItem.style().marginBottom().isAuto();
}
bool hasStretchableSizeInColumnAxis(const RenderBox& gridItem, const RenderGrid& gridContainer)
{
// Auto and stretch sizes are stretchable — alignment stretch handles the
// final layout for both. During intrinsic track sizing, the CSS stretch
// keyword falls back to auto, and alignment stretch applies afterward.
auto& columnAxisSize = gridContainer.isHorizontalWritingMode() ? gridItem.style().height() : gridItem.style().width();
if (!(columnAxisSize.isAuto() || columnAxisSize.isStretch()))
return false;
if (gridItem.style().aspectRatio().hasRatio() && !gridContainer.selfAlignmentForGridItem(gridItem, LogicalBoxAxis::Block, StretchingMode::Explicit).isStretch()) {
if (gridContainer.isHorizontalWritingMode() == gridItem.isHorizontalWritingMode()) {
// A non-auto inline size means the same for block size (column axis size) because of the aspect ratio.
if (!gridItem.style().logicalWidth().isAuto())
return false;
} else {
auto& logicalHeight = gridItem.style().logicalHeight();
if (logicalHeight.isFixed() || (logicalHeight.isPercentOrCalculated() && gridItem.percentageLogicalHeightIsResolvable()))
return false;
}
// Explicit stretching is like an explicit size.
if (gridContainer.willStretchItem(gridItem, LogicalBoxAxis::Inline, StretchingMode::Explicit))
return false;
}
return true;
}
bool hasStretchableSizeInRowAxis(const RenderBox& gridItem, const RenderGrid& gridContainer)
{
// Auto and stretch sizes are stretchable — alignment stretch handles the
// final layout for both.
auto& rowAxisSize = gridContainer.isHorizontalWritingMode() ? gridItem.style().width() : gridItem.style().height();
if (!(rowAxisSize.isAuto() || rowAxisSize.isStretch()))
return false;
if (gridItem.style().aspectRatio().hasRatio() && !gridContainer.selfAlignmentForGridItem(gridItem, LogicalBoxAxis::Inline, StretchingMode::Explicit).isStretch()) {
if (gridContainer.isHorizontalWritingMode() != gridItem.isHorizontalWritingMode()) {
// A non-auto inline size (column axis size) means the same for block size (row axis size) because of the aspect ratio.
if (!gridItem.style().logicalWidth().isAuto())
return false;
} else {
auto& logicalHeight = gridItem.style().logicalHeight();
if (logicalHeight.isFixed() || (logicalHeight.isPercentOrCalculated() && gridItem.percentageLogicalHeightIsResolvable()))
return false;
}
// Explicit stretching is like an explicit size.
if (gridContainer.willStretchItem(gridItem, LogicalBoxAxis::Block, StretchingMode::Explicit))
return false;
}
return true;
}
LayoutUnit availableAlignmentSpaceForGridItemBeforeStretching(const RenderGrid& grid, LayoutUnit gridAreaBreadthForGridItem, const RenderBox& gridItem, Style::GridTrackSizingDirection direction)
{
// Because we want to avoid multiple layouts, stretching logic might be performed before
// grid items are laid out, so we can't use the grid item cached values. Hence, we need to
// compute margins in order to determine the available height before stretching.
auto gridItemFlowDirection = flowAwareDirectionForGridItem(grid, gridItem, direction);
return std::max(0_lu, gridAreaBreadthForGridItem - marginLogicalSizeForGridItem(grid, gridItemFlowDirection, gridItem));
}
void updateAutoMarginsIfNeeded(RenderBox& gridItem, WritingMode writingMode)
{
updateAutoMarginsInRowAxisIfNeeded(gridItem, writingMode);
updateAutoMarginsInColumnAxisIfNeeded(gridItem, writingMode);
}
void updateAutoMarginsInRowAxisIfNeeded(RenderBox& gridItem, WritingMode writingMode)
{
ASSERT(!gridItem.isOutOfFlowPositioned());
auto& marginStart = gridItem.style().marginStart(writingMode);
auto& marginEnd = gridItem.style().marginEnd(writingMode);
LayoutUnit marginLogicalWidth;
// We should only consider computed margins if their specified value isn't
// 'auto', since such computed value may come from a previous layout and may
// be incorrect now.
if (!marginStart.isAuto())
marginLogicalWidth += gridItem.marginStart();
if (!marginEnd.isAuto())
marginLogicalWidth += gridItem.marginEnd();
auto availableAlignmentSpace = gridItem.gridAreaContentLogicalWidth()->value() - gridItem.logicalWidth() - marginLogicalWidth;
if (availableAlignmentSpace <= 0)
return;
if (marginStart.isAuto() && marginEnd.isAuto()) {
gridItem.setMarginStart(availableAlignmentSpace / 2, writingMode);
gridItem.setMarginEnd(availableAlignmentSpace / 2, writingMode);
} else if (marginStart.isAuto()) {
gridItem.setMarginStart(availableAlignmentSpace, writingMode);
} else if (marginEnd.isAuto())
gridItem.setMarginEnd(availableAlignmentSpace, writingMode);
}
void updateAutoMarginsInColumnAxisIfNeeded(RenderBox& gridItem, WritingMode writingMode)
{
ASSERT(!gridItem.isOutOfFlowPositioned());
auto& marginBefore = gridItem.style().marginBefore(writingMode);
auto& marginAfter = gridItem.style().marginAfter(writingMode);
LayoutUnit marginLogicalHeight;
// We should only consider computed margins if their specified value isn't
// 'auto', since such computed value may come from a previous layout and may
// be incorrect now.
if (!marginBefore.isAuto())
marginLogicalHeight += gridItem.marginBefore();
if (!marginAfter.isAuto())
marginLogicalHeight += gridItem.marginAfter();
auto availableAlignmentSpace = gridItem.gridAreaContentLogicalHeight()->value() - gridItem.logicalHeight() - marginLogicalHeight;
if (availableAlignmentSpace <= 0)
return;
if (marginBefore.isAuto() && marginAfter.isAuto()) {
gridItem.setMarginBefore(availableAlignmentSpace / 2, writingMode);
gridItem.setMarginAfter(availableAlignmentSpace / 2, writingMode);
} else if (marginBefore.isAuto()) {
gridItem.setMarginBefore(availableAlignmentSpace, writingMode);
} else if (marginAfter.isAuto())
gridItem.setMarginAfter(availableAlignmentSpace, writingMode);
}
bool isRelativeGridTrackBreadthAsAuto(const Style::GridTrackFitContentLength& length, std::optional<LayoutUnit> availableSpace)
{
return length.isPercentOrCalculated() && !availableSpace;
}
bool isRelativeGridTrackBreadthAsAuto(const Style::GridTrackBreadth& length, std::optional<LayoutUnit> availableSpace)
{
return length.isPercentOrCalculated() && !availableSpace;
}
const Style::GridTrackSize& rawGridTrackSize(const Style::ComputedStyle& renderStyle, Style::GridTrackSizingDirection direction, unsigned translatedIndex, unsigned autoRepeatTracksCount, unsigned explicitGridStart)
{
auto& autoTrackStyles = renderStyle.gridAutoList(direction);
auto& tracks = renderStyle.gridTemplateList(direction);
auto& trackStyles = tracks.sizes;
auto& autoRepeatTrackStyles = tracks.autoRepeatSizes;
unsigned insertionPoint = tracks.autoRepeatInsertionPoint;
// We should not use Style::GridPositionsResolver::explicitGridXXXCount() for this because the
// explicit grid might be larger than the number of tracks in grid-template-rows|columns (if
// grid-template-areas is specified for example).
unsigned explicitTracksCount = trackStyles.size() + autoRepeatTracksCount;
int untranslatedIndexAsInt = translatedIndex - explicitGridStart;
unsigned autoTrackStylesSize = autoTrackStyles.size();
if (untranslatedIndexAsInt < 0) {
int index = untranslatedIndexAsInt % static_cast<int>(autoTrackStylesSize);
// We need to transpose the index because the first negative implicit line will get the last defined auto track and so on.
index += index ? autoTrackStylesSize : 0;
ASSERT(index >= 0);
return autoTrackStyles[index];
}
unsigned untranslatedIndex = static_cast<unsigned>(untranslatedIndexAsInt);
if (untranslatedIndex >= explicitTracksCount)
return autoTrackStyles[(untranslatedIndex - explicitTracksCount) % autoTrackStylesSize];
if (!autoRepeatTracksCount || untranslatedIndex < insertionPoint)
return trackStyles[untranslatedIndex];
if (untranslatedIndex < (insertionPoint + autoRepeatTracksCount)) {
unsigned autoRepeatLocalIndex = untranslatedIndexAsInt - insertionPoint;
return autoRepeatTrackStyles[autoRepeatLocalIndex % autoRepeatTrackStyles.size()];
}
return trackStyles[untranslatedIndex - autoRepeatTracksCount];
}
} // namespace GridLayoutFunctions
} // namespace WebCore