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