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