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 "BlockFormattingContext.h"
28
29#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
30
31#include "BlockFormattingState.h"
32#include "DisplayBox.h"
33#include "FloatingContext.h"
34#include "FloatingState.h"
35#include "LayoutBox.h"
36#include "LayoutContainer.h"
37#include "LayoutState.h"
38#include "Logging.h"
39#include <wtf/IsoMallocInlines.h>
40#include <wtf/text/TextStream.h>
41
42namespace WebCore {
43namespace Layout {
44
45WTF_MAKE_ISO_ALLOCATED_IMPL(BlockFormattingContext);
46
47BlockFormattingContext::BlockFormattingContext(const Box& formattingContextRoot, BlockFormattingState& formattingState)
48 : FormattingContext(formattingContextRoot, formattingState)
49{
50}
51
52void BlockFormattingContext::layout() const
53{
54 // 9.4.1 Block formatting contexts
55 // In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.
56 // The vertical distance between two sibling boxes is determined by the 'margin' properties.
57 // Vertical margins between adjacent block-level boxes in a block formatting context collapse.
58 if (!is<Container>(root()))
59 return;
60
61 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> block formatting context -> formatting root(" << &root() << ")");
62
63 auto& formattingRoot = downcast<Container>(root());
64 LayoutQueue layoutQueue;
65 FloatingContext floatingContext(formattingState().floatingState());
66 // This is a post-order tree traversal layout.
67 // The root container layout is done in the formatting context it lives in, not that one it creates, so let's start with the first child.
68 if (auto* firstChild = formattingRoot.firstInFlowOrFloatingChild())
69 layoutQueue.append(firstChild);
70 // 1. Go all the way down to the leaf node
71 // 2. Compute static position and width as we traverse down
72 // 3. As we climb back on the tree, compute height and finialize position
73 // (Any subtrees with new formatting contexts need to layout synchronously)
74 while (!layoutQueue.isEmpty()) {
75 // Traverse down on the descendants and compute width/static position until we find a leaf node.
76 while (true) {
77 auto& layoutBox = *layoutQueue.last();
78
79 if (layoutBox.establishesFormattingContext()) {
80 layoutFormattingContextRoot(floatingContext, layoutBox);
81 layoutQueue.removeLast();
82 // Since this box is a formatting context root, it takes care of its entire subtree.
83 // Continue with next sibling if exists.
84 if (!layoutBox.nextInFlowOrFloatingSibling())
85 break;
86 layoutQueue.append(layoutBox.nextInFlowOrFloatingSibling());
87 continue;
88 }
89
90 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Compute] -> [Position][Border][Padding][Width][Margin] -> for layoutBox(" << &layoutBox << ")");
91 computeBorderAndPadding(layoutBox);
92 computeWidthAndMargin(layoutBox);
93 computeStaticPosition(floatingContext, layoutBox);
94 if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
95 break;
96 layoutQueue.append(downcast<Container>(layoutBox).firstInFlowOrFloatingChild());
97 }
98
99 // Climb back on the ancestors and compute height/final position.
100 while (!layoutQueue.isEmpty()) {
101 // All inflow descendants (if there are any) are laid out by now. Let's compute the box's height.
102 auto& layoutBox = *layoutQueue.takeLast();
103
104 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Compute] -> [Height][Margin] -> for layoutBox(" << &layoutBox << ")");
105 // Formatting root boxes are special-cased and they don't come here.
106 ASSERT(!layoutBox.establishesFormattingContext());
107 computeHeightAndMargin(layoutBox);
108 // Move in-flow positioned children to their final position.
109 placeInFlowPositionedChildren(layoutBox);
110 if (auto* nextSibling = layoutBox.nextInFlowOrFloatingSibling()) {
111 layoutQueue.append(nextSibling);
112 break;
113 }
114 }
115 }
116 // Place the inflow positioned children.
117 placeInFlowPositionedChildren(formattingRoot);
118 LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> block formatting context -> formatting root(" << &root() << ")");
119}
120
121void BlockFormattingContext::layoutFormattingContextRoot(FloatingContext& floatingContext, const Box& layoutBox) const
122{
123 // Start laying out this formatting root in the formatting contenxt it lives in.
124 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Compute] -> [Position][Border][Padding][Width][Margin] -> for layoutBox(" << &layoutBox << ")");
125 computeBorderAndPadding(layoutBox);
126 computeWidthAndMargin(layoutBox);
127 computeStaticPosition(floatingContext, layoutBox);
128 // Swich over to the new formatting context (the one that the root creates).
129 auto formattingContext = layoutState().createFormattingContext(layoutBox);
130 formattingContext->layout();
131
132 // Come back and finalize the root's geometry.
133 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Compute] -> [Height][Margin] -> for layoutBox(" << &layoutBox << ")");
134 computeHeightAndMargin(layoutBox);
135
136 // Float related final positioning.
137 if (layoutBox.isFloatingPositioned()) {
138 computeFloatingPosition(floatingContext, layoutBox);
139 floatingContext.floatingState().append(layoutBox);
140 } else if (layoutBox.establishesBlockFormattingContext())
141 computePositionToAvoidFloats(floatingContext, layoutBox);
142
143 // Now that we computed the root's height, we can go back and layout the out-of-flow descedants (if any).
144 formattingContext->layoutOutOfFlowDescendants(layoutBox);
145}
146
147void BlockFormattingContext::placeInFlowPositionedChildren(const Box& layoutBox) const
148{
149 if (!is<Container>(layoutBox))
150 return;
151
152 LOG_WITH_STREAM(FormattingContextLayout, stream << "Start: move in-flow positioned children -> parent: " << &layoutBox);
153 auto& container = downcast<Container>(layoutBox);
154 for (auto& childBox : childrenOfType<Box>(container)) {
155 if (!childBox.isInFlowPositioned())
156 continue;
157
158 auto computeInFlowPositionedPosition = [&] {
159 auto& layoutState = this->layoutState();
160 auto positionOffset = Geometry::inFlowPositionedPositionOffset(layoutState, childBox);
161
162 auto& displayBox = layoutState.displayBoxForLayoutBox(childBox);
163 auto topLeft = displayBox.topLeft();
164
165 topLeft.move(positionOffset);
166
167 displayBox.setTopLeft(topLeft);
168 };
169
170 computeInFlowPositionedPosition();
171 }
172 LOG_WITH_STREAM(FormattingContextLayout, stream << "End: move in-flow positioned children -> parent: " << &layoutBox);
173}
174
175void BlockFormattingContext::computeStaticPosition(const FloatingContext& floatingContext, const Box& layoutBox) const
176{
177 auto& layoutState = this->layoutState();
178 layoutState.displayBoxForLayoutBox(layoutBox).setTopLeft(Geometry::staticPosition(layoutState, layoutBox));
179 if (layoutBox.hasFloatClear())
180 computeEstimatedVerticalPositionForFloatClear(floatingContext, layoutBox);
181 else if (layoutBox.establishesFormattingContext())
182 computeEstimatedVerticalPositionForFormattingRoot(layoutBox);
183}
184
185void BlockFormattingContext::computeEstimatedVerticalPosition(const Box& layoutBox) const
186{
187 auto& layoutState = this->layoutState();
188 auto estimatedMarginBefore = MarginCollapse::estimatedMarginBefore(layoutState, layoutBox);
189 setEstimatedMarginBefore(layoutBox, estimatedMarginBefore);
190
191 auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
192 auto nonCollapsedValues = UsedVerticalMargin::NonCollapsedValues { estimatedMarginBefore.nonCollapsedValue, { } };
193 auto collapsedValues = UsedVerticalMargin::CollapsedValues { estimatedMarginBefore.collapsedValue, { }, estimatedMarginBefore.isCollapsedThrough };
194 auto verticalMargin = UsedVerticalMargin { nonCollapsedValues, collapsedValues };
195 displayBox.setVerticalMargin(verticalMargin);
196 displayBox.setTop(verticalPositionWithMargin(layoutBox, verticalMargin));
197#if !ASSERT_DISABLED
198 displayBox.setHasEstimatedMarginBefore();
199#endif
200}
201
202void BlockFormattingContext::computeEstimatedVerticalPositionForAncestors(const Box& layoutBox) const
203{
204 // We only need to estimate margin top for float related layout (formatting context roots avoid floats).
205 ASSERT(layoutBox.isFloatingPositioned() || layoutBox.hasFloatClear() || layoutBox.establishesBlockFormattingContext() || layoutBox.establishesInlineFormattingContext());
206
207 // In order to figure out whether a box should avoid a float, we need to know the final positions of both (ignore relative positioning for now).
208 // In block formatting context the final position for a normal flow box includes
209 // 1. the static position and
210 // 2. the corresponding (non)collapsed margins.
211 // Now the vertical margins are computed when all the descendants are finalized, because the margin values might be depending on the height of the box
212 // (and the height might be based on the content).
213 // So when we get to the point where we intersect the box with the float to decide if the box needs to move, we don't yet have the final vertical position.
214 //
215 // The idea here is that as long as we don't cross the block formatting context boundary, we should be able to pre-compute the final top position.
216 for (auto* ancestor = layoutBox.containingBlock(); ancestor && !ancestor->establishesBlockFormattingContext(); ancestor = ancestor->containingBlock()) {
217 // FIXME: with incremental layout, we might actually have a valid (non-estimated) margin top as well.
218 if (hasEstimatedMarginBefore(*ancestor))
219 return;
220 computeEstimatedVerticalPosition(*ancestor);
221 }
222}
223
224void BlockFormattingContext::computeEstimatedVerticalPositionForFormattingRoot(const Box& layoutBox) const
225{
226 ASSERT(layoutBox.establishesFormattingContext());
227 ASSERT(!layoutBox.hasFloatClear());
228
229 auto avoidsFloats = layoutBox.isFloatingPositioned() || layoutBox.establishesBlockFormattingContext();
230 if (avoidsFloats)
231 computeEstimatedVerticalPositionForAncestors(layoutBox);
232
233 // If the inline formatting root is also the root for the floats (happens when the root box also establishes a block formatting context)
234 // the floats are in the coordinate system of this root. No need to find the final vertical position.
235 auto inlineContextInheritsFloats = layoutBox.establishesInlineFormattingContext() && !layoutBox.establishesBlockFormattingContext();
236 if (inlineContextInheritsFloats) {
237 computeEstimatedVerticalPosition(layoutBox);
238 computeEstimatedVerticalPositionForAncestors(layoutBox);
239 }
240}
241
242void BlockFormattingContext::computeEstimatedVerticalPositionForFloatClear(const FloatingContext& floatingContext, const Box& layoutBox) const
243{
244 ASSERT(layoutBox.hasFloatClear());
245 if (floatingContext.floatingState().isEmpty())
246 return;
247 // The static position with clear requires margin esitmation to see if clearance is needed.
248 computeEstimatedVerticalPosition(layoutBox);
249 computeEstimatedVerticalPositionForAncestors(layoutBox);
250 auto verticalPositionAndClearance = floatingContext.verticalPositionWithClearance(layoutBox);
251 if (!verticalPositionAndClearance.position) {
252 ASSERT(!verticalPositionAndClearance.clearance);
253 return;
254 }
255
256 auto& displayBox = layoutState().displayBoxForLayoutBox(layoutBox);
257 ASSERT(*verticalPositionAndClearance.position >= displayBox.top());
258 displayBox.setTop(*verticalPositionAndClearance.position);
259 if (verticalPositionAndClearance.clearance)
260 displayBox.setHasClearance();
261}
262
263#ifndef NDEBUG
264bool BlockFormattingContext::hasPrecomputedMarginBefore(const Box& layoutBox) const
265{
266 for (auto* ancestor = layoutBox.containingBlock(); ancestor && !ancestor->establishesBlockFormattingContext(); ancestor = ancestor->containingBlock()) {
267 if (hasEstimatedMarginBefore(*ancestor))
268 continue;
269 return false;
270 }
271 return true;
272}
273#endif
274
275void BlockFormattingContext::computeFloatingPosition(const FloatingContext& floatingContext, const Box& layoutBox) const
276{
277 ASSERT(layoutBox.isFloatingPositioned());
278 ASSERT(hasPrecomputedMarginBefore(layoutBox));
279 layoutState().displayBoxForLayoutBox(layoutBox).setTopLeft(floatingContext.positionForFloat(layoutBox));
280}
281
282void BlockFormattingContext::computePositionToAvoidFloats(const FloatingContext& floatingContext, const Box& layoutBox) const
283{
284 auto& layoutState = this->layoutState();
285 // Formatting context roots avoid floats.
286 ASSERT(layoutBox.establishesBlockFormattingContext());
287 ASSERT(!layoutBox.isFloatingPositioned());
288 ASSERT(!layoutBox.hasFloatClear());
289 ASSERT(hasPrecomputedMarginBefore(layoutBox));
290
291 if (floatingContext.floatingState().isEmpty())
292 return;
293
294 if (auto adjustedPosition = floatingContext.positionForFormattingContextRoot(layoutBox))
295 layoutState.displayBoxForLayoutBox(layoutBox).setTopLeft(*adjustedPosition);
296}
297
298void BlockFormattingContext::computeWidthAndMargin(const Box& layoutBox) const
299{
300 auto& layoutState = this->layoutState();
301 auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
302
303 auto compute = [&](Optional<LayoutUnit> usedWidth) -> WidthAndMargin {
304 auto usedValues = UsedHorizontalValues { containingBlockWidth, usedWidth, { } };
305 if (layoutBox.isInFlow())
306 return Geometry::inFlowWidthAndMargin(layoutState, layoutBox, usedValues);
307
308 if (layoutBox.isFloatingPositioned())
309 return Geometry::floatingWidthAndMargin(layoutState, layoutBox, usedValues);
310
311 ASSERT_NOT_REACHED();
312 return { };
313 };
314
315 auto widthAndMargin = compute({ });
316
317 if (auto maxWidth = Geometry::computedValueIfNotAuto(layoutBox.style().logicalMaxWidth(), containingBlockWidth)) {
318 auto maxWidthAndMargin = compute(maxWidth);
319 if (widthAndMargin.width > maxWidthAndMargin.width)
320 widthAndMargin = maxWidthAndMargin;
321 }
322
323 auto minWidth = Geometry::computedValueIfNotAuto(layoutBox.style().logicalMinWidth(), containingBlockWidth).valueOr(0);
324 auto minWidthAndMargin = compute(minWidth);
325 if (widthAndMargin.width < minWidthAndMargin.width)
326 widthAndMargin = minWidthAndMargin;
327
328 auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
329 displayBox.setContentBoxWidth(widthAndMargin.width);
330 displayBox.setHorizontalMargin(widthAndMargin.usedMargin);
331 displayBox.setHorizontalComputedMargin(widthAndMargin.computedMargin);
332}
333
334void BlockFormattingContext::computeHeightAndMargin(const Box& layoutBox) const
335{
336 auto& layoutState = this->layoutState();
337
338 auto compute = [&](UsedVerticalValues usedValues) -> HeightAndMargin {
339
340 if (layoutBox.isInFlow())
341 return Geometry::inFlowHeightAndMargin(layoutState, layoutBox, usedValues);
342
343 if (layoutBox.isFloatingPositioned())
344 return Geometry::floatingHeightAndMargin(layoutState, layoutBox, usedValues, UsedHorizontalValues { layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth() });
345
346 ASSERT_NOT_REACHED();
347 return { };
348 };
349
350 auto heightAndMargin = compute({ });
351 if (auto maxHeight = Geometry::computedMaxHeight(layoutState, layoutBox)) {
352 if (heightAndMargin.height > *maxHeight) {
353 auto maxHeightAndMargin = compute({ *maxHeight });
354 // Used height should remain the same.
355 ASSERT((layoutState.inQuirksMode() && (layoutBox.isBodyBox() || layoutBox.isDocumentBox())) || maxHeightAndMargin.height == *maxHeight);
356 heightAndMargin = { *maxHeight, maxHeightAndMargin.nonCollapsedMargin };
357 }
358 }
359
360 if (auto minHeight = Geometry::computedMinHeight(layoutState, layoutBox)) {
361 if (heightAndMargin.height < *minHeight) {
362 auto minHeightAndMargin = compute({ *minHeight });
363 // Used height should remain the same.
364 ASSERT((layoutState.inQuirksMode() && (layoutBox.isBodyBox() || layoutBox.isDocumentBox())) || minHeightAndMargin.height == *minHeight);
365 heightAndMargin = { *minHeight, minHeightAndMargin.nonCollapsedMargin };
366 }
367 }
368
369 // 1. Compute collapsed margins.
370 // 2. Adjust vertical position using the collapsed values
371 // 3. Adjust previous in-flow sibling margin after using this margin.
372 auto collapsedMargin = MarginCollapse::collapsedVerticalValues(layoutState, layoutBox, heightAndMargin.nonCollapsedMargin);
373 auto verticalMargin = UsedVerticalMargin { heightAndMargin.nonCollapsedMargin, collapsedMargin };
374 auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
375
376 // Out of flow boxes don't need vertical adjustment after margin collapsing.
377 if (layoutBox.isOutOfFlowPositioned()) {
378 ASSERT(!hasEstimatedMarginBefore(layoutBox));
379 displayBox.setContentBoxHeight(heightAndMargin.height);
380 displayBox.setVerticalMargin(verticalMargin);
381 return;
382 }
383
384 ASSERT(!hasEstimatedMarginBefore(layoutBox) || estimatedMarginBefore(layoutBox).usedValue() == verticalMargin.before());
385 removeEstimatedMarginBefore(layoutBox);
386 displayBox.setTop(verticalPositionWithMargin(layoutBox, verticalMargin));
387 displayBox.setContentBoxHeight(heightAndMargin.height);
388 displayBox.setVerticalMargin(verticalMargin);
389
390 MarginCollapse::updatePositiveNegativeMarginValues(layoutState, layoutBox);
391 // Adjust the previous sibling's margin bottom now that this box's vertical margin is computed.
392 MarginCollapse::updateMarginAfterForPreviousSibling(layoutState, layoutBox);
393}
394
395void BlockFormattingContext::computeIntrinsicWidthConstraints() const
396{
397 auto& layoutState = this->layoutState();
398 auto& formattingRoot = root();
399 auto& formattingStateForRoot = layoutState.formattingStateForBox(formattingRoot);
400 ASSERT(!formattingStateForRoot.intrinsicWidthConstraints(formattingRoot));
401
402 // Can we just compute them without checking the children?
403 if (!Geometry::intrinsicWidthConstraintsNeedChildrenWidth(formattingRoot))
404 return formattingStateForRoot.setIntrinsicWidthConstraints(formattingRoot, Geometry::intrinsicWidthConstraints(layoutState, formattingRoot));
405
406 // Visit the in-flow descendants and compute their min/max intrinsic width if needed.
407 // 1. Go all the way down to the leaf node
408 // 2. Check if actually need to visit all the boxes as we traverse down (already computed, container's min/max does not depend on descendants etc)
409 // 3. As we climb back on the tree, compute min/max intrinsic width
410 // (Any subtrees with new formatting contexts need to layout synchronously)
411 Vector<const Box*> queue;
412 ASSERT(is<Container>(formattingRoot));
413 if (auto* firstChild = downcast<Container>(formattingRoot).firstInFlowOrFloatingChild())
414 queue.append(firstChild);
415
416 auto& formattingState = this->formattingState();
417 while (!queue.isEmpty()) {
418 while (true) {
419 auto& childBox = *queue.last();
420 auto childIntrinsicWidthConstraints = formattingState.intrinsicWidthConstraints(childBox);
421 auto skipDescendants = childIntrinsicWidthConstraints || !Geometry::intrinsicWidthConstraintsNeedChildrenWidth(childBox) || childBox.establishesFormattingContext();
422
423 if (skipDescendants) {
424 if (!childIntrinsicWidthConstraints) {
425 if (!Geometry::intrinsicWidthConstraintsNeedChildrenWidth(childBox))
426 formattingState.setIntrinsicWidthConstraints(childBox, Geometry::intrinsicWidthConstraints(layoutState, childBox));
427 else if (childBox.establishesFormattingContext())
428 layoutState.createFormattingContext(childBox)->computeIntrinsicWidthConstraints();
429 else
430 ASSERT_NOT_REACHED();
431 }
432 queue.removeLast();
433 if (!childBox.nextInFlowOrFloatingSibling())
434 break;
435 queue.append(childBox.nextInFlowOrFloatingSibling());
436 // Skip descendants
437 continue;
438 }
439 }
440
441 // Compute min/max intrinsic width bottom up.
442 while (!queue.isEmpty()) {
443 auto& childBox = *queue.takeLast();
444 formattingState.setIntrinsicWidthConstraints(childBox, Geometry::intrinsicWidthConstraints(layoutState, childBox));
445 // Move over to the next sibling or take the next box in the queue.
446 if (!is<Container>(childBox) || !downcast<Container>(childBox).nextInFlowOrFloatingSibling())
447 continue;
448 queue.append(downcast<Container>(childBox).nextInFlowOrFloatingSibling());
449 }
450 }
451 formattingStateForRoot.setIntrinsicWidthConstraints(formattingRoot, Geometry::intrinsicWidthConstraints(layoutState, formattingRoot));
452}
453
454LayoutUnit BlockFormattingContext::verticalPositionWithMargin(const Box& layoutBox, const UsedVerticalMargin& verticalMargin) const
455{
456 ASSERT(!layoutBox.isOutOfFlowPositioned());
457 // Now that we've computed the final margin before, let's shift the box's vertical position if needed.
458 // 1. Check if the box has clearance. If so, we've already precomputed/finalized the top value and vertical margin does not impact it anymore.
459 // 2. Check if the margin before collapses with the previous box's margin after. if not -> return previous box's bottom including margin after + marginBefore
460 // 3. Check if the previous box's margins collapse through. If not -> return previous box' bottom excluding margin after + marginBefore (they are supposed to be equal)
461 // 4. Go to previous box and start from step #1 until we hit the parent box.
462 auto& layoutState = this->layoutState();
463 auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
464 if (displayBox.hasClearance())
465 return displayBox.top();
466
467 auto* currentLayoutBox = &layoutBox;
468 while (currentLayoutBox) {
469 if (!currentLayoutBox->previousInFlowSibling())
470 break;
471 auto& previousInFlowSibling = *currentLayoutBox->previousInFlowSibling();
472 if (!MarginCollapse::marginBeforeCollapsesWithPreviousSiblingMarginAfter(layoutState, *currentLayoutBox)) {
473 auto& previousDisplayBox = layoutState.displayBoxForLayoutBox(previousInFlowSibling);
474 return previousDisplayBox.rectWithMargin().bottom() + verticalMargin.before();
475 }
476
477 if (!MarginCollapse::marginsCollapseThrough(layoutState, previousInFlowSibling)) {
478 auto& previousDisplayBox = layoutState.displayBoxForLayoutBox(previousInFlowSibling);
479 return previousDisplayBox.bottom() + verticalMargin.before();
480 }
481 currentLayoutBox = &previousInFlowSibling;
482 }
483
484 auto& containingBlock = *layoutBox.containingBlock();
485 auto containingBlockContentBoxTop = layoutState.displayBoxForLayoutBox(containingBlock).contentBoxTop();
486 // Adjust vertical position depending whether this box directly or indirectly adjoins with its parent.
487 auto directlyAdjoinsParent = !layoutBox.previousInFlowSibling();
488 if (directlyAdjoinsParent) {
489 // If the top and bottom margins of a box are adjoining, then it is possible for margins to collapse through it.
490 // In this case, the position of the element depends on its relationship with the other elements whose margins are being collapsed.
491 if (verticalMargin.collapsedValues().isCollapsedThrough) {
492 // If the element's margins are collapsed with its parent's top margin, the top border edge of the box is defined to be the same as the parent's.
493 if (MarginCollapse::marginBeforeCollapsesWithParentMarginBefore(layoutState, layoutBox))
494 return containingBlockContentBoxTop;
495 // Otherwise, either the element's parent is not taking part in the margin collapsing, or only the parent's bottom margin is involved.
496 // The position of the element's top border edge is the same as it would have been if the element had a non-zero bottom border.
497 auto beforeMarginWithBottomBorder = MarginCollapse::marginBeforeIgnoringCollapsingThrough(layoutState, layoutBox, verticalMargin.nonCollapsedValues());
498 return containingBlockContentBoxTop + beforeMarginWithBottomBorder;
499 }
500 // Non-collapsed through box vertical position depending whether the margin collapses.
501 if (MarginCollapse::marginBeforeCollapsesWithParentMarginBefore(layoutState, layoutBox))
502 return containingBlockContentBoxTop;
503
504 return containingBlockContentBoxTop + verticalMargin.before();
505 }
506 // At this point this box indirectly (via collapsed through previous in-flow siblings) adjoins the parent. Let's check if it margin collapses with the parent.
507 ASSERT(containingBlock.firstInFlowChild());
508 ASSERT(containingBlock.firstInFlowChild() != &layoutBox);
509 if (MarginCollapse::marginBeforeCollapsesWithParentMarginBefore(layoutState, *containingBlock.firstInFlowChild()))
510 return containingBlockContentBoxTop;
511
512 return containingBlockContentBoxTop + verticalMargin.before();
513}
514
515void BlockFormattingContext::setEstimatedMarginBefore(const Box& layoutBox, const EstimatedMarginBefore& estimatedMarginBefore) const
516{
517 // Can't cross formatting context boundary.
518 ASSERT(&layoutState().formattingStateForBox(layoutBox) == &formattingState());
519 m_estimatedMarginBeforeList.set(&layoutBox, estimatedMarginBefore);
520}
521
522bool BlockFormattingContext::hasEstimatedMarginBefore(const Box& layoutBox) const
523{
524 // Can't cross formatting context boundary.
525 ASSERT(&layoutState().formattingStateForBox(layoutBox) == &formattingState());
526 return m_estimatedMarginBeforeList.contains(&layoutBox);
527}
528
529}
530}
531
532#endif
533