| 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 | |