Commit 032a699c authored by Sergio Villar Senin's avatar Sergio Villar Senin Committed by Commit Bot

[css-grid] Refactor Grid to allow different implementations

The current Grid data structure is mostly generic but it still
depends on a grid representation based on Vectors of Vectors. In
order to allow future memory efficient representations we should
remove that dependency. This CL moves all the vector dependent
code to a new class named VectorGrid.

Apart from that, GridIterator was moved to Grid and also
refectored as we would need to provide different implementations
of the iterator depending on the data structure used for grids.

Last but not least, in order to limit the amount of changes in
the clients of these two interfaces, a couple of factory methods
were added to create both Grid and GridIterator objects. These
Create() methods are the ones instantiating the specific
Grid (and GridIterator) representations.

R=jfernandez@igalia.com, rego@igalia.com

Change-Id: I59d9f8df93c3c65390a0f22aaf8064359f9248f4
Reviewed-on: https://chromium-review.googlesource.com/1075331
Commit-Queue: Sergio Villar <svillar@igalia.com>
Reviewed-by: default avatarJavier Fernandez <jfernandez@igalia.com>
Reviewed-by: default avatarManuel Rego Casasnovas <rego@igalia.com>
Cr-Commit-Position: refs/heads/master@{#564857}
parent 3b37c4e4
...@@ -12,41 +12,56 @@ ...@@ -12,41 +12,56 @@
namespace blink { namespace blink {
std::unique_ptr<Grid> Grid::Create(const LayoutGrid* layout_grid) {
return base::WrapUnique(new VectorGrid(layout_grid));
}
Grid::Grid(const LayoutGrid* grid) : order_iterator_(grid) {} Grid::Grid(const LayoutGrid* grid) : order_iterator_(grid) {}
size_t Grid::NumTracks(GridTrackSizingDirection direction) const { VectorGrid::VectorGrid(const LayoutGrid* grid) : Grid(grid) {}
size_t VectorGrid::NumTracks(GridTrackSizingDirection direction) const {
if (direction == kForRows) if (direction == kForRows)
return grid_.size(); return matrix_.size();
return grid_.size() ? grid_[0].size() : 0; return matrix_.size() ? matrix_[0].size() : 0;
} }
void Grid::EnsureGridSize(size_t maximum_row_size, size_t maximum_column_size) { void VectorGrid::EnsureGridSize(size_t maximum_row_size,
size_t maximum_column_size) {
const size_t old_row_size = NumTracks(kForRows); const size_t old_row_size = NumTracks(kForRows);
if (maximum_row_size > old_row_size) { if (maximum_row_size > old_row_size) {
grid_.Grow(maximum_row_size); matrix_.Grow(maximum_row_size);
for (size_t row = old_row_size; row < NumTracks(kForRows); ++row) for (size_t row = old_row_size; row < NumTracks(kForRows); ++row)
grid_[row].Grow(NumTracks(kForColumns)); matrix_[row].Grow(NumTracks(kForColumns));
} }
if (maximum_column_size > NumTracks(kForColumns)) { if (maximum_column_size > NumTracks(kForColumns)) {
for (size_t row = 0; row < NumTracks(kForRows); ++row) for (size_t row = 0; row < NumTracks(kForRows); ++row)
grid_[row].Grow(maximum_column_size); matrix_[row].Grow(maximum_column_size);
} }
} }
void Grid::insert(LayoutBox& child, const GridArea& area) { void VectorGrid::insert(LayoutBox& child, const GridArea& area) {
DCHECK(area.rows.IsTranslatedDefinite()); DCHECK(area.rows.IsTranslatedDefinite());
DCHECK(area.columns.IsTranslatedDefinite()); DCHECK(area.columns.IsTranslatedDefinite());
EnsureGridSize(area.rows.EndLine(), area.columns.EndLine()); EnsureGridSize(area.rows.EndLine(), area.columns.EndLine());
for (const auto& row : area.rows) { for (const auto& row : area.rows) {
for (const auto& column : area.columns) for (const auto& column : area.columns)
grid_[row][column].push_back(&child); matrix_[row][column].push_back(&child);
} }
SetGridItemArea(child, area); SetGridItemArea(child, area);
} }
std::unique_ptr<Grid::GridIterator> VectorGrid::CreateIterator(
GridTrackSizingDirection direction,
size_t fixed_track_index,
size_t varying_track_index) const {
return base::WrapUnique(new VectorGridIterator(
*this, direction, fixed_track_index, varying_track_index));
}
void Grid::SetSmallestTracksStart(int row_start, int column_start) { void Grid::SetSmallestTracksStart(int row_start, int column_start) {
smallest_row_start_ = row_start; smallest_row_start_ = row_start;
smallest_column_start_ = column_start; smallest_column_start_ = column_start;
...@@ -73,10 +88,6 @@ void Grid::SetGridItemPaintOrder(const LayoutBox& item, size_t order) { ...@@ -73,10 +88,6 @@ void Grid::SetGridItemPaintOrder(const LayoutBox& item, size_t order) {
grid_items_indexes_map_.Set(&item, order); grid_items_indexes_map_.Set(&item, order);
} }
const GridCell& Grid::Cell(size_t row, size_t column) const {
return grid_[row][column];
}
#if DCHECK_IS_ON() #if DCHECK_IS_ON()
bool Grid::HasAnyGridItemPaintOrder() const { bool Grid::HasAnyGridItemPaintOrder() const {
return !grid_items_indexes_map_.IsEmpty(); return !grid_items_indexes_map_.IsEmpty();
...@@ -139,11 +150,11 @@ void Grid::SetNeedsItemsPlacement(bool needs_items_placement) { ...@@ -139,11 +150,11 @@ void Grid::SetNeedsItemsPlacement(bool needs_items_placement) {
needs_items_placement_ = needs_items_placement; needs_items_placement_ = needs_items_placement;
if (!needs_items_placement) { if (!needs_items_placement) {
grid_.ShrinkToFit(); ConsolidateGridDataStructure();
return; return;
} }
grid_.resize(0); ClearGridDataStructure();
grid_item_area_.clear(); grid_item_area_.clear();
grid_items_indexes_map_.clear(); grid_items_indexes_map_.clear();
has_any_orthogonal_grid_item_ = false; has_any_orthogonal_grid_item_ = false;
...@@ -155,34 +166,39 @@ void Grid::SetNeedsItemsPlacement(bool needs_items_placement) { ...@@ -155,34 +166,39 @@ void Grid::SetNeedsItemsPlacement(bool needs_items_placement) {
auto_repeat_empty_rows_ = nullptr; auto_repeat_empty_rows_ = nullptr;
} }
GridIterator::GridIterator(const Grid& grid, Grid::GridIterator::GridIterator(GridTrackSizingDirection direction,
GridTrackSizingDirection direction,
size_t fixed_track_index, size_t fixed_track_index,
size_t varying_track_index) size_t varying_track_index)
: grid_(grid.grid_), : direction_(direction),
direction_(direction),
row_index_((direction == kForColumns) ? varying_track_index row_index_((direction == kForColumns) ? varying_track_index
: fixed_track_index), : fixed_track_index),
column_index_((direction == kForColumns) ? fixed_track_index column_index_((direction == kForColumns) ? fixed_track_index
: varying_track_index), : varying_track_index),
child_index_(0) { child_index_(0) {}
DCHECK(!grid_.IsEmpty());
DCHECK(!grid_[0].IsEmpty()); VectorGridIterator::VectorGridIterator(const VectorGrid& grid,
DCHECK_LT(row_index_, grid_.size()); GridTrackSizingDirection direction,
DCHECK_LT(column_index_, grid_[0].size()); size_t fixed_track_index,
size_t varying_track_index)
: GridIterator(direction, fixed_track_index, varying_track_index),
matrix_(grid.matrix_) {
DCHECK(!matrix_.IsEmpty());
DCHECK(!matrix_[0].IsEmpty());
DCHECK_LT(row_index_, matrix_.size());
DCHECK_LT(column_index_, matrix_[0].size());
} }
LayoutBox* GridIterator::NextGridItem() { LayoutBox* VectorGridIterator::NextGridItem() {
DCHECK(!grid_.IsEmpty()); DCHECK(!matrix_.IsEmpty());
DCHECK(!grid_[0].IsEmpty()); DCHECK(!matrix_[0].IsEmpty());
size_t& varying_track_index = size_t& varying_track_index =
(direction_ == kForColumns) ? row_index_ : column_index_; (direction_ == kForColumns) ? row_index_ : column_index_;
const size_t end_of_varying_track_index = const size_t end_of_varying_track_index =
(direction_ == kForColumns) ? grid_.size() : grid_[0].size(); (direction_ == kForColumns) ? matrix_.size() : matrix_[0].size();
for (; varying_track_index < end_of_varying_track_index; for (; varying_track_index < end_of_varying_track_index;
++varying_track_index) { ++varying_track_index) {
const GridCell& children = grid_[row_index_][column_index_]; const GridCell& children = matrix_[row_index_][column_index_];
if (child_index_ < children.size()) if (child_index_ < children.size())
return children[child_index_++]; return children[child_index_++];
...@@ -191,19 +207,20 @@ LayoutBox* GridIterator::NextGridItem() { ...@@ -191,19 +207,20 @@ LayoutBox* GridIterator::NextGridItem() {
return nullptr; return nullptr;
} }
bool GridIterator::CheckEmptyCells(size_t row_span, size_t column_span) const { bool VectorGridIterator::CheckEmptyCells(size_t row_span,
DCHECK(!grid_.IsEmpty()); size_t column_span) const {
DCHECK(!grid_[0].IsEmpty()); DCHECK(!matrix_.IsEmpty());
DCHECK(!matrix_[0].IsEmpty());
// Ignore cells outside current grid as we will grow it later if needed. // Ignore cells outside current grid as we will grow it later if needed.
size_t max_rows = std::min(row_index_ + row_span, grid_.size()); size_t max_rows = std::min(row_index_ + row_span, matrix_.size());
size_t max_columns = std::min(column_index_ + column_span, grid_[0].size()); size_t max_columns = std::min(column_index_ + column_span, matrix_[0].size());
// This adds a O(N^2) behavior that shouldn't be a big deal as we expect // This adds a O(N^2) behavior that shouldn't be a big deal as we expect
// spanning areas to be small. // spanning areas to be small.
for (size_t row = row_index_; row < max_rows; ++row) { for (size_t row = row_index_; row < max_rows; ++row) {
for (size_t column = column_index_; column < max_columns; ++column) { for (size_t column = column_index_; column < max_columns; ++column) {
const GridCell& children = grid_[row][column]; const GridCell& children = matrix_[row][column];
if (!children.IsEmpty()) if (!children.IsEmpty())
return false; return false;
} }
...@@ -212,11 +229,11 @@ bool GridIterator::CheckEmptyCells(size_t row_span, size_t column_span) const { ...@@ -212,11 +229,11 @@ bool GridIterator::CheckEmptyCells(size_t row_span, size_t column_span) const {
return true; return true;
} }
std::unique_ptr<GridArea> GridIterator::NextEmptyGridArea( std::unique_ptr<GridArea> VectorGridIterator::NextEmptyGridArea(
size_t fixed_track_span, size_t fixed_track_span,
size_t varying_track_span) { size_t varying_track_span) {
DCHECK(!grid_.IsEmpty()); DCHECK(!matrix_.IsEmpty());
DCHECK(!grid_[0].IsEmpty()); DCHECK(!matrix_[0].IsEmpty());
DCHECK_GE(fixed_track_span, 1u); DCHECK_GE(fixed_track_span, 1u);
DCHECK_GE(varying_track_span, 1u); DCHECK_GE(varying_track_span, 1u);
...@@ -228,7 +245,7 @@ std::unique_ptr<GridArea> GridIterator::NextEmptyGridArea( ...@@ -228,7 +245,7 @@ std::unique_ptr<GridArea> GridIterator::NextEmptyGridArea(
size_t& varying_track_index = size_t& varying_track_index =
(direction_ == kForColumns) ? row_index_ : column_index_; (direction_ == kForColumns) ? row_index_ : column_index_;
const size_t end_of_varying_track_index = const size_t end_of_varying_track_index =
(direction_ == kForColumns) ? grid_.size() : grid_[0].size(); (direction_ == kForColumns) ? matrix_.size() : matrix_[0].size();
for (; varying_track_index < end_of_varying_track_index; for (; varying_track_index < end_of_varying_track_index;
++varying_track_index) { ++varying_track_index) {
if (CheckEmptyCells(row_span, column_span)) { if (CheckEmptyCells(row_span, column_span)) {
......
...@@ -29,14 +29,19 @@ class GridIterator; ...@@ -29,14 +29,19 @@ class GridIterator;
// LayoutGrid object to place the grid items on a grid like structure, so that // LayoutGrid object to place the grid items on a grid like structure, so that
// they could be accessed by rows/columns instead of just traversing the DOM or // they could be accessed by rows/columns instead of just traversing the DOM or
// Layout trees. // Layout trees.
class Grid final { class Grid {
public: public:
Grid(const LayoutGrid*); static std::unique_ptr<Grid> Create(const LayoutGrid*);
virtual size_t NumTracks(GridTrackSizingDirection) const = 0;
virtual void EnsureGridSize(size_t maximum_row_size,
size_t maximum_column_size) = 0;
virtual void insert(LayoutBox&, const GridArea&) = 0;
size_t NumTracks(GridTrackSizingDirection) const; virtual const GridCell& Cell(size_t row, size_t column) const = 0;
void EnsureGridSize(size_t maximum_row_size, size_t maximum_column_size); virtual ~Grid(){};
void insert(LayoutBox&, const GridArea&);
// Note that out of flow children are not grid items. // Note that out of flow children are not grid items.
bool HasGridItems() const { return !grid_item_area_.IsEmpty(); } bool HasGridItems() const { return !grid_item_area_.IsEmpty(); }
...@@ -54,8 +59,6 @@ class Grid final { ...@@ -54,8 +59,6 @@ class Grid final {
size_t GridItemPaintOrder(const LayoutBox&) const; size_t GridItemPaintOrder(const LayoutBox&) const;
void SetGridItemPaintOrder(const LayoutBox&, size_t order); void SetGridItemPaintOrder(const LayoutBox&, size_t order);
const GridCell& Cell(size_t row, size_t column) const;
int SmallestTrackStart(GridTrackSizingDirection) const; int SmallestTrackStart(GridTrackSizingDirection) const;
void SetSmallestTracksStart(int row_start, int column_start); void SetSmallestTracksStart(int row_start, int column_start);
...@@ -81,6 +84,42 @@ class Grid final { ...@@ -81,6 +84,42 @@ class Grid final {
bool HasAnyGridItemPaintOrder() const; bool HasAnyGridItemPaintOrder() const;
#endif #endif
class GridIterator {
public:
virtual LayoutBox* NextGridItem() = 0;
virtual std::unique_ptr<GridArea> NextEmptyGridArea(
size_t fixed_track_span,
size_t varying_track_span) = 0;
virtual ~GridIterator() = default;
protected:
// |direction| is the direction that is fixed to |fixed_track_index| so e.g
// GridIterator(grid_, kForColumns, 1) will walk over the rows of the 2nd
// column.
GridIterator(GridTrackSizingDirection,
size_t fixed_track_index,
size_t varying_track_index);
GridTrackSizingDirection direction_;
size_t row_index_;
size_t column_index_;
size_t child_index_;
DISALLOW_COPY_AND_ASSIGN(GridIterator);
};
virtual std::unique_ptr<GridIterator> CreateIterator(
GridTrackSizingDirection,
size_t fixed_track_index,
size_t varying_track_index = 0) const = 0;
protected:
Grid(const LayoutGrid*);
virtual void ClearGridDataStructure() = 0;
virtual void ConsolidateGridDataStructure() = 0;
private: private:
friend class GridIterator; friend class GridIterator;
...@@ -95,8 +134,6 @@ class Grid final { ...@@ -95,8 +134,6 @@ class Grid final {
bool has_any_orthogonal_grid_item_{false}; bool has_any_orthogonal_grid_item_{false};
bool needs_items_placement_{true}; bool needs_items_placement_{true};
GridAsMatrix grid_;
HashMap<const LayoutBox*, GridArea> grid_item_area_; HashMap<const LayoutBox*, GridArea> grid_item_area_;
HashMap<const LayoutBox*, size_t> grid_items_indexes_map_; HashMap<const LayoutBox*, size_t> grid_items_indexes_map_;
...@@ -104,32 +141,50 @@ class Grid final { ...@@ -104,32 +141,50 @@ class Grid final {
std::unique_ptr<OrderedTrackIndexSet> auto_repeat_empty_rows_{nullptr}; std::unique_ptr<OrderedTrackIndexSet> auto_repeat_empty_rows_{nullptr};
}; };
// TODO(svillar): ideally the Grid class should be the one returning an iterator class VectorGrid final : public Grid {
// for its contents.
class GridIterator final {
public: public:
// |direction| is the direction that is fixed to |fixedTrackIndex| so e.g explicit VectorGrid(const LayoutGrid*);
// GridIterator(m_grid, ForColumns, 1) will walk over the rows of the 2nd
// column. size_t NumTracks(GridTrackSizingDirection) const override;
GridIterator(const Grid&, const GridCell& Cell(size_t row, size_t column) const override {
return matrix_[row][column];
}
void insert(LayoutBox&, const GridArea&) override;
private:
friend class VectorGridIterator;
void EnsureGridSize(size_t maximum_row_size,
size_t maximum_column_size) override;
void ClearGridDataStructure() override { matrix_.resize(0); };
void ConsolidateGridDataStructure() override { matrix_.ShrinkToFit(); }
std::unique_ptr<GridIterator> CreateIterator(
GridTrackSizingDirection, GridTrackSizingDirection,
size_t fixed_track_index, size_t fixed_track_index,
size_t varying_track_index = 0); size_t varying_track_index = 0) const override;
LayoutBox* NextGridItem(); GridAsMatrix matrix_;
};
bool CheckEmptyCells(size_t row_span, size_t column_span) const; class VectorGridIterator final : public Grid::GridIterator {
public:
VectorGridIterator(const VectorGrid&,
GridTrackSizingDirection,
size_t fixed_track_span,
size_t varying_track_span = 0);
std::unique_ptr<GridArea> NextEmptyGridArea(size_t fixed_track_span, LayoutBox* NextGridItem() override;
size_t varying_track_span); std::unique_ptr<GridArea> NextEmptyGridArea(
size_t fixed_track_span,
size_t varying_track_span) override;
private: private:
const GridAsMatrix& grid_; bool CheckEmptyCells(size_t row_span, size_t column_span) const;
GridTrackSizingDirection direction_;
size_t row_index_; const GridAsMatrix& matrix_;
size_t column_index_; DISALLOW_COPY_AND_ASSIGN(VectorGridIterator);
size_t child_index_;
DISALLOW_COPY_AND_ASSIGN(GridIterator);
}; };
} // namespace blink } // namespace blink
......
...@@ -524,8 +524,9 @@ double IndefiniteSizeStrategy::FindUsedFlexFraction( ...@@ -524,8 +524,9 @@ double IndefiniteSizeStrategy::FindUsedFlexFraction(
return flex_fraction; return flex_fraction;
for (size_t i = 0; i < flexible_sized_tracks_index.size(); ++i) { for (size_t i = 0; i < flexible_sized_tracks_index.size(); ++i) {
GridIterator iterator(grid, direction, flexible_sized_tracks_index[i]); auto iterator =
while (LayoutBox* grid_item = iterator.NextGridItem()) { grid.CreateIterator(direction, flexible_sized_tracks_index[i]);
while (LayoutBox* grid_item = iterator->NextGridItem()) {
const GridSpan& span = grid.GridItemSpan(*grid_item, direction); const GridSpan& span = grid.GridItemSpan(*grid_item, direction);
// Do not include already processed items. // Do not include already processed items.
...@@ -1226,9 +1227,9 @@ void GridTrackSizingAlgorithm::ResolveIntrinsicTrackSizes() { ...@@ -1226,9 +1227,9 @@ void GridTrackSizingAlgorithm::ResolveIntrinsicTrackSizes() {
if (grid_.HasGridItems()) { if (grid_.HasGridItems()) {
HashSet<LayoutBox*> items_set; HashSet<LayoutBox*> items_set;
for (const auto& track_index : content_sized_tracks_index_) { for (const auto& track_index : content_sized_tracks_index_) {
GridIterator iterator(grid_, direction_, track_index); auto iterator = grid_.CreateIterator(direction_, track_index);
GridTrack& track = Tracks(direction_)[track_index]; GridTrack& track = Tracks(direction_)[track_index];
while (LayoutBox* grid_item = iterator.NextGridItem()) { while (auto* grid_item = iterator->NextGridItem()) {
if (items_set.insert(grid_item).is_new_entry) { if (items_set.insert(grid_item).is_new_entry) {
const GridSpan& span = grid_.GridItemSpan(*grid_item, direction_); const GridSpan& span = grid_.GridItemSpan(*grid_item, direction_);
if (span.IntegerSpan() == 1) { if (span.IntegerSpan() == 1) {
......
...@@ -58,7 +58,9 @@ struct ContentAlignmentData { ...@@ -58,7 +58,9 @@ struct ContentAlignmentData {
}; };
LayoutGrid::LayoutGrid(Element* element) LayoutGrid::LayoutGrid(Element* element)
: LayoutBlock(element), grid_(this), track_sizing_algorithm_(this, grid_) { : LayoutBlock(element),
grid_(Grid::Create(this)),
track_sizing_algorithm_(this, *grid_) {
DCHECK(!ChildrenInline()); DCHECK(!ChildrenInline());
if (!IsAnonymous()) if (!IsAnonymous())
UseCounter::Count(GetDocument(), WebFeature::kCSSGridLayout); UseCounter::Count(GetDocument(), WebFeature::kCSSGridLayout);
...@@ -232,7 +234,7 @@ LayoutUnit LayoutGrid::ComputeTrackBasedLogicalHeight() const { ...@@ -232,7 +234,7 @@ LayoutUnit LayoutGrid::ComputeTrackBasedLogicalHeight() const {
for (const auto& row : all_rows) for (const auto& row : all_rows)
logical_height += row.BaseSize(); logical_height += row.BaseSize();
logical_height += GuttersSize(grid_, kForRows, 0, all_rows.size(), logical_height += GuttersSize(*grid_, kForRows, 0, all_rows.size(),
AvailableSpaceForGutters(kForRows)); AvailableSpaceForGutters(kForRows));
return logical_height; return logical_height;
...@@ -241,7 +243,7 @@ LayoutUnit LayoutGrid::ComputeTrackBasedLogicalHeight() const { ...@@ -241,7 +243,7 @@ LayoutUnit LayoutGrid::ComputeTrackBasedLogicalHeight() const {
void LayoutGrid::ComputeTrackSizesForDefiniteSize( void LayoutGrid::ComputeTrackSizesForDefiniteSize(
GridTrackSizingDirection direction, GridTrackSizingDirection direction,
LayoutUnit available_space) { LayoutUnit available_space) {
track_sizing_algorithm_.Setup(direction, NumTracks(direction, grid_), track_sizing_algorithm_.Setup(direction, NumTracks(direction, *grid_),
available_space); available_space);
track_sizing_algorithm_.Run(); track_sizing_algorithm_.Run();
...@@ -286,7 +288,7 @@ void LayoutGrid::UpdateBlockLayout(bool relayout_children) { ...@@ -286,7 +288,7 @@ void LayoutGrid::UpdateBlockLayout(bool relayout_children) {
// We cannot perform a simplifiedLayout() on a dirty grid that // We cannot perform a simplifiedLayout() on a dirty grid that
// has positioned items to be laid out. // has positioned items to be laid out.
if (!relayout_children && if (!relayout_children &&
(!grid_.NeedsItemsPlacement() || !PosChildNeedsLayout()) && (!grid_->NeedsItemsPlacement() || !PosChildNeedsLayout()) &&
SimplifiedLayout()) SimplifiedLayout())
return; return;
...@@ -315,7 +317,7 @@ void LayoutGrid::UpdateBlockLayout(bool relayout_children) { ...@@ -315,7 +317,7 @@ void LayoutGrid::UpdateBlockLayout(bool relayout_children) {
TextAutosizer::LayoutScope text_autosizer_layout_scope(this, &layout_scope); TextAutosizer::LayoutScope text_autosizer_layout_scope(this, &layout_scope);
LayoutUnit available_space_for_columns = AvailableLogicalWidth(); LayoutUnit available_space_for_columns = AvailableLogicalWidth();
PlaceItemsOnGrid(grid_, available_space_for_columns); PlaceItemsOnGrid(*grid_, available_space_for_columns);
track_sizing_algorithm_.ComputeBaselineAlignmentContext(); track_sizing_algorithm_.ComputeBaselineAlignmentContext();
...@@ -335,7 +337,7 @@ void LayoutGrid::UpdateBlockLayout(bool relayout_children) { ...@@ -335,7 +337,7 @@ void LayoutGrid::UpdateBlockLayout(bool relayout_children) {
kForRows, AvailableLogicalHeight(kExcludeMarginBorderPadding)); kForRows, AvailableLogicalHeight(kExcludeMarginBorderPadding));
} else { } else {
ComputeTrackSizesForIndefiniteSize(track_sizing_algorithm_, kForRows, ComputeTrackSizesForIndefiniteSize(track_sizing_algorithm_, kForRows,
grid_, min_content_height_, *grid_, min_content_height_,
max_content_height_); max_content_height_);
} }
LayoutUnit track_based_logical_height = ComputeTrackBasedLogicalHeight() + LayoutUnit track_based_logical_height = ComputeTrackBasedLogicalHeight() +
...@@ -495,12 +497,12 @@ LayoutUnit LayoutGrid::GuttersSize( ...@@ -495,12 +497,12 @@ LayoutUnit LayoutGrid::GuttersSize(
void LayoutGrid::ComputeIntrinsicLogicalWidths( void LayoutGrid::ComputeIntrinsicLogicalWidths(
LayoutUnit& min_logical_width, LayoutUnit& min_logical_width,
LayoutUnit& max_logical_width) const { LayoutUnit& max_logical_width) const {
Grid grid(this); std::unique_ptr<Grid> grid = Grid::Create(this);
PlaceItemsOnGrid(grid, base::nullopt); PlaceItemsOnGrid(*grid, base::nullopt);
GridTrackSizingAlgorithm algorithm(this, grid); GridTrackSizingAlgorithm algorithm(this, *grid);
algorithm.ComputeBaselineAlignmentContext(); algorithm.ComputeBaselineAlignmentContext();
ComputeTrackSizesForIndefiniteSize(algorithm, kForColumns, grid, ComputeTrackSizesForIndefiniteSize(algorithm, kForColumns, *grid,
min_logical_width, max_logical_width); min_logical_width, max_logical_width);
LayoutUnit scrollbar_width = LayoutUnit(ScrollbarLogicalWidth()); LayoutUnit scrollbar_width = LayoutUnit(ScrollbarLogicalWidth());
...@@ -707,8 +709,8 @@ LayoutGrid::ComputeEmptyTracksForAutoRepeat( ...@@ -707,8 +709,8 @@ LayoutGrid::ComputeEmptyTracksForAutoRepeat(
} else { } else {
for (size_t track_index = first_auto_repeat_track; for (size_t track_index = first_auto_repeat_track;
track_index < last_auto_repeat_track; ++track_index) { track_index < last_auto_repeat_track; ++track_index) {
GridIterator iterator(grid, direction, track_index); auto iterator = grid.CreateIterator(direction, track_index);
if (!iterator.NextGridItem()) { if (!iterator->NextGridItem()) {
if (!empty_track_indexes) if (!empty_track_indexes)
empty_track_indexes = std::make_unique<OrderedTrackIndexSet>(); empty_track_indexes = std::make_unique<OrderedTrackIndexSet>();
empty_track_indexes->insert(track_index); empty_track_indexes->insert(track_index);
...@@ -994,13 +996,12 @@ void LayoutGrid::PlaceSpecifiedMajorAxisItemsOnGrid( ...@@ -994,13 +996,12 @@ void LayoutGrid::PlaceSpecifiedMajorAxisItemsOnGrid(
*Style(), *auto_grid_item, AutoPlacementMinorAxisDirection()); *Style(), *auto_grid_item, AutoPlacementMinorAxisDirection());
unsigned major_axis_initial_position = major_axis_positions.StartLine(); unsigned major_axis_initial_position = major_axis_positions.StartLine();
GridIterator iterator( auto iterator = grid.CreateIterator(
grid, AutoPlacementMajorAxisDirection(), AutoPlacementMajorAxisDirection(), major_axis_positions.StartLine(),
major_axis_positions.StartLine(),
is_grid_auto_flow_dense is_grid_auto_flow_dense
? 0 ? 0
: minor_axis_cursors.at(major_axis_initial_position)); : minor_axis_cursors.at(major_axis_initial_position));
std::unique_ptr<GridArea> empty_grid_area = iterator.NextEmptyGridArea( std::unique_ptr<GridArea> empty_grid_area = iterator->NextEmptyGridArea(
major_axis_positions.IntegerSpan(), minor_axis_span_size); major_axis_positions.IntegerSpan(), minor_axis_span_size);
if (!empty_grid_area) { if (!empty_grid_area) {
empty_grid_area = CreateEmptyGridAreaAtSpecifiedPositionsOutsideGrid( empty_grid_area = CreateEmptyGridAreaAtSpecifiedPositionsOutsideGrid(
...@@ -1066,10 +1067,10 @@ void LayoutGrid::PlaceAutoMajorAxisItemOnGrid( ...@@ -1066,10 +1067,10 @@ void LayoutGrid::PlaceAutoMajorAxisItemOnGrid(
major_axis_auto_placement_cursor++; major_axis_auto_placement_cursor++;
if (major_axis_auto_placement_cursor < end_of_major_axis) { if (major_axis_auto_placement_cursor < end_of_major_axis) {
GridIterator iterator(grid, AutoPlacementMinorAxisDirection(), auto iterator = grid.CreateIterator(AutoPlacementMinorAxisDirection(),
minor_axis_positions.StartLine(), minor_axis_positions.StartLine(),
major_axis_auto_placement_cursor); major_axis_auto_placement_cursor);
empty_grid_area = iterator.NextEmptyGridArea( empty_grid_area = iterator->NextEmptyGridArea(
minor_axis_positions.IntegerSpan(), major_axis_span_size); minor_axis_positions.IntegerSpan(), major_axis_span_size);
} }
...@@ -1085,9 +1086,10 @@ void LayoutGrid::PlaceAutoMajorAxisItemOnGrid( ...@@ -1085,9 +1086,10 @@ void LayoutGrid::PlaceAutoMajorAxisItemOnGrid(
for (size_t major_axis_index = major_axis_auto_placement_cursor; for (size_t major_axis_index = major_axis_auto_placement_cursor;
major_axis_index < end_of_major_axis; ++major_axis_index) { major_axis_index < end_of_major_axis; ++major_axis_index) {
GridIterator iterator(grid, AutoPlacementMajorAxisDirection(), auto iterator = grid.CreateIterator(AutoPlacementMajorAxisDirection(),
major_axis_index, minor_axis_auto_placement_cursor); major_axis_index,
empty_grid_area = iterator.NextEmptyGridArea(major_axis_span_size, minor_axis_auto_placement_cursor);
empty_grid_area = iterator->NextEmptyGridArea(major_axis_span_size,
minor_axis_span_size); minor_axis_span_size);
if (empty_grid_area) { if (empty_grid_area) {
...@@ -1135,10 +1137,10 @@ GridTrackSizingDirection LayoutGrid::AutoPlacementMinorAxisDirection() const { ...@@ -1135,10 +1137,10 @@ GridTrackSizingDirection LayoutGrid::AutoPlacementMinorAxisDirection() const {
} }
void LayoutGrid::DirtyGrid() { void LayoutGrid::DirtyGrid() {
if (grid_.NeedsItemsPlacement()) if (grid_->NeedsItemsPlacement())
return; return;
grid_.SetNeedsItemsPlacement(true); grid_->SetNeedsItemsPlacement(true);
grid_items_overflowing_grid_area_.resize(0); grid_items_overflowing_grid_area_.resize(0);
} }
...@@ -1154,8 +1156,8 @@ Vector<LayoutUnit> LayoutGrid::TrackSizesForComputedStyle( ...@@ -1154,8 +1156,8 @@ Vector<LayoutUnit> LayoutGrid::TrackSizesForComputedStyle(
if (num_positions < 2) if (num_positions < 2)
return tracks; return tracks;
DCHECK(!grid_.NeedsItemsPlacement()); DCHECK(!grid_->NeedsItemsPlacement());
bool has_collapsed_tracks = grid_.HasAutoRepeatEmptyTracks(direction); bool has_collapsed_tracks = grid_->HasAutoRepeatEmptyTracks(direction);
LayoutUnit gap = !has_collapsed_tracks ? GridGap(direction) : LayoutUnit(); LayoutUnit gap = !has_collapsed_tracks ? GridGap(direction) : LayoutUnit();
tracks.ReserveCapacity(num_positions - 1); tracks.ReserveCapacity(num_positions - 1);
for (size_t i = 0; i < num_positions - 2; ++i) for (size_t i = 0; i < num_positions - 2; ++i)
...@@ -1167,11 +1169,11 @@ Vector<LayoutUnit> LayoutGrid::TrackSizesForComputedStyle( ...@@ -1167,11 +1169,11 @@ Vector<LayoutUnit> LayoutGrid::TrackSizesForComputedStyle(
return tracks; return tracks;
size_t remaining_empty_tracks = size_t remaining_empty_tracks =
grid_.AutoRepeatEmptyTracks(direction)->size(); grid_->AutoRepeatEmptyTracks(direction)->size();
size_t last_line = tracks.size(); size_t last_line = tracks.size();
gap = GridGap(direction); gap = GridGap(direction);
for (size_t i = 1; i < last_line; ++i) { for (size_t i = 1; i < last_line; ++i) {
if (grid_.IsEmptyAutoRepeatTrack(direction, i - 1)) { if (grid_->IsEmptyAutoRepeatTrack(direction, i - 1)) {
--remaining_empty_tracks; --remaining_empty_tracks;
} else { } else {
// Remove the gap between consecutive non empty tracks. Remove it also // Remove the gap between consecutive non empty tracks. Remove it also
...@@ -1180,7 +1182,7 @@ Vector<LayoutUnit> LayoutGrid::TrackSizesForComputedStyle( ...@@ -1180,7 +1182,7 @@ Vector<LayoutUnit> LayoutGrid::TrackSizesForComputedStyle(
bool all_remaining_tracks_are_empty = bool all_remaining_tracks_are_empty =
remaining_empty_tracks == (last_line - i); remaining_empty_tracks == (last_line - i);
if (!all_remaining_tracks_are_empty || if (!all_remaining_tracks_are_empty ||
!grid_.IsEmptyAutoRepeatTrack(direction, i)) !grid_->IsEmptyAutoRepeatTrack(direction, i))
tracks[i - 1] -= gap; tracks[i - 1] -= gap;
} }
} }
...@@ -1267,7 +1269,7 @@ void LayoutGrid::LayoutGridItems() { ...@@ -1267,7 +1269,7 @@ void LayoutGrid::LayoutGridItems() {
UpdateAutoMarginsInColumnAxisIfNeeded(*child); UpdateAutoMarginsInColumnAxisIfNeeded(*child);
UpdateAutoMarginsInRowAxisIfNeeded(*child); UpdateAutoMarginsInRowAxisIfNeeded(*child);
const GridArea& area = grid_.GridItemArea(*child); const GridArea& area = grid_->GridItemArea(*child);
#if DCHECK_IS_ON() #if DCHECK_IS_ON()
DCHECK_LT(area.columns.StartLine(), DCHECK_LT(area.columns.StartLine(),
track_sizing_algorithm_.Tracks(kForColumns).size()); track_sizing_algorithm_.Tracks(kForColumns).size());
...@@ -1368,7 +1370,7 @@ LayoutUnit LayoutGrid::EstimatedGridAreaBreadthForChild( ...@@ -1368,7 +1370,7 @@ LayoutUnit LayoutGrid::EstimatedGridAreaBreadthForChild(
} }
} }
grid_area_size += GuttersSize(grid_, direction, span.StartLine(), grid_area_size += GuttersSize(*grid_, direction, span.StartLine(),
span.IntegerSpan(), available_size); span.IntegerSpan(), available_size);
GridTrackSizingDirection child_inline_direction = GridTrackSizingDirection child_inline_direction =
...@@ -1417,9 +1419,10 @@ void LayoutGrid::PopulateGridPositionsForDirection( ...@@ -1417,9 +1419,10 @@ void LayoutGrid::PopulateGridPositionsForDirection(
size_t number_of_tracks = tracks.size(); size_t number_of_tracks = tracks.size();
size_t number_of_lines = number_of_tracks + 1; size_t number_of_lines = number_of_tracks + 1;
size_t last_line = number_of_lines - 1; size_t last_line = number_of_lines - 1;
bool has_collapsed_tracks = grid_.HasAutoRepeatEmptyTracks(direction); bool has_collapsed_tracks = grid_->HasAutoRepeatEmptyTracks(direction);
size_t number_of_collapsed_tracks = size_t number_of_collapsed_tracks =
has_collapsed_tracks ? grid_.AutoRepeatEmptyTracks(direction)->size() : 0; has_collapsed_tracks ? grid_->AutoRepeatEmptyTracks(direction)->size()
: 0;
ContentAlignmentData offset = ComputeContentPositionAndDistributionOffset( ContentAlignmentData offset = ComputeContentPositionAndDistributionOffset(
direction, track_sizing_algorithm_.FreeSpace(direction).value(), direction, track_sizing_algorithm_.FreeSpace(direction).value(),
number_of_tracks - number_of_collapsed_tracks); number_of_tracks - number_of_collapsed_tracks);
...@@ -1449,7 +1452,7 @@ void LayoutGrid::PopulateGridPositionsForDirection( ...@@ -1449,7 +1452,7 @@ void LayoutGrid::PopulateGridPositionsForDirection(
LayoutUnit offset_accumulator; LayoutUnit offset_accumulator;
LayoutUnit gap_accumulator; LayoutUnit gap_accumulator;
for (size_t i = 1; i < last_line; ++i) { for (size_t i = 1; i < last_line; ++i) {
if (grid_.IsEmptyAutoRepeatTrack(direction, i - 1)) { if (grid_->IsEmptyAutoRepeatTrack(direction, i - 1)) {
--remaining_empty_tracks; --remaining_empty_tracks;
offset_accumulator += offset.distribution_offset; offset_accumulator += offset.distribution_offset;
} else { } else {
...@@ -1458,7 +1461,7 @@ void LayoutGrid::PopulateGridPositionsForDirection( ...@@ -1458,7 +1461,7 @@ void LayoutGrid::PopulateGridPositionsForDirection(
bool all_remaining_tracks_are_empty = bool all_remaining_tracks_are_empty =
remaining_empty_tracks == (last_line - i); remaining_empty_tracks == (last_line - i);
if (!all_remaining_tracks_are_empty || if (!all_remaining_tracks_are_empty ||
!grid_.IsEmptyAutoRepeatTrack(direction, i)) !grid_->IsEmptyAutoRepeatTrack(direction, i))
gap_accumulator += gap; gap_accumulator += gap;
} }
positions[i] += gap_accumulator - offset_accumulator; positions[i] += gap_accumulator - offset_accumulator;
...@@ -1651,17 +1654,17 @@ LayoutUnit LayoutGrid::BaselinePosition(FontBaseline, ...@@ -1651,17 +1654,17 @@ LayoutUnit LayoutGrid::BaselinePosition(FontBaseline,
} }
LayoutUnit LayoutGrid::FirstLineBoxBaseline() const { LayoutUnit LayoutGrid::FirstLineBoxBaseline() const {
if (IsWritingModeRoot() || !grid_.HasGridItems()) if (IsWritingModeRoot() || !grid_->HasGridItems())
return LayoutUnit(-1); return LayoutUnit(-1);
const LayoutBox* baseline_child = nullptr; const LayoutBox* baseline_child = nullptr;
const LayoutBox* first_child = nullptr; const LayoutBox* first_child = nullptr;
bool is_baseline_aligned = false; bool is_baseline_aligned = false;
// Finding the first grid item in grid order. // Finding the first grid item in grid order.
for (size_t column = 0; for (size_t column = 0;
!is_baseline_aligned && column < grid_.NumTracks(kForColumns); !is_baseline_aligned && column < grid_->NumTracks(kForColumns);
column++) { column++) {
for (size_t index = 0; index < grid_.Cell(0, column).size(); index++) { for (size_t index = 0; index < grid_->Cell(0, column).size(); index++) {
const LayoutBox* child = grid_.Cell(0, column)[index]; const LayoutBox* child = grid_->Cell(0, column)[index];
DCHECK(!child->IsOutOfFlowPositioned()); DCHECK(!child->IsOutOfFlowPositioned());
// If an item participates in baseline alignment, we select such item. // If an item participates in baseline alignment, we select such item.
if (IsBaselineAlignmentForChild(*child)) { if (IsBaselineAlignmentForChild(*child)) {
...@@ -1673,8 +1676,8 @@ LayoutUnit LayoutGrid::FirstLineBoxBaseline() const { ...@@ -1673,8 +1676,8 @@ LayoutUnit LayoutGrid::FirstLineBoxBaseline() const {
} }
if (!baseline_child) { if (!baseline_child) {
// Use dom order for items in the same cell. // Use dom order for items in the same cell.
if (!first_child || (grid_.GridItemPaintOrder(*child) < if (!first_child || (grid_->GridItemPaintOrder(*child) <
grid_.GridItemPaintOrder(*first_child))) grid_->GridItemPaintOrder(*first_child)))
first_child = child; first_child = child;
} }
} }
...@@ -1967,7 +1970,7 @@ LayoutUnit LayoutGrid::ResolveAutoStartGridPosition( ...@@ -1967,7 +1970,7 @@ LayoutUnit LayoutGrid::ResolveAutoStartGridPosition(
if (direction == kForRows || StyleRef().IsLeftToRightDirection()) if (direction == kForRows || StyleRef().IsLeftToRightDirection())
return LayoutUnit(); return LayoutUnit();
int last_line = NumTracks(kForColumns, grid_); int last_line = NumTracks(kForColumns, *grid_);
ContentPosition position = StyleRef().ResolvedJustifyContentPosition( ContentPosition position = StyleRef().ResolvedJustifyContentPosition(
ContentAlignmentNormalBehavior()); ContentAlignmentNormalBehavior());
if (position == ContentPosition::kEnd) if (position == ContentPosition::kEnd)
...@@ -1987,7 +1990,7 @@ LayoutUnit LayoutGrid::ResolveAutoEndGridPosition( ...@@ -1987,7 +1990,7 @@ LayoutUnit LayoutGrid::ResolveAutoEndGridPosition(
if (StyleRef().IsLeftToRightDirection()) if (StyleRef().IsLeftToRightDirection())
return ClientLogicalWidth(); return ClientLogicalWidth();
int last_line = NumTracks(kForColumns, grid_); int last_line = NumTracks(kForColumns, *grid_);
ContentPosition position = StyleRef().ResolvedJustifyContentPosition( ContentPosition position = StyleRef().ResolvedJustifyContentPosition(
ContentAlignmentNormalBehavior()); ContentAlignmentNormalBehavior());
if (position == ContentPosition::kEnd) if (position == ContentPosition::kEnd)
...@@ -2012,10 +2015,10 @@ LayoutUnit LayoutGrid::GridAreaBreadthForOutOfFlowChild( ...@@ -2012,10 +2015,10 @@ LayoutUnit LayoutGrid::GridAreaBreadthForOutOfFlowChild(
if (span.IsIndefinite()) if (span.IsIndefinite())
return is_row_axis ? ClientLogicalWidth() : ClientLogicalHeight(); return is_row_axis ? ClientLogicalWidth() : ClientLogicalHeight();
int smallest_start = abs(grid_.SmallestTrackStart(direction)); int smallest_start = abs(grid_->SmallestTrackStart(direction));
int start_line = span.UntranslatedStartLine() + smallest_start; int start_line = span.UntranslatedStartLine() + smallest_start;
int end_line = span.UntranslatedEndLine() + smallest_start; int end_line = span.UntranslatedEndLine() + smallest_start;
int last_line = NumTracks(direction, grid_); int last_line = NumTracks(direction, *grid_);
GridPosition start_position = direction == kForColumns GridPosition start_position = direction == kForColumns
? child.Style()->GridColumnStart() ? child.Style()->GridColumnStart()
: child.Style()->GridRowStart(); : child.Style()->GridRowStart();
...@@ -2053,8 +2056,8 @@ LayoutUnit LayoutGrid::GridAreaBreadthForOutOfFlowChild( ...@@ -2053,8 +2056,8 @@ LayoutUnit LayoutGrid::GridAreaBreadthForOutOfFlowChild(
base::Optional<LayoutUnit> available_size_for_gutters = base::Optional<LayoutUnit> available_size_for_gutters =
AvailableSpaceForGutters(direction); AvailableSpaceForGutters(direction);
if (end_line > 0 && end_line < last_line) { if (end_line > 0 && end_line < last_line) {
DCHECK(!grid_.NeedsItemsPlacement()); DCHECK(!grid_->NeedsItemsPlacement());
end -= GuttersSize(grid_, direction, end_line - 1, 2, end -= GuttersSize(*grid_, direction, end_line - 1, 2,
available_size_for_gutters); available_size_for_gutters);
end -= is_row_axis ? offset_between_columns_ : offset_between_rows_; end -= is_row_axis ? offset_between_columns_ : offset_between_rows_;
} }
...@@ -2334,8 +2337,8 @@ LayoutPoint LayoutGrid::GridAreaLogicalPosition(const GridArea& area) const { ...@@ -2334,8 +2337,8 @@ LayoutPoint LayoutGrid::GridAreaLogicalPosition(const GridArea& area) const {
void LayoutGrid::PaintChildren(const PaintInfo& paint_info, void LayoutGrid::PaintChildren(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) const { const LayoutPoint& paint_offset) const {
DCHECK(!grid_.NeedsItemsPlacement()); DCHECK(!grid_->NeedsItemsPlacement());
if (grid_.HasGridItems()) if (grid_->HasGridItems())
GridPainter(*this).PaintChildren(paint_info, paint_offset); GridPainter(*this).PaintChildren(paint_info, paint_offset);
} }
......
...@@ -57,32 +57,32 @@ class LayoutGrid final : public LayoutBlock { ...@@ -57,32 +57,32 @@ class LayoutGrid final : public LayoutBlock {
Vector<LayoutUnit> TrackSizesForComputedStyle(GridTrackSizingDirection) const; Vector<LayoutUnit> TrackSizesForComputedStyle(GridTrackSizingDirection) const;
const Vector<LayoutUnit>& ColumnPositions() const { const Vector<LayoutUnit>& ColumnPositions() const {
DCHECK(!grid_.NeedsItemsPlacement()); DCHECK(!grid_->NeedsItemsPlacement());
return column_positions_; return column_positions_;
} }
const Vector<LayoutUnit>& RowPositions() const { const Vector<LayoutUnit>& RowPositions() const {
DCHECK(!grid_.NeedsItemsPlacement()); DCHECK(!grid_->NeedsItemsPlacement());
return row_positions_; return row_positions_;
} }
const GridCell& GetGridCell(int row, int column) const { const GridCell& GetGridCell(int row, int column) const {
SECURITY_DCHECK(!grid_.NeedsItemsPlacement()); SECURITY_DCHECK(!grid_->NeedsItemsPlacement());
return grid_.Cell(row, column); return grid_->Cell(row, column);
} }
const Vector<LayoutBox*>& ItemsOverflowingGridArea() const { const Vector<LayoutBox*>& ItemsOverflowingGridArea() const {
SECURITY_DCHECK(!grid_.NeedsItemsPlacement()); SECURITY_DCHECK(!grid_->NeedsItemsPlacement());
return grid_items_overflowing_grid_area_; return grid_items_overflowing_grid_area_;
} }
int PaintIndexForGridItem(const LayoutBox* layout_box) const { int PaintIndexForGridItem(const LayoutBox* layout_box) const {
SECURITY_DCHECK(!grid_.NeedsItemsPlacement()); SECURITY_DCHECK(!grid_->NeedsItemsPlacement());
return grid_.GridItemPaintOrder(*layout_box); return grid_->GridItemPaintOrder(*layout_box);
} }
size_t AutoRepeatCountForDirection(GridTrackSizingDirection direction) const { size_t AutoRepeatCountForDirection(GridTrackSizingDirection direction) const {
return grid_.AutoRepeatTracks(direction); return grid_->AutoRepeatTracks(direction);
} }
LayoutUnit TranslateOutOfFlowRTLCoordinate(const LayoutBox&, LayoutUnit TranslateOutOfFlowRTLCoordinate(const LayoutBox&,
...@@ -310,7 +310,7 @@ class LayoutGrid final : public LayoutBlock { ...@@ -310,7 +310,7 @@ class LayoutGrid final : public LayoutBlock {
LineDirectionMode); LineDirectionMode);
static const StyleContentAlignmentData& ContentAlignmentNormalBehavior(); static const StyleContentAlignmentData& ContentAlignmentNormalBehavior();
Grid grid_; std::unique_ptr<Grid> grid_;
GridTrackSizingAlgorithm track_sizing_algorithm_; GridTrackSizingAlgorithm track_sizing_algorithm_;
Vector<LayoutUnit> row_positions_; Vector<LayoutUnit> row_positions_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment