1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/generic/nsBlockReflowContext.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,406 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +// vim:cindent:ts=2:et:sw=2: 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* class that a parent frame uses to reflow a block frame */ 1.11 + 1.12 +#include "nsBlockReflowContext.h" 1.13 +#include "nsBlockReflowState.h" 1.14 +#include "nsFloatManager.h" 1.15 +#include "nsContainerFrame.h" 1.16 +#include "nsBlockFrame.h" 1.17 +#include "nsLineBox.h" 1.18 +#include "nsLayoutUtils.h" 1.19 + 1.20 +#ifdef DEBUG 1.21 +#undef NOISY_MAX_ELEMENT_SIZE 1.22 +#undef REALLY_NOISY_MAX_ELEMENT_SIZE 1.23 +#undef NOISY_VERTICAL_MARGINS 1.24 +#else 1.25 +#undef NOISY_MAX_ELEMENT_SIZE 1.26 +#undef REALLY_NOISY_MAX_ELEMENT_SIZE 1.27 +#undef NOISY_VERTICAL_MARGINS 1.28 +#endif 1.29 + 1.30 +nsBlockReflowContext::nsBlockReflowContext(nsPresContext* aPresContext, 1.31 + const nsHTMLReflowState& aParentRS) 1.32 + : mPresContext(aPresContext), 1.33 + mOuterReflowState(aParentRS), 1.34 + mMetrics(aParentRS.GetWritingMode()) 1.35 +{ 1.36 +} 1.37 + 1.38 +static nsIFrame* DescendIntoBlockLevelFrame(nsIFrame* aFrame) 1.39 +{ 1.40 + nsIAtom* type = aFrame->GetType(); 1.41 + if (type == nsGkAtoms::columnSetFrame) 1.42 + return DescendIntoBlockLevelFrame(aFrame->GetFirstPrincipalChild()); 1.43 + return aFrame; 1.44 +} 1.45 + 1.46 +bool 1.47 +nsBlockReflowContext::ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS, 1.48 + nsCollapsingMargin* aMargin, nsIFrame* aClearanceFrame, 1.49 + bool* aMayNeedRetry, bool* aBlockIsEmpty) 1.50 +{ 1.51 + // Include frame's top margin 1.52 + aMargin->Include(aRS.ComputedPhysicalMargin().top); 1.53 + 1.54 + // The inclusion of the bottom margin when empty is done by the caller 1.55 + // since it doesn't need to be done by the top-level (non-recursive) 1.56 + // caller. 1.57 + 1.58 +#ifdef NOISY_VERTICAL_MARGINS 1.59 + nsFrame::ListTag(stdout, aRS.frame); 1.60 + printf(": %d => %d\n", aRS.ComputedPhysicalMargin().top, aMargin->get()); 1.61 +#endif 1.62 + 1.63 + bool dirtiedLine = false; 1.64 + bool setBlockIsEmpty = false; 1.65 + 1.66 + // Calculate the frame's generational top-margin from its child 1.67 + // blocks. Note that if the frame has a non-zero top-border or 1.68 + // top-padding then this step is skipped because it will be a margin 1.69 + // root. It is also skipped if the frame is a margin root for other 1.70 + // reasons. 1.71 + nsIFrame* frame = DescendIntoBlockLevelFrame(aRS.frame); 1.72 + nsPresContext* prescontext = frame->PresContext(); 1.73 + nsBlockFrame* block = nullptr; 1.74 + if (0 == aRS.ComputedPhysicalBorderPadding().top) { 1.75 + block = nsLayoutUtils::GetAsBlock(frame); 1.76 + if (block) { 1.77 + bool topMarginRoot, unused; 1.78 + block->IsMarginRoot(&topMarginRoot, &unused); 1.79 + if (topMarginRoot) { 1.80 + block = nullptr; 1.81 + } 1.82 + } 1.83 + } 1.84 + 1.85 + // iterate not just through the lines of 'block' but also its 1.86 + // overflow lines and the normal and overflow lines of its next in 1.87 + // flows. Note that this will traverse some frames more than once: 1.88 + // for example, if A contains B and A->nextinflow contains 1.89 + // B->nextinflow, we'll traverse B->nextinflow twice. But this is 1.90 + // OK because our traversal is idempotent. 1.91 + for ( ;block; block = static_cast<nsBlockFrame*>(block->GetNextInFlow())) { 1.92 + for (int overflowLines = 0; overflowLines <= 1; ++overflowLines) { 1.93 + nsBlockFrame::line_iterator line; 1.94 + nsBlockFrame::line_iterator line_end; 1.95 + bool anyLines = true; 1.96 + if (overflowLines) { 1.97 + nsBlockFrame::FrameLines* frames = block->GetOverflowLines(); 1.98 + nsLineList* lines = frames ? &frames->mLines : nullptr; 1.99 + if (!lines) { 1.100 + anyLines = false; 1.101 + } else { 1.102 + line = lines->begin(); 1.103 + line_end = lines->end(); 1.104 + } 1.105 + } else { 1.106 + line = block->begin_lines(); 1.107 + line_end = block->end_lines(); 1.108 + } 1.109 + for (; anyLines && line != line_end; ++line) { 1.110 + if (!aClearanceFrame && line->HasClearance()) { 1.111 + // If we don't have a clearance frame, then we're computing 1.112 + // the collapsed margin in the first pass, assuming that all 1.113 + // lines have no clearance. So clear their clearance flags. 1.114 + line->ClearHasClearance(); 1.115 + line->MarkDirty(); 1.116 + dirtiedLine = true; 1.117 + } 1.118 + 1.119 + bool isEmpty; 1.120 + if (line->IsInline()) { 1.121 + isEmpty = line->IsEmpty(); 1.122 + } else { 1.123 + nsIFrame* kid = line->mFirstChild; 1.124 + if (kid == aClearanceFrame) { 1.125 + line->SetHasClearance(); 1.126 + line->MarkDirty(); 1.127 + dirtiedLine = true; 1.128 + goto done; 1.129 + } 1.130 + // Here is where we recur. Now that we have determined that a 1.131 + // generational collapse is required we need to compute the 1.132 + // child blocks margin and so in so that we can look into 1.133 + // it. For its margins to be computed we need to have a reflow 1.134 + // state for it. 1.135 + 1.136 + // We may have to construct an extra reflow state here if 1.137 + // we drilled down through a block wrapper. At the moment 1.138 + // we can only drill down one level so we only have to support 1.139 + // one extra reflow state. 1.140 + const nsHTMLReflowState* outerReflowState = &aRS; 1.141 + if (frame != aRS.frame) { 1.142 + NS_ASSERTION(frame->GetParent() == aRS.frame, 1.143 + "Can only drill through one level of block wrapper"); 1.144 + nsSize availSpace(aRS.ComputedWidth(), aRS.ComputedHeight()); 1.145 + outerReflowState = new nsHTMLReflowState(prescontext, 1.146 + aRS, frame, availSpace); 1.147 + } 1.148 + { 1.149 + nsSize availSpace(outerReflowState->ComputedWidth(), 1.150 + outerReflowState->ComputedHeight()); 1.151 + nsHTMLReflowState innerReflowState(prescontext, 1.152 + *outerReflowState, kid, 1.153 + availSpace); 1.154 + // Record that we're being optimistic by assuming the kid 1.155 + // has no clearance 1.156 + if (kid->StyleDisplay()->mBreakType != NS_STYLE_CLEAR_NONE) { 1.157 + *aMayNeedRetry = true; 1.158 + } 1.159 + if (ComputeCollapsedTopMargin(innerReflowState, aMargin, aClearanceFrame, aMayNeedRetry, &isEmpty)) { 1.160 + line->MarkDirty(); 1.161 + dirtiedLine = true; 1.162 + } 1.163 + if (isEmpty) 1.164 + aMargin->Include(innerReflowState.ComputedPhysicalMargin().bottom); 1.165 + } 1.166 + if (outerReflowState != &aRS) { 1.167 + delete const_cast<nsHTMLReflowState*>(outerReflowState); 1.168 + } 1.169 + } 1.170 + if (!isEmpty) { 1.171 + if (!setBlockIsEmpty && aBlockIsEmpty) { 1.172 + setBlockIsEmpty = true; 1.173 + *aBlockIsEmpty = false; 1.174 + } 1.175 + goto done; 1.176 + } 1.177 + } 1.178 + if (!setBlockIsEmpty && aBlockIsEmpty) { 1.179 + // The first time we reach here is when this is the first block 1.180 + // and we have processed all its normal lines. 1.181 + setBlockIsEmpty = true; 1.182 + // All lines are empty, or we wouldn't be here! 1.183 + *aBlockIsEmpty = aRS.frame->IsSelfEmpty(); 1.184 + } 1.185 + } 1.186 + } 1.187 + done: 1.188 + 1.189 + if (!setBlockIsEmpty && aBlockIsEmpty) { 1.190 + *aBlockIsEmpty = aRS.frame->IsEmpty(); 1.191 + } 1.192 + 1.193 +#ifdef NOISY_VERTICAL_MARGINS 1.194 + nsFrame::ListTag(stdout, aRS.frame); 1.195 + printf(": => %d\n", aMargin->get()); 1.196 +#endif 1.197 + 1.198 + return dirtiedLine; 1.199 +} 1.200 + 1.201 +nsresult 1.202 +nsBlockReflowContext::ReflowBlock(const nsRect& aSpace, 1.203 + bool aApplyTopMargin, 1.204 + nsCollapsingMargin& aPrevMargin, 1.205 + nscoord aClearance, 1.206 + bool aIsAdjacentWithTop, 1.207 + nsLineBox* aLine, 1.208 + nsHTMLReflowState& aFrameRS, 1.209 + nsReflowStatus& aFrameReflowStatus, 1.210 + nsBlockReflowState& aState) 1.211 +{ 1.212 + nsresult rv = NS_OK; 1.213 + mFrame = aFrameRS.frame; 1.214 + mSpace = aSpace; 1.215 + 1.216 + if (!aIsAdjacentWithTop) { 1.217 + aFrameRS.mFlags.mIsTopOfPage = false; // make sure this is cleared 1.218 + } 1.219 + 1.220 + if (aApplyTopMargin) { 1.221 + mTopMargin = aPrevMargin; 1.222 + 1.223 +#ifdef NOISY_VERTICAL_MARGINS 1.224 + nsFrame::ListTag(stdout, mOuterReflowState.frame); 1.225 + printf(": reflowing "); 1.226 + nsFrame::ListTag(stdout, mFrame); 1.227 + printf(" margin => %d, clearance => %d\n", mTopMargin.get(), aClearance); 1.228 +#endif 1.229 + 1.230 + // Adjust the available height if its constrained so that the 1.231 + // child frame doesn't think it can reflow into its margin area. 1.232 + if (NS_UNCONSTRAINEDSIZE != aFrameRS.AvailableHeight()) { 1.233 + aFrameRS.AvailableHeight() -= mTopMargin.get() + aClearance; 1.234 + } 1.235 + } 1.236 + 1.237 + nscoord tx = 0, ty = 0; 1.238 + // The values of x and y do not matter for floats, so don't bother calculating 1.239 + // them. Floats are guaranteed to have their own float manager, so tx and ty 1.240 + // don't matter. mX and mY don't matter becacuse they are only used in 1.241 + // PlaceBlock, which is not used for floats. 1.242 + if (aLine) { 1.243 + // Compute x/y coordinate where reflow will begin. Use the rules 1.244 + // from 10.3.3 to determine what to apply. At this point in the 1.245 + // reflow auto left/right margins will have a zero value. 1.246 + 1.247 + mX = tx = mSpace.x + aFrameRS.ComputedPhysicalMargin().left; 1.248 + mY = ty = mSpace.y + mTopMargin.get() + aClearance; 1.249 + 1.250 + if ((mFrame->GetStateBits() & NS_BLOCK_FLOAT_MGR) == 0) 1.251 + aFrameRS.mBlockDelta = 1.252 + mOuterReflowState.mBlockDelta + ty - aLine->BStart(); 1.253 + } 1.254 + 1.255 + // Let frame know that we are reflowing it 1.256 + mFrame->WillReflow(mPresContext); 1.257 + 1.258 +#ifdef DEBUG 1.259 + mMetrics.Width() = nscoord(0xdeadbeef); 1.260 + mMetrics.Height() = nscoord(0xdeadbeef); 1.261 +#endif 1.262 + 1.263 + mOuterReflowState.mFloatManager->Translate(tx, ty); 1.264 + rv = mFrame->Reflow(mPresContext, mMetrics, aFrameRS, aFrameReflowStatus); 1.265 + mOuterReflowState.mFloatManager->Translate(-tx, -ty); 1.266 + 1.267 +#ifdef DEBUG 1.268 + if (!NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus)) { 1.269 + if (CRAZY_SIZE(mMetrics.Width()) || CRAZY_SIZE(mMetrics.Height())) { 1.270 + printf("nsBlockReflowContext: "); 1.271 + nsFrame::ListTag(stdout, mFrame); 1.272 + printf(" metrics=%d,%d!\n", mMetrics.Width(), mMetrics.Height()); 1.273 + } 1.274 + if ((mMetrics.Width() == nscoord(0xdeadbeef)) || 1.275 + (mMetrics.Height() == nscoord(0xdeadbeef))) { 1.276 + printf("nsBlockReflowContext: "); 1.277 + nsFrame::ListTag(stdout, mFrame); 1.278 + printf(" didn't set w/h %d,%d!\n", mMetrics.Width(), mMetrics.Height()); 1.279 + } 1.280 + } 1.281 +#endif 1.282 + 1.283 + if (!mFrame->HasOverflowAreas()) { 1.284 + mMetrics.SetOverflowAreasToDesiredBounds(); 1.285 + } 1.286 + 1.287 + if (!NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus) || 1.288 + (mFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) { 1.289 + // If frame is complete and has a next-in-flow, we need to delete 1.290 + // them now. Do not do this when a break-before is signaled because 1.291 + // the frame is going to get reflowed again (and may end up wanting 1.292 + // a next-in-flow where it ends up), unless it is an out of flow frame. 1.293 + if (NS_FRAME_IS_FULLY_COMPLETE(aFrameReflowStatus)) { 1.294 + nsIFrame* kidNextInFlow = mFrame->GetNextInFlow(); 1.295 + if (nullptr != kidNextInFlow) { 1.296 + // Remove all of the childs next-in-flows. Make sure that we ask 1.297 + // the right parent to do the removal (it's possible that the 1.298 + // parent is not this because we are executing pullup code). 1.299 + // Floats will eventually be removed via nsBlockFrame::RemoveFloat 1.300 + // which detaches the placeholder from the float. 1.301 + nsOverflowContinuationTracker::AutoFinish fini(aState.mOverflowTracker, mFrame); 1.302 + static_cast<nsContainerFrame*>(kidNextInFlow->GetParent()) 1.303 + ->DeleteNextInFlowChild(kidNextInFlow, true); 1.304 + } 1.305 + } 1.306 + } 1.307 + 1.308 + return rv; 1.309 +} 1.310 + 1.311 +/** 1.312 + * Attempt to place the block frame within the available space. If 1.313 + * it fits, apply horizontal positioning (CSS 10.3.3), collapse 1.314 + * margins (CSS2 8.3.1). Also apply relative positioning. 1.315 + */ 1.316 +bool 1.317 +nsBlockReflowContext::PlaceBlock(const nsHTMLReflowState& aReflowState, 1.318 + bool aForceFit, 1.319 + nsLineBox* aLine, 1.320 + nsCollapsingMargin& aBottomMarginResult, 1.321 + nsOverflowAreas& aOverflowAreas, 1.322 + nsReflowStatus aReflowStatus, 1.323 + nscoord aContainerWidth) 1.324 +{ 1.325 + // Compute collapsed bottom margin value. 1.326 + if (NS_FRAME_IS_COMPLETE(aReflowStatus)) { 1.327 + aBottomMarginResult = mMetrics.mCarriedOutBottomMargin; 1.328 + aBottomMarginResult.Include(aReflowState.ComputedPhysicalMargin().bottom); 1.329 + } else { 1.330 + // The used bottom-margin is set to zero above a break. 1.331 + aBottomMarginResult.Zero(); 1.332 + } 1.333 + 1.334 + nsPoint position(mX, mY); 1.335 + nscoord backupContainingBlockAdvance = 0; 1.336 + 1.337 + // Check whether the block's bottom margin collapses with its top 1.338 + // margin. See CSS 2.1 section 8.3.1; those rules seem to match 1.339 + // nsBlockFrame::IsEmpty(). Any such block must have zero height so 1.340 + // check that first. Note that a block can have clearance and still 1.341 + // have adjoining top/bottom margins, because the clearance goes 1.342 + // above the top margin. 1.343 + // Mark the frame as non-dirty; it has been reflowed (or we wouldn't 1.344 + // be here), and we don't want to assert in CachedIsEmpty() 1.345 + mFrame->RemoveStateBits(NS_FRAME_IS_DIRTY); 1.346 + bool empty = 0 == mMetrics.Height() && aLine->CachedIsEmpty(); 1.347 + if (empty) { 1.348 + // Collapse the bottom margin with the top margin that was already 1.349 + // applied. 1.350 + aBottomMarginResult.Include(mTopMargin); 1.351 + 1.352 +#ifdef NOISY_VERTICAL_MARGINS 1.353 + printf(" "); 1.354 + nsFrame::ListTag(stdout, mOuterReflowState.frame); 1.355 + printf(": "); 1.356 + nsFrame::ListTag(stdout, mFrame); 1.357 + printf(" -- collapsing top & bottom margin together; y=%d spaceY=%d\n", 1.358 + position.y, mSpace.y); 1.359 +#endif 1.360 + // Section 8.3.1 of CSS 2.1 says that blocks with adjoining 1.361 + // top/bottom margins whose top margin collapses with their 1.362 + // parent's top margin should have their top border-edge at the 1.363 + // top border-edge of their parent. We actually don't have to do 1.364 + // anything special to make this happen. In that situation, 1.365 + // nsBlockFrame::ShouldApplyTopMargin will have returned false, 1.366 + // and mTopMargin and aClearance will have been zero in 1.367 + // ReflowBlock. 1.368 + 1.369 + // If we did apply our top margin, but now we're collapsing it 1.370 + // into the bottom margin, we need to back up the containing 1.371 + // block's y-advance by our top margin so that it doesn't get 1.372 + // counted twice. Note that here we're allowing the line's bounds 1.373 + // to become different from the block's position; we do this 1.374 + // because the containing block will place the next line at the 1.375 + // line's YMost, and it must place the next line at a different 1.376 + // point from where this empty block will be. 1.377 + backupContainingBlockAdvance = mTopMargin.get(); 1.378 + } 1.379 + 1.380 + // See if the frame fit. If it's the first frame or empty then it 1.381 + // always fits. If the height is unconstrained then it always fits, 1.382 + // even if there's some sort of integer overflow that makes y + 1.383 + // mMetrics.Height() appear to go beyond the available height. 1.384 + if (!empty && !aForceFit && mSpace.height != NS_UNCONSTRAINEDSIZE) { 1.385 + nscoord yMost = position.y - backupContainingBlockAdvance + mMetrics.Height(); 1.386 + if (yMost > mSpace.YMost()) { 1.387 + // didn't fit, we must acquit. 1.388 + mFrame->DidReflow(mPresContext, &aReflowState, nsDidReflowStatus::FINISHED); 1.389 + return false; 1.390 + } 1.391 + } 1.392 + 1.393 + aLine->SetBounds(aReflowState.GetWritingMode(), 1.394 + nsRect(position.x, 1.395 + position.y - backupContainingBlockAdvance, 1.396 + mMetrics.Width(), 1.397 + mMetrics.Height()), 1.398 + aContainerWidth); 1.399 + 1.400 + aReflowState.ApplyRelativePositioning(&position); 1.401 + 1.402 + // Now place the frame and complete the reflow process 1.403 + nsContainerFrame::FinishReflowChild(mFrame, mPresContext, mMetrics, 1.404 + &aReflowState, position.x, position.y, 0); 1.405 + 1.406 + aOverflowAreas = mMetrics.mOverflowAreas + position; 1.407 + 1.408 + return true; 1.409 +}