8eaaf3c23de7e3265a38b05918ae700ed7d19993
[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 "LayoutDescendantIterator.h"
36 #include "Logging.h"
37 #include <wtf/IsoMallocInlines.h>
38 #include <wtf/text/TextStream.h>
39
40 namespace WebCore {
41 namespace Layout {
42
43 WTF_MAKE_ISO_ALLOCATED_IMPL(FormattingContext);
44
45 FormattingContext::FormattingContext(const Box& formattingContextRoot)
46     : m_root(makeWeakPtr(formattingContextRoot))
47 {
48 }
49
50 FormattingContext::~FormattingContext()
51 {
52 }
53
54 void FormattingContext::computeOutOfFlowHorizontalGeometry(LayoutContext& layoutContext, const Box& layoutBox) const
55 {
56     auto compute = [&](std::optional<LayoutUnit> precomputedWidth) {
57         return Geometry::outOfFlowHorizontalGeometry(layoutContext, *this, layoutBox, precomputedWidth);
58     };
59
60     auto horizontalGeometry = compute({ });
61     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
62
63     if (auto maxWidth = Geometry::computedValueIfNotAuto(layoutBox.style().logicalMaxWidth(), containingBlockWidth)) {
64         auto maxHorizontalGeometry = compute(maxWidth);
65         if (horizontalGeometry.widthAndMargin.width > maxHorizontalGeometry.widthAndMargin.width)
66             horizontalGeometry = maxHorizontalGeometry;
67     }
68
69     if (auto minWidth = Geometry::computedValueIfNotAuto(layoutBox.style().logicalMinWidth(), containingBlockWidth)) {
70         auto minHorizontalGeometry = compute(minWidth);
71         if (horizontalGeometry.widthAndMargin.width < minHorizontalGeometry.widthAndMargin.width)
72             horizontalGeometry = minHorizontalGeometry;
73     }
74
75     auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
76     displayBox.setLeft(horizontalGeometry.left + horizontalGeometry.widthAndMargin.margin.left);
77     displayBox.setContentBoxWidth(horizontalGeometry.widthAndMargin.width);
78     displayBox.setHorizontalMargin(horizontalGeometry.widthAndMargin.margin);
79     displayBox.setHorizontalNonComputedMargin(horizontalGeometry.widthAndMargin.nonComputedMargin);
80 }
81
82 void FormattingContext::computeOutOfFlowVerticalGeometry(const LayoutContext& layoutContext, const Box& layoutBox) const
83 {
84     auto compute = [&](std::optional<LayoutUnit> precomputedHeight) {
85         return Geometry::outOfFlowVerticalGeometry(layoutContext, layoutBox, precomputedHeight);
86     };
87
88     auto verticalGeometry = compute({ });
89     // FIXME: Add support for percentage values where the containing block's height is explicitly specified.
90     if (auto maxHeight = Geometry::fixedValue(layoutBox.style().logicalMaxHeight())) {
91         auto maxVerticalGeometry = compute(maxHeight);
92         if (verticalGeometry.heightAndMargin.height > maxVerticalGeometry.heightAndMargin.height)
93             verticalGeometry = maxVerticalGeometry;
94     }
95
96     if (auto minHeight = Geometry::fixedValue(layoutBox.style().logicalMinHeight())) {
97         auto minVerticalGeometry = compute(minHeight);
98         if (verticalGeometry.heightAndMargin.height < minVerticalGeometry.heightAndMargin.height)
99             verticalGeometry = minVerticalGeometry;
100     }
101
102     auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
103     displayBox.setTop(verticalGeometry.top + verticalGeometry.heightAndMargin.margin.top);
104     displayBox.setContentBoxHeight(verticalGeometry.heightAndMargin.height);
105     ASSERT(!verticalGeometry.heightAndMargin.collapsedMargin);
106     displayBox.setVerticalMargin(verticalGeometry.heightAndMargin.margin);
107     displayBox.setVerticalNonCollapsedMargin(verticalGeometry.heightAndMargin.margin);
108 }
109
110 void FormattingContext::computeBorderAndPadding(const LayoutContext& layoutContext, const Box& layoutBox) const
111 {
112     auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
113     displayBox.setBorder(Geometry::computedBorder(layoutContext, layoutBox));
114     displayBox.setPadding(Geometry::computedPadding(layoutContext, layoutBox));
115 }
116
117 void FormattingContext::placeInFlowPositionedChildren(const LayoutContext& layoutContext, const Container& container) const
118 {
119     // If this container also establishes a formatting context, then positioning already has happend in that the formatting context.
120     if (container.establishesFormattingContext() && &container != &root())
121         return;
122
123     LOG_WITH_STREAM(FormattingContextLayout, stream << "Start: move in-flow positioned children -> context: " << &layoutContext << " parent: " << &container);
124     for (auto& layoutBox : childrenOfType<Box>(container)) {
125         if (!layoutBox.isInFlowPositioned())
126             continue;
127         computeInFlowPositionedPosition(layoutContext, layoutBox);
128     }
129     LOG_WITH_STREAM(FormattingContextLayout, stream << "End: move in-flow positioned children -> context: " << &layoutContext << " parent: " << &container);
130 }
131
132 void FormattingContext::layoutOutOfFlowDescendants(LayoutContext& layoutContext, const Box& layoutBox) const
133 {
134     // Initial containing block by definition is a containing block.
135     if (!layoutBox.isPositioned() && !layoutBox.isInitialContainingBlock())
136         return;
137
138     if (!is<Container>(layoutBox))
139         return;
140
141     auto& container = downcast<Container>(layoutBox);
142     if (!container.hasChild())
143         return;
144
145     LOG_WITH_STREAM(FormattingContextLayout, stream << "Start: layout out-of-flow descendants -> context: " << &layoutContext << " root: " << &root());
146
147     for (auto& outOfFlowBox : container.outOfFlowDescendants()) {
148         auto& layoutBox = *outOfFlowBox;
149
150         ASSERT(layoutBox.establishesFormattingContext());
151         auto formattingContext = layoutContext.formattingContext(layoutBox);
152
153         computeBorderAndPadding(layoutContext, layoutBox);
154         computeOutOfFlowHorizontalGeometry(layoutContext, layoutBox);
155
156         auto& formattingState = layoutContext.createFormattingStateForFormattingRootIfNeeded(layoutBox);
157         formattingContext->layout(layoutContext, formattingState);
158
159         computeOutOfFlowVerticalGeometry(layoutContext, layoutBox);
160         layoutOutOfFlowDescendants(layoutContext, layoutBox);
161     }
162     LOG_WITH_STREAM(FormattingContextLayout, stream << "End: layout out-of-flow descendants -> context: " << &layoutContext << " root: " << &root());
163 }
164
165 Display::Box FormattingContext::mapBoxToAncestor(const LayoutContext& layoutContext, const Box& layoutBox, const Container& ancestor)
166 {
167     ASSERT(layoutBox.isDescendantOf(ancestor));
168
169     auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
170     auto topLeft = displayBox.topLeft();
171
172     auto* containingBlock = layoutBox.containingBlock();
173     for (; containingBlock && containingBlock != &ancestor; containingBlock = containingBlock->containingBlock())
174         topLeft.moveBy(layoutContext.displayBoxForLayoutBox(*containingBlock).topLeft());
175
176     if (!containingBlock) {
177         ASSERT_NOT_REACHED();
178         return Display::Box(displayBox);
179     }
180
181     auto mappedDisplayBox = Display::Box(displayBox);
182     mappedDisplayBox.setTopLeft(topLeft);
183     return mappedDisplayBox;
184 }
185
186 Position FormattingContext::mapTopLeftToAncestor(const LayoutContext& layoutContext, const Box& layoutBox, const Container& ancestor)
187 {
188     ASSERT(layoutBox.isDescendantOf(ancestor));
189     return mapCoordinateToAncestor(layoutContext, layoutContext.displayBoxForLayoutBox(layoutBox).topLeft(), *layoutBox.containingBlock(), ancestor);
190 }
191
192 Position FormattingContext::mapCoordinateToAncestor(const LayoutContext& layoutContext, Position position, const Container& containingBlock, const Container& ancestor)
193 {
194     auto mappedPosition = position;
195     auto* container = &containingBlock;
196     for (; container && container != &ancestor; container = container->containingBlock())
197         mappedPosition.moveBy(layoutContext.displayBoxForLayoutBox(*container).topLeft());
198
199     if (!container) {
200         ASSERT_NOT_REACHED();
201         return position;
202     }
203
204     return mappedPosition;
205 }
206
207 #ifndef NDEBUG
208 void FormattingContext::validateGeometryConstraintsAfterLayout(const LayoutContext& layoutContext) const
209 {
210     if (!is<Container>(root()))
211         return;
212     auto& formattingContextRoot = downcast<Container>(root());
213     // FIXME: add a descendantsOfType<> flavor that stops at nested formatting contexts
214     for (auto& layoutBox : descendantsOfType<Box>(formattingContextRoot)) {
215         if (&layoutBox.formattingContextRoot() != &formattingContextRoot)
216             continue;
217         auto& containingBlockDisplayBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock());
218         auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
219
220         // 10.3.3 Block-level, non-replaced elements in normal flow
221         // 10.3.7 Absolutely positioned, non-replaced elements
222         if ((layoutBox.isBlockLevelBox() || layoutBox.isOutOfFlowPositioned()) && !layoutBox.replaced()) {
223             // margin-left + border-left-width + padding-left + width + padding-right + border-right-width + margin-right = width of containing block
224             auto containingBlockWidth = containingBlockDisplayBox.contentBoxWidth();
225             ASSERT(displayBox.marginLeft() + displayBox.borderLeft() + displayBox.paddingLeft().value_or(0) + displayBox.contentBoxWidth()
226                 + displayBox.paddingRight().value_or(0) + displayBox.borderRight() + displayBox.marginRight() == containingBlockWidth);
227         }
228
229         // 10.6.4 Absolutely positioned, non-replaced elements
230         if (layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced()) {
231             // top + margin-top + border-top-width + padding-top + height + padding-bottom + border-bottom-width + margin-bottom + bottom = height of containing block
232             auto containingBlockHeight = containingBlockDisplayBox.contentBoxHeight();
233             ASSERT(displayBox.top() + displayBox.marginTop() + displayBox.borderTop() + displayBox.paddingTop().value_or(0) + displayBox.contentBoxHeight()
234                 + displayBox.paddingBottom().value_or(0) + displayBox.borderBottom() + displayBox.marginBottom() == containingBlockHeight);
235         }
236     }
237 }
238 #endif
239
240 }
241 }
242 #endif