fe84bd8bd01649bfd5544c41ff968b31f7577710
[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, UsedVerticalValues usedValues)
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& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
61         auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
62         auto computedVerticalMargin = Geometry::computedVerticalMargin(layoutBox, UsedHorizontalValues { containingBlockWidth });
63         auto nonCollapsedMargin = UsedVerticalMargin::NonCollapsedValues { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) }; 
64         auto borderAndPaddingTop = displayBox.borderTop() + displayBox.paddingTop().valueOr(0);
65         auto height = usedValues.height ? usedValues.height.value() : computedHeightValue(layoutState, layoutBox, HeightType::Normal);
66
67         if (height) {
68             auto borderAndPaddingBottom = displayBox.borderBottom() + displayBox.paddingBottom().valueOr(0);
69             auto contentHeight = layoutBox.style().boxSizing() == BoxSizing::ContentBox ? *height : *height - (borderAndPaddingTop + borderAndPaddingBottom);  
70             return { contentHeight, nonCollapsedMargin };
71         }
72
73         if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
74             return { 0, nonCollapsedMargin };
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& inlineRuns = downcast<InlineFormattingState>(layoutState.establishedFormattingState(layoutBox)).inlineRuns();
80             auto bottomEdge = inlineRuns.isEmpty() ? LayoutUnit() : inlineRuns.last().logicalBottom();
81             return { bottomEdge - borderAndPaddingTop, nonCollapsedMargin };
82         }
83
84         // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin...
85         auto* lastInFlowChild = downcast<Container>(layoutBox).lastInFlowChild();
86         ASSERT(lastInFlowChild);
87         if (!MarginCollapse::marginAfterCollapsesWithParentMarginAfter(layoutState, *lastInFlowChild)) {
88             auto& lastInFlowDisplayBox = layoutState.displayBoxForLayoutBox(*lastInFlowChild);
89             auto bottomEdgeOfBottomMargin = lastInFlowDisplayBox.bottom() + (lastInFlowDisplayBox.hasCollapsedThroughMargin() ? LayoutUnit() : lastInFlowDisplayBox.marginAfter()); 
90             return { bottomEdgeOfBottomMargin - borderAndPaddingTop, nonCollapsedMargin };
91         }
92
93         // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
94         auto* inFlowChild = lastInFlowChild;
95         while (inFlowChild && MarginCollapse::marginBeforeCollapsesWithParentMarginAfter(layoutState, *inFlowChild))
96             inFlowChild = inFlowChild->previousInFlowSibling();
97         if (inFlowChild) {
98             auto& inFlowDisplayBox = layoutState.displayBoxForLayoutBox(*inFlowChild);
99             return { inFlowDisplayBox.top() + inFlowDisplayBox.borderBox().height() - borderAndPaddingTop, nonCollapsedMargin };
100         }
101
102         // 4. zero, otherwise
103         return { 0, nonCollapsedMargin };
104     };
105
106     auto heightAndMargin = compute();
107     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> height(" << heightAndMargin.height << "px) margin(" << heightAndMargin.nonCollapsedMargin.before << "px, " << heightAndMargin.nonCollapsedMargin.after << "px) -> layoutBox(" << &layoutBox << ")");
108     return heightAndMargin;
109 }
110
111 WidthAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, UsedHorizontalValues usedValues)
112 {
113     ASSERT(layoutBox.isInFlow());
114
115     auto compute = [&]() {
116
117         // 10.3.3 Block-level, non-replaced elements in normal flow
118         //
119         // The following constraints must hold among the used values of the other properties:
120         // 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
121         //
122         // 1. If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' 
123         //    (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then
124         //    any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero.
125         //
126         // 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
127         //    have to be different from its computed value. If the 'direction' property of the containing block has the value 'ltr', the specified value
128         //    of 'margin-right' is ignored and the value is calculated so as to make the equality true. If the value of 'direction' is 'rtl',
129         //    this happens to 'margin-left' instead.
130         //
131         // 3. If there is exactly one value specified as 'auto', its used value follows from the equality.
132         //
133         // 4. If 'width' is set to 'auto', any other 'auto' values become '0' and 'width' follows from the resulting equality.
134         //
135         // 5. If both 'margin-left' and 'margin-right' are 'auto', their used values are equal. This horizontally centers the element with respect to the
136         //    edges of the containing block.
137
138         auto& style = layoutBox.style();
139         auto* containingBlock = layoutBox.containingBlock();
140         auto containingBlockWidth = usedValues.containingBlockWidth.valueOr(0);
141         auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
142
143         auto width = computedValueIfNotAuto(usedValues.width ? Length { usedValues.width.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
144         auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutBox, usedValues);
145         UsedHorizontalMargin usedHorizontalMargin;
146         auto borderLeft = displayBox.borderLeft();
147         auto borderRight = displayBox.borderRight();
148         auto paddingLeft = displayBox.paddingLeft().valueOr(0);
149         auto paddingRight = displayBox.paddingRight().valueOr(0);
150         auto contentWidth = [&] {
151             ASSERT(width);
152             return style.boxSizing() == BoxSizing::ContentBox ? *width : *width - (borderLeft + paddingLeft + paddingRight + borderRight);
153         };
154
155         // #1
156         if (width) {
157             auto horizontalSpaceForMargin = containingBlockWidth - (computedHorizontalMargin.start.valueOr(0) + borderLeft + paddingLeft + contentWidth() + paddingRight + borderRight + computedHorizontalMargin.end.valueOr(0));
158             if (horizontalSpaceForMargin < 0)
159                 usedHorizontalMargin = { computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) };
160         }
161
162         // #2
163         if (width && computedHorizontalMargin.start && computedHorizontalMargin.end) {
164             if (containingBlock->style().isLeftToRightDirection()) {
165                 usedHorizontalMargin.start = *computedHorizontalMargin.start;
166                 usedHorizontalMargin.end = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + contentWidth() + paddingRight + borderRight);
167             } else {
168                 usedHorizontalMargin.end = *computedHorizontalMargin.end;
169                 usedHorizontalMargin.start = containingBlockWidth - (borderLeft + paddingLeft + contentWidth() + paddingRight + borderRight + usedHorizontalMargin.end);
170             }
171         }
172
173         // #3
174         if (!computedHorizontalMargin.start && width && computedHorizontalMargin.end) {
175             usedHorizontalMargin.end = *computedHorizontalMargin.end;
176             usedHorizontalMargin.start = containingBlockWidth - (borderLeft + paddingLeft  + contentWidth() + paddingRight + borderRight + usedHorizontalMargin.end);
177         } else if (computedHorizontalMargin.start && !width && computedHorizontalMargin.end) {
178             usedHorizontalMargin = { *computedHorizontalMargin.start, *computedHorizontalMargin.end };
179             width = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + paddingRight + borderRight + usedHorizontalMargin.end);
180         } else if (computedHorizontalMargin.start && width && !computedHorizontalMargin.end) {
181             usedHorizontalMargin.start = *computedHorizontalMargin.start;
182             usedHorizontalMargin.end = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + contentWidth() + paddingRight + borderRight);
183         }
184
185         // #4
186         if (!width) {
187             usedHorizontalMargin = { computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) };
188             width = containingBlockWidth - (usedHorizontalMargin.start + borderLeft + paddingLeft + paddingRight + borderRight + usedHorizontalMargin.end);
189         }
190
191         // #5
192         if (!computedHorizontalMargin.start && !computedHorizontalMargin.end) {
193             auto horizontalSpaceForMargin = containingBlockWidth - (borderLeft + paddingLeft  + contentWidth() + paddingRight + borderRight);
194             usedHorizontalMargin = { horizontalSpaceForMargin / 2, horizontalSpaceForMargin / 2 };
195         }
196
197         ASSERT(width);
198
199         return WidthAndMargin { contentWidth(), usedHorizontalMargin, computedHorizontalMargin };
200     };
201
202     auto widthAndMargin = compute();
203     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow non-replaced -> width(" << widthAndMargin.width << "px) margin(" << widthAndMargin.usedMargin.start << "px, " << widthAndMargin.usedMargin.end << "px) -> layoutBox(" << &layoutBox << ")");
204     return widthAndMargin;
205 }
206
207 WidthAndMargin BlockFormattingContext::Geometry::inFlowReplacedWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, UsedHorizontalValues usedValues)
208 {
209     ASSERT(layoutBox.isInFlow() && layoutBox.replaced());
210
211     // 10.3.4 Block-level, replaced elements in normal flow
212     //
213     // 1. The used value of 'width' is determined as for inline replaced elements.
214     // 2. Then the rules for non-replaced block-level elements are applied to determine the margins.
215
216     // #1
217     usedValues.width = inlineReplacedWidthAndMargin(layoutState, layoutBox, usedValues).width;
218     // #2
219     auto nonReplacedWidthAndMargin = inFlowNonReplacedWidthAndMargin(layoutState, layoutBox, usedValues);
220
221     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow replaced -> width(" << *usedValues.width << "px) margin(" << nonReplacedWidthAndMargin.usedMargin.start << "px, " << nonReplacedWidthAndMargin.usedMargin.end << "px) -> layoutBox(" << &layoutBox << ")");
222     return { *usedValues.width, nonReplacedWidthAndMargin.usedMargin, nonReplacedWidthAndMargin.computedMargin };
223 }
224
225 Point BlockFormattingContext::Geometry::staticPosition(const LayoutState& layoutState, const Box& layoutBox)
226 {
227     // https://www.w3.org/TR/CSS22/visuren.html#block-formatting
228     // In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.
229     // The vertical distance between two sibling boxes is determined by the 'margin' properties.
230     // Vertical margins between adjacent block-level boxes in a block formatting context collapse.
231     // 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).
232
233     LayoutUnit top;
234     auto& containingBlockDisplayBox = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock());
235     if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
236         auto& previousInFlowDisplayBox = layoutState.displayBoxForLayoutBox(*previousInFlowSibling);
237         top = previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.marginAfter();
238     } else
239         top = containingBlockDisplayBox.contentBoxTop();
240
241     auto left = containingBlockDisplayBox.contentBoxLeft() + layoutState.displayBoxForLayoutBox(layoutBox).marginStart();
242     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position] -> static -> top(" << top << "px) left(" << left << "px) layoutBox(" << &layoutBox << ")");
243     return { left, top };
244 }
245
246 HeightAndMargin BlockFormattingContext::Geometry::inFlowHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, UsedVerticalValues usedValues)
247 {
248     ASSERT(layoutBox.isInFlow());
249
250     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block'
251     // replaced elements in normal flow and floating replaced elements
252     if (layoutBox.replaced())
253         return inlineReplacedHeightAndMargin(layoutState, layoutBox, usedValues);
254
255     HeightAndMargin heightAndMargin;
256     // TODO: Figure out the case for the document element. Let's just complicated-case it for now.
257     if (layoutBox.isOverflowVisible() && !layoutBox.isDocumentBox())
258         heightAndMargin = inFlowNonReplacedHeightAndMargin(layoutState, layoutBox, usedValues);
259     else {
260         // 10.6.6 Complicated cases
261         // 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).
262         auto usedHorizontalValues = UsedHorizontalValues { layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth() };
263         heightAndMargin = complicatedCases(layoutState, layoutBox, usedValues, usedHorizontalValues);
264     }
265
266     if (!Quirks::needsStretching(layoutState, layoutBox))
267         return heightAndMargin;
268
269     heightAndMargin = Quirks::stretchedInFlowHeight(layoutState, layoutBox, heightAndMargin);
270
271     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> streched to viewport -> height(" << heightAndMargin.height << "px) margin(" << heightAndMargin.nonCollapsedMargin.before << "px, " << heightAndMargin.nonCollapsedMargin.after << "px) -> layoutBox(" << &layoutBox << ")");
272     return heightAndMargin;
273 }
274
275 WidthAndMargin BlockFormattingContext::Geometry::inFlowWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, UsedHorizontalValues usedValues)
276 {
277     ASSERT(layoutBox.isInFlow());
278
279     if (!layoutBox.replaced())
280         return inFlowNonReplacedWidthAndMargin(layoutState, layoutBox, usedValues);
281     return inFlowReplacedWidthAndMargin(layoutState, layoutBox, usedValues);
282 }
283
284 bool BlockFormattingContext::Geometry::intrinsicWidthConstraintsNeedChildrenWidth(const Box& layoutBox)
285 {
286     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
287         return false;
288     return layoutBox.style().width().isAuto();
289 }
290
291 FormattingContext::IntrinsicWidthConstraints BlockFormattingContext::Geometry::intrinsicWidthConstraints(const LayoutState& layoutState, const Box& layoutBox)
292 {
293     auto computedIntrinsicWidthConstraints = [&]() -> IntrinsicWidthConstraints {
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         auto intrinsicWidthConstraints = IntrinsicWidthConstraints { };
306         for (auto& child : childrenOfType<Box>(downcast<Container>(layoutBox))) {
307             if (child.isOutOfFlowPositioned())
308                 continue;
309             const auto& formattingState = layoutState.formattingStateForBox(child);
310             ASSERT(formattingState.isBlockFormattingState());
311             auto childIntrinsicWidthConstraints = formattingState.intrinsicWidthConstraints(child);
312             ASSERT(childIntrinsicWidthConstraints);
313
314             auto& childStyle = child.style();
315             auto marginBorderAndPadding = fixedValue(childStyle.marginStart()).valueOr(0)
316                 + LayoutUnit { childStyle.borderLeftWidth() }
317                 + fixedValue(childStyle.paddingLeft()).valueOr(0)
318                 + fixedValue(childStyle.paddingRight()).valueOr(0)
319                 + LayoutUnit { childStyle.borderRightWidth() }
320                 + fixedValue(childStyle.marginEnd()).valueOr(0);
321             intrinsicWidthConstraints.minimum = std::max(intrinsicWidthConstraints.minimum, childIntrinsicWidthConstraints->minimum + marginBorderAndPadding);
322             intrinsicWidthConstraints.maximum = std::max(intrinsicWidthConstraints.maximum, childIntrinsicWidthConstraints->maximum + marginBorderAndPadding);
323         }
324         return intrinsicWidthConstraints;
325     };
326
327     return constrainByMinMaxWidth(layoutBox, computedIntrinsicWidthConstraints());
328 }
329
330 }
331 }
332
333 #endif