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
41namespace WebCore {
42namespace Layout {
43
44WTF_MAKE_ISO_ALLOCATED_IMPL(FormattingContext);
45
46FormattingContext::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
55FormattingContext::~FormattingContext()
56{
57#ifndef NDEBUG
58 layoutState().deregisterFormattingContext(*this);
59#endif
60}
61
62LayoutState& FormattingContext::layoutState() const
63{
64 return m_formattingState.layoutState();
65}
66
67void 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
97void 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
126void 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
136void 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
168Display::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
189LayoutUnit 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
199Point 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
215void 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