[LFC] Layout::Box::containingBlock should return a const ContainerBox&
[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 "FormattingState.h"
33 #include "InvalidationState.h"
34 #include "LayoutBox.h"
35 #include "LayoutContainerBox.h"
36 #include "LayoutContext.h"
37 #include "LayoutDescendantIterator.h"
38 #include "LayoutReplacedBox.h"
39 #include "LayoutState.h"
40 #include "Logging.h"
41 #include <wtf/IsoMallocInlines.h>
42 #include <wtf/text/TextStream.h>
43
44 namespace WebCore {
45 namespace Layout {
46
47 WTF_MAKE_ISO_ALLOCATED_IMPL(FormattingContext);
48
49 FormattingContext::FormattingContext(const ContainerBox& formattingContextRoot, FormattingState& formattingState)
50     : m_root(makeWeakPtr(formattingContextRoot))
51     , m_formattingState(formattingState)
52 {
53     ASSERT(formattingContextRoot.hasChild());
54 #ifndef NDEBUG
55     layoutState().registerFormattingContext(*this);
56 #endif
57 }
58
59 FormattingContext::~FormattingContext()
60 {
61 #ifndef NDEBUG
62     layoutState().deregisterFormattingContext(*this);
63 #endif
64 }
65
66 LayoutState& FormattingContext::layoutState() const
67 {
68     return m_formattingState.layoutState();
69 }
70
71 void FormattingContext::computeOutOfFlowHorizontalGeometry(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints, const VerticalConstraints& verticalConstraints)
72 {
73     ASSERT(layoutBox.isOutOfFlowPositioned());
74     auto compute = [&](Optional<LayoutUnit> usedWidth) {
75         return geometry().outOfFlowHorizontalGeometry(layoutBox, horizontalConstraints, verticalConstraints, { usedWidth, { } });
76     };
77
78     auto containingBlockWidth = horizontalConstraints.logicalWidth;
79     auto horizontalGeometry = compute({ });
80     if (auto maxWidth = geometry().computedMaxWidth(layoutBox, containingBlockWidth)) {
81         auto maxHorizontalGeometry = compute(maxWidth);
82         if (horizontalGeometry.contentWidthAndMargin.contentWidth > maxHorizontalGeometry.contentWidthAndMargin.contentWidth)
83             horizontalGeometry = maxHorizontalGeometry;
84     }
85
86     if (auto minWidth = geometry().computedMinWidth(layoutBox, containingBlockWidth)) {
87         auto minHorizontalGeometry = compute(minWidth);
88         if (horizontalGeometry.contentWidthAndMargin.contentWidth < minHorizontalGeometry.contentWidthAndMargin.contentWidth)
89             horizontalGeometry = minHorizontalGeometry;
90     }
91
92     auto& displayBox = formattingState().displayBox(layoutBox);
93     displayBox.setLeft(horizontalGeometry.left + horizontalGeometry.contentWidthAndMargin.usedMargin.start);
94     displayBox.setContentBoxWidth(horizontalGeometry.contentWidthAndMargin.contentWidth);
95     displayBox.setHorizontalMargin(horizontalGeometry.contentWidthAndMargin.usedMargin);
96     displayBox.setHorizontalComputedMargin(horizontalGeometry.contentWidthAndMargin.computedMargin);
97 }
98
99 void FormattingContext::computeOutOfFlowVerticalGeometry(const Box& layoutBox, const HorizontalConstraints& horizontalConstraints, const VerticalConstraints& verticalConstraints)
100 {
101     ASSERT(layoutBox.isOutOfFlowPositioned());
102     auto compute = [&](Optional<LayoutUnit> usedHeight) {
103         return geometry().outOfFlowVerticalGeometry(layoutBox, horizontalConstraints, verticalConstraints, { usedHeight });
104     };
105
106     auto containingBlockHeight = *verticalConstraints.logicalHeight;
107     auto verticalGeometry = compute({ });
108     if (auto maxHeight = geometry().computedMaxHeight(layoutBox, containingBlockHeight)) {
109         auto maxVerticalGeometry = compute(maxHeight);
110         if (verticalGeometry.contentHeightAndMargin.contentHeight > maxVerticalGeometry.contentHeightAndMargin.contentHeight)
111             verticalGeometry = maxVerticalGeometry;
112     }
113
114     if (auto minHeight = geometry().computedMinHeight(layoutBox, containingBlockHeight)) {
115         auto minVerticalGeometry = compute(minHeight);
116         if (verticalGeometry.contentHeightAndMargin.contentHeight < minVerticalGeometry.contentHeightAndMargin.contentHeight)
117             verticalGeometry = minVerticalGeometry;
118     }
119
120     auto& displayBox = formattingState().displayBox(layoutBox);
121     auto nonCollapsedVerticalMargin = verticalGeometry.contentHeightAndMargin.nonCollapsedMargin;
122     displayBox.setTop(verticalGeometry.top + nonCollapsedVerticalMargin.before);
123     displayBox.setContentBoxHeight(verticalGeometry.contentHeightAndMargin.contentHeight);
124     // Margins of absolutely positioned boxes do not collapse
125     displayBox.setVerticalMargin({ nonCollapsedVerticalMargin, { } });
126 }
127
128 void FormattingContext::computeBorderAndPadding(const Box& layoutBox, const HorizontalConstraints& horizontalConstraint)
129 {
130     auto& displayBox = formattingState().displayBox(layoutBox);
131     displayBox.setBorder(geometry().computedBorder(layoutBox));
132     displayBox.setPadding(geometry().computedPadding(layoutBox, horizontalConstraint));
133 }
134
135 void FormattingContext::layoutOutOfFlowContent(InvalidationState& invalidationState, const OutOfFlowHorizontalConstraints& rootHorizontalConstraints, const VerticalConstraints& rootVerticalConstraints)
136 {
137     LOG_WITH_STREAM(FormattingContextLayout, stream << "Start: layout out-of-flow content -> context: " << &layoutState() << " root: " << &root());
138
139     collectOutOfFlowDescendantsIfNeeded();
140
141     auto horizontalConstraintsForLayoutBox = [&] (const auto& outOfFlowBox) {
142         auto& containingBlock = outOfFlowBox.containingBlock();
143         if (&containingBlock == &root())
144             return rootHorizontalConstraints;
145         return Geometry::horizontalConstraintsForOutOfFlow(geometryForBox(containingBlock));
146     };
147
148     auto verticalConstraintsForLayoutBox = [&] (const auto& outOfFlowBox) {
149         auto& containingBlock = outOfFlowBox.containingBlock();
150         if (&containingBlock == &root())
151             return rootVerticalConstraints;
152         return Geometry::verticalConstraintsForOutOfFlow(geometryForBox(containingBlock));
153     };
154
155     for (auto& outOfFlowBox : formattingState().outOfFlowBoxes()) {
156         ASSERT(outOfFlowBox->establishesFormattingContext());
157         if (!invalidationState.needsLayout(*outOfFlowBox))
158             continue;
159
160         auto outOfFlowHorizontalConstraints = horizontalConstraintsForLayoutBox(*outOfFlowBox);
161         auto horizontalConstraintsForBorderAndPadding = HorizontalConstraints { outOfFlowHorizontalConstraints.value.logicalLeft, outOfFlowHorizontalConstraints.borderAndPaddingSpecificWidth };
162         computeBorderAndPadding(*outOfFlowBox, horizontalConstraintsForBorderAndPadding);
163
164         auto horizontalConstraints = outOfFlowHorizontalConstraints.value;
165         auto verticalConstraints = verticalConstraintsForLayoutBox(*outOfFlowBox);
166         computeOutOfFlowHorizontalGeometry(*outOfFlowBox, horizontalConstraints, verticalConstraints);
167         if (is<ContainerBox>(*outOfFlowBox)) {
168             auto& containerBox = downcast<ContainerBox>(*outOfFlowBox);
169             auto& containerDisplayBox = geometryForBox(containerBox);
170
171             if (containerBox.hasInFlowOrFloatingChild()) {
172                 auto horizontalConstraintsForInFlowContent = Geometry::horizontalConstraintsForInFlow(containerDisplayBox);
173                 auto verticalConstraintsForInFlowContent = Geometry::verticalConstraintsForInFlow(containerDisplayBox);
174                 auto formattingContext = LayoutContext::createFormattingContext(containerBox, layoutState());
175                 formattingContext->layoutInFlowContent(invalidationState, horizontalConstraintsForInFlowContent, verticalConstraintsForInFlowContent);
176             }
177             computeOutOfFlowVerticalGeometry(containerBox, horizontalConstraints, verticalConstraints);
178             if (containerBox.hasChild()) {
179                 auto horizontalConstraintsForOutOfFlowContent =  Geometry::horizontalConstraintsForOutOfFlow(containerDisplayBox);
180                 auto verticalConstraintsForOutOfFlowContent = Geometry::verticalConstraintsForOutOfFlow(containerDisplayBox);
181                 auto formattingContext = LayoutContext::createFormattingContext(containerBox, layoutState());
182                 formattingContext->layoutOutOfFlowContent(invalidationState, horizontalConstraintsForOutOfFlowContent, verticalConstraintsForOutOfFlowContent);
183             }
184         } else
185             computeOutOfFlowVerticalGeometry(*outOfFlowBox, horizontalConstraints, verticalConstraints);
186     }
187     LOG_WITH_STREAM(FormattingContextLayout, stream << "End: layout out-of-flow content -> context: " << &layoutState() << " root: " << &root());
188 }
189
190 const Display::Box& FormattingContext::geometryForBox(const Box& layoutBox, Optional<EscapeReason> escapeReason) const
191 {
192     UNUSED_PARAM(escapeReason);
193 #if ASSERT_ENABLED
194     auto isOkToAccessDisplayBox = [&] {
195         if (!layoutBox.isInitialContainingBlock() && &layoutBox.formattingContextRoot() == &root()) {
196             // This is the non-escape case of accessing a box's geometry information within the same formatting context.
197             return true;
198         }
199
200         if (!escapeReason) {
201             // Any geometry access outside of the formatting context without a valid reason is considered an escape.
202             return false;
203         }
204
205         if (*escapeReason == EscapeReason::DocumentBoxStrechesToViewportQuirk) {
206             ASSERT(layoutState().inQuirksMode());
207             return layoutBox.isInitialContainingBlock();
208         }
209
210         if (*escapeReason == EscapeReason::BodyStrechesToViewportQuirk) {
211             ASSERT(layoutState().inQuirksMode());
212             return layoutBox.isInitialContainingBlock() || layoutBox.isDocumentBox();
213
214         }
215
216         if (*escapeReason == EscapeReason::StrokeOverflowNeedsViewportGeometry)
217             return layoutBox.isInitialContainingBlock();
218
219         if (*escapeReason == EscapeReason::NeedsGeometryFromEstablishedFormattingContext) {
220             // This is the case when a formatting root collects geometry information from the established
221             // formatting context to be able to determine width/height.
222             // e.g <div>text content</div>. The <div> is a formatting root of the IFC.
223             // In order to compute the height of the <div>, we need to look inside the IFC and gather geometry information.
224             return &layoutBox.formattingContextRoot().formattingContextRoot() == &root();
225         }
226
227         if (*escapeReason == EscapeReason::OutOfFlowBoxNeedsInFlowGeometry) {
228             // When computing the static position of an out-of-flow box, we need to gather sibling/parent geometry information
229             // as if the out-of-flow box was a simple inflow box.
230             // Now since the out-of-flow and the sibling/parent boxes could very well be in different containing block subtrees
231             // the formatting context they live in could also be very different.
232             return true;
233         }
234
235         if (*escapeReason == EscapeReason::FloatBoxNeedsToBeInAbsoluteCoordinates) {
236             // Float box top/left values are mapped relative to the FloatState's root. Inline formatting contexts(A) inherit floats from parent
237             // block formatting contexts(B). Floats in these inline formatting contexts(A) need to be mapped to the parent, block formatting context(B).
238             auto& formattingContextRoot = layoutBox.formattingContextRoot();
239             return &formattingContextRoot == &root() || &formattingContextRoot == &root().formattingContextRoot();
240         }
241
242         if (*escapeReason == EscapeReason::FindFixedHeightAncestorQuirk) {
243             ASSERT(layoutState().inQuirksMode());
244             // Find the first containing block with fixed height quirk. See Quirks::heightValueOfNearestContainingBlockWithFixedHeight.
245             // This is only to check if the targetFormattingRoot is an ancestor formatting root.
246             if (layoutBox.isInitialContainingBlock())
247                 return true;
248             auto& targetFormattingRoot = layoutBox.formattingContextRoot();
249             auto* ancestorFormattingContextRoot = &root().formattingContextRoot();
250             while (true) {
251                 if (&targetFormattingRoot == ancestorFormattingContextRoot)
252                     return true;
253                 ancestorFormattingContextRoot = &ancestorFormattingContextRoot->formattingContextRoot();
254                 if (ancestorFormattingContextRoot->isInitialContainingBlock())
255                     return true;
256             }
257             return false;
258         }
259
260         if (*escapeReason == EscapeReason::TableNeedsAccessToTableWrapper) {
261             // Tables are wrapped in a 2 level formatting context structure. A <table> element initiates a block formatting context for its principal table box
262             // where the caption and the table content live. It also initiates a table wrapper box which establishes the table formatting context.
263             // In many cases the TFC needs access to the parent (generated) BFC.
264             return &layoutBox == &root().formattingContextRoot();
265         }
266
267         ASSERT_NOT_REACHED();
268         return false;
269     };
270 #endif
271     ASSERT(isOkToAccessDisplayBox());
272     ASSERT(layoutState().hasDisplayBox(layoutBox));
273     return layoutState().displayBoxForLayoutBox(layoutBox);
274 }
275
276 void FormattingContext::collectOutOfFlowDescendantsIfNeeded()
277 {
278     if (!formattingState().outOfFlowBoxes().isEmpty())
279         return;
280     auto& root = this->root();
281     if (!root.hasChild())
282         return;
283     if (!root.isPositioned() && !root.isInitialContainingBlock())
284         return;
285     // Collect the out-of-flow descendants at the formatting root level (as opposed to at the containing block level, though they might be the same).
286     // FIXME: Turn this into a register-self as boxes are being inserted.
287     for (auto& descendant : descendantsOfType<Box>(root)) {
288         if (!descendant.isOutOfFlowPositioned())
289             continue;
290         if (&descendant.formattingContextRoot() != &root)
291             continue;
292         formattingState().addOutOfFlowBox(descendant);
293     }
294 }
295
296 #ifndef NDEBUG
297 void FormattingContext::validateGeometryConstraintsAfterLayout() const
298 {
299     auto& formattingContextRoot = root();
300     // FIXME: add a descendantsOfType<> flavor that stops at nested formatting contexts
301     for (auto& layoutBox : descendantsOfType<Box>(formattingContextRoot)) {
302         if (&layoutBox.formattingContextRoot() != &formattingContextRoot)
303             continue;
304         auto& containingBlockGeometry = geometryForBox(layoutBox.containingBlock());
305         auto& boxGeometry = geometryForBox(layoutBox);
306
307         // 10.3.3 Block-level, non-replaced elements in normal flow
308         // 10.3.7 Absolutely positioned, non-replaced elements
309         if ((layoutBox.isBlockLevelBox() || layoutBox.isOutOfFlowPositioned()) && !layoutBox.isReplacedBox()) {
310             // margin-left + border-left-width + padding-left + width + padding-right + border-right-width + margin-right = width of containing block
311             auto containingBlockWidth = containingBlockGeometry.contentBoxWidth();
312             ASSERT(boxGeometry.horizontalMarginBorderAndPadding() + boxGeometry.contentBoxWidth() == containingBlockWidth);
313         }
314
315         // 10.6.4 Absolutely positioned, non-replaced elements
316         if (layoutBox.isOutOfFlowPositioned() && !layoutBox.isReplacedBox()) {
317             // top + margin-top + border-top-width + padding-top + height + padding-bottom + border-bottom-width + margin-bottom + bottom = height of containing block
318             auto containingBlockHeight = containingBlockGeometry.contentBoxHeight();
319             ASSERT(boxGeometry.top() + boxGeometry.marginBefore() + boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0) + boxGeometry.contentBoxHeight()
320                 + boxGeometry.paddingBottom().valueOr(0) + boxGeometry.borderBottom() + boxGeometry.marginAfter() == containingBlockHeight);
321         }
322     }
323 }
324 #endif
325
326 }
327 }
328 #endif