02e644d2bc79e76038e91ad0b978fe86d273d435
[WebKit-https.git] / Source / WebCore / layout / FormattingContextGeometry.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 "FormattingContext.h"
28
29 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
30
31 #include "FloatingState.h"
32 #include "FormattingState.h"
33
34 namespace WebCore {
35 namespace Layout {
36
37 static LayoutUnit contentHeightForFormattingContextRoot(const LayoutContext& layoutContext, const Box& layoutBox)
38 {
39     ASSERT(layoutBox.style().logicalHeight().isAuto() && (layoutBox.establishesFormattingContext() || layoutBox.isDocumentBox()));
40
41     // 10.6.7 'Auto' heights for block formatting context roots
42
43     // If it only has inline-level children, the height is the distance between the top of the topmost line box and the bottom of the bottommost line box.
44     // If it has block-level children, the height is the distance between the top margin-edge of the topmost block-level
45     // child box and the bottom margin-edge of the bottommost block-level child box.
46
47     // In addition, if the element has any floating descendants whose bottom margin edge is below the element's bottom content edge,
48     // then the height is increased to include those edges. Only floats that participate in this block formatting context are taken
49     // into account, e.g., floats inside absolutely positioned descendants or other floats are not.
50     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
51         return 0;
52
53     auto& formattingRootContainer = downcast<Container>(layoutBox);
54     if (formattingRootContainer.establishesInlineFormattingContext())
55         return 0;
56
57     auto& firstDisplayBox = layoutContext.displayBoxForLayoutBox(*formattingRootContainer.firstInFlowChild());
58     auto& lastDisplayBox = layoutContext.displayBoxForLayoutBox(*formattingRootContainer.lastInFlowChild());
59     auto top = firstDisplayBox.rectWithMargin().top();
60     auto bottom = lastDisplayBox.rectWithMargin().bottom();
61
62     auto* formattingContextRoot = &layoutBox;
63     // TODO: The document renderer is not a formatting context root by default at all. Need to find out what it is.
64     if (!layoutBox.establishesFormattingContext()) {
65         ASSERT(layoutBox.isDocumentBox());
66         formattingContextRoot = &layoutBox.formattingContextRoot();
67     }
68
69     auto floatsBottom = layoutContext.establishedFormattingState(*formattingContextRoot).floatingState().bottom(*formattingContextRoot);
70     if (floatsBottom)
71         bottom = std::max(*floatsBottom, bottom);
72
73     auto computedHeight = bottom - top;
74     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height] -> content height for formatting context root -> height(" << computedHeight << "px) layoutBox("<< &layoutBox << ")");
75     return computedHeight;
76 }
77
78 std::optional<LayoutUnit> FormattingContext::Geometry::computedValueIfNotAuto(const Length& geometryProperty, LayoutUnit containingBlockWidth)
79 {
80     if (geometryProperty.isUndefined())
81         return std::nullopt;
82
83     if (geometryProperty.isAuto())
84         return std::nullopt;
85     return valueForLength(geometryProperty, containingBlockWidth);
86 }
87
88 std::optional<LayoutUnit> FormattingContext::Geometry::fixedValue(const Length& geometryProperty)
89 {
90     if (!geometryProperty.isFixed())
91         return std::nullopt;
92     return { geometryProperty.value() };
93 }
94
95 static LayoutUnit staticVerticalPositionForOutOfFlowPositioned(const LayoutContext& layoutContext, const Box& layoutBox)
96 {
97     ASSERT(layoutBox.isOutOfFlowPositioned());
98
99     // For the purposes of this section and the next, the term "static position" (of an element) refers, roughly, to the position an element would have
100     // had in the normal flow. More precisely, the static position for 'top' is the distance from the top edge of the containing block to the top margin
101     // edge of a hypothetical box that would have been the first box of the element if its specified 'position' value had been 'static' and its specified
102     // 'float' had been 'none' and its specified 'clear' had been 'none'. (Note that due to the rules in section 9.7 this might require also assuming a different
103     // computed value for 'display'.) The value is negative if the hypothetical box is above the containing block.
104
105     // Start with this box's border box offset from the parent's border box.
106     LayoutUnit top;
107     if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
108         // Add sibling offset
109         auto& previousInFlowDisplayBox = layoutContext.displayBoxForLayoutBox(*previousInFlowSibling);
110         top += previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.nonCollapsedMarginBottom();
111     } else {
112         ASSERT(layoutBox.parent());
113         top = layoutContext.displayBoxForLayoutBox(*layoutBox.parent()).contentBoxTop();
114     }
115
116     // Resolve top all the way up to the containing block.
117     auto* containingBlock = layoutBox.containingBlock();
118     for (auto* container = layoutBox.parent(); container != containingBlock; container = container->containingBlock()) {
119         auto& displayBox = layoutContext.displayBoxForLayoutBox(*container);
120         // Display::Box::top is the border box top position in its containing block's coordinate system.
121         top += displayBox.top();
122         ASSERT(!container->isPositioned());
123     }
124     // FIXME: floatings need to be taken into account.
125     return top;
126 }
127
128 static LayoutUnit staticHorizontalPositionForOutOfFlowPositioned(const LayoutContext& layoutContext, const Box& layoutBox)
129 {
130     ASSERT(layoutBox.isOutOfFlowPositioned());
131     // See staticVerticalPositionForOutOfFlowPositioned for the definition of the static position.
132
133     // Start with this box's border box offset from the parent's border box.
134     ASSERT(layoutBox.parent());
135     auto left = layoutContext.displayBoxForLayoutBox(*layoutBox.parent()).contentBoxLeft();
136
137     // Resolve left all the way up to the containing block.
138     auto* containingBlock = layoutBox.containingBlock();
139     for (auto* container = layoutBox.parent(); container != containingBlock; container = container->containingBlock()) {
140         auto& displayBox = layoutContext.displayBoxForLayoutBox(*container);
141         // Display::Box::left is the border box left position in its containing block's coordinate system.
142         left += displayBox.left();
143         ASSERT(!container->isPositioned());
144     }
145     // FIXME: floatings need to be taken into account.
146     return left;
147 }
148
149 LayoutUnit FormattingContext::Geometry::shrinkToFitWidth(LayoutContext& layoutContext, const FormattingContext& formattingContext, const Box& layoutBox)
150 {
151     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width] -> shrink to fit -> unsupported -> width(" << LayoutUnit { } << "px) layoutBox: " << &layoutBox << ")");
152     // Calculation of the shrink-to-fit width is similar to calculating the width of a table cell using the automatic table layout algorithm.
153     // Roughly: calculate the preferred width by formatting the content without breaking lines other than where explicit line breaks occur,
154     // and also calculate the preferred minimum width, e.g., by trying all possible line breaks. CSS 2.2 does not define the exact algorithm.
155     // Thirdly, find the available width: in this case, this is the width of the containing block minus the used values of 'margin-left', 'border-left-width',
156     // 'padding-left', 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars.
157
158     // Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
159     auto availableWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock()).width();
160     auto instrinsicWidthConstraints = formattingContext.instrinsicWidthConstraints(layoutContext, layoutBox);
161     return std::min(std::max(instrinsicWidthConstraints.minimum, availableWidth), instrinsicWidthConstraints.maximum);
162 }
163
164 VerticalGeometry FormattingContext::Geometry::outOfFlowNonReplacedVerticalGeometry(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
165 {
166     ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
167
168     // 10.6.4 Absolutely positioned, non-replaced elements
169     //
170     // For absolutely positioned elements, the used values of the vertical dimensions must satisfy this constraint:
171     // 'top' + 'margin-top' + 'border-top-width' + 'padding-top' + 'height' + 'padding-bottom' + 'border-bottom-width' + 'margin-bottom' + 'bottom'
172     // = height of containing block
173
174     // If all three of 'top', 'height', and 'bottom' are auto, set 'top' to the static position and apply rule number three below.
175
176     // If none of the three are 'auto': If both 'margin-top' and 'margin-bottom' are 'auto', solve the equation under the extra
177     // constraint that the two margins get equal values. If one of 'margin-top' or 'margin-bottom' is 'auto', solve the equation for that value.
178     // If the values are over-constrained, ignore the value for 'bottom' and solve for that value.
179
180     // Otherwise, pick the one of the following six rules that applies.
181
182     // 1. 'top' and 'height' are 'auto' and 'bottom' is not 'auto', then the height is based on the content per 10.6.7,
183     //     set 'auto' values for 'margin-top' and 'margin-bottom' to 0, and solve for 'top'
184     // 2. 'top' and 'bottom' are 'auto' and 'height' is not 'auto', then set 'top' to the static position, set 'auto' values for
185     //    'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
186     // 3. 'height' and 'bottom' are 'auto' and 'top' is not 'auto', then the height is based on the content per 10.6.7, set 'auto'
187     //     values for 'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
188     // 4. 'top' is 'auto', 'height' and 'bottom' are not 'auto', then set 'auto' values for 'margin-top' and 'margin-bottom' to 0, and solve for 'top'
189     // 5. 'height' is 'auto', 'top' and 'bottom' are not 'auto', then 'auto' values for 'margin-top' and 'margin-bottom' are set to 0 and solve for 'height'
190     // 6. 'bottom' is 'auto', 'top' and 'height' are not 'auto', then set 'auto' values for 'margin-top' and 'margin-bottom' to 0 and solve for 'bottom'
191
192     auto& style = layoutBox.style();
193     auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
194     auto& containingBlockDisplayBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock());
195     auto containingBlockHeight = containingBlockDisplayBox.height();
196     auto containingBlockWidth = containingBlockDisplayBox.width();
197
198     auto top = computedValueIfNotAuto(style.logicalTop(), containingBlockWidth);
199     auto bottom = computedValueIfNotAuto(style.logicalBottom(), containingBlockWidth);
200     auto height = computedValueIfNotAuto(precomputedHeight ? Length { precomputedHeight.value(), Fixed } : style.logicalHeight(), containingBlockHeight);
201     auto marginTop = computedValueIfNotAuto(style.marginTop(), containingBlockWidth);
202     auto marginBottom = computedValueIfNotAuto(style.marginBottom(), containingBlockWidth);
203     auto paddingTop = displayBox.paddingTop().value_or(0);
204     auto paddingBottom = displayBox.paddingBottom().value_or(0);
205     auto borderTop = displayBox.borderTop();
206     auto borderBottom = displayBox.borderBottom();
207
208     if (!top && !height && !bottom)
209         top = staticVerticalPositionForOutOfFlowPositioned(layoutContext, layoutBox);
210
211     if (top && height && bottom) {
212         if (!marginTop && !marginBottom) {
213             auto marginTopAndBottom = containingBlockHeight - (*top + borderTop + paddingTop + *height + paddingBottom + borderBottom + *bottom);
214             marginTop = marginBottom = marginTopAndBottom / 2;
215         } else if (!marginTop)
216             marginTop = containingBlockHeight - (*top + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom + *bottom);
217         else
218             marginBottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *bottom);
219         // Over-constrained?
220         auto boxHeight = *top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom + *bottom;
221         if (boxHeight > containingBlockHeight)
222             bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom); 
223     }
224
225     if (!top && !height && bottom) {
226         // #1
227         height = contentHeightForFormattingContextRoot(layoutContext, layoutBox);
228         marginTop = marginTop.value_or(0);
229         marginBottom = marginBottom.value_or(0);
230         top = containingBlockHeight - (*marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom + *bottom); 
231     }
232
233     if (!top && !bottom && height) {
234         // #2
235         top = staticVerticalPositionForOutOfFlowPositioned(layoutContext, layoutBox);
236         marginTop = marginTop.value_or(0);
237         marginBottom = marginBottom.value_or(0);
238         bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom); 
239     }
240
241     if (!height && !bottom && top) {
242         // #3
243         height = contentHeightForFormattingContextRoot(layoutContext, layoutBox);
244         marginTop = marginTop.value_or(0);
245         marginBottom = marginBottom.value_or(0);
246         bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom); 
247     }
248
249     if (!top && height && bottom) {
250         // #4
251         marginTop = marginTop.value_or(0);
252         marginBottom = marginBottom.value_or(0);
253         top = containingBlockHeight - (*marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom + *bottom); 
254     }
255
256     if (!height && top && bottom) {
257         // #5
258         marginTop = marginTop.value_or(0);
259         marginBottom = marginBottom.value_or(0);
260         height = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + paddingBottom + borderBottom + *marginBottom + *bottom); 
261     }
262
263     if (!bottom && top && height) {
264         // #6
265         marginTop = marginTop.value_or(0);
266         marginBottom = marginBottom.value_or(0);
267         bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom); 
268     }
269
270     ASSERT(top);
271     ASSERT(bottom);
272     ASSERT(height);
273     ASSERT(marginTop);
274     ASSERT(marginBottom);
275
276     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Height][Margin] -> out-of-flow non-replaced -> top(" << *top << "px) bottom("  << *bottom << "px) height(" << *height << "px) margin(" << *marginTop << "px, "  << *marginBottom << "px) layoutBox(" << &layoutBox << ")");
277     return { *top, *bottom, { *height, { *marginTop, *marginBottom }, { } } };
278 }
279
280 HorizontalGeometry FormattingContext::Geometry::outOfFlowNonReplacedHorizontalGeometry(LayoutContext& layoutContext, const FormattingContext& formattingContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedWidth)
281 {
282     ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
283     
284     // 10.3.7 Absolutely positioned, non-replaced elements
285     //
286     // 'left' + 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' + 'right'
287     // = width of containing block
288
289     // If all three of 'left', 'width', and 'right' are 'auto': First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
290     // Then, if the 'direction' property of the element establishing the static-position containing block is 'ltr' set 'left' to the static
291     // position and apply rule number three below; otherwise, set 'right' to the static position and apply rule number one below.
292     //
293     // If none of the three is 'auto': If both 'margin-left' and 'margin-right' are 'auto', solve the equation under the extra constraint that the two margins get equal values,
294     // unless this would make them negative, in which case when direction of the containing block is 'ltr' ('rtl'), set 'margin-left' ('margin-right') to zero and
295     // solve for 'margin-right' ('margin-left'). If one of 'margin-left' or 'margin-right' is 'auto', solve the equation for that value.
296     // If the values are over-constrained, ignore the value for 'left' (in case the 'direction' property of the containing block is 'rtl') or 'right'
297     // (in case 'direction' is 'ltr') and solve for that value.
298     //
299     // Otherwise, set 'auto' values for 'margin-left' and 'margin-right' to 0, and pick the one of the following six rules that applies.
300     //
301     // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', then the width is shrink-to-fit. Then solve for 'left'
302     // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', then if the 'direction' property of the element establishing the static-position 
303     //    containing block is 'ltr' set 'left' to the static position, otherwise set 'right' to the static position.
304     //    Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr').
305     // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', then the width is shrink-to-fit . Then solve for 'right'
306     // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left'
307     // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width'
308     // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right'
309
310     auto& style = layoutBox.style();
311     auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
312     auto& containingBlock = *layoutBox.containingBlock();
313     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
314     auto isLeftToRightDirection = containingBlock.style().isLeftToRightDirection();
315     
316     auto left = computedValueIfNotAuto(style.logicalLeft(), containingBlockWidth);
317     auto right = computedValueIfNotAuto(style.logicalRight(), containingBlockWidth);
318     auto width = computedValueIfNotAuto(precomputedWidth ? Length { precomputedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
319     auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth);
320     auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth);
321     auto nonComputedMarginLeft = marginLeft.value_or(0);
322     auto nonComputedMarginRight = marginRight.value_or(0);
323     auto paddingLeft = displayBox.paddingLeft().value_or(0);
324     auto paddingRight = displayBox.paddingRight().value_or(0);
325     auto borderLeft = displayBox.borderLeft();
326     auto borderRight = displayBox.borderRight();
327
328     if (!left && !width && !right) {
329         // If all three of 'left', 'width', and 'right' are 'auto': First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
330         // Then, if the 'direction' property of the element establishing the static-position containing block is 'ltr' set 'left' to the static
331         // position and apply rule number three below; otherwise, set 'right' to the static position and apply rule number one below.
332         marginLeft = marginLeft.value_or(0);
333         marginRight = marginRight.value_or(0);
334
335         auto staticHorizontalPosition = staticHorizontalPositionForOutOfFlowPositioned(layoutContext, layoutBox);
336         if (isLeftToRightDirection)
337             left = staticHorizontalPosition;
338         else
339             right = staticHorizontalPosition;
340     } else if (left && width && right) {
341         // If none of the three is 'auto': If both 'margin-left' and 'margin-right' are 'auto', solve the equation under the extra constraint that the two margins get equal values,
342         // unless this would make them negative, in which case when direction of the containing block is 'ltr' ('rtl'), set 'margin-left' ('margin-right') to zero and
343         // solve for 'margin-right' ('margin-left'). If one of 'margin-left' or 'margin-right' is 'auto', solve the equation for that value.
344         // If the values are over-constrained, ignore the value for 'left' (in case the 'direction' property of the containing block is 'rtl') or 'right'
345         // (in case 'direction' is 'ltr') and solve for that value.
346         if (!marginLeft && !marginRight) {
347             auto marginLeftAndRight = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
348             if (marginLeftAndRight >= 0)
349                 marginLeft = marginRight = marginLeftAndRight / 2;  
350             else {
351                 if (isLeftToRightDirection) {
352                     marginLeft = LayoutUnit { 0 };
353                     marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
354                 } else {
355                     marginRight = LayoutUnit { 0 };
356                     marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
357                 }
358             }
359         } else if (!marginLeft) {
360             marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
361             // Overconstrained? Ignore right (left).
362             if (*marginLeft < 0) {
363                 if (isLeftToRightDirection)
364                     marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
365                 else
366                     marginLeft = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
367             }
368         } else if (!marginRight) {
369             marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
370             // Overconstrained? Ignore right (left).
371             if (*marginRight < 0) {
372                 if (isLeftToRightDirection)
373                     marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight);
374                 else
375                     marginRight = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
376             }
377         }
378     } else {
379         // Otherwise, set 'auto' values for 'margin-left' and 'margin-right' to 0, and pick the one of the following six rules that applies.
380         marginLeft = marginLeft.value_or(0);
381         marginRight = marginRight.value_or(0);
382     }
383
384     ASSERT(marginLeft);
385     ASSERT(marginRight);
386
387     if (!left && !width && right) {
388         // #1
389         width = shrinkToFitWidth(layoutContext, formattingContext, layoutBox);
390         left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight  + borderRight + *marginRight + *right);
391     } else if (!left && !right && width) {
392         // #2
393         auto staticHorizontalPosition = staticHorizontalPositionForOutOfFlowPositioned(layoutContext, layoutBox);
394         if (isLeftToRightDirection) {
395             left = staticHorizontalPosition;
396             right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
397         } else {
398             right = staticHorizontalPosition;
399             left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
400         }
401     } else if (!width && !right && left) {
402         // #3
403         width = shrinkToFitWidth(layoutContext, formattingContext, layoutBox);
404         right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
405     } else if (!left && width && right) {
406         // #4
407         left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
408     } else if (!width && left && right) {
409         // #5
410         width = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + paddingRight  + borderRight + *marginRight + *right);
411     } else if (!right && left && width) {
412         // #6
413         right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
414     }
415
416     ASSERT(left);
417     ASSERT(right);
418     ASSERT(width);
419     ASSERT(marginLeft);
420     ASSERT(marginRight);
421
422     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Width][Margin] -> out-of-flow non-replaced -> left(" << *left << "px) right("  << *right << "px) width(" << *width << "px) margin(" << *marginLeft << "px, "  << *marginRight << "px) layoutBox(" << &layoutBox << ")");
423     return { *left, *right, { *width, { *marginLeft, *marginRight }, { nonComputedMarginLeft, nonComputedMarginRight } } };
424 }
425
426 VerticalGeometry FormattingContext::Geometry::outOfFlowReplacedVerticalGeometry(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
427 {
428     ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
429
430     // 10.6.5 Absolutely positioned, replaced elements
431     //
432     // The used value of 'height' is determined as for inline replaced elements.
433     // If 'margin-top' or 'margin-bottom' is specified as 'auto' its used value is determined by the rules below.
434     // 1. If both 'top' and 'bottom' have the value 'auto', replace 'top' with the element's static position.
435     // 2. If 'bottom' is 'auto', replace any 'auto' on 'margin-top' or 'margin-bottom' with '0'.
436     // 3. If at this point both 'margin-top' and 'margin-bottom' are still 'auto', solve the equation under the extra constraint that the two margins must get equal values.
437     // 4. If at this point there is only one 'auto' left, solve the equation for that value.
438     // 5. If at this point the values are over-constrained, ignore the value for 'bottom' and solve for that value.
439
440     auto& style = layoutBox.style();
441     auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
442     auto& containingBlockDisplayBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock());
443     auto containingBlockHeight = containingBlockDisplayBox.height();
444     auto containingBlockWidth = containingBlockDisplayBox.width();
445
446     auto top = computedValueIfNotAuto(style.logicalTop(), containingBlockWidth);
447     auto bottom = computedValueIfNotAuto(style.logicalBottom(), containingBlockWidth);
448     auto height = inlineReplacedHeightAndMargin(layoutContext, layoutBox, precomputedHeight).height;
449     auto marginTop = computedValueIfNotAuto(style.marginTop(), containingBlockWidth);
450     auto marginBottom = computedValueIfNotAuto(style.marginBottom(), containingBlockWidth);
451     auto paddingTop = displayBox.paddingTop().value_or(0);
452     auto paddingBottom = displayBox.paddingBottom().value_or(0);
453     auto borderTop = displayBox.borderTop();
454     auto borderBottom = displayBox.borderBottom();
455
456     if (!top && !bottom) {
457         // #1
458         top = staticVerticalPositionForOutOfFlowPositioned(layoutContext, layoutBox);
459     }
460
461     if (!bottom) {
462         // #2
463         marginTop = marginTop.value_or(0);
464         marginBottom = marginBottom.value_or(0);
465     }
466
467     if (!marginTop && !marginBottom) {
468         // #3
469         auto marginTopAndBottom = containingBlockHeight - (*top + borderTop + paddingTop + height + paddingBottom + borderBottom + *bottom);
470         marginTop = marginBottom = marginTopAndBottom / 2;
471     }
472
473     // #4
474     if (!top)
475         top = containingBlockHeight - (*marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom + *bottom);
476
477     if (!bottom)
478         bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom);
479
480     if (!marginTop)
481         marginTop = containingBlockHeight - (*top + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom + *bottom);
482
483     if (!marginBottom)
484         marginBottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *bottom);
485
486     // #5
487     auto boxHeight = *top + *marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom + *bottom;
488     if (boxHeight > containingBlockHeight)
489         bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom); 
490
491     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Height][Margin] -> out-of-flow replaced -> top(" << *top << "px) bottom("  << *bottom << "px) height(" << height << "px) margin(" << *marginTop << "px, "  << *marginBottom << "px) layoutBox(" << &layoutBox << ")");
492     return { *top, *bottom, { height, { *marginTop, *marginBottom }, { } } };
493 }
494
495 HorizontalGeometry FormattingContext::Geometry::outOfFlowReplacedHorizontalGeometry(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedWidth)
496 {
497     ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
498
499     // 10.3.8 Absolutely positioned, replaced elements
500     // In this case, section 10.3.7 applies up through and including the constraint equation, but the rest of section 10.3.7 is replaced by the following rules:
501     //
502     // The used value of 'width' is determined as for inline replaced elements. If 'margin-left' or 'margin-right' is specified as 'auto' its used value is determined by the rules below.
503     // 1. If both 'left' and 'right' have the value 'auto', then if the 'direction' property of the element establishing the static-position containing block is 'ltr',
504     //   set 'left' to the static position; else if 'direction' is 'rtl', set 'right' to the static position.
505     // 2. If 'left' or 'right' are 'auto', replace any 'auto' on 'margin-left' or 'margin-right' with '0'.
506     // 3. If at this point both 'margin-left' and 'margin-right' are still 'auto', solve the equation under the extra constraint that the two margins must get equal values,
507     //   unless this would make them negative, in which case when the direction of the containing block is 'ltr' ('rtl'), set 'margin-left' ('margin-right') to zero and
508     //   solve for 'margin-right' ('margin-left').
509     // 4. If at this point there is an 'auto' left, solve the equation for that value.
510     // 5. If at this point the values are over-constrained, ignore the value for either 'left' (in case the 'direction' property of the containing block is 'rtl') or
511     //   'right' (in case 'direction' is 'ltr') and solve for that value.
512
513     auto& style = layoutBox.style();
514     auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
515     auto& containingBlock = *layoutBox.containingBlock();
516     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
517     auto isLeftToRightDirection = containingBlock.style().isLeftToRightDirection();
518
519     auto left = computedValueIfNotAuto(style.logicalLeft(), containingBlockWidth);
520     auto right = computedValueIfNotAuto(style.logicalRight(), containingBlockWidth);
521     auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth);
522     auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth);
523     auto nonComputedMarginLeft = marginLeft.value_or(0);
524     auto nonComputedMarginRight = marginRight.value_or(0);
525     auto width = inlineReplacedWidthAndMargin(layoutContext, layoutBox, precomputedWidth).width;
526     auto paddingLeft = displayBox.paddingLeft().value_or(0);
527     auto paddingRight = displayBox.paddingRight().value_or(0);
528     auto borderLeft = displayBox.borderLeft();
529     auto borderRight = displayBox.borderRight();
530
531     if (!left && !right) {
532         // #1
533         auto staticHorizontalPosition = staticHorizontalPositionForOutOfFlowPositioned(layoutContext, layoutBox);
534         if (isLeftToRightDirection)
535             left = staticHorizontalPosition;
536         else
537             right = staticHorizontalPosition;
538     }
539
540     if (!left || !right) {
541         // #2
542         marginLeft = marginLeft.value_or(0); 
543         marginRight = marginRight.value_or(0); 
544     }
545
546     if (!marginLeft && !marginRight) {
547         // #3
548         auto marginLeftAndRight = containingBlockWidth - (*left + borderLeft + paddingLeft + width + paddingRight + borderRight + *right);
549         if (marginLeftAndRight >= 0)
550             marginLeft = marginRight = marginLeftAndRight / 2;
551         else {
552             if (isLeftToRightDirection) {
553                 marginLeft = LayoutUnit { 0 };
554                 marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *right);
555             } else {
556                 marginRight = LayoutUnit { 0 };
557                 marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
558             }
559         }
560     }
561
562     // #4
563     if (!left)
564         left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
565
566     if (!right)
567         right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight);
568
569     if (!marginLeft)
570         marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
571
572     if (!marginRight)
573         marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *right);
574
575     auto boxWidth = (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
576     if (boxWidth > containingBlockWidth) {
577         // #5 Over-constrained?
578         if (isLeftToRightDirection)
579             right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight);
580         else
581             left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
582     }
583
584     ASSERT(left);
585     ASSERT(right);
586     ASSERT(marginLeft);
587     ASSERT(marginRight);
588
589     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Width][Margin] -> out-of-flow replaced -> left(" << *left << "px) right("  << *right << "px) width(" << width << "px) margin(" << *marginLeft << "px, "  << *marginRight << "px) layoutBox(" << &layoutBox << ")");
590     return { *left, *right, { width, { *marginLeft, *marginRight }, { nonComputedMarginLeft, nonComputedMarginRight } } };
591 }
592
593 HeightAndMargin FormattingContext::Geometry::complicatedCases(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
594 {
595     ASSERT(!layoutBox.replaced());
596     // TODO: Use complicated-case for document renderer for now (see BlockFormattingContext::Geometry::inFlowHeightAndMargin).
597     ASSERT((layoutBox.isBlockLevelBox() && layoutBox.isInFlow() && !layoutBox.isOverflowVisible()) || layoutBox.isInlineBlockBox() || layoutBox.isFloatingPositioned() || layoutBox.isDocumentBox());
598
599     // 10.6.6 Complicated cases
600     //
601     // 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).
602     // 'Inline-block', non-replaced elements.
603     // Floating, non-replaced elements.
604     //
605     // 1. If 'margin-top', or 'margin-bottom' are 'auto', their used value is 0.
606     // 2. If 'height' is 'auto', the height depends on the element's descendants per 10.6.7.
607
608     auto& style = layoutBox.style();
609     auto& containingBlock = *layoutBox.containingBlock();
610     auto& containingBlockDisplayBox = layoutContext.displayBoxForLayoutBox(containingBlock);
611     auto containingBlockWidth = containingBlockDisplayBox.contentBoxWidth();
612
613     auto height = fixedValue(precomputedHeight ? Length { precomputedHeight.value(), Fixed } : style.logicalHeight());
614     auto marginTop = computedValueIfNotAuto(style.marginTop(), containingBlockWidth);
615     auto marginBottom = computedValueIfNotAuto(style.marginBottom(), containingBlockWidth);
616
617     // #1
618     marginTop = marginTop.value_or(0);
619     marginBottom = marginBottom.value_or(0);
620     // #2
621     if (!height) {
622         if (style.logicalHeight().isAuto())
623             height = contentHeightForFormattingContextRoot(layoutContext, layoutBox);
624         else
625             ASSERT_NOT_IMPLEMENTED_YET();
626     }
627
628     ASSERT(height);
629     ASSERT(marginTop);
630     ASSERT(marginBottom);
631
632     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> floating non-replaced -> height(" << *height << "px) margin(" << *marginTop << "px, " << *marginBottom << "px) -> layoutBox(" << &layoutBox << ")");
633     return HeightAndMargin { *height, { *marginTop, *marginBottom }, { } };
634 }
635
636 WidthAndMargin FormattingContext::Geometry::floatingNonReplacedWidthAndMargin(LayoutContext& layoutContext, const FormattingContext& formattingContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedWidth)
637 {
638     ASSERT(layoutBox.isFloatingPositioned() && !layoutBox.replaced());
639
640     // 10.3.5 Floating, non-replaced elements
641     //
642     // 1. If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'.
643     // 2. If 'width' is computed as 'auto', the used value is the "shrink-to-fit" width.
644
645     auto& containingBlock = *layoutBox.containingBlock();
646     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
647
648     // #1
649     auto margin = computedNonCollapsedHorizontalMarginValue(layoutContext, layoutBox);
650     // #2
651     auto width = computedValueIfNotAuto(precomputedWidth ? Length { precomputedWidth.value(), Fixed } : layoutBox.style().logicalWidth(), containingBlockWidth);
652     if (!width)
653         width = shrinkToFitWidth(layoutContext, formattingContext, layoutBox);
654
655     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> floating non-replaced -> width(" << *width << "px) margin(" << margin.left << "px, " << margin.right << "px) -> layoutBox(" << &layoutBox << ")");
656     return WidthAndMargin { *width, margin, margin };
657 }
658
659 HeightAndMargin FormattingContext::Geometry::floatingReplacedHeightAndMargin(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
660 {
661     ASSERT(layoutBox.isFloatingPositioned() && layoutBox.replaced());
662
663     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block'
664     // replaced elements in normal flow and floating replaced elements
665     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> floating replaced -> redirected to inline replaced");
666     return inlineReplacedHeightAndMargin(layoutContext, layoutBox, precomputedHeight);
667 }
668
669 WidthAndMargin FormattingContext::Geometry::floatingReplacedWidthAndMargin(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedWidth)
670 {
671     ASSERT(layoutBox.isFloatingPositioned() && layoutBox.replaced());
672
673     // 10.3.6 Floating, replaced elements
674     //
675     // 1. If 'margin-left' or 'margin-right' are computed as 'auto', their used value is '0'.
676     // 2. The used value of 'width' is determined as for inline replaced elements.
677     auto margin = computedNonCollapsedHorizontalMarginValue(layoutContext, layoutBox);
678
679     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> floating replaced -> redirected to inline replaced");
680     return inlineReplacedWidthAndMargin(layoutContext, layoutBox, precomputedWidth, margin.left, margin.right);
681 }
682
683 VerticalGeometry FormattingContext::Geometry::outOfFlowVerticalGeometry(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
684 {
685     ASSERT(layoutBox.isOutOfFlowPositioned());
686
687     if (!layoutBox.replaced())
688         return outOfFlowNonReplacedVerticalGeometry(layoutContext, layoutBox, precomputedHeight);
689     return outOfFlowReplacedVerticalGeometry(layoutContext, layoutBox, precomputedHeight);
690 }
691
692 HorizontalGeometry FormattingContext::Geometry::outOfFlowHorizontalGeometry(LayoutContext& layoutContext, const FormattingContext& formattingContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedWidth)
693 {
694     ASSERT(layoutBox.isOutOfFlowPositioned());
695
696     if (!layoutBox.replaced())
697         return outOfFlowNonReplacedHorizontalGeometry(layoutContext, formattingContext, layoutBox, precomputedWidth);
698     return outOfFlowReplacedHorizontalGeometry(layoutContext, layoutBox, precomputedWidth);
699 }
700
701 HeightAndMargin FormattingContext::Geometry::floatingHeightAndMargin(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
702 {
703     ASSERT(layoutBox.isFloatingPositioned());
704
705     if (!layoutBox.replaced())
706         return complicatedCases(layoutContext, layoutBox, precomputedHeight);
707     return floatingReplacedHeightAndMargin(layoutContext, layoutBox, precomputedHeight);
708 }
709
710 WidthAndMargin FormattingContext::Geometry::floatingWidthAndMargin(LayoutContext& layoutContext, const FormattingContext& formattingContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedWidth)
711 {
712     ASSERT(layoutBox.isFloatingPositioned());
713
714     if (!layoutBox.replaced())
715         return floatingNonReplacedWidthAndMargin(layoutContext, formattingContext, layoutBox, precomputedWidth);
716     return floatingReplacedWidthAndMargin(layoutContext, layoutBox, precomputedWidth);
717 }
718
719 HeightAndMargin FormattingContext::Geometry::inlineReplacedHeightAndMargin(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
720 {
721     ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
722
723     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block' replaced elements in normal flow and floating replaced elements
724     //
725     // 1. If 'margin-top', or 'margin-bottom' are 'auto', their used value is 0.
726     // 2. If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic height, then that intrinsic height is the used value of 'height'.
727     // 3. Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic ratio then the used value of 'height' is:
728     //    (used width) / (intrinsic ratio)
729     // 4. Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic height, then that intrinsic height is the used value of 'height'.
730     // 5. Otherwise, if 'height' has a computed value of 'auto', but none of the conditions above are met, then the used value of 'height' must be set to
731     //    the height of the largest rectangle that has a 2:1 ratio, has a height not greater than 150px, and has a width not greater than the device width.
732
733     // #1
734     auto margin = computedNonCollapsedVerticalMarginValue(layoutContext, layoutBox);
735
736     auto& style = layoutBox.style();
737     auto replaced = layoutBox.replaced();
738     auto& containingBlockDisplayBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock());
739     auto containingBlockWidth = containingBlockDisplayBox.width();
740
741     auto height = fixedValue(precomputedHeight ? Length { precomputedHeight.value(), Fixed } : style.logicalHeight());
742     auto heightIsAuto = style.logicalHeight().isAuto();
743     auto width = computedValueIfNotAuto(style.logicalWidth(), containingBlockWidth);
744
745     if (!height && !heightIsAuto)
746         ASSERT_NOT_IMPLEMENTED_YET();
747
748     if (heightIsAuto && !width && replaced->hasIntrinsicHeight()) {
749         // #2
750         height = replaced->intrinsicHeight();
751     } else if (heightIsAuto && replaced->hasIntrinsicRatio()) {
752         // #3
753         height = *width / replaced->intrinsicRatio();
754     } else if (heightIsAuto && replaced->hasIntrinsicHeight()) {
755         // #4
756         height = replaced->intrinsicHeight();
757     } else if (heightIsAuto) {
758         // #5
759         height = { 150 };
760     }
761
762     ASSERT(height);
763
764     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow replaced -> height(" << *height << "px) margin(" << margin.top << "px, " << margin.bottom << "px) -> layoutBox(" << &layoutBox << ")");
765     return { *height, margin, { } };
766 }
767
768 WidthAndMargin FormattingContext::Geometry::inlineReplacedWidthAndMargin(const LayoutContext& layoutContext, const Box& layoutBox,
769     std::optional<LayoutUnit> precomputedWidth, std::optional<LayoutUnit> precomputedMarginLeft, std::optional<LayoutUnit> precomputedMarginRight)
770 {
771     ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
772
773     // 10.3.2 Inline, replaced elements
774     //
775     // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
776     //
777     // 1. If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width, then that intrinsic width is the used value of 'width'.
778     //
779     // 2. If 'height' and 'width' both have computed values of 'auto' and the element has no intrinsic width, but does have an intrinsic height and intrinsic ratio;
780     //    or if 'width' has a computed value of 'auto', 'height' has some other computed value, and the element does have an intrinsic ratio;
781     //    then the used value of 'width' is: (used height) * (intrinsic ratio)
782     //
783     // 3. If 'height' and 'width' both have computed values of 'auto' and the element has an intrinsic ratio but no intrinsic height or width,
784     //    then the used value of 'width' is undefined in CSS 2.2. However, it is suggested that, if the containing block's width does not itself depend on the replaced
785     //    element's width, then the used value of 'width' is calculated from the constraint equation used for block-level, non-replaced elements in normal flow.
786     //
787     // 4. Otherwise, if 'width' has a computed value of 'auto', and the element has an intrinsic width, then that intrinsic width is the used value of 'width'.
788     //
789     // 5. Otherwise, if 'width' has a computed value of 'auto', but none of the conditions above are met, then the used value of 'width' becomes 300px.
790     //    If 300px is too wide to fit the device, UAs should use the width of the largest rectangle that has a 2:1 ratio and fits the device instead.
791
792     auto& style = layoutBox.style();
793     auto& containingBlockDisplayBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock());
794     auto containingBlockWidth = containingBlockDisplayBox.width();
795
796     auto computeMarginRight = [&]() {
797         if (precomputedMarginRight)
798             return precomputedMarginRight.value();
799         auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth);
800         return marginRight.value_or(LayoutUnit { 0 });
801     };
802
803     auto computeMarginLeft = [&]() {
804         if (precomputedMarginLeft)
805             return precomputedMarginLeft.value();
806         auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth);
807         return marginLeft.value_or(LayoutUnit { 0 });
808     };
809
810     auto replaced = layoutBox.replaced();
811     ASSERT(replaced);
812
813     auto marginLeft = computeMarginLeft();
814     auto marginRight = computeMarginRight();
815     auto nonComputedMarginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth).value_or(0);
816     auto nonComputedMarginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth).value_or(0);
817     auto width = computedValueIfNotAuto(precomputedWidth ? Length { precomputedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
818
819     auto heightIsAuto = style.logicalHeight().isAuto();
820     auto height = fixedValue(style.logicalHeight());
821     if (!height && !heightIsAuto)
822         ASSERT_NOT_IMPLEMENTED_YET();
823
824     if (!width && heightIsAuto && replaced->hasIntrinsicWidth()) {
825         // #1
826         width = replaced->intrinsicWidth();
827     } else if ((!width && heightIsAuto && !replaced->hasIntrinsicWidth() && replaced->hasIntrinsicHeight() && replaced->hasIntrinsicRatio())
828         || (!width && height && replaced->hasIntrinsicRatio())) {
829         // #2
830         width = height.value_or(replaced->hasIntrinsicHeight()) * replaced->intrinsicRatio();
831     } else if (!width && heightIsAuto && replaced->hasIntrinsicRatio() && !replaced->hasIntrinsicWidth() && !replaced->hasIntrinsicHeight()) {
832         // #3
833         // FIXME: undefined but surely doable.
834         ASSERT_NOT_IMPLEMENTED_YET();
835     } else if (!width && replaced->hasIntrinsicWidth()) {
836         // #4
837         width = replaced->intrinsicWidth();
838     } else {
839         // #5
840         width = { 300 };
841     }
842
843     ASSERT(width);
844
845     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow replaced -> width(" << *width << "px) margin(" << marginLeft << "px, " << marginRight << "px) -> layoutBox(" << &layoutBox << ")");
846     return { *width, { marginLeft, marginRight }, { nonComputedMarginLeft, nonComputedMarginRight } };
847 }
848
849 Edges FormattingContext::Geometry::computedBorder(const LayoutContext&, const Box& layoutBox)
850 {
851     auto& style = layoutBox.style();
852     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Border] -> layoutBox: " << &layoutBox);
853     return {
854         { style.borderLeft().boxModelWidth(), style.borderRight().boxModelWidth() },
855         { style.borderTop().boxModelWidth(), style.borderBottom().boxModelWidth() }
856     };
857 }
858
859 std::optional<Edges> FormattingContext::Geometry::computedPadding(const LayoutContext& layoutContext, const Box& layoutBox)
860 {
861     if (!layoutBox.isPaddingApplicable())
862         return std::nullopt;
863
864     auto& style = layoutBox.style();
865     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
866     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Padding] -> layoutBox: " << &layoutBox);
867     return Edges {
868         { valueForLength(style.paddingLeft(), containingBlockWidth), valueForLength(style.paddingRight(), containingBlockWidth) },
869         { valueForLength(style.paddingTop(), containingBlockWidth), valueForLength(style.paddingBottom(), containingBlockWidth) }
870     };
871 }
872
873 HorizontalEdges FormattingContext::Geometry::computedNonCollapsedHorizontalMarginValue(const LayoutContext& layoutContext, const Box& layoutBox)
874 {
875     auto& style = layoutBox.style();
876     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
877
878     auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth).value_or(LayoutUnit { 0 });
879     auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth).value_or(LayoutUnit { 0 });
880
881     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Margin] -> non collapsed horizontal -> margin(" << marginLeft << "px, " << marginRight << "px) -> layoutBox: " << &layoutBox);
882     return { marginLeft, marginRight };
883 }
884
885 VerticalEdges FormattingContext::Geometry::computedNonCollapsedVerticalMarginValue(const LayoutContext& layoutContext, const Box& layoutBox)
886 {
887     auto& style = layoutBox.style();
888     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
889
890     auto marginTop = computedValueIfNotAuto(style.marginTop(), containingBlockWidth).value_or(LayoutUnit { 0 });
891     auto marginBottom = computedValueIfNotAuto(style.marginBottom(), containingBlockWidth).value_or(LayoutUnit { 0 });
892
893     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Margin] -> non collapsed vertical -> margin(" << marginTop << "px, " << marginBottom << "px) -> layoutBox: " << &layoutBox);
894     return { marginTop, marginBottom };
895 }
896
897 }
898 }
899 #endif