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 | |
42 | namespace WebCore { |
43 | namespace Layout { |
44 | |
45 | WTF_MAKE_ISO_ALLOCATED_IMPL(BlockFormattingContext); |
46 | |
47 | BlockFormattingContext::BlockFormattingContext(const Box& formattingContextRoot, BlockFormattingState& formattingState) |
48 | : FormattingContext(formattingContextRoot, formattingState) |
49 | { |
50 | } |
51 | |
52 | void 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 | |
121 | void 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 | |
147 | void 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 | |
175 | void 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 | |
185 | void 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 | |
202 | void 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 | |
224 | void 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 | |
242 | void 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 |
264 | bool 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 | |
275 | void 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 | |
282 | void 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 | |
298 | void 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 | |
334 | void 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 | |
395 | void 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 | |
454 | LayoutUnit 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 | |
515 | void 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 | |
522 | bool 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 | |