[css-grid] Support positioned grid children
[WebKit-https.git] / Source / WebCore / rendering / style / GridResolvedPosition.cpp
1 /*
2  * Copyright (C) 2014 Igalia S.L.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "GridResolvedPosition.h"
33
34 #if ENABLE(CSS_GRID_LAYOUT)
35
36 #include "GridCoordinate.h"
37 #include "RenderBox.h"
38
39 namespace WebCore {
40
41 static inline bool isColumnSide(GridPositionSide side)
42 {
43     return side == ColumnStartSide || side == ColumnEndSide;
44 }
45
46 static inline bool isStartSide(GridPositionSide side)
47 {
48     return side == ColumnStartSide || side == RowStartSide;
49 }
50
51 static const NamedGridLinesMap& gridLinesForSide(const RenderStyle& style, GridPositionSide side)
52 {
53     return isColumnSide(side) ? style.namedGridColumnLines() : style.namedGridRowLines();
54 }
55
56 static const String implicitNamedGridLineForSide(const String& lineName, GridPositionSide side)
57 {
58     return lineName + (isStartSide(side) ? "-start" : "-end");
59 }
60
61 bool GridResolvedPosition::isNonExistentNamedLineOrArea(const String& lineName, const RenderStyle& style, GridPositionSide side)
62 {
63     const NamedGridLinesMap& gridLineNames = gridLinesForSide(style, side);
64     return !gridLineNames.contains(implicitNamedGridLineForSide(lineName, side)) && !gridLineNames.contains(lineName);
65 }
66
67 bool GridUnresolvedSpan::requiresAutoPlacement() const
68 {
69     return m_initialPosition.shouldBeResolvedAgainstOppositePosition() && m_finalPosition.shouldBeResolvedAgainstOppositePosition();
70 }
71
72 void GridUnresolvedSpan::adjustGridPositionsFromStyle(const RenderStyle& gridContainerStyle)
73 {
74     ASSERT(isColumnSide(m_initialPositionSide) == isColumnSide(m_finalPositionSide));
75
76     // We must handle the placement error handling code here instead of in the StyleAdjuster because we don't want to
77     // overwrite the specified values.
78     if (m_initialPosition.isSpan() && m_finalPosition.isSpan())
79         m_finalPosition.setAutoPosition();
80
81     // Try to early detect the case of non existing named grid lines. This way we could assume later that
82     // GridResolvedPosition::resolveGrisPositionFromStyle() won't require the autoplacement to run, i.e., it'll always return a
83     // valid resolved position.
84     if (m_initialPosition.isNamedGridArea() && GridResolvedPosition::isNonExistentNamedLineOrArea(m_initialPosition.namedGridLine(), gridContainerStyle, m_initialPositionSide))
85         m_initialPosition.setAutoPosition();
86
87     if (m_finalPosition.isNamedGridArea() && GridResolvedPosition::isNonExistentNamedLineOrArea(m_finalPosition.namedGridLine(), gridContainerStyle, m_finalPositionSide))
88         m_finalPosition.setAutoPosition();
89
90     // If the grid item has an automatic position and a grid span for a named line in a given dimension, instead treat the grid span as one.
91     if (m_initialPosition.isAuto() && m_finalPosition.isSpan() && !m_finalPosition.namedGridLine().isNull())
92         m_finalPosition.setSpanPosition(1, String());
93     if (m_finalPosition.isAuto() && m_initialPosition.isSpan() && !m_initialPosition.namedGridLine().isNull())
94         m_initialPosition.setSpanPosition(1, String());
95 }
96
97 unsigned GridResolvedPosition::explicitGridColumnCount(const RenderStyle& gridContainerStyle)
98 {
99     return std::min<unsigned>(gridContainerStyle.gridColumns().size(), kGridMaxTracks);
100 }
101
102 unsigned GridResolvedPosition::explicitGridRowCount(const RenderStyle& gridContainerStyle)
103 {
104     return std::min<unsigned>(gridContainerStyle.gridRows().size(), kGridMaxTracks);
105 }
106
107 static unsigned explicitGridSizeForSide(const RenderStyle& gridContainerStyle, GridPositionSide side)
108 {
109     return isColumnSide(side) ? GridResolvedPosition::explicitGridColumnCount(gridContainerStyle) : GridResolvedPosition::explicitGridRowCount(gridContainerStyle);
110 }
111
112 static GridResolvedPosition adjustGridPositionForRowEndColumnEndSide(unsigned resolvedPosition)
113 {
114     return resolvedPosition ? GridResolvedPosition(resolvedPosition - 1) : GridResolvedPosition(0);
115 }
116
117 static GridResolvedPosition adjustGridPositionForSide(unsigned resolvedPosition, GridPositionSide side)
118 {
119     // An item finishing on the N-th line belongs to the N-1-th cell.
120     if (side == ColumnEndSide || side == RowEndSide)
121         return adjustGridPositionForRowEndColumnEndSide(resolvedPosition);
122
123     return GridResolvedPosition(resolvedPosition);
124 }
125
126 static GridResolvedPosition resolveNamedGridLinePositionFromStyle(const RenderStyle& gridContainerStyle, const GridPosition& position, GridPositionSide side)
127 {
128     ASSERT(!position.namedGridLine().isNull());
129
130     const NamedGridLinesMap& gridLinesNames = isColumnSide(side) ? gridContainerStyle.namedGridColumnLines() : gridContainerStyle.namedGridRowLines();
131     NamedGridLinesMap::const_iterator it = gridLinesNames.find(position.namedGridLine());
132     if (it == gridLinesNames.end()) {
133         if (position.isPositive())
134             return 0;
135         const unsigned lastLine = explicitGridSizeForSide(gridContainerStyle, side);
136         return adjustGridPositionForSide(lastLine, side);
137     }
138
139     unsigned namedGridLineIndex;
140     if (position.isPositive())
141         namedGridLineIndex = std::min<unsigned>(position.integerPosition(), it->value.size()) - 1;
142     else
143         namedGridLineIndex = std::max<int>(0, it->value.size() - abs(position.integerPosition()));
144     return adjustGridPositionForSide(it->value[namedGridLineIndex], side);
145 }
146
147 static inline unsigned firstNamedGridLineBeforePosition(unsigned position, const Vector<unsigned>& gridLines)
148 {
149     // The grid line inequality needs to be strict (which doesn't match the after / end case) because |position| is
150     // already converted to an index in our grid representation (ie one was removed from the grid line to account for
151     // the side).
152     unsigned firstLineBeforePositionIndex = 0;
153     auto firstLineBeforePosition = std::lower_bound(gridLines.begin(), gridLines.end(), position);
154     if (firstLineBeforePosition != gridLines.end()) {
155         if (*firstLineBeforePosition > position && firstLineBeforePosition != gridLines.begin())
156             --firstLineBeforePosition;
157
158         firstLineBeforePositionIndex = firstLineBeforePosition - gridLines.begin();
159     }
160     return firstLineBeforePositionIndex;
161 }
162
163 static GridSpan resolveRowStartColumnStartNamedGridLinePositionAgainstOppositePosition(const GridResolvedPosition& resolvedOppositePosition, const GridPosition& position, const Vector<unsigned>& gridLines)
164 {
165     unsigned gridLineIndex = std::max<int>(0, firstNamedGridLineBeforePosition(resolvedOppositePosition.toInt(), gridLines) - position.spanPosition() + 1);
166     GridResolvedPosition resolvedGridLinePosition = GridResolvedPosition(gridLines[gridLineIndex]);
167     return GridSpan(std::min<GridResolvedPosition>(resolvedGridLinePosition, resolvedOppositePosition), resolvedOppositePosition);
168 }
169
170 static GridSpan resolveRowEndColumnEndNamedGridLinePositionAgainstOppositePosition(const GridResolvedPosition& resolvedOppositePosition, const GridPosition& position, const Vector<unsigned>& gridLines)
171 {
172     ASSERT(gridLines.size());
173     unsigned firstLineAfterOppositePositionIndex = gridLines.size() - 1;
174     const unsigned* firstLineAfterOppositePosition = std::upper_bound(gridLines.begin(), gridLines.end(), resolvedOppositePosition);
175     if (firstLineAfterOppositePosition != gridLines.end())
176         firstLineAfterOppositePositionIndex = firstLineAfterOppositePosition - gridLines.begin();
177
178     unsigned gridLineIndex = std::min<unsigned>(gridLines.size() - 1, firstLineAfterOppositePositionIndex + position.spanPosition() - 1);
179     GridResolvedPosition resolvedGridLinePosition = adjustGridPositionForRowEndColumnEndSide(gridLines[gridLineIndex]);
180     if (resolvedGridLinePosition < resolvedOppositePosition)
181         resolvedGridLinePosition = resolvedOppositePosition;
182     return GridSpan(resolvedOppositePosition, resolvedGridLinePosition);
183 }
184
185 static GridSpan resolveNamedGridLinePositionAgainstOppositePosition(const RenderStyle& gridContainerStyle, const GridResolvedPosition& resolvedOppositePosition, const GridPosition& position, GridPositionSide side)
186 {
187     ASSERT(position.isSpan());
188     ASSERT(!position.namedGridLine().isNull());
189     // Negative positions are not allowed per the specification and should have been handled during parsing.
190     ASSERT(position.spanPosition() > 0);
191
192     const NamedGridLinesMap& gridLinesNames = isColumnSide(side) ? gridContainerStyle.namedGridColumnLines() : gridContainerStyle.namedGridRowLines();
193     NamedGridLinesMap::const_iterator it = gridLinesNames.find(position.namedGridLine());
194
195     // If there is no named grid line of that name, we resolve the position to 'auto' (which is equivalent to 'span 1' in this case).
196     // See http://lists.w3.org/Archives/Public/www-style/2013Jun/0394.html.
197     if (it == gridLinesNames.end())
198         return GridSpan(resolvedOppositePosition, resolvedOppositePosition);
199
200     if (side == RowStartSide || side == ColumnStartSide)
201         return resolveRowStartColumnStartNamedGridLinePositionAgainstOppositePosition(resolvedOppositePosition, position, it->value);
202
203     return resolveRowEndColumnEndNamedGridLinePositionAgainstOppositePosition(resolvedOppositePosition, position, it->value);
204 }
205
206 static GridSpan resolveGridPositionAgainstOppositePosition(const RenderStyle& gridContainerStyle, const GridResolvedPosition& resolvedOppositePosition, const GridPosition& position, GridPositionSide side)
207 {
208     if (position.isAuto())
209         return GridSpan(resolvedOppositePosition, resolvedOppositePosition);
210
211     ASSERT(position.isSpan());
212     ASSERT(position.spanPosition() > 0);
213
214     if (!position.namedGridLine().isNull()) {
215         // span 2 'c' -> we need to find the appropriate grid line before / after our opposite position.
216         return resolveNamedGridLinePositionAgainstOppositePosition(gridContainerStyle, resolvedOppositePosition, position, side);
217     }
218
219     // 'span 1' is contained inside a single grid track regardless of the direction.
220     // That's why the CSS span value is one more than the offset we apply.
221     unsigned positionOffset = position.spanPosition() - 1;
222     if (isStartSide(side)) {
223         unsigned initialResolvedPosition = std::max<int>(0, resolvedOppositePosition.toInt() - positionOffset);
224         return GridSpan(initialResolvedPosition, resolvedOppositePosition);
225     }
226
227     return GridSpan(resolvedOppositePosition, resolvedOppositePosition.toInt() + positionOffset);
228 }
229
230 GridSpan GridResolvedPosition::resolveGridPositionsFromAutoPlacementPosition(const RenderStyle& gridContainerStyle, const RenderBox& gridItem, GridTrackSizingDirection direction, const GridResolvedPosition& resolvedInitialPosition)
231 {
232     GridUnresolvedSpan unresolvedSpan = unresolvedSpanFromStyle(gridContainerStyle, gridItem, direction);
233
234     // This method will only be used when both positions need to be resolved against the opposite one.
235     ASSERT(unresolvedSpan.requiresAutoPlacement());
236
237     GridResolvedPosition resolvedFinalPosition = resolvedInitialPosition;
238
239     if (unresolvedSpan.initialPosition().isSpan())
240         return resolveGridPositionAgainstOppositePosition(gridContainerStyle, resolvedInitialPosition, unresolvedSpan.initialPosition(), unresolvedSpan.finalPositionSide());
241     if (unresolvedSpan.finalPosition().isSpan())
242         return resolveGridPositionAgainstOppositePosition(gridContainerStyle, resolvedInitialPosition, unresolvedSpan.finalPosition(), unresolvedSpan.finalPositionSide());
243
244     return GridSpan(resolvedInitialPosition, resolvedFinalPosition);
245 }
246
247 static GridResolvedPosition resolveGridPositionFromStyle(const RenderStyle& gridContainerStyle, const GridPosition& position, GridPositionSide side)
248 {
249     switch (position.type()) {
250     case ExplicitPosition: {
251         ASSERT(position.integerPosition());
252
253         if (!position.namedGridLine().isNull())
254             return resolveNamedGridLinePositionFromStyle(gridContainerStyle, position, side);
255
256         // Handle <integer> explicit position.
257         if (position.isPositive())
258             return adjustGridPositionForSide(position.integerPosition() - 1, side);
259
260         unsigned resolvedPosition = abs(position.integerPosition()) - 1;
261         const unsigned endOfTrack = explicitGridSizeForSide(gridContainerStyle, side);
262
263         // Per http://lists.w3.org/Archives/Public/www-style/2013Mar/0589.html, we clamp negative value to the first line.
264         if (endOfTrack < resolvedPosition)
265             return 0;
266
267         return adjustGridPositionForSide(endOfTrack - resolvedPosition, side);
268     }
269     case NamedGridAreaPosition:
270     {
271         // First attempt to match the grid area's edge to a named grid area: if there is a named line with the name
272         // ''<custom-ident>-start (for grid-*-start) / <custom-ident>-end'' (for grid-*-end), contributes the first such
273         // line to the grid item's placement.
274         String namedGridLine = position.namedGridLine();
275         ASSERT(!GridResolvedPosition::isNonExistentNamedLineOrArea(namedGridLine, gridContainerStyle, side));
276
277         const NamedGridLinesMap& gridLineNames = gridLinesForSide(gridContainerStyle, side);
278         auto implicitLine = gridLineNames.find(implicitNamedGridLineForSide(namedGridLine, side));
279         if (implicitLine != gridLineNames.end())
280             return adjustGridPositionForSide(implicitLine->value[0], side);
281
282         // Otherwise, if there is a named line with the specified name, contributes the first such line to the grid
283         // item's placement.
284         auto explicitLine = gridLineNames.find(namedGridLine);
285         if (explicitLine != gridLineNames.end())
286             return adjustGridPositionForSide(explicitLine->value[0], side);
287
288         // If none of the above works specs mandate us to treat it as auto BUT we should have detected it before calling
289         // this function in resolveGridPositionsFromStyle(). We should be covered anyway by the ASSERT at the beginning
290         // of this case block.
291         ASSERT_NOT_REACHED();
292         return 0;
293     }
294     case AutoPosition:
295     case SpanPosition:
296         // 'auto' and span depend on the opposite position for resolution (e.g. grid-row: auto / 1 or grid-column: span 3 / "myHeader").
297         ASSERT_NOT_REACHED();
298         return GridResolvedPosition(0);
299     }
300     ASSERT_NOT_REACHED();
301     return GridResolvedPosition(0);
302 }
303
304 GridResolvedPosition::GridResolvedPosition(const GridPosition& position, GridPositionSide side)
305 {
306     ASSERT(position.integerPosition());
307     unsigned integerPosition = position.integerPosition() - 1;
308
309     m_integerPosition = adjustGridPositionForSide(integerPosition, side).m_integerPosition;
310 }
311
312 GridUnresolvedSpan GridResolvedPosition::unresolvedSpanFromStyle(const RenderStyle& gridContainerStyle, const RenderBox& gridItem, GridTrackSizingDirection direction)
313 {
314     GridPosition initialPosition = (direction == ForColumns) ? gridItem.style().gridItemColumnStart() : gridItem.style().gridItemRowStart();
315     auto initialPositionSide = (direction == ForColumns) ? ColumnStartSide : RowStartSide;
316     GridPosition finalPosition = (direction == ForColumns) ? gridItem.style().gridItemColumnEnd() : gridItem.style().gridItemRowEnd();
317     auto finalPositionSide = (direction == ForColumns) ? ColumnEndSide : RowEndSide;
318
319     GridUnresolvedSpan unresolvedSpan(initialPosition, initialPositionSide, finalPosition, finalPositionSide);
320     unresolvedSpan.adjustGridPositionsFromStyle(gridContainerStyle);
321
322     return unresolvedSpan;
323 }
324
325 GridSpan GridResolvedPosition::resolveGridPositionsFromStyle(const GridUnresolvedSpan& unresolvedSpan, const RenderStyle& gridContainerStyle)
326 {
327     ASSERT(!unresolvedSpan.requiresAutoPlacement());
328
329     if (unresolvedSpan.initialPosition().shouldBeResolvedAgainstOppositePosition()) {
330         // Infer the position from the final position ('auto / 1' or 'span 2 / 3' case).
331         auto finalResolvedPosition = resolveGridPositionFromStyle(gridContainerStyle, unresolvedSpan.finalPosition(), unresolvedSpan.finalPositionSide());
332         return resolveGridPositionAgainstOppositePosition(gridContainerStyle, finalResolvedPosition, unresolvedSpan.initialPosition(), unresolvedSpan.initialPositionSide());
333     }
334
335     if (unresolvedSpan.finalPosition().shouldBeResolvedAgainstOppositePosition()) {
336         // Infer our position from the initial position ('1 / auto' or '3 / span 2' case).
337         auto initialResolvedPosition = resolveGridPositionFromStyle(gridContainerStyle, unresolvedSpan.initialPosition(), unresolvedSpan.initialPositionSide());
338         return resolveGridPositionAgainstOppositePosition(gridContainerStyle, initialResolvedPosition, unresolvedSpan.finalPosition(), unresolvedSpan.finalPositionSide());
339     }
340
341     GridResolvedPosition resolvedInitialPosition = resolveGridPositionFromStyle(gridContainerStyle, unresolvedSpan.initialPosition(), unresolvedSpan.initialPositionSide());
342     GridResolvedPosition resolvedFinalPosition = resolveGridPositionFromStyle(gridContainerStyle, unresolvedSpan.finalPosition(), unresolvedSpan.finalPositionSide());
343
344     // If 'grid-row-end' specifies a line at or before that specified by 'grid-row-start', it computes to 'span 1'.
345     return GridSpan(resolvedInitialPosition, std::max<GridResolvedPosition>(resolvedInitialPosition, resolvedFinalPosition));
346 }
347
348 } // namespace WebCore
349
350 #endif // ENABLE(CSS_GRID_LAYOUT)