2 * Copyright (C) 2018 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "FormattingContext.h"
29 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
31 #include "DisplayBox.h"
32 #include "LayoutBox.h"
33 #include "LayoutContainer.h"
34 #include "LayoutContext.h"
35 #include "LayoutDescendantIterator.h"
37 #include <wtf/IsoMallocInlines.h>
38 #include <wtf/text/TextStream.h>
43 WTF_MAKE_ISO_ALLOCATED_IMPL(FormattingContext);
45 FormattingContext::FormattingContext(const Box& formattingContextRoot)
46 : m_root(makeWeakPtr(formattingContextRoot))
50 FormattingContext::~FormattingContext()
54 void FormattingContext::computeOutOfFlowHorizontalGeometry(LayoutContext& layoutContext, const Box& layoutBox) const
56 auto compute = [&](std::optional<LayoutUnit> precomputedWidth) {
57 return Geometry::outOfFlowHorizontalGeometry(layoutContext, *this, layoutBox, precomputedWidth);
60 auto horizontalGeometry = compute({ });
61 auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
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;
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;
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);
82 void FormattingContext::computeOutOfFlowVerticalGeometry(const LayoutContext& layoutContext, const Box& layoutBox) const
84 auto compute = [&](std::optional<LayoutUnit> precomputedHeight) {
85 return Geometry::outOfFlowVerticalGeometry(layoutContext, layoutBox, precomputedHeight);
88 auto verticalGeometry = compute({ });
89 if (auto maxHeight = Geometry::computedMaxHeight(layoutContext, layoutBox)) {
90 auto maxVerticalGeometry = compute(maxHeight);
91 if (verticalGeometry.heightAndMargin.height > maxVerticalGeometry.heightAndMargin.height)
92 verticalGeometry = maxVerticalGeometry;
95 if (auto minHeight = Geometry::computedMinHeight(layoutContext, layoutBox)) {
96 auto minVerticalGeometry = compute(minHeight);
97 if (verticalGeometry.heightAndMargin.height < minVerticalGeometry.heightAndMargin.height)
98 verticalGeometry = minVerticalGeometry;
101 auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
102 displayBox.setTop(verticalGeometry.top + verticalGeometry.heightAndMargin.margin.top);
103 displayBox.setContentBoxHeight(verticalGeometry.heightAndMargin.height);
104 ASSERT(!verticalGeometry.heightAndMargin.collapsedMargin);
105 displayBox.setVerticalMargin(verticalGeometry.heightAndMargin.margin);
106 displayBox.setVerticalNonCollapsedMargin(verticalGeometry.heightAndMargin.margin);
109 void FormattingContext::computeBorderAndPadding(const LayoutContext& layoutContext, const Box& layoutBox) const
111 auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
112 displayBox.setBorder(Geometry::computedBorder(layoutContext, layoutBox));
113 displayBox.setPadding(Geometry::computedPadding(layoutContext, layoutBox));
116 void FormattingContext::placeInFlowPositionedChildren(const LayoutContext& layoutContext, const Container& container) const
118 // If this container also establishes a formatting context, then positioning already has happend in that the formatting context.
119 if (container.establishesFormattingContext() && &container != &root())
122 LOG_WITH_STREAM(FormattingContextLayout, stream << "Start: move in-flow positioned children -> context: " << &layoutContext << " parent: " << &container);
123 for (auto& layoutBox : childrenOfType<Box>(container)) {
124 if (!layoutBox.isInFlowPositioned())
126 computeInFlowPositionedPosition(layoutContext, layoutBox);
128 LOG_WITH_STREAM(FormattingContextLayout, stream << "End: move in-flow positioned children -> context: " << &layoutContext << " parent: " << &container);
131 void FormattingContext::layoutOutOfFlowDescendants(LayoutContext& layoutContext, const Box& layoutBox) const
133 // Initial containing block by definition is a containing block.
134 if (!layoutBox.isPositioned() && !layoutBox.isInitialContainingBlock())
137 if (!is<Container>(layoutBox))
140 auto& container = downcast<Container>(layoutBox);
141 if (!container.hasChild())
144 LOG_WITH_STREAM(FormattingContextLayout, stream << "Start: layout out-of-flow descendants -> context: " << &layoutContext << " root: " << &root());
146 for (auto& outOfFlowBox : container.outOfFlowDescendants()) {
147 auto& layoutBox = *outOfFlowBox;
149 ASSERT(layoutBox.establishesFormattingContext());
150 auto formattingContext = layoutContext.formattingContext(layoutBox);
152 computeBorderAndPadding(layoutContext, layoutBox);
153 computeOutOfFlowHorizontalGeometry(layoutContext, layoutBox);
155 auto& formattingState = layoutContext.createFormattingStateForFormattingRootIfNeeded(layoutBox);
156 formattingContext->layout(layoutContext, formattingState);
158 computeOutOfFlowVerticalGeometry(layoutContext, layoutBox);
159 layoutOutOfFlowDescendants(layoutContext, layoutBox);
161 LOG_WITH_STREAM(FormattingContextLayout, stream << "End: layout out-of-flow descendants -> context: " << &layoutContext << " root: " << &root());
164 Display::Box FormattingContext::mapBoxToAncestor(const LayoutContext& layoutContext, const Box& layoutBox, const Container& ancestor)
166 ASSERT(layoutBox.isDescendantOf(ancestor));
168 auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
169 auto topLeft = displayBox.topLeft();
171 auto* containingBlock = layoutBox.containingBlock();
172 for (; containingBlock && containingBlock != &ancestor; containingBlock = containingBlock->containingBlock())
173 topLeft.moveBy(layoutContext.displayBoxForLayoutBox(*containingBlock).topLeft());
175 if (!containingBlock) {
176 ASSERT_NOT_REACHED();
177 return Display::Box(displayBox);
180 auto mappedDisplayBox = Display::Box(displayBox);
181 mappedDisplayBox.setTopLeft(topLeft);
182 return mappedDisplayBox;
185 Position FormattingContext::mapTopLeftToAncestor(const LayoutContext& layoutContext, const Box& layoutBox, const Container& ancestor)
187 ASSERT(layoutBox.isDescendantOf(ancestor));
188 return mapCoordinateToAncestor(layoutContext, layoutContext.displayBoxForLayoutBox(layoutBox).topLeft(), *layoutBox.containingBlock(), ancestor);
191 Position FormattingContext::mapCoordinateToAncestor(const LayoutContext& layoutContext, Position position, const Container& containingBlock, const Container& ancestor)
193 auto mappedPosition = position;
194 auto* container = &containingBlock;
195 for (; container && container != &ancestor; container = container->containingBlock())
196 mappedPosition.moveBy(layoutContext.displayBoxForLayoutBox(*container).topLeft());
199 ASSERT_NOT_REACHED();
203 return mappedPosition;
207 void FormattingContext::validateGeometryConstraintsAfterLayout(const LayoutContext& layoutContext) const
209 if (!is<Container>(root()))
211 auto& formattingContextRoot = downcast<Container>(root());
212 // FIXME: add a descendantsOfType<> flavor that stops at nested formatting contexts
213 for (auto& layoutBox : descendantsOfType<Box>(formattingContextRoot)) {
214 if (&layoutBox.formattingContextRoot() != &formattingContextRoot)
216 auto& containingBlockDisplayBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock());
217 auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
219 // 10.3.3 Block-level, non-replaced elements in normal flow
220 // 10.3.7 Absolutely positioned, non-replaced elements
221 if ((layoutBox.isBlockLevelBox() || layoutBox.isOutOfFlowPositioned()) && !layoutBox.replaced()) {
222 // margin-left + border-left-width + padding-left + width + padding-right + border-right-width + margin-right = width of containing block
223 auto containingBlockWidth = containingBlockDisplayBox.contentBoxWidth();
224 ASSERT(displayBox.marginLeft() + displayBox.borderLeft() + displayBox.paddingLeft().value_or(0) + displayBox.contentBoxWidth()
225 + displayBox.paddingRight().value_or(0) + displayBox.borderRight() + displayBox.marginRight() == containingBlockWidth);
228 // 10.6.4 Absolutely positioned, non-replaced elements
229 if (layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced()) {
230 // top + margin-top + border-top-width + padding-top + height + padding-bottom + border-bottom-width + margin-bottom + bottom = height of containing block
231 auto containingBlockHeight = containingBlockDisplayBox.contentBoxHeight();
232 ASSERT(displayBox.top() + displayBox.marginTop() + displayBox.borderTop() + displayBox.paddingTop().value_or(0) + displayBox.contentBoxHeight()
233 + displayBox.paddingBottom().value_or(0) + displayBox.borderBottom() + displayBox.marginBottom() == containingBlockHeight);