/* * 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 "config.h" #include "core/rendering/RenderGrid.h" #include "core/rendering/RenderLayer.h" #include "core/rendering/RenderView.h" #include "core/rendering/style/GridCoordinate.h" #include "platform/LengthFunctions.h" namespace blink { static const int infinity = -1; class GridTrack { public: GridTrack() : m_usedBreadth(0) , m_maxBreadth(0) { } void growUsedBreadth(LayoutUnit growth) { ASSERT(growth >= 0); m_usedBreadth += growth; } LayoutUnit usedBreadth() const { return m_usedBreadth; } void growMaxBreadth(LayoutUnit growth) { if (m_maxBreadth == infinity) m_maxBreadth = m_usedBreadth + growth; else m_maxBreadth += growth; } LayoutUnit maxBreadthIfNotInfinite() const { return (m_maxBreadth == infinity) ? m_usedBreadth : m_maxBreadth; } LayoutUnit m_usedBreadth; LayoutUnit m_maxBreadth; }; struct GridTrackForNormalization { GridTrackForNormalization(const GridTrack& track, double flex) : m_track(&track) , m_flex(flex) , m_normalizedFlexValue(track.m_usedBreadth / flex) { } // Required by std::sort. GridTrackForNormalization& operator=(const GridTrackForNormalization& o) { m_track = o.m_track; m_flex = o.m_flex; m_normalizedFlexValue = o.m_normalizedFlexValue; return *this; } const GridTrack* m_track; double m_flex; LayoutUnit m_normalizedFlexValue; }; class RenderGrid::GridIterator { WTF_MAKE_NONCOPYABLE(GridIterator); public: // |direction| is the direction that is fixed to |fixedTrackIndex| so e.g // GridIterator(m_grid, ForColumns, 1) will walk over the rows of the 2nd column. GridIterator(const GridRepresentation& grid, GridTrackSizingDirection direction, size_t fixedTrackIndex, size_t varyingTrackIndex = 0) : m_grid(grid) , m_direction(direction) , m_rowIndex((direction == ForColumns) ? varyingTrackIndex : fixedTrackIndex) , m_columnIndex((direction == ForColumns) ? fixedTrackIndex : varyingTrackIndex) , m_childIndex(0) { ASSERT(m_rowIndex < m_grid.size()); ASSERT(m_columnIndex < m_grid[0].size()); } RenderBox* nextGridItem() { ASSERT(!m_grid.isEmpty()); size_t& varyingTrackIndex = (m_direction == ForColumns) ? m_rowIndex : m_columnIndex; const size_t endOfVaryingTrackIndex = (m_direction == ForColumns) ? m_grid.size() : m_grid[0].size(); for (; varyingTrackIndex < endOfVaryingTrackIndex; ++varyingTrackIndex) { const GridCell& children = m_grid[m_rowIndex][m_columnIndex]; if (m_childIndex < children.size()) return children[m_childIndex++]; m_childIndex = 0; } return 0; } bool checkEmptyCells(size_t rowSpan, size_t columnSpan) const { // Ignore cells outside current grid as we will grow it later if needed. size_t maxRows = std::min(m_rowIndex + rowSpan, m_grid.size()); size_t maxColumns = std::min(m_columnIndex + columnSpan, m_grid[0].size()); // This adds a O(N^2) behavior that shouldn't be a big deal as we expect spanning areas to be small. for (size_t row = m_rowIndex; row < maxRows; ++row) { for (size_t column = m_columnIndex; column < maxColumns; ++column) { const GridCell& children = m_grid[row][column]; if (!children.isEmpty()) return false; } } return true; } PassOwnPtr nextEmptyGridArea(size_t fixedTrackSpan, size_t varyingTrackSpan) { ASSERT(!m_grid.isEmpty()); ASSERT(fixedTrackSpan >= 1 && varyingTrackSpan >= 1); size_t rowSpan = (m_direction == ForColumns) ? varyingTrackSpan : fixedTrackSpan; size_t columnSpan = (m_direction == ForColumns) ? fixedTrackSpan : varyingTrackSpan; size_t& varyingTrackIndex = (m_direction == ForColumns) ? m_rowIndex : m_columnIndex; const size_t endOfVaryingTrackIndex = (m_direction == ForColumns) ? m_grid.size() : m_grid[0].size(); for (; varyingTrackIndex < endOfVaryingTrackIndex; ++varyingTrackIndex) { if (checkEmptyCells(rowSpan, columnSpan)) { OwnPtr result = adoptPtr(new GridCoordinate(GridSpan(m_rowIndex, m_rowIndex + rowSpan - 1), GridSpan(m_columnIndex, m_columnIndex + columnSpan - 1))); // Advance the iterator to avoid an infinite loop where we would return the same grid area over and over. ++varyingTrackIndex; return result.release(); } } return nullptr; } private: const GridRepresentation& m_grid; GridTrackSizingDirection m_direction; size_t m_rowIndex; size_t m_columnIndex; size_t m_childIndex; }; struct RenderGrid::GridSizingData { WTF_MAKE_NONCOPYABLE(GridSizingData); public: GridSizingData(size_t gridColumnCount, size_t gridRowCount) : columnTracks(gridColumnCount) , rowTracks(gridRowCount) { } Vector columnTracks; Vector rowTracks; Vector contentSizedTracksIndex; // Performance optimization: hold onto these Vectors until the end of Layout to avoid repeated malloc / free. Vector distributeTrackVector; Vector filteredTracks; }; RenderGrid::RenderGrid(Element* element) : RenderBlock(element) , m_gridIsDirty(true) , m_orderIterator(this) { ASSERT(!childrenInline()); } RenderGrid::~RenderGrid() { } void RenderGrid::addChild(RenderObject* newChild, RenderObject* beforeChild) { // If the new requested beforeChild is not one of our children is because it's wrapped by an anonymous container. If // we do not special case this situation we could end up calling addChild() twice for the newChild, one with the // initial beforeChild and another one with its parent. if (beforeChild && beforeChild->parent() != this) { ASSERT(beforeChild->parent()->isAnonymous()); beforeChild = splitAnonymousBoxesAroundChild(beforeChild); dirtyGrid(); } RenderBlock::addChild(newChild, beforeChild); if (gridIsDirty()) return; if (!newChild->isBox()) { dirtyGrid(); return; } // FIXME: Implement properly "stack" value in auto-placement algorithm. if (!style()->isGridAutoFlowAlgorithmStack()) { // The grid needs to be recomputed as it might contain auto-placed items that will change their position. dirtyGrid(); return; } RenderBox* newChildBox = toRenderBox(newChild); OwnPtr rowPositions = GridResolvedPosition::resolveGridPositionsFromStyle(*style(), *newChildBox, ForRows); OwnPtr columnPositions = GridResolvedPosition::resolveGridPositionsFromStyle(*style(), *newChildBox, ForColumns); if (!rowPositions || !columnPositions) { // The new child requires the auto-placement algorithm to run so we need to recompute the grid fully. dirtyGrid(); return; } else { insertItemIntoGrid(newChildBox, GridCoordinate(*rowPositions, *columnPositions)); addChildToIndexesMap(newChildBox); } } void RenderGrid::addChildToIndexesMap(RenderBox* child) { ASSERT(!m_gridItemsIndexesMap.contains(child)); RenderBox* sibling = child->nextSiblingBox(); bool lastSibling = !sibling; if (lastSibling) sibling = child->previousSiblingBox(); size_t index = 0; if (sibling) index = lastSibling ? m_gridItemsIndexesMap.get(sibling) + 1 : m_gridItemsIndexesMap.get(sibling); if (sibling && !lastSibling) { for (; sibling; sibling = sibling->nextSiblingBox()) m_gridItemsIndexesMap.set(sibling, m_gridItemsIndexesMap.get(sibling) + 1); } m_gridItemsIndexesMap.set(child, index); } void RenderGrid::removeChild(RenderObject* child) { RenderBlock::removeChild(child); if (gridIsDirty()) return; ASSERT(child->isBox()); // FIXME: Implement properly "stack" value in auto-placement algorithm. if (!style()->isGridAutoFlowAlgorithmStack()) { // The grid needs to be recomputed as it might contain auto-placed items that will change their position. dirtyGrid(); return; } const RenderBox* childBox = toRenderBox(child); GridCoordinate coordinate = m_gridItemCoordinate.take(childBox); for (GridSpan::iterator row = coordinate.rows.begin(); row != coordinate.rows.end(); ++row) { for (GridSpan::iterator column = coordinate.columns.begin(); column != coordinate.columns.end(); ++column) { GridCell& cell = m_grid[row.toInt()][column.toInt()]; cell.remove(cell.find(childBox)); } } m_gridItemsIndexesMap.remove(childBox); } void RenderGrid::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) { RenderBlock::styleDidChange(diff, oldStyle); if (!oldStyle) 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(oldStyle) || namedGridLinesDefinitionDidChange(oldStyle) || oldStyle->gridAutoFlow() != style()->gridAutoFlow()) dirtyGrid(); } bool RenderGrid::explicitGridDidResize(const RenderStyle* oldStyle) const { return oldStyle->gridTemplateColumns().size() != style()->gridTemplateColumns().size() || oldStyle->gridTemplateRows().size() != style()->gridTemplateRows().size(); } bool RenderGrid::namedGridLinesDefinitionDidChange(const RenderStyle* oldStyle) const { return oldStyle->namedGridRowLines() != style()->namedGridRowLines() || oldStyle->namedGridColumnLines() != style()->namedGridColumnLines(); } void RenderGrid::layoutBlock(bool relayoutChildren) { ASSERT(needsLayout()); if (!relayoutChildren && simplifiedLayout()) return; // FIXME: Much of this method is boiler plate that matches RenderBox::layoutBlock and Render*FlexibleBox::layoutBlock. // It would be nice to refactor some of the duplicate code. LayoutState state(*this, locationOffset()); LayoutSize previousSize = size(); setLogicalHeight(0); updateLogicalWidth(); layoutGridItems(); LayoutUnit oldClientAfterEdge = clientLogicalBottom(); updateLogicalHeight(); if (size() != previousSize) relayoutChildren = true; layoutPositionedObjects(relayoutChildren || isDocumentElement()); computeOverflow(oldClientAfterEdge); updateLayerTransformAfterLayout(); // Update our scroll information if we're overflow:auto/scroll/hidden now that we know if // we overflow or not. if (hasOverflowClip()) layer()->scrollableArea()->updateAfterLayout(); clearNeedsLayout(); } void RenderGrid::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const { const_cast(this)->placeItemsOnGrid(); GridSizingData sizingData(gridColumnCount(), gridRowCount()); LayoutUnit availableLogicalSpace = 0; const_cast(this)->computeUsedBreadthOfGridTracks(ForColumns, sizingData, availableLogicalSpace); for (size_t i = 0; i < sizingData.columnTracks.size(); ++i) { LayoutUnit minTrackBreadth = sizingData.columnTracks[i].m_usedBreadth; LayoutUnit maxTrackBreadth = sizingData.columnTracks[i].m_maxBreadth; maxTrackBreadth = std::max(maxTrackBreadth, minTrackBreadth); minLogicalWidth += minTrackBreadth; maxLogicalWidth += maxTrackBreadth; // FIXME: This should add in the scrollbarWidth (e.g. see RenderFlexibleBox). } } void RenderGrid::computePreferredLogicalWidths() { ASSERT(preferredLogicalWidthsDirty()); m_minPreferredLogicalWidth = 0; m_maxPreferredLogicalWidth = 0; // FIXME: We don't take our own logical width into account. Once we do, we need to make sure // we apply (and test the interaction with) min-width / max-width. computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth); LayoutUnit borderAndPaddingInInlineDirection = borderAndPaddingLogicalWidth(); m_minPreferredLogicalWidth += borderAndPaddingInInlineDirection; m_maxPreferredLogicalWidth += borderAndPaddingInInlineDirection; clearPreferredLogicalWidthsDirty(); } void RenderGrid::computeUsedBreadthOfGridTracks(GridTrackSizingDirection direction, GridSizingData& sizingData) { LayoutUnit availableLogicalSpace = (direction == ForColumns) ? availableLogicalWidth() : availableLogicalHeight(IncludeMarginBorderPadding); computeUsedBreadthOfGridTracks(direction, sizingData, availableLogicalSpace); } bool RenderGrid::gridElementIsShrinkToFit() { return isFloatingOrOutOfFlowPositioned(); } void RenderGrid::computeUsedBreadthOfGridTracks(GridTrackSizingDirection direction, GridSizingData& sizingData, LayoutUnit& availableLogicalSpace) { Vector& tracks = (direction == ForColumns) ? sizingData.columnTracks : sizingData.rowTracks; Vector flexibleSizedTracksIndex; sizingData.contentSizedTracksIndex.shrink(0); // 1. Initialize per Grid track variables. for (size_t i = 0; i < tracks.size(); ++i) { GridTrack& track = tracks[i]; const GridTrackSize& trackSize = gridTrackSize(direction, i); const GridLength& minTrackBreadth = trackSize.minTrackBreadth(); const GridLength& maxTrackBreadth = trackSize.maxTrackBreadth(); track.m_usedBreadth = computeUsedBreadthOfMinLength(direction, minTrackBreadth); track.m_maxBreadth = computeUsedBreadthOfMaxLength(direction, maxTrackBreadth, track.m_usedBreadth); if (track.m_maxBreadth != infinity) track.m_maxBreadth = std::max(track.m_maxBreadth, track.m_usedBreadth); if (trackSize.isContentSized()) sizingData.contentSizedTracksIndex.append(i); if (trackSize.maxTrackBreadth().isFlex()) flexibleSizedTracksIndex.append(i); } // 2. Resolve content-based TrackSizingFunctions. if (!sizingData.contentSizedTracksIndex.isEmpty()) resolveContentBasedTrackSizingFunctions(direction, sizingData, availableLogicalSpace); for (size_t i = 0; i < tracks.size(); ++i) { ASSERT(tracks[i].m_maxBreadth != infinity); availableLogicalSpace -= tracks[i].m_usedBreadth; } const bool hasUndefinedRemainingSpace = (direction == ForRows) ? style()->logicalHeight().isAuto() : gridElementIsShrinkToFit(); if (!hasUndefinedRemainingSpace && availableLogicalSpace <= 0) return; // 3. Grow all Grid tracks in GridTracks from their UsedBreadth up to their MaxBreadth value until // availableLogicalSpace (RemainingSpace in the specs) is exhausted. const size_t tracksSize = tracks.size(); if (!hasUndefinedRemainingSpace) { Vector tracksForDistribution(tracksSize); for (size_t i = 0; i < tracksSize; ++i) tracksForDistribution[i] = tracks.data() + i; distributeSpaceToTracks(tracksForDistribution, 0, &GridTrack::usedBreadth, &GridTrack::growUsedBreadth, sizingData, availableLogicalSpace); } else { for (size_t i = 0; i < tracksSize; ++i) tracks[i].m_usedBreadth = tracks[i].m_maxBreadth; } if (flexibleSizedTracksIndex.isEmpty()) return; // 4. Grow all Grid tracks having a fraction as the MaxTrackSizingFunction. double normalizedFractionBreadth = 0; if (!hasUndefinedRemainingSpace) { normalizedFractionBreadth = computeNormalizedFractionBreadth(tracks, GridSpan(0, tracks.size() - 1), direction, availableLogicalSpace); } else { for (size_t i = 0; i < flexibleSizedTracksIndex.size(); ++i) { const size_t trackIndex = flexibleSizedTracksIndex[i]; const GridTrackSize& trackSize = gridTrackSize(direction, trackIndex); normalizedFractionBreadth = std::max(normalizedFractionBreadth, tracks[trackIndex].m_usedBreadth / trackSize.maxTrackBreadth().flex()); } for (size_t i = 0; i < flexibleSizedTracksIndex.size(); ++i) { GridIterator iterator(m_grid, direction, flexibleSizedTracksIndex[i]); while (RenderBox* gridItem = iterator.nextGridItem()) { const GridCoordinate coordinate = cachedGridCoordinate(gridItem); const GridSpan span = (direction == ForColumns) ? coordinate.columns : coordinate.rows; // Do not include already processed items. if (i > 0 && span.resolvedInitialPosition.toInt() <= flexibleSizedTracksIndex[i - 1]) continue; double itemNormalizedFlexBreadth = computeNormalizedFractionBreadth(tracks, span, direction, maxContentForChild(gridItem, direction, sizingData.columnTracks)); normalizedFractionBreadth = std::max(normalizedFractionBreadth, itemNormalizedFlexBreadth); } } } for (size_t i = 0; i < flexibleSizedTracksIndex.size(); ++i) { const size_t trackIndex = flexibleSizedTracksIndex[i]; const GridTrackSize& trackSize = gridTrackSize(direction, trackIndex); tracks[trackIndex].m_usedBreadth = std::max(tracks[trackIndex].m_usedBreadth, normalizedFractionBreadth * trackSize.maxTrackBreadth().flex()); } } LayoutUnit RenderGrid::computeUsedBreadthOfMinLength(GridTrackSizingDirection direction, const GridLength& gridLength) const { if (gridLength.isFlex()) return 0; const Length& trackLength = gridLength.length(); ASSERT(!trackLength.isAuto()); if (trackLength.isSpecified()) return computeUsedBreadthOfSpecifiedLength(direction, trackLength); ASSERT(trackLength.isMinContent() || trackLength.isMaxContent()); return 0; } LayoutUnit RenderGrid::computeUsedBreadthOfMaxLength(GridTrackSizingDirection direction, const GridLength& gridLength, LayoutUnit usedBreadth) const { if (gridLength.isFlex()) return usedBreadth; const Length& trackLength = gridLength.length(); ASSERT(!trackLength.isAuto()); if (trackLength.isSpecified()) { LayoutUnit computedBreadth = computeUsedBreadthOfSpecifiedLength(direction, trackLength); ASSERT(computedBreadth != infinity); return computedBreadth; } ASSERT(trackLength.isMinContent() || trackLength.isMaxContent()); return infinity; } LayoutUnit RenderGrid::computeUsedBreadthOfSpecifiedLength(GridTrackSizingDirection direction, const Length& trackLength) const { ASSERT(trackLength.isSpecified()); // FIXME: The -1 here should be replaced by whatever the intrinsic height of the grid is. return valueForLength(trackLength, direction == ForColumns ? logicalWidth() : computeContentLogicalHeight(style()->logicalHeight(), -1)); } static bool sortByGridNormalizedFlexValue(const GridTrackForNormalization& track1, const GridTrackForNormalization& track2) { return track1.m_normalizedFlexValue < track2.m_normalizedFlexValue; } double RenderGrid::computeNormalizedFractionBreadth(Vector& tracks, const GridSpan& tracksSpan, GridTrackSizingDirection direction, LayoutUnit availableLogicalSpace) const { // |availableLogicalSpace| already accounts for the used breadths so no need to remove it here. Vector tracksForNormalization; for (GridSpan::iterator resolvedPosition = tracksSpan.begin(); resolvedPosition != tracksSpan.end(); ++resolvedPosition) { const GridTrackSize& trackSize = gridTrackSize(direction, resolvedPosition.toInt()); if (!trackSize.maxTrackBreadth().isFlex()) continue; tracksForNormalization.append(GridTrackForNormalization(tracks[resolvedPosition.toInt()], trackSize.maxTrackBreadth().flex())); } // The function is not called if we don't have grid tracks ASSERT(!tracksForNormalization.isEmpty()); std::sort(tracksForNormalization.begin(), tracksForNormalization.end(), sortByGridNormalizedFlexValue); // These values work together: as we walk over our grid tracks, we increase fractionValueBasedOnGridItemsRatio // to match a grid track's usedBreadth to ratio until the total fractions sized grid tracks wouldn't // fit into availableLogicalSpaceIgnoringFractionTracks. double accumulatedFractions = 0; LayoutUnit fractionValueBasedOnGridItemsRatio = 0; LayoutUnit availableLogicalSpaceIgnoringFractionTracks = availableLogicalSpace; for (size_t i = 0; i < tracksForNormalization.size(); ++i) { const GridTrackForNormalization& track = tracksForNormalization[i]; if (track.m_normalizedFlexValue > fractionValueBasedOnGridItemsRatio) { // If the normalized flex value (we ordered |tracksForNormalization| by increasing normalized flex value) // will make us overflow our container, then stop. We have the previous step's ratio is the best fit. if (track.m_normalizedFlexValue * accumulatedFractions > availableLogicalSpaceIgnoringFractionTracks) break; fractionValueBasedOnGridItemsRatio = track.m_normalizedFlexValue; } accumulatedFractions += track.m_flex; // This item was processed so we re-add its used breadth to the available space to accurately count the remaining space. availableLogicalSpaceIgnoringFractionTracks += track.m_track->m_usedBreadth; } return availableLogicalSpaceIgnoringFractionTracks / accumulatedFractions; } const GridTrackSize& RenderGrid::gridTrackSize(GridTrackSizingDirection direction, size_t i) const { const Vector& trackStyles = (direction == ForColumns) ? style()->gridTemplateColumns() : style()->gridTemplateRows(); if (i >= trackStyles.size()) return (direction == ForColumns) ? style()->gridAutoColumns() : style()->gridAutoRows(); const GridTrackSize& trackSize = trackStyles[i]; // If the logical width/height of the grid container is indefinite, percentage values are treated as . if (trackSize.isPercentage()) { Length logicalSize = direction == ForColumns ? style()->logicalWidth() : style()->logicalHeight(); if (logicalSize.isIntrinsicOrAuto()) { DEFINE_STATIC_LOCAL(GridTrackSize, autoTrackSize, (Length(Auto))); return autoTrackSize; } } return trackSize; } LayoutUnit RenderGrid::logicalHeightForChild(RenderBox* child, Vector& columnTracks) { SubtreeLayoutScope layoutScope(*child); LayoutUnit oldOverrideContainingBlockContentLogicalWidth = child->hasOverrideContainingBlockLogicalWidth() ? child->overrideContainingBlockContentLogicalWidth() : LayoutUnit(); LayoutUnit overrideContainingBlockContentLogicalWidth = gridAreaBreadthForChild(child, ForColumns, columnTracks); if (child->style()->logicalHeight().isPercent() || oldOverrideContainingBlockContentLogicalWidth != overrideContainingBlockContentLogicalWidth) layoutScope.setNeedsLayout(child); child->setOverrideContainingBlockContentLogicalWidth(overrideContainingBlockContentLogicalWidth); // If |child| has a percentage logical height, we shouldn't let it override its intrinsic height, which is // what we are interested in here. Thus we need to set the override logical height to -1 (no possible resolution). child->setOverrideContainingBlockContentLogicalHeight(-1); child->layoutIfNeeded(); return child->logicalHeight() + child->marginLogicalHeight(); } LayoutUnit RenderGrid::minContentForChild(RenderBox* child, GridTrackSizingDirection direction, Vector& columnTracks) { if (direction == ForColumns) { // FIXME: It's unclear if we should return the intrinsic width or the preferred width. // See http://lists.w3.org/Archives/Public/www-style/2013Jan/0245.html return child->minPreferredLogicalWidth() + marginIntrinsicLogicalWidthForChild(child); } return logicalHeightForChild(child, columnTracks); } LayoutUnit RenderGrid::maxContentForChild(RenderBox* child, GridTrackSizingDirection direction, Vector& columnTracks) { if (direction == ForColumns) { // FIXME: It's unclear if we should return the intrinsic width or the preferred width. // See http://lists.w3.org/Archives/Public/www-style/2013Jan/0245.html return child->maxPreferredLogicalWidth() + marginIntrinsicLogicalWidthForChild(child); } return logicalHeightForChild(child, columnTracks); } size_t RenderGrid::gridItemSpan(const RenderBox* child, GridTrackSizingDirection direction) { GridCoordinate childCoordinate = cachedGridCoordinate(child); GridSpan childSpan = (direction == ForRows) ? childCoordinate.rows : childCoordinate.columns; return childSpan.resolvedFinalPosition.toInt() - childSpan.resolvedInitialPosition.toInt() + 1; } typedef std::pair GridItemWithSpan; // This function sorts by span (.second in the pair) but also places pointers (.first in the pair) to the same object in // consecutive positions so duplicates could be easily removed with std::unique() for example. static bool gridItemWithSpanSorter(const GridItemWithSpan& item1, const GridItemWithSpan& item2) { if (item1.second != item2.second) return item1.second < item2.second; return item1.first < item2.first; } static bool uniquePointerInPair(const GridItemWithSpan& item1, const GridItemWithSpan& item2) { return item1.first == item2.first; } void RenderGrid::resolveContentBasedTrackSizingFunctions(GridTrackSizingDirection direction, GridSizingData& sizingData, LayoutUnit& availableLogicalSpace) { // FIXME: Split the grid tracks into groups that doesn't overlap a grid track (crbug.com/235258). for (size_t i = 0; i < sizingData.contentSizedTracksIndex.size(); ++i) { size_t trackIndex = sizingData.contentSizedTracksIndex[i]; GridIterator iterator(m_grid, direction, trackIndex); Vector itemsSortedByIncreasingSpan; while (RenderBox* gridItem = iterator.nextGridItem()) itemsSortedByIncreasingSpan.append(std::make_pair(gridItem, gridItemSpan(gridItem, direction))); std::stable_sort(itemsSortedByIncreasingSpan.begin(), itemsSortedByIncreasingSpan.end(), gridItemWithSpanSorter); Vector::iterator end = std::unique(itemsSortedByIncreasingSpan.begin(), itemsSortedByIncreasingSpan.end(), uniquePointerInPair); for (Vector::iterator it = itemsSortedByIncreasingSpan.begin(); it != end; ++it) { RenderBox* gridItem = it->first; resolveContentBasedTrackSizingFunctionsForItems(direction, sizingData, gridItem, &GridTrackSize::hasMinOrMaxContentMinTrackBreadth, &RenderGrid::minContentForChild, &GridTrack::usedBreadth, &GridTrack::growUsedBreadth); resolveContentBasedTrackSizingFunctionsForItems(direction, sizingData, gridItem, &GridTrackSize::hasMaxContentMinTrackBreadth, &RenderGrid::maxContentForChild, &GridTrack::usedBreadth, &GridTrack::growUsedBreadth); resolveContentBasedTrackSizingFunctionsForItems(direction, sizingData, gridItem, &GridTrackSize::hasMinOrMaxContentMaxTrackBreadth, &RenderGrid::minContentForChild, &GridTrack::maxBreadthIfNotInfinite, &GridTrack::growMaxBreadth); resolveContentBasedTrackSizingFunctionsForItems(direction, sizingData, gridItem, &GridTrackSize::hasMaxContentMaxTrackBreadth, &RenderGrid::maxContentForChild, &GridTrack::maxBreadthIfNotInfinite, &GridTrack::growMaxBreadth); } GridTrack& track = (direction == ForColumns) ? sizingData.columnTracks[trackIndex] : sizingData.rowTracks[trackIndex]; if (track.m_maxBreadth == infinity) track.m_maxBreadth = track.m_usedBreadth; } } void RenderGrid::resolveContentBasedTrackSizingFunctionsForItems(GridTrackSizingDirection direction, GridSizingData& sizingData, RenderBox* gridItem, FilterFunction filterFunction, SizingFunction sizingFunction, AccumulatorGetter trackGetter, AccumulatorGrowFunction trackGrowthFunction) { const GridCoordinate coordinate = cachedGridCoordinate(gridItem); const GridResolvedPosition initialTrackPosition = (direction == ForColumns) ? coordinate.columns.resolvedInitialPosition : coordinate.rows.resolvedInitialPosition; const GridResolvedPosition finalTrackPosition = (direction == ForColumns) ? coordinate.columns.resolvedFinalPosition : coordinate.rows.resolvedFinalPosition; sizingData.filteredTracks.shrink(0); for (GridResolvedPosition trackPosition = initialTrackPosition; trackPosition <= finalTrackPosition; ++trackPosition) { const GridTrackSize& trackSize = gridTrackSize(direction, trackPosition.toInt()); if (!(trackSize.*filterFunction)()) continue; GridTrack& track = (direction == ForColumns) ? sizingData.columnTracks[trackPosition.toInt()] : sizingData.rowTracks[trackPosition.toInt()]; sizingData.filteredTracks.append(&track); } if (sizingData.filteredTracks.isEmpty()) return; LayoutUnit additionalBreadthSpace = (this->*sizingFunction)(gridItem, direction, sizingData.columnTracks); for (GridResolvedPosition trackIndexForSpace = initialTrackPosition; trackIndexForSpace <= finalTrackPosition; ++trackIndexForSpace) { GridTrack& track = (direction == ForColumns) ? sizingData.columnTracks[trackIndexForSpace.toInt()] : sizingData.rowTracks[trackIndexForSpace.toInt()]; additionalBreadthSpace -= (track.*trackGetter)(); } // FIXME: We should pass different values for |tracksForGrowthAboveMaxBreadth|. // Specs mandate to floor additionalBreadthSpace (extra-space in specs) to 0. Instead we directly avoid the function // call in those cases as it will be a noop in terms of track sizing. if (additionalBreadthSpace > 0) distributeSpaceToTracks(sizingData.filteredTracks, &sizingData.filteredTracks, trackGetter, trackGrowthFunction, sizingData, additionalBreadthSpace); } static bool sortByGridTrackGrowthPotential(const GridTrack* track1, const GridTrack* track2) { if (track1->m_maxBreadth == infinity) return track2->m_maxBreadth == infinity; if (track2->m_maxBreadth == infinity) return true; return (track1->m_maxBreadth - track1->m_usedBreadth) < (track2->m_maxBreadth - track2->m_usedBreadth); } void RenderGrid::distributeSpaceToTracks(Vector& tracks, Vector* tracksForGrowthAboveMaxBreadth, AccumulatorGetter trackGetter, AccumulatorGrowFunction trackGrowthFunction, GridSizingData& sizingData, LayoutUnit& availableLogicalSpace) { ASSERT(availableLogicalSpace > 0); std::sort(tracks.begin(), tracks.end(), sortByGridTrackGrowthPotential); size_t tracksSize = tracks.size(); sizingData.distributeTrackVector.resize(tracksSize); for (size_t i = 0; i < tracksSize; ++i) { GridTrack& track = *tracks[i]; LayoutUnit availableLogicalSpaceShare = availableLogicalSpace / (tracksSize - i); LayoutUnit trackBreadth = (tracks[i]->*trackGetter)(); LayoutUnit growthShare = track.m_maxBreadth == infinity ? availableLogicalSpaceShare : std::min(availableLogicalSpaceShare, track.m_maxBreadth - trackBreadth); ASSERT(growthShare != infinity); sizingData.distributeTrackVector[i] = trackBreadth; // We should never shrink any grid track or else we can't guarantee we abide by our min-sizing function. if (growthShare > 0) { sizingData.distributeTrackVector[i] += growthShare; availableLogicalSpace -= growthShare; } } if (availableLogicalSpace > 0 && tracksForGrowthAboveMaxBreadth) { tracksSize = tracksForGrowthAboveMaxBreadth->size(); for (size_t i = 0; i < tracksSize; ++i) { LayoutUnit growthShare = availableLogicalSpace / (tracksSize - i); sizingData.distributeTrackVector[i] += growthShare; availableLogicalSpace -= growthShare; } } for (size_t i = 0; i < tracksSize; ++i) { LayoutUnit growth = sizingData.distributeTrackVector[i] - (tracks[i]->*trackGetter)(); if (growth >= 0) (tracks[i]->*trackGrowthFunction)(growth); } } #if ENABLE(ASSERT) bool RenderGrid::tracksAreWiderThanMinTrackBreadth(GridTrackSizingDirection direction, const Vector& tracks) { for (size_t i = 0; i < tracks.size(); ++i) { const GridTrackSize& trackSize = gridTrackSize(direction, i); const GridLength& minTrackBreadth = trackSize.minTrackBreadth(); if (computeUsedBreadthOfMinLength(direction, minTrackBreadth) > tracks[i].m_usedBreadth) return false; } return true; } #endif void RenderGrid::ensureGridSize(size_t maximumRowIndex, size_t maximumColumnIndex) { const size_t oldRowSize = gridRowCount(); if (maximumRowIndex >= oldRowSize) { m_grid.grow(maximumRowIndex + 1); for (size_t row = oldRowSize; row < gridRowCount(); ++row) m_grid[row].grow(gridColumnCount()); } if (maximumColumnIndex >= gridColumnCount()) { for (size_t row = 0; row < gridRowCount(); ++row) m_grid[row].grow(maximumColumnIndex + 1); } } void RenderGrid::insertItemIntoGrid(RenderBox* child, const GridCoordinate& coordinate) { ensureGridSize(coordinate.rows.resolvedFinalPosition.toInt(), coordinate.columns.resolvedFinalPosition.toInt()); for (GridSpan::iterator row = coordinate.rows.begin(); row != coordinate.rows.end(); ++row) { for (GridSpan::iterator column = coordinate.columns.begin(); column != coordinate.columns.end(); ++column) m_grid[row.toInt()][column.toInt()].append(child); } RELEASE_ASSERT(!m_gridItemCoordinate.contains(child)); m_gridItemCoordinate.set(child, coordinate); } void RenderGrid::placeItemsOnGrid() { if (!gridIsDirty()) return; ASSERT(m_gridItemCoordinate.isEmpty()); populateExplicitGridAndOrderIterator(); // We clear the dirty bit here as the grid sizes have been updated, this means // that we can safely call gridRowCount() / gridColumnCount(). m_gridIsDirty = false; Vector autoMajorAxisAutoGridItems; Vector specifiedMajorAxisAutoGridItems; for (RenderBox* child = m_orderIterator.first(); child; child = m_orderIterator.next()) { // FIXME: We never re-resolve positions if the grid is grown during auto-placement which may lead auto / // positions to not match the author's intent. The specification is unclear on what should be done in this case. OwnPtr rowPositions = GridResolvedPosition::resolveGridPositionsFromStyle(*style(), *child, ForRows); OwnPtr columnPositions = GridResolvedPosition::resolveGridPositionsFromStyle(*style(), *child, ForColumns); if (!rowPositions || !columnPositions) { GridSpan* majorAxisPositions = (autoPlacementMajorAxisDirection() == ForColumns) ? columnPositions.get() : rowPositions.get(); if (!majorAxisPositions) autoMajorAxisAutoGridItems.append(child); else specifiedMajorAxisAutoGridItems.append(child); continue; } insertItemIntoGrid(child, GridCoordinate(*rowPositions, *columnPositions)); } ASSERT(gridRowCount() >= style()->gridTemplateRows().size()); ASSERT(gridColumnCount() >= style()->gridTemplateColumns().size()); // FIXME: Implement properly "stack" value in auto-placement algorithm. if (style()->isGridAutoFlowAlgorithmStack()) { // If we did collect some grid items, they won't be placed thus never laid out. ASSERT(!autoMajorAxisAutoGridItems.size()); ASSERT(!specifiedMajorAxisAutoGridItems.size()); return; } placeSpecifiedMajorAxisItemsOnGrid(specifiedMajorAxisAutoGridItems); placeAutoMajorAxisItemsOnGrid(autoMajorAxisAutoGridItems); m_grid.shrinkToFit(); } void RenderGrid::populateExplicitGridAndOrderIterator() { OrderIteratorPopulator populator(m_orderIterator); size_t maximumRowIndex = std::max(1, GridResolvedPosition::explicitGridRowCount(*style())); size_t maximumColumnIndex = std::max(1, GridResolvedPosition::explicitGridColumnCount(*style())); ASSERT(m_gridItemsIndexesMap.isEmpty()); size_t childIndex = 0; for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { populator.collectChild(child); m_gridItemsIndexesMap.set(child, childIndex++); // This function bypasses the cache (cachedGridCoordinate()) as it is used to build it. OwnPtr rowPositions = GridResolvedPosition::resolveGridPositionsFromStyle(*style(), *child, ForRows); OwnPtr columnPositions = GridResolvedPosition::resolveGridPositionsFromStyle(*style(), *child, ForColumns); // |positions| is 0 if we need to run the auto-placement algorithm. if (rowPositions) { maximumRowIndex = std::max(maximumRowIndex, rowPositions->resolvedFinalPosition.next().toInt()); } else { // Grow the grid for items with a definite row span, getting the largest such span. GridSpan positions = GridResolvedPosition::resolveGridPositionsFromAutoPlacementPosition(*style(), *child, ForRows, GridResolvedPosition(0)); maximumRowIndex = std::max(maximumRowIndex, positions.resolvedFinalPosition.next().toInt()); } if (columnPositions) { maximumColumnIndex = std::max(maximumColumnIndex, columnPositions->resolvedFinalPosition.next().toInt()); } else { // Grow the grid for items with a definite column span, getting the largest such span. GridSpan positions = GridResolvedPosition::resolveGridPositionsFromAutoPlacementPosition(*style(), *child, ForColumns, GridResolvedPosition(0)); maximumColumnIndex = std::max(maximumColumnIndex, positions.resolvedFinalPosition.next().toInt()); } } m_grid.grow(maximumRowIndex); for (size_t i = 0; i < m_grid.size(); ++i) m_grid[i].grow(maximumColumnIndex); } PassOwnPtr RenderGrid::createEmptyGridAreaAtSpecifiedPositionsOutsideGrid(const RenderBox* gridItem, GridTrackSizingDirection specifiedDirection, const GridSpan& specifiedPositions) const { GridTrackSizingDirection crossDirection = specifiedDirection == ForColumns ? ForRows : ForColumns; const size_t endOfCrossDirection = crossDirection == ForColumns ? gridColumnCount() : gridRowCount(); GridSpan crossDirectionPositions = GridResolvedPosition::resolveGridPositionsFromAutoPlacementPosition(*style(), *gridItem, crossDirection, GridResolvedPosition(endOfCrossDirection)); return adoptPtr(new GridCoordinate(specifiedDirection == ForColumns ? crossDirectionPositions : specifiedPositions, specifiedDirection == ForColumns ? specifiedPositions : crossDirectionPositions)); } void RenderGrid::placeSpecifiedMajorAxisItemsOnGrid(const Vector& autoGridItems) { for (size_t i = 0; i < autoGridItems.size(); ++i) { OwnPtr majorAxisPositions = GridResolvedPosition::resolveGridPositionsFromStyle(*style(), *autoGridItems[i], autoPlacementMajorAxisDirection()); GridSpan minorAxisPositions = GridResolvedPosition::resolveGridPositionsFromAutoPlacementPosition(*style(), *autoGridItems[i], autoPlacementMinorAxisDirection(), GridResolvedPosition(0)); GridIterator iterator(m_grid, autoPlacementMajorAxisDirection(), majorAxisPositions->resolvedInitialPosition.toInt()); OwnPtr emptyGridArea = iterator.nextEmptyGridArea(majorAxisPositions->integerSpan(), minorAxisPositions.integerSpan()); if (!emptyGridArea) emptyGridArea = createEmptyGridAreaAtSpecifiedPositionsOutsideGrid(autoGridItems[i], autoPlacementMajorAxisDirection(), *majorAxisPositions); insertItemIntoGrid(autoGridItems[i], *emptyGridArea); } } void RenderGrid::placeAutoMajorAxisItemsOnGrid(const Vector& autoGridItems) { std::pair autoPlacementCursor = std::make_pair(0, 0); bool isGridAutoFlowDense = style()->isGridAutoFlowAlgorithmDense(); for (size_t i = 0; i < autoGridItems.size(); ++i) { placeAutoMajorAxisItemOnGrid(autoGridItems[i], autoPlacementCursor); // If grid-auto-flow is dense, reset auto-placement cursor. if (isGridAutoFlowDense) { autoPlacementCursor.first = 0; autoPlacementCursor.second = 0; } } } void RenderGrid::placeAutoMajorAxisItemOnGrid(RenderBox* gridItem, std::pair& autoPlacementCursor) { OwnPtr minorAxisPositions = GridResolvedPosition::resolveGridPositionsFromStyle(*style(), *gridItem, autoPlacementMinorAxisDirection()); ASSERT(!GridResolvedPosition::resolveGridPositionsFromStyle(*style(), *gridItem, autoPlacementMajorAxisDirection())); GridSpan majorAxisPositions = GridResolvedPosition::resolveGridPositionsFromAutoPlacementPosition(*style(), *gridItem, autoPlacementMajorAxisDirection(), GridResolvedPosition(0)); const size_t endOfMajorAxis = (autoPlacementMajorAxisDirection() == ForColumns) ? gridColumnCount() : gridRowCount(); size_t majorAxisAutoPlacementCursor = autoPlacementMajorAxisDirection() == ForColumns ? autoPlacementCursor.second : autoPlacementCursor.first; size_t minorAxisAutoPlacementCursor = autoPlacementMajorAxisDirection() == ForColumns ? autoPlacementCursor.first : autoPlacementCursor.second; OwnPtr emptyGridArea; if (minorAxisPositions) { // Move to the next track in major axis if initial position in minor axis is before auto-placement cursor. if (minorAxisPositions->resolvedInitialPosition.toInt() < minorAxisAutoPlacementCursor) majorAxisAutoPlacementCursor++; if (majorAxisAutoPlacementCursor < endOfMajorAxis) { GridIterator iterator(m_grid, autoPlacementMinorAxisDirection(), minorAxisPositions->resolvedInitialPosition.toInt(), majorAxisAutoPlacementCursor); emptyGridArea = iterator.nextEmptyGridArea(minorAxisPositions->integerSpan(), majorAxisPositions.integerSpan()); } if (!emptyGridArea) emptyGridArea = createEmptyGridAreaAtSpecifiedPositionsOutsideGrid(gridItem, autoPlacementMinorAxisDirection(), *minorAxisPositions); } else { GridSpan minorAxisPositions = GridResolvedPosition::resolveGridPositionsFromAutoPlacementPosition(*style(), *gridItem, autoPlacementMinorAxisDirection(), GridResolvedPosition(0)); for (size_t majorAxisIndex = majorAxisAutoPlacementCursor; majorAxisIndex < endOfMajorAxis; ++majorAxisIndex) { GridIterator iterator(m_grid, autoPlacementMajorAxisDirection(), majorAxisIndex, minorAxisAutoPlacementCursor); emptyGridArea = iterator.nextEmptyGridArea(majorAxisPositions.integerSpan(), minorAxisPositions.integerSpan()); if (emptyGridArea) { // Check that it fits in the minor axis direction, as we shouldn't grow in that direction here (it was already managed in populateExplicitGridAndOrderIterator()). GridResolvedPosition minorAxisFinalPositionIndex = autoPlacementMinorAxisDirection() == ForColumns ? emptyGridArea->columns.resolvedFinalPosition : emptyGridArea->rows.resolvedFinalPosition; const size_t endOfMinorAxis = autoPlacementMinorAxisDirection() == ForColumns ? gridColumnCount() : gridRowCount(); if (minorAxisFinalPositionIndex.toInt() < endOfMinorAxis) break; // Discard empty grid area as it does not fit in the minor axis direction. // We don't need to create a new empty grid area yet as we might find a valid one in the next iteration. emptyGridArea = nullptr; } // As we're moving to the next track in the major axis we should reset the auto-placement cursor in the minor axis. minorAxisAutoPlacementCursor = 0; } if (!emptyGridArea) emptyGridArea = createEmptyGridAreaAtSpecifiedPositionsOutsideGrid(gridItem, autoPlacementMinorAxisDirection(), minorAxisPositions); } insertItemIntoGrid(gridItem, *emptyGridArea); // Move auto-placement cursor to the new position. autoPlacementCursor.first = emptyGridArea->rows.resolvedInitialPosition.toInt(); autoPlacementCursor.second = emptyGridArea->columns.resolvedInitialPosition.toInt(); } GridTrackSizingDirection RenderGrid::autoPlacementMajorAxisDirection() const { return style()->isGridAutoFlowDirectionColumn() ? ForColumns : ForRows; } GridTrackSizingDirection RenderGrid::autoPlacementMinorAxisDirection() const { return style()->isGridAutoFlowDirectionColumn() ? ForRows : ForColumns; } void RenderGrid::dirtyGrid() { m_grid.resize(0); m_gridItemCoordinate.clear(); m_gridIsDirty = true; m_gridItemsOverflowingGridArea.resize(0); m_gridItemsIndexesMap.clear(); } void RenderGrid::layoutGridItems() { placeItemsOnGrid(); GridSizingData sizingData(gridColumnCount(), gridRowCount()); computeUsedBreadthOfGridTracks(ForColumns, sizingData); ASSERT(tracksAreWiderThanMinTrackBreadth(ForColumns, sizingData.columnTracks)); computeUsedBreadthOfGridTracks(ForRows, sizingData); ASSERT(tracksAreWiderThanMinTrackBreadth(ForRows, sizingData.rowTracks)); populateGridPositions(sizingData); m_gridItemsOverflowingGridArea.resize(0); for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { // Because the grid area cannot be styled, we don't need to adjust // the grid breadth to account for 'box-sizing'. LayoutUnit oldOverrideContainingBlockContentLogicalWidth = child->hasOverrideContainingBlockLogicalWidth() ? child->overrideContainingBlockContentLogicalWidth() : LayoutUnit(); LayoutUnit oldOverrideContainingBlockContentLogicalHeight = child->hasOverrideContainingBlockLogicalHeight() ? child->overrideContainingBlockContentLogicalHeight() : LayoutUnit(); LayoutUnit overrideContainingBlockContentLogicalWidth = gridAreaBreadthForChild(child, ForColumns, sizingData.columnTracks); LayoutUnit overrideContainingBlockContentLogicalHeight = gridAreaBreadthForChild(child, ForRows, sizingData.rowTracks); SubtreeLayoutScope layoutScope(*child); if (oldOverrideContainingBlockContentLogicalWidth != overrideContainingBlockContentLogicalWidth || (oldOverrideContainingBlockContentLogicalHeight != overrideContainingBlockContentLogicalHeight && child->hasRelativeLogicalHeight())) layoutScope.setNeedsLayout(child); child->setOverrideContainingBlockContentLogicalWidth(overrideContainingBlockContentLogicalWidth); child->setOverrideContainingBlockContentLogicalHeight(overrideContainingBlockContentLogicalHeight); // FIXME: Grid items should stretch to fill their cells. Once we // implement grid-{column,row}-align, we can also shrink to fit. For // now, just size as if we were a regular child. child->layoutIfNeeded(); #if ENABLE(ASSERT) const GridCoordinate& coordinate = cachedGridCoordinate(child); ASSERT(coordinate.columns.resolvedInitialPosition.toInt() < sizingData.columnTracks.size()); ASSERT(coordinate.rows.resolvedInitialPosition.toInt() < sizingData.rowTracks.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 if (child->logicalHeight() > overrideContainingBlockContentLogicalHeight || child->logicalWidth() > overrideContainingBlockContentLogicalWidth) m_gridItemsOverflowingGridArea.append(child); } for (size_t i = 0; i < sizingData.rowTracks.size(); ++i) setLogicalHeight(logicalHeight() + sizingData.rowTracks[i].m_usedBreadth); // Min / max logical height is handled by the call to updateLogicalHeight in layoutBlock. setLogicalHeight(logicalHeight() + borderAndPaddingLogicalHeight()); } GridCoordinate RenderGrid::cachedGridCoordinate(const RenderBox* gridItem) const { ASSERT(m_gridItemCoordinate.contains(gridItem)); return m_gridItemCoordinate.get(gridItem); } LayoutUnit RenderGrid::gridAreaBreadthForChild(const RenderBox* child, GridTrackSizingDirection direction, const Vector& tracks) const { const GridCoordinate& coordinate = cachedGridCoordinate(child); const GridSpan& span = (direction == ForColumns) ? coordinate.columns : coordinate.rows; LayoutUnit gridAreaBreadth = 0; for (GridSpan::iterator trackPosition = span.begin(); trackPosition != span.end(); ++trackPosition) gridAreaBreadth += tracks[trackPosition.toInt()].m_usedBreadth; return gridAreaBreadth; } void RenderGrid::populateGridPositions(const GridSizingData& sizingData) { m_columnPositions.resize(sizingData.columnTracks.size() + 1); m_columnPositions[0] = borderAndPaddingStart(); for (size_t i = 0; i < m_columnPositions.size() - 1; ++i) m_columnPositions[i + 1] = m_columnPositions[i] + sizingData.columnTracks[i].m_usedBreadth; m_rowPositions.resize(sizingData.rowTracks.size() + 1); m_rowPositions[0] = borderAndPaddingBefore(); for (size_t i = 0; i < m_rowPositions.size() - 1; ++i) m_rowPositions[i + 1] = m_rowPositions[i] + sizingData.rowTracks[i].m_usedBreadth; } LayoutUnit RenderGrid::startOfColumnForChild(const RenderBox* child) const { const GridCoordinate& coordinate = cachedGridCoordinate(child); LayoutUnit startOfColumn = m_columnPositions[coordinate.columns.resolvedInitialPosition.toInt()]; // The grid items should be inside the grid container's border box, that's why they need to be shifted. // FIXME: This should account for the grid item's . return startOfColumn + marginStartForChild(child); } LayoutUnit RenderGrid::endOfColumnForChild(const RenderBox* child) const { const GridCoordinate& coordinate = cachedGridCoordinate(child); LayoutUnit startOfColumn = m_columnPositions[coordinate.columns.resolvedInitialPosition.toInt()]; // The grid items should be inside the grid container's border box, that's why they need to be shifted. LayoutUnit columnPosition = startOfColumn + marginStartForChild(child); LayoutUnit endOfColumn = m_columnPositions[coordinate.columns.resolvedFinalPosition.next().toInt()]; // FIXME: This should account for the grid item's . return columnPosition + std::max(0, endOfColumn - m_columnPositions[coordinate.columns.resolvedInitialPosition.toInt()] - child->logicalWidth()); } LayoutUnit RenderGrid::columnPositionAlignedWithGridContainerStart(const RenderBox* child) const { if (style()->isLeftToRightDirection()) return startOfColumnForChild(child); return endOfColumnForChild(child); } LayoutUnit RenderGrid::columnPositionAlignedWithGridContainerEnd(const RenderBox* child) const { if (!style()->isLeftToRightDirection()) return startOfColumnForChild(child); return endOfColumnForChild(child); } LayoutUnit RenderGrid::centeredColumnPositionForChild(const RenderBox* child) const { const GridCoordinate& coordinate = cachedGridCoordinate(child); LayoutUnit startOfColumn = m_columnPositions[coordinate.columns.resolvedInitialPosition.toInt()]; LayoutUnit endOfColumn = m_columnPositions[coordinate.columns.resolvedFinalPosition.next().toInt()]; LayoutUnit columnPosition = startOfColumn + marginStartForChild(child); // FIXME: This should account for the grid item's . return columnPosition + std::max(0, endOfColumn - startOfColumn - child->logicalWidth()) / 2; } static ItemPosition resolveJustification(const RenderStyle* parentStyle, const RenderStyle* childStyle) { ItemPosition justify = childStyle->justifySelf(); if (justify == ItemPositionAuto) justify = (parentStyle->justifyItems() == ItemPositionAuto) ? ItemPositionStretch : parentStyle->justifyItems(); return justify; } LayoutUnit RenderGrid::columnPositionForChild(const RenderBox* child) const { switch (resolveJustification(style(), child->style())) { case ItemPositionSelfStart: // self-start is based on the child's direction. That's why we need to check against the grid container's direction. if (child->style()->direction() != style()->direction()) return columnPositionAlignedWithGridContainerEnd(child); return columnPositionAlignedWithGridContainerStart(child); case ItemPositionSelfEnd: // self-end is based on the child's direction. That's why we need to check against the grid container's direction. if (child->style()->direction() != style()->direction()) return columnPositionAlignedWithGridContainerStart(child); return columnPositionAlignedWithGridContainerEnd(child); case ItemPositionFlexStart: // Only used in flex layout, for other layout, it's equivalent to 'start'. return columnPositionAlignedWithGridContainerStart(child); case ItemPositionFlexEnd: // Only used in flex layout, for other layout, it's equivalent to 'start'. return columnPositionAlignedWithGridContainerEnd(child); case ItemPositionLeft: if (style()->isLeftToRightDirection()) return columnPositionAlignedWithGridContainerStart(child); return columnPositionAlignedWithGridContainerEnd(child); case ItemPositionRight: if (style()->isLeftToRightDirection()) return columnPositionAlignedWithGridContainerEnd(child); return columnPositionAlignedWithGridContainerStart(child); case ItemPositionCenter: return centeredColumnPositionForChild(child); case ItemPositionStart: return columnPositionAlignedWithGridContainerStart(child); case ItemPositionEnd: return columnPositionAlignedWithGridContainerEnd(child); case ItemPositionAuto: break; case ItemPositionStretch: case ItemPositionBaseline: case ItemPositionLastBaseline: // FIXME: Implement the previous values. For now, we always start align the child. return startOfColumnForChild(child); } ASSERT_NOT_REACHED(); return 0; } LayoutUnit RenderGrid::endOfRowForChild(const RenderBox* child) const { const GridCoordinate& coordinate = cachedGridCoordinate(child); LayoutUnit startOfRow = m_rowPositions[coordinate.rows.resolvedInitialPosition.toInt()]; // The grid items should be inside the grid container's border box, that's why they need to be shifted. LayoutUnit rowPosition = startOfRow + marginBeforeForChild(child); LayoutUnit endOfRow = m_rowPositions[coordinate.rows.resolvedFinalPosition.next().toInt()]; // FIXME: This should account for the grid item's . return rowPosition + std::max(0, endOfRow - startOfRow - child->logicalHeight()); } LayoutUnit RenderGrid::startOfRowForChild(const RenderBox* child) const { const GridCoordinate& coordinate = cachedGridCoordinate(child); LayoutUnit startOfRow = m_rowPositions[coordinate.rows.resolvedInitialPosition.toInt()]; // The grid items should be inside the grid container's border box, that's why they need to be shifted. // FIXME: This should account for the grid item's . LayoutUnit rowPosition = startOfRow + marginBeforeForChild(child); return rowPosition; } LayoutUnit RenderGrid::centeredRowPositionForChild(const RenderBox* child) const { const GridCoordinate& coordinate = cachedGridCoordinate(child); // The grid items should be inside the grid container's border box, that's why they need to be shifted. LayoutUnit startOfRow = m_rowPositions[coordinate.rows.resolvedInitialPosition.toInt()] + marginBeforeForChild(child); LayoutUnit endOfRow = m_rowPositions[coordinate.rows.resolvedFinalPosition.next().toInt()]; // FIXME: This should account for the grid item's . return startOfRow + std::max(0, endOfRow - startOfRow - child->logicalHeight()) / 2; } // FIXME: We should move this logic to the StyleAdjuster or the StyleBuilder. static ItemPosition resolveAlignment(const RenderStyle* parentStyle, const RenderStyle* childStyle) { ItemPosition align = childStyle->alignSelf(); // The auto keyword computes to the parent's align-items computed value, or to "stretch", if not set or "auto". if (align == ItemPositionAuto) align = (parentStyle->alignItems() == ItemPositionAuto) ? ItemPositionStretch : parentStyle->alignItems(); return align; } LayoutUnit RenderGrid::rowPositionForChild(const RenderBox* child) const { ItemPosition alignSelf = resolveAlignment(style(), child->style()); switch (alignSelf) { case ItemPositionSelfStart: return startOfRowForChild(child); case ItemPositionSelfEnd: return endOfRowForChild(child); case ItemPositionLeft: // orthogonal modes make property and inline axes to be parallel, but in any case // this is always equivalent to 'Start'. // // self-align's axis is never parallel to the inline axis, except in orthogonal // writing-mode, so this is equivalent to 'Start’. return startOfRowForChild(child); case ItemPositionRight: // self-align's axis is never parallel to the inline axis, except in orthogonal // writing-mode, so this is equivalent to 'Start'. return startOfRowForChild(child); case ItemPositionCenter: return centeredRowPositionForChild(child); // Only used in flex layout, for other layout, it's equivalent to 'Start'. case ItemPositionFlexStart: case ItemPositionStart: return startOfRowForChild(child); // Only used in flex layout, for other layout, it's equivalent to 'End'. case ItemPositionFlexEnd: case ItemPositionEnd: return endOfRowForChild(child); case ItemPositionStretch: // FIXME: Implement the Stretch value. For now, we always start align the child. return startOfRowForChild(child); case ItemPositionBaseline: case ItemPositionLastBaseline: // FIXME: Implement the ItemPositionBaseline value. For now, we always start align the child. return startOfRowForChild(child); case ItemPositionAuto: break; } ASSERT_NOT_REACHED(); return 0; } LayoutPoint RenderGrid::findChildLogicalPosition(const RenderBox* child) const { return LayoutPoint(columnPositionForChild(child), rowPositionForChild(child)); } static GridSpan dirtiedGridAreas(const Vector& coordinates, LayoutUnit start, LayoutUnit end) { // This function does a binary search over the coordinates. // This doesn't work with grid items overflowing their grid areas, but that is managed with m_gridItemsOverflowingGridArea. size_t startGridAreaIndex = std::upper_bound(coordinates.begin(), coordinates.end() - 1, start) - coordinates.begin(); if (startGridAreaIndex > 0) --startGridAreaIndex; size_t endGridAreaIndex = std::upper_bound(coordinates.begin() + startGridAreaIndex, coordinates.end() - 1, end) - coordinates.begin(); if (endGridAreaIndex > 0) --endGridAreaIndex; return GridSpan(startGridAreaIndex, endGridAreaIndex); } class GridItemsSorter { public: bool operator()(const std::pair& firstChild, const std::pair& secondChild) const { if (firstChild.first->style()->order() != secondChild.first->style()->order()) return firstChild.first->style()->order() < secondChild.first->style()->order(); return firstChild.second < secondChild.second; } }; void RenderGrid::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { ASSERT_WITH_SECURITY_IMPLICATION(!gridIsDirty()); LayoutRect localPaintInvalidationRect = paintInfo.rect; localPaintInvalidationRect.moveBy(-paintOffset); GridSpan dirtiedColumns = dirtiedGridAreas(m_columnPositions, localPaintInvalidationRect.x(), localPaintInvalidationRect.maxX()); GridSpan dirtiedRows = dirtiedGridAreas(m_rowPositions, localPaintInvalidationRect.y(), localPaintInvalidationRect.maxY()); Vector > gridItemsToBePainted; for (GridSpan::iterator row = dirtiedRows.begin(); row != dirtiedRows.end(); ++row) { for (GridSpan::iterator column = dirtiedColumns.begin(); column != dirtiedColumns.end(); ++column) { const Vector& children = m_grid[row.toInt()][column.toInt()]; for (size_t j = 0; j < children.size(); ++j) gridItemsToBePainted.append(std::make_pair(children[j], m_gridItemsIndexesMap.get(children[j]))); } } for (Vector::const_iterator it = m_gridItemsOverflowingGridArea.begin(); it != m_gridItemsOverflowingGridArea.end(); ++it) { if ((*it)->frameRect().intersects(localPaintInvalidationRect)) gridItemsToBePainted.append(std::make_pair(*it, m_gridItemsIndexesMap.get(*it))); } // Sort grid items following order-modified document order. // See http://www.w3.org/TR/css-flexbox/#order-modified-document-order std::stable_sort(gridItemsToBePainted.begin(), gridItemsToBePainted.end(), GridItemsSorter()); RenderBox* previous = 0; for (Vector >::const_iterator it = gridItemsToBePainted.begin(); it != gridItemsToBePainted.end(); ++it) { // We might have duplicates because of spanning children are included in all cells they span. // Skip them here to avoid painting items several times. RenderBox* current = (*it).first; if (current == previous) continue; paintChild(current, paintInfo, paintOffset); previous = current; } } const char* RenderGrid::renderName() const { if (isFloating()) return "RenderGrid (floating)"; if (isOutOfFlowPositioned()) return "RenderGrid (positioned)"; if (isAnonymous()) return "RenderGrid (generated)"; if (isRelPositioned()) return "RenderGrid (relative positioned)"; return "RenderGrid"; } } // namespace blink