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