layout/generic/nsBlockReflowContext.cpp

changeset 0
6474c204b198
     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 +}

mercurial