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 "FormattingState.h" |
33 | #include "LayoutBox.h" |
34 | #include "LayoutContainer.h" |
35 | #include "LayoutDescendantIterator.h" |
36 | #include "LayoutState.h" |
37 | #include "Logging.h" |
38 | #include <wtf/IsoMallocInlines.h> |
39 | #include <wtf/text/TextStream.h> |
40 | |
41 | namespace WebCore { |
42 | namespace Layout { |
43 | |
44 | WTF_MAKE_ISO_ALLOCATED_IMPL(FormattingContext); |
45 | |
46 | FormattingContext::FormattingContext(const Box& formattingContextRoot, FormattingState& formattingState) |
47 | : m_root(makeWeakPtr(formattingContextRoot)) |
48 | , m_formattingState(formattingState) |
49 | { |
50 | #ifndef NDEBUG |
51 | layoutState().registerFormattingContext(*this); |
52 | #endif |
53 | } |
54 | |
55 | FormattingContext::~FormattingContext() |
56 | { |
57 | #ifndef NDEBUG |
58 | layoutState().deregisterFormattingContext(*this); |
59 | #endif |
60 | } |
61 | |
62 | LayoutState& FormattingContext::layoutState() const |
63 | { |
64 | return m_formattingState.layoutState(); |
65 | } |
66 | |
67 | void FormattingContext::computeOutOfFlowHorizontalGeometry(const Box& layoutBox) const |
68 | { |
69 | auto& layoutState = this->layoutState(); |
70 | auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).paddingBoxWidth(); |
71 | |
72 | auto compute = [&](Optional<LayoutUnit> usedWidth) { |
73 | auto usedValues = UsedHorizontalValues { containingBlockWidth, usedWidth, { } }; |
74 | return Geometry::outOfFlowHorizontalGeometry(layoutState, layoutBox, usedValues); |
75 | }; |
76 | |
77 | auto horizontalGeometry = compute({ }); |
78 | if (auto maxWidth = Geometry::computedValueIfNotAuto(layoutBox.style().logicalMaxWidth(), containingBlockWidth)) { |
79 | auto maxHorizontalGeometry = compute(maxWidth); |
80 | if (horizontalGeometry.widthAndMargin.width > maxHorizontalGeometry.widthAndMargin.width) |
81 | horizontalGeometry = maxHorizontalGeometry; |
82 | } |
83 | |
84 | if (auto minWidth = Geometry::computedValueIfNotAuto(layoutBox.style().logicalMinWidth(), containingBlockWidth)) { |
85 | auto minHorizontalGeometry = compute(minWidth); |
86 | if (horizontalGeometry.widthAndMargin.width < minHorizontalGeometry.widthAndMargin.width) |
87 | horizontalGeometry = minHorizontalGeometry; |
88 | } |
89 | |
90 | auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox); |
91 | displayBox.setLeft(horizontalGeometry.left + horizontalGeometry.widthAndMargin.usedMargin.start); |
92 | displayBox.setContentBoxWidth(horizontalGeometry.widthAndMargin.width); |
93 | displayBox.setHorizontalMargin(horizontalGeometry.widthAndMargin.usedMargin); |
94 | displayBox.setHorizontalComputedMargin(horizontalGeometry.widthAndMargin.computedMargin); |
95 | } |
96 | |
97 | void FormattingContext::computeOutOfFlowVerticalGeometry(const Box& layoutBox) const |
98 | { |
99 | auto& layoutState = this->layoutState(); |
100 | |
101 | auto compute = [&](UsedVerticalValues usedValues) { |
102 | return Geometry::outOfFlowVerticalGeometry(layoutState, layoutBox, usedValues); |
103 | }; |
104 | |
105 | auto verticalGeometry = compute({ }); |
106 | if (auto maxHeight = Geometry::computedMaxHeight(layoutState, layoutBox)) { |
107 | auto maxVerticalGeometry = compute({ *maxHeight }); |
108 | if (verticalGeometry.heightAndMargin.height > maxVerticalGeometry.heightAndMargin.height) |
109 | verticalGeometry = maxVerticalGeometry; |
110 | } |
111 | |
112 | if (auto minHeight = Geometry::computedMinHeight(layoutState, layoutBox)) { |
113 | auto minVerticalGeometry = compute({ *minHeight }); |
114 | if (verticalGeometry.heightAndMargin.height < minVerticalGeometry.heightAndMargin.height) |
115 | verticalGeometry = minVerticalGeometry; |
116 | } |
117 | |
118 | auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox); |
119 | auto nonCollapsedVerticalMargin = verticalGeometry.heightAndMargin.nonCollapsedMargin; |
120 | displayBox.setTop(verticalGeometry.top + nonCollapsedVerticalMargin.before); |
121 | displayBox.setContentBoxHeight(verticalGeometry.heightAndMargin.height); |
122 | // Margins of absolutely positioned boxes do not collapse |
123 | displayBox.setVerticalMargin({ nonCollapsedVerticalMargin, { } }); |
124 | } |
125 | |
126 | void FormattingContext::computeBorderAndPadding(const Box& layoutBox, Optional<UsedHorizontalValues> usedValues) const |
127 | { |
128 | auto& layoutState = this->layoutState(); |
129 | if (!usedValues) |
130 | usedValues = UsedHorizontalValues { layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth() }; |
131 | auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox); |
132 | displayBox.setBorder(Geometry::computedBorder(layoutBox)); |
133 | displayBox.setPadding(Geometry::computedPadding(layoutBox, *usedValues)); |
134 | } |
135 | |
136 | void FormattingContext::layoutOutOfFlowDescendants(const Box& layoutBox) const |
137 | { |
138 | // Initial containing block by definition is a containing block. |
139 | if (!layoutBox.isPositioned() && !layoutBox.isInitialContainingBlock()) |
140 | return; |
141 | |
142 | if (!is<Container>(layoutBox)) |
143 | return; |
144 | |
145 | auto& container = downcast<Container>(layoutBox); |
146 | if (!container.hasChild()) |
147 | return; |
148 | |
149 | auto& layoutState = this->layoutState(); |
150 | LOG_WITH_STREAM(FormattingContextLayout, stream << "Start: layout out-of-flow descendants -> context: " << &layoutState << " root: " << &root()); |
151 | |
152 | for (auto& outOfFlowBox : container.outOfFlowDescendants()) { |
153 | auto& layoutBox = *outOfFlowBox; |
154 | |
155 | ASSERT(layoutBox.establishesFormattingContext()); |
156 | |
157 | computeBorderAndPadding(layoutBox); |
158 | computeOutOfFlowHorizontalGeometry(layoutBox); |
159 | |
160 | layoutState.createFormattingContext(layoutBox)->layout(); |
161 | |
162 | computeOutOfFlowVerticalGeometry(layoutBox); |
163 | layoutOutOfFlowDescendants(layoutBox); |
164 | } |
165 | LOG_WITH_STREAM(FormattingContextLayout, stream << "End: layout out-of-flow descendants -> context: " << &layoutState << " root: " << &root()); |
166 | } |
167 | |
168 | Display::Box FormattingContext::mapBoxToAncestor(const LayoutState& layoutState, const Box& layoutBox, const Container& ancestor) |
169 | { |
170 | ASSERT(layoutBox.isDescendantOf(ancestor)); |
171 | |
172 | auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox); |
173 | auto topLeft = displayBox.topLeft(); |
174 | |
175 | auto* containingBlock = layoutBox.containingBlock(); |
176 | for (; containingBlock && containingBlock != &ancestor; containingBlock = containingBlock->containingBlock()) |
177 | topLeft.moveBy(layoutState.displayBoxForLayoutBox(*containingBlock).topLeft()); |
178 | |
179 | if (!containingBlock) { |
180 | ASSERT_NOT_REACHED(); |
181 | return Display::Box(displayBox); |
182 | } |
183 | |
184 | auto mappedDisplayBox = Display::Box(displayBox); |
185 | mappedDisplayBox.setTopLeft(topLeft); |
186 | return mappedDisplayBox; |
187 | } |
188 | |
189 | LayoutUnit FormattingContext::mapTopToAncestor(const LayoutState& layoutState, const Box& layoutBox, const Container& ancestor) |
190 | { |
191 | ASSERT(layoutBox.isDescendantOf(ancestor)); |
192 | auto top = layoutState.displayBoxForLayoutBox(layoutBox).top(); |
193 | auto* container = layoutBox.containingBlock(); |
194 | for (; container && container != &ancestor; container = container->containingBlock()) |
195 | top += layoutState.displayBoxForLayoutBox(*container).top(); |
196 | return top; |
197 | } |
198 | |
199 | Point FormattingContext::mapCoordinateToAncestor(const LayoutState& layoutState, Point position, const Container& containingBlock, const Container& ancestor) |
200 | { |
201 | auto mappedPosition = position; |
202 | auto* container = &containingBlock; |
203 | for (; container && container != &ancestor; container = container->containingBlock()) |
204 | mappedPosition.moveBy(layoutState.displayBoxForLayoutBox(*container).topLeft()); |
205 | |
206 | if (!container) { |
207 | ASSERT_NOT_REACHED(); |
208 | return position; |
209 | } |
210 | |
211 | return mappedPosition; |
212 | } |
213 | |
214 | #ifndef NDEBUG |
215 | void FormattingContext::validateGeometryConstraintsAfterLayout() const |
216 | { |
217 | if (!is<Container>(root())) |
218 | return; |
219 | auto& formattingContextRoot = downcast<Container>(root()); |
220 | auto& layoutState = this->layoutState(); |
221 | // FIXME: add a descendantsOfType<> flavor that stops at nested formatting contexts |
222 | for (auto& layoutBox : descendantsOfType<Box>(formattingContextRoot)) { |
223 | if (&layoutBox.formattingContextRoot() != &formattingContextRoot) |
224 | continue; |
225 | auto& containingBlockDisplayBox = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()); |
226 | auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox); |
227 | |
228 | // 10.3.3 Block-level, non-replaced elements in normal flow |
229 | // 10.3.7 Absolutely positioned, non-replaced elements |
230 | if ((layoutBox.isBlockLevelBox() || layoutBox.isOutOfFlowPositioned()) && !layoutBox.replaced()) { |
231 | // margin-left + border-left-width + padding-left + width + padding-right + border-right-width + margin-right = width of containing block |
232 | auto containingBlockWidth = containingBlockDisplayBox.contentBoxWidth(); |
233 | ASSERT(displayBox.marginStart() + displayBox.borderLeft() + displayBox.paddingLeft().valueOr(0) + displayBox.contentBoxWidth() |
234 | + displayBox.paddingRight().valueOr(0) + displayBox.borderRight() + displayBox.marginEnd() == containingBlockWidth); |
235 | } |
236 | |
237 | // 10.6.4 Absolutely positioned, non-replaced elements |
238 | if (layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced()) { |
239 | // top + margin-top + border-top-width + padding-top + height + padding-bottom + border-bottom-width + margin-bottom + bottom = height of containing block |
240 | auto containingBlockHeight = containingBlockDisplayBox.contentBoxHeight(); |
241 | ASSERT(displayBox.top() + displayBox.marginBefore() + displayBox.borderTop() + displayBox.paddingTop().valueOr(0) + displayBox.contentBoxHeight() |
242 | + displayBox.paddingBottom().valueOr(0) + displayBox.borderBottom() + displayBox.marginAfter() == containingBlockHeight); |
243 | } |
244 | } |
245 | } |
246 | #endif |
247 | |
248 | } |
249 | } |
250 | #endif |
251 | |