27dd35a7b3855093f80f53d4f1a28bccebd273ca
[WebKit-https.git] / Source / WebCore / layout / blockformatting / BlockMarginCollapse.cpp
1 /*
2  * Copyright (C) 2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "BlockFormattingContext.h"
28
29 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
30
31 #include "LayoutBox.h"
32 #include "LayoutContainer.h"
33 #include "LayoutUnit.h"
34 #include "RenderStyle.h"
35
36 namespace WebCore {
37 namespace Layout {
38
39 static bool isQuirkContainer(const Box& layoutBox)
40 {
41     return layoutBox.isBodyBox() || layoutBox.isDocumentBox() || layoutBox.isTableCell();
42 }
43
44 static bool hasMarginTopQuirkValue(const Box& layoutBox)
45 {
46     return layoutBox.style().hasMarginBeforeQuirk();
47 }
48
49 static bool shouldIgnoreMarginTopInQuirkContext(const LayoutState& layoutState, const Box& layoutBox)
50 {
51     if (!layoutBox.parent())
52         return false;
53     return layoutState.inQuirksMode() && isQuirkContainer(*layoutBox.parent()) && hasMarginTopQuirkValue(layoutBox);
54 }
55
56 static LayoutUnit marginValue(LayoutUnit currentMarginValue, LayoutUnit candidateMarginValue)
57 {
58     if (!candidateMarginValue)
59         return currentMarginValue;
60     if (!currentMarginValue)
61         return candidateMarginValue;
62     // Both margins are positive.
63     if (candidateMarginValue > 0 && currentMarginValue > 0)
64         return std::max(candidateMarginValue, currentMarginValue);
65     // Both margins are negative.
66     if (candidateMarginValue < 0 && currentMarginValue < 0)
67         return 0 - std::max(std::abs(candidateMarginValue.toFloat()), std::abs(currentMarginValue.toFloat()));
68     // One of the margins is negative.
69     return currentMarginValue + candidateMarginValue;
70 }
71
72 static bool isMarginTopCollapsedWithSibling(const Box& layoutBox)
73 {
74     ASSERT(layoutBox.isBlockLevelBox());
75
76     if (layoutBox.isFloatingPositioned())
77         return false;
78
79     if (!layoutBox.isPositioned() || layoutBox.isInFlowPositioned())
80         return true;
81
82     // Out of flow positioned.
83     ASSERT(layoutBox.isOutOfFlowPositioned());
84     return layoutBox.style().top().isAuto();
85 }
86
87 static bool isMarginBottomCollapsedWithSibling(const Box& layoutBox)
88 {
89     ASSERT(layoutBox.isBlockLevelBox());
90
91     if (layoutBox.isFloatingPositioned())
92         return false;
93
94     if (!layoutBox.isPositioned() || layoutBox.isInFlowPositioned())
95         return true;
96
97     // Out of flow positioned.
98     ASSERT(layoutBox.isOutOfFlowPositioned());
99     return layoutBox.style().bottom().isAuto();
100 }
101
102 static bool isMarginTopCollapsedWithParent(const LayoutState& layoutState, const Box& layoutBox)
103 {
104     // The first inflow child could propagate its top margin to parent.
105     // https://www.w3.org/TR/CSS21/box.html#collapsing-margins
106     if (layoutBox.isAnonymous())
107         return false;
108
109     ASSERT(layoutBox.isBlockLevelBox());
110
111     if (layoutBox.isFloatingOrOutOfFlowPositioned())
112         return false;
113
114     // We never margin collapse the initial containing block.
115     ASSERT(layoutBox.parent());
116     auto& parent = *layoutBox.parent();
117     // Only the first inlflow child collapses with parent.
118     if (layoutBox.previousInFlowSibling())
119         return false;
120
121     if (parent.establishesBlockFormattingContext())
122         return false;
123
124     // Margins of the root element's box do not collapse.
125     if (parent.isDocumentBox() || parent.isInitialContainingBlock())
126         return false;
127
128     auto& parentDisplayBox = layoutState.displayBoxForLayoutBox(parent);
129     if (parentDisplayBox.borderTop())
130         return false;
131
132     if (parentDisplayBox.paddingTop().value_or(0))
133         return false;
134
135     if (shouldIgnoreMarginTopInQuirkContext(layoutState, layoutBox))
136         return false;
137
138     return true;
139 }
140
141 static bool isMarginBottomCollapsedThrough(const LayoutState& layoutState, const Box& layoutBox)
142 {
143     ASSERT(layoutBox.isBlockLevelBox());
144
145     // If the top and bottom margins of a box are adjoining, then it is possible for margins to collapse through it.
146     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
147
148     if (displayBox.borderTop() || displayBox.borderBottom())
149         return false;
150
151     if (displayBox.paddingTop().value_or(0) || displayBox.paddingBottom().value_or(0))
152         return false;
153
154     if (!layoutBox.style().height().isAuto() || !layoutBox.style().minHeight().isAuto())
155         return false;
156
157     if (!is<Container>(layoutBox))
158         return true;
159
160     auto& container = downcast<Container>(layoutBox);
161     if (container.hasInFlowOrFloatingChild())
162         return false;
163
164     return true;
165 }
166
167 LayoutUnit BlockFormattingContext::Geometry::MarginCollapse::collapsedMarginTopFromFirstChild(const LayoutState& layoutState, const Box& layoutBox)
168 {
169     ASSERT(layoutBox.isBlockLevelBox());
170
171     // Check if the first child collapses its margin top.
172     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
173         return 0;
174
175     // Do not collapse margin with a box from a non-block formatting context <div><span>foobar</span></div>.
176     if (layoutBox.establishesFormattingContext() && !layoutBox.establishesBlockFormattingContextOnly())
177         return 0;
178
179     // FIXME: Take collapsed through margin into account.
180     auto& firstInFlowChild = *downcast<Container>(layoutBox).firstInFlowChild();
181     if (!isMarginTopCollapsedWithParent(layoutState, firstInFlowChild))
182         return 0;
183     // Collect collapsed margin top recursively.
184     return marginValue(computedNonCollapsedMarginTop(layoutState, firstInFlowChild), collapsedMarginTopFromFirstChild(layoutState, firstInFlowChild));
185 }
186
187 LayoutUnit BlockFormattingContext::Geometry::MarginCollapse::nonCollapsedMarginTop(const LayoutState& layoutState, const Box& layoutBox)
188 {
189     ASSERT(layoutBox.isBlockLevelBox());
190
191     // Non collapsed margin top includes collapsed margin from inflow first child.
192     return marginValue(computedNonCollapsedMarginTop(layoutState, layoutBox), collapsedMarginTopFromFirstChild(layoutState, layoutBox));
193 }
194
195 /*static bool hasAdjoiningMarginTopAndBottom(const Box&)
196 {
197     // Two margins are adjoining if and only if:
198     // 1. both belong to in-flow block-level boxes that participate in the same block formatting context
199     // 2. no line boxes, no clearance, no padding and no border separate them (Note that certain zero-height line boxes (see 9.4.2) are ignored for this purpose.)
200     // 3. both belong to vertically-adjacent box edges, i.e. form one of the following pairs:
201     //        top margin of a box and top margin of its first in-flow child
202     //        bottom margin of box and top margin of its next in-flow following sibling
203     //        bottom margin of a last in-flow child and bottom margin of its parent if the parent has 'auto' computed height
204     //        top and bottom margins of a box that does not establish a new block formatting context and that has zero computed 'min-height',
205     //        zero or 'auto' computed 'height', and no in-flow children
206     // A collapsed margin is considered adjoining to another margin if any of its component margins is adjoining to that margin.
207     return false;
208 }*/
209 LayoutUnit BlockFormattingContext::Geometry::MarginCollapse::computedNonCollapsedMarginTop(const LayoutState& layoutState, const Box& layoutBox)
210 {
211     ASSERT(layoutBox.isBlockLevelBox());
212
213     return computedNonCollapsedVerticalMarginValue(layoutState, layoutBox).top;
214 }
215
216 LayoutUnit BlockFormattingContext::Geometry::MarginCollapse::computedNonCollapsedMarginBottom(const LayoutState& layoutState, const Box& layoutBox)
217 {
218     ASSERT(layoutBox.isBlockLevelBox());
219
220     return computedNonCollapsedVerticalMarginValue(layoutState, layoutBox).bottom;
221 }
222
223 LayoutUnit BlockFormattingContext::Geometry::MarginCollapse::marginTop(const LayoutState& layoutState, const Box& layoutBox)
224 {
225     if (layoutBox.isAnonymous())
226         return 0;
227
228     ASSERT(layoutBox.isBlockLevelBox());
229
230     // TODO: take _hasAdjoiningMarginTopAndBottom() into account.
231     if (isMarginTopCollapsedWithParent(layoutState, layoutBox))
232         return 0;
233
234     // FIXME: Find out the logic behind this.
235     if (shouldIgnoreMarginTopInQuirkContext(layoutState, layoutBox))
236         return 0;
237
238     if (!isMarginTopCollapsedWithSibling(layoutBox)) {
239         if (!isMarginBottomCollapsedThrough(layoutState, layoutBox))
240             return nonCollapsedMarginTop(layoutState, layoutBox);
241         // Compute the collapsed through value.
242         auto marginTop = nonCollapsedMarginTop(layoutState, layoutBox);
243         auto marginBottom = nonCollapsedMarginBottom(layoutState, layoutBox); 
244         return marginValue(marginTop, marginBottom);
245     }
246
247     // The bottom margin of an in-flow block-level element always collapses with the top margin of its next in-flow block-level sibling,
248     // unless that sibling has clearance.
249     auto* previousInFlowSibling = layoutBox.previousInFlowSibling();
250     if (!previousInFlowSibling)
251         return nonCollapsedMarginTop(layoutState, layoutBox);
252
253     auto previousSiblingMarginBottom = nonCollapsedMarginBottom(layoutState, *previousInFlowSibling);
254     auto marginTop = nonCollapsedMarginTop(layoutState, layoutBox);
255     return marginValue(marginTop, previousSiblingMarginBottom);
256 }
257
258 LayoutUnit BlockFormattingContext::Geometry::MarginCollapse::marginBottom(const LayoutState& layoutState, const Box& layoutBox)
259 {
260     if (layoutBox.isAnonymous())
261         return 0;
262
263     ASSERT(layoutBox.isBlockLevelBox());
264
265     // TODO: take _hasAdjoiningMarginTopAndBottom() into account.
266     if (isMarginBottomCollapsedWithParent(layoutState, layoutBox))
267         return 0;
268
269     if (isMarginBottomCollapsedThrough(layoutState, layoutBox))
270         return 0;
271
272     // Floats and out of flow positioned boxes do not collapse their margins.
273     if (!isMarginBottomCollapsedWithSibling(layoutBox))
274         return nonCollapsedMarginBottom(layoutState, layoutBox);
275
276     // The bottom margin of an in-flow block-level element always collapses with the top margin of its next in-flow block-level sibling,
277     // unless that sibling has clearance.
278     if (layoutBox.nextInFlowSibling())
279         return 0;
280     return nonCollapsedMarginBottom(layoutState, layoutBox);
281 }
282
283 bool BlockFormattingContext::Geometry::MarginCollapse::isMarginBottomCollapsedWithParent(const LayoutState& layoutState, const Box& layoutBox)
284 {
285     // last inflow box to parent.
286     // https://www.w3.org/TR/CSS21/box.html#collapsing-margins
287     if (layoutBox.isAnonymous())
288         return false;
289
290     ASSERT(layoutBox.isBlockLevelBox());
291
292     if (layoutBox.isFloatingOrOutOfFlowPositioned())
293         return false;
294
295     if (isMarginBottomCollapsedThrough(layoutState, layoutBox))
296         return false;
297
298     // We never margin collapse the initial containing block.
299     ASSERT(layoutBox.parent());
300     auto& parent = *layoutBox.parent();
301     // Only the last inlflow child collapses with parent.
302     if (layoutBox.nextInFlowSibling())
303         return false;
304
305     if (parent.establishesBlockFormattingContext())
306         return false;
307
308     // Margins of the root element's box do not collapse.
309     if (parent.isDocumentBox() || parent.isInitialContainingBlock())
310         return false;
311
312     auto& parentDisplayBox = layoutState.displayBoxForLayoutBox(parent);
313     if (parentDisplayBox.borderTop())
314         return false;
315
316     if (parentDisplayBox.paddingTop().value_or(0))
317         return false;
318
319     if (!parent.style().height().isAuto())
320         return false;
321
322     return true;
323 }
324
325 bool BlockFormattingContext::Geometry::MarginCollapse::isMarginTopCollapsedWithParentMarginBottom(const Box&)
326 {
327     return false;
328 }
329
330 LayoutUnit BlockFormattingContext::Geometry::MarginCollapse::collapsedMarginBottomFromLastChild(const LayoutState& layoutState, const Box& layoutBox)
331 {
332     ASSERT(layoutBox.isBlockLevelBox());
333
334     // Check if the last child propagates its margin bottom.
335     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
336         return 0;
337
338     // Do not collapse margin with a box from a non-block formatting context <div><span>foobar</span></div>.
339     if (layoutBox.establishesFormattingContext() && !layoutBox.establishesBlockFormattingContextOnly())
340         return 0;
341
342     // FIXME: Check for collapsed through margin.
343     auto& lastInFlowChild = *downcast<Container>(layoutBox).lastInFlowChild();
344     if (!isMarginBottomCollapsedWithParent(layoutState, lastInFlowChild))
345         return 0;
346
347     // Collect collapsed margin bottom recursively.
348     return marginValue(computedNonCollapsedMarginBottom(layoutState, lastInFlowChild), collapsedMarginBottomFromLastChild(layoutState, lastInFlowChild));
349 }
350
351 LayoutUnit BlockFormattingContext::Geometry::MarginCollapse::nonCollapsedMarginBottom(const LayoutState& layoutState, const Box& layoutBox)
352 {
353     ASSERT(layoutBox.isBlockLevelBox());
354
355     // Non collapsed margin bottom includes collapsed margin from inflow last child.
356     return marginValue(computedNonCollapsedMarginBottom(layoutState, layoutBox), collapsedMarginBottomFromLastChild(layoutState, layoutBox));
357 }
358
359 }
360 }
361 #endif