blob: 1fc805f25f5ebcb96d3706627ae0b054aa7958e1 [file] [log] [blame]
/*
* 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 "RenderStyle+GettersInlines.h"
#include "StyleGridTrackSizingDirection.h"
namespace WebCore {
namespace GridLayoutFunctions {
static inline bool marginStartIsAuto(const RenderBox& gridItem, Style::GridTrackSizingDirection direction)
{
return direction == Style::GridTrackSizingDirection::Columns ? gridItem.style().marginStart().isAuto() : gridItem.style().marginBefore().isAuto();
}
static inline bool 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();
};
if (direction == Style::GridTrackSizingDirection::Columns)
return hasMarginEdge(gridItem.style().marginStart()) || hasMarginEdge(gridItem.style().marginEnd());
return hasMarginEdge(gridItem.style().marginBefore()) || hasMarginEdge(gridItem.style().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 hasRelativeOrIntrinsicSizeForGridItem(const RenderBox& gridItem, Style::GridTrackSizingDirection direction)
{
if (direction == Style::GridTrackSizingDirection::Columns)
return gridItem.hasRelativeLogicalWidth() || gridItem.style().logicalWidth().isIntrinsicOrLegacyIntrinsicOrAuto();
return gridItem.hasRelativeLogicalHeight() || gridItem.style().logicalHeight().isIntrinsicOrLegacyIntrinsicOrAuto();
}
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 (!hasRelativeOrIntrinsicSizeForGridItem(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().hasAspectRatio() || 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();
auto gridItemFlexWrap = gridItemStyle.flexWrap();
if (gridItem.isRenderFlexibleBox() && gridItem.style().isColumnFlexDirection() && (gridItemFlexWrap == FlexWrap::Wrap || gridItemFlexWrap == FlexWrap::Reverse))
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.hasAspectRatio();
return rendererHasAspectRatio && rendererStyle.logicalWidth().isAuto() && !rendererStyle.logicalHeight().isIntrinsicOrLegacyIntrinsicOrAuto();
};
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)
{
// Only auto sizes are stretchable.
if (!(gridContainer.isHorizontalWritingMode() ? gridItem.style().height().isAuto() : gridItem.style().width().isAuto()))
return false;
if (gridItem.style().hasAspectRatio() && !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)
{
// Only auto sizes are stretchable.
if (!(gridContainer.isHorizontalWritingMode() ? gridItem.style().width().isAuto() : gridItem.style().height().isAuto()))
return false;
if (gridItem.style().hasAspectRatio() && !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;
}
} // namespace GridLayoutFunctions
} // namespace WebCore