michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: // vim:cindent:ts=2:et:sw=2: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* state used in reflow of block frames */ michael@0: michael@0: #include "nsBlockReflowState.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "nsBlockFrame.h" michael@0: #include "nsLineLayout.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIFrameInlines.h" michael@0: #include "mozilla/AutoRestore.h" michael@0: #include michael@0: michael@0: #ifdef DEBUG michael@0: #include "nsBlockDebugFlags.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::layout; michael@0: michael@0: nsBlockReflowState::nsBlockReflowState(const nsHTMLReflowState& aReflowState, michael@0: nsPresContext* aPresContext, michael@0: nsBlockFrame* aFrame, michael@0: bool aTopMarginRoot, michael@0: bool aBottomMarginRoot, michael@0: bool aBlockNeedsFloatManager, michael@0: nscoord aConsumedHeight) michael@0: : mBlock(aFrame), michael@0: mPresContext(aPresContext), michael@0: mReflowState(aReflowState), michael@0: mPushedFloats(nullptr), michael@0: mOverflowTracker(nullptr), michael@0: mPrevBottomMargin(), michael@0: mLineNumber(0), michael@0: mFlags(0), michael@0: mFloatBreakType(NS_STYLE_CLEAR_NONE), michael@0: mConsumedHeight(aConsumedHeight) michael@0: { michael@0: SetFlag(BRS_ISFIRSTINFLOW, aFrame->GetPrevInFlow() == nullptr); michael@0: SetFlag(BRS_ISOVERFLOWCONTAINER, michael@0: IS_TRUE_OVERFLOW_CONTAINER(aFrame)); michael@0: michael@0: const nsMargin& borderPadding = BorderPadding(); michael@0: mContainerWidth = aReflowState.ComputedWidth() + michael@0: aReflowState.ComputedPhysicalBorderPadding().LeftRight(); michael@0: michael@0: if (aTopMarginRoot || 0 != aReflowState.ComputedPhysicalBorderPadding().top) { michael@0: SetFlag(BRS_ISTOPMARGINROOT, true); michael@0: } michael@0: if (aBottomMarginRoot || 0 != aReflowState.ComputedPhysicalBorderPadding().bottom) { michael@0: SetFlag(BRS_ISBOTTOMMARGINROOT, true); michael@0: } michael@0: if (GetFlag(BRS_ISTOPMARGINROOT)) { michael@0: SetFlag(BRS_APPLYTOPMARGIN, true); michael@0: } michael@0: if (aBlockNeedsFloatManager) { michael@0: SetFlag(BRS_FLOAT_MGR, true); michael@0: } michael@0: michael@0: mFloatManager = aReflowState.mFloatManager; michael@0: michael@0: NS_ASSERTION(mFloatManager, michael@0: "FloatManager should be set in nsBlockReflowState" ); michael@0: if (mFloatManager) { michael@0: // Save the coordinate system origin for later. michael@0: mFloatManager->GetTranslation(mFloatManagerX, mFloatManagerY); michael@0: mFloatManager->PushState(&mFloatManagerStateBefore); // never popped michael@0: } michael@0: michael@0: mReflowStatus = NS_FRAME_COMPLETE; michael@0: michael@0: mNextInFlow = static_cast(mBlock->GetNextInFlow()); michael@0: michael@0: NS_WARN_IF_FALSE(NS_UNCONSTRAINEDSIZE != aReflowState.ComputedWidth(), michael@0: "have unconstrained width; this should only result from " michael@0: "very large sizes, not attempts at intrinsic width " michael@0: "calculation"); michael@0: mContentArea.width = aReflowState.ComputedWidth(); michael@0: michael@0: // Compute content area height. Unlike the width, if we have a michael@0: // specified style height we ignore it since extra content is michael@0: // managed by the "overflow" property. When we don't have a michael@0: // specified style height then we may end up limiting our height if michael@0: // the availableHeight is constrained (this situation occurs when we michael@0: // are paginated). michael@0: if (NS_UNCONSTRAINEDSIZE != aReflowState.AvailableHeight()) { michael@0: // We are in a paginated situation. The bottom edge is just inside michael@0: // the bottom border and padding. The content area height doesn't michael@0: // include either border or padding edge. michael@0: mBottomEdge = aReflowState.AvailableHeight() - borderPadding.bottom; michael@0: mContentArea.height = std::max(0, mBottomEdge - borderPadding.top); michael@0: } michael@0: else { michael@0: // When we are not in a paginated situation then we always use michael@0: // an constrained height. michael@0: SetFlag(BRS_UNCONSTRAINEDHEIGHT, true); michael@0: mContentArea.height = mBottomEdge = NS_UNCONSTRAINEDSIZE; michael@0: } michael@0: mContentArea.x = borderPadding.left; michael@0: mY = mContentArea.y = borderPadding.top; michael@0: michael@0: mPrevChild = nullptr; michael@0: mCurrentLine = aFrame->end_lines(); michael@0: michael@0: mMinLineHeight = aReflowState.CalcLineHeight(); michael@0: } michael@0: michael@0: nscoord michael@0: nsBlockReflowState::GetConsumedHeight() michael@0: { michael@0: if (mConsumedHeight == NS_INTRINSICSIZE) { michael@0: mConsumedHeight = mBlock->GetConsumedHeight(); michael@0: } michael@0: michael@0: return mConsumedHeight; michael@0: } michael@0: michael@0: void michael@0: nsBlockReflowState::ComputeReplacedBlockOffsetsForFloats(nsIFrame* aFrame, michael@0: const nsRect& aFloatAvailableSpace, michael@0: nscoord& aLeftResult, michael@0: nscoord& aRightResult) michael@0: { michael@0: // The frame is clueless about the float manager and therefore we michael@0: // only give it free space. An example is a table frame - the michael@0: // tables do not flow around floats. michael@0: // However, we can let its margins intersect floats. michael@0: NS_ASSERTION(aFloatAvailableSpace.x >= mContentArea.x, "bad avail space rect x"); michael@0: NS_ASSERTION(aFloatAvailableSpace.width == 0 || michael@0: aFloatAvailableSpace.XMost() <= mContentArea.XMost(), michael@0: "bad avail space rect width"); michael@0: michael@0: nscoord leftOffset, rightOffset; michael@0: if (aFloatAvailableSpace.width == mContentArea.width) { michael@0: // We don't need to compute margins when there are no floats around. michael@0: leftOffset = 0; michael@0: rightOffset = 0; michael@0: } else { michael@0: nsMargin frameMargin; michael@0: nsCSSOffsetState os(aFrame, mReflowState.rendContext, mContentArea.width); michael@0: frameMargin = os.ComputedPhysicalMargin(); michael@0: michael@0: nscoord leftFloatXOffset = aFloatAvailableSpace.x - mContentArea.x; michael@0: leftOffset = std::max(leftFloatXOffset, frameMargin.left) - michael@0: frameMargin.left; michael@0: leftOffset = std::max(leftOffset, 0); // in case of negative margin michael@0: nscoord rightFloatXOffset = michael@0: mContentArea.XMost() - aFloatAvailableSpace.XMost(); michael@0: rightOffset = std::max(rightFloatXOffset, frameMargin.right) - michael@0: frameMargin.right; michael@0: rightOffset = std::max(rightOffset, 0); // in case of negative margin michael@0: } michael@0: aLeftResult = leftOffset; michael@0: aRightResult = rightOffset; michael@0: } michael@0: michael@0: // Compute the amount of available space for reflowing a block frame michael@0: // at the current Y coordinate. This method assumes that michael@0: // GetAvailableSpace has already been called. michael@0: void michael@0: nsBlockReflowState::ComputeBlockAvailSpace(nsIFrame* aFrame, michael@0: const nsStyleDisplay* aDisplay, michael@0: const nsFlowAreaRect& aFloatAvailableSpace, michael@0: bool aBlockAvoidsFloats, michael@0: nsRect& aResult) michael@0: { michael@0: #ifdef REALLY_NOISY_REFLOW michael@0: printf("CBAS frame=%p has floats %d\n", michael@0: aFrame, aFloatAvailableSpace.mHasFloats); michael@0: #endif michael@0: aResult.y = mY; michael@0: aResult.height = GetFlag(BRS_UNCONSTRAINEDHEIGHT) michael@0: ? NS_UNCONSTRAINEDSIZE michael@0: : mReflowState.AvailableHeight() - mY; michael@0: // mY might be greater than mBottomEdge if the block's top margin pushes michael@0: // it off the page/column. Negative available height can confuse other code michael@0: // and is nonsense in principle. michael@0: michael@0: // XXX Do we really want this condition to be this restrictive (i.e., michael@0: // more restrictive than it used to be)? The |else| here is allowed michael@0: // by the CSS spec, but only out of desperation given implementations, michael@0: // and the behavior it leads to is quite undesirable (it can cause michael@0: // things to become extremely narrow when they'd fit quite well a michael@0: // little bit lower). Should the else be a quirk or something that michael@0: // applies to a specific set of frame classes and no new ones? michael@0: // If we did that, then for those frames where the condition below is michael@0: // true but nsBlockFrame::BlockCanIntersectFloats is false, michael@0: // nsBlockFrame::WidthToClearPastFloats would need to use the michael@0: // shrink-wrap formula, max(MIN_WIDTH, min(avail width, PREF_WIDTH)) michael@0: // rather than just using MIN_WIDTH. michael@0: NS_ASSERTION(nsBlockFrame::BlockCanIntersectFloats(aFrame) == michael@0: !aBlockAvoidsFloats, michael@0: "unexpected replaced width"); michael@0: if (!aBlockAvoidsFloats) { michael@0: if (aFloatAvailableSpace.mHasFloats) { michael@0: // Use the float-edge property to determine how the child block michael@0: // will interact with the float. michael@0: const nsStyleBorder* borderStyle = aFrame->StyleBorder(); michael@0: switch (borderStyle->mFloatEdge) { michael@0: default: michael@0: case NS_STYLE_FLOAT_EDGE_CONTENT: // content and only content does runaround of floats michael@0: // The child block will flow around the float. Therefore michael@0: // give it all of the available space. michael@0: aResult.x = mContentArea.x; michael@0: aResult.width = mContentArea.width; michael@0: break; michael@0: case NS_STYLE_FLOAT_EDGE_MARGIN: michael@0: { michael@0: // The child block's margins should be placed adjacent to, michael@0: // but not overlap the float. michael@0: aResult.x = aFloatAvailableSpace.mRect.x; michael@0: aResult.width = aFloatAvailableSpace.mRect.width; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: else { michael@0: // Since there are no floats present the float-edge property michael@0: // doesn't matter therefore give the block element all of the michael@0: // available space since it will flow around the float itself. michael@0: aResult.x = mContentArea.x; michael@0: aResult.width = mContentArea.width; michael@0: } michael@0: } michael@0: else { michael@0: nscoord leftOffset, rightOffset; michael@0: ComputeReplacedBlockOffsetsForFloats(aFrame, aFloatAvailableSpace.mRect, michael@0: leftOffset, rightOffset); michael@0: aResult.x = mContentArea.x + leftOffset; michael@0: aResult.width = mContentArea.width - leftOffset - rightOffset; michael@0: } michael@0: michael@0: #ifdef REALLY_NOISY_REFLOW michael@0: printf(" CBAS: result %d %d %d %d\n", aResult.x, aResult.y, aResult.width, aResult.height); michael@0: #endif michael@0: } michael@0: michael@0: nsFlowAreaRect michael@0: nsBlockReflowState::GetFloatAvailableSpaceWithState( michael@0: nscoord aY, michael@0: nsFloatManager::SavedState *aState) const michael@0: { michael@0: #ifdef DEBUG michael@0: // Verify that the caller setup the coordinate system properly michael@0: nscoord wx, wy; michael@0: mFloatManager->GetTranslation(wx, wy); michael@0: NS_ASSERTION((wx == mFloatManagerX) && (wy == mFloatManagerY), michael@0: "bad coord system"); michael@0: #endif michael@0: michael@0: nscoord height = (mContentArea.height == nscoord_MAX) michael@0: ? nscoord_MAX : std::max(mContentArea.YMost() - aY, 0); michael@0: nsFlowAreaRect result = michael@0: mFloatManager->GetFlowArea(aY, nsFloatManager::BAND_FROM_POINT, michael@0: height, mContentArea, aState); michael@0: // Keep the width >= 0 for compatibility with nsSpaceManager. michael@0: if (result.mRect.width < 0) michael@0: result.mRect.width = 0; michael@0: michael@0: #ifdef DEBUG michael@0: if (nsBlockFrame::gNoisyReflow) { michael@0: nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent); michael@0: printf("GetAvailableSpace: band=%d,%d,%d,%d hasfloats=%d\n", michael@0: result.mRect.x, result.mRect.y, result.mRect.width, michael@0: result.mRect.height, result.mHasFloats); michael@0: } michael@0: #endif michael@0: return result; michael@0: } michael@0: michael@0: nsFlowAreaRect michael@0: nsBlockReflowState::GetFloatAvailableSpaceForHeight( michael@0: nscoord aY, nscoord aHeight, michael@0: nsFloatManager::SavedState *aState) const michael@0: { michael@0: #ifdef DEBUG michael@0: // Verify that the caller setup the coordinate system properly michael@0: nscoord wx, wy; michael@0: mFloatManager->GetTranslation(wx, wy); michael@0: NS_ASSERTION((wx == mFloatManagerX) && (wy == mFloatManagerY), michael@0: "bad coord system"); michael@0: #endif michael@0: michael@0: nsFlowAreaRect result = michael@0: mFloatManager->GetFlowArea(aY, nsFloatManager::WIDTH_WITHIN_HEIGHT, michael@0: aHeight, mContentArea, aState); michael@0: // Keep the width >= 0 for compatibility with nsSpaceManager. michael@0: if (result.mRect.width < 0) michael@0: result.mRect.width = 0; michael@0: michael@0: #ifdef DEBUG michael@0: if (nsBlockFrame::gNoisyReflow) { michael@0: nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent); michael@0: printf("GetAvailableSpaceForHeight: space=%d,%d,%d,%d hasfloats=%d\n", michael@0: result.mRect.x, result.mRect.y, result.mRect.width, michael@0: result.mRect.height, result.mHasFloats); michael@0: } michael@0: #endif michael@0: return result; michael@0: } michael@0: michael@0: /* michael@0: * Reconstruct the vertical margin before the line |aLine| in order to michael@0: * do an incremental reflow that begins with |aLine| without reflowing michael@0: * the line before it. |aLine| may point to the fencepost at the end of michael@0: * the line list, and it is used this way since we (for now, anyway) michael@0: * always need to recover margins at the end of a block. michael@0: * michael@0: * The reconstruction involves walking backward through the line list to michael@0: * find any collapsed margins preceding the line that would have been in michael@0: * the reflow state's |mPrevBottomMargin| when we reflowed that line in michael@0: * a full reflow (under the rule in CSS2 that all adjacent vertical michael@0: * margins of blocks collapse). michael@0: */ michael@0: void michael@0: nsBlockReflowState::ReconstructMarginAbove(nsLineList::iterator aLine) michael@0: { michael@0: mPrevBottomMargin.Zero(); michael@0: nsBlockFrame *block = mBlock; michael@0: michael@0: nsLineList::iterator firstLine = block->begin_lines(); michael@0: for (;;) { michael@0: --aLine; michael@0: if (aLine->IsBlock()) { michael@0: mPrevBottomMargin = aLine->GetCarriedOutBottomMargin(); michael@0: break; michael@0: } michael@0: if (!aLine->IsEmpty()) { michael@0: break; michael@0: } michael@0: if (aLine == firstLine) { michael@0: // If the top margin was carried out (and thus already applied), michael@0: // set it to zero. Either way, we're done. michael@0: if (!GetFlag(BRS_ISTOPMARGINROOT)) { michael@0: mPrevBottomMargin.Zero(); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockReflowState::SetupPushedFloatList() michael@0: { michael@0: NS_ABORT_IF_FALSE(!GetFlag(BRS_PROPTABLE_FLOATCLIST) == !mPushedFloats, michael@0: "flag mismatch"); michael@0: if (!GetFlag(BRS_PROPTABLE_FLOATCLIST)) { michael@0: // If we're being re-Reflow'd without our next-in-flow having been michael@0: // reflowed, some pushed floats from our previous reflow might michael@0: // still be on our pushed floats list. However, that's michael@0: // actually fine, since they'll all end up being stolen and michael@0: // reordered into the correct order again. michael@0: // (nsBlockFrame::ReflowDirtyLines ensures that any lines with michael@0: // pushed floats are reflowed.) michael@0: mPushedFloats = mBlock->EnsurePushedFloats(); michael@0: SetFlag(BRS_PROPTABLE_FLOATCLIST, true); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockReflowState::AppendPushedFloat(nsIFrame* aFloatCont) michael@0: { michael@0: SetupPushedFloatList(); michael@0: aFloatCont->AddStateBits(NS_FRAME_IS_PUSHED_FLOAT); michael@0: mPushedFloats->AppendFrame(mBlock, aFloatCont); michael@0: } michael@0: michael@0: /** michael@0: * Restore information about floats into the float manager for an michael@0: * incremental reflow, and simultaneously push the floats by michael@0: * |aDeltaY|, which is the amount |aLine| was pushed relative to its michael@0: * parent. The recovery of state is one of the things that makes michael@0: * incremental reflow O(N^2) and this state should really be kept michael@0: * around, attached to the frame tree. michael@0: */ michael@0: void michael@0: nsBlockReflowState::RecoverFloats(nsLineList::iterator aLine, michael@0: nscoord aDeltaY) michael@0: { michael@0: if (aLine->HasFloats()) { michael@0: // Place the floats into the space-manager again. Also slide michael@0: // them, just like the regular frames on the line. michael@0: nsFloatCache* fc = aLine->GetFirstFloat(); michael@0: while (fc) { michael@0: nsIFrame* floatFrame = fc->mFloat; michael@0: if (aDeltaY != 0) { michael@0: floatFrame->MovePositionBy(nsPoint(0, aDeltaY)); michael@0: nsContainerFrame::PositionFrameView(floatFrame); michael@0: nsContainerFrame::PositionChildViews(floatFrame); michael@0: } michael@0: #ifdef DEBUG michael@0: if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisyFloatManager) { michael@0: nscoord tx, ty; michael@0: mFloatManager->GetTranslation(tx, ty); michael@0: nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent); michael@0: printf("RecoverFloats: txy=%d,%d (%d,%d) ", michael@0: tx, ty, mFloatManagerX, mFloatManagerY); michael@0: nsFrame::ListTag(stdout, floatFrame); michael@0: nsRect region = nsFloatManager::GetRegionFor(floatFrame); michael@0: printf(" aDeltaY=%d region={%d,%d,%d,%d}\n", michael@0: aDeltaY, region.x, region.y, region.width, region.height); michael@0: } michael@0: #endif michael@0: mFloatManager->AddFloat(floatFrame, michael@0: nsFloatManager::GetRegionFor(floatFrame)); michael@0: fc = fc->Next(); michael@0: } michael@0: } else if (aLine->IsBlock()) { michael@0: nsBlockFrame::RecoverFloatsFor(aLine->mFirstChild, *mFloatManager); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Everything done in this function is done O(N) times for each pass of michael@0: * reflow so it is O(N*M) where M is the number of incremental reflow michael@0: * passes. That's bad. Don't do stuff here. michael@0: * michael@0: * When this function is called, |aLine| has just been slid by |aDeltaY| michael@0: * and the purpose of RecoverStateFrom is to ensure that the michael@0: * nsBlockReflowState is in the same state that it would have been in michael@0: * had the line just been reflowed. michael@0: * michael@0: * Most of the state recovery that we have to do involves floats. michael@0: */ michael@0: void michael@0: nsBlockReflowState::RecoverStateFrom(nsLineList::iterator aLine, michael@0: nscoord aDeltaY) michael@0: { michael@0: // Make the line being recovered the current line michael@0: mCurrentLine = aLine; michael@0: michael@0: // Place floats for this line into the float manager michael@0: if (aLine->HasFloats() || aLine->IsBlock()) { michael@0: RecoverFloats(aLine, aDeltaY); michael@0: michael@0: #ifdef DEBUG michael@0: if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisyFloatManager) { michael@0: mFloatManager->List(stdout); michael@0: } michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: // This is called by the line layout's AddFloat method when a michael@0: // place-holder frame is reflowed in a line. If the float is a michael@0: // left-most child (it's x coordinate is at the line's left margin) michael@0: // then the float is place immediately, otherwise the float michael@0: // placement is deferred until the line has been reflowed. michael@0: michael@0: // XXXldb This behavior doesn't quite fit with CSS1 and CSS2 -- michael@0: // technically we're supposed let the current line flow around the michael@0: // float as well unless it won't fit next to what we already have. michael@0: // But nobody else implements it that way... michael@0: bool michael@0: nsBlockReflowState::AddFloat(nsLineLayout* aLineLayout, michael@0: nsIFrame* aFloat, michael@0: nscoord aAvailableWidth) michael@0: { michael@0: NS_PRECONDITION(aLineLayout, "must have line layout"); michael@0: NS_PRECONDITION(mBlock->end_lines() != mCurrentLine, "null ptr"); michael@0: NS_PRECONDITION(aFloat->GetStateBits() & NS_FRAME_OUT_OF_FLOW, michael@0: "aFloat must be an out-of-flow frame"); michael@0: michael@0: NS_ABORT_IF_FALSE(aFloat->GetParent(), "float must have parent"); michael@0: NS_ABORT_IF_FALSE(aFloat->GetParent()->IsFrameOfType(nsIFrame::eBlockFrame), michael@0: "float's parent must be block"); michael@0: NS_ABORT_IF_FALSE(aFloat->GetParent() == mBlock || michael@0: (aFloat->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT), michael@0: "float should be in this block unless it was marked as " michael@0: "pushed float"); michael@0: if (aFloat->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT) { michael@0: // If, in a previous reflow, the float was pushed entirely to michael@0: // another column/page, we need to steal it back. (We might just michael@0: // push it again, though.) Likewise, if that previous reflow michael@0: // reflowed this block but not its next continuation, we might need michael@0: // to steal it from our own float-continuations list. michael@0: // michael@0: // For more about pushed floats, see the comment above michael@0: // nsBlockFrame::DrainPushedFloats. michael@0: nsBlockFrame *floatParent = michael@0: static_cast(aFloat->GetParent()); michael@0: floatParent->StealFrame(aFloat); michael@0: michael@0: aFloat->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT); michael@0: michael@0: // Appending is fine, since if a float was pushed to the next michael@0: // page/column, all later floats were also pushed. michael@0: mBlock->mFloats.AppendFrame(mBlock, aFloat); michael@0: } michael@0: michael@0: // Because we are in the middle of reflowing a placeholder frame michael@0: // within a line (and possibly nested in an inline frame or two michael@0: // that's a child of our block) we need to restore the space michael@0: // manager's translation to the space that the block resides in michael@0: // before placing the float. michael@0: nscoord ox, oy; michael@0: mFloatManager->GetTranslation(ox, oy); michael@0: nscoord dx = ox - mFloatManagerX; michael@0: nscoord dy = oy - mFloatManagerY; michael@0: mFloatManager->Translate(-dx, -dy); michael@0: michael@0: bool placed; michael@0: michael@0: // Now place the float immediately if possible. Otherwise stash it michael@0: // away in mPendingFloats and place it later. michael@0: // If one or more floats has already been pushed to the next line, michael@0: // don't let this one go on the current line, since that would violate michael@0: // float ordering. michael@0: nsRect floatAvailableSpace = GetFloatAvailableSpace().mRect; michael@0: if (mBelowCurrentLineFloats.IsEmpty() && michael@0: (aLineLayout->LineIsEmpty() || michael@0: mBlock->ComputeFloatWidth(*this, floatAvailableSpace, aFloat) michael@0: <= aAvailableWidth)) { michael@0: // And then place it michael@0: placed = FlowAndPlaceFloat(aFloat); michael@0: if (placed) { michael@0: // Pass on updated available space to the current inline reflow engine michael@0: nsFlowAreaRect floatAvailSpace = GetFloatAvailableSpace(mY); michael@0: nsRect availSpace(nsPoint(floatAvailSpace.mRect.x, mY), michael@0: floatAvailSpace.mRect.Size()); michael@0: aLineLayout->UpdateBand(availSpace, aFloat); michael@0: // Record this float in the current-line list michael@0: mCurrentLineFloats.Append(mFloatCacheFreeList.Alloc(aFloat)); michael@0: } else { michael@0: (*aLineLayout->GetLine())->SetHadFloatPushed(); michael@0: } michael@0: } michael@0: else { michael@0: // Always claim to be placed; we don't know whether we fit yet, so we michael@0: // deal with this in PlaceBelowCurrentLineFloats michael@0: placed = true; michael@0: // This float will be placed after the line is done (it is a michael@0: // below-current-line float). michael@0: mBelowCurrentLineFloats.Append(mFloatCacheFreeList.Alloc(aFloat)); michael@0: } michael@0: michael@0: // Restore coordinate system michael@0: mFloatManager->Translate(dx, dy); michael@0: michael@0: return placed; michael@0: } michael@0: michael@0: bool michael@0: nsBlockReflowState::CanPlaceFloat(nscoord aFloatWidth, michael@0: const nsFlowAreaRect& aFloatAvailableSpace) michael@0: { michael@0: // A float fits at a given vertical position if there are no floats at michael@0: // its horizontal position (no matter what its width) or if its width michael@0: // fits in the space remaining after prior floats have been placed. michael@0: // FIXME: We should allow overflow by up to half a pixel here (bug 21193). michael@0: return !aFloatAvailableSpace.mHasFloats || michael@0: aFloatAvailableSpace.mRect.width >= aFloatWidth; michael@0: } michael@0: michael@0: static nscoord michael@0: FloatMarginWidth(const nsHTMLReflowState& aCBReflowState, michael@0: nscoord aFloatAvailableWidth, michael@0: nsIFrame *aFloat, michael@0: const nsCSSOffsetState& aFloatOffsetState) michael@0: { michael@0: AutoMaybeDisableFontInflation an(aFloat); michael@0: return aFloat->ComputeSize( michael@0: aCBReflowState.rendContext, michael@0: nsSize(aCBReflowState.ComputedWidth(), michael@0: aCBReflowState.ComputedHeight()), michael@0: aFloatAvailableWidth, michael@0: nsSize(aFloatOffsetState.ComputedPhysicalMargin().LeftRight(), michael@0: aFloatOffsetState.ComputedPhysicalMargin().TopBottom()), michael@0: nsSize(aFloatOffsetState.ComputedPhysicalBorderPadding().LeftRight() - michael@0: aFloatOffsetState.ComputedPhysicalPadding().LeftRight(), michael@0: aFloatOffsetState.ComputedPhysicalBorderPadding().TopBottom() - michael@0: aFloatOffsetState.ComputedPhysicalPadding().TopBottom()), michael@0: nsSize(aFloatOffsetState.ComputedPhysicalPadding().LeftRight(), michael@0: aFloatOffsetState.ComputedPhysicalPadding().TopBottom()), michael@0: true).width + michael@0: aFloatOffsetState.ComputedPhysicalMargin().LeftRight() + michael@0: aFloatOffsetState.ComputedPhysicalBorderPadding().LeftRight(); michael@0: } michael@0: michael@0: bool michael@0: nsBlockReflowState::FlowAndPlaceFloat(nsIFrame* aFloat) michael@0: { michael@0: // Save away the Y coordinate before placing the float. We will michael@0: // restore mY at the end after placing the float. This is michael@0: // necessary because any adjustments to mY during the float michael@0: // placement are for the float only, not for any non-floating michael@0: // content. michael@0: AutoRestore restoreY(mY); michael@0: // FIXME: Should give AutoRestore a getter for the value to avoid this. michael@0: const nscoord saveY = mY; michael@0: michael@0: // Grab the float's display information michael@0: const nsStyleDisplay* floatDisplay = aFloat->StyleDisplay(); michael@0: michael@0: // The float's old region, so we can propagate damage. michael@0: nsRect oldRegion = nsFloatManager::GetRegionFor(aFloat); michael@0: michael@0: // Enforce CSS2 9.5.1 rule [2], i.e., make sure that a float isn't michael@0: // ``above'' another float that preceded it in the flow. michael@0: mY = std::max(mFloatManager->GetLowestFloatTop(), mY); michael@0: michael@0: // See if the float should clear any preceding floats... michael@0: // XXX We need to mark this float somehow so that it gets reflowed michael@0: // when floats are inserted before it. michael@0: if (NS_STYLE_CLEAR_NONE != floatDisplay->mBreakType) { michael@0: // XXXldb Does this handle vertical margins correctly? michael@0: mY = ClearFloats(mY, floatDisplay->mBreakType); michael@0: } michael@0: // Get the band of available space michael@0: nsFlowAreaRect floatAvailableSpace = GetFloatAvailableSpace(mY); michael@0: nsRect adjustedAvailableSpace = mBlock->AdjustFloatAvailableSpace(*this, michael@0: floatAvailableSpace.mRect, aFloat); michael@0: michael@0: NS_ASSERTION(aFloat->GetParent() == mBlock, michael@0: "Float frame has wrong parent"); michael@0: michael@0: nsCSSOffsetState offsets(aFloat, mReflowState.rendContext, michael@0: mReflowState.ComputedWidth()); michael@0: michael@0: nscoord floatMarginWidth = FloatMarginWidth(mReflowState, michael@0: adjustedAvailableSpace.width, michael@0: aFloat, offsets); michael@0: michael@0: nsMargin floatMargin; // computed margin michael@0: nsMargin floatOffsets; michael@0: nsReflowStatus reflowStatus; michael@0: michael@0: // If it's a floating first-letter, we need to reflow it before we michael@0: // know how wide it is (since we don't compute which letters are part michael@0: // of the first letter until reflow!). michael@0: bool isLetter = aFloat->GetType() == nsGkAtoms::letterFrame; michael@0: if (isLetter) { michael@0: mBlock->ReflowFloat(*this, adjustedAvailableSpace, aFloat, floatMargin, michael@0: floatOffsets, false, reflowStatus); michael@0: floatMarginWidth = aFloat->GetSize().width + floatMargin.LeftRight(); michael@0: NS_ASSERTION(NS_FRAME_IS_COMPLETE(reflowStatus), michael@0: "letter frames shouldn't break, and if they do now, " michael@0: "then they're breaking at the wrong point"); michael@0: } michael@0: michael@0: // Find a place to place the float. The CSS2 spec doesn't want michael@0: // floats overlapping each other or sticking out of the containing michael@0: // block if possible (CSS2 spec section 9.5.1, see the rule list). michael@0: NS_ASSERTION((NS_STYLE_FLOAT_LEFT == floatDisplay->mFloats) || michael@0: (NS_STYLE_FLOAT_RIGHT == floatDisplay->mFloats), michael@0: "invalid float type"); michael@0: michael@0: // Can the float fit here? michael@0: bool keepFloatOnSameLine = false; michael@0: michael@0: // Are we required to place at least part of the float because we're michael@0: // at the top of the page (to avoid an infinite loop of pushing and michael@0: // breaking). michael@0: bool mustPlaceFloat = michael@0: mReflowState.mFlags.mIsTopOfPage && IsAdjacentWithTop(); michael@0: michael@0: for (;;) { michael@0: if (mReflowState.AvailableHeight() != NS_UNCONSTRAINEDSIZE && michael@0: floatAvailableSpace.mRect.height <= 0 && michael@0: !mustPlaceFloat) { michael@0: // No space, nowhere to put anything. michael@0: PushFloatPastBreak(aFloat); michael@0: return false; michael@0: } michael@0: michael@0: if (CanPlaceFloat(floatMarginWidth, floatAvailableSpace)) { michael@0: // We found an appropriate place. michael@0: break; michael@0: } michael@0: michael@0: // Nope. try to advance to the next band. michael@0: if (NS_STYLE_DISPLAY_TABLE != floatDisplay->mDisplay || michael@0: eCompatibility_NavQuirks != mPresContext->CompatibilityMode() ) { michael@0: michael@0: mY += floatAvailableSpace.mRect.height; michael@0: if (adjustedAvailableSpace.height != NS_UNCONSTRAINEDSIZE) { michael@0: adjustedAvailableSpace.height -= floatAvailableSpace.mRect.height; michael@0: } michael@0: floatAvailableSpace = GetFloatAvailableSpace(mY); michael@0: } else { michael@0: // This quirk matches the one in nsBlockFrame::AdjustFloatAvailableSpace michael@0: // IE handles float tables in a very special way michael@0: michael@0: // see if the previous float is also a table and has "align" michael@0: nsFloatCache* fc = mCurrentLineFloats.Head(); michael@0: nsIFrame* prevFrame = nullptr; michael@0: while (fc) { michael@0: if (fc->mFloat == aFloat) { michael@0: break; michael@0: } michael@0: prevFrame = fc->mFloat; michael@0: fc = fc->Next(); michael@0: } michael@0: michael@0: if(prevFrame) { michael@0: //get the frame type michael@0: if (nsGkAtoms::tableOuterFrame == prevFrame->GetType()) { michael@0: //see if it has "align=" michael@0: // IE makes a difference between align and he float property michael@0: nsIContent* content = prevFrame->GetContent(); michael@0: if (content) { michael@0: // we're interested only if previous frame is align=left michael@0: // IE messes things up when "right" (overlapping frames) michael@0: if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::align, michael@0: NS_LITERAL_STRING("left"), eIgnoreCase)) { michael@0: keepFloatOnSameLine = true; michael@0: // don't advance to next line (IE quirkie behaviour) michael@0: // it breaks rule CSS2/9.5.1/1, but what the hell michael@0: // since we cannot evangelize the world michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // the table does not fit anymore in this line so advance to next band michael@0: mY += floatAvailableSpace.mRect.height; michael@0: // To match nsBlockFrame::AdjustFloatAvailableSpace, we have to michael@0: // get a new width for the new band. michael@0: floatAvailableSpace = GetFloatAvailableSpace(mY); michael@0: adjustedAvailableSpace = mBlock->AdjustFloatAvailableSpace(*this, michael@0: floatAvailableSpace.mRect, aFloat); michael@0: floatMarginWidth = FloatMarginWidth(mReflowState, michael@0: adjustedAvailableSpace.width, michael@0: aFloat, offsets); michael@0: } michael@0: michael@0: mustPlaceFloat = false; michael@0: } michael@0: michael@0: // If the float is continued, it will get the same absolute x value as its prev-in-flow michael@0: michael@0: // We don't worry about the geometry of the prev in flow, let the continuation michael@0: // place and size itself as required. michael@0: michael@0: // Assign an x and y coordinate to the float. michael@0: nscoord floatX, floatY; michael@0: if (NS_STYLE_FLOAT_LEFT == floatDisplay->mFloats) { michael@0: floatX = floatAvailableSpace.mRect.x; michael@0: } michael@0: else { michael@0: if (!keepFloatOnSameLine) { michael@0: floatX = floatAvailableSpace.mRect.XMost() - floatMarginWidth; michael@0: } michael@0: else { michael@0: // this is the IE quirk (see few lines above) michael@0: // the table is kept in the same line: don't let it overlap the michael@0: // previous float michael@0: floatX = floatAvailableSpace.mRect.x; michael@0: } michael@0: } michael@0: // CSS2 spec, 9.5.1 rule [4]: "A floating box's outer top may not michael@0: // be higher than the top of its containing block." (Since the michael@0: // containing block is the content edge of the block box, this michael@0: // means the margin edge of the float can't be higher than the michael@0: // content edge of the block that contains it.) michael@0: floatY = std::max(mY, mContentArea.y); michael@0: michael@0: // Reflow the float after computing its vertical position so it knows michael@0: // where to break. michael@0: if (!isLetter) { michael@0: bool pushedDown = mY != saveY; michael@0: mBlock->ReflowFloat(*this, adjustedAvailableSpace, aFloat, floatMargin, michael@0: floatOffsets, pushedDown, reflowStatus); michael@0: } michael@0: if (aFloat->GetPrevInFlow()) michael@0: floatMargin.top = 0; michael@0: if (NS_FRAME_IS_NOT_COMPLETE(reflowStatus)) michael@0: floatMargin.bottom = 0; michael@0: michael@0: // In the case that we're in columns and not splitting floats, we need michael@0: // to check here that the float's height fit, and if it didn't, bail. michael@0: // (This code is only for DISABLE_FLOAT_BREAKING_IN_COLUMNS .) michael@0: // michael@0: // Likewise, if none of the float fit, and it needs to be pushed in michael@0: // its entirety to the next page (NS_FRAME_IS_TRUNCATED or michael@0: // NS_INLINE_IS_BREAK_BEFORE), we need to do the same. michael@0: if ((mContentArea.height != NS_UNCONSTRAINEDSIZE && michael@0: adjustedAvailableSpace.height == NS_UNCONSTRAINEDSIZE && michael@0: !mustPlaceFloat && michael@0: aFloat->GetSize().height + floatMargin.TopBottom() > michael@0: mContentArea.YMost() - floatY) || michael@0: NS_FRAME_IS_TRUNCATED(reflowStatus) || michael@0: NS_INLINE_IS_BREAK_BEFORE(reflowStatus)) { michael@0: PushFloatPastBreak(aFloat); michael@0: return false; michael@0: } michael@0: michael@0: // We can't use aFloat->ShouldAvoidBreakInside(mReflowState) here since michael@0: // its mIsTopOfPage may be true even though the float isn't at the michael@0: // top when floatY > 0. michael@0: if (mContentArea.height != NS_UNCONSTRAINEDSIZE && michael@0: !mustPlaceFloat && (!mReflowState.mFlags.mIsTopOfPage || floatY > 0) && michael@0: NS_STYLE_PAGE_BREAK_AVOID == aFloat->StyleDisplay()->mBreakInside && michael@0: (!NS_FRAME_IS_FULLY_COMPLETE(reflowStatus) || michael@0: aFloat->GetSize().height + floatMargin.TopBottom() > michael@0: mContentArea.YMost() - floatY) && michael@0: !aFloat->GetPrevInFlow()) { michael@0: PushFloatPastBreak(aFloat); michael@0: return false; michael@0: } michael@0: michael@0: // Calculate the actual origin of the float frame's border rect michael@0: // relative to the parent block; the margin must be added in michael@0: // to get the border rect michael@0: nsPoint origin(floatMargin.left + floatX, michael@0: floatMargin.top + floatY); michael@0: michael@0: // If float is relatively positioned, factor that in as well michael@0: nsHTMLReflowState::ApplyRelativePositioning(aFloat, floatOffsets, &origin); michael@0: michael@0: // Position the float and make sure and views are properly michael@0: // positioned. We need to explicitly position its child views as michael@0: // well, since we're moving the float after flowing it. michael@0: bool moved = aFloat->GetPosition() != origin; michael@0: if (moved) { michael@0: aFloat->SetPosition(origin); michael@0: nsContainerFrame::PositionFrameView(aFloat); michael@0: nsContainerFrame::PositionChildViews(aFloat); michael@0: } michael@0: michael@0: // Update the float combined area state michael@0: // XXX Floats should really just get invalidated here if necessary michael@0: mFloatOverflowAreas.UnionWith(aFloat->GetOverflowAreas() + origin); michael@0: michael@0: // Place the float in the float manager michael@0: // calculate region michael@0: nsRect region = nsFloatManager::CalculateRegionFor(aFloat, floatMargin); michael@0: // if the float split, then take up all of the vertical height michael@0: if (NS_FRAME_IS_NOT_COMPLETE(reflowStatus) && michael@0: (NS_UNCONSTRAINEDSIZE != mContentArea.height)) { michael@0: region.height = std::max(region.height, mContentArea.height - floatY); michael@0: } michael@0: DebugOnly rv = michael@0: mFloatManager->AddFloat(aFloat, region); michael@0: NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "bad float placement"); michael@0: // store region michael@0: nsFloatManager::StoreRegionFor(aFloat, region); michael@0: michael@0: // If the float's dimensions have changed, note the damage in the michael@0: // float manager. michael@0: if (!region.IsEqualEdges(oldRegion)) { michael@0: // XXXwaterson conservative: we could probably get away with noting michael@0: // less damage; e.g., if only height has changed, then only note the michael@0: // area into which the float has grown or from which the float has michael@0: // shrunk. michael@0: nscoord top = std::min(region.y, oldRegion.y); michael@0: nscoord bottom = std::max(region.YMost(), oldRegion.YMost()); michael@0: mFloatManager->IncludeInDamage(top, bottom); michael@0: } michael@0: michael@0: if (!NS_FRAME_IS_FULLY_COMPLETE(reflowStatus)) { michael@0: mBlock->SplitFloat(*this, aFloat, reflowStatus); michael@0: } michael@0: michael@0: #ifdef NOISY_FLOATMANAGER michael@0: nscoord tx, ty; michael@0: mFloatManager->GetTranslation(tx, ty); michael@0: nsFrame::ListTag(stdout, mBlock); michael@0: printf(": FlowAndPlaceFloat: AddFloat: txy=%d,%d (%d,%d) {%d,%d,%d,%d}\n", michael@0: tx, ty, mFloatManagerX, mFloatManagerY, michael@0: region.x, region.y, region.width, region.height); michael@0: #endif michael@0: michael@0: #ifdef DEBUG michael@0: if (nsBlockFrame::gNoisyReflow) { michael@0: nsRect r = aFloat->GetRect(); michael@0: nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent); michael@0: printf("placed float: "); michael@0: nsFrame::ListTag(stdout, aFloat); michael@0: printf(" %d,%d,%d,%d\n", r.x, r.y, r.width, r.height); michael@0: } michael@0: #endif michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsBlockReflowState::PushFloatPastBreak(nsIFrame *aFloat) michael@0: { michael@0: // This ensures that we: michael@0: // * don't try to place later but smaller floats (which CSS says michael@0: // must have their tops below the top of this float) michael@0: // * don't waste much time trying to reflow this float again until michael@0: // after the break michael@0: if (aFloat->StyleDisplay()->mFloats == NS_STYLE_FLOAT_LEFT) { michael@0: mFloatManager->SetPushedLeftFloatPastBreak(); michael@0: } else { michael@0: NS_ABORT_IF_FALSE(aFloat->StyleDisplay()->mFloats == michael@0: NS_STYLE_FLOAT_RIGHT, michael@0: "unexpected float value"); michael@0: mFloatManager->SetPushedRightFloatPastBreak(); michael@0: } michael@0: michael@0: // Put the float on the pushed floats list, even though it michael@0: // isn't actually a continuation. michael@0: DebugOnly rv = mBlock->StealFrame(aFloat); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "StealFrame should succeed"); michael@0: AppendPushedFloat(aFloat); michael@0: michael@0: NS_FRAME_SET_OVERFLOW_INCOMPLETE(mReflowStatus); michael@0: } michael@0: michael@0: /** michael@0: * Place below-current-line floats. michael@0: */ michael@0: void michael@0: nsBlockReflowState::PlaceBelowCurrentLineFloats(nsFloatCacheFreeList& aList, michael@0: nsLineBox* aLine) michael@0: { michael@0: nsFloatCache* fc = aList.Head(); michael@0: while (fc) { michael@0: #ifdef DEBUG michael@0: if (nsBlockFrame::gNoisyReflow) { michael@0: nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent); michael@0: printf("placing bcl float: "); michael@0: nsFrame::ListTag(stdout, fc->mFloat); michael@0: printf("\n"); michael@0: } michael@0: #endif michael@0: // Place the float michael@0: bool placed = FlowAndPlaceFloat(fc->mFloat); michael@0: nsFloatCache *next = fc->Next(); michael@0: if (!placed) { michael@0: aList.Remove(fc); michael@0: delete fc; michael@0: aLine->SetHadFloatPushed(); michael@0: } michael@0: fc = next; michael@0: } michael@0: } michael@0: michael@0: nscoord michael@0: nsBlockReflowState::ClearFloats(nscoord aY, uint8_t aBreakType, michael@0: nsIFrame *aReplacedBlock, michael@0: uint32_t aFlags) michael@0: { michael@0: #ifdef DEBUG michael@0: if (nsBlockFrame::gNoisyReflow) { michael@0: nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent); michael@0: printf("clear floats: in: aY=%d\n", aY); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef NOISY_FLOAT_CLEARING michael@0: printf("nsBlockReflowState::ClearFloats: aY=%d breakType=%d\n", michael@0: aY, aBreakType); michael@0: mFloatManager->List(stdout); michael@0: #endif michael@0: michael@0: if (!mFloatManager->HasAnyFloats()) { michael@0: return aY; michael@0: } michael@0: michael@0: nscoord newY = aY; michael@0: michael@0: if (aBreakType != NS_STYLE_CLEAR_NONE) { michael@0: newY = mFloatManager->ClearFloats(newY, aBreakType, aFlags); michael@0: } michael@0: michael@0: if (aReplacedBlock) { michael@0: for (;;) { michael@0: nsFlowAreaRect floatAvailableSpace = GetFloatAvailableSpace(newY); michael@0: if (!floatAvailableSpace.mHasFloats) { michael@0: // If there aren't any floats here, then we always fit. michael@0: // We check this before calling WidthToClearPastFloats, which is michael@0: // somewhat expensive. michael@0: break; michael@0: } michael@0: nsBlockFrame::ReplacedElementWidthToClear replacedWidth = michael@0: nsBlockFrame::WidthToClearPastFloats(*this, floatAvailableSpace.mRect, michael@0: aReplacedBlock); michael@0: if (std::max(floatAvailableSpace.mRect.x - mContentArea.x, michael@0: replacedWidth.marginLeft) + michael@0: replacedWidth.borderBoxWidth + michael@0: std::max(mContentArea.XMost() - floatAvailableSpace.mRect.XMost(), michael@0: replacedWidth.marginRight) <= michael@0: mContentArea.width) { michael@0: break; michael@0: } michael@0: // See the analogous code for inlines in nsBlockFrame::DoReflowInlineFrames michael@0: if (floatAvailableSpace.mRect.height > 0) { michael@0: // See if there's room in the next band. michael@0: newY += floatAvailableSpace.mRect.height; michael@0: } else { michael@0: if (mReflowState.AvailableHeight() != NS_UNCONSTRAINEDSIZE) { michael@0: // Stop trying to clear here; we'll just get pushed to the michael@0: // next column or page and try again there. michael@0: break; michael@0: } michael@0: NS_NOTREACHED("avail space rect with zero height!"); michael@0: newY += 1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: if (nsBlockFrame::gNoisyReflow) { michael@0: nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent); michael@0: printf("clear floats: out: y=%d\n", newY); michael@0: } michael@0: #endif michael@0: michael@0: return newY; michael@0: } michael@0: