layout/generic/nsBlockReflowContext.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 // vim:cindent:ts=2:et:sw=2:
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /* class that a parent frame uses to reflow a block frame */
michael@0 8
michael@0 9 #include "nsBlockReflowContext.h"
michael@0 10 #include "nsBlockReflowState.h"
michael@0 11 #include "nsFloatManager.h"
michael@0 12 #include "nsContainerFrame.h"
michael@0 13 #include "nsBlockFrame.h"
michael@0 14 #include "nsLineBox.h"
michael@0 15 #include "nsLayoutUtils.h"
michael@0 16
michael@0 17 #ifdef DEBUG
michael@0 18 #undef NOISY_MAX_ELEMENT_SIZE
michael@0 19 #undef REALLY_NOISY_MAX_ELEMENT_SIZE
michael@0 20 #undef NOISY_VERTICAL_MARGINS
michael@0 21 #else
michael@0 22 #undef NOISY_MAX_ELEMENT_SIZE
michael@0 23 #undef REALLY_NOISY_MAX_ELEMENT_SIZE
michael@0 24 #undef NOISY_VERTICAL_MARGINS
michael@0 25 #endif
michael@0 26
michael@0 27 nsBlockReflowContext::nsBlockReflowContext(nsPresContext* aPresContext,
michael@0 28 const nsHTMLReflowState& aParentRS)
michael@0 29 : mPresContext(aPresContext),
michael@0 30 mOuterReflowState(aParentRS),
michael@0 31 mMetrics(aParentRS.GetWritingMode())
michael@0 32 {
michael@0 33 }
michael@0 34
michael@0 35 static nsIFrame* DescendIntoBlockLevelFrame(nsIFrame* aFrame)
michael@0 36 {
michael@0 37 nsIAtom* type = aFrame->GetType();
michael@0 38 if (type == nsGkAtoms::columnSetFrame)
michael@0 39 return DescendIntoBlockLevelFrame(aFrame->GetFirstPrincipalChild());
michael@0 40 return aFrame;
michael@0 41 }
michael@0 42
michael@0 43 bool
michael@0 44 nsBlockReflowContext::ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS,
michael@0 45 nsCollapsingMargin* aMargin, nsIFrame* aClearanceFrame,
michael@0 46 bool* aMayNeedRetry, bool* aBlockIsEmpty)
michael@0 47 {
michael@0 48 // Include frame's top margin
michael@0 49 aMargin->Include(aRS.ComputedPhysicalMargin().top);
michael@0 50
michael@0 51 // The inclusion of the bottom margin when empty is done by the caller
michael@0 52 // since it doesn't need to be done by the top-level (non-recursive)
michael@0 53 // caller.
michael@0 54
michael@0 55 #ifdef NOISY_VERTICAL_MARGINS
michael@0 56 nsFrame::ListTag(stdout, aRS.frame);
michael@0 57 printf(": %d => %d\n", aRS.ComputedPhysicalMargin().top, aMargin->get());
michael@0 58 #endif
michael@0 59
michael@0 60 bool dirtiedLine = false;
michael@0 61 bool setBlockIsEmpty = false;
michael@0 62
michael@0 63 // Calculate the frame's generational top-margin from its child
michael@0 64 // blocks. Note that if the frame has a non-zero top-border or
michael@0 65 // top-padding then this step is skipped because it will be a margin
michael@0 66 // root. It is also skipped if the frame is a margin root for other
michael@0 67 // reasons.
michael@0 68 nsIFrame* frame = DescendIntoBlockLevelFrame(aRS.frame);
michael@0 69 nsPresContext* prescontext = frame->PresContext();
michael@0 70 nsBlockFrame* block = nullptr;
michael@0 71 if (0 == aRS.ComputedPhysicalBorderPadding().top) {
michael@0 72 block = nsLayoutUtils::GetAsBlock(frame);
michael@0 73 if (block) {
michael@0 74 bool topMarginRoot, unused;
michael@0 75 block->IsMarginRoot(&topMarginRoot, &unused);
michael@0 76 if (topMarginRoot) {
michael@0 77 block = nullptr;
michael@0 78 }
michael@0 79 }
michael@0 80 }
michael@0 81
michael@0 82 // iterate not just through the lines of 'block' but also its
michael@0 83 // overflow lines and the normal and overflow lines of its next in
michael@0 84 // flows. Note that this will traverse some frames more than once:
michael@0 85 // for example, if A contains B and A->nextinflow contains
michael@0 86 // B->nextinflow, we'll traverse B->nextinflow twice. But this is
michael@0 87 // OK because our traversal is idempotent.
michael@0 88 for ( ;block; block = static_cast<nsBlockFrame*>(block->GetNextInFlow())) {
michael@0 89 for (int overflowLines = 0; overflowLines <= 1; ++overflowLines) {
michael@0 90 nsBlockFrame::line_iterator line;
michael@0 91 nsBlockFrame::line_iterator line_end;
michael@0 92 bool anyLines = true;
michael@0 93 if (overflowLines) {
michael@0 94 nsBlockFrame::FrameLines* frames = block->GetOverflowLines();
michael@0 95 nsLineList* lines = frames ? &frames->mLines : nullptr;
michael@0 96 if (!lines) {
michael@0 97 anyLines = false;
michael@0 98 } else {
michael@0 99 line = lines->begin();
michael@0 100 line_end = lines->end();
michael@0 101 }
michael@0 102 } else {
michael@0 103 line = block->begin_lines();
michael@0 104 line_end = block->end_lines();
michael@0 105 }
michael@0 106 for (; anyLines && line != line_end; ++line) {
michael@0 107 if (!aClearanceFrame && line->HasClearance()) {
michael@0 108 // If we don't have a clearance frame, then we're computing
michael@0 109 // the collapsed margin in the first pass, assuming that all
michael@0 110 // lines have no clearance. So clear their clearance flags.
michael@0 111 line->ClearHasClearance();
michael@0 112 line->MarkDirty();
michael@0 113 dirtiedLine = true;
michael@0 114 }
michael@0 115
michael@0 116 bool isEmpty;
michael@0 117 if (line->IsInline()) {
michael@0 118 isEmpty = line->IsEmpty();
michael@0 119 } else {
michael@0 120 nsIFrame* kid = line->mFirstChild;
michael@0 121 if (kid == aClearanceFrame) {
michael@0 122 line->SetHasClearance();
michael@0 123 line->MarkDirty();
michael@0 124 dirtiedLine = true;
michael@0 125 goto done;
michael@0 126 }
michael@0 127 // Here is where we recur. Now that we have determined that a
michael@0 128 // generational collapse is required we need to compute the
michael@0 129 // child blocks margin and so in so that we can look into
michael@0 130 // it. For its margins to be computed we need to have a reflow
michael@0 131 // state for it.
michael@0 132
michael@0 133 // We may have to construct an extra reflow state here if
michael@0 134 // we drilled down through a block wrapper. At the moment
michael@0 135 // we can only drill down one level so we only have to support
michael@0 136 // one extra reflow state.
michael@0 137 const nsHTMLReflowState* outerReflowState = &aRS;
michael@0 138 if (frame != aRS.frame) {
michael@0 139 NS_ASSERTION(frame->GetParent() == aRS.frame,
michael@0 140 "Can only drill through one level of block wrapper");
michael@0 141 nsSize availSpace(aRS.ComputedWidth(), aRS.ComputedHeight());
michael@0 142 outerReflowState = new nsHTMLReflowState(prescontext,
michael@0 143 aRS, frame, availSpace);
michael@0 144 }
michael@0 145 {
michael@0 146 nsSize availSpace(outerReflowState->ComputedWidth(),
michael@0 147 outerReflowState->ComputedHeight());
michael@0 148 nsHTMLReflowState innerReflowState(prescontext,
michael@0 149 *outerReflowState, kid,
michael@0 150 availSpace);
michael@0 151 // Record that we're being optimistic by assuming the kid
michael@0 152 // has no clearance
michael@0 153 if (kid->StyleDisplay()->mBreakType != NS_STYLE_CLEAR_NONE) {
michael@0 154 *aMayNeedRetry = true;
michael@0 155 }
michael@0 156 if (ComputeCollapsedTopMargin(innerReflowState, aMargin, aClearanceFrame, aMayNeedRetry, &isEmpty)) {
michael@0 157 line->MarkDirty();
michael@0 158 dirtiedLine = true;
michael@0 159 }
michael@0 160 if (isEmpty)
michael@0 161 aMargin->Include(innerReflowState.ComputedPhysicalMargin().bottom);
michael@0 162 }
michael@0 163 if (outerReflowState != &aRS) {
michael@0 164 delete const_cast<nsHTMLReflowState*>(outerReflowState);
michael@0 165 }
michael@0 166 }
michael@0 167 if (!isEmpty) {
michael@0 168 if (!setBlockIsEmpty && aBlockIsEmpty) {
michael@0 169 setBlockIsEmpty = true;
michael@0 170 *aBlockIsEmpty = false;
michael@0 171 }
michael@0 172 goto done;
michael@0 173 }
michael@0 174 }
michael@0 175 if (!setBlockIsEmpty && aBlockIsEmpty) {
michael@0 176 // The first time we reach here is when this is the first block
michael@0 177 // and we have processed all its normal lines.
michael@0 178 setBlockIsEmpty = true;
michael@0 179 // All lines are empty, or we wouldn't be here!
michael@0 180 *aBlockIsEmpty = aRS.frame->IsSelfEmpty();
michael@0 181 }
michael@0 182 }
michael@0 183 }
michael@0 184 done:
michael@0 185
michael@0 186 if (!setBlockIsEmpty && aBlockIsEmpty) {
michael@0 187 *aBlockIsEmpty = aRS.frame->IsEmpty();
michael@0 188 }
michael@0 189
michael@0 190 #ifdef NOISY_VERTICAL_MARGINS
michael@0 191 nsFrame::ListTag(stdout, aRS.frame);
michael@0 192 printf(": => %d\n", aMargin->get());
michael@0 193 #endif
michael@0 194
michael@0 195 return dirtiedLine;
michael@0 196 }
michael@0 197
michael@0 198 nsresult
michael@0 199 nsBlockReflowContext::ReflowBlock(const nsRect& aSpace,
michael@0 200 bool aApplyTopMargin,
michael@0 201 nsCollapsingMargin& aPrevMargin,
michael@0 202 nscoord aClearance,
michael@0 203 bool aIsAdjacentWithTop,
michael@0 204 nsLineBox* aLine,
michael@0 205 nsHTMLReflowState& aFrameRS,
michael@0 206 nsReflowStatus& aFrameReflowStatus,
michael@0 207 nsBlockReflowState& aState)
michael@0 208 {
michael@0 209 nsresult rv = NS_OK;
michael@0 210 mFrame = aFrameRS.frame;
michael@0 211 mSpace = aSpace;
michael@0 212
michael@0 213 if (!aIsAdjacentWithTop) {
michael@0 214 aFrameRS.mFlags.mIsTopOfPage = false; // make sure this is cleared
michael@0 215 }
michael@0 216
michael@0 217 if (aApplyTopMargin) {
michael@0 218 mTopMargin = aPrevMargin;
michael@0 219
michael@0 220 #ifdef NOISY_VERTICAL_MARGINS
michael@0 221 nsFrame::ListTag(stdout, mOuterReflowState.frame);
michael@0 222 printf(": reflowing ");
michael@0 223 nsFrame::ListTag(stdout, mFrame);
michael@0 224 printf(" margin => %d, clearance => %d\n", mTopMargin.get(), aClearance);
michael@0 225 #endif
michael@0 226
michael@0 227 // Adjust the available height if its constrained so that the
michael@0 228 // child frame doesn't think it can reflow into its margin area.
michael@0 229 if (NS_UNCONSTRAINEDSIZE != aFrameRS.AvailableHeight()) {
michael@0 230 aFrameRS.AvailableHeight() -= mTopMargin.get() + aClearance;
michael@0 231 }
michael@0 232 }
michael@0 233
michael@0 234 nscoord tx = 0, ty = 0;
michael@0 235 // The values of x and y do not matter for floats, so don't bother calculating
michael@0 236 // them. Floats are guaranteed to have their own float manager, so tx and ty
michael@0 237 // don't matter. mX and mY don't matter becacuse they are only used in
michael@0 238 // PlaceBlock, which is not used for floats.
michael@0 239 if (aLine) {
michael@0 240 // Compute x/y coordinate where reflow will begin. Use the rules
michael@0 241 // from 10.3.3 to determine what to apply. At this point in the
michael@0 242 // reflow auto left/right margins will have a zero value.
michael@0 243
michael@0 244 mX = tx = mSpace.x + aFrameRS.ComputedPhysicalMargin().left;
michael@0 245 mY = ty = mSpace.y + mTopMargin.get() + aClearance;
michael@0 246
michael@0 247 if ((mFrame->GetStateBits() & NS_BLOCK_FLOAT_MGR) == 0)
michael@0 248 aFrameRS.mBlockDelta =
michael@0 249 mOuterReflowState.mBlockDelta + ty - aLine->BStart();
michael@0 250 }
michael@0 251
michael@0 252 // Let frame know that we are reflowing it
michael@0 253 mFrame->WillReflow(mPresContext);
michael@0 254
michael@0 255 #ifdef DEBUG
michael@0 256 mMetrics.Width() = nscoord(0xdeadbeef);
michael@0 257 mMetrics.Height() = nscoord(0xdeadbeef);
michael@0 258 #endif
michael@0 259
michael@0 260 mOuterReflowState.mFloatManager->Translate(tx, ty);
michael@0 261 rv = mFrame->Reflow(mPresContext, mMetrics, aFrameRS, aFrameReflowStatus);
michael@0 262 mOuterReflowState.mFloatManager->Translate(-tx, -ty);
michael@0 263
michael@0 264 #ifdef DEBUG
michael@0 265 if (!NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus)) {
michael@0 266 if (CRAZY_SIZE(mMetrics.Width()) || CRAZY_SIZE(mMetrics.Height())) {
michael@0 267 printf("nsBlockReflowContext: ");
michael@0 268 nsFrame::ListTag(stdout, mFrame);
michael@0 269 printf(" metrics=%d,%d!\n", mMetrics.Width(), mMetrics.Height());
michael@0 270 }
michael@0 271 if ((mMetrics.Width() == nscoord(0xdeadbeef)) ||
michael@0 272 (mMetrics.Height() == nscoord(0xdeadbeef))) {
michael@0 273 printf("nsBlockReflowContext: ");
michael@0 274 nsFrame::ListTag(stdout, mFrame);
michael@0 275 printf(" didn't set w/h %d,%d!\n", mMetrics.Width(), mMetrics.Height());
michael@0 276 }
michael@0 277 }
michael@0 278 #endif
michael@0 279
michael@0 280 if (!mFrame->HasOverflowAreas()) {
michael@0 281 mMetrics.SetOverflowAreasToDesiredBounds();
michael@0 282 }
michael@0 283
michael@0 284 if (!NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus) ||
michael@0 285 (mFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
michael@0 286 // If frame is complete and has a next-in-flow, we need to delete
michael@0 287 // them now. Do not do this when a break-before is signaled because
michael@0 288 // the frame is going to get reflowed again (and may end up wanting
michael@0 289 // a next-in-flow where it ends up), unless it is an out of flow frame.
michael@0 290 if (NS_FRAME_IS_FULLY_COMPLETE(aFrameReflowStatus)) {
michael@0 291 nsIFrame* kidNextInFlow = mFrame->GetNextInFlow();
michael@0 292 if (nullptr != kidNextInFlow) {
michael@0 293 // Remove all of the childs next-in-flows. Make sure that we ask
michael@0 294 // the right parent to do the removal (it's possible that the
michael@0 295 // parent is not this because we are executing pullup code).
michael@0 296 // Floats will eventually be removed via nsBlockFrame::RemoveFloat
michael@0 297 // which detaches the placeholder from the float.
michael@0 298 nsOverflowContinuationTracker::AutoFinish fini(aState.mOverflowTracker, mFrame);
michael@0 299 static_cast<nsContainerFrame*>(kidNextInFlow->GetParent())
michael@0 300 ->DeleteNextInFlowChild(kidNextInFlow, true);
michael@0 301 }
michael@0 302 }
michael@0 303 }
michael@0 304
michael@0 305 return rv;
michael@0 306 }
michael@0 307
michael@0 308 /**
michael@0 309 * Attempt to place the block frame within the available space. If
michael@0 310 * it fits, apply horizontal positioning (CSS 10.3.3), collapse
michael@0 311 * margins (CSS2 8.3.1). Also apply relative positioning.
michael@0 312 */
michael@0 313 bool
michael@0 314 nsBlockReflowContext::PlaceBlock(const nsHTMLReflowState& aReflowState,
michael@0 315 bool aForceFit,
michael@0 316 nsLineBox* aLine,
michael@0 317 nsCollapsingMargin& aBottomMarginResult,
michael@0 318 nsOverflowAreas& aOverflowAreas,
michael@0 319 nsReflowStatus aReflowStatus,
michael@0 320 nscoord aContainerWidth)
michael@0 321 {
michael@0 322 // Compute collapsed bottom margin value.
michael@0 323 if (NS_FRAME_IS_COMPLETE(aReflowStatus)) {
michael@0 324 aBottomMarginResult = mMetrics.mCarriedOutBottomMargin;
michael@0 325 aBottomMarginResult.Include(aReflowState.ComputedPhysicalMargin().bottom);
michael@0 326 } else {
michael@0 327 // The used bottom-margin is set to zero above a break.
michael@0 328 aBottomMarginResult.Zero();
michael@0 329 }
michael@0 330
michael@0 331 nsPoint position(mX, mY);
michael@0 332 nscoord backupContainingBlockAdvance = 0;
michael@0 333
michael@0 334 // Check whether the block's bottom margin collapses with its top
michael@0 335 // margin. See CSS 2.1 section 8.3.1; those rules seem to match
michael@0 336 // nsBlockFrame::IsEmpty(). Any such block must have zero height so
michael@0 337 // check that first. Note that a block can have clearance and still
michael@0 338 // have adjoining top/bottom margins, because the clearance goes
michael@0 339 // above the top margin.
michael@0 340 // Mark the frame as non-dirty; it has been reflowed (or we wouldn't
michael@0 341 // be here), and we don't want to assert in CachedIsEmpty()
michael@0 342 mFrame->RemoveStateBits(NS_FRAME_IS_DIRTY);
michael@0 343 bool empty = 0 == mMetrics.Height() && aLine->CachedIsEmpty();
michael@0 344 if (empty) {
michael@0 345 // Collapse the bottom margin with the top margin that was already
michael@0 346 // applied.
michael@0 347 aBottomMarginResult.Include(mTopMargin);
michael@0 348
michael@0 349 #ifdef NOISY_VERTICAL_MARGINS
michael@0 350 printf(" ");
michael@0 351 nsFrame::ListTag(stdout, mOuterReflowState.frame);
michael@0 352 printf(": ");
michael@0 353 nsFrame::ListTag(stdout, mFrame);
michael@0 354 printf(" -- collapsing top & bottom margin together; y=%d spaceY=%d\n",
michael@0 355 position.y, mSpace.y);
michael@0 356 #endif
michael@0 357 // Section 8.3.1 of CSS 2.1 says that blocks with adjoining
michael@0 358 // top/bottom margins whose top margin collapses with their
michael@0 359 // parent's top margin should have their top border-edge at the
michael@0 360 // top border-edge of their parent. We actually don't have to do
michael@0 361 // anything special to make this happen. In that situation,
michael@0 362 // nsBlockFrame::ShouldApplyTopMargin will have returned false,
michael@0 363 // and mTopMargin and aClearance will have been zero in
michael@0 364 // ReflowBlock.
michael@0 365
michael@0 366 // If we did apply our top margin, but now we're collapsing it
michael@0 367 // into the bottom margin, we need to back up the containing
michael@0 368 // block's y-advance by our top margin so that it doesn't get
michael@0 369 // counted twice. Note that here we're allowing the line's bounds
michael@0 370 // to become different from the block's position; we do this
michael@0 371 // because the containing block will place the next line at the
michael@0 372 // line's YMost, and it must place the next line at a different
michael@0 373 // point from where this empty block will be.
michael@0 374 backupContainingBlockAdvance = mTopMargin.get();
michael@0 375 }
michael@0 376
michael@0 377 // See if the frame fit. If it's the first frame or empty then it
michael@0 378 // always fits. If the height is unconstrained then it always fits,
michael@0 379 // even if there's some sort of integer overflow that makes y +
michael@0 380 // mMetrics.Height() appear to go beyond the available height.
michael@0 381 if (!empty && !aForceFit && mSpace.height != NS_UNCONSTRAINEDSIZE) {
michael@0 382 nscoord yMost = position.y - backupContainingBlockAdvance + mMetrics.Height();
michael@0 383 if (yMost > mSpace.YMost()) {
michael@0 384 // didn't fit, we must acquit.
michael@0 385 mFrame->DidReflow(mPresContext, &aReflowState, nsDidReflowStatus::FINISHED);
michael@0 386 return false;
michael@0 387 }
michael@0 388 }
michael@0 389
michael@0 390 aLine->SetBounds(aReflowState.GetWritingMode(),
michael@0 391 nsRect(position.x,
michael@0 392 position.y - backupContainingBlockAdvance,
michael@0 393 mMetrics.Width(),
michael@0 394 mMetrics.Height()),
michael@0 395 aContainerWidth);
michael@0 396
michael@0 397 aReflowState.ApplyRelativePositioning(&position);
michael@0 398
michael@0 399 // Now place the frame and complete the reflow process
michael@0 400 nsContainerFrame::FinishReflowChild(mFrame, mPresContext, mMetrics,
michael@0 401 &aReflowState, position.x, position.y, 0);
michael@0 402
michael@0 403 aOverflowAreas = mMetrics.mOverflowAreas + position;
michael@0 404
michael@0 405 return true;
michael@0 406 }

mercurial