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