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 "InlineFormattingState.h"
32#include "LayoutBox.h"
33#include "LayoutContainer.h"
34#include "LayoutUnit.h"
35#include "RenderStyle.h"
36
37namespace WebCore {
38namespace Layout {
39
40static bool hasBorder(const BorderValue& borderValue)
41{
42 if (borderValue.style() == BorderStyle::None || borderValue.style() == BorderStyle::Hidden)
43 return false;
44 return !!borderValue.width();
45}
46
47static bool hasPadding(const Length& paddingValue)
48{
49 // FIXME: Check if percent value needs to be resolved.
50 return !paddingValue.isZero();
51}
52
53static bool hasBorderBefore(const Box& layoutBox)
54{
55 return hasBorder(layoutBox.style().borderBefore());
56}
57
58static bool hasBorderAfter(const Box& layoutBox)
59{
60 return hasBorder(layoutBox.style().borderAfter());
61}
62
63static bool hasPaddingBefore(const Box& layoutBox)
64{
65 return hasPadding(layoutBox.style().paddingBefore());
66}
67
68static bool hasPaddingAfter(const Box& layoutBox)
69{
70 return hasPadding(layoutBox.style().paddingAfter());
71}
72
73static bool hasClearance(const LayoutState& layoutState, const Box& layoutBox)
74{
75 if (!layoutBox.hasFloatClear())
76 return false;
77 return layoutState.displayBoxForLayoutBox(layoutBox).hasClearance();
78}
79
80static bool establishesBlockFormattingContext(const Box& layoutBox)
81{
82 // WebKit treats the document element renderer as a block formatting context root. It probably only impacts margin collapsing, so let's not do
83 // a layout wide quirk on this for now.
84 if (layoutBox.isDocumentBox())
85 return true;
86 return layoutBox.establishesBlockFormattingContext();
87}
88
89bool BlockFormattingContext::MarginCollapse::marginBeforeCollapsesWithParentMarginAfter(const LayoutState& layoutState, const Box& layoutBox)
90{
91 // 1. This is the last in-flow child and its margins collapse through and the margin after collapses with parent's margin after or
92 // 2. This box's margin after collapses with the next sibling's margin before and that sibling collapses through and
93 // we can get to the last in-flow child like that.
94 auto* lastInFlowChild = layoutBox.parent()->lastInFlowChild();
95 for (auto* currentBox = &layoutBox; currentBox; currentBox = currentBox->nextInFlowSibling()) {
96 if (!marginsCollapseThrough(layoutState, *currentBox))
97 return false;
98 if (currentBox == lastInFlowChild)
99 return marginAfterCollapsesWithParentMarginAfter(layoutState, *currentBox);
100 if (!marginAfterCollapsesWithNextSiblingMarginBefore(layoutState, *currentBox))
101 return false;
102 }
103 ASSERT_NOT_REACHED();
104 return false;
105}
106
107bool BlockFormattingContext::MarginCollapse::marginBeforeCollapsesWithParentMarginBefore(const LayoutState& layoutState, const Box& layoutBox)
108{
109 // The first inflow child could propagate its top margin to parent.
110 // https://www.w3.org/TR/CSS21/box.html#collapsing-margins
111 if (layoutBox.isAnonymous())
112 return false;
113
114 ASSERT(layoutBox.isBlockLevelBox());
115
116 // Margins between a floated box and any other box do not collapse.
117 if (layoutBox.isFloatingPositioned())
118 return false;
119
120 // Margins of absolutely positioned boxes do not collapse.
121 if (layoutBox.isOutOfFlowPositioned())
122 return false;
123
124 // Margins of inline-block boxes do not collapse.
125 if (layoutBox.isInlineBlockBox())
126 return false;
127
128 // Only the first inlflow child collapses with parent.
129 if (layoutBox.previousInFlowSibling())
130 return false;
131
132 auto& parent = *layoutBox.parent();
133 // Margins of elements that establish new block formatting contexts do not collapse with their in-flow children
134 if (establishesBlockFormattingContext(parent))
135 return false;
136
137 if (hasBorderBefore(parent))
138 return false;
139
140 if (hasPaddingBefore(parent))
141 return false;
142
143 // ...and the child has no clearance.
144 if (hasClearance(layoutState, layoutBox))
145 return false;
146
147 return true;
148}
149
150bool BlockFormattingContext::MarginCollapse::marginBeforeCollapsesWithPreviousSiblingMarginAfter(const LayoutState& layoutState, const Box& layoutBox)
151{
152 ASSERT(layoutBox.isBlockLevelBox());
153
154 if (layoutBox.isAnonymous())
155 return false;
156
157 if (!layoutBox.previousInFlowSibling())
158 return false;
159
160 auto& previousInFlowSibling = *layoutBox.previousInFlowSibling();
161 // Margins between a floated box and any other box do not collapse.
162 if (layoutBox.isFloatingPositioned() || previousInFlowSibling.isFloatingPositioned())
163 return false;
164
165 // Margins of absolutely positioned boxes do not collapse.
166 if ((layoutBox.isOutOfFlowPositioned() && !layoutBox.style().top().isAuto())
167 || (previousInFlowSibling.isOutOfFlowPositioned() && !previousInFlowSibling.style().bottom().isAuto()))
168 return false;
169
170 // Margins of inline-block boxes do not collapse.
171 if (layoutBox.isInlineBlockBox() || previousInFlowSibling.isInlineBlockBox())
172 return false;
173
174 // The bottom margin of an in-flow block-level element always collapses with the top margin of
175 // its next in-flow block-level sibling, unless that sibling has clearance.
176 if (hasClearance(layoutState, layoutBox))
177 return false;
178
179 return true;
180}
181
182bool BlockFormattingContext::MarginCollapse::marginBeforeCollapsesWithFirstInFlowChildMarginBefore(const LayoutState& layoutState, const Box& layoutBox)
183{
184 if (layoutBox.isAnonymous())
185 return false;
186
187 ASSERT(layoutBox.isBlockLevelBox());
188 // Margins of elements that establish new block formatting contexts do not collapse with their in-flow children.
189 if (establishesBlockFormattingContext(layoutBox))
190 return false;
191
192 // The top margin of an in-flow block element collapses with its first in-flow block-level
193 // child's top margin if the element has no top border...
194 if (hasBorderBefore(layoutBox))
195 return false;
196
197 // ...no top padding
198 if (hasPaddingBefore(layoutBox))
199 return false;
200
201 if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
202 return false;
203
204 auto& firstInFlowChild = *downcast<Container>(layoutBox).firstInFlowChild();
205 if (!firstInFlowChild.isBlockLevelBox())
206 return false;
207
208 // ...and the child has no clearance.
209 if (hasClearance(layoutState, firstInFlowChild))
210 return false;
211
212 // Margins between a floated box and any other box do not collapse.
213 if (firstInFlowChild.isFloatingPositioned())
214 return false;
215
216 // Margins of absolutely positioned boxes do not collapse.
217 if (firstInFlowChild.isOutOfFlowPositioned())
218 return false;
219
220 // Margins of inline-block boxes do not collapse.
221 if (firstInFlowChild.isInlineBlockBox())
222 return false;
223
224 return true;
225}
226
227bool BlockFormattingContext::MarginCollapse::marginAfterCollapsesWithSiblingMarginBeforeWithClearance(const LayoutState& layoutState, const Box& layoutBox)
228{
229 // If the top and bottom margins of an element with clearance are adjoining, its margins collapse with the adjoining margins
230 // of following siblings but that resulting margin does not collapse with the bottom margin of the parent block.
231 if (!marginsCollapseThrough(layoutState, layoutBox))
232 return false;
233
234 for (auto* previousSibling = layoutBox.previousInFlowSibling(); previousSibling; previousSibling = previousSibling->previousInFlowSibling()) {
235 if (!marginsCollapseThrough(layoutState, *previousSibling))
236 return false;
237 if (hasClearance(layoutState, *previousSibling))
238 return true;
239 }
240 return false;
241}
242
243bool BlockFormattingContext::MarginCollapse::marginAfterCollapsesWithParentMarginBefore(const LayoutState& layoutState, const Box& layoutBox)
244{
245 // 1. This is the first in-flow child and its margins collapse through and the margin before collapses with parent's margin before or
246 // 2. This box's margin before collapses with the previous sibling's margin after and that sibling collapses through and
247 // we can get to the first in-flow child like that.
248 auto* firstInFlowChild = layoutBox.parent()->firstInFlowChild();
249 for (auto* currentBox = &layoutBox; currentBox; currentBox = currentBox->previousInFlowSibling()) {
250 if (!marginsCollapseThrough(layoutState, *currentBox))
251 return false;
252 if (currentBox == firstInFlowChild)
253 return marginBeforeCollapsesWithParentMarginBefore(layoutState, *currentBox);
254 if (!marginBeforeCollapsesWithPreviousSiblingMarginAfter(layoutState, *currentBox))
255 return false;
256 }
257 ASSERT_NOT_REACHED();
258 return false;
259}
260
261bool BlockFormattingContext::MarginCollapse::marginAfterCollapsesWithParentMarginAfter(const LayoutState& layoutState, const Box& layoutBox)
262{
263 if (layoutBox.isAnonymous())
264 return false;
265
266 ASSERT(layoutBox.isBlockLevelBox());
267
268 // Margins between a floated box and any other box do not collapse.
269 if (layoutBox.isFloatingPositioned())
270 return false;
271
272 // Margins of absolutely positioned boxes do not collapse.
273 if (layoutBox.isOutOfFlowPositioned())
274 return false;
275
276 // Margins of inline-block boxes do not collapse.
277 if (layoutBox.isInlineBlockBox())
278 return false;
279
280 // Only the last inlflow child collapses with parent.
281 if (layoutBox.nextInFlowSibling())
282 return false;
283
284 auto& parent = *layoutBox.parent();
285 // Margins of elements that establish new block formatting contexts do not collapse with their in-flow children.
286 if (establishesBlockFormattingContext(parent))
287 return false;
288
289 // The bottom margin of an in-flow block box with a 'height' of 'auto' collapses with its last in-flow block-level child's bottom margin, if:
290 if (!parent.style().height().isAuto())
291 return false;
292
293 // the box has no bottom padding, and
294 if (hasPaddingAfter(parent))
295 return false;
296
297 // the box has no bottom border, and
298 if (hasBorderAfter(parent))
299 return false;
300
301 // the child's bottom margin neither collapses with a top margin that has clearance...
302 if (marginAfterCollapsesWithSiblingMarginBeforeWithClearance(layoutState, layoutBox))
303 return false;
304
305 // nor (if the box's min-height is non-zero) with the box's top margin.
306 auto computedMinHeight = parent.style().logicalMinHeight();
307 if (!computedMinHeight.isAuto() && computedMinHeight.value() && marginAfterCollapsesWithParentMarginBefore(layoutState, layoutBox))
308 return false;
309
310 return true;
311}
312
313bool BlockFormattingContext::MarginCollapse::marginAfterCollapsesWithLastInFlowChildMarginAfter(const LayoutState& layoutState, const Box& layoutBox)
314{
315 ASSERT(layoutBox.isBlockLevelBox());
316
317 // Margins of elements that establish new block formatting contexts do not collapse with their in-flow children.
318 if (establishesBlockFormattingContext(layoutBox))
319 return false;
320
321 if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
322 return false;
323
324 auto& lastInFlowChild = *downcast<Container>(layoutBox).lastInFlowChild();
325 if (!lastInFlowChild.isBlockLevelBox())
326 return false;
327
328 // The bottom margin of an in-flow block box with a 'height' of 'auto' collapses with its last in-flow block-level child's bottom margin, if:
329 if (!layoutBox.style().height().isAuto())
330 return false;
331
332 // the box has no bottom padding, and
333 if (hasPaddingAfter(layoutBox))
334 return false;
335
336 // the box has no bottom border, and
337 if (hasBorderAfter(layoutBox))
338 return false;
339
340 // the child's bottom margin neither collapses with a top margin that has clearance...
341 if (marginAfterCollapsesWithSiblingMarginBeforeWithClearance(layoutState, lastInFlowChild))
342 return false;
343
344 // nor (if the box's min-height is non-zero) with the box's top margin.
345 auto computedMinHeight = layoutBox.style().logicalMinHeight();
346 if (!computedMinHeight.isAuto() && computedMinHeight.value()
347 && (marginAfterCollapsesWithParentMarginBefore(layoutState, lastInFlowChild) || hasClearance(layoutState, lastInFlowChild)))
348 return false;
349
350 // Margins between a floated box and any other box do not collapse.
351 if (lastInFlowChild.isFloatingPositioned())
352 return false;
353
354 // Margins of absolutely positioned boxes do not collapse.
355 if (lastInFlowChild.isOutOfFlowPositioned())
356 return false;
357
358 // Margins of inline-block boxes do not collapse.
359 if (lastInFlowChild.isInlineBlockBox())
360 return false;
361
362 return true;
363}
364
365bool BlockFormattingContext::MarginCollapse::marginAfterCollapsesWithNextSiblingMarginBefore(const LayoutState& layoutState, const Box& layoutBox)
366{
367 ASSERT(layoutBox.isBlockLevelBox());
368
369 if (!layoutBox.nextInFlowSibling())
370 return false;
371
372 return marginBeforeCollapsesWithPreviousSiblingMarginAfter(layoutState, *layoutBox.nextInFlowSibling());
373}
374
375bool BlockFormattingContext::MarginCollapse::marginsCollapseThrough(const LayoutState& layoutState, const Box& layoutBox)
376{
377 ASSERT(layoutBox.isBlockLevelBox());
378
379 // A box's own margins collapse if the 'min-height' property is zero, and it has neither top or bottom borders nor top or bottom padding,
380 // and it has a 'height' of either 0 or 'auto', and it does not contain a line box, and all of its in-flow children's margins (if any) collapse.
381 if (hasBorderBefore(layoutBox) || hasBorderAfter(layoutBox))
382 return false;
383
384 if (hasPaddingBefore(layoutBox) || hasPaddingAfter(layoutBox))
385 return false;
386
387 // FIXME: Check for computed 0 height.
388 if (!layoutBox.style().height().isAuto())
389 return false;
390
391 // FIXME: Check for computed 0 height.
392 if (!layoutBox.style().minHeight().isAuto())
393 return false;
394
395 // FIXME: Block replaced boxes clearly don't collapse through their margins, but I couldn't find it in the spec yet (and no, it's not a quirk).
396 if (layoutBox.isReplaced())
397 return false;
398
399 if (!is<Container>(layoutBox))
400 return true;
401
402 if (!downcast<Container>(layoutBox).hasInFlowChild())
403 return !establishesBlockFormattingContext(layoutBox);
404
405 if (layoutBox.establishesFormattingContext()) {
406 if (layoutBox.establishesInlineFormattingContext()) {
407 // If we get here through margin estimation, we don't necessarily have an actual state for this layout box since
408 // we haven't started laying it out yet.
409 if (!layoutState.hasFormattingState(layoutBox))
410 return false;
411 auto& formattingState = downcast<InlineFormattingState>(layoutState.establishedFormattingState(layoutBox));
412 if (!formattingState.inlineRuns().isEmpty())
413 return false;
414 // Any float box in this formatting context prevents collapsing through.
415 auto& floats = formattingState.floatingState().floats();
416 for (auto& floatItem : floats) {
417 if (floatItem.isDescendantOfFormattingRoot(downcast<Container>(layoutBox)))
418 return false;
419 }
420 return true;
421 }
422
423 if (establishesBlockFormattingContext(layoutBox))
424 return false;
425 }
426
427 for (auto* inflowChild = downcast<Container>(layoutBox).firstInFlowOrFloatingChild(); inflowChild; inflowChild = inflowChild->nextInFlowOrFloatingSibling()) {
428 if (establishesBlockFormattingContext(*inflowChild))
429 return false;
430 if (!marginsCollapseThrough(layoutState, *inflowChild))
431 return false;
432 }
433 return true;
434}
435
436static PositiveAndNegativeVerticalMargin::Values computedPositiveAndNegativeMargin(PositiveAndNegativeVerticalMargin::Values a, PositiveAndNegativeVerticalMargin::Values b)
437{
438 PositiveAndNegativeVerticalMargin::Values computedValues;
439 if (a.positive && b.positive)
440 computedValues.positive = std::max(*a.positive, *b.positive);
441 else
442 computedValues.positive = a.positive ? a.positive : b.positive;
443
444 if (a.negative && b.negative)
445 computedValues.negative = std::min(*a.negative, *b.negative);
446 else
447 computedValues.negative = a.negative ? a.negative : b.negative;
448
449 if (a.isNonZero() && b.isNonZero())
450 computedValues.isQuirk = a.isQuirk && b.isQuirk;
451 else if (a.isNonZero())
452 computedValues.isQuirk = a.isQuirk;
453 else
454 computedValues.isQuirk = b.isQuirk;
455
456 return computedValues;
457}
458
459static Optional<LayoutUnit> marginValue(PositiveAndNegativeVerticalMargin::Values marginValues)
460{
461 // When two or more margins collapse, the resulting margin width is the maximum of the collapsing margins' widths.
462 // In the case of negative margins, the maximum of the absolute values of the negative adjoining margins is deducted from the maximum
463 // of the positive adjoining margins. If there are no positive margins, the maximum of the absolute values of the adjoining margins is deducted from zero.
464 if (!marginValues.negative)
465 return marginValues.positive;
466
467 if (!marginValues.positive)
468 return marginValues.negative;
469
470 return *marginValues.positive + *marginValues.negative;
471}
472
473void BlockFormattingContext::MarginCollapse::updateMarginAfterForPreviousSibling(const LayoutState& layoutState, const Box& layoutBox)
474{
475 // 1. Get the margin before value from the next in-flow sibling. This is the same as this box's margin after value now since they are collapsed.
476 // 2. Update the collapsed margin after value as well as the positive/negative cache.
477 // 3. Check if the box's margins collapse through.
478 // 4. If so, update the collapsed margin before value as well as the positive/negative cache.
479 // 5. In case of collapsed through margins check if the before margin collapes with the previous inflow sibling's after margin.
480 // 6. If so, jump to #2.
481 // 7. No need to propagate to parent because its margin is not computed yet (estimated at most).
482 auto* currentBox = &layoutBox;
483 while (marginBeforeCollapsesWithPreviousSiblingMarginAfter(layoutState, *currentBox)) {
484 auto& previousSibling = *currentBox->previousInFlowSibling();
485 auto& previousSiblingDisplayBox = layoutState.displayBoxForLayoutBox(previousSibling);
486 auto previousSiblingVerticalMargin = previousSiblingDisplayBox.verticalMargin();
487
488 auto collapsedVerticalMarginBefore = previousSiblingVerticalMargin.collapsedValues().before;
489 auto collapsedVerticalMarginAfter = layoutState.displayBoxForLayoutBox(*currentBox).verticalMargin().before();
490
491 auto marginsCollapseThrough = MarginCollapse::marginsCollapseThrough(layoutState, previousSibling);
492 if (marginsCollapseThrough)
493 collapsedVerticalMarginBefore = collapsedVerticalMarginAfter;
494
495 // Update collapsed vertical margin values.
496 previousSiblingVerticalMargin.setCollapsedValues({ collapsedVerticalMarginBefore, collapsedVerticalMarginAfter });
497 previousSiblingDisplayBox.setVerticalMargin(previousSiblingVerticalMargin);
498
499 // Update positive/negative cache.
500 auto& blockFormattingState = downcast<BlockFormattingState>(layoutState.formattingStateForBox(previousSibling));
501 auto previousSiblingPositiveNegativeMargin = blockFormattingState.positiveAndNegativeVerticalMargin(previousSibling);
502 auto positiveNegativeMarginBefore = blockFormattingState.positiveAndNegativeVerticalMargin(*currentBox).before;
503
504 previousSiblingPositiveNegativeMargin.after = computedPositiveAndNegativeMargin(positiveNegativeMarginBefore, previousSiblingPositiveNegativeMargin.after);
505 if (marginsCollapseThrough) {
506 previousSiblingPositiveNegativeMargin.before = computedPositiveAndNegativeMargin(previousSiblingPositiveNegativeMargin.before, previousSiblingPositiveNegativeMargin.after);
507 previousSiblingPositiveNegativeMargin.after = previousSiblingPositiveNegativeMargin.before;
508 }
509 blockFormattingState.setPositiveAndNegativeVerticalMargin(previousSibling, previousSiblingPositiveNegativeMargin);
510
511 if (!marginsCollapseThrough)
512 break;
513
514 currentBox = &previousSibling;
515 }
516}
517
518PositiveAndNegativeVerticalMargin::Values BlockFormattingContext::MarginCollapse::positiveNegativeValues(const LayoutState& layoutState, const Box& layoutBox, MarginType marginType)
519{
520 auto& blockFormattingState = downcast<BlockFormattingState>(layoutState.formattingStateForBox(layoutBox));
521 if (blockFormattingState.hasPositiveAndNegativeVerticalMargin(layoutBox)) {
522 auto positiveAndNegativeVerticalMargin = blockFormattingState.positiveAndNegativeVerticalMargin(layoutBox);
523 return marginType == MarginType::Before ? positiveAndNegativeVerticalMargin.before : positiveAndNegativeVerticalMargin.after;
524 }
525 // This is the estimate path. We don't yet have positive/negative margin computed.
526 auto usedValues = UsedHorizontalValues { layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth() };
527 auto computedVerticalMargin = Geometry::computedVerticalMargin(layoutBox, usedValues);
528 auto nonCollapsedMargin = UsedVerticalMargin::NonCollapsedValues { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
529
530 if (marginType == MarginType::Before)
531 return positiveNegativeMarginBefore(layoutState, layoutBox, nonCollapsedMargin);
532 return positiveNegativeMarginAfter(layoutState, layoutBox, nonCollapsedMargin);
533}
534
535PositiveAndNegativeVerticalMargin::Values BlockFormattingContext::MarginCollapse::positiveNegativeMarginBefore(const LayoutState& layoutState, const Box& layoutBox, const UsedVerticalMargin::NonCollapsedValues& nonCollapsedValues)
536{
537 auto firstChildCollapsedMarginBefore = [&]() -> PositiveAndNegativeVerticalMargin::Values {
538 if (!marginBeforeCollapsesWithFirstInFlowChildMarginBefore(layoutState, layoutBox))
539 return { };
540 return positiveNegativeValues(layoutState, *downcast<Container>(layoutBox).firstInFlowChild(), MarginType::Before);
541 };
542
543 auto previouSiblingCollapsedMarginAfter = [&]() -> PositiveAndNegativeVerticalMargin::Values {
544 if (!marginBeforeCollapsesWithPreviousSiblingMarginAfter(layoutState, layoutBox))
545 return { };
546 return positiveNegativeValues(layoutState, *layoutBox.previousInFlowSibling(), MarginType::After);
547 };
548
549 // 1. Gather positive and negative margin values from first child if margins are adjoining.
550 // 2. Gather positive and negative margin values from previous inflow sibling if margins are adjoining.
551 // 3. Compute min/max positive and negative collapsed margin values using non-collpased computed margin before.
552 auto collapsedMarginBefore = computedPositiveAndNegativeMargin(firstChildCollapsedMarginBefore(), previouSiblingCollapsedMarginAfter());
553 if (collapsedMarginBefore.isQuirk && Quirks::shouldIgnoreCollapsedQuirkMargin(layoutState, layoutBox))
554 collapsedMarginBefore = { };
555
556 PositiveAndNegativeVerticalMargin::Values nonCollapsedBefore;
557 if (nonCollapsedValues.before > 0)
558 nonCollapsedBefore = { nonCollapsedValues.before, { }, layoutBox.style().hasMarginBeforeQuirk() };
559 else if (nonCollapsedValues.before < 0)
560 nonCollapsedBefore = { { }, nonCollapsedValues.before, layoutBox.style().hasMarginBeforeQuirk() };
561
562 return computedPositiveAndNegativeMargin(collapsedMarginBefore, nonCollapsedBefore);
563}
564
565PositiveAndNegativeVerticalMargin::Values BlockFormattingContext::MarginCollapse::positiveNegativeMarginAfter(const LayoutState& layoutState, const Box& layoutBox, const UsedVerticalMargin::NonCollapsedValues& nonCollapsedValues)
566{
567 auto lastChildCollapsedMarginAfter = [&]() -> PositiveAndNegativeVerticalMargin::Values {
568 if (!marginAfterCollapsesWithLastInFlowChildMarginAfter(layoutState, layoutBox))
569 return { };
570 return positiveNegativeValues(layoutState, *downcast<Container>(layoutBox).lastInFlowChild(), MarginType::After);
571 };
572
573 // We don't know yet the margin before value of the next sibling. Let's just pretend it does not have one and
574 // update it later when we compute the next sibling's margin before. See updateCollapsedMarginAfter.
575 PositiveAndNegativeVerticalMargin::Values nonCollapsedAfter;
576 if (nonCollapsedValues.after > 0)
577 nonCollapsedAfter = { nonCollapsedValues.after, { }, layoutBox.style().hasMarginAfterQuirk() };
578 else if (nonCollapsedValues.after < 0)
579 nonCollapsedAfter = { { }, nonCollapsedValues.after, layoutBox.style().hasMarginAfterQuirk() };
580
581 return computedPositiveAndNegativeMargin(lastChildCollapsedMarginAfter(), nonCollapsedAfter);
582}
583
584EstimatedMarginBefore BlockFormattingContext::MarginCollapse::estimatedMarginBefore(const LayoutState& layoutState, const Box& layoutBox)
585{
586 if (layoutBox.isAnonymous())
587 return { };
588
589 ASSERT(layoutBox.isBlockLevelBox());
590 // Don't estimate vertical margins for out of flow boxes.
591 ASSERT(layoutBox.isInFlow() || layoutBox.isFloatingPositioned());
592 ASSERT(!layoutBox.replaced());
593
594 auto usedValues = UsedHorizontalValues { layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth() };
595 auto computedVerticalMargin = Geometry::computedVerticalMargin(layoutBox, usedValues);
596 auto nonCollapsedMargin = UsedVerticalMargin::NonCollapsedValues { computedVerticalMargin.before.valueOr(0), computedVerticalMargin.after.valueOr(0) };
597 auto marginsCollapseThrough = MarginCollapse::marginsCollapseThrough(layoutState, layoutBox);
598 auto positiveNegativeMarginBefore = MarginCollapse::positiveNegativeMarginBefore(layoutState, layoutBox, nonCollapsedMargin);
599
600 auto collapsedMarginBefore = marginValue(!marginsCollapseThrough ? positiveNegativeMarginBefore
601 : computedPositiveAndNegativeMargin(positiveNegativeMarginBefore, positiveNegativeMarginAfter(layoutState, layoutBox, nonCollapsedMargin)));
602
603 return { nonCollapsedMargin.before, collapsedMarginBefore, marginsCollapseThrough };
604}
605
606LayoutUnit BlockFormattingContext::MarginCollapse::marginBeforeIgnoringCollapsingThrough(const LayoutState& layoutState, const Box& layoutBox, const UsedVerticalMargin::NonCollapsedValues& nonCollapsedValues)
607{
608 ASSERT(!layoutBox.isAnonymous());
609 ASSERT(layoutBox.isBlockLevelBox());
610 return marginValue(positiveNegativeMarginBefore(layoutState, layoutBox, nonCollapsedValues)).valueOr(nonCollapsedValues.before);
611}
612
613void BlockFormattingContext::MarginCollapse::updatePositiveNegativeMarginValues(const LayoutState& layoutState, const Box& layoutBox)
614{
615 ASSERT(layoutBox.isBlockLevelBox());
616 auto nonCollapsedValues = layoutState.displayBoxForLayoutBox(layoutBox).verticalMargin().nonCollapsedValues();
617
618 auto positiveNegativeMarginBefore = MarginCollapse::positiveNegativeMarginBefore(layoutState, layoutBox, nonCollapsedValues);
619 auto positiveNegativeMarginAfter = MarginCollapse::positiveNegativeMarginAfter(layoutState, layoutBox, nonCollapsedValues);
620
621 if (MarginCollapse::marginsCollapseThrough(layoutState, layoutBox)) {
622 positiveNegativeMarginBefore = computedPositiveAndNegativeMargin(positiveNegativeMarginBefore, positiveNegativeMarginAfter);
623 positiveNegativeMarginAfter = positiveNegativeMarginBefore;
624 }
625 auto& blockFormattingState = downcast<BlockFormattingState>(layoutState.formattingStateForBox(layoutBox));
626 blockFormattingState.setPositiveAndNegativeVerticalMargin(layoutBox, { positiveNegativeMarginBefore, positiveNegativeMarginAfter });
627}
628
629UsedVerticalMargin::CollapsedValues BlockFormattingContext::MarginCollapse::collapsedVerticalValues(const LayoutState& layoutState, const Box& layoutBox, const UsedVerticalMargin::NonCollapsedValues& nonCollapsedValues)
630{
631 if (layoutBox.isAnonymous())
632 return { };
633
634 ASSERT(layoutBox.isBlockLevelBox());
635 // 1. Get min/max margin top values from the first in-flow child if we are collapsing margin top with it.
636 // 2. Get min/max margin top values from the previous in-flow sibling, if we are collapsing margin top with it.
637 // 3. Get this layout box's computed margin top value.
638 // 4. Update the min/max value and compute the final margin.
639 auto positiveNegativeMarginBefore = MarginCollapse::positiveNegativeMarginBefore(layoutState, layoutBox, nonCollapsedValues);
640 auto positiveNegativeMarginAfter = MarginCollapse::positiveNegativeMarginAfter(layoutState, layoutBox, nonCollapsedValues);
641
642 auto marginsCollapseThrough = MarginCollapse::marginsCollapseThrough(layoutState, layoutBox);
643 if (marginsCollapseThrough) {
644 positiveNegativeMarginBefore = computedPositiveAndNegativeMargin(positiveNegativeMarginBefore, positiveNegativeMarginAfter);
645 positiveNegativeMarginAfter = positiveNegativeMarginBefore;
646 }
647
648 auto beforeMarginIsCollapsedValue = marginBeforeCollapsesWithFirstInFlowChildMarginBefore(layoutState, layoutBox) || marginBeforeCollapsesWithPreviousSiblingMarginAfter(layoutState, layoutBox);
649 auto afterMarginIsCollapsedValue = marginAfterCollapsesWithLastInFlowChildMarginAfter(layoutState, layoutBox);
650
651 if ((beforeMarginIsCollapsedValue && afterMarginIsCollapsedValue) || marginsCollapseThrough)
652 return { marginValue(positiveNegativeMarginBefore), marginValue(positiveNegativeMarginAfter), marginsCollapseThrough };
653 if (beforeMarginIsCollapsedValue)
654 return { marginValue(positiveNegativeMarginBefore), { }, false };
655 return { { }, marginValue(positiveNegativeMarginAfter), false };
656}
657
658}
659}
660#endif
661