eb088ef8d25310966b3aebb566320848430ec53d
[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(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     ASSERT_NOT_IMPLEMENTED_YET();
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     ASSERT_NOT_IMPLEMENTED_YET();
106 }
107
108 void FormattingContext::computeFloatingHeight(const Box&, Display::Box&) const
109 {
110 }
111
112 LayoutUnit FormattingContext::marginTop(const Box&) const
113 {
114     return 0;
115 }
116
117 LayoutUnit FormattingContext::marginLeft(const Box&) const
118 {
119     return 0;
120 }
121
122 LayoutUnit FormattingContext::marginBottom(const Box&) const
123 {
124     return 0;
125 }
126
127 LayoutUnit FormattingContext::marginRight(const Box&) const
128 {
129     return 0;
130 }
131
132 void FormattingContext::placeInFlowPositionedChildren(const Container&) const
133 {
134 }
135
136 void FormattingContext::layoutOutOfFlowDescendants(LayoutContext& layoutContext) const
137 {
138     if (!is<Container>(m_root.get()))
139         return;
140     for (auto& outOfFlowBox : downcast<Container>(*m_root).outOfFlowDescendants()) {
141         auto& layoutBox = *outOfFlowBox;
142         auto& displayBox = layoutContext.createDisplayBox(layoutBox);
143
144         computeOutOfFlowPosition(layoutBox, displayBox);
145         computeOutOfFlowWidth(layoutContext, layoutBox, displayBox);
146
147         ASSERT(layoutBox.establishesFormattingContext());
148         auto formattingContext = layoutContext.formattingContext(layoutBox);
149         formattingContext->layout(layoutContext, layoutContext.establishedFormattingState(layoutBox, *formattingContext));
150
151         computeOutOfFlowHeight(layoutContext, layoutBox, displayBox);
152     }
153 }
154
155 void FormattingContext::computeOutOfFlowNonReplacedHeight(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
156 {
157     ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
158
159     // 10.6.4 Absolutely positioned, non-replaced elements
160     //
161     // For absolutely positioned elements, the used values of the vertical dimensions must satisfy this constraint:
162     // 'top' + 'margin-top' + 'border-top-width' + 'padding-top' + 'height' + 'padding-bottom' + 'border-bottom-width' + 'margin-bottom' + 'bottom'
163     // = height of containing block
164
165     // If all three of 'top', 'height', and 'bottom' are auto, set 'top' to the static position and apply rule number three below.
166
167     // If none of the three are 'auto': If both 'margin-top' and 'margin-bottom' are 'auto', solve the equation under the extra
168     // constraint that the two margins get equal values. If one of 'margin-top' or 'margin-bottom' is 'auto', solve the equation for that value.
169     // If the values are over-constrained, ignore the value for 'bottom' and solve for that value.
170
171     // Otherwise, pick the one of the following six rules that applies.
172
173     // 1. 'top' and 'height' are 'auto' and 'bottom' is not 'auto', then the height is based on the content per 10.6.7,
174     //     set 'auto' values for 'margin-top' and 'margin-bottom' to 0, and solve for 'top'
175     // 2. 'top' and 'bottom' are 'auto' and 'height' is not 'auto', then set 'top' to the static position, set 'auto' values for
176     //    'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
177     // 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'
178     //     values for 'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
179     // 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'
180     // 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'
181     // 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'
182     auto& style = layoutBox.style();
183     auto top = style.logicalTop();
184     auto bottom = style.logicalBottom();
185     auto height = style.logicalHeight(); 
186
187     auto containingBlockHeight = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->height();
188     LayoutUnit computedHeightValue;
189
190     if ((top.isAuto() && height.isAuto() && bottom.isAuto())
191         || (top.isAuto() && height.isAuto() && !bottom.isAuto())
192         || (!top.isAuto() && height.isAuto() && bottom.isAuto())) {
193         // All auto (#3), #1 and #3
194         computedHeightValue = contentHeightForFormattingContextRoot(layoutContext, layoutBox);
195     } else if (!top.isAuto() && height.isAuto() && !bottom.isAuto()) {
196         // #5
197         auto marginTop = displayBox.marginTop();
198         auto marginBottom = displayBox.marginBottom();
199     
200         auto paddingTop = displayBox.paddingTop();
201         auto paddingBottom = displayBox.paddingBottom();
202
203         auto borderTop = displayBox.borderTop();
204         auto borderBottom = displayBox.borderBottom();
205
206         computedHeightValue = containingBlockHeight - (top.value() + marginTop + borderTop + paddingTop + paddingBottom + borderBottom + marginBottom + bottom.value());
207     } else if (!height.isAuto())
208         computedHeightValue = valueForLength(height, containingBlockHeight);
209     else
210         ASSERT_NOT_REACHED();
211
212     displayBox.setHeight(computedHeightValue);
213 }
214
215 void FormattingContext::computeReplacedWidth(LayoutContext&, const Box& layoutBox, Display::Box& displayBox) const
216 {
217     ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
218
219     // 10.3.4 Block-level, replaced elements in normal flow: The used value of 'width' is determined as for inline replaced elements.
220     // 10.3.6 Floating, replaced elements: The used value of 'width' is determined as for inline replaced elements.
221     // 10.3.8 Absolutely positioned, replaced elements: The used value of 'width' is determined as for inline replaced elements.
222
223     // 10.3.2 Inline, replaced elements
224     //
225     // 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'.
226     //
227     // 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; 
228     //    or if 'width' has a computed value of 'auto', 'height' has some other computed value, and the element does have an intrinsic ratio;
229     //    then the used value of 'width' is: (used height) * (intrinsic ratio)
230     //
231     // 3. If 'height' and 'width' both have computed values of 'auto' and the element has an intrinsic ratio but no intrinsic height or width,
232     //    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 
233     //    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.
234     //
235     // 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'.
236     // 
237     // 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. 
238     //    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.
239     auto& style = layoutBox.style();
240     auto width = style.logicalWidth();
241     auto height = style.logicalHeight();
242
243     LayoutUnit computedWidthValue;
244     auto replaced = layoutBox.replaced();
245     ASSERT(replaced);
246
247     if (width.isAuto() && height.isAuto() && replaced->hasIntrinsicWidth()) {
248         // #1
249         computedWidthValue = replaced->intrinsicWidth();
250     } else if (width.isAuto() && (height.isCalculated() || replaced->hasIntrinsicHeight()) && replaced->hasIntrinsicRatio()) {
251         // #2
252         auto usedHeight = height.isCalculated() ? LayoutUnit(height.value()) : replaced->intrinsicHeight();   
253         computedWidthValue = usedHeight * replaced->intrinsicRatio();
254     } else if (width.isAuto() && height.isAuto() && replaced->hasIntrinsicRatio()) {
255         // #3
256         // FIXME: undefined but surely doable.
257         ASSERT_NOT_IMPLEMENTED_YET();
258     } else if (width.isAuto() && replaced->hasIntrinsicWidth()) {
259         // #4
260         computedWidthValue = replaced->intrinsicWidth();
261     } else {
262         // #5
263         computedWidthValue = 300;
264     }
265
266     displayBox.setWidth(computedWidthValue);   
267 }
268
269 LayoutUnit FormattingContext::contentHeightForFormattingContextRoot(LayoutContext& layoutContext, const Box& layoutBox) const
270 {
271     ASSERT(layoutBox.style().logicalHeight().isAuto());
272
273     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
274         return 0;
275
276     auto& formattingRootContainer = downcast<Container>(layoutBox);
277     // 10.6.7 'Auto' heights for block formatting context roots
278
279     // 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.
280     // If it has block-level children, the height is the distance between the top margin-edge of the topmost block-level
281     // child box and the bottom margin-edge of the bottommost block-level child box.
282
283     // In addition, if the element has any floating descendants whose bottom margin edge is below the element's bottom content edge,
284     // then the height is increased to include those edges. Only floats that participate in this block formatting context are taken
285     // into account, e.g., floats inside absolutely positioned descendants or other floats are not.
286     if (formattingRootContainer.establishesInlineFormattingContext())
287         return 0;
288
289     auto* firstDisplayBox = layoutContext.displayBoxForLayoutBox(*formattingRootContainer.firstInFlowChild());
290     auto* lastDisplayBox = layoutContext.displayBoxForLayoutBox(*formattingRootContainer.lastInFlowChild());
291
292     auto top = firstDisplayBox->marginBox().y();
293     auto bottom = lastDisplayBox->marginBox().maxY();
294     // FIXME: add floating support.
295     return bottom - top;
296 }
297
298 void FormattingContext::computeFloatingNonReplacedWidth(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
299 {
300     ASSERT(layoutBox.isFloatingPositioned() && !layoutBox.replaced());
301     // 10.3.5 Floating, non-replaced elements
302
303     // If 'width' is computed as 'auto', the used value is the "shrink-to-fit" width.
304     auto width = layoutBox.style().logicalWidth();
305     displayBox.setWidth(width.isAuto() ? shrinkToFitWidth(layoutContext, layoutBox) : LayoutUnit(width.value()));
306 }
307
308 void FormattingContext::computeOutOfFlowNonReplacedWidth(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
309 {
310     ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
311     
312     // 10.3.7 Absolutely positioned, non-replaced elements
313     //
314     // 'left' + 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' + 'right'
315     // = width of containing block
316
317     // If all three of 'left', 'width', and 'right' are 'auto': First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
318     // Then, if the 'direction' property of the element establishing the static-position containing block is 'ltr' set 'left' to the static
319     // position and apply rule number three below; otherwise, set 'right' to the static position and apply rule number one below.
320
321     // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', then the width is shrink-to-fit. Then solve for 'left'
322     // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', then if the 'direction' property of the element establishing the static-position 
323     //    containing block is 'ltr' set 'left' to the static position, otherwise set 'right' to the static position.
324     //    Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr').
325     // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', then the width is shrink-to-fit . Then solve for 'right'
326     // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left'
327     // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width'
328     // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right'
329     auto& style = layoutBox.style();
330     auto left = style.logicalLeft();
331     auto right = style.logicalRight();
332     auto width = style.logicalWidth();
333
334     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->width();
335     LayoutUnit computedWidthValue;
336
337     if ((left.isAuto() && width.isAuto() && right.isAuto())
338         || (left.isAuto() && width.isAuto() && !right.isAuto())
339         || (!left.isAuto() && width.isAuto() && right.isAuto())) {
340         // All auto (#1), #1 and #3
341         computedWidthValue = shrinkToFitWidth(layoutContext, layoutBox);
342     } else if (!left.isAuto() && width.isAuto() && !right.isAuto()) {
343         // #5
344         auto marginLeft = displayBox.marginLeft();
345         auto marginRight = displayBox.marginRight();
346     
347         auto paddingLeft = displayBox.paddingLeft();
348         auto paddingRight = displayBox.paddingRight();
349
350         auto borderLeft = displayBox.borderLeft();
351         auto borderRight = displayBox.borderRight();
352
353         computedWidthValue = containingBlockWidth - (left.value() + marginLeft + borderLeft + paddingLeft + paddingRight + borderRight + marginRight + right.value());
354     } else if (!width.isAuto())
355         computedWidthValue = valueForLength(width, containingBlockWidth);
356     else
357         ASSERT_NOT_REACHED();
358
359     displayBox.setWidth(computedWidthValue);
360 }
361
362 void FormattingContext::computeOutOfFlowReplacedWidth(LayoutContext& layoutContext, const Box& layoutBox, Display::Box& displayBox) const
363 {
364     ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
365     // 10.3.8 Absolutely positioned, replaced elements
366     //
367     // The used value of 'width' is determined as for inline replaced elements.
368     computeReplacedWidth(layoutContext, layoutBox, displayBox);
369 }
370
371 LayoutUnit FormattingContext::shrinkToFitWidth(LayoutContext&, const Box&) const
372 {
373     return 0;
374 }
375
376 }
377 }
378 #endif