[LFC] Implement height computation for replaced elements.
[WebKit-https.git] / Source / WebCore / layout / FormattingContext.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 "DisplayBox.h"
32 #include "LayoutBox.h"
33 #include "LayoutContainer.h"
34 #include "LayoutContext.h"
35 #include <wtf/IsoMallocInlines.h>
36
37 namespace WebCore {
38 namespace Layout {
39
40 WTF_MAKE_ISO_ALLOCATED_IMPL(FormattingContext);
41
42 FormattingContext::FormattingContext(const Box& formattingContextRoot)
43     : m_root(makeWeakPtr(const_cast<Box&>(formattingContextRoot)))
44 {
45 }
46
47 FormattingContext::~FormattingContext()
48 {
49 }
50
51 void FormattingContext::computeStaticPosition(LayoutContext&, const Box&, Display::Box&) const
52 {
53 }
54
55 void FormattingContext::computeInFlowPositionedPosition(const Box&, Display::Box&) const
56 {
57 }
58
59 void FormattingContext::computeOutOfFlowPosition(const Box&, Display::Box&) const
60 {
61 }
62
63 void FormattingContext::computeWidth(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
64 {
65     if (layoutBox.isOutOfFlowPositioned())
66         return computeOutOfFlowWidth(layoutContext, layoutBox, displayBox);
67     if (layoutBox.isFloatingPositioned())
68         return computeFloatingWidth(layoutContext, layoutBox, displayBox);
69     return computeInFlowWidth(layoutContext, layoutBox, displayBox);
70 }
71
72 void FormattingContext::computeHeight(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
73 {
74     if (layoutBox.isOutOfFlowPositioned())
75         return computeOutOfFlowHeight(layoutContext, layoutBox, displayBox);
76     if (layoutBox.isFloatingPositioned())
77         return computeFloatingHeight(layoutContext, layoutBox, displayBox);
78     return computeInFlowHeight(layoutContext, layoutBox, displayBox);
79 }
80
81 void FormattingContext::computeOutOfFlowWidth(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
82 {
83     if (!layoutBox.replaced()) {
84         computeOutOfFlowNonReplacedWidth(layoutContext, layoutBox, displayBox);
85         return;
86     }
87     computeOutOfFlowReplacedWidth(layoutContext, layoutBox, displayBox);
88 }
89
90 void FormattingContext::computeFloatingWidth(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
91 {
92     if (!layoutBox.replaced()) {
93         computeFloatingNonReplacedWidth(layoutContext, layoutBox, displayBox);
94         return;
95     }
96     computeReplacedWidth(layoutContext, layoutBox, displayBox);
97 }
98
99 void FormattingContext::computeOutOfFlowHeight(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
100 {
101     if (!layoutBox.replaced()) {
102         computeOutOfFlowNonReplacedHeight(layoutContext, layoutBox, displayBox);
103         return;
104     }
105     computeOutOfFlowReplacedHeight(layoutContext, layoutBox, displayBox);
106 }
107
108 void FormattingContext::computeFloatingHeight(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
109 {
110     if (!layoutBox.replaced()) {
111         ASSERT_NOT_IMPLEMENTED_YET();
112         return;
113     }
114     computeReplacedHeight(layoutContext, layoutBox, displayBox);
115 }
116
117 LayoutUnit FormattingContext::marginTop(const Box&) const
118 {
119     return 0;
120 }
121
122 LayoutUnit FormattingContext::marginLeft(const Box&) const
123 {
124     return 0;
125 }
126
127 LayoutUnit FormattingContext::marginBottom(const Box&) const
128 {
129     return 0;
130 }
131
132 LayoutUnit FormattingContext::marginRight(const Box&) const
133 {
134     return 0;
135 }
136
137 void FormattingContext::placeInFlowPositionedChildren(const Container&) const
138 {
139 }
140
141 void FormattingContext::layoutOutOfFlowDescendants(LayoutContext& layoutContext) const
142 {
143     if (!is<Container>(m_root.get()))
144         return;
145     for (auto& outOfFlowBox : downcast<Container>(*m_root).outOfFlowDescendants()) {
146         auto& layoutBox = *outOfFlowBox;
147         auto& displayBox = layoutContext.createDisplayBox(layoutBox);
148
149         computeOutOfFlowPosition(layoutBox, displayBox);
150         computeOutOfFlowWidth(layoutContext, layoutBox, displayBox);
151
152         ASSERT(layoutBox.establishesFormattingContext());
153         auto formattingContext = layoutContext.formattingContext(layoutBox);
154         formattingContext->layout(layoutContext, layoutContext.establishedFormattingState(layoutBox, *formattingContext));
155
156         computeOutOfFlowHeight(layoutContext, layoutBox, displayBox);
157     }
158 }
159
160 void FormattingContext::computeOutOfFlowNonReplacedHeight(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
161 {
162     ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
163
164     // 10.6.4 Absolutely positioned, non-replaced elements
165     //
166     // For absolutely positioned elements, the used values of the vertical dimensions must satisfy this constraint:
167     // 'top' + 'margin-top' + 'border-top-width' + 'padding-top' + 'height' + 'padding-bottom' + 'border-bottom-width' + 'margin-bottom' + 'bottom'
168     // = height of containing block
169
170     // If all three of 'top', 'height', and 'bottom' are auto, set 'top' to the static position and apply rule number three below.
171
172     // If none of the three are 'auto': If both 'margin-top' and 'margin-bottom' are 'auto', solve the equation under the extra
173     // constraint that the two margins get equal values. If one of 'margin-top' or 'margin-bottom' is 'auto', solve the equation for that value.
174     // If the values are over-constrained, ignore the value for 'bottom' and solve for that value.
175
176     // Otherwise, pick the one of the following six rules that applies.
177
178     // 1. 'top' and 'height' are 'auto' and 'bottom' is not 'auto', then the height is based on the content per 10.6.7,
179     //     set 'auto' values for 'margin-top' and 'margin-bottom' to 0, and solve for 'top'
180     // 2. 'top' and 'bottom' are 'auto' and 'height' is not 'auto', then set 'top' to the static position, set 'auto' values for
181     //    'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
182     // 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'
183     //     values for 'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
184     // 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'
185     // 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'
186     // 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'
187     auto& style = layoutBox.style();
188     auto top = style.logicalTop();
189     auto bottom = style.logicalBottom();
190     auto height = style.logicalHeight(); 
191
192     auto containingBlockHeight = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->height();
193     LayoutUnit computedHeightValue;
194
195     if ((top.isAuto() && height.isAuto() && bottom.isAuto())
196         || (top.isAuto() && height.isAuto() && !bottom.isAuto())
197         || (!top.isAuto() && height.isAuto() && bottom.isAuto())) {
198         // All auto (#3), #1 and #3
199         computedHeightValue = contentHeightForFormattingContextRoot(layoutContext, layoutBox);
200     } else if (!top.isAuto() && height.isAuto() && !bottom.isAuto()) {
201         // #5
202         auto marginTop = displayBox.marginTop();
203         auto marginBottom = displayBox.marginBottom();
204     
205         auto paddingTop = displayBox.paddingTop();
206         auto paddingBottom = displayBox.paddingBottom();
207
208         auto borderTop = displayBox.borderTop();
209         auto borderBottom = displayBox.borderBottom();
210
211         computedHeightValue = containingBlockHeight - (top.value() + marginTop + borderTop + paddingTop + paddingBottom + borderBottom + marginBottom + bottom.value());
212     } else if (!height.isAuto())
213         computedHeightValue = valueForLength(height, containingBlockHeight);
214     else
215         ASSERT_NOT_REACHED();
216
217     displayBox.setHeight(computedHeightValue);
218 }
219
220 void FormattingContext::computeReplacedHeight(LayoutContext&, const Box& layoutBox, Display::Box& displayBox) const
221 {
222     ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
223     // 10.6.5 Absolutely positioned, replaced elements. The used value of 'height' is determined as for inline replaced elements.
224
225     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block' replaced elements in normal flow and floating replaced elements
226     //
227     // 1. 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'.
228     //
229     // 2. Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic ratio then the used value of 'height' is:
230     //    (used width) / (intrinsic ratio)
231     //
232     // 3. 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'.
233     //
234     // 4. 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
235     //    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.
236     auto& style = layoutBox.style();
237     auto width = style.logicalWidth();
238     auto height = style.logicalHeight();
239
240     LayoutUnit computedHeightValue;
241     auto replaced = layoutBox.replaced();
242     ASSERT(replaced);
243
244     if (height.isAuto()) {
245         if (width.isAuto() && replaced->hasIntrinsicHeight()) {
246             // #1
247             computedHeightValue = replaced->intrinsicHeight();
248         } else if (replaced->hasIntrinsicRatio()) {
249             // #2
250             computedHeightValue = width.value() / replaced->intrinsicRatio();
251         } else if (replaced->hasIntrinsicHeight()) {
252             // #3
253             computedHeightValue = replaced->intrinsicHeight();
254         } else {
255             // #4
256             computedHeightValue = 150;
257         }
258     } else
259         computedHeightValue = height.value();
260
261     displayBox.setHeight(computedHeightValue);
262 }
263
264 void FormattingContext::computeReplacedWidth(LayoutContext&, const Box& layoutBox, Display::Box& displayBox) const
265 {
266     ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
267
268     // 10.3.4 Block-level, replaced elements in normal flow: The used value of 'width' is determined as for inline replaced elements.
269     // 10.3.6 Floating, replaced elements: The used value of 'width' is determined as for inline replaced elements.
270     // 10.3.8 Absolutely positioned, replaced elements: The used value of 'width' is determined as for inline replaced elements.
271
272     // 10.3.2 Inline, replaced elements
273     //
274     // 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'.
275     //
276     // 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; 
277     //    or if 'width' has a computed value of 'auto', 'height' has some other computed value, and the element does have an intrinsic ratio;
278     //    then the used value of 'width' is: (used height) * (intrinsic ratio)
279     //
280     // 3. If 'height' and 'width' both have computed values of 'auto' and the element has an intrinsic ratio but no intrinsic height or width,
281     //    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 
282     //    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.
283     //
284     // 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'.
285     // 
286     // 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. 
287     //    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.
288     auto& style = layoutBox.style();
289     auto width = style.logicalWidth();
290     auto height = style.logicalHeight();
291
292     LayoutUnit computedWidthValue;
293     auto replaced = layoutBox.replaced();
294     ASSERT(replaced);
295
296     if (width.isAuto() && height.isAuto() && replaced->hasIntrinsicWidth()) {
297         // #1
298         computedWidthValue = replaced->intrinsicWidth();
299     } else if (width.isAuto() && (height.isCalculated() || replaced->hasIntrinsicHeight()) && replaced->hasIntrinsicRatio()) {
300         // #2
301         auto usedHeight = height.isCalculated() ? LayoutUnit(height.value()) : replaced->intrinsicHeight();   
302         computedWidthValue = usedHeight * replaced->intrinsicRatio();
303     } else if (width.isAuto() && height.isAuto() && replaced->hasIntrinsicRatio()) {
304         // #3
305         // FIXME: undefined but surely doable.
306         ASSERT_NOT_IMPLEMENTED_YET();
307     } else if (width.isAuto() && replaced->hasIntrinsicWidth()) {
308         // #4
309         computedWidthValue = replaced->intrinsicWidth();
310     } else {
311         // #5
312         computedWidthValue = 300;
313     }
314
315     displayBox.setWidth(computedWidthValue);
316 }
317
318 LayoutUnit FormattingContext::contentHeightForFormattingContextRoot(LayoutContext& layoutContext, const Box& layoutBox) const
319 {
320     ASSERT(layoutBox.style().logicalHeight().isAuto());
321
322     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
323         return 0;
324
325     auto& formattingRootContainer = downcast<Container>(layoutBox);
326     // 10.6.7 'Auto' heights for block formatting context roots
327
328     // 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.
329     // If it has block-level children, the height is the distance between the top margin-edge of the topmost block-level
330     // child box and the bottom margin-edge of the bottommost block-level child box.
331
332     // In addition, if the element has any floating descendants whose bottom margin edge is below the element's bottom content edge,
333     // then the height is increased to include those edges. Only floats that participate in this block formatting context are taken
334     // into account, e.g., floats inside absolutely positioned descendants or other floats are not.
335     if (formattingRootContainer.establishesInlineFormattingContext())
336         return 0;
337
338     auto* firstDisplayBox = layoutContext.displayBoxForLayoutBox(*formattingRootContainer.firstInFlowChild());
339     auto* lastDisplayBox = layoutContext.displayBoxForLayoutBox(*formattingRootContainer.lastInFlowChild());
340
341     auto top = firstDisplayBox->marginBox().y();
342     auto bottom = lastDisplayBox->marginBox().maxY();
343     // FIXME: add floating support.
344     return bottom - top;
345 }
346
347 void FormattingContext::computeFloatingNonReplacedWidth(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
348 {
349     ASSERT(layoutBox.isFloatingPositioned() && !layoutBox.replaced());
350     // 10.3.5 Floating, non-replaced elements
351
352     // If 'width' is computed as 'auto', the used value is the "shrink-to-fit" width.
353     auto width = layoutBox.style().logicalWidth();
354     displayBox.setWidth(width.isAuto() ? shrinkToFitWidth(layoutContext, layoutBox) : LayoutUnit(width.value()));
355 }
356
357 void FormattingContext::computeOutOfFlowNonReplacedWidth(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
358 {
359     ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
360     
361     // 10.3.7 Absolutely positioned, non-replaced elements
362     //
363     // 'left' + 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' + 'right'
364     // = width of containing block
365
366     // If all three of 'left', 'width', and 'right' are 'auto': First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
367     // Then, if the 'direction' property of the element establishing the static-position containing block is 'ltr' set 'left' to the static
368     // position and apply rule number three below; otherwise, set 'right' to the static position and apply rule number one below.
369
370     // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', then the width is shrink-to-fit. Then solve for 'left'
371     // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', then if the 'direction' property of the element establishing the static-position 
372     //    containing block is 'ltr' set 'left' to the static position, otherwise set 'right' to the static position.
373     //    Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr').
374     // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', then the width is shrink-to-fit . Then solve for 'right'
375     // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left'
376     // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width'
377     // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right'
378     auto& style = layoutBox.style();
379     auto left = style.logicalLeft();
380     auto right = style.logicalRight();
381     auto width = style.logicalWidth();
382
383     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->width();
384     LayoutUnit computedWidthValue;
385
386     if ((left.isAuto() && width.isAuto() && right.isAuto())
387         || (left.isAuto() && width.isAuto() && !right.isAuto())
388         || (!left.isAuto() && width.isAuto() && right.isAuto())) {
389         // All auto (#1), #1 and #3
390         computedWidthValue = shrinkToFitWidth(layoutContext, layoutBox);
391     } else if (!left.isAuto() && width.isAuto() && !right.isAuto()) {
392         // #5
393         auto marginLeft = displayBox.marginLeft();
394         auto marginRight = displayBox.marginRight();
395     
396         auto paddingLeft = displayBox.paddingLeft();
397         auto paddingRight = displayBox.paddingRight();
398
399         auto borderLeft = displayBox.borderLeft();
400         auto borderRight = displayBox.borderRight();
401
402         computedWidthValue = containingBlockWidth - (left.value() + marginLeft + borderLeft + paddingLeft + paddingRight + borderRight + marginRight + right.value());
403     } else if (!width.isAuto())
404         computedWidthValue = valueForLength(width, containingBlockWidth);
405     else
406         ASSERT_NOT_REACHED();
407
408     displayBox.setWidth(computedWidthValue);
409 }
410
411 void FormattingContext::computeOutOfFlowReplacedHeight(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
412 {
413     ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
414     // 10.6.5 Absolutely positioned, replaced elements
415     //
416     // The used value of 'height' is determined as for inline replaced elements.
417     computeReplacedHeight(layoutContext, layoutBox, displayBox);
418 }
419
420 void FormattingContext::computeOutOfFlowReplacedWidth(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
421 {
422     ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
423     // 10.3.8 Absolutely positioned, replaced elements
424     //
425     // The used value of 'width' is determined as for inline replaced elements.
426     computeReplacedWidth(layoutContext, layoutBox, displayBox);
427 }
428
429 LayoutUnit FormattingContext::shrinkToFitWidth(LayoutContext&, const Box&) const
430 {
431     return 0;
432 }
433
434 }
435 }
436 #endif