[LFC] Do not collapse margin with the parent when element has border/padding.
[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     if (layoutBox.isFloatingPositioned())
58         return false;
59
60     if (!layoutBox.isPositioned() || layoutBox.isInFlowPositioned())
61         return true;
62
63     // Out of flow positioned.
64     ASSERT(layoutBox.isOutOfFlowPositioned());
65     return layoutBox.style().top().isAuto();
66 }
67
68 static bool isMarginBottomCollapsedWithSibling(const Box& layoutBox)
69 {
70     if (layoutBox.isFloatingPositioned())
71         return false;
72
73     if (!layoutBox.isPositioned() || layoutBox.isInFlowPositioned())
74         return true;
75
76     // Out of flow positioned.
77     ASSERT(layoutBox.isOutOfFlowPositioned());
78     return layoutBox.style().bottom().isAuto();
79 }
80
81 static bool isMarginTopCollapsedWithParent(const Box& layoutBox, const Display::Box& displayBox)
82 {
83     // The first inflow child could propagate its top margin to parent.
84     // https://www.w3.org/TR/CSS21/box.html#collapsing-margins
85     if (layoutBox.isAnonymous())
86         return false;
87
88     if (layoutBox.isFloatingOrOutOfFlowPositioned())
89         return false;
90
91     // We never margin collapse the initial containing block.
92     ASSERT(layoutBox.parent());
93     auto& parent = *layoutBox.parent();
94     // Is this box the first inlflow child?
95     if (parent.firstInFlowChild() != &layoutBox)
96         return false;
97
98     if (parent.establishesBlockFormattingContext())
99         return false;
100
101     // Margins of the root element's box do not collapse.
102     if (layoutBox.isInitialContainingBlock())
103         return false;
104
105     if (displayBox.borderTop())
106         return false;
107
108     if (!displayBox.paddingTop())
109         return false;
110
111     return true;
112 }
113
114 LayoutUnit BlockFormattingContext::MarginCollapse::collapsedMarginTopFromFirstChild(const LayoutContext& layoutContext, const Box& layoutBox)
115 {
116     // Check if the first child collapses its margin top.
117     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
118         return 0;
119
120     auto& firstInFlowChild = *downcast<Container>(layoutBox).firstInFlowChild();
121     auto& fistInflowDisplayBox = *layoutContext.displayBoxForLayoutBox(firstInFlowChild);
122     if (!isMarginTopCollapsedWithParent(firstInFlowChild, fistInflowDisplayBox))
123         return 0;
124
125     // Collect collapsed margin top recursively.
126     return marginValue(computedNonCollapsedMarginTop(layoutContext, firstInFlowChild), collapsedMarginTopFromFirstChild(layoutContext, firstInFlowChild));
127 }
128
129 LayoutUnit BlockFormattingContext::MarginCollapse::nonCollapsedMarginTop(const LayoutContext& layoutContext, const Box& layoutBox)
130 {
131     // Non collapsed margin top includes collapsed margin from inflow first child.
132     return marginValue(computedNonCollapsedMarginTop(layoutContext, layoutBox), collapsedMarginTopFromFirstChild(layoutContext, layoutBox));
133 }
134
135 /*static bool hasAdjoiningMarginTopAndBottom(const Box&)
136 {
137     // Two margins are adjoining if and only if:
138     // 1. both belong to in-flow block-level boxes that participate in the same block formatting context
139     // 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.)
140     // 3. both belong to vertically-adjacent box edges, i.e. form one of the following pairs:
141     //        top margin of a box and top margin of its first in-flow child
142     //        bottom margin of box and top margin of its next in-flow following sibling
143     //        bottom margin of a last in-flow child and bottom margin of its parent if the parent has 'auto' computed height
144     //        top and bottom margins of a box that does not establish a new block formatting context and that has zero computed 'min-height',
145     //        zero or 'auto' computed 'height', and no in-flow children
146     // A collapsed margin is considered adjoining to another margin if any of its component margins is adjoining to that margin.
147     return false;
148 }*/
149 LayoutUnit BlockFormattingContext::MarginCollapse::computedNonCollapsedMarginTop(const LayoutContext& layoutContext, const Box& layoutBox)
150 {
151     return FormattingContext::Geometry::computedNonCollapsedVerticalMarginValue(layoutContext, layoutBox).top;
152 }
153
154 LayoutUnit BlockFormattingContext::MarginCollapse::computedNonCollapsedMarginBottom(const LayoutContext& layoutContext, const Box& layoutBox)
155 {
156     return FormattingContext::Geometry::computedNonCollapsedVerticalMarginValue(layoutContext, layoutBox).bottom;
157 }
158
159 LayoutUnit BlockFormattingContext::MarginCollapse::marginTop(const LayoutContext& layoutContext, const Box& layoutBox)
160 {
161     if (layoutBox.isAnonymous())
162         return 0;
163
164     // TODO: take _hasAdjoiningMarginTopAndBottom() into account.
165     auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
166     if (isMarginTopCollapsedWithParent(layoutBox, displayBox))
167         return 0;
168
169     // Floats and out of flow positioned boxes do not collapse their margins.
170     if (!isMarginTopCollapsedWithSibling(layoutBox))
171         return nonCollapsedMarginTop(layoutContext, layoutBox);
172
173     // The bottom margin of an in-flow block-level element always collapses with the top margin of its next in-flow block-level sibling,
174     // unless that sibling has clearance.
175     auto* previousInFlowSibling = layoutBox.previousInFlowSibling();
176     if (!previousInFlowSibling)
177         return nonCollapsedMarginTop(layoutContext, layoutBox);
178
179     auto previousSiblingMarginBottom = nonCollapsedMarginBottom(layoutContext, *previousInFlowSibling);
180     auto marginTop = nonCollapsedMarginTop(layoutContext, layoutBox);
181     return marginValue(marginTop, previousSiblingMarginBottom);
182 }
183
184 LayoutUnit BlockFormattingContext::MarginCollapse::marginBottom(const LayoutContext& layoutContext, const Box& layoutBox)
185 {
186     if (layoutBox.isAnonymous())
187         return 0;
188
189     // TODO: take _hasAdjoiningMarginTopAndBottom() into account.
190     if (BlockFormattingContext::MarginCollapse::isMarginBottomCollapsedWithParent(layoutBox))
191         return 0;
192
193     // Floats and out of flow positioned boxes do not collapse their margins.
194     if (!isMarginBottomCollapsedWithSibling(layoutBox))
195         return nonCollapsedMarginBottom(layoutContext, layoutBox);
196
197     // The bottom margin of an in-flow block-level element always collapses with the top margin of its next in-flow block-level sibling,
198     // unless that sibling has clearance.
199     if (layoutBox.nextInFlowSibling())
200         return 0;
201     return nonCollapsedMarginBottom(layoutContext, layoutBox);
202 }
203
204 bool BlockFormattingContext::MarginCollapse::isMarginBottomCollapsedWithParent(const Box& layoutBox)
205 {
206     // last inflow box to parent.
207     // https://www.w3.org/TR/CSS21/box.html#collapsing-margins
208     if (layoutBox.isAnonymous())
209         return false;
210
211     if (layoutBox.isFloatingOrOutOfFlowPositioned())
212         return false;
213
214     // We never margin collapse the initial containing block.
215     ASSERT(layoutBox.parent());
216     auto& parent = *layoutBox.parent();
217     // Is this the last inlflow child?
218     if (parent.lastInFlowChild() != &layoutBox)
219         return false;
220
221     if (parent.establishesBlockFormattingContext())
222         return false;
223
224     // Margins of the root element's box do not collapse.
225     if (parent.isInitialContainingBlock())
226         return false;
227
228     if (!parent.style().borderTop().nonZero())
229         return false;
230
231     if (!parent.style().paddingTop().isZero())
232         return false;
233
234     if (!parent.style().height().isAuto())
235         return false;
236
237     return true;
238 }
239
240 bool BlockFormattingContext::MarginCollapse::isMarginTopCollapsedWithParentMarginBottom(const Box&)
241 {
242     return false;
243 }
244
245 LayoutUnit BlockFormattingContext::MarginCollapse::collapsedMarginBottomFromLastChild(const LayoutContext& layoutContext, const Box& layoutBox)
246 {
247     // Check if the last child propagates its margin bottom.
248     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
249         return 0;
250
251     auto& lastInFlowChild = *downcast<Container>(layoutBox).lastInFlowChild();
252     if (!isMarginBottomCollapsedWithParent(lastInFlowChild))
253         return 0;
254
255     // Collect collapsed margin bottom recursively.
256     return marginValue(computedNonCollapsedMarginBottom(layoutContext, lastInFlowChild), collapsedMarginBottomFromLastChild(layoutContext, lastInFlowChild));
257 }
258
259 LayoutUnit BlockFormattingContext::MarginCollapse::nonCollapsedMarginBottom(const LayoutContext& layoutContext, const Box& layoutBox)
260 {
261     // Non collapsed margin bottom includes collapsed margin from inflow last child.
262     return marginValue(computedNonCollapsedMarginBottom(layoutContext, layoutBox), collapsedMarginBottomFromLastChild(layoutContext, layoutBox));
263 }
264
265 }
266 }
267 #endif