[LFC] Add FormattingContextLayout logging channel
[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 "Logging.h"
33 #include <wtf/text/TextStream.h>
34
35 namespace WebCore {
36 namespace Layout {
37
38 static bool isStretchedToViewport(const LayoutContext& layoutContext, const Box& layoutBox)
39 {
40     ASSERT(layoutBox.isInFlow());
41     // In quirks mode, body and html stretch to the viewport.
42     if (!layoutContext.inQuirksMode())
43         return false;
44
45     if (!layoutBox.isDocumentBox() && !layoutBox.isBodyBox())
46         return false;
47
48     return layoutBox.style().logicalHeight().isAuto();
49 }
50
51 static const Container& initialContainingBlock(const Box& layoutBox)
52 {
53     auto* containingBlock = layoutBox.containingBlock();
54     while (containingBlock->containingBlock())
55         containingBlock = containingBlock->containingBlock();
56     return *containingBlock;
57 }
58
59 FormattingContext::Geometry::HeightAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedHeightAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
60 {
61     ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
62
63     auto compute = [&]() -> LayoutUnit {
64         // 10.6.3 Block-level non-replaced elements in normal flow when 'overflow' computes to 'visible'
65         //
66         // If 'margin-top', or 'margin-bottom' are 'auto', their used value is 0.
67         // If 'height' is 'auto', the height depends on whether the element has any block-level children and whether it has padding or borders:
68         // The element's height is the distance from its top content edge to the first applicable of the following:
69         // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
70         // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin
71         //    does not collapse with the element's bottom margin
72         // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
73         // 4. zero, otherwise
74         // Only children in the normal flow are taken into account (i.e., floating boxes and absolutely positioned boxes are ignored,
75         // and relatively positioned boxes are considered without their offset). Note that the child box may be an anonymous block box.
76         if (!layoutBox.style().logicalHeight().isAuto()) {
77             // FIXME: Only fixed values yet.
78             return layoutBox.style().logicalHeight().value();
79         }
80
81         if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
82             return 0;
83
84         // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
85         if (layoutBox.establishesInlineFormattingContext()) {
86             // height = lastLineBox().bottom();
87             return 0;
88         }
89
90         // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin...
91         auto* lastInFlowChild = downcast<Container>(layoutBox).lastInFlowChild();
92         ASSERT(lastInFlowChild);
93         if (!MarginCollapse::isMarginBottomCollapsedWithParent(*lastInFlowChild)) {
94             auto* lastInFlowDisplayBox = layoutContext.displayBoxForLayoutBox(*lastInFlowChild);
95             ASSERT(lastInFlowDisplayBox);
96             return lastInFlowDisplayBox->bottom() + lastInFlowDisplayBox->marginBottom();
97         }
98
99         // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
100         auto* inFlowChild = lastInFlowChild;
101         while (inFlowChild && MarginCollapse::isMarginTopCollapsedWithParentMarginBottom(*inFlowChild))
102             inFlowChild = inFlowChild->previousInFlowSibling();
103         if (inFlowChild) {
104             auto* inFlowDisplayBox = layoutContext.displayBoxForLayoutBox(*inFlowChild);
105             ASSERT(inFlowDisplayBox);
106             return inFlowDisplayBox->top() + inFlowDisplayBox->borderBox().height();
107         }
108
109         // 4. zero, otherwise
110         return 0;
111     };
112
113     auto height = compute();
114     auto marginTop = MarginCollapse::marginTop(layoutContext, layoutBox);
115     auto marginBottom =  MarginCollapse::marginBottom(layoutContext, layoutBox);
116
117     if (!isStretchedToViewport(layoutContext, layoutBox)) {
118         LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> height(" << height << "px) margin(" << marginTop << "px, " << marginBottom << "px) -> layoutBox(" << &layoutBox << ")");
119         return { height, { marginTop, marginBottom } };
120     }
121
122     auto initialContainingBlockHeight = layoutContext.displayBoxForLayoutBox(initialContainingBlock(layoutBox))->contentBox().height();
123     // Stretch but never overstretch with the margins.
124     if (height + marginTop + marginBottom < initialContainingBlockHeight)
125         height = initialContainingBlockHeight - marginTop - marginBottom;
126
127     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> streched to viewport -> height(" << height << "px) margin(" << marginTop << "px, " << marginBottom << "px) -> layoutBox(" << &layoutBox << ")");
128     return { height, { marginTop, marginBottom } };
129 }
130
131 FormattingContext::Geometry::WidthAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox,
132     std::optional<LayoutUnit> precomputedWidth)
133 {
134     ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
135
136     auto compute = [&]() {
137         // 10.3.3 Block-level, non-replaced elements in normal flow
138         // The following constraints must hold among the used values of the other properties:
139         // 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
140         //
141         // 1. If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' 
142         //    (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then
143         //    any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero.
144         //
145         // 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
146         //    have to be different from its computed value. If the 'direction' property of the containing block has the value 'ltr', the specified value
147         //    of 'margin-right' is ignored and the value is calculated so as to make the equality true. If the value of 'direction' is 'rtl',
148         //    this happens to 'margin-left' instead.
149         //
150         // 3. If there is exactly one value specified as 'auto', its used value follows from the equality.
151         //
152         // 4. If 'width' is set to 'auto', any other 'auto' values become '0' and 'width' follows from the resulting equality.
153         //
154         // 5. If both 'margin-left' and 'margin-right' are 'auto', their used values are equal. This horizontally centers the element with respect to the
155         //    edges of the containing block.
156
157         auto& style = layoutBox.style();
158         auto width = precomputedWidth ? Length { precomputedWidth.value(), Fixed } : style.logicalWidth();
159         auto* containingBlock = layoutBox.containingBlock();
160         auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*containingBlock)->width();
161         auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
162
163         LayoutUnit computedWidthValue;
164         std::optional<LayoutUnit> computedMarginLeftValue;
165         std::optional<LayoutUnit> computedMarginRightValue;
166
167         auto marginLeft = style.marginLeft();
168         if (!marginLeft.isAuto())
169             computedMarginLeftValue = valueForLength(marginLeft, containingBlockWidth);
170
171         auto marginRight = style.marginRight();
172         if (!marginRight.isAuto())
173             computedMarginRightValue = valueForLength(marginRight, containingBlockWidth);
174
175         auto borderLeft = displayBox.borderLeft();
176         auto borderRight = displayBox.borderRight();
177         auto paddingLeft = displayBox.paddingLeft();
178         auto paddingRight = displayBox.paddingRight();
179
180         // #1
181         if (!width.isAuto()) {
182             computedWidthValue = valueForLength(width, containingBlockWidth);
183             auto horizontalSpaceForMargin = containingBlockWidth - (computedMarginLeftValue.value_or(0) + borderLeft + paddingLeft 
184                 + computedWidthValue + paddingRight + borderRight + computedMarginRightValue.value_or(0));
185
186             if (horizontalSpaceForMargin < 0) {
187                 if (!computedMarginLeftValue)
188                     computedMarginLeftValue = LayoutUnit(0);
189                 if (!computedMarginRightValue)
190                     computedMarginRightValue = LayoutUnit(0);
191             }
192         }
193
194         // #2
195         if (!width.isAuto() && !marginLeft.isAuto() && !marginRight.isAuto()) {
196             ASSERT(computedMarginLeftValue);
197             ASSERT(computedMarginRightValue);
198
199             if (containingBlock->style().isLeftToRightDirection())
200                 computedMarginRightValue = containingBlockWidth - (*computedMarginLeftValue + borderLeft + paddingLeft  + computedWidthValue + paddingRight + borderRight);
201             else
202                 computedMarginLeftValue = containingBlockWidth - (borderLeft + paddingLeft  + computedWidthValue + paddingRight + borderRight + *computedMarginRightValue);
203         }
204
205         // #3
206         if (!computedMarginLeftValue && !marginRight.isAuto() && !width.isAuto()) {
207             ASSERT(computedMarginRightValue);
208             computedMarginLeftValue = containingBlockWidth - (borderLeft + paddingLeft  + computedWidthValue + paddingRight + borderRight + *computedMarginRightValue);
209         } else if (!computedMarginRightValue && !marginLeft.isAuto() && !width.isAuto()) {
210             ASSERT(computedMarginLeftValue);
211             computedMarginRightValue = containingBlockWidth - (*computedMarginLeftValue + borderLeft + paddingLeft  + computedWidthValue + paddingRight + borderRight);
212         }
213
214         // #4
215         if (width.isAuto())
216             computedWidthValue = containingBlockWidth - (computedMarginLeftValue.value_or(0) + borderLeft + paddingLeft + paddingRight + borderRight + computedMarginRightValue.value_or(0));
217
218         // #5
219         if (!computedMarginLeftValue && !computedMarginRightValue) {
220             auto horizontalSpaceForMargin = containingBlockWidth - (borderLeft + paddingLeft  + computedWidthValue + paddingRight + borderRight);
221             computedMarginLeftValue = computedMarginRightValue = horizontalSpaceForMargin / 2;
222         }
223
224         ASSERT(computedMarginLeftValue);
225         ASSERT(computedMarginRightValue);
226
227         return FormattingContext::Geometry::WidthAndMargin { computedWidthValue, { *computedMarginLeftValue, *computedMarginRightValue } };
228     };
229
230     auto computedWidthAndMarginValue = compute();
231     if (!isStretchedToViewport(layoutContext, layoutBox)) {
232         LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow non-replaced -> width(" << computedWidthAndMarginValue.width << "px) margin(" << computedWidthAndMarginValue.margin.left << "px, " << computedWidthAndMarginValue.margin.right << "px) -> layoutBox(" << &layoutBox << ")");
233         return computedWidthAndMarginValue;
234     }
235
236     auto initialContainingBlockWidth = layoutContext.displayBoxForLayoutBox(initialContainingBlock(layoutBox))->contentBox().width();
237     auto horizontalMargins = computedWidthAndMarginValue.margin.left + computedWidthAndMarginValue.margin.right;
238     // Stretch but never overstretch with the margins.
239     if (computedWidthAndMarginValue.width + horizontalMargins < initialContainingBlockWidth)
240         computedWidthAndMarginValue.width = initialContainingBlockWidth - horizontalMargins;
241
242     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow non-replaced -> streched to viewport-> width(" << computedWidthAndMarginValue.width << "px) margin(" << computedWidthAndMarginValue.margin.left << "px, " << computedWidthAndMarginValue.margin.right << "px) -> layoutBox(" << &layoutBox << ")");
243     return { computedWidthAndMarginValue.width, computedWidthAndMarginValue.margin };
244 }
245
246 FormattingContext::Geometry::WidthAndMargin BlockFormattingContext::Geometry::inFlowReplacedWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
247 {
248     ASSERT(layoutBox.isInFlow() && layoutBox.replaced());
249
250     // 10.3.4 Block-level, replaced elements in normal flow
251     //
252     // 1. The used value of 'width' is determined as for inline replaced elements.
253     // 2. Then the rules for non-replaced block-level elements are applied to determine the margins.
254
255     // #1
256     auto inlineReplacedWidthAndMargin = FormattingContext::Geometry::inlineReplacedWidthAndMargin(layoutContext, layoutBox);
257     // #2
258     auto inlineReplacedWidthAndBlockNonReplacedMargin = inFlowNonReplacedWidthAndMargin(layoutContext, layoutBox, inlineReplacedWidthAndMargin.width);
259     ASSERT(inlineReplacedWidthAndMargin.width == inlineReplacedWidthAndBlockNonReplacedMargin.width);
260     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow replaced -> width(" << inlineReplacedWidthAndBlockNonReplacedMargin.width << "px) margin(" << inlineReplacedWidthAndBlockNonReplacedMargin.margin.left << "px, " << inlineReplacedWidthAndBlockNonReplacedMargin.margin.left << "px) -> layoutBox(" << &layoutBox << ")");
261     return inlineReplacedWidthAndBlockNonReplacedMargin;
262 }
263
264 LayoutPoint BlockFormattingContext::Geometry::staticPosition(LayoutContext& layoutContext, const Box& layoutBox)
265 {
266     // https://www.w3.org/TR/CSS22/visuren.html#block-formatting
267     // In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.
268     // The vertical distance between two sibling boxes is determined by the 'margin' properties.
269     // Vertical margins between adjacent block-level boxes in a block formatting context collapse.
270     // 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).
271     auto containingBlockContentBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->contentBox();
272     // Start from the top of the container's content box.
273     auto top = containingBlockContentBox.top();
274     auto left = containingBlockContentBox.left();
275     if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
276         auto& previousInFlowDisplayBox = *layoutContext.displayBoxForLayoutBox(*previousInFlowSibling);
277         top = previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.marginBottom();
278     }
279     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position] -> static -> top(" << top << "px) left(" << left << "px) layoutBox(" << &layoutBox << ")");
280     return { top, left };
281 }
282
283 LayoutPoint BlockFormattingContext::Geometry::inFlowPositionedPosition(LayoutContext& layoutContext, const Box& layoutBox)
284 {
285     ASSERT(layoutBox.isInFlowPositioned());
286     // 9.4.3 Relative positioning
287     //
288     // The 'top' and 'bottom' properties move relatively positioned element(s) up or down without changing their size.
289     // Top' moves the boxes down, and 'bottom' moves them up. Since boxes are not split or stretched as a result of 'top' or 'bottom', the used values are always: top = -bottom.
290     //
291     // 1. If both are 'auto', their used values are both '0'.
292     // 2. If one of them is 'auto', it becomes the negative of the other.
293     // 3. If neither is 'auto', 'bottom' is ignored (i.e., the used value of 'bottom' will be minus the value of 'top').
294     auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
295     auto& style = layoutBox.style();
296
297     auto top = style.logicalTop();
298     auto bottom = style.logicalBottom();
299     LayoutUnit topDelta;
300
301     if (top.isAuto() && bottom.isAuto()) {
302         // #1
303         topDelta = 0;
304     } else if (top.isAuto()) {
305         // #2
306         topDelta = -bottom.value();
307     } else if (bottom.isAuto()) {
308         // #3 #4
309         topDelta = top.value();
310     }
311
312     // For relatively positioned elements, 'left' and 'right' move the box(es) horizontally, without changing their size.
313     // 'Left' moves the boxes to the right, and 'right' moves them to the left.
314     // Since boxes are not split or stretched as a result of 'left' or 'right', the used values are always: left = -right.
315     //
316     // 1. If both 'left' and 'right' are 'auto' (their initial values), the used values are '0' (i.e., the boxes stay in their original position).
317     // 2. If 'left' is 'auto', its used value is minus the value of 'right' (i.e., the boxes move to the left by the value of 'right').
318     // 3. If 'right' is specified as 'auto', its used value is minus the value of 'left'.
319     // 4. If neither 'left' nor 'right' is 'auto', the position is over-constrained, and one of them has to be ignored.
320     //    If the 'direction' property of the containing block is 'ltr', the value of 'left' wins and 'right' becomes -'left'.
321     //    If 'direction' of the containing block is 'rtl', 'right' wins and 'left' is ignored.
322     //
323     auto left = style.logicalLeft();
324     auto right = style.logicalRight();
325     LayoutUnit leftDelta;
326
327     if (left.isAuto() && right.isAuto()) {
328         // #1
329         leftDelta = 0;
330     } else if (left.isAuto()) {
331         // #2
332         leftDelta = -right.value();
333     } else if (right.isAuto()) {
334         // #3
335         leftDelta = left.value();
336     } else {
337         // #4
338         // FIXME: take direction into account
339         leftDelta = left.value();
340     }
341
342     auto computedLeft = displayBox.left() + leftDelta;
343     auto computedTop = displayBox.top() + topDelta;
344     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position] -> positioned inflow -> top(" << top << "px) left(" << left << "px) layoutBox(" << &layoutBox << ")");
345     return { computedLeft, computedTop };
346 }
347
348 FormattingContext::Geometry::HeightAndMargin BlockFormattingContext::Geometry::inFlowHeightAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
349 {
350     ASSERT(layoutBox.isInFlow());
351
352     if (!layoutBox.replaced())
353         return inFlowNonReplacedHeightAndMargin(layoutContext, layoutBox);
354     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block'
355     // replaced elements in normal flow and floating replaced elements
356     return FormattingContext::Geometry::inlineReplacedHeightAndMargin(layoutContext, layoutBox);
357 }
358
359 FormattingContext::Geometry::WidthAndMargin BlockFormattingContext::Geometry::inFlowWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
360 {
361     ASSERT(layoutBox.isInFlow());
362
363     if (!layoutBox.replaced())
364         return inFlowNonReplacedWidthAndMargin(layoutContext, layoutBox);
365     return inFlowReplacedWidthAndMargin(layoutContext, layoutBox);
366 }
367
368 }
369 }
370
371 #endif