blob: 5ad5e7298be7466e0ef7603db0a487b01f7670cc [file] [log] [blame]
/*
* Copyright (C) 2011 Apple Inc. All rights reserved.
*
* 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 COMPUTER, 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 "core/layout/LayoutGrid.h"
#include <algorithm>
#include <memory>
#include "core/frame/UseCounter.h"
#include "core/layout/LayoutState.h"
#include "core/layout/TextAutosizer.h"
#include "core/paint/GridPainter.h"
#include "core/paint/PaintLayer.h"
#include "core/style/ComputedStyle.h"
#include "core/style/GridArea.h"
#include "platform/LengthFunctions.h"
#include "platform/text/WritingMode.h"
#include "platform/wtf/PtrUtil.h"
namespace blink {
struct ContentAlignmentData {
STACK_ALLOCATED();
public:
ContentAlignmentData(){};
ContentAlignmentData(LayoutUnit position, LayoutUnit distribution)
: position_offset(position), distribution_offset(distribution) {}
bool IsValid() { return position_offset >= 0 && distribution_offset >= 0; }
LayoutUnit position_offset = LayoutUnit(-1);
LayoutUnit distribution_offset = LayoutUnit(-1);
};
LayoutGrid::LayoutGrid(Element* element)
: LayoutBlock(element), grid_(this), track_sizing_algorithm_(this, grid_) {
DCHECK(!ChildrenInline());
if (!IsAnonymous())
UseCounter::Count(GetDocument(), UseCounter::kCSSGridLayout);
}
LayoutGrid::~LayoutGrid() {}
LayoutGrid* LayoutGrid::CreateAnonymous(Document* document) {
LayoutGrid* layout_grid = new LayoutGrid(nullptr);
layout_grid->SetDocumentForAnonymous(document);
return layout_grid;
}
void LayoutGrid::AddChild(LayoutObject* new_child, LayoutObject* before_child) {
LayoutBlock::AddChild(new_child, before_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 added.
if (new_child->IsOutOfFlowPositioned())
return;
// The grid needs to be recomputed as it might contain auto-placed items that
// will change their position.
DirtyGrid();
}
void LayoutGrid::RemoveChild(LayoutObject* child) {
LayoutBlock::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 LayoutGrid::StyleDidChange(StyleDifference diff,
const ComputedStyle* old_style) {
LayoutBlock::StyleDidChange(diff, old_style);
if (!old_style)
return;
// FIXME: The following checks could be narrowed down if we kept track of
// which type of grid items we have:
// - explicit grid size changes impact negative explicitely positioned and
// auto-placed grid items.
// - named grid lines only impact grid items with named grid lines.
// - auto-flow changes only impacts auto-placed children.
if (ExplicitGridDidResize(*old_style) ||
NamedGridLinesDefinitionDidChange(*old_style) ||
old_style->GetGridAutoFlow() != StyleRef().GetGridAutoFlow() ||
(diff.NeedsLayout() && (StyleRef().GridAutoRepeatColumns().size() ||
StyleRef().GridAutoRepeatRows().size())))
DirtyGrid();
}
bool LayoutGrid::ExplicitGridDidResize(const ComputedStyle& old_style) const {
return old_style.GridTemplateColumns().size() !=
StyleRef().GridTemplateColumns().size() ||
old_style.GridTemplateRows().size() !=
StyleRef().GridTemplateRows().size() ||
old_style.NamedGridAreaColumnCount() !=
StyleRef().NamedGridAreaColumnCount() ||
old_style.NamedGridAreaRowCount() !=
StyleRef().NamedGridAreaRowCount() ||
old_style.GridAutoRepeatColumns().size() !=
StyleRef().GridAutoRepeatColumns().size() ||
old_style.GridAutoRepeatRows().size() !=
StyleRef().GridAutoRepeatRows().size();
}
bool LayoutGrid::NamedGridLinesDefinitionDidChange(
const ComputedStyle& old_style) const {
return old_style.NamedGridRowLines() != StyleRef().NamedGridRowLines() ||
old_style.NamedGridColumnLines() != StyleRef().NamedGridColumnLines();
}
LayoutUnit LayoutGrid::ComputeTrackBasedLogicalHeight() const {
LayoutUnit logical_height;
const Vector<GridTrack>& all_rows = track_sizing_algorithm_.Tracks(kForRows);
for (const auto& row : all_rows)
logical_height += row.BaseSize();
logical_height +=
GuttersSize(grid_, kForRows, 0, all_rows.size(), kTrackSizing);
return logical_height;
}
void LayoutGrid::ComputeTrackSizesForDefiniteSize(
GridTrackSizingDirection direction,
LayoutUnit available_space) {
LayoutUnit free_space =
available_space - GuttersSize(grid_, direction, 0,
grid_.NumTracks(direction), kTrackSizing);
track_sizing_algorithm_.Setup(direction, NumTracks(direction, grid_),
kTrackSizing, available_space, free_space);
track_sizing_algorithm_.Run();
#if DCHECK_IS_ON()
DCHECK(track_sizing_algorithm_.TracksAreWiderThanMinTrackBreadth());
#endif
}
void LayoutGrid::RepeatTracksSizingIfNeeded(
LayoutUnit available_space_for_columns,
LayoutUnit available_space_for_rows) {
// Baseline alignment may change item's intrinsic size, hence changing its
// min-content contribution.
// https://drafts.csswg.org/css-align-3/#baseline-align-content
// https://drafts.csswg.org/css-align-3/#baseline-align-self
bool baseline_affect_intrinsic_width = BaselineMayAffectIntrinsicWidth();
bool baseline_affect_intrinsic_height = BaselineMayAffectIntrinsicHeight();
// 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.
bool has_any_orthogonal =
track_sizing_algorithm_.GetGrid().HasAnyOrthogonalGridItem();
// TODO (lajava): these are just some 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 (!baseline_affect_intrinsic_width && !baseline_affect_intrinsic_height &&
!has_any_orthogonal)
return;
// TODO (lajava): Whenever the min-content contribution of a grid item changes
// we may need to update the grid container's intrinsic width. The new
// intrinsic width may also affect the extra Track Sizing algorithm cycles we
// are about to execute.
// https://crbug.com/704713
// https://github.com/w3c/csswg-drafts/issues/1039
// Hence we need to repeat computeUsedBreadthOfGridTracks for both, columns
// and rows, to determine the final values.
ComputeTrackSizesForDefiniteSize(kForColumns, available_space_for_columns);
ComputeTrackSizesForDefiniteSize(kForRows, available_space_for_rows);
if (baseline_affect_intrinsic_height) {
SetLogicalHeight(ComputeTrackBasedLogicalHeight() +
BorderAndPaddingLogicalHeight() +
ScrollbarLogicalHeight());
}
}
void LayoutGrid::UpdateBlockLayout(bool relayout_children) {
DCHECK(NeedsLayout());
// We cannot perform a simplifiedLayout() on a dirty grid that
// has positioned items to be laid out.
if (!relayout_children &&
(!grid_.NeedsItemsPlacement() || !PosChildNeedsLayout()) &&
SimplifiedLayout())
return;
row_axis_alignment_context_.Clear();
col_axis_alignment_context_.Clear();
SubtreeLayoutScope layout_scope(*this);
{
// LayoutState needs this deliberate scope to pop before updating scroll
// information (which may trigger relayout).
LayoutState state(*this);
LayoutSize previous_size = Size();
// We need to clear both own and containingBlock override sizes to
// ensure we get the same result when grid's intrinsic size is
// computed again in the updateLogicalWidth call bellow.
if (SizesLogicalWidthToFitContent(StyleRef().LogicalWidth()) ||
StyleRef().LogicalWidth().IsIntrinsicOrAuto()) {
for (auto* child = FirstInFlowChildBox(); child;
child = child->NextInFlowSiblingBox()) {
if (!IsOrthogonalChild(*child))
continue;
child->ClearOverrideSize();
child->ClearContainingBlockOverrideSize();
child->ForceLayout();
}
}
UpdateLogicalWidth();
has_definite_logical_height_ = HasDefiniteLogicalHeight();
TextAutosizer::LayoutScope text_autosizer_layout_scope(this, &layout_scope);
PlaceItemsOnGrid(grid_, kTrackSizing);
// 1- First, the track sizing algorithm is used to resolve the sizes of the
// grid columns.
// At this point the logical width is always definite as the above call to
// updateLogicalWidth() properly resolves intrinsic sizes. We cannot do the
// same for heights though because many code paths inside
// updateLogicalHeight() require a previous call to setLogicalHeight() to
// resolve heights properly (like for positioned items for example).
LayoutUnit available_space_for_columns = AvailableLogicalWidth();
ComputeTrackSizesForDefiniteSize(kForColumns, available_space_for_columns);
// We take the chance to store the intrinsic sizes as they are just an
// intermediate result of the track sizing algorithm. Apart from eventually
// saving an algorithm execution (if {min|max}PreferredLogicalWidth() are
// called later), it fixes the use case of computing the preferred logical
// widths *after* the layout process. Although not very common, this happens
// in the Mac (content::RenderViewImpl::didUpdateLayout()) or under some
// circumstances when grids are also flex items (crbug.com/708159).
if (PreferredLogicalWidthsDirty()) {
LayoutUnit scrollbar_width = LayoutUnit(ScrollbarLogicalWidth());
min_preferred_logical_width_ =
track_sizing_algorithm_.MinContentSize() + scrollbar_width;
max_preferred_logical_width_ =
track_sizing_algorithm_.MaxContentSize() + scrollbar_width;
ClearPreferredLogicalWidthsDirty();
}
// 2- Next, the track sizing algorithm resolves the sizes of the grid rows,
// using the grid column sizes calculated in the previous step.
if (CachedHasDefiniteLogicalHeight()) {
ComputeTrackSizesForDefiniteSize(
kForRows, AvailableLogicalHeight(kExcludeMarginBorderPadding));
} else {
ComputeTrackSizesForIndefiniteSize(track_sizing_algorithm_, kForRows,
grid_, min_content_height_,
max_content_height_);
}
LayoutUnit track_based_logical_height = ComputeTrackBasedLogicalHeight() +
BorderAndPaddingLogicalHeight() +
ScrollbarLogicalHeight();
SetLogicalHeight(track_based_logical_height);
LayoutUnit old_client_after_edge = ClientLogicalBottom();
UpdateLogicalHeight();
// Once grid's indefinite height is resolved, we can compute the
// available free space for Content Alignment.
if (!CachedHasDefiniteLogicalHeight()) {
track_sizing_algorithm_.SetFreeSpace(
kForRows, LogicalHeight() - track_based_logical_height);
}
// TODO (lajava): We need to compute baselines after step 2 so
// items with a relative size (percentages) can resolve it before
// determining its baseline. However, we only set item's grid area
// (via override sizes) as part of the content-sized tracks sizing
// logic. Hence, items located at fixed or flexible tracks can't
// resolve correctly their size at this stage, which may lead to
// an incorrect computation of their shared context's baseline.
ComputeBaselineAlignmentContext();
// 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(available_space_for_columns,
ContentLogicalHeight());
// Grid container should have the minimum height of a line if it's editable.
// That doesn't affect track sizing though.
if (HasLineIfEmpty())
SetLogicalHeight(
std::max(LogicalHeight(), MinimumLogicalHeightForEmptyLine()));
ApplyStretchAlignmentToTracksIfNeeded(kForColumns);
ApplyStretchAlignmentToTracksIfNeeded(kForRows);
LayoutGridItems();
track_sizing_algorithm_.Reset();
if (Size() != previous_size)
relayout_children = true;
LayoutPositionedObjects(relayout_children || IsDocumentElement());
ComputeOverflow(old_client_after_edge);
}
UpdateLayerTransformAfterLayout();
UpdateAfterLayout();
ClearNeedsLayout();
}
LayoutUnit LayoutGrid::GridGapForDirection(
GridTrackSizingDirection direction,
SizingOperation sizing_operation) const {
LayoutUnit available_size;
const Length& gap = direction == kForColumns ? StyleRef().GridColumnGap()
: StyleRef().GridRowGap();
if (sizing_operation == kTrackSizing && gap.IsPercent())
available_size = direction == kForColumns
? AvailableLogicalWidth()
: AvailableLogicalHeightForPercentageComputation();
// TODO(rego): Maybe we could cache the computed percentage as a performance
// improvement.
return ValueForLength(gap, available_size);
}
LayoutUnit LayoutGrid::GuttersSize(const Grid& grid,
GridTrackSizingDirection direction,
size_t start_line,
size_t span,
SizingOperation sizing_operation) const {
if (span <= 1)
return LayoutUnit();
LayoutUnit gap = GridGapForDirection(direction, sizing_operation);
// Fast path, no collapsing tracks.
if (!grid.HasAutoRepeatEmptyTracks(direction))
return gap * (span - 1);
// If there are collapsing tracks we need to be sure that gutters are properly
// collapsed. Apart from that, if we have a collapsed track in the edges of
// the span we're considering, we need to move forward (or backwards) in order
// to know whether the collapsed tracks reach the end of the grid (so the gap
// becomes 0) or there is a non empty track before that.
LayoutUnit gap_accumulator;
size_t end_line = start_line + span;
for (size_t line = start_line; line < end_line - 1; ++line) {
if (!grid.IsEmptyAutoRepeatTrack(direction, line))
gap_accumulator += gap;
}
// The above loop adds one extra gap for trailing collapsed tracks.
if (gap_accumulator && grid.IsEmptyAutoRepeatTrack(direction, end_line - 1)) {
DCHECK_GE(gap_accumulator, gap);
gap_accumulator -= gap;
}
// If the startLine is the start line of a collapsed track we need to go
// backwards till we reach a non collapsed track. If we find a non collapsed
// track we need to add that gap.
if (start_line && grid.IsEmptyAutoRepeatTrack(direction, start_line)) {
size_t non_empty_tracks_before_start_line = start_line;
auto begin = grid.AutoRepeatEmptyTracks(direction)->begin();
for (auto it = begin; *it != start_line; ++it) {
DCHECK(non_empty_tracks_before_start_line);
--non_empty_tracks_before_start_line;
}
if (non_empty_tracks_before_start_line)
gap_accumulator += gap;
}
// If the endLine is the end line of a collapsed track we need to go forward
// till we reach a non collapsed track. If we find a non collapsed track we
// need to add that gap.
if (grid.IsEmptyAutoRepeatTrack(direction, end_line - 1)) {
size_t non_empty_tracks_after_end_line =
grid.NumTracks(direction) - end_line;
auto current_empty_track =
grid.AutoRepeatEmptyTracks(direction)->Find(end_line - 1);
auto end_empty_track = 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 = ++current_empty_track; it != end_empty_track; ++it) {
DCHECK(non_empty_tracks_after_end_line);
--non_empty_tracks_after_end_line;
}
if (non_empty_tracks_after_end_line)
gap_accumulator += gap;
}
return gap_accumulator;
}
void LayoutGrid::ComputeIntrinsicLogicalWidths(
LayoutUnit& min_logical_width,
LayoutUnit& max_logical_width) const {
Grid grid(this);
PlaceItemsOnGrid(grid, kIntrinsicSizeComputation);
GridTrackSizingAlgorithm algorithm(this, grid);
ComputeTrackSizesForIndefiniteSize(algorithm, kForColumns, grid,
min_logical_width, max_logical_width);
LayoutUnit scrollbar_width = LayoutUnit(ScrollbarLogicalWidth());
min_logical_width += scrollbar_width;
max_logical_width += scrollbar_width;
}
void LayoutGrid::ComputeTrackSizesForIndefiniteSize(
GridTrackSizingAlgorithm& algo,
GridTrackSizingDirection direction,
Grid& grid,
LayoutUnit& min_intrinsic_size,
LayoutUnit& max_intrinsic_size) const {
algo.Setup(direction, NumTracks(direction, grid), kIntrinsicSizeComputation,
WTF::kNullopt, WTF::kNullopt);
algo.Run();
min_intrinsic_size = algo.MinContentSize();
max_intrinsic_size = algo.MaxContentSize();
size_t number_of_tracks = algo.Tracks(direction).size();
LayoutUnit total_gutters_size = GuttersSize(
grid, direction, 0, number_of_tracks, kIntrinsicSizeComputation);
min_intrinsic_size += total_gutters_size;
max_intrinsic_size += total_gutters_size;
#if DCHECK_IS_ON()
DCHECK(algo.TracksAreWiderThanMinTrackBreadth());
#endif
}
LayoutUnit LayoutGrid::ComputeIntrinsicLogicalContentHeightUsing(
const Length& logical_height_length,
LayoutUnit intrinsic_content_height,
LayoutUnit border_and_padding) const {
if (logical_height_length.IsMinContent())
return min_content_height_;
if (logical_height_length.IsMaxContent())
return max_content_height_;
if (logical_height_length.IsFitContent()) {
if (min_content_height_ == -1 || max_content_height_ == -1)
return LayoutUnit(-1);
LayoutUnit fill_available_extent =
ContainingBlock()->AvailableLogicalHeight(kExcludeMarginBorderPadding);
return std::min<LayoutUnit>(
max_content_height_,
std::max(min_content_height_, fill_available_extent));
}
if (logical_height_length.IsFillAvailable())
return ContainingBlock()->AvailableLogicalHeight(
kExcludeMarginBorderPadding) -
border_and_padding;
NOTREACHED();
return LayoutUnit();
}
LayoutUnit LayoutGrid::OverrideContainingBlockContentSizeForChild(
const LayoutBox& child,
GridTrackSizingDirection direction) {
return direction == kForColumns
? child.OverrideContainingBlockContentLogicalWidth()
: child.OverrideContainingBlockContentLogicalHeight();
}
bool LayoutGrid::IsOrthogonalChild(const LayoutBox& child) const {
return child.IsHorizontalWritingMode() != IsHorizontalWritingMode();
}
size_t LayoutGrid::ComputeAutoRepeatTracksCount(
GridTrackSizingDirection direction,
SizingOperation sizing_operation) const {
bool is_row_axis = direction == kForColumns;
const auto& auto_repeat_tracks = is_row_axis
? StyleRef().GridAutoRepeatColumns()
: StyleRef().GridAutoRepeatRows();
size_t auto_repeat_track_list_length = auto_repeat_tracks.size();
if (!auto_repeat_track_list_length)
return 0;
LayoutUnit available_size;
if (is_row_axis) {
available_size = sizing_operation == kIntrinsicSizeComputation
? LayoutUnit(-1)
: AvailableLogicalWidth();
} else {
available_size = AvailableLogicalHeightForPercentageComputation();
if (available_size == -1) {
const Length& max_length = StyleRef().LogicalMaxHeight();
if (!max_length.IsMaxSizeNone()) {
available_size = ConstrainContentBoxLogicalHeightByMinMax(
AvailableLogicalHeightUsing(max_length,
kExcludeMarginBorderPadding),
LayoutUnit(-1));
}
}
}
bool needs_to_fulfill_minimum_size = false;
bool indefinite_main_and_max_sizes = available_size == LayoutUnit(-1);
if (indefinite_main_and_max_sizes) {
const Length& min_size = is_row_axis ? StyleRef().LogicalMinWidth()
: StyleRef().LogicalMinHeight();
if (!min_size.IsSpecified())
return auto_repeat_track_list_length;
LayoutUnit containing_block_available_size =
is_row_axis ? ContainingBlockLogicalWidthForContent()
: ContainingBlockLogicalHeightForContent(
kExcludeMarginBorderPadding);
available_size = ValueForLength(min_size, containing_block_available_size);
needs_to_fulfill_minimum_size = true;
}
LayoutUnit auto_repeat_tracks_size;
for (auto auto_track_size : auto_repeat_tracks) {
DCHECK(auto_track_size.MinTrackBreadth().IsLength());
DCHECK(!auto_track_size.MinTrackBreadth().IsFlex());
bool has_definite_max_track_sizing_function =
auto_track_size.MaxTrackBreadth().IsLength() &&
!auto_track_size.MaxTrackBreadth().IsContentSized();
auto track_length = has_definite_max_track_sizing_function
? auto_track_size.MaxTrackBreadth().length()
: auto_track_size.MinTrackBreadth().length();
auto_repeat_tracks_size += ValueForLength(track_length, available_size);
}
// 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.
auto_repeat_tracks_size =
std::max<LayoutUnit>(LayoutUnit(1), auto_repeat_tracks_size);
// There will be always at least 1 auto-repeat track, so take it already into
// account when computing the total track size.
LayoutUnit tracks_size = auto_repeat_tracks_size;
const Vector<GridTrackSize>& track_sizes =
is_row_axis ? StyleRef().GridTemplateColumns()
: StyleRef().GridTemplateRows();
for (const auto& track : track_sizes) {
bool has_definite_max_track_breadth =
track.MaxTrackBreadth().IsLength() &&
!track.MaxTrackBreadth().IsContentSized();
DCHECK(has_definite_max_track_breadth ||
(track.MinTrackBreadth().IsLength() &&
!track.MinTrackBreadth().IsContentSized()));
tracks_size += ValueForLength(has_definite_max_track_breadth
? track.MaxTrackBreadth().length()
: track.MinTrackBreadth().length(),
available_size);
}
// 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 gap_size = GridGapForDirection(direction, sizing_operation);
tracks_size += gap_size * track_sizes.size();
LayoutUnit free_space = available_size - tracks_size;
if (free_space <= 0)
return auto_repeat_track_list_length;
size_t repetitions =
1 + (free_space / (auto_repeat_tracks_size + gap_size)).ToInt();
// 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 (needs_to_fulfill_minimum_size)
++repetitions;
return repetitions * auto_repeat_track_list_length;
}
std::unique_ptr<OrderedTrackIndexSet>
LayoutGrid::ComputeEmptyTracksForAutoRepeat(
Grid& grid,
GridTrackSizingDirection direction) const {
bool is_row_axis = direction == kForColumns;
if ((is_row_axis && StyleRef().GridAutoRepeatColumnsType() != kAutoFit) ||
(!is_row_axis && StyleRef().GridAutoRepeatRowsType() != kAutoFit))
return nullptr;
std::unique_ptr<OrderedTrackIndexSet> empty_track_indexes;
size_t insertion_point =
is_row_axis ? StyleRef().GridAutoRepeatColumnsInsertionPoint()
: StyleRef().GridAutoRepeatRowsInsertionPoint();
size_t first_auto_repeat_track =
insertion_point + std::abs(grid.SmallestTrackStart(direction));
size_t last_auto_repeat_track =
first_auto_repeat_track + grid.AutoRepeatTracks(direction);
if (!grid.HasGridItems()) {
empty_track_indexes = WTF::WrapUnique(new OrderedTrackIndexSet);
for (size_t track_index = first_auto_repeat_track;
track_index < last_auto_repeat_track; ++track_index)
empty_track_indexes->insert(track_index);
} else {
for (size_t track_index = first_auto_repeat_track;
track_index < last_auto_repeat_track; ++track_index) {
GridIterator iterator(grid, direction, track_index);
if (!iterator.NextGridItem()) {
if (!empty_track_indexes)
empty_track_indexes = WTF::WrapUnique(new OrderedTrackIndexSet);
empty_track_indexes->insert(track_index);
}
}
}
return empty_track_indexes;
}
size_t LayoutGrid::ClampAutoRepeatTracks(GridTrackSizingDirection direction,
size_t auto_repeat_tracks) const {
if (!auto_repeat_tracks)
return 0;
size_t insertion_point =
direction == kForColumns
? StyleRef().GridAutoRepeatColumnsInsertionPoint()
: StyleRef().GridAutoRepeatRowsInsertionPoint();
if (insertion_point == 0)
return std::min<size_t>(auto_repeat_tracks, kGridMaxTracks);
if (insertion_point >= kGridMaxTracks)
return 0;
return std::min(auto_repeat_tracks,
static_cast<size_t>(kGridMaxTracks) - insertion_point);
}
void LayoutGrid::PlaceItemsOnGrid(Grid& grid,
SizingOperation sizing_operation) const {
size_t auto_repeat_rows =
ComputeAutoRepeatTracksCount(kForRows, sizing_operation);
size_t auto_repeat_columns =
ComputeAutoRepeatTracksCount(kForColumns, sizing_operation);
auto_repeat_rows = ClampAutoRepeatTracks(kForRows, auto_repeat_rows);
auto_repeat_columns = ClampAutoRepeatTracks(kForColumns, auto_repeat_columns);
if (auto_repeat_rows != grid.AutoRepeatTracks(kForRows) ||
auto_repeat_columns != grid.AutoRepeatTracks(kForColumns)) {
grid.SetNeedsItemsPlacement(true);
grid.SetAutoRepeatTracks(auto_repeat_rows, auto_repeat_columns);
}
if (!grid.NeedsItemsPlacement())
return;
DCHECK(!grid.HasGridItems());
PopulateExplicitGridAndOrderIterator(grid);
Vector<LayoutBox*> auto_major_axis_auto_grid_items;
Vector<LayoutBox*> specified_major_axis_auto_grid_items;
#if DCHECK_IS_ON()
DCHECK(!grid.HasAnyGridItemPaintOrder());
#endif
DCHECK(!grid.HasAnyOrthogonalGridItem());
bool has_any_orthogonal_grid_item = false;
size_t child_index = 0;
for (LayoutBox* child = grid.GetOrderIterator().First(); child;
child = grid.GetOrderIterator().Next()) {
if (child->IsOutOfFlowPositioned())
continue;
has_any_orthogonal_grid_item =
has_any_orthogonal_grid_item || IsOrthogonalChild(*child);
grid.SetGridItemPaintOrder(*child, child_index++);
GridArea area = grid.GridItemArea(*child);
if (!area.rows.IsIndefinite())
area.rows.Translate(abs(grid.SmallestTrackStart(kForRows)));
if (!area.columns.IsIndefinite())
area.columns.Translate(abs(grid.SmallestTrackStart(kForColumns)));
if (area.rows.IsIndefinite() || area.columns.IsIndefinite()) {
grid.SetGridItemArea(*child, area);
GridSpan major_axis_positions =
(AutoPlacementMajorAxisDirection() == kForColumns) ? area.columns
: area.rows;
if (major_axis_positions.IsIndefinite())
auto_major_axis_auto_grid_items.push_back(child);
else
specified_major_axis_auto_grid_items.push_back(child);
continue;
}
grid.insert(*child, area);
}
grid.SetHasAnyOrthogonalGridItem(has_any_orthogonal_grid_item);
#if DCHECK_IS_ON()
if (grid.HasGridItems()) {
DCHECK_GE(grid.NumTracks(kForRows),
GridPositionsResolver::ExplicitGridRowCount(
*Style(), grid.AutoRepeatTracks(kForRows)));
DCHECK_GE(grid.NumTracks(kForColumns),
GridPositionsResolver::ExplicitGridColumnCount(
*Style(), grid.AutoRepeatTracks(kForColumns)));
}
#endif
PlaceSpecifiedMajorAxisItemsOnGrid(grid,
specified_major_axis_auto_grid_items);
PlaceAutoMajorAxisItemsOnGrid(grid, auto_major_axis_auto_grid_items);
// Compute collapsable tracks for auto-fit.
grid.SetAutoRepeatEmptyColumns(
ComputeEmptyTracksForAutoRepeat(grid, kForColumns));
grid.SetAutoRepeatEmptyRows(ComputeEmptyTracksForAutoRepeat(grid, kForRows));
grid.SetNeedsItemsPlacement(false);
#if DCHECK_IS_ON()
for (LayoutBox* child = grid.GetOrderIterator().First(); child;
child = grid.GetOrderIterator().Next()) {
if (child->IsOutOfFlowPositioned())
continue;
GridArea area = grid.GridItemArea(*child);
DCHECK(area.rows.IsTranslatedDefinite());
DCHECK(area.columns.IsTranslatedDefinite());
}
#endif
}
void LayoutGrid::PopulateExplicitGridAndOrderIterator(Grid& grid) const {
OrderIteratorPopulator populator(grid.GetOrderIterator());
int smallest_row_start = 0;
int smallest_column_start = 0;
size_t auto_repeat_rows = grid.AutoRepeatTracks(kForRows);
size_t auto_repeat_columns = grid.AutoRepeatTracks(kForColumns);
size_t maximum_row_index =
GridPositionsResolver::ExplicitGridRowCount(*Style(), auto_repeat_rows);
size_t maximum_column_index = GridPositionsResolver::ExplicitGridColumnCount(
*Style(), auto_repeat_columns);
for (LayoutBox* child = FirstInFlowChildBox(); child;
child = child->NextInFlowSiblingBox()) {
populator.CollectChild(child);
// This function bypasses the cache (gridItemArea()) as it is used to
// build it.
GridSpan row_positions =
GridPositionsResolver::ResolveGridPositionsFromStyle(
*Style(), *child, kForRows, auto_repeat_rows);
GridSpan column_positions =
GridPositionsResolver::ResolveGridPositionsFromStyle(
*Style(), *child, kForColumns, auto_repeat_columns);
grid.SetGridItemArea(*child, GridArea(row_positions, column_positions));
// |positions| is 0 if we need to run the auto-placement algorithm.
if (!row_positions.IsIndefinite()) {
smallest_row_start =
std::min(smallest_row_start, row_positions.UntranslatedStartLine());
maximum_row_index =
std::max<int>(maximum_row_index, row_positions.UntranslatedEndLine());
} else {
// Grow the grid for items with a definite row span, getting the largest
// such span.
size_t span_size = GridPositionsResolver::SpanSizeForAutoPlacedItem(
*Style(), *child, kForRows);
maximum_row_index = std::max(maximum_row_index, span_size);
}
if (!column_positions.IsIndefinite()) {
smallest_column_start = std::min(
smallest_column_start, column_positions.UntranslatedStartLine());
maximum_column_index = std::max<int>(
maximum_column_index, column_positions.UntranslatedEndLine());
} else {
// Grow the grid for items with a definite column span, getting the
// largest such span.
size_t span_size = GridPositionsResolver::SpanSizeForAutoPlacedItem(
*Style(), *child, kForColumns);
maximum_column_index = std::max(maximum_column_index, span_size);
}
}
grid.SetSmallestTracksStart(smallest_row_start, smallest_column_start);
grid.EnsureGridSize(maximum_row_index + abs(smallest_row_start),
maximum_column_index + abs(smallest_column_start));
}
std::unique_ptr<GridArea>
LayoutGrid::CreateEmptyGridAreaAtSpecifiedPositionsOutsideGrid(
const Grid& grid,
const LayoutBox& grid_item,
GridTrackSizingDirection specified_direction,
const GridSpan& specified_positions) const {
GridTrackSizingDirection cross_direction =
specified_direction == kForColumns ? kForRows : kForColumns;
const size_t end_of_cross_direction = grid.NumTracks(cross_direction);
size_t cross_direction_span_size =
GridPositionsResolver::SpanSizeForAutoPlacedItem(*Style(), grid_item,
cross_direction);
GridSpan cross_direction_positions = GridSpan::TranslatedDefiniteGridSpan(
end_of_cross_direction,
end_of_cross_direction + cross_direction_span_size);
return WTF::WrapUnique(new GridArea(
specified_direction == kForColumns ? cross_direction_positions
: specified_positions,
specified_direction == kForColumns ? specified_positions
: cross_direction_positions));
}
void LayoutGrid::PlaceSpecifiedMajorAxisItemsOnGrid(
Grid& grid,
const Vector<LayoutBox*>& auto_grid_items) const {
bool is_for_columns = AutoPlacementMajorAxisDirection() == kForColumns;
bool is_grid_auto_flow_dense = 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-algo
HashMap<unsigned, unsigned, DefaultHash<unsigned>::Hash,
WTF::UnsignedWithZeroKeyHashTraits<unsigned>>
minor_axis_cursors;
for (const auto& auto_grid_item : auto_grid_items) {
GridSpan major_axis_positions =
grid.GridItemSpan(*auto_grid_item, AutoPlacementMajorAxisDirection());
DCHECK(major_axis_positions.IsTranslatedDefinite());
DCHECK(
!grid.GridItemSpan(*auto_grid_item, AutoPlacementMinorAxisDirection())
.IsTranslatedDefinite());
size_t minor_axis_span_size =
GridPositionsResolver::SpanSizeForAutoPlacedItem(
*Style(), *auto_grid_item, AutoPlacementMinorAxisDirection());
unsigned major_axis_initial_position = major_axis_positions.StartLine();
GridIterator iterator(
grid, AutoPlacementMajorAxisDirection(),
major_axis_positions.StartLine(),
is_grid_auto_flow_dense
? 0
: minor_axis_cursors.at(major_axis_initial_position));
std::unique_ptr<GridArea> empty_grid_area = iterator.NextEmptyGridArea(
major_axis_positions.IntegerSpan(), minor_axis_span_size);
if (!empty_grid_area) {
empty_grid_area = CreateEmptyGridAreaAtSpecifiedPositionsOutsideGrid(
grid, *auto_grid_item, AutoPlacementMajorAxisDirection(),
major_axis_positions);
}
grid.insert(*auto_grid_item, *empty_grid_area);
if (!is_grid_auto_flow_dense)
minor_axis_cursors.Set(major_axis_initial_position,
is_for_columns
? empty_grid_area->rows.StartLine()
: empty_grid_area->columns.StartLine());
}
}
void LayoutGrid::PlaceAutoMajorAxisItemsOnGrid(
Grid& grid,
const Vector<LayoutBox*>& auto_grid_items) const {
std::pair<size_t, size_t> auto_placement_cursor = std::make_pair(0, 0);
bool is_grid_auto_flow_dense = Style()->IsGridAutoFlowAlgorithmDense();
for (const auto& auto_grid_item : auto_grid_items) {
PlaceAutoMajorAxisItemOnGrid(grid, *auto_grid_item, auto_placement_cursor);
// If grid-auto-flow is dense, reset auto-placement cursor.
if (is_grid_auto_flow_dense) {
auto_placement_cursor.first = 0;
auto_placement_cursor.second = 0;
}
}
}
void LayoutGrid::PlaceAutoMajorAxisItemOnGrid(
Grid& grid,
LayoutBox& grid_item,
std::pair<size_t, size_t>& auto_placement_cursor) const {
GridSpan minor_axis_positions =
grid.GridItemSpan(grid_item, AutoPlacementMinorAxisDirection());
DCHECK(!grid.GridItemSpan(grid_item, AutoPlacementMajorAxisDirection())
.IsTranslatedDefinite());
size_t major_axis_span_size =
GridPositionsResolver::SpanSizeForAutoPlacedItem(
*Style(), grid_item, AutoPlacementMajorAxisDirection());
const size_t end_of_major_axis =
grid.NumTracks(AutoPlacementMajorAxisDirection());
size_t major_axis_auto_placement_cursor =
AutoPlacementMajorAxisDirection() == kForColumns
? auto_placement_cursor.second
: auto_placement_cursor.first;
size_t minor_axis_auto_placement_cursor =
AutoPlacementMajorAxisDirection() == kForColumns
? auto_placement_cursor.first
: auto_placement_cursor.second;
std::unique_ptr<GridArea> empty_grid_area;
if (minor_axis_positions.IsTranslatedDefinite()) {
// Move to the next track in major axis if initial position in minor axis is
// before auto-placement cursor.
if (minor_axis_positions.StartLine() < minor_axis_auto_placement_cursor)
major_axis_auto_placement_cursor++;
if (major_axis_auto_placement_cursor < end_of_major_axis) {
GridIterator iterator(grid, AutoPlacementMinorAxisDirection(),
minor_axis_positions.StartLine(),
major_axis_auto_placement_cursor);
empty_grid_area = iterator.NextEmptyGridArea(
minor_axis_positions.IntegerSpan(), major_axis_span_size);
}
if (!empty_grid_area) {
empty_grid_area = CreateEmptyGridAreaAtSpecifiedPositionsOutsideGrid(
grid, grid_item, AutoPlacementMinorAxisDirection(),
minor_axis_positions);
}
} else {
size_t minor_axis_span_size =
GridPositionsResolver::SpanSizeForAutoPlacedItem(
*Style(), grid_item, AutoPlacementMinorAxisDirection());
for (size_t major_axis_index = major_axis_auto_placement_cursor;
major_axis_index < end_of_major_axis; ++major_axis_index) {
GridIterator iterator(grid, AutoPlacementMajorAxisDirection(),
major_axis_index, minor_axis_auto_placement_cursor);
empty_grid_area = iterator.NextEmptyGridArea(major_axis_span_size,
minor_axis_span_size);
if (empty_grid_area) {
// Check that it fits in the minor axis direction, as we shouldn't grow
// in that direction here (it was already managed in
// populateExplicitGridAndOrderIterator()).
size_t minor_axis_final_position_index =
AutoPlacementMinorAxisDirection() == kForColumns
? empty_grid_area->columns.EndLine()
: empty_grid_area->rows.EndLine();
const size_t end_of_minor_axis =
grid.NumTracks(AutoPlacementMinorAxisDirection());
if (minor_axis_final_position_index <= end_of_minor_axis)
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.
empty_grid_area = nullptr;
}
// As we're moving to the next track in the major axis we should reset the
// auto-placement cursor in the minor axis.
minor_axis_auto_placement_cursor = 0;
}
if (!empty_grid_area)
empty_grid_area = CreateEmptyGridAreaAtSpecifiedPositionsOutsideGrid(
grid, grid_item, AutoPlacementMinorAxisDirection(),
GridSpan::TranslatedDefiniteGridSpan(0, minor_axis_span_size));
}
grid.insert(grid_item, *empty_grid_area);
// Move auto-placement cursor to the new position.
auto_placement_cursor.first = empty_grid_area->rows.StartLine();
auto_placement_cursor.second = empty_grid_area->columns.StartLine();
}
GridTrackSizingDirection LayoutGrid::AutoPlacementMajorAxisDirection() const {
return Style()->IsGridAutoFlowDirectionColumn() ? kForColumns : kForRows;
}
GridTrackSizingDirection LayoutGrid::AutoPlacementMinorAxisDirection() const {
return Style()->IsGridAutoFlowDirectionColumn() ? kForRows : kForColumns;
}
void LayoutGrid::DirtyGrid() {
if (grid_.NeedsItemsPlacement())
return;
grid_.SetNeedsItemsPlacement(true);
grid_items_overflowing_grid_area_.Resize(0);
}
Vector<LayoutUnit> LayoutGrid::TrackSizesForComputedStyle(
GridTrackSizingDirection direction) const {
bool is_row_axis = direction == kForColumns;
auto& positions = is_row_axis ? column_positions_ : row_positions_;
size_t num_positions = positions.size();
LayoutUnit offset_between_tracks =
is_row_axis ? offset_between_columns_ : offset_between_rows_;
Vector<LayoutUnit> tracks;
if (num_positions < 2)
return tracks;
DCHECK(!grid_.NeedsItemsPlacement());
bool has_collapsed_tracks = grid_.HasAutoRepeatEmptyTracks(direction);
LayoutUnit gap = !has_collapsed_tracks
? GridGapForDirection(direction, kTrackSizing)
: LayoutUnit();
tracks.ReserveCapacity(num_positions - 1);
for (size_t i = 0; i < num_positions - 2; ++i)
tracks.push_back(positions[i + 1] - positions[i] - offset_between_tracks -
gap);
tracks.push_back(positions[num_positions - 1] - positions[num_positions - 2]);
if (!has_collapsed_tracks)
return tracks;
size_t remaining_empty_tracks =
grid_.AutoRepeatEmptyTracks(direction)->size();
size_t last_line = tracks.size();
gap = GridGapForDirection(direction, kTrackSizing);
for (size_t i = 1; i < last_line; ++i) {
if (grid_.IsEmptyAutoRepeatTrack(direction, i - 1)) {
--remaining_empty_tracks;
} 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 all_remaining_tracks_are_empty =
remaining_empty_tracks == (last_line - i);
if (!all_remaining_tracks_are_empty ||
!grid_.IsEmptyAutoRepeatTrack(direction, i))
tracks[i - 1] -= gap;
}
}
return tracks;
}
const StyleContentAlignmentData& LayoutGrid::ContentAlignmentNormalBehavior() {
static const StyleContentAlignmentData kNormalBehavior = {
kContentPositionNormal, kContentDistributionStretch};
return kNormalBehavior;
}
void LayoutGrid::ApplyStretchAlignmentToTracksIfNeeded(
GridTrackSizingDirection direction) {
Optional<LayoutUnit> free_space =
track_sizing_algorithm_.FreeSpace(direction);
if (!free_space || free_space.value() <= 0 ||
(direction == kForColumns &&
StyleRef().ResolvedJustifyContentDistribution(
ContentAlignmentNormalBehavior()) != kContentDistributionStretch) ||
(direction == kForRows &&
StyleRef().ResolvedAlignContentDistribution(
ContentAlignmentNormalBehavior()) != kContentDistributionStretch))
return;
// Spec defines auto-sized tracks as the ones with an 'auto' max-sizing
// function.
Vector<GridTrack>& all_tracks = track_sizing_algorithm_.Tracks(direction);
Vector<unsigned> auto_sized_tracks_index;
for (unsigned i = 0; i < all_tracks.size(); ++i) {
const GridTrackSize& track_size =
track_sizing_algorithm_.GetGridTrackSize(direction, i, kTrackSizing);
if (track_size.HasAutoMaxTrackBreadth())
auto_sized_tracks_index.push_back(i);
}
unsigned number_of_auto_sized_tracks = auto_sized_tracks_index.size();
if (number_of_auto_sized_tracks < 1)
return;
LayoutUnit size_to_increase =
free_space.value() / number_of_auto_sized_tracks;
for (const auto& track_index : auto_sized_tracks_index) {
GridTrack* track = all_tracks.Data() + track_index;
LayoutUnit base_size = track->BaseSize() + size_to_increase;
track->SetBaseSize(base_size);
}
track_sizing_algorithm_.SetFreeSpace(direction, LayoutUnit());
}
void LayoutGrid::LayoutGridItems() {
PopulateGridPositionsForDirection(kForColumns);
PopulateGridPositionsForDirection(kForRows);
grid_items_overflowing_grid_area_.Resize(0);
for (LayoutBox* 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 old_override_containing_block_content_logical_width =
child->HasOverrideContainingBlockLogicalWidth()
? child->OverrideContainingBlockContentLogicalWidth()
: LayoutUnit();
LayoutUnit old_override_containing_block_content_logical_height =
child->HasOverrideContainingBlockLogicalHeight()
? child->OverrideContainingBlockContentLogicalHeight()
: LayoutUnit();
LayoutUnit override_containing_block_content_logical_width =
GridAreaBreadthForChildIncludingAlignmentOffsets(*child, kForColumns);
LayoutUnit override_containing_block_content_logical_height =
GridAreaBreadthForChildIncludingAlignmentOffsets(*child, kForRows);
if (old_override_containing_block_content_logical_width !=
override_containing_block_content_logical_width ||
(old_override_containing_block_content_logical_height !=
override_containing_block_content_logical_height &&
child->HasRelativeLogicalHeight()))
child->SetNeedsLayout(LayoutInvalidationReason::kGridChanged);
child->SetOverrideContainingBlockContentLogicalWidth(
override_containing_block_content_logical_width);
child->SetOverrideContainingBlockContentLogicalHeight(
override_containing_block_content_logical_height);
// 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();
// We need pending layouts to be done in order to compute auto-margins
// properly.
UpdateAutoMarginsInColumnAxisIfNeeded(*child);
UpdateAutoMarginsInRowAxisIfNeeded(*child);
const GridArea& area = grid_.GridItemArea(*child);
#if DCHECK_IS_ON()
DCHECK_LT(area.columns.StartLine(),
track_sizing_algorithm_.Tracks(kForColumns).size());
DCHECK_LT(area.rows.StartLine(),
track_sizing_algorithm_.Tracks(kForRows).size());
#endif
child->SetLogicalLocation(FindChildLogicalPosition(*child));
// Keep track of children overflowing their grid area as we might need to
// paint them even if the grid-area is not visible. Using physical
// dimensions for simplicity, so we can forget about orthogonalty.
LayoutUnit child_grid_area_height =
IsHorizontalWritingMode()
? override_containing_block_content_logical_height
: override_containing_block_content_logical_width;
LayoutUnit child_grid_area_width =
IsHorizontalWritingMode()
? override_containing_block_content_logical_width
: override_containing_block_content_logical_height;
LayoutRect grid_area_rect(
GridAreaLogicalPosition(area),
LayoutSize(child_grid_area_width, child_grid_area_height));
if (!grid_area_rect.Contains(child->FrameRect()))
grid_items_overflowing_grid_area_.push_back(child);
}
}
void LayoutGrid::PrepareChildForPositionedLayout(LayoutBox& child) {
DCHECK(child.IsOutOfFlowPositioned());
child.ContainingBlock()->InsertPositionedObject(&child);
PaintLayer* child_layer = child.Layer();
child_layer->SetStaticInlinePosition(LayoutUnit(BorderStart()));
child_layer->SetStaticBlockPosition(LayoutUnit(BorderBefore()));
}
void LayoutGrid::LayoutPositionedObjects(bool relayout_children,
PositionedLayoutBehavior info) {
TrackedLayoutBoxListHashSet* positioned_descendants = PositionedObjects();
if (!positioned_descendants)
return;
for (auto* child : *positioned_descendants) {
if (IsOrthogonalChild(*child)) {
// FIXME: Properly support orthogonal writing mode.
LayoutPositionedObject(child, relayout_children, info);
continue;
}
LayoutUnit column_offset = LayoutUnit();
LayoutUnit column_breadth = LayoutUnit();
OffsetAndBreadthForPositionedChild(*child, kForColumns, column_offset,
column_breadth);
LayoutUnit row_offset = LayoutUnit();
LayoutUnit row_breadth = LayoutUnit();
OffsetAndBreadthForPositionedChild(*child, kForRows, row_offset,
row_breadth);
child->SetOverrideContainingBlockContentLogicalWidth(column_breadth);
child->SetOverrideContainingBlockContentLogicalHeight(row_breadth);
child->SetExtraInlineOffset(column_offset);
child->SetExtraBlockOffset(row_offset);
if (child->Parent() == this) {
PaintLayer* child_layer = child->Layer();
child_layer->SetStaticInlinePosition(BorderStart() + column_offset);
child_layer->SetStaticBlockPosition(BorderBefore() + row_offset);
}
LayoutPositionedObject(child, relayout_children, info);
}
}
void LayoutGrid::OffsetAndBreadthForPositionedChild(
const LayoutBox& child,
GridTrackSizingDirection direction,
LayoutUnit& offset,
LayoutUnit& breadth) {
DCHECK(!IsOrthogonalChild(child));
bool is_for_columns = direction == kForColumns;
GridSpan positions = GridPositionsResolver::ResolveGridPositionsFromStyle(
*Style(), child, direction, AutoRepeatCountForDirection(direction));
if (positions.IsIndefinite()) {
offset = LayoutUnit();
breadth = is_for_columns ? 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 smallest_start = abs(grid_.SmallestTrackStart(direction));
int start_line = positions.UntranslatedStartLine() + smallest_start;
int end_line = positions.UntranslatedEndLine() + smallest_start;
GridPosition start_position = is_for_columns
? child.Style()->GridColumnStart()
: child.Style()->GridRowStart();
GridPosition end_position = is_for_columns ? child.Style()->GridColumnEnd()
: child.Style()->GridRowEnd();
int last_line = NumTracks(direction, grid_);
bool start_is_auto =
start_position.IsAuto() ||
(start_position.IsNamedGridArea() &&
!NamedLineCollection::IsValidNamedLineOrArea(
start_position.NamedGridLine(), StyleRef(),
GridPositionsResolver::InitialPositionSide(direction))) ||
(start_line < 0) || (start_line > last_line);
bool end_is_auto =
end_position.IsAuto() ||
(end_position.IsNamedGridArea() &&
!NamedLineCollection::IsValidNamedLineOrArea(
end_position.NamedGridLine(), StyleRef(),
GridPositionsResolver::FinalPositionSide(direction))) ||
(end_line < 0) || (end_line > last_line);
LayoutUnit start;
if (!start_is_auto) {
if (is_for_columns) {
if (StyleRef().IsLeftToRightDirection())
start = column_positions_[start_line] - BorderLogicalLeft();
else
start = LogicalWidth() -
TranslateRTLCoordinate(column_positions_[start_line]) -
BorderLogicalRight();
} else {
start = row_positions_[start_line] - BorderBefore();
}
}
LayoutUnit end =
is_for_columns ? ClientLogicalWidth() : ClientLogicalHeight();
if (!end_is_auto) {
if (is_for_columns) {
if (StyleRef().IsLeftToRightDirection())
end = column_positions_[end_line] - BorderLogicalLeft();
else
end = LogicalWidth() -
TranslateRTLCoordinate(column_positions_[end_line]) -
BorderLogicalRight();
} else {
end = row_positions_[end_line] - BorderBefore();
}
// These vectors store line positions including gaps, but we shouldn't
// consider them for the edges of the grid.
if (end_line > 0 && end_line < last_line) {
DCHECK(!grid_.NeedsItemsPlacement());
end -= GuttersSize(grid_, direction, end_line - 1, 2, kTrackSizing);
end -= is_for_columns ? offset_between_columns_ : offset_between_rows_;
}
}
breadth = std::max(end - start, LayoutUnit());
offset = start;
if (is_for_columns && !StyleRef().IsLeftToRightDirection() &&
!child.StyleRef().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 (end_is_auto) {
offset = LayoutUnit();
} else {
offset = TranslateRTLCoordinate(column_positions_[end_line]) -
BorderLogicalLeft();
if (end_line > 0 && end_line < last_line) {
DCHECK(!grid_.NeedsItemsPlacement());
offset += GuttersSize(grid_, direction, end_line - 1, 2, kTrackSizing);
offset +=
is_for_columns ? offset_between_columns_ : offset_between_rows_;
}
}
}
}
LayoutUnit LayoutGrid::GridAreaBreadthForChildIncludingAlignmentOffsets(
const LayoutBox& child,
GridTrackSizingDirection direction) const {
// We need the cached value when available because Content Distribution
// alignment properties may have some influence in the final grid area
// breadth.
const Vector<GridTrack>& tracks = track_sizing_algorithm_.Tracks(direction);
const GridSpan& span =
track_sizing_algorithm_.GetGrid().GridItemSpan(child, direction);
const Vector<LayoutUnit>& line_positions =
(direction == kForColumns) ? column_positions_ : row_positions_;
LayoutUnit initial_track_position = line_positions[span.StartLine()];
LayoutUnit final_track_position = line_positions[span.EndLine() - 1];
// Track Positions vector stores the 'start' grid line of each track, so we
// have to add last track's baseSize.
return final_track_position - initial_track_position +
tracks[span.EndLine() - 1].BaseSize();
}
void LayoutGrid::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 is_row_axis = direction == kForColumns;
auto& tracks = track_sizing_algorithm_.Tracks(direction);
size_t number_of_tracks = tracks.size();
size_t number_of_lines = number_of_tracks + 1;
size_t last_line = number_of_lines - 1;
ContentAlignmentData offset = ComputeContentPositionAndDistributionOffset(
direction, track_sizing_algorithm_.FreeSpace(direction).value(),
number_of_tracks);
auto& positions = is_row_axis ? column_positions_ : row_positions_;
positions.Resize(number_of_lines);
auto border_and_padding =
is_row_axis ? BorderAndPaddingLogicalLeft() : BorderAndPaddingBefore();
positions[0] = border_and_padding + offset.position_offset;
const Grid& grid = track_sizing_algorithm_.GetGrid();
if (number_of_lines > 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 has_collapsed_tracks = grid.HasAutoRepeatEmptyTracks(direction);
LayoutUnit gap = !has_collapsed_tracks
? GridGapForDirection(direction, kTrackSizing)
: LayoutUnit();
size_t next_to_last_line = number_of_lines - 2;
for (size_t i = 0; i < next_to_last_line; ++i)
positions[i + 1] = positions[i] + offset.distribution_offset +
tracks[i].BaseSize() + gap;
positions[last_line] =
positions[next_to_last_line] + tracks[next_to_last_line].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 (has_collapsed_tracks) {
gap = GridGapForDirection(direction, kTrackSizing);
size_t remaining_empty_tracks =
grid.AutoRepeatEmptyTracks(direction)->size();
LayoutUnit gap_accumulator;
for (size_t i = 1; i < last_line; ++i) {
if (grid.IsEmptyAutoRepeatTrack(direction, i - 1)) {
--remaining_empty_tracks;
} 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 all_remaining_tracks_are_empty =
remaining_empty_tracks == (last_line - i);
if (!all_remaining_tracks_are_empty ||
!grid.IsEmptyAutoRepeatTrack(direction, i))
gap_accumulator += gap;
}
positions[i] += gap_accumulator;
}
positions[last_line] += gap_accumulator;
}
}
auto& offset_between_tracks =
is_row_axis ? offset_between_columns_ : offset_between_rows_;
offset_between_tracks = offset.distribution_offset;
}
static LayoutUnit ComputeOverflowAlignmentOffset(OverflowAlignment overflow,
LayoutUnit track_size,
LayoutUnit child_size) {
LayoutUnit offset = track_size - child_size;
switch (overflow) {
case kOverflowAlignmentSafe:
// 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 offset.ClampNegativeToZero();
case kOverflowAlignmentUnsafe:
case kOverflowAlignmentDefault:
// 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;
}
NOTREACHED();
return LayoutUnit();
}
// FIXME: This logic is shared by LayoutFlexibleBox, so it should be moved to
// LayoutBox.
LayoutUnit LayoutGrid::MarginLogicalHeightForChild(
const LayoutBox& child) const {
return IsHorizontalWritingMode() ? child.MarginHeight() : child.MarginWidth();
}
LayoutUnit LayoutGrid::ComputeMarginLogicalSizeForChild(
MarginDirection for_direction,
const LayoutBox& child) const {
if (!child.StyleRef().HasMargin())
return LayoutUnit();
bool is_row_axis = for_direction == kInlineDirection;
LayoutUnit margin_start;
LayoutUnit margin_end;
LayoutUnit logical_size =
is_row_axis ? child.LogicalWidth() : child.LogicalHeight();
Length margin_start_length = is_row_axis ? child.StyleRef().MarginStart()
: child.StyleRef().MarginBefore();
Length margin_end_length = is_row_axis ? child.StyleRef().MarginEnd()
: child.StyleRef().MarginAfter();
child.ComputeMarginsForDirection(
for_direction, this, child.ContainingBlockLogicalWidthForContent(),
logical_size, margin_start, margin_end, margin_start_length,
margin_end_length);
return margin_start + margin_end;
}
LayoutUnit LayoutGrid::AvailableAlignmentSpaceForChildBeforeStretching(
LayoutUnit grid_area_breadth_for_child,
const LayoutBox& child) const {
// 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 grid_area_breadth_for_child -
(child.NeedsLayout()
? ComputeMarginLogicalSizeForChild(kBlockDirection, child)
: MarginLogicalHeightForChild(child));
}
StyleSelfAlignmentData LayoutGrid::AlignSelfForChild(
const LayoutBox& child) const {
if (!child.IsAnonymous()) {
return child.StyleRef().ResolvedAlignSelf(
SelfAlignmentNormalBehavior(&child));
}
// All the 'auto' values has been solved by the StyleAdjuster, but it's
// possible that some grid items generate Anonymous boxes, which need to be
// solved during layout.
return child.StyleRef().ResolvedAlignSelf(SelfAlignmentNormalBehavior(&child),
Style());
}
StyleSelfAlignmentData LayoutGrid::JustifySelfForChild(
const LayoutBox& child) const {
if (!child.IsAnonymous()) {
return child.StyleRef().ResolvedJustifySelf(
SelfAlignmentNormalBehavior(&child));
}
// All the 'auto' values has been solved by the StyleAdjuster, but it's
// possible that some grid items generate Anonymous boxes, which need to be
// solved during layout.
return child.StyleRef().ResolvedJustifySelf(
SelfAlignmentNormalBehavior(&child), Style());
}
GridTrackSizingDirection LayoutGrid::FlowAwareDirectionForChild(
const LayoutBox& child,
GridTrackSizingDirection direction) const {
return !IsOrthogonalChild(child)
? direction
: (direction == kForColumns ? kForRows : kForColumns);
}
// FIXME: This logic is shared by LayoutFlexibleBox, so it should be moved to
// LayoutBox.
void LayoutGrid::ApplyStretchAlignmentToChildIfNeeded(LayoutBox& child) {
// 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 child_block_direction =
FlowAwareDirectionForChild(child, kForRows);
bool block_flow_is_column_axis = child_block_direction == kForRows;
bool allowed_to_stretch_child_block_size =
block_flow_is_column_axis ? AllowedToStretchChildAlongColumnAxis(child)
: AllowedToStretchChildAlongRowAxis(child);
if (allowed_to_stretch_child_block_size) {
LayoutUnit stretched_logical_height =
AvailableAlignmentSpaceForChildBeforeStretching(
OverrideContainingBlockContentSizeForChild(child,
child_block_direction),
child);
LayoutUnit desired_logical_height = child.ConstrainLogicalHeightByMinMax(
stretched_logical_height, LayoutUnit(-1));
child.SetOverrideLogicalContentHeight(
(desired_logical_height - child.BorderAndPaddingLogicalHeight())
.ClampNegativeToZero());
if (desired_logical_height != child.LogicalHeight()) {
// TODO (lajava): Can avoid laying out here in some cases. See
// https://webkit.org/b/87905.
child.SetLogicalHeight(LayoutUnit());
child.SetNeedsLayout(LayoutInvalidationReason::kGridChanged);
}
}
}
// TODO(lajava): This logic is shared by LayoutFlexibleBox, so it should be
// moved to LayoutBox.
bool LayoutGrid::HasAutoMarginsInColumnAxis(const LayoutBox& child) const {
if (IsHorizontalWritingMode())
return child.StyleRef().MarginTop().IsAuto() ||
child.StyleRef().MarginBottom().IsAuto();
return child.StyleRef().MarginLeft().IsAuto() ||
child.StyleRef().MarginRight().IsAuto();
}
// TODO(lajava): This logic is shared by LayoutFlexibleBox, so it should be
// moved to LayoutBox.
bool LayoutGrid::HasAutoMarginsInRowAxis(const LayoutBox& child) const {
if (IsHorizontalWritingMode())
return child.StyleRef().MarginLeft().IsAuto() ||
child.StyleRef().MarginRight().IsAuto();
return child.StyleRef().MarginTop().IsAuto() ||
child.StyleRef().MarginBottom().IsAuto();
}
// TODO(lajava): This logic is shared by LayoutFlexibleBox, so it should be
// moved to LayoutBox.
DISABLE_CFI_PERF
void LayoutGrid::UpdateAutoMarginsInRowAxisIfNeeded(LayoutBox& child) {
DCHECK(!child.IsOutOfFlowPositioned());
LayoutUnit available_alignment_space =
child.OverrideContainingBlockContentLogicalWidth() -
child.LogicalWidth() - child.MarginLogicalWidth();
if (available_alignment_space <= 0)
return;
Length margin_start = child.Style()->MarginStartUsing(Style());
Length margin_end = child.Style()->MarginEndUsing(Style());
if (margin_start.IsAuto() && margin_end.IsAuto()) {
child.SetMarginStart(available_alignment_space / 2, Style());
child.SetMarginEnd(available_alignment_space / 2, Style());
} else if (margin_start.IsAuto()) {
child.SetMarginStart(available_alignment_space, Style());
} else if (margin_end.IsAuto()) {
child.SetMarginEnd(available_alignment_space, Style());
}
}
// TODO(lajava): This logic is shared by LayoutFlexibleBox, so it should be
// moved to LayoutBox.
DISABLE_CFI_PERF
void LayoutGrid::UpdateAutoMarginsInColumnAxisIfNeeded(LayoutBox& child) {
DCHECK(!child.IsOutOfFlowPositioned());
LayoutUnit available_alignment_space =
child.OverrideContainingBlockContentLogicalHeight() -
child.LogicalHeight() - child.MarginLogicalHeight();
if (available_alignment_space <= 0)
return;
Length margin_before = child.Style()->MarginBeforeUsing(Style());
Length margin_after = child.Style()->MarginAfterUsing(Style());
if (margin_before.IsAuto() && margin_after.IsAuto()) {
child.SetMarginBefore(available_alignment_space / 2, Style());
child.SetMarginAfter(available_alignment_space / 2, Style());
} else if (margin_before.IsAuto()) {
child.SetMarginBefore(available_alignment_space, Style());
} else if (margin_after.IsAuto()) {
child.SetMarginAfter(available_alignment_space, Style());
}
}
// TODO(lajava): This logic is shared by LayoutFlexibleBox, so it might be
// refactored somehow.
int LayoutGrid::SynthesizedBaselineFromContentBox(const LayoutBox& box,
LineDirectionMode direction) {
if (direction == kHorizontalLine) {
return (box.Size().Height() - box.BorderBottom() - box.PaddingBottom() -
box.HorizontalScrollbarHeight())
.ToInt();
}
return (box.Size().Width() - box.BorderLeft() - box.PaddingLeft() -
box.VerticalScrollbarWidth())
.ToInt();
}
int LayoutGrid::SynthesizedBaselineFromBorderBox(const LayoutBox& box,
LineDirectionMode direction) {
return (direction == kHorizontalLine ? box.Size().Height()
: box.Size().Width())
.ToInt();
}
int LayoutGrid::BaselinePosition(FontBaseline,
bool,
LineDirectionMode direction,
LinePositionMode mode) const {
DCHECK_EQ(mode, kPositionOnContainingLine);
int baseline = FirstLineBoxBaseline();
// We take content-box's bottom if no valid baseline.
if (baseline == -1)
baseline = SynthesizedBaselineFromContentBox(*this, direction);
return baseline + BeforeMarginInLineDirection(direction);
}
int LayoutGrid::FirstLineBoxBaseline() const {
if (IsWritingModeRoot() || !grid_.HasGridItems())
return -1;
const LayoutBox* baseline_child = nullptr;
const LayoutBox* first_child = nullptr;
bool is_baseline_aligned = false;
// Finding the first grid item in grid order.
for (size_t column = 0;
!is_baseline_aligned && column < grid_.NumTracks(kForColumns);
column++) {
for (size_t index = 0; index < grid_.Cell(0, column).size(); index++) {
const LayoutBox* child = grid_.Cell(0, column)[index];
DCHECK(!child->IsOutOfFlowPositioned());
// If an item participates in baseline alignment, we select such item.
if (IsBaselineAlignmentForChild(*child)) {
// TODO (lajava): self-baseline and content-baseline alignment
// still not implemented.
baseline_child = child;
is_baseline_aligned = true;
break;
}
if (!baseline_child) {
// Use dom order for items in the same cell.
if (!first_child || (grid_.GridItemPaintOrder(*child) <
grid_.GridItemPaintOrder(*first_child)))
first_child = child;
}
}
if (!baseline_child && first_child)
baseline_child = first_child;
}
if (!baseline_child)
return -1;
int baseline = IsOrthogonalChild(*baseline_child)
? -1
: baseline_child->FirstLineBoxBaseline();
// We take border-box's bottom if no valid baseline.
if (baseline == -1) {
// TODO (lajava): We should pass |direction| into
// firstLineBoxBaseline 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() ? kHorizontalLine : kVerticalLine;
return (SynthesizedBaselineFromBorderBox(*baseline_child, direction) +
baseline_child->LogicalTop())
.ToInt();
}
return (baseline + baseline_child->LogicalTop()).ToInt();
}
int LayoutGrid::InlineBlockBaseline(LineDirectionMode direction) const {
int baseline = FirstLineBoxBaseline();
if (baseline != -1)
return baseline;
int margin_height =
(direction == kHorizontalLine ? MarginTop() : MarginRight()).ToInt();
return SynthesizedBaselineFromContentBox(*this, direction) + margin_height;
}
bool LayoutGrid::IsHorizontalGridAxis(GridAxis axis) const {
return axis == kGridRowAxis ? IsHorizontalWritingMode()
: !IsHorizontalWritingMode();
}
bool LayoutGrid::IsParallelToBlockAxisForChild(const LayoutBox& child,
GridAxis axis) const {
return axis == kGridColumnAxis ? !IsOrthogonalChild(child)
: IsOrthogonalChild(child);
}
bool LayoutGrid::IsDescentBaselineForChild(const LayoutBox& child,
GridAxis baseline_axis) const {
return IsHorizontalGridAxis(baseline_axis) &&
((child.StyleRef().IsFlippedBlocksWritingMode() &&
!StyleRef().IsFlippedBlocksWritingMode()) ||
(child.StyleRef().IsFlippedLinesWritingMode() &&
StyleRef().IsFlippedBlocksWritingMode()));
}
bool LayoutGrid::IsBaselineAlignmentForChild(const LayoutBox& child,
GridAxis baseline_axis) const {
bool is_column_axis_baseline = baseline_axis == kGridColumnAxis;
ItemPosition align = is_column_axis_baseline
? AlignSelfForChild(child).GetPosition()
: JustifySelfForChild(child).GetPosition();
bool has_auto_margins = is_column_axis_baseline
? HasAutoMarginsInColumnAxis(child)
: HasAutoMarginsInRowAxis(child);
return IsBaselinePosition(align) && !has_auto_margins;
}
const BaselineGroup& LayoutGrid::GetBaselineGroupForChild(
const LayoutBox& child,
GridAxis baseline_axis) const {
DCHECK(IsBaselineAlignmentForChild(child, baseline_axis));
auto& grid = track_sizing_algorithm_.GetGrid();
bool is_column_axis_baseline = baseline_axis == kGridColumnAxis;
bool is_row_axis_context = is_column_axis_baseline;
const auto& span = is_row_axis_context
? grid.GridItemSpan(child, kForRows)
: grid.GridItemSpan(child, kForColumns);
auto& contexts_map = is_row_axis_context ? row_axis_alignment_context_
: col_axis_alignment_context_;
auto* context = contexts_map.at(span.StartLine());
DCHECK(context);
ItemPosition align = is_column_axis_baseline
? AlignSelfForChild(child).GetPosition()
: JustifySelfForChild(child).GetPosition();
return context->GetSharedGroup(child, align);
}
LayoutUnit LayoutGrid::MarginOverForChild(const LayoutBox& child,
GridAxis axis) const {
return IsHorizontalGridAxis(axis) ? child.MarginRight() : child.MarginTop();
}
LayoutUnit LayoutGrid::MarginUnderForChild(const LayoutBox& child,
GridAxis axis) const {
return IsHorizontalGridAxis(axis) ? child.MarginLeft() : child.MarginBottom();
}
LayoutUnit LayoutGrid::LogicalAscentForChild(const LayoutBox& child,
GridAxis baseline_axis) const {
LayoutUnit ascent = AscentForChild(child, baseline_axis);
return IsDescentBaselineForChild(child, baseline_axis)
? DescentForChild(child, ascent, baseline_axis)
: ascent;
}
LayoutUnit LayoutGrid::AscentForChild(const LayoutBox& child,
GridAxis baseline_axis) const {
LayoutUnit margin = IsDescentBaselineForChild(child, baseline_axis)
? MarginUnderForChild(child, baseline_axis)
: MarginOverForChild(child, baseline_axis);
int baseline = IsParallelToBlockAxisForChild(child, baseline_axis)
? child.FirstLineBoxBaseline()
: -1;
// We take border-box's under edge if no valid baseline.
if (baseline == -1) {
if (IsHorizontalGridAxis(baseline_axis)) {
return StyleRef().IsFlippedBlocksWritingMode()
? child.Size().Width().ToInt() + margin
: margin;
}
return child.Size().Height() + margin;
}
return LayoutUnit(baseline) + margin;
}
LayoutUnit LayoutGrid::DescentForChild(const LayoutBox& child,
LayoutUnit ascent,
GridAxis baseline_axis) const {
if (IsParallelToBlockAxisForChild(child, baseline_axis))
return child.MarginLogicalHeight() + child.LogicalHeight() - ascent;
return child.MarginLogicalWidth() + child.LogicalWidth() - ascent;
}
bool LayoutGrid::IsBaselineContextComputed(GridAxis baseline_axis) const {
return baseline_axis == kGridColumnAxis
? !row_axis_alignment_context_.IsEmpty()
: !col_axis_alignment_context_.IsEmpty();
}
bool LayoutGrid::BaselineMayAffectIntrinsicWidth() const {
if (!StyleRef().LogicalWidth().IsIntrinsicOrAuto())
return false;
for (const auto& context : col_axis_alignment_context_) {
for (const auto& group : context.value->SharedGroups()) {
if (group.size() > 1)
return true;
}
}
return false;
}
bool LayoutGrid::BaselineMayAffectIntrinsicHeight() const {
if (!StyleRef().LogicalHeight().IsIntrinsicOrAuto())
return false;
for (const auto& context : row_axis_alignment_context_) {
for (const auto& group : context.value->SharedGroups()) {
if (group.size() > 1)
return true;
}
}
return false;
}
void LayoutGrid::ComputeBaselineAlignmentContext() {
for (auto* child = FirstInFlowChildBox(); child;
child = child->NextInFlowSiblingBox()) {
UpdateBaselineAlignmentContextIfNeeded(*child, kGridRowAxis);
UpdateBaselineAlignmentContextIfNeeded(*child, kGridColumnAxis);
}
}
void LayoutGrid::UpdateBaselineAlignmentContextIfNeeded(
LayoutBox& child,
GridAxis baseline_axis) {
// TODO (lajava): We must ensure this method is not called as part of an
// intrinsic size computation.
if (!IsBaselineAlignmentForChild(child, baseline_axis))
return;
child.LayoutIfNeeded();
// Determine Ascent and Descent values of this child with respect to
// its grid container.
LayoutUnit ascent = AscentForChild(child, baseline_axis);
LayoutUnit descent = DescentForChild(child, ascent, baseline_axis);
if (IsDescentBaselineForChild(child, baseline_axis))
std::swap(ascent, descent);
// Looking up for a shared alignment context perpendicular to the
// baseline axis.
auto& grid = track_sizing_algorithm_.GetGrid();
bool is_column_axis_baseline = baseline_axis == kGridColumnAxis;
bool is_row_axis_context = is_column_axis_baseline;
const auto& span = is_row_axis_context
? grid.GridItemSpan(child, kForRows)
: grid.GridItemSpan(child, kForColumns);
auto& contexts_map = is_row_axis_context ? row_axis_alignment_context_
: col_axis_alignment_context_;
auto add_result = contexts_map.insert(span.StartLine(), nullptr);
// Looking for a compatible baseline-sharing group.
ItemPosition align = is_column_axis_baseline
? AlignSelfForChild(child).GetPosition()
: JustifySelfForChild(child).GetPosition();
if (add_result.is_new_entry) {
add_result.stored_value->value =
WTF::MakeUnique<BaselineContext>(child, align, ascent, descent);
} else {
auto* context = add_result.stored_value->value.get();
context->UpdateSharedGroup(child, align, ascent, descent);
}
}
LayoutUnit LayoutGrid::ColumnAxisBaselineOffsetForChild(
const LayoutBox& child) const {
if (!IsBaselineAlignmentForChild(child, kGridColumnAxis))
return LayoutUnit();
auto& group = GetBaselineGroupForChild(child, kGridColumnAxis);
if (group.size() > 1)
return group.MaxAscent() - LogicalAscentForChild(child, kGridColumnAxis);
return LayoutUnit();
}
LayoutUnit LayoutGrid::RowAxisBaselineOffsetForChild(
const LayoutBox& child) const {
if (!IsBaselineAlignmentForChild(child, kGridRowAxis))
return LayoutUnit();
auto& group = GetBaselineGroupForChild(child, kGridRowAxis);
if (group.size() > 1)
return group.MaxAscent() - LogicalAscentForChild(child, kGridRowAxis);
return LayoutUnit();
}
GridAxisPosition LayoutGrid::ColumnAxisPositionForChild(
const LayoutBox& child) const {
bool has_same_writing_mode =
child.StyleRef().GetWritingMode() == StyleRef().GetWritingMode();
bool child_is_ltr = child.StyleRef().IsLeftToRightDirection();
switch (AlignSelfForChild(child).GetPosition()) {
case kItemPositionSelfStart:
// TODO (lajava): 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 (StyleRef().IsFlippedBlocksWritingMode())
return child_is_ltr ? kGridAxisEnd : kGridAxisStart;
return child_is_ltr ? kGridAxisStart : kGridAxisEnd;
}
// 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 has_same_writing_mode ? kGridAxisStart : kGridAxisEnd;
case kItemPositionSelfEnd:
// TODO (lajava): 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 (StyleRef().IsFlippedBlocksWritingMode())
return child_is_ltr ? kGridAxisStart : kGridAxisEnd;
return child_is_ltr ? kGridAxisEnd : kGridAxisStart;
}
// 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 has_same_writing_mode ? kGridAxisEnd : kGridAxisStart;
case kItemPositionLeft:
// 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 kGridAxisStart;
case kItemPositionRight:
// 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 kGridAxisStart;
case kItemPositionCenter:
return kGridAxisCenter;
// Only used in flex layout, otherwise equivalent to 'start'.
case kItemPositionFlexStart:
// Aligns the alignment subject to be flush with the alignment container's
// 'start' edge (block-start) in the column axis.
case kItemPositionStart:
return kGridAxisStart;
// Only used in flex layout, otherwise equivalent to 'end'.
case kItemPositionFlexEnd:
// Aligns the alignment subject to be flush with the alignment container's
// 'end' edge (block-end) in the column axis.
case kItemPositionEnd:
return kGridAxisEnd;
case kItemPositionStretch:
return kGridAxisStart;
case kItemPositionBaseline:
case kItemPositionLastBaseline:
return kGridAxisStart;
case kItemPositionAuto:
case kItemPositionNormal:
break;
}
NOTREACHED();
return kGridAxisStart;
}
GridAxisPosition LayoutGrid::RowAxisPositionForChild(
const LayoutBox& child) const {
bool has_same_direction =
child.StyleRef().Direction() == StyleRef().Direction();
bool grid_is_ltr = StyleRef().IsLeftToRightDirection();
switch (JustifySelfForChild(child).GetPosition()) {
case kItemPositionSelfStart:
// TODO (lajava): 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.StyleRef().IsFlippedBlocksWritingMode())
return grid_is_ltr ? kGridAxisEnd : kGridAxisStart;
return grid_is_ltr ? kGridAxisStart : kGridAxisEnd;
}
// 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 has_same_direction ? kGridAxisStart : kGridAxisEnd;
case kItemPositionSelfEnd:
// TODO (lajava): 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.StyleRef().IsFlippedBlocksWritingMode())
return grid_is_ltr ? kGridAxisStart : kGridAxisEnd;
return grid_is_ltr ? kGridAxisEnd : kGridAxisStart;
}
// 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 has_same_direction ? kGridAxisEnd : kGridAxisStart;
case kItemPositionLeft:
// 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 grid_is_ltr ? kGridAxisStart : kGridAxisEnd;
case kItemPositionRight:
// 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 grid_is_ltr ? kGridAxisEnd : kGridAxisStart;
case kItemPositionCenter:
return kGridAxisCenter;
// Only used in flex layout, otherwise equivalent to 'start'.
case kItemPositionFlexStart:
// Aligns the alignment subject to be flush with the alignment container's
// 'start' edge (inline-start) in the row axis.
case kItemPositionStart:
return kGridAxisStart;
// Only used in flex layout, otherwise equivalent to 'end'.
case kItemPositionFlexEnd:
// Aligns the alignment subject to be flush with the alignment container's
// 'end' edge (inline-end) in the row axis.
case kItemPositionEnd:
return kGridAxisEnd;
case kItemPositionStretch:
return kGridAxisStart;
case kItemPositionBaseline:
case kItemPositionLastBaseline:
return kGridAxisStart;
case kItemPositionAuto:
case kItemPositionNormal:
break;
}
NOTREACHED();
return kGridAxisStart;
}
LayoutUnit LayoutGrid::ColumnAxisOffsetForChild(const LayoutBox& child) const {
const GridSpan& rows_span =
track_sizing_algorithm_.GetGrid().GridItemSpan(child, kForRows);
size_t child_start_line = rows_span.StartLine();
LayoutUnit start_of_row = row_positions_[child_start_line];
LayoutUnit start_position = start_of_row + MarginBeforeForChild(child);
if (HasAutoMarginsInColumnAxis(child))
return start_position;
GridAxisPosition axis_position = ColumnAxisPositionForChild(child);
switch (axis_position) {
case kGridAxisStart:
return start_position + ColumnAxisBaselineOffsetForChild(child);
case kGridAxisEnd:
case kGridAxisCenter: {
size_t child_end_line = rows_span.EndLine();
LayoutUnit end_of_row = row_positions_[child_end_line];
// 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_columnPositions after it).
LayoutUnit track_gap = GridGapForDirection(kForRows, kTrackSizing);
if (child_end_line < row_positions_.size() - 1) {
end_of_row -= track_gap;
end_of_row -= offset_between_rows_;
}
LayoutUnit column_axis_child_size =
IsOrthogonalChild(child)
? child.LogicalWidth() + child.MarginLogicalWidth()
: child.LogicalHeight() + child.MarginLogicalHeight();
OverflowAlignment overflow = AlignSelfForChild(child).Overflow();
LayoutUnit offset_from_start_position = ComputeOverflowAlignmentOffset(
overflow, end_of_row - start_of_row, column_axis_child_size);
return start_position + (axis_position == kGridAxisEnd
? offset_from_start_position
: offset_from_start_position / 2);
}
}
NOTREACHED();
return LayoutUnit();
}
LayoutUnit LayoutGrid::RowAxisOffsetForChild(const LayoutBox& child) const {
const GridSpan& columns_span =
track_sizing_algorithm_.GetGrid().GridItemSpan(child, kForColumns);
size_t child_start_line = columns_span.StartLine();
LayoutUnit start_of_column = column_positions_[child_start_line];
LayoutUnit start_position = start_of_column + MarginStartForChild(child);
if (HasAutoMarginsInRowAxis(child))
return start_position;
GridAxisPosition axis_position = RowAxisPositionForChild(child);
switch (axis_position) {
case kGridAxisStart:
return start_position + RowAxisBaselineOffsetForChild(child);
case kGridAxisEnd:
case kGridAxisCenter: {
size_t child_end_line = columns_span.EndLine();
LayoutUnit end_of_column = column_positions_[child_end_line];
// 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).
LayoutUnit track_gap = GridGapForDirection(kForColumns, kTrackSizing);
if (child_end_line < column_positions_.size() - 1) {
end_of_column -= track_gap;
end_of_column -= offset_between_columns_;
}
LayoutUnit row_axis_child_size =
IsOrthogonalChild(child)
? child.LogicalHeight() + child.MarginLogicalHeight()
: child.LogicalWidth() + child.MarginLogicalWidth();
OverflowAlignment overflow = JustifySelfForChild(child).Overflow();
LayoutUnit offset_from_start_position = ComputeOverflowAlignmentOffset(
overflow, end_of_column - start_of_column, row_axis_child_size);
return start_position + (axis_position == kGridAxisEnd
? offset_from_start_position
: offset_from_start_position / 2);
}
}
NOTREACHED();
return LayoutUnit();
}
ContentPosition static ResolveContentDistributionFallback(
ContentDistributionType distribution) {
switch (distribution) {
case kContentDistributionSpaceBetween:
return kContentPositionStart;
case kContentDistributionSpaceAround:
return kContentPositionCenter;
case kContentDistributionSpaceEvenly:
return kContentPositionCenter;
case kContentDistributionStretch:
return kContentPositionStart;
case kContentDistributionDefault:
return kContentPositionNormal;
}
NOTREACHED();
return kContentPositionNormal;
}
static ContentAlignmentData ContentDistributionOffset(
const LayoutUnit& available_free_space,
ContentPosition& fallback_position,
ContentDistributionType distribution,
unsigned number_of_grid_tracks) {
if (distribution != kContentDistributionDefault &&
fallback_position == kContentPositionNormal)
fallback_position = ResolveContentDistributionFallback(distribution);
if (available_free_space <= 0)
return {};
LayoutUnit distribution_offset;
switch (distribution) {
case kContentDistributionSpaceBetween:
if (number_of_grid_tracks < 2)
return {};
return {LayoutUnit(), available_free_space / (number_of_grid_tracks - 1)};
case kContentDistributionSpaceAround:
if (number_of_grid_tracks < 1)
return {};
distribution_offset = available_free_space / number_of_grid_tracks;
return {distribution_offset / 2, distribution_offset};
case kContentDistributionSpaceEvenly:
distribution_offset = available_free_space / (number_of_grid_tracks + 1);
return {distribution_offset, distribution_offset};
case kContentDistributionStretch:
case kContentDistributionDefault:
return {};
}
NOTREACHED();
return {};
}
ContentAlignmentData LayoutGrid::ComputeContentPositionAndDistributionOffset(
GridTrackSizingDirection direction,
const LayoutUnit& available_free_space,
unsigned number_of_grid_tracks) const {
bool is_row_axis = direction == kForColumns;
ContentPosition position = is_row_axis
? StyleRef().ResolvedJustifyContentPosition(
ContentAlignmentNormalBehavior())
: StyleRef().ResolvedAlignContentPosition(
ContentAlignmentNormalBehavior());
ContentDistributionType distribution =
is_row_axis ? StyleRef().ResolvedJustifyContentDistribution(
ContentAlignmentNormalBehavior())
: StyleRef().ResolvedAlignContentDistribution(
ContentAlignmentNormalBehavior());
// If <content-distribution> value can't be applied, 'position' will become
// the associated <content-position> fallback value.
ContentAlignmentData content_alignment = ContentDistributionOffset(
available_free_space, position, distribution, number_of_grid_tracks);
if (content_alignment.IsValid())
return content_alignment;
OverflowAlignment overflow =
is_row_axis ? StyleRef().JustifyContentOverflowAlignment()
: StyleRef().AlignContentOverflowAlignment();
// TODO (lajava): Default value for overflow isn't exaclty as 'unsafe'.
// https://drafts.csswg.org/css-align/#overflow-values
if (available_free_space == 0 ||
(available_free_space < 0 && overflow == kOverflowAlignmentSafe))
return {LayoutUnit(), LayoutUnit()};
switch (position) {
case kContentPositionLeft:
// The align-content's axis is always orthogonal to the inline-axis.
return {LayoutUnit(), LayoutUnit()};
case kContentPositionRight:
if (is_row_axis)
return {available_free_space, LayoutUnit()};
// The align-content's axis is always orthogonal to the inline-axis.
return {LayoutUnit(), LayoutUnit()};
case kContentPositionCenter:
return {available_free_space / 2, LayoutUnit()};
// Only used in flex layout, for other layout, it's equivalent to 'End'.
case kContentPositionFlexEnd:
case kContentPositionEnd:
if (is_row_axis)
return {StyleRef().IsLeftToRightDirection() ? available_free_space
: LayoutUnit(),
LayoutUnit()};
return {available_free_space, LayoutUnit()};
// Only used in flex layout, for other layout, it's equivalent to 'Start'.
case kContentPositionFlexStart:
case kContentPositionStart:
if (is_row_axis)
return {StyleRef().IsLeftToRightDirection() ? LayoutUnit()
: available_free_space,
LayoutUnit()};
return {LayoutUnit(), LayoutUnit()};
case kContentPositionBaseline:
case kContentPositionLastBaseline:
// FIXME: These two require implementing Baseline Alignment. For now, we
// always 'start' align the child. crbug.com/234191
if (is_row_axis)
return {StyleRef().IsLeftToRightDirection() ? LayoutUnit()
: available_free_space,
LayoutUnit()};
return {LayoutUnit(), LayoutUnit()};
case kContentPositionNormal:
break;
}
NOTREACHED();
return {LayoutUnit(), LayoutUnit()};
}
LayoutUnit LayoutGrid::TranslateRTLCoordinate(LayoutUnit coordinate) const {
DCHECK(!StyleRef().IsLeftToRightDirection());
LayoutUnit alignment_offset = column_positions_[0];
LayoutUnit right_grid_edge_position =
column_positions_[column_positions_.size() - 1];
return right_grid_edge_position + alignment_offset - coordinate;
}
LayoutPoint LayoutGrid::FindChildLogicalPosition(const LayoutBox& child) const {
LayoutUnit column_axis_offset = ColumnAxisOffsetForChild(child);
LayoutUnit row_axis_offset = RowAxisOffsetForChild(child);
// We stored m_columnPosition'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())
row_axis_offset = TranslateRTLCoordinate(row_axis_offset) -
(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 child_location(row_axis_offset, column_axis_offset);
return IsOrthogonalChild(child) ? child_location.TransposedPoint()
: child_location;
}
LayoutPoint LayoutGrid::GridAreaLogicalPosition(const GridArea& area) const {
LayoutUnit column_axis_offset = row_positions_[area.rows.StartLine()];
LayoutUnit row_axis_offset = column_positions_[area.columns.StartLine()];
// See comment in findChildLogicalPosition() about why we need sometimes to
// translate from RTL to LTR the rowAxisOffset coordinate.
return LayoutPoint(Style()->IsLeftToRightDirection()
? row_axis_offset
: TranslateRTLCoordinate(row_axis_offset),
column_axis_offset);
}
void LayoutGrid::PaintChildren(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) const {
DCHECK(!grid_.NeedsItemsPlacement());
if (grid_.HasGridItems())
GridPainter(*this).PaintChildren(paint_info, paint_offset);
}
bool LayoutGrid::CachedHasDefiniteLogicalHeight() const {
SECURITY_DCHECK(has_definite_logical_height_);
return has_definite_logical_height_.value();
}
size_t LayoutGrid::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).
DCHECK(!grid.NeedsItemsPlacement());
if (direction == kForRows)
return grid.NumTracks(kForRows);
return grid.NumTracks(kForRows)
? grid.NumTracks(kForColumns)
: GridPositionsResolver::ExplicitGridColumnCount(
StyleRef(), grid.AutoRepeatTracks(kForColumns));
}
} // namespace blink