Add changes missing from r234925.
[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 LayoutUnit marginValue(LayoutUnit currentMarginValue, LayoutUnit candidateMarginValue)
40 {
41     if (!candidateMarginValue)
42         return currentMarginValue;
43     if (!currentMarginValue)
44         return candidateMarginValue;
45     // Both margins are positive.
46     if (candidateMarginValue > 0 && currentMarginValue > 0)
47         return std::max(candidateMarginValue, currentMarginValue);
48     // Both margins are negative.
49     if (candidateMarginValue < 0 && currentMarginValue < 0)
50         return 0 - std::max(std::abs(candidateMarginValue.toFloat()), std::abs(currentMarginValue.toFloat()));
51     // One of the margins is negative.
52     return currentMarginValue + candidateMarginValue;
53 }
54
55 static bool isMarginTopCollapsedWithSibling(const Box& layoutBox)
56 {
57     ASSERT(layoutBox.isBlockLevelBox());
58
59     if (layoutBox.isFloatingPositioned())
60         return false;
61
62     if (!layoutBox.isPositioned() || layoutBox.isInFlowPositioned())
63         return true;
64
65     // Out of flow positioned.
66     ASSERT(layoutBox.isOutOfFlowPositioned());
67     return layoutBox.style().top().isAuto();
68 }
69
70 static bool isMarginBottomCollapsedWithSibling(const Box& layoutBox)
71 {
72     ASSERT(layoutBox.isBlockLevelBox());
73
74     if (layoutBox.isFloatingPositioned())
75         return false;
76
77     if (!layoutBox.isPositioned() || layoutBox.isInFlowPositioned())
78         return true;
79
80     // Out of flow positioned.
81     ASSERT(layoutBox.isOutOfFlowPositioned());
82     return layoutBox.style().bottom().isAuto();
83 }
84
85 static bool isMarginTopCollapsedWithParent(const LayoutContext& layoutContext, const Box& layoutBox)
86 {
87     // The first inflow child could propagate its top margin to parent.
88     // https://www.w3.org/TR/CSS21/box.html#collapsing-margins
89     if (layoutBox.isAnonymous())
90         return false;
91
92     ASSERT(layoutBox.isBlockLevelBox());
93
94     if (layoutBox.isFloatingOrOutOfFlowPositioned())
95         return false;
96
97     // We never margin collapse the initial containing block.
98     ASSERT(layoutBox.parent());
99     auto& parent = *layoutBox.parent();
100     // Only the first inlflow child collapses with parent (floating sibling also prevents collapsing). 
101     if (layoutBox.previousInFlowOrFloatingSibling())
102         return false;
103
104     if (parent.establishesBlockFormattingContext())
105         return false;
106
107     // Margins of the root element's box do not collapse.
108     if (parent.isDocumentBox() || parent.isInitialContainingBlock())
109         return false;
110
111     auto& parentDisplayBox = *layoutContext.displayBoxForLayoutBox(parent);
112     if (parentDisplayBox.borderTop())
113         return false;
114
115     if (parentDisplayBox.paddingTop().value_or(0))
116         return false;
117
118     return true;
119 }
120
121 static bool isMarginBottomCollapsedThrough(const LayoutContext& layoutContext, const Box& layoutBox)
122 {
123     ASSERT(layoutBox.isBlockLevelBox());
124
125     // If the top and bottom margins of a box are adjoining, then it is possible for margins to collapse through it.
126     auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
127
128     if (displayBox.borderTop() || displayBox.borderBottom())
129         return false;
130
131     if (displayBox.paddingTop().value_or(0) || displayBox.paddingBottom().value_or(0))
132         return false;
133
134     if (!layoutBox.style().height().isAuto() || !layoutBox.style().minHeight().isAuto())
135         return false;
136
137     if (!is<Container>(layoutBox))
138         return true;
139
140     auto& container = downcast<Container>(layoutBox);
141     if (container.hasInFlowOrFloatingChild())
142         return false;
143
144     return true;
145 }
146
147 LayoutUnit BlockFormattingContext::MarginCollapse::collapsedMarginTopFromFirstChild(const LayoutContext& layoutContext, const Box& layoutBox)
148 {
149     ASSERT(layoutBox.isBlockLevelBox());
150
151     // Check if the first child collapses its margin top.
152     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
153         return 0;
154
155     // Do not collapse margin with a box from a non-block formatting context <div><span>foobar</span></div>.
156     if (layoutBox.establishesFormattingContext() && !layoutBox.establishesBlockFormattingContextOnly())
157         return 0;
158
159     // FIXME: Take collapsed through margin into account.
160     auto& firstInFlowChild = *downcast<Container>(layoutBox).firstInFlowChild();
161     if (!isMarginTopCollapsedWithParent(layoutContext, firstInFlowChild))
162         return 0;
163     // Collect collapsed margin top recursively.
164     return marginValue(computedNonCollapsedMarginTop(layoutContext, firstInFlowChild), collapsedMarginTopFromFirstChild(layoutContext, firstInFlowChild));
165 }
166
167 LayoutUnit BlockFormattingContext::MarginCollapse::nonCollapsedMarginTop(const LayoutContext& layoutContext, const Box& layoutBox)
168 {
169     ASSERT(layoutBox.isBlockLevelBox());
170
171     // Non collapsed margin top includes collapsed margin from inflow first child.
172     return marginValue(computedNonCollapsedMarginTop(layoutContext, layoutBox), collapsedMarginTopFromFirstChild(layoutContext, layoutBox));
173 }
174
175 /*static bool hasAdjoiningMarginTopAndBottom(const Box&)
176 {
177     // Two margins are adjoining if and only if:
178     // 1. both belong to in-flow block-level boxes that participate in the same block formatting context
179     // 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.)
180     // 3. both belong to vertically-adjacent box edges, i.e. form one of the following pairs:
181     //        top margin of a box and top margin of its first in-flow child
182     //        bottom margin of box and top margin of its next in-flow following sibling
183     //        bottom margin of a last in-flow child and bottom margin of its parent if the parent has 'auto' computed height
184     //        top and bottom margins of a box that does not establish a new block formatting context and that has zero computed 'min-height',
185     //        zero or 'auto' computed 'height', and no in-flow children
186     // A collapsed margin is considered adjoining to another margin if any of its component margins is adjoining to that margin.
187     return false;
188 }*/
189 LayoutUnit BlockFormattingContext::MarginCollapse::computedNonCollapsedMarginTop(const LayoutContext& layoutContext, const Box& layoutBox)
190 {
191     ASSERT(layoutBox.isBlockLevelBox());
192
193     return FormattingContext::Geometry::computedNonCollapsedVerticalMarginValue(layoutContext, layoutBox).top;
194 }
195
196 LayoutUnit BlockFormattingContext::MarginCollapse::computedNonCollapsedMarginBottom(const LayoutContext& layoutContext, const Box& layoutBox)
197 {
198     ASSERT(layoutBox.isBlockLevelBox());
199
200     return FormattingContext::Geometry::computedNonCollapsedVerticalMarginValue(layoutContext, layoutBox).bottom;
201 }
202
203 LayoutUnit BlockFormattingContext::MarginCollapse::marginTop(const LayoutContext& layoutContext, const Box& layoutBox)
204 {
205     if (layoutBox.isAnonymous())
206         return 0;
207
208     ASSERT(layoutBox.isBlockLevelBox());
209
210     // TODO: take _hasAdjoiningMarginTopAndBottom() into account.
211     if (isMarginTopCollapsedWithParent(layoutContext, layoutBox))
212         return 0;
213
214     if (!isMarginTopCollapsedWithSibling(layoutBox)) {
215         if (!isMarginBottomCollapsedThrough(layoutContext, layoutBox))
216             return nonCollapsedMarginTop(layoutContext, layoutBox);
217         // Compute the collapsed through value.
218         auto marginTop = nonCollapsedMarginTop(layoutContext, layoutBox);
219         auto marginBottom = nonCollapsedMarginBottom(layoutContext, layoutBox); 
220         return marginValue(marginTop, marginBottom);
221     }
222
223     // The bottom margin of an in-flow block-level element always collapses with the top margin of its next in-flow block-level sibling,
224     // unless that sibling has clearance.
225     auto* previousInFlowSibling = layoutBox.previousInFlowSibling();
226     if (!previousInFlowSibling)
227         return nonCollapsedMarginTop(layoutContext, layoutBox);
228
229     auto previousSiblingMarginBottom = nonCollapsedMarginBottom(layoutContext, *previousInFlowSibling);
230     auto marginTop = nonCollapsedMarginTop(layoutContext, layoutBox);
231     return marginValue(marginTop, previousSiblingMarginBottom);
232 }
233
234 LayoutUnit BlockFormattingContext::MarginCollapse::marginBottom(const LayoutContext& layoutContext, const Box& layoutBox)
235 {
236     if (layoutBox.isAnonymous())
237         return 0;
238
239     ASSERT(layoutBox.isBlockLevelBox());
240
241     // TODO: take _hasAdjoiningMarginTopAndBottom() into account.
242     if (isMarginBottomCollapsedWithParent(layoutContext, layoutBox))
243         return 0;
244
245     if (isMarginBottomCollapsedThrough(layoutContext, layoutBox))
246         return 0;
247
248     // Floats and out of flow positioned boxes do not collapse their margins.
249     if (!isMarginBottomCollapsedWithSibling(layoutBox))
250         return nonCollapsedMarginBottom(layoutContext, layoutBox);
251
252     // The bottom margin of an in-flow block-level element always collapses with the top margin of its next in-flow block-level sibling,
253     // unless that sibling has clearance.
254     if (layoutBox.nextInFlowSibling())
255         return 0;
256     return nonCollapsedMarginBottom(layoutContext, layoutBox);
257 }
258
259 bool BlockFormattingContext::MarginCollapse::isMarginBottomCollapsedWithParent(const LayoutContext& layoutContext, const Box& layoutBox)
260 {
261     // last inflow box to parent.
262     // https://www.w3.org/TR/CSS21/box.html#collapsing-margins
263     if (layoutBox.isAnonymous())
264         return false;
265
266     ASSERT(layoutBox.isBlockLevelBox());
267
268     if (layoutBox.isFloatingOrOutOfFlowPositioned())
269         return false;
270
271     if (isMarginBottomCollapsedThrough(layoutContext, layoutBox))
272         return false;
273
274     // We never margin collapse the initial containing block.
275     ASSERT(layoutBox.parent());
276     auto& parent = *layoutBox.parent();
277     // Only the last inlflow child collapses with parent (floating sibling also prevents collapsing). 
278     if (layoutBox.nextInFlowOrFloatingSibling())
279         return false;
280
281     if (parent.establishesBlockFormattingContext())
282         return false;
283
284     // Margins of the root element's box do not collapse.
285     if (parent.isDocumentBox() || parent.isInitialContainingBlock())
286         return false;
287
288     auto& parentDisplayBox = *layoutContext.displayBoxForLayoutBox(parent);
289     if (parentDisplayBox.borderTop())
290         return false;
291
292     if (parentDisplayBox.paddingTop().value_or(0))
293         return false;
294
295     if (!parent.style().height().isAuto())
296         return false;
297
298     return true;
299 }
300
301 bool BlockFormattingContext::MarginCollapse::isMarginTopCollapsedWithParentMarginBottom(const Box&)
302 {
303     return false;
304 }
305
306 LayoutUnit BlockFormattingContext::MarginCollapse::collapsedMarginBottomFromLastChild(const LayoutContext& layoutContext, const Box& layoutBox)
307 {
308     ASSERT(layoutBox.isBlockLevelBox());
309
310     // Check if the last child propagates its margin bottom.
311     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
312         return 0;
313
314     // Do not collapse margin with a box from a non-block formatting context <div><span>foobar</span></div>.
315     if (layoutBox.establishesFormattingContext() && !layoutBox.establishesBlockFormattingContextOnly())
316         return 0;
317
318     // FIXME: Check for collapsed through margin.
319     auto& lastInFlowChild = *downcast<Container>(layoutBox).lastInFlowChild();
320     if (!isMarginBottomCollapsedWithParent(layoutContext, lastInFlowChild))
321         return 0;
322
323     // Collect collapsed margin bottom recursively.
324     return marginValue(computedNonCollapsedMarginBottom(layoutContext, lastInFlowChild), collapsedMarginBottomFromLastChild(layoutContext, lastInFlowChild));
325 }
326
327 LayoutUnit BlockFormattingContext::MarginCollapse::nonCollapsedMarginBottom(const LayoutContext& layoutContext, const Box& layoutBox)
328 {
329     ASSERT(layoutBox.isBlockLevelBox());
330
331     // Non collapsed margin bottom includes collapsed margin from inflow last child.
332     return marginValue(computedNonCollapsedMarginBottom(layoutContext, layoutBox), collapsedMarginBottomFromLastChild(layoutContext, layoutBox));
333 }
334
335 LayoutUnit BlockFormattingContext::MarginCollapse::estimatedMarginTop(const LayoutContext& layoutContext, const Box& layoutBox)
336 {
337     ASSERT(layoutBox.isBlockLevelBox());
338     // Can't estimate vertical margins for out of flow boxes (and we shouldn't need to do it for float boxes).
339     ASSERT(layoutBox.isInFlow());
340     // Can't cross block formatting context boundary.
341     ASSERT(!layoutBox.establishesBlockFormattingContext());
342
343     // Let's just use the normal path for now.
344     return marginTop(layoutContext, layoutBox);
345 }
346
347 }
348 }
349 #endif