nsBlockReflowContext.cpp (17880B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* class that a parent frame uses to reflow a block frame */ 8 9 #include "nsBlockReflowContext.h" 10 11 #include "BlockReflowState.h" 12 #include "nsBlockFrame.h" 13 #include "nsColumnSetFrame.h" 14 #include "nsContainerFrame.h" 15 #include "nsFloatManager.h" 16 #include "nsLayoutUtils.h" 17 #include "nsLineBox.h" 18 19 using namespace mozilla; 20 21 #ifdef DEBUG 22 # include "nsBlockDebugFlags.h" // For NOISY_BLOCK_DIR_MARGINS 23 #endif 24 25 nsBlockReflowContext::nsBlockReflowContext(nsPresContext* aPresContext, 26 const ReflowInput& aParentRI) 27 : mPresContext(aPresContext), 28 mOuterReflowInput(aParentRI), 29 mFrame(nullptr), 30 mSpace(aParentRI.GetWritingMode()), 31 mICoord(0), 32 mBCoord(0), 33 mMetrics(aParentRI) {} 34 35 static nsIFrame* DescendIntoBlockLevelFrame(nsIFrame* aFrame) { 36 LayoutFrameType type = aFrame->Type(); 37 if (type == LayoutFrameType::ColumnSet) { 38 static_cast<nsColumnSetFrame*>(aFrame)->DrainOverflowColumns(); 39 nsIFrame* child = aFrame->PrincipalChildList().FirstChild(); 40 if (child) { 41 return DescendIntoBlockLevelFrame(child); 42 } 43 } 44 return aFrame; 45 } 46 47 bool nsBlockReflowContext::ComputeCollapsedBStartMargin( 48 const ReflowInput& aRI, CollapsingMargin* aMargin, 49 nsIFrame* aClearanceFrame, bool* aMayNeedRetry, bool* aBlockIsEmpty) { 50 WritingMode wm = aRI.GetWritingMode(); 51 WritingMode parentWM = mMetrics.GetWritingMode(); 52 53 // Include block-start element of frame's margin 54 aMargin->Include(aRI.ComputedLogicalMargin(parentWM).BStart(parentWM)); 55 56 // The inclusion of the block-end margin when empty is done by the caller 57 // since it doesn't need to be done by the top-level (non-recursive) 58 // caller. 59 60 #ifdef NOISY_BLOCK_DIR_MARGINS 61 aRI.mFrame->ListTag(stdout); 62 printf(": %d => %d\n", aRI.ComputedLogicalMargin(wm).BStart(wm), 63 aMargin->get()); 64 #endif 65 66 bool dirtiedLine = false; 67 bool setBlockIsEmpty = false; 68 69 // Calculate the frame's generational block-start-margin from its child 70 // blocks. Note that if the frame has a non-zero block-start-border or 71 // block-start-padding then this step is skipped because it will be a margin 72 // root. It is also skipped if the frame is a margin root for other 73 // reasons. 74 nsIFrame* frame = DescendIntoBlockLevelFrame(aRI.mFrame); 75 nsPresContext* prescontext = frame->PresContext(); 76 nsBlockFrame* block = nullptr; 77 if (0 == aRI.ComputedLogicalBorderPadding(wm).BStart(wm)) { 78 block = do_QueryFrame(frame); 79 if (block) { 80 bool bStartMarginRoot, unused; 81 block->IsMarginRoot(&bStartMarginRoot, &unused); 82 if (bStartMarginRoot) { 83 block = nullptr; 84 } 85 } 86 } 87 88 // iterate not just through the lines of 'block' but also its 89 // overflow lines and the normal and overflow lines of its next in 90 // flows. Note that this will traverse some frames more than once: 91 // for example, if A contains B and A->nextinflow contains 92 // B->nextinflow, we'll traverse B->nextinflow twice. But this is 93 // OK because our traversal is idempotent. 94 for (; block; block = static_cast<nsBlockFrame*>(block->GetNextInFlow())) { 95 for (int overflowLines = 0; overflowLines <= 1; ++overflowLines) { 96 nsBlockFrame::LineIterator line; 97 nsBlockFrame::LineIterator line_end; 98 bool anyLines = true; 99 if (overflowLines) { 100 nsBlockFrame::FrameLines* frames = block->GetOverflowLines(); 101 nsLineList* lines = frames ? &frames->mLines : nullptr; 102 if (!lines) { 103 anyLines = false; 104 } else { 105 line = lines->begin(); 106 line_end = lines->end(); 107 } 108 } else { 109 line = block->LinesBegin(); 110 line_end = block->LinesEnd(); 111 } 112 for (; anyLines && line != line_end; ++line) { 113 if (!aClearanceFrame && line->HasClearance()) { 114 // If we don't have a clearance frame, then we're computing 115 // the collapsed margin in the first pass, assuming that all 116 // lines have no clearance. So clear their clearance flags. 117 line->ClearHasClearance(); 118 line->MarkDirty(); 119 dirtiedLine = true; 120 } 121 122 bool isEmpty; 123 if (line->IsInline()) { 124 isEmpty = line->IsEmpty(); 125 } else { 126 nsIFrame* kid = line->mFirstChild; 127 if (kid == aClearanceFrame) { 128 line->SetHasClearance(); 129 line->MarkDirty(); 130 dirtiedLine = true; 131 if (!setBlockIsEmpty && aBlockIsEmpty) { 132 setBlockIsEmpty = true; 133 *aBlockIsEmpty = false; 134 } 135 goto done; 136 } 137 // Here is where we recur. Now that we have determined that a 138 // generational collapse is required we need to compute the 139 // child blocks margin and so in so that we can look into 140 // it. For its margins to be computed we need to have a reflow 141 // input for it. 142 143 // We may have to construct an extra reflow input here if 144 // we drilled down through a block wrapper. At the moment 145 // we can only drill down one level so we only have to support 146 // one extra reflow input. 147 const ReflowInput* outerReflowInput = &aRI; 148 if (frame != aRI.mFrame) { 149 NS_ASSERTION(frame->GetParent() == aRI.mFrame, 150 "Can only drill through one level of block wrapper"); 151 LogicalSize availSpace = aRI.ComputedSize(frame->GetWritingMode()); 152 outerReflowInput = 153 new ReflowInput(prescontext, aRI, frame, availSpace); 154 } 155 { 156 LogicalSize availSpace = 157 outerReflowInput->ComputedSize(kid->GetWritingMode()); 158 ReflowInput innerReflowInput(prescontext, *outerReflowInput, kid, 159 availSpace); 160 // Record that we're being optimistic by assuming the kid 161 // has no clearance 162 if (kid->StyleDisplay()->mClear != StyleClear::None || 163 !nsBlockFrame::BlockCanIntersectFloats(kid)) { 164 *aMayNeedRetry = true; 165 } 166 if (ComputeCollapsedBStartMargin(innerReflowInput, aMargin, 167 aClearanceFrame, aMayNeedRetry, 168 &isEmpty)) { 169 line->MarkDirty(); 170 dirtiedLine = true; 171 } 172 if (isEmpty) { 173 LogicalMargin innerMargin = 174 innerReflowInput.ComputedLogicalMargin(parentWM); 175 aMargin->Include(innerMargin.BEnd(parentWM)); 176 } 177 } 178 if (outerReflowInput != &aRI) { 179 delete const_cast<ReflowInput*>(outerReflowInput); 180 } 181 } 182 if (!isEmpty) { 183 if (!setBlockIsEmpty && aBlockIsEmpty) { 184 setBlockIsEmpty = true; 185 *aBlockIsEmpty = false; 186 } 187 goto done; 188 } 189 } 190 if (!setBlockIsEmpty && aBlockIsEmpty) { 191 // The first time we reach here is when this is the first block 192 // and we have processed all its normal lines. 193 setBlockIsEmpty = true; 194 // All lines are empty, or we wouldn't be here! 195 *aBlockIsEmpty = aRI.mFrame->IsSelfEmpty(); 196 } 197 } 198 } 199 done: 200 201 if (!setBlockIsEmpty && aBlockIsEmpty) { 202 *aBlockIsEmpty = aRI.mFrame->IsEmpty(); 203 } 204 205 #ifdef NOISY_BLOCK_DIR_MARGINS 206 aRI.mFrame->ListTag(stdout); 207 printf(": => %d\n", aMargin->get()); 208 #endif 209 210 return dirtiedLine; 211 } 212 213 void nsBlockReflowContext::ReflowBlock(const LogicalRect& aSpace, 214 bool aApplyBStartMargin, 215 CollapsingMargin& aPrevMargin, 216 nscoord aClearance, nsLineBox* aLine, 217 ReflowInput& aFrameRI, 218 nsReflowStatus& aFrameReflowStatus, 219 BlockReflowState& aState) { 220 mFrame = aFrameRI.mFrame; 221 mWritingMode = aState.mReflowInput.GetWritingMode(); 222 mContainerSize = aState.ContainerSize(); 223 mSpace = aSpace; 224 225 if (!aState.IsAdjacentWithBStart()) { 226 aFrameRI.mFlags.mIsTopOfPage = false; // make sure this is cleared 227 } 228 229 if (aApplyBStartMargin) { 230 mBStartMargin = aPrevMargin; 231 232 #ifdef NOISY_BLOCK_DIR_MARGINS 233 mOuterReflowInput.mFrame->ListTag(stdout); 234 printf(": reflowing "); 235 mFrame->ListTag(stdout); 236 printf(" margin => %d, clearance => %d\n", mBStartMargin.get(), aClearance); 237 #endif 238 239 // Adjust the available size if it's constrained so that the 240 // child frame doesn't think it can reflow into its margin area. 241 if (mWritingMode.IsOrthogonalTo(mFrame->GetWritingMode())) { 242 if (NS_UNCONSTRAINEDSIZE != aFrameRI.AvailableISize()) { 243 aFrameRI.SetAvailableISize(std::max( 244 0, aFrameRI.AvailableISize() - mBStartMargin.Get() - aClearance)); 245 } 246 } else { 247 if (NS_UNCONSTRAINEDSIZE != aFrameRI.AvailableBSize()) { 248 aFrameRI.SetAvailableBSize(std::max( 249 0, aFrameRI.AvailableBSize() - mBStartMargin.Get() - aClearance)); 250 } 251 } 252 } else { 253 // nsBlockFrame::ReflowBlock might call us multiple times with 254 // *different* values of aApplyBStartMargin. 255 mBStartMargin.Zero(); 256 } 257 258 nscoord tI = 0, tB = 0; 259 // The values of x and y do not matter for floats, so don't bother 260 // calculating them. Floats are guaranteed to have their own float 261 // manager, so tI and tB don't matter. mICoord and mBCoord don't 262 // matter becacuse they are only used in PlaceBlock, which is not used 263 // for floats. 264 if (aLine) { 265 // Compute inline/block coordinate where reflow will begin. Use the 266 // rules from 10.3.3 to determine what to apply. At this point in the 267 // reflow auto inline-start/end margins will have a zero value. 268 LogicalMargin usedMargin = aFrameRI.ComputedLogicalMargin(mWritingMode); 269 mICoord = mSpace.IStart(mWritingMode) + usedMargin.IStart(mWritingMode); 270 mBCoord = mSpace.BStart(mWritingMode) + mBStartMargin.Get() + aClearance; 271 272 LogicalRect space( 273 mWritingMode, mICoord, mBCoord, 274 mSpace.ISize(mWritingMode) - usedMargin.IStartEnd(mWritingMode), 275 mSpace.BSize(mWritingMode) - usedMargin.BStartEnd(mWritingMode)); 276 tI = space.LineLeft(mWritingMode, mContainerSize); 277 tB = mBCoord; 278 279 if (!mFrame->HasAnyStateBits(NS_BLOCK_BFC)) { 280 aFrameRI.mBlockDelta = 281 mOuterReflowInput.mBlockDelta + mBCoord - aLine->BStart(); 282 } 283 } 284 285 #ifdef DEBUG 286 mMetrics.ISize(mWritingMode) = nscoord(0xdeadbeef); 287 mMetrics.BSize(mWritingMode) = nscoord(0xdeadbeef); 288 #endif 289 290 mOuterReflowInput.mFloatManager->Translate(tI, tB); 291 mFrame->Reflow(mPresContext, mMetrics, aFrameRI, aFrameReflowStatus); 292 mOuterReflowInput.mFloatManager->Translate(-tI, -tB); 293 294 #ifdef DEBUG 295 if (!aFrameReflowStatus.IsInlineBreakBefore()) { 296 if ((ABSURD_SIZE(mMetrics.ISize(mWritingMode)) || 297 ABSURD_SIZE(mMetrics.BSize(mWritingMode))) && 298 !mFrame->GetParent()->IsAbsurdSizeAssertSuppressed()) { 299 printf("nsBlockReflowContext: "); 300 mFrame->ListTag(stdout); 301 printf(" metrics=%d,%d!\n", mMetrics.ISize(mWritingMode), 302 mMetrics.BSize(mWritingMode)); 303 } 304 if ((mMetrics.ISize(mWritingMode) == nscoord(0xdeadbeef)) || 305 (mMetrics.BSize(mWritingMode) == nscoord(0xdeadbeef))) { 306 printf("nsBlockReflowContext: "); 307 mFrame->ListTag(stdout); 308 printf(" didn't set i/b %d,%d!\n", mMetrics.ISize(mWritingMode), 309 mMetrics.BSize(mWritingMode)); 310 } 311 } 312 #endif 313 314 if (!mFrame->HasOverflowAreas()) { 315 mMetrics.SetOverflowAreasToDesiredBounds(); 316 } 317 318 if (!aFrameReflowStatus.IsInlineBreakBefore() && 319 !aFrameRI.WillReflowAgainForClearance() && 320 aFrameReflowStatus.IsFullyComplete()) { 321 // If mFrame is fully-complete and has a next-in-flow, we need to delete 322 // them now. Do not do this when a break-before is signaled or when a 323 // clearance frame is discovered in mFrame's subtree because mFrame is going 324 // to get reflowed again (whether the frame is (in)complete is undefined in 325 // that case anyway). 326 if (nsIFrame* kidNextInFlow = mFrame->GetNextInFlow()) { 327 // Remove all of the childs next-in-flows. Make sure that we ask 328 // the right parent to do the removal (it's possible that the 329 // parent is not this because we are executing pullup code). 330 // Floats will eventually be removed via nsBlockFrame::RemoveFloat 331 // which detaches the placeholder from the float. 332 nsOverflowContinuationTracker::AutoFinish fini(aState.mOverflowTracker, 333 mFrame); 334 nsIFrame::DestroyContext context(mPresContext->PresShell()); 335 kidNextInFlow->GetParent()->DeleteNextInFlowChild(context, kidNextInFlow, 336 true); 337 } 338 } 339 } 340 341 /** 342 * Attempt to place the block frame within the available space. If 343 * it fits, apply inline-dir ("horizontal") positioning (CSS 10.3.3), 344 * collapse margins (CSS2 8.3.1). Also apply relative positioning. 345 */ 346 bool nsBlockReflowContext::PlaceBlock(const ReflowInput& aReflowInput, 347 bool aForceFit, nsLineBox* aLine, 348 CollapsingMargin& aBEndMarginResult, 349 OverflowAreas& aOverflowAreas, 350 const nsReflowStatus& aReflowStatus) { 351 // Compute collapsed block-end margin value. 352 WritingMode parentWM = mMetrics.GetWritingMode(); 353 354 // Don't apply the block-end margin if the block has a *later* sibling across 355 // column-span split. 356 if (aReflowStatus.IsComplete() && !mFrame->HasColumnSpanSiblings()) { 357 aBEndMarginResult = mMetrics.mCarriedOutBEndMargin; 358 aBEndMarginResult.Include( 359 aReflowInput.ComputedLogicalMargin(parentWM).BEnd(parentWM)); 360 } else { 361 // The used block-end-margin is set to zero before a break. 362 aBEndMarginResult.Zero(); 363 } 364 365 nscoord backupContainingBlockAdvance = 0; 366 367 // Check whether the block's block-end margin collapses with its block-start 368 // margin. See CSS 2.1 section 8.3.1; those rules seem to match 369 // nsBlockFrame::IsEmpty(). Any such block must have zero block-size so 370 // check that first. Note that a block can have clearance and still 371 // have adjoining block-start/end margins, because the clearance goes 372 // above the block-start margin. 373 // Mark the frame as non-dirty; it has been reflowed (or we wouldn't 374 // be here), and we don't want to assert in CachedIsEmpty() 375 mFrame->RemoveStateBits(NS_FRAME_IS_DIRTY); 376 bool empty = 0 == mMetrics.BSize(parentWM) && aLine->CachedIsEmpty(); 377 if (empty) { 378 // Collapse the block-end margin with the block-start margin that was 379 // already applied. 380 aBEndMarginResult.Include(mBStartMargin); 381 382 #ifdef NOISY_BLOCK_DIR_MARGINS 383 printf(" "); 384 mOuterReflowInput.mFrame->ListTag(stdout); 385 printf(": "); 386 mFrame->ListTag(stdout); 387 printf( 388 " -- collapsing block start & end margin together; BStart=%d " 389 "spaceBStart=%d\n", 390 mBCoord, mSpace.BStart(mWritingMode)); 391 #endif 392 // Section 8.3.1 of CSS 2.1 says that blocks with adjoining 393 // "top/bottom" (i.e. block-start/end) margins whose top margin collapses 394 // with their parent's top margin should have their top border-edge at the 395 // top border-edge of their parent. We actually don't have to do 396 // anything special to make this happen. In that situation, 397 // nsBlockFrame::ShouldApplyBStartMargin will have returned false, 398 // and mBStartMargin and aClearance will have been zero in 399 // ReflowBlock. 400 401 // If we did apply our block-start margin, but now we're collapsing it 402 // into the block-end margin, we need to back up the containing 403 // block's bCoord-advance by our block-start margin so that it doesn't get 404 // counted twice. Note that here we're allowing the line's bounds 405 // to become different from the block's position; we do this 406 // because the containing block will place the next line at the 407 // line's BEnd, and it must place the next line at a different 408 // point from where this empty block will be. 409 backupContainingBlockAdvance = mBStartMargin.Get(); 410 } 411 412 // See if the frame fit. If it's the first frame or empty then it 413 // always fits. If the block-size is unconstrained then it always fits, 414 // even if there's some sort of integer overflow that makes bCoord + 415 // mMetrics.BSize() appear to go beyond the available block size. 416 if (!empty && !aForceFit && 417 mSpace.BSize(mWritingMode) != NS_UNCONSTRAINEDSIZE) { 418 nscoord bEnd = 419 mBCoord - backupContainingBlockAdvance + mMetrics.BSize(mWritingMode); 420 if (bEnd > mSpace.BEnd(mWritingMode)) { 421 // didn't fit, we must acquit. 422 mFrame->DidReflow(mPresContext, &aReflowInput); 423 return false; 424 } 425 } 426 427 aLine->SetBounds(mWritingMode, mICoord, 428 mBCoord - backupContainingBlockAdvance, 429 mMetrics.ISize(mWritingMode), mMetrics.BSize(mWritingMode), 430 mContainerSize); 431 432 // Now place the frame and complete the reflow process 433 nsContainerFrame::FinishReflowChild( 434 mFrame, mPresContext, mMetrics, &aReflowInput, mWritingMode, 435 LogicalPoint(mWritingMode, mICoord, mBCoord), mContainerSize, 436 nsIFrame::ReflowChildFlags::ApplyRelativePositioning); 437 438 aOverflowAreas = mMetrics.mOverflowAreas + mFrame->GetPosition(); 439 440 return true; 441 }