[LFC][Quirk] Move quirk functions to dedicated classes.
[WebKit-https.git] / Source / WebCore / layout / blockformatting / BlockFormattingContextGeometry.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 "FormattingContext.h"
32 #include "InlineFormattingState.h"
33 #include "LayoutChildIterator.h"
34 #include "Logging.h"
35 #include <wtf/text/TextStream.h>
36
37 namespace WebCore {
38 namespace Layout {
39
40 HeightAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedHeight)
41 {
42     ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
43     ASSERT(layoutBox.isOverflowVisible());
44
45     auto compute = [&]() -> HeightAndMargin {
46
47         // 10.6.3 Block-level non-replaced elements in normal flow when 'overflow' computes to 'visible'
48         //
49         // If 'margin-top', or 'margin-bottom' are 'auto', their used value is 0.
50         // If 'height' is 'auto', the height depends on whether the element has any block-level children and whether it has padding or borders:
51         // The element's height is the distance from its top content edge to the first applicable of the following:
52         // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
53         // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin
54         //    does not collapse with the element's bottom margin
55         // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
56         // 4. zero, otherwise
57         // Only children in the normal flow are taken into account (i.e., floating boxes and absolutely positioned boxes are ignored,
58         // and relatively positioned boxes are considered without their offset). Note that the child box may be an anonymous block box.
59
60         auto& style = layoutBox.style();
61         auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
62         auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
63
64         VerticalEdges nonCollapsedMargin = { computedValueIfNotAuto(style.marginTop(), containingBlockWidth).value_or(0),
65             computedValueIfNotAuto(style.marginBottom(), containingBlockWidth).value_or(0) }; 
66         VerticalEdges collapsedMargin = { MarginCollapse::marginTop(layoutState, layoutBox), MarginCollapse::marginBottom(layoutState, layoutBox) };
67         auto borderAndPaddingTop = displayBox.borderTop() + displayBox.paddingTop().value_or(0);
68         
69         auto height = usedHeight ? usedHeight.value() : computedHeightValue(layoutState, layoutBox, HeightType::Normal);
70         if (height)
71             return { height.value(), nonCollapsedMargin, collapsedMargin };
72
73         if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
74             return { 0, nonCollapsedMargin, collapsedMargin };
75
76         // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
77         if (layoutBox.establishesInlineFormattingContext()) {
78             // This is temp and will be replaced by the correct display box once inline runs move over to the display tree.
79             auto& lastInlineRun = downcast<InlineFormattingState>(layoutState.establishedFormattingState(layoutBox)).inlineRuns().last();
80             return { lastInlineRun.logicalBottom(), nonCollapsedMargin, collapsedMargin };
81         }
82
83         // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin...
84         auto* lastInFlowChild = downcast<Container>(layoutBox).lastInFlowChild();
85         ASSERT(lastInFlowChild);
86         if (!MarginCollapse::isMarginBottomCollapsedWithParent(layoutState, *lastInFlowChild)) {
87             auto& lastInFlowDisplayBox = layoutState.displayBoxForLayoutBox(*lastInFlowChild);
88             return { lastInFlowDisplayBox.bottom() + lastInFlowDisplayBox.marginBottom() - borderAndPaddingTop, nonCollapsedMargin, collapsedMargin };
89         }
90
91         // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
92         auto* inFlowChild = lastInFlowChild;
93         while (inFlowChild && MarginCollapse::isMarginTopCollapsedWithParentMarginBottom(*inFlowChild))
94             inFlowChild = inFlowChild->previousInFlowSibling();
95         if (inFlowChild) {
96             auto& inFlowDisplayBox = layoutState.displayBoxForLayoutBox(*inFlowChild);
97             return { inFlowDisplayBox.top() + inFlowDisplayBox.borderBox().height() - borderAndPaddingTop, nonCollapsedMargin, collapsedMargin };
98         }
99
100         // 4. zero, otherwise
101         return { 0, nonCollapsedMargin, collapsedMargin };
102     };
103
104     auto heightAndMargin = compute();
105
106     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> height(" << heightAndMargin.height << "px) margin(" << heightAndMargin.margin.top << "px, " << heightAndMargin.margin.bottom << "px) -> layoutBox(" << &layoutBox << ")");
107     return heightAndMargin;
108 }
109
110 WidthAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedWidth)
111 {
112     ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
113
114     auto compute = [&]() {
115
116         // 10.3.3 Block-level, non-replaced elements in normal flow
117         //
118         // The following constraints must hold among the used values of the other properties:
119         // 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
120         //
121         // 1. If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' 
122         //    (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then
123         //    any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero.
124         //
125         // 2. If all of the above have a computed value other than 'auto', the values are said to be "over-constrained" and one of the used values will
126         //    have to be different from its computed value. If the 'direction' property of the containing block has the value 'ltr', the specified value
127         //    of 'margin-right' is ignored and the value is calculated so as to make the equality true. If the value of 'direction' is 'rtl',
128         //    this happens to 'margin-left' instead.
129         //
130         // 3. If there is exactly one value specified as 'auto', its used value follows from the equality.
131         //
132         // 4. If 'width' is set to 'auto', any other 'auto' values become '0' and 'width' follows from the resulting equality.
133         //
134         // 5. If both 'margin-left' and 'margin-right' are 'auto', their used values are equal. This horizontally centers the element with respect to the
135         //    edges of the containing block.
136
137         auto& style = layoutBox.style();
138         auto* containingBlock = layoutBox.containingBlock();
139         auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*containingBlock).contentBoxWidth();
140         auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
141
142         auto width = computedValueIfNotAuto(usedWidth ? Length { usedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
143         auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth);
144         auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth);
145         auto nonComputedMarginLeft = marginLeft.value_or(0);
146         auto nonComputedMarginRight = marginRight.value_or(0);
147         auto borderLeft = displayBox.borderLeft();
148         auto borderRight = displayBox.borderRight();
149         auto paddingLeft = displayBox.paddingLeft().value_or(0);
150         auto paddingRight = displayBox.paddingRight().value_or(0);
151
152         // #1
153         if (width) {
154             auto horizontalSpaceForMargin = containingBlockWidth - (marginLeft.value_or(0) + borderLeft + paddingLeft + *width + paddingRight + borderRight + marginRight.value_or(0));
155             if (horizontalSpaceForMargin < 0) {
156                 marginLeft = marginLeft.value_or(0);
157                 marginRight = marginRight.value_or(0);
158             }
159         }
160
161         // #2
162         if (width && marginLeft && marginRight) {
163             if (containingBlock->style().isLeftToRightDirection())
164                 marginRight = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft  + *width + paddingRight + borderRight);
165             else
166                 marginLeft = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
167         }
168
169         // #3
170         if (!marginLeft && width && marginRight)
171             marginLeft = containingBlockWidth - (borderLeft + paddingLeft  + *width + paddingRight + borderRight + *marginRight);
172         else if (marginLeft && !width && marginRight)
173             width = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + paddingRight + borderRight + *marginRight);
174         else if (marginLeft && width && !marginRight)
175             marginRight = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight);
176
177         // #4
178         if (!width) {
179             marginLeft = marginLeft.value_or(0);
180             marginRight = marginRight.value_or(0);
181             width = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + paddingRight + borderRight + *marginRight);
182         }
183
184         // #5
185         if (!marginLeft && !marginRight) {
186             auto horizontalSpaceForMargin = containingBlockWidth - (borderLeft + paddingLeft  + *width + paddingRight + borderRight);
187             marginLeft = marginRight = horizontalSpaceForMargin / 2;
188         }
189
190         ASSERT(width);
191         ASSERT(marginLeft);
192         ASSERT(marginRight);
193
194         return WidthAndMargin { *width, { *marginLeft, *marginRight }, { nonComputedMarginLeft, nonComputedMarginRight } };
195     };
196
197     auto widthAndMargin = compute();
198     if (!Quirks::isStretchedToInitialContainingBlock(layoutState, layoutBox)) {
199         LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow non-replaced -> width(" << widthAndMargin.width << "px) margin(" << widthAndMargin.margin.left << "px, " << widthAndMargin.margin.right << "px) -> layoutBox(" << &layoutBox << ")");
200         return widthAndMargin;
201     }
202
203     widthAndMargin = Quirks::stretchedWidth(layoutState, layoutBox, widthAndMargin);
204
205     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow non-replaced -> streched to viewport-> width(" << widthAndMargin.width << "px) margin(" << widthAndMargin.margin.left << "px, " << widthAndMargin.margin.right << "px) -> layoutBox(" << &layoutBox << ")");
206     return widthAndMargin;
207 }
208
209 WidthAndMargin BlockFormattingContext::Geometry::inFlowReplacedWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedWidth)
210 {
211     ASSERT(layoutBox.isInFlow() && layoutBox.replaced());
212
213     // 10.3.4 Block-level, replaced elements in normal flow
214     //
215     // 1. The used value of 'width' is determined as for inline replaced elements.
216     // 2. Then the rules for non-replaced block-level elements are applied to determine the margins.
217
218     // #1
219     auto width = inlineReplacedWidthAndMargin(layoutState, layoutBox, usedWidth).width;
220     // #2
221     auto nonReplacedWidthAndMargin = inFlowNonReplacedWidthAndMargin(layoutState, layoutBox, width);
222
223     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow replaced -> width(" << width << "px) margin(" << nonReplacedWidthAndMargin.margin.left << "px, " << nonReplacedWidthAndMargin.margin.right << "px) -> layoutBox(" << &layoutBox << ")");
224     return { width, nonReplacedWidthAndMargin.margin, nonReplacedWidthAndMargin.nonComputedMargin };
225 }
226
227 Point BlockFormattingContext::Geometry::staticPosition(const LayoutState& layoutState, const Box& layoutBox)
228 {
229     // https://www.w3.org/TR/CSS22/visuren.html#block-formatting
230     // In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.
231     // The vertical distance between two sibling boxes is determined by the 'margin' properties.
232     // Vertical margins between adjacent block-level boxes in a block formatting context collapse.
233     // In a block formatting context, each box's left outer edge touches the left edge of the containing block (for right-to-left formatting, right edges touch).
234
235     LayoutUnit top;
236     auto& containingBlockDisplayBox = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock());
237     if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
238         auto& previousInFlowDisplayBox = layoutState.displayBoxForLayoutBox(*previousInFlowSibling);
239         top = previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.marginBottom();
240     } else
241         top = containingBlockDisplayBox.contentBoxTop();
242
243     auto left = containingBlockDisplayBox.contentBoxLeft();
244     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position] -> static -> top(" << top << "px) left(" << left << "px) layoutBox(" << &layoutBox << ")");
245     return { left, top };
246 }
247
248 HeightAndMargin BlockFormattingContext::Geometry::inFlowHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedHeight)
249 {
250     ASSERT(layoutBox.isInFlow());
251
252     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block'
253     // replaced elements in normal flow and floating replaced elements
254     if (layoutBox.replaced())
255         return inlineReplacedHeightAndMargin(layoutState, layoutBox, usedHeight);
256
257     HeightAndMargin heightAndMargin;
258     // TODO: Figure out the case for the document element. Let's just complicated-case it for now.
259     if (layoutBox.isOverflowVisible() && !layoutBox.isDocumentBox())
260         heightAndMargin = inFlowNonReplacedHeightAndMargin(layoutState, layoutBox, usedHeight);
261     else {
262         // 10.6.6 Complicated cases
263         // Block-level, non-replaced elements in normal flow when 'overflow' does not compute to 'visible' (except if the 'overflow' property's value has been propagated to the viewport).
264         heightAndMargin = complicatedCases(layoutState, layoutBox, usedHeight);
265     }
266
267     if (!Quirks::isStretchedToInitialContainingBlock(layoutState, layoutBox))
268         return heightAndMargin;
269
270     heightAndMargin = Quirks::stretchedHeight(layoutState, layoutBox, heightAndMargin);
271
272     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> streched to viewport -> height(" << heightAndMargin.height << "px) margin(" << heightAndMargin.margin.top << "px, " << heightAndMargin.margin.bottom << "px) -> layoutBox(" << &layoutBox << ")");
273     return heightAndMargin;
274 }
275
276 WidthAndMargin BlockFormattingContext::Geometry::inFlowWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedWidth)
277 {
278     ASSERT(layoutBox.isInFlow());
279
280     if (!layoutBox.replaced())
281         return inFlowNonReplacedWidthAndMargin(layoutState, layoutBox, usedWidth);
282     return inFlowReplacedWidthAndMargin(layoutState, layoutBox, usedWidth);
283 }
284
285 bool BlockFormattingContext::Geometry::instrinsicWidthConstraintsNeedChildrenWidth(const Box& layoutBox)
286 {
287     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
288         return false;
289     return layoutBox.style().width().isAuto();
290 }
291
292 FormattingContext::InstrinsicWidthConstraints BlockFormattingContext::Geometry::instrinsicWidthConstraints(const LayoutState& layoutState, const Box& layoutBox)
293 {
294     auto& style = layoutBox.style();
295     if (auto width = fixedValue(style.logicalWidth()))
296         return { *width, *width };
297
298     // Minimum/maximum width can't be depending on the containing block's width.
299     if (!style.logicalWidth().isAuto())
300         return { };
301
302     if (!is<Container>(layoutBox))
303         return { };
304
305     LayoutUnit minimumIntrinsicWidth;
306     LayoutUnit maximumIntrinsicWidth;
307
308     for (auto& child : childrenOfType<Box>(downcast<Container>(layoutBox))) {
309         if (child.isOutOfFlowPositioned())
310             continue;
311         auto& formattingState = layoutState.formattingStateForBox(child);
312         ASSERT(formattingState.isBlockFormattingState());
313         auto childInstrinsicWidthConstraints = formattingState.instrinsicWidthConstraints(child);
314         ASSERT(childInstrinsicWidthConstraints);
315         
316         auto& style = child.style();
317         auto horizontalMarginBorderAndPadding = fixedValue(style.marginLeft()).value_or(0)
318             + LayoutUnit { style.borderLeftWidth() }
319             + fixedValue(style.paddingLeft()).value_or(0)
320             + fixedValue(style.paddingRight()).value_or(0)
321             + LayoutUnit { style.borderRightWidth() }
322             + fixedValue(style.marginRight()).value_or(0);
323
324         minimumIntrinsicWidth = std::max(minimumIntrinsicWidth, childInstrinsicWidthConstraints->minimum + horizontalMarginBorderAndPadding); 
325         maximumIntrinsicWidth = std::max(maximumIntrinsicWidth, childInstrinsicWidthConstraints->maximum + horizontalMarginBorderAndPadding); 
326     }
327
328     return { minimumIntrinsicWidth, maximumIntrinsicWidth };
329 }
330
331 LayoutUnit BlockFormattingContext::Geometry::estimatedMarginTop(const LayoutState& layoutState, const Box& layoutBox)
332 {
333     ASSERT(layoutBox.isBlockLevelBox());
334     // Can't estimate vertical margins for out of flow boxes (and we shouldn't need to do it for float boxes).
335     ASSERT(layoutBox.isInFlow());
336     // Can't cross block formatting context boundary.
337     ASSERT(!layoutBox.establishesBlockFormattingContext());
338
339     // Let's just use the normal path for now.
340     return MarginCollapse::marginTop(layoutState, layoutBox);
341 }
342
343
344 }
345 }
346
347 #endif