diff --git a/mozilla/layout/base/nsLayoutUtils.cpp b/mozilla/layout/base/nsLayoutUtils.cpp index 51ee20b741d..239897fc072 100644 --- a/mozilla/layout/base/nsLayoutUtils.cpp +++ b/mozilla/layout/base/nsLayoutUtils.cpp @@ -434,3 +434,33 @@ nsLayoutUtils::GetNearestScrollingView(nsIView* aView, Direction aDirection) } return scrollableView; } + +// Combine aNewBreakType with aOrigBreakType, but limit the break types +// to NS_STYLE_CLEAR_LEFT, RIGHT, LEFT_AND_RIGHT. +PRUint8 +nsLayoutUtils::CombineBreakType(PRUint8 aOrigBreakType, + PRUint8 aNewBreakType) +{ + PRUint8 breakType = aOrigBreakType; + switch(breakType) { + case NS_STYLE_CLEAR_LEFT: + if ((NS_STYLE_CLEAR_RIGHT == aNewBreakType) || + (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) { + breakType = NS_STYLE_CLEAR_LEFT_AND_RIGHT; + } + break; + case NS_STYLE_CLEAR_RIGHT: + if ((NS_STYLE_CLEAR_LEFT == aNewBreakType) || + (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) { + breakType = NS_STYLE_CLEAR_LEFT_AND_RIGHT; + } + break; + case NS_STYLE_CLEAR_NONE: + if ((NS_STYLE_CLEAR_LEFT == aNewBreakType) || + (NS_STYLE_CLEAR_RIGHT == aNewBreakType) || + (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) { + breakType = aNewBreakType; + } + } + return breakType; +} diff --git a/mozilla/layout/base/nsLayoutUtils.h b/mozilla/layout/base/nsLayoutUtils.h index 1641961e266..548f1a7a0e0 100644 --- a/mozilla/layout/base/nsLayoutUtils.h +++ b/mozilla/layout/base/nsLayoutUtils.h @@ -236,6 +236,10 @@ public: * otherwise return nsnull. */ static nsIFrame* GetFloatFromPlaceholder(nsIFrame* aPossiblePlaceholder); + + // Combine aNewBreakType with aOrigBreakType, but limit the break types + // to NS_STYLE_CLEAR_LEFT, RIGHT, LEFT_AND_RIGHT. + static PRUint8 CombineBreakType(PRUint8 aOrigBreakType, PRUint8 aNewBreakType); }; #endif // nsLayoutUtils_h__ diff --git a/mozilla/layout/base/public/nsHTMLReflowState.h b/mozilla/layout/base/public/nsHTMLReflowState.h index 5cb0de47324..39d93fcda2d 100644 --- a/mozilla/layout/base/public/nsHTMLReflowState.h +++ b/mozilla/layout/base/public/nsHTMLReflowState.h @@ -237,6 +237,13 @@ struct nsHTMLReflowState { // a frame (e.g. nsTableFrame) which initiates a special reflow for percent height calculations nsIFrame* mPercentHeightReflowInitiator; + // CSS margin collapsing sometimes requires us to reflow + // optimistically assuming that margins collapse to see if clearance + // is required. When we discover that clearance is required, we + // store the frame in which clearance was discovered to the location + // requested here. + nsIFrame** mDiscoveredClearance; + // This value keeps track of how deeply nested a given reflow state // is from the top of the frame tree. PRInt16 mReflowDepth; @@ -249,6 +256,7 @@ struct nsHTMLReflowState { PRUint16 mIsTopOfPage:1; // is the current context at the top of a page? PRUint16 mBlinks:1; // Keep track of text-decoration: blink PRUint16 mVisualBidiFormControl:1; // Keep track of descendants of form controls on Visual Bidi pages + PRUint16 mHasClearance:1; // Block has clearance } mFlags; #ifdef IBMBIDI diff --git a/mozilla/layout/base/public/nsLayoutUtils.h b/mozilla/layout/base/public/nsLayoutUtils.h index 1641961e266..548f1a7a0e0 100644 --- a/mozilla/layout/base/public/nsLayoutUtils.h +++ b/mozilla/layout/base/public/nsLayoutUtils.h @@ -236,6 +236,10 @@ public: * otherwise return nsnull. */ static nsIFrame* GetFloatFromPlaceholder(nsIFrame* aPossiblePlaceholder); + + // Combine aNewBreakType with aOrigBreakType, but limit the break types + // to NS_STYLE_CLEAR_LEFT, RIGHT, LEFT_AND_RIGHT. + static PRUint8 CombineBreakType(PRUint8 aOrigBreakType, PRUint8 aNewBreakType); }; #endif // nsLayoutUtils_h__ diff --git a/mozilla/layout/base/src/nsLayoutUtils.cpp b/mozilla/layout/base/src/nsLayoutUtils.cpp index 51ee20b741d..239897fc072 100644 --- a/mozilla/layout/base/src/nsLayoutUtils.cpp +++ b/mozilla/layout/base/src/nsLayoutUtils.cpp @@ -434,3 +434,33 @@ nsLayoutUtils::GetNearestScrollingView(nsIView* aView, Direction aDirection) } return scrollableView; } + +// Combine aNewBreakType with aOrigBreakType, but limit the break types +// to NS_STYLE_CLEAR_LEFT, RIGHT, LEFT_AND_RIGHT. +PRUint8 +nsLayoutUtils::CombineBreakType(PRUint8 aOrigBreakType, + PRUint8 aNewBreakType) +{ + PRUint8 breakType = aOrigBreakType; + switch(breakType) { + case NS_STYLE_CLEAR_LEFT: + if ((NS_STYLE_CLEAR_RIGHT == aNewBreakType) || + (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) { + breakType = NS_STYLE_CLEAR_LEFT_AND_RIGHT; + } + break; + case NS_STYLE_CLEAR_RIGHT: + if ((NS_STYLE_CLEAR_LEFT == aNewBreakType) || + (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) { + breakType = NS_STYLE_CLEAR_LEFT_AND_RIGHT; + } + break; + case NS_STYLE_CLEAR_NONE: + if ((NS_STYLE_CLEAR_LEFT == aNewBreakType) || + (NS_STYLE_CLEAR_RIGHT == aNewBreakType) || + (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) { + breakType = aNewBreakType; + } + } + return breakType; +} diff --git a/mozilla/layout/base/src/nsSpaceManager.cpp b/mozilla/layout/base/src/nsSpaceManager.cpp index 6b79a1890b6..3e50acf7289 100644 --- a/mozilla/layout/base/src/nsSpaceManager.cpp +++ b/mozilla/layout/base/src/nsSpaceManager.cpp @@ -1080,6 +1080,22 @@ nsSpaceManager::PopState() } } +void +nsSpaceManager::DiscardState() +{ + NS_ASSERTION(mSavedStates, "Invalid call to DiscardState()!"); + + if (!mSavedStates) { + return; + } + + SpaceManagerState *state = mSavedStates; + mSavedStates = mSavedStates->mNext; + if(state != &mAutoState) { + delete state; + } +} + nscoord nsSpaceManager::GetLowestRegionTop() { diff --git a/mozilla/layout/base/src/nsSpaceManager.h b/mozilla/layout/base/src/nsSpaceManager.h index e825ad9cd0d..f903308497c 100644 --- a/mozilla/layout/base/src/nsSpaceManager.h +++ b/mozilla/layout/base/src/nsSpaceManager.h @@ -311,6 +311,12 @@ public: */ void PopState(); + /** + * Pops the state off the stack without restoring it. Useful for speculative + * reflow where we're not sure if we're going to keep the result. + */ + void DiscardState(); + /** * Get the top of the last region placed into the space manager, to * enforce the rule that a float can't be above an earlier float. diff --git a/mozilla/layout/generic/nsBlockFrame.cpp b/mozilla/layout/generic/nsBlockFrame.cpp index 49eae095f6a..b7d327a7176 100644 --- a/mozilla/layout/generic/nsBlockFrame.cpp +++ b/mozilla/layout/generic/nsBlockFrame.cpp @@ -695,7 +695,8 @@ nsBlockFrame::Reflow(nsPresContext* aPresContext, } nsBlockReflowState state(aReflowState, aPresContext, this, aMetrics, - NS_BLOCK_MARGIN_ROOT & mState); + aReflowState.mFlags.mHasClearance || (NS_BLOCK_MARGIN_ROOT & mState), + (NS_BLOCK_MARGIN_ROOT & mState)); // The condition for doing Bidi resolutions includes a test for the // dirtiness flags, because blocks sometimes send a resize reflow @@ -1133,6 +1134,27 @@ IsPercentageAwareChild(const nsIFrame* aFrame) return PR_FALSE; } +PRBool +nsBlockFrame::CheckForCollapsedBottomMarginFromClearanceLine() +{ + line_iterator begin = begin_lines(); + line_iterator line = end_lines(); + + while (PR_TRUE) { + if (begin == line) { + return PR_FALSE; + } + --line; + if (line->mBounds.height != 0 || !line->CachedIsEmpty()) { + return PR_FALSE; + } + if (line->HasClearance()) { + return PR_TRUE; + } + } + // not reached +} + void nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, nsBlockReflowState& aState, @@ -1260,7 +1282,9 @@ nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, nscoord oldDesiredWidth = aMetrics.width; #endif nsBlockReflowState state(reflowState, aState.mPresContext, this, - aMetrics, NS_BLOCK_MARGIN_ROOT & mState); + aMetrics, + aReflowState.mFlags.mHasClearance || (NS_BLOCK_MARGIN_ROOT & mState), + (NS_BLOCK_MARGIN_ROOT & mState)); ReflowDirtyLines(state); aState.mY = state.mY; NS_ASSERTION(oldDesiredWidth == aMetrics.width, "bad desired width"); @@ -1268,6 +1292,29 @@ nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, } } + // Return bottom margin information + // rbs says he hit this assertion occasionally (see bug 86947), so + // just set the margin to zero and we'll figure out why later + //NS_ASSERTION(aMetrics.mCarriedOutBottomMargin.IsZero(), + // "someone else set the margin"); + nscoord nonCarriedOutVerticalMargin = 0; + if (!aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) { + // Apply rule from CSS 2.1 section 8.3.1. If we have some empty + // line with clearance and a non-zero top margin and all + // subsequent lines are empty, then we do not allow our childrens' + // carried out bottom margin to be carried out of us and collapse + // with our own bottom margin. + if (CheckForCollapsedBottomMarginFromClearanceLine()) { + // Convert the children's carried out margin to something that + // we will include in our height + nonCarriedOutVerticalMargin = aState.mPrevBottomMargin.get(); + aState.mPrevBottomMargin.Zero(); + } + aMetrics.mCarriedOutBottomMargin = aState.mPrevBottomMargin; + } else { + aMetrics.mCarriedOutBottomMargin.Zero(); + } + // Compute final height if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedHeight) { if (NS_FRAME_IS_COMPLETE(aState.mReflowStatus)) { @@ -1296,14 +1343,14 @@ nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, } else { // Use the current height; continuations will take up the rest. - aMetrics.height = aState.mY; + aMetrics.height = aState.mY + nonCarriedOutVerticalMargin; } // Don't carry out a bottom margin when our height is fixed. - aState.mPrevBottomMargin.Zero(); + aMetrics.mCarriedOutBottomMargin.Zero(); } else { - nscoord autoHeight = aState.mY; + nscoord autoHeight = aState.mY + nonCarriedOutVerticalMargin; // Shrink wrap our height around our contents. if (aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) { @@ -1357,16 +1404,6 @@ nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, #endif } - // Return bottom margin information - // rbs says he hit this assertion occasionally (see bug 86947), so - // just set the margin to zero and we'll figure out why later - //NS_ASSERTION(aMetrics.mCarriedOutBottomMargin.IsZero(), - // "someone else set the margin"); - if (!aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) - aMetrics.mCarriedOutBottomMargin = aState.mPrevBottomMargin; - else - aMetrics.mCarriedOutBottomMargin.Zero(); - #ifdef DEBUG_blocks if (CRAZY_WIDTH(aMetrics.width) || CRAZY_HEIGHT(aMetrics.height)) { ListTag(stdout); @@ -1537,7 +1574,7 @@ nsBlockFrame::RetargetInlineIncrementalReflow(nsReflowPath::iterator &aTarget, // continuations will be preserved during an unconstrained reflow. // XXXwaterson should this be `!= NS_STYLE_CLEAR_NONE'? --aLine; - if (aLine->GetBreakType() == NS_STYLE_CLEAR_LINE) + if (aLine->GetBreakTypeAfter() == NS_STYLE_CLEAR_LINE) break; *aTarget = aPrevInFlow; @@ -1793,7 +1830,7 @@ nsBlockFrame::PrepareResizeReflow(nsBlockReflowState& aState) if (line->IsBlock() || line->HasPercentageChild() || line->HasFloats() || - (line != mLines.back() && !line->HasBreak()) || + (line != mLines.back() && !line->HasBreakAfter()) || line->ResizeReflowOptimizationDisabled() || line->IsImpactedByFloat() || (line->mBounds.XMost() > newAvailWidth)) { @@ -1809,14 +1846,14 @@ nsBlockFrame::PrepareResizeReflow(nsBlockReflowState& aState) #ifdef DEBUG if (gNoisyReflow && !line->IsDirty()) { IndentBy(stdout, gNoiseIndent + 1); - printf("skipped: line=%p next=%p %s %s%s%s breakType=%d xmost=%d\n", + printf("skipped: line=%p next=%p %s %s%s%s breakTypeBefore/After=%d/%d xmost=%d\n", NS_STATIC_CAST(void*, line.get()), NS_STATIC_CAST(void*, (line.next() != end_lines() ? line.next().get() : nsnull)), line->IsBlock() ? "block" : "inline", - line->HasBreak() ? "has-break " : "", + line->HasBreakAfter() ? "has-break-after " : "", line->HasFloats() ? "has-floats " : "", line->IsImpactedByFloat() ? "impacted " : "", - line->GetBreakType(), + line->GetBreakTypeBefore(), line->GetBreakTypeAfter(), line->mBounds.XMost()); } #endif @@ -1970,10 +2007,15 @@ DirtyLineIfWrappedLinesAreDirty(const nsLineList::iterator& aLine, } static void PlaceFrameView(nsPresContext* aPresContext, nsIFrame* aFrame); -static PRUint8 CombineBreakType(PRUint8 aOrigBreakType, PRUint8 aNewBreakType); static void CollectFloats(nsIFrame* aFrame, nsIFrame* aBlockParent, nsIFrame** aHead, nsIFrame** aTail); +static PRBool LineHasClear(nsLineBox* aLine) { + return aLine->GetBreakTypeBefore() || aLine->HasFloatBreakAfter() + || (aLine->IsBlock() && (aLine->mFirstChild->GetStateBits() & NS_BLOCK_HAS_CLEAR_CHILDREN)); +} + + static void ReparentFrame(nsIFrame* aFrame, nsIFrame* aOldParent, nsIFrame* aNewParent) { aFrame->SetParent(aNewParent); @@ -2026,6 +2068,7 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) nsresult rv = NS_OK; PRBool keepGoing = PR_TRUE; PRBool repositionViews = PR_FALSE; // should we really need this? + PRBool foundAnyClears = PR_FALSE; #ifdef DEBUG if (gNoisyReflow) { @@ -2066,8 +2109,9 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) // reflow it or if its previous margin is dirty PRBool needToRecoverState = PR_FALSE; PRBool lastLineMovedUp = PR_FALSE; - PRUint8 floatBreakType = NS_STYLE_CLEAR_NONE; - + // We save up information about BR-clearance here + PRUint8 inlineFloatBreakType = NS_STYLE_CLEAR_NONE; + // Reflow the lines that are already ours line_iterator line = begin_lines(), line_end = end_lines(); for ( ; line != line_end; ++line, aState.AdvanceToNextLine()) { @@ -2097,15 +2141,59 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) ::DirtyLineIfWrappedLinesAreDirty(line, line_end); } + // This really sucks, but we have to look inside any blocks that have clear + // elements inside them. + // XXX what can we do smarter here? + if (line->IsBlock() && + (line->mFirstChild->GetStateBits() & NS_BLOCK_HAS_CLEAR_CHILDREN)) { + line->MarkDirty(); + } + + // We have to reflow the line if it's a block whose clearance + // might have changed, so detect that. + if (!line->IsDirty() && line->GetBreakTypeBefore() != NS_STYLE_CLEAR_NONE) { + nscoord curY = aState.mY; + // See where we would be after applying any clearance due to + // BRs. + if (inlineFloatBreakType != NS_STYLE_CLEAR_NONE) { + curY = aState.ClearFloats(curY, inlineFloatBreakType); + } + + nscoord newY = aState.ClearFloats(curY, line->GetBreakTypeBefore()); + + if (line->HasClearance()) { + // Reflow the line if it might not have clearance anymore. + if (newY == curY + // aState.mY is the clearance point which should be the + // top border-edge of the block frame. If sliding the + // block by deltaY isn't going to put it in the predicted + // position, then we'd better reflow the line. + || newY != line->mBounds.y + deltaY) { + line->MarkDirty(); + } + } else { + // Reflow the line if the line might have clearance now. + if (curY != newY) { + line->MarkDirty(); + } + } + } + + // We might have to reflow a line that is after a clearing BR. + if (inlineFloatBreakType != NS_STYLE_CLEAR_NONE) { + aState.mY = aState.ClearFloats(aState.mY, inlineFloatBreakType); + if (aState.mY != line->mBounds.y + deltaY) { + // SlideLine is not going to put the line where the clearance + // put it. Reflow the line to be sure. + line->MarkDirty(); + } + inlineFloatBreakType = NS_STYLE_CLEAR_NONE; + } + // Make sure |aState.mPrevBottomMargin| is at the correct position // before calling PropagateFloatDamage. if (needToRecoverState && (line->IsDirty() || line->IsPreviousMarginDirty())) { - if (floatBreakType != NS_STYLE_CLEAR_NONE) { - aState.ClearFloats(aState.mY, floatBreakType); - floatBreakType = NS_STYLE_CLEAR_NONE; - } - // We need to reconstruct the bottom margin only if we didn't // reflow the previous line and we do need to reflow (or repair // the top position of) the next line. @@ -2218,25 +2306,29 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) aState.RecoverStateFrom(line, deltaY); // Keep mY up to date in case we're propagating reflow damage - // and also because our final height may depend on it. Only - // update mY if the line is not empty, because that's what - // PlaceLine does. - if (!line->CachedIsEmpty()) { + // and also because our final height may depend on it. If the + // line is inlines, then only update mY if the line is not + // empty, because that's what PlaceLine does. (Empty blocks may + // want to update mY, e.g. if they have clearance.) + if (line->IsBlock() || !line->CachedIsEmpty()) { aState.mY = line->mBounds.YMost(); - // This will include any pending float clearing height, so - // don't bother clearing previous lines' floats - floatBreakType = NS_STYLE_CLEAR_NONE; } // Record if we need to clear floats before reflowing the next - // line - if (line->HasFloatBreak()) { - floatBreakType = ::CombineBreakType(floatBreakType, line->GetBreakType()); + // line. Note that inlineFloatBreakType will be handled and + // cleared before the next line is processed, so there is no + // need to combine break types here. + if (line->HasFloatBreakAfter()) { + inlineFloatBreakType = line->GetBreakTypeAfter(); } needToRecoverState = PR_TRUE; } + if (LineHasClear(line.get())) { + foundAnyClears = PR_TRUE; + } + #ifdef DEBUG if (gNoisyReflow) { gNoiseIndent--; @@ -2399,6 +2491,10 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) break; } + if (LineHasClear(line.get())) { + foundAnyClears = PR_TRUE; + } + // If this is an inline frame then its time to stop ++line; aState.AdvanceToNextLine(); @@ -2420,6 +2516,12 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) aState.mY += metrics.height; } + if (foundAnyClears) { + AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN); + } else { + RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN); + } + #ifdef DEBUG if (gNoisyReflow) { gNoiseIndent--; @@ -3101,38 +3203,6 @@ nsBlockFrame::UndoSplitPlaceholders(nsBlockReflowState& aState, } } -// Combine aNewBreakType with aOrigBreakType, but limit the break types -// to NS_STYLE_CLEAR_LEFT, RIGHT, LEFT_AND_RIGHT. When there is a
right -// after a float and the float splits, then the
's break type is combined -// with the break type of the frame right after the floats next-in-flow. -static PRUint8 -CombineBreakType(PRUint8 aOrigBreakType, - PRUint8 aNewBreakType) -{ - PRUint8 breakType = aOrigBreakType; - switch(breakType) { - case NS_STYLE_CLEAR_LEFT: - if ((NS_STYLE_CLEAR_RIGHT == aNewBreakType) || - (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) { - breakType = NS_STYLE_CLEAR_LEFT_AND_RIGHT; - } - break; - case NS_STYLE_CLEAR_RIGHT: - if ((NS_STYLE_CLEAR_LEFT == aNewBreakType) || - (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) { - breakType = NS_STYLE_CLEAR_LEFT_AND_RIGHT; - } - break; - case NS_STYLE_CLEAR_NONE: - if ((NS_STYLE_CLEAR_LEFT == aNewBreakType) || - (NS_STYLE_CLEAR_RIGHT == aNewBreakType) || - (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) { - breakType = aNewBreakType; - } - } - return breakType; -} - nsresult nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState, line_iterator aLine, @@ -3154,320 +3224,434 @@ nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState, aState.GetFlag(BRS_COMPUTEMAXELEMENTWIDTH), aState.GetFlag(BRS_COMPUTEMAXWIDTH)); - // See if we should apply the top margin. If the block frame being - // reflowed is a continuation (non-null prev-in-flow) then we don't - // apply its top margin because its not significant. Otherwise, dig - // deeper. - PRBool applyTopMargin = PR_FALSE; - nsIFrame* framePrevInFlow = frame->GetPrevInFlow(); - if (nsnull == framePrevInFlow) { - applyTopMargin = ShouldApplyTopMargin(aState, aLine); - } - PRUint8 breakType = display->mBreakType; // If a float split and its prev-in-flow was followed by a
, then combine // the
's break type with the block's break type (the block will be the very // next frame after the split float). if (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType) { - breakType = ::CombineBreakType(breakType, aState.mFloatBreakType); + breakType = nsLayoutUtils::CombineBreakType(breakType, + aState.mFloatBreakType); aState.mFloatBreakType = NS_STYLE_CLEAR_NONE; } + // Clear past floats before the block if the clear style is not none - aLine->SetBreakType(breakType); - if (NS_STYLE_CLEAR_NONE != breakType) { - PRBool alsoApplyTopMargin = aState.ClearPastFloats(breakType); - if (alsoApplyTopMargin) { - applyTopMargin = PR_TRUE; - } -#ifdef NOISY_VERTICAL_MARGINS - ListTag(stdout); - printf(": y=%d child ", aState.mY); - ListTag(stdout, frame); - printf(" has clear of %d => %s, mPrevBottomMargin=%d\n", - breakType, - applyTopMargin ? "applyTopMargin" : "nope", - aState.mPrevBottomMargin); -#endif - } + aLine->SetBreakTypeBefore(breakType); + + // See if we should apply the top margin. If the block frame being + // reflowed is a continuation (non-null prev-in-flow) then we don't + // apply its top margin because its not significant. Otherwise, dig + // deeper. + PRBool applyTopMargin = + !frame->GetPrevInFlow() && ShouldApplyTopMargin(aState, aLine); - nscoord topMargin = 0; if (applyTopMargin) { - // Precompute the blocks top margin value so that we can get the - // correct available space (there might be a float that's - // already been placed below the aState.mPrevBottomMargin - - // Setup a reflowState to get the style computed margin-top - // value. We'll use a reason of `resize' so that we don't fudge - // any incremental reflow state. - - // The availSpace here is irrelevant to our needs - all we want - // out if this setup is the margin-top value which doesn't depend - // on the childs available space. - nsSize availSpace(aState.mContentArea.width, NS_UNCONSTRAINEDSIZE); - nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState, - frame, availSpace, eReflowReason_Resize); - - // Now compute the collapsed margin-top value into aState.mPrevBottomMargin - nsCollapsingMargin oldPrevBottomMargin = aState.mPrevBottomMargin; - nsBlockReflowContext::ComputeCollapsedTopMargin(aState.mPresContext, - reflowState, - aState.mPrevBottomMargin); - topMargin = aState.mPrevBottomMargin.get(); - aState.mPrevBottomMargin = oldPrevBottomMargin; // perhaps not needed - - // Temporarily advance the running Y value so that the - // GetAvailableSpace method will return the right available - // space. This undone as soon as the margin is computed. - aState.mY += topMargin; + // The HasClearance setting is only valid if ShouldApplyTopMargin + // returned PR_FALSE (in which case the top-margin-root set our + // clearance flag). Otherwise clear it now. We'll set it later on + // ourselves if necessary. + aLine->ClearHasClearance(); + } + PRBool treatWithClearance = aLine->HasClearance(); + // If our top margin was counted as part of some parents top-margin + // collapse and we are being speculatively reflowed assuming this + // frame DID NOT need clearance, then we need to check that + // assumption. + if (!treatWithClearance && !applyTopMargin && breakType != NS_STYLE_CLEAR_NONE && + aState.mReflowState.mDiscoveredClearance) { + nscoord curY = aState.mY + aState.mPrevBottomMargin.get(); + nscoord clearY = aState.ClearFloats(curY, breakType); + if (clearY != curY) { + // Looks like that assumption was invalid, we do need + // clearance. Tell our ancestor so it can reflow again. It is + // responsible for actually setting our clearance flag before + // the next reflow. + treatWithClearance = PR_TRUE; + // Only record the first frame that requires clearance + if (!*aState.mReflowState.mDiscoveredClearance) { + *aState.mReflowState.mDiscoveredClearance = frame; + } + // Exactly what we do now is flexible since we'll definitely be + // reflowed. + } + } + if (treatWithClearance) { + applyTopMargin = PR_TRUE; } - // Compute the available space for the block - aState.GetAvailableSpace(); -#ifdef REALLY_NOISY_REFLOW - printf("setting line %p isImpacted to %s\n", aLine, aState.IsImpactedByFloat()?"true":"false"); -#endif - PRBool isImpacted = aState.IsImpactedByFloat() ? PR_TRUE : PR_FALSE; - aLine->SetLineIsImpactedByFloat(isImpacted); - nsSplittableType splitType = NS_FRAME_NOT_SPLITTABLE; - frame->IsSplittable(splitType); - nsRect availSpace; - aState.ComputeBlockAvailSpace(frame, splitType, display, availSpace); + nsIFrame* clearanceFrame = nsnull; + nscoord startingY = aState.mY; + nsCollapsingMargin incomingMargin = aState.mPrevBottomMargin; + while (PR_TRUE) { + nscoord clearance = 0; + nscoord topMargin = 0; + PRBool mayNeedRetry = PR_FALSE; + if (applyTopMargin) { + // Precompute the blocks top margin value so that we can get the + // correct available space (there might be a float that's + // already been placed below the aState.mPrevBottomMargin - // Now put the Y coordinate back and flow the block letting the - // block reflow context compute the same top margin value we just - // computed (sigh). - if (topMargin) { + // Setup a reflowState to get the style computed margin-top + // value. We'll use a reason of `resize' so that we don't fudge + // any incremental reflow state. + + // The availSpace here is irrelevant to our needs - all we want + // out if this setup is the margin-top value which doesn't depend + // on the childs available space. + // XXX building a complete nsHTMLReflowState just to get the margin-top + // seems like a waste. And we do this for almost every block! + nsSize availSpace(aState.mContentArea.width, NS_UNCONSTRAINEDSIZE); + nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState, + frame, availSpace, eReflowReason_Resize); + + if (treatWithClearance) { + aState.mY += aState.mPrevBottomMargin.get(); + aState.mPrevBottomMargin.Zero(); + } + + // Now compute the collapsed margin-top value into aState.mPrevBottomMargin, assuming + // that all child margins collapse down to clearanceFrame. + nsBlockReflowContext::ComputeCollapsedTopMargin(reflowState, + &aState.mPrevBottomMargin, clearanceFrame, &mayNeedRetry); + + // XXX optimization; we could check the collapsing children to see if they are sure + // to require clearance, and so avoid retrying them + + if (clearanceFrame) { + // Don't allow retries on the second pass. The clearance decisions for the + // blocks whose top-margins collapse with ours are now fixed. + mayNeedRetry = PR_FALSE; + } + + if (!treatWithClearance && !clearanceFrame && breakType != NS_STYLE_CLEAR_NONE) { + // We don't know if we need clearance and this is the first, + // optimistic pass. So determine whether *this block* needs + // clearance. Note that we do not allow the decision for whether + // this block has clearance to change on the second pass; that + // decision is only allowed to be made under the optimistic + // first pass. + nscoord curY = aState.mY + aState.mPrevBottomMargin.get(); + nscoord clearY = aState.ClearFloats(curY, breakType); + if (clearY != curY) { + // Looks like we need clearance and we didn't know about it already. So + // recompute collapsed margin + treatWithClearance = PR_TRUE; + // Remember this decision, needed for incremental reflow + aLine->SetHasClearance(); + + // Apply incoming margins + aState.mY += aState.mPrevBottomMargin.get(); + aState.mPrevBottomMargin.Zero(); + + // Compute the collapsed margin again, ignoring the incoming margin this time + mayNeedRetry = PR_FALSE; + nsBlockReflowContext::ComputeCollapsedTopMargin(reflowState, + &aState.mPrevBottomMargin, clearanceFrame, &mayNeedRetry); + } + } + + // Temporarily advance the running Y value so that the + // GetAvailableSpace method will return the right available + // space. This undone as soon as the horizontal margins are + // computed. + topMargin = aState.mPrevBottomMargin.get(); + + if (treatWithClearance) { + nscoord currentY = aState.mY; + // advance mY to the clear position. + aState.mY = aState.ClearFloats(aState.mY, breakType); + + // Compute clearance. It's the amount we need to add to the top + // border-edge of the frame, after applying collapsed margins + // from the frame and its children, to get it to line up with + // the bottom of the floats. The former is currentY + topMargin, + // the latter is the current aState.mY. + // Note that negative clearance is possible + clearance = aState.mY - (currentY + topMargin); + + // Add clearance to our top margin while we compute available + // space for the frame + topMargin += clearance; + + // Note that aState.mY should stay where it is: at the top + // border-edge of the frame + } else { + // Advance aState.mY to the top border-edge of the frame. + aState.mY += topMargin; + } + } + + // Here aState.mY is the top border-edge of the block. + // Compute the available space for the block + aState.GetAvailableSpace(); +#ifdef REALLY_NOISY_REFLOW + printf("setting line %p isImpacted to %s\n", aLine, aState.IsImpactedByFloat()?"true":"false"); +#endif + PRBool isImpacted = aState.IsImpactedByFloat() ? PR_TRUE : PR_FALSE; + aLine->SetLineIsImpactedByFloat(isImpacted); + nsSplittableType splitType = NS_FRAME_NOT_SPLITTABLE; + frame->IsSplittable(splitType); + nsRect availSpace; + aState.ComputeBlockAvailSpace(frame, splitType, display, availSpace); + + // Now put the Y coordinate back to the top of the top-margin + + // clearance, and flow the block. aState.mY -= topMargin; availSpace.y -= topMargin; if (NS_UNCONSTRAINEDSIZE != availSpace.height) { availSpace.height += topMargin; } - } - - // keep track of the last overflow float in case we need to undo any new additions - nsFrameList* overflowPlace = GetOverflowPlaceholders(); - nsIFrame* lastPlaceholder = (overflowPlace) ? overflowPlace->LastChild() : nsnull; - - // Reflow the block into the available space - nsReflowStatus frameReflowStatus=NS_FRAME_COMPLETE; - nsMargin computedOffsets; - // construct the html reflow state for the block. ReflowBlock - // will initialize it and set its reason. - nsHTMLReflowState blockHtmlRS(aState.mPresContext, aState.mReflowState, frame, - nsSize(availSpace.width, availSpace.height), - aState.mReflowState.reason, PR_FALSE); - rv = brc.ReflowBlock(availSpace, applyTopMargin, aState.mPrevBottomMargin, - aState.IsAdjacentWithTop(), computedOffsets, - blockHtmlRS, frameReflowStatus); - + + // keep track of the last overflow float in case we need to undo any new additions + nsFrameList* overflowPlace = GetOverflowPlaceholders(); + nsIFrame* lastPlaceholder = (overflowPlace) ? overflowPlace->LastChild() : nsnull; + + // Reflow the block into the available space + nsMargin computedOffsets; + // construct the html reflow state for the block. ReflowBlock + // will initialize it + nsHTMLReflowState blockHtmlRS(aState.mPresContext, aState.mReflowState, frame, + nsSize(availSpace.width, availSpace.height), + aState.mReflowState.reason, PR_TRUE); + blockHtmlRS.mFlags.mHasClearance = aLine->HasClearance(); + + if (mayNeedRetry) { + blockHtmlRS.mDiscoveredClearance = &clearanceFrame; + aState.mSpaceManager->PushState(); + } else if (!applyTopMargin) { + blockHtmlRS.mDiscoveredClearance = aState.mReflowState.mDiscoveredClearance; + } + + nsReflowStatus frameReflowStatus = NS_FRAME_COMPLETE; + rv = brc.ReflowBlock(availSpace, applyTopMargin, aState.mPrevBottomMargin, + clearance, aState.IsAdjacentWithTop(), computedOffsets, + blockHtmlRS, frameReflowStatus); + // Remove the frame from the reflow tree. - if (aState.mReflowState.path) - aState.mReflowState.path->RemoveChild(frame); - - if (NS_FAILED(rv)) { - return rv; - } - aState.mPrevChild = frame; - + if (aState.mReflowState.path) + aState.mReflowState.path->RemoveChild(frame); + if (NS_FAILED(rv)) { + return rv; + } + + if (mayNeedRetry) { + if (clearanceFrame) { + aState.mSpaceManager->PopState(); + aState.mY = startingY; + aState.mPrevBottomMargin = incomingMargin; + continue; + } else { + // pop the saved state off the stack and discard it, because we + // want to keep the current state, since our speculation + // succeeded + aState.mSpaceManager->DiscardState(); + } + } + + aState.mPrevChild = frame; + #if defined(REFLOW_STATUS_COVERAGE) - RecordReflowStatus(PR_TRUE, frameReflowStatus); + RecordReflowStatus(PR_TRUE, frameReflowStatus); #endif - - if (NS_INLINE_IS_BREAK_BEFORE(frameReflowStatus)) { - // None of the child block fits. - UndoSplitPlaceholders(aState, lastPlaceholder); - PushLines(aState, aLine.prev()); - *aKeepReflowGoing = PR_FALSE; - aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; - } - else { - // Note: line-break-after a block is a nop - - // Try to place the child block - PRBool isAdjacentWithTop = aState.IsAdjacentWithTop(); - nsCollapsingMargin collapsedBottomMargin; - nsRect combinedArea(0,0,0,0); - *aKeepReflowGoing = brc.PlaceBlock(blockHtmlRS, isAdjacentWithTop, - computedOffsets, collapsedBottomMargin, - aLine->mBounds, combinedArea); - if (aLine->SetCarriedOutBottomMargin(collapsedBottomMargin)) { - line_iterator nextLine = aLine; - ++nextLine; - if (nextLine != end_lines()) { - nextLine->MarkPreviousMarginDirty(); - } - } - - if (aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { - // Mark the line as dirty so once we known the final shrink wrap width - // we can reflow the block to the correct size - // XXX We don't always need to do this... - aLine->MarkDirty(); - aState.SetFlag(BRS_NEEDRESIZEREFLOW, PR_TRUE); - } - if (aState.GetFlag(BRS_UNCONSTRAINEDWIDTH) || aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { - // Add the right margin to the line's bounds. That way it will be - // taken into account when we compute our shrink wrap size. - nscoord marginRight = brc.GetMargin().right; - if (marginRight != NS_UNCONSTRAINEDSIZE) { - aLine->mBounds.width += marginRight; - } - } - aLine->SetCombinedArea(combinedArea); - if (*aKeepReflowGoing) { - // Some of the child block fit - - // Advance to new Y position - nscoord newY = aLine->mBounds.YMost(); - aState.mY = newY; - - // Continue the block frame now if it didn't completely fit in - // the available space. - if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) { - PRBool madeContinuation; - rv = CreateContinuationFor(aState, nsnull, frame, madeContinuation); - if (NS_FAILED(rv)) - return rv; - - nsIFrame* nextFrame = frame->GetNextInFlow(); - - // Push continuation to a new line, but only if we actually made one. - if (madeContinuation) { - nsLineBox* line = aState.NewLineBox(nextFrame, 1, PR_TRUE); - if (nsnull == line) { - return NS_ERROR_OUT_OF_MEMORY; - } - mLines.after_insert(aLine, line); - } - - // Advance to next line since some of the block fit. That way - // only the following lines will be pushed. - PushLines(aState, aLine); - aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; - // If we need to reflow the continuation of the block child, - // then we'd better reflow our continuation - if (frameReflowStatus & NS_FRAME_REFLOW_NEXTINFLOW) { - aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW; - // We also need to make that continuation's line dirty so it - // gets reflowed when we reflow our next in flow. The - // nif's line must always be either the first line - // of the nif's parent block or else one of our own overflow - // lines. In the latter case the line is already marked dirty, - // so just detect and handle the first case. - nsBlockFrame* nifBlock = NS_STATIC_CAST(nsBlockFrame*, nextFrame->GetParent()); - NS_ASSERTION(nifBlock->GetType() == nsLayoutAtoms::blockFrame - || nifBlock->GetType() == nsLayoutAtoms::areaFrame, - "A block's child's next in flow's parent must be a block!"); - line_iterator firstLine = nifBlock->begin_lines(); - if (firstLine != nifBlock->end_lines() && firstLine->Contains(nextFrame)) { - firstLine->MarkDirty(); - } - } - *aKeepReflowGoing = PR_FALSE; - - // The bottom margin for a block is only applied on the last - // flow block. Since we just continued the child block frame, - // we know that line->mFirstChild is not the last flow block - // therefore zero out the running margin value. -#ifdef NOISY_VERTICAL_MARGINS - ListTag(stdout); - printf(": reflow incomplete, frame="); - nsFrame::ListTag(stdout, frame); - printf(" prevBottomMargin=%d, setting to zero\n", - aState.mPrevBottomMargin); -#endif - aState.mPrevBottomMargin.Zero(); - } - else { -#ifdef NOISY_VERTICAL_MARGINS - ListTag(stdout); - printf(": reflow complete for "); - nsFrame::ListTag(stdout, frame); - printf(" prevBottomMargin=%d collapsedBottomMargin=%d\n", - aState.mPrevBottomMargin, collapsedBottomMargin.get()); -#endif - aState.mPrevBottomMargin = collapsedBottomMargin; - } -#ifdef NOISY_VERTICAL_MARGINS - ListTag(stdout); - printf(": frame="); - nsFrame::ListTag(stdout, frame); - printf(" carriedOutBottomMargin=%d collapsedBottomMargin=%d => %d\n", - brc.GetCarriedOutBottomMargin(), collapsedBottomMargin.get(), - aState.mPrevBottomMargin); -#endif - - // Post-process the "line" - nscoord maxElementWidth = 0; - if (aState.GetFlag(BRS_COMPUTEMAXELEMENTWIDTH)) { - maxElementWidth = brc.GetMaxElementWidth(); - } - // If we asked the block to update its maximum width, then record the - // updated value in the line, and update the current maximum width - if (aState.GetFlag(BRS_COMPUTEMAXWIDTH)) { - aLine->mMaximumWidth = brc.GetMaximumWidth(); - aState.UpdateMaximumWidth(aLine->mMaximumWidth); - } - PostPlaceLine(aState, aLine, maxElementWidth); - - // If the block frame that we just reflowed happens to be our - // first block, then its computed ascent is ours - if (frame == GetTopBlockChild(aState.mPresContext)) { - const nsHTMLReflowMetrics& metrics = brc.GetMetrics(); - mAscent = metrics.ascent; - } - - // Place the "marker" (bullet) frame. - // - // According to the CSS2 spec, section 12.6.1, the "marker" box - // participates in the height calculation of the list-item box's - // first line box. - // - // There are exactly two places a bullet can be placed: near the - // first or second line. Its only placed on the second line in a - // rare case: an empty first line followed by a second line that - // contains a block (example:
  • \n

    ... ). This is where - // the second case can happen. - if (mBullet && HaveOutsideBullet() && - ((aLine == mLines.front()) || - ((0 == mLines.front()->mBounds.height) && - (aLine == begin_lines().next())))) { - // Reflow the bullet - nsHTMLReflowMetrics metrics(nsnull); - ReflowBullet(aState, metrics); - - // Doing the alignment using |mAscent| will also cater for bullets - // that are placed next to a child block (bug 92896) - // (Note that mAscent should be set by now, otherwise why would - // we be placing the bullet yet?) - - // Tall bullets won't look particularly nice here... - nsRect bbox = mBullet->GetRect(); - nscoord bulletTopMargin = applyTopMargin - ? collapsedBottomMargin.get() - : 0; - bbox.y = aState.BorderPadding().top + mAscent - - metrics.ascent + bulletTopMargin; - mBullet->SetRect(bbox); - } + + if (NS_INLINE_IS_BREAK_BEFORE(frameReflowStatus)) { + // None of the child block fits. + UndoSplitPlaceholders(aState, lastPlaceholder); + PushLines(aState, aLine.prev()); + *aKeepReflowGoing = PR_FALSE; + aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; } else { - // None of the block fits. Determine the correct reflow status. - if (aLine == mLines.front()) { - // If it's our very first line then we need to be pushed to - // our parents next-in-flow. Therefore, return break-before - // status for our reflow status. - aState.mReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); + // Note: line-break-after a block is a nop + + // Try to place the child block + PRBool isAdjacentWithTop = aState.IsAdjacentWithTop(); + nsCollapsingMargin collapsedBottomMargin; + nsRect combinedArea(0,0,0,0); + *aKeepReflowGoing = brc.PlaceBlock(blockHtmlRS, isAdjacentWithTop, + aLine.get(), + computedOffsets, collapsedBottomMargin, + aLine->mBounds, combinedArea); + if (aLine->SetCarriedOutBottomMargin(collapsedBottomMargin)) { + line_iterator nextLine = aLine; + ++nextLine; + if (nextLine != end_lines()) { + nextLine->MarkPreviousMarginDirty(); + } + } + + if (aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { + // Mark the line as dirty so once we known the final shrink wrap width + // we can reflow the block to the correct size + // XXX We don't always need to do this... + aLine->MarkDirty(); + aState.SetFlag(BRS_NEEDRESIZEREFLOW, PR_TRUE); + } + if (aState.GetFlag(BRS_UNCONSTRAINEDWIDTH) || aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { + // Add the right margin to the line's bounds. That way it will be + // taken into account when we compute our shrink wrap size. + nscoord marginRight = brc.GetMargin().right; + if (marginRight != NS_UNCONSTRAINEDSIZE) { + aLine->mBounds.width += marginRight; + } + } + aLine->SetCombinedArea(combinedArea); + if (*aKeepReflowGoing) { + // Some of the child block fit + + // Advance to new Y position + nscoord newY = aLine->mBounds.YMost(); + aState.mY = newY; + + // Continue the block frame now if it didn't completely fit in + // the available space. + if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) { + PRBool madeContinuation; + rv = CreateContinuationFor(aState, nsnull, frame, madeContinuation); + if (NS_FAILED(rv)) + return rv; + + nsIFrame* nextFrame = frame->GetNextInFlow(); + + // Push continuation to a new line, but only if we actually made one. + if (madeContinuation) { + nsLineBox* line = aState.NewLineBox(nextFrame, 1, PR_TRUE); + if (nsnull == line) { + return NS_ERROR_OUT_OF_MEMORY; + } + mLines.after_insert(aLine, line); + } + + // Advance to next line since some of the block fit. That way + // only the following lines will be pushed. + PushLines(aState, aLine); + aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; + // If we need to reflow the continuation of the block child, + // then we'd better reflow our continuation + if (frameReflowStatus & NS_FRAME_REFLOW_NEXTINFLOW) { + aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW; + // We also need to make that continuation's line dirty so it + // gets reflowed when we reflow our next in flow. The + // nif's line must always be either the first line + // of the nif's parent block or else one of our own overflow + // lines. In the latter case the line is already marked dirty, + // so just detect and handle the first case. + nsBlockFrame* nifBlock = NS_STATIC_CAST(nsBlockFrame*, nextFrame->GetParent()); + NS_ASSERTION(nifBlock->GetType() == nsLayoutAtoms::blockFrame + || nifBlock->GetType() == nsLayoutAtoms::areaFrame, + "A block's child's next in flow's parent must be a block!"); + line_iterator firstLine = nifBlock->begin_lines(); + if (firstLine != nifBlock->end_lines() && firstLine->Contains(nextFrame)) { + firstLine->MarkDirty(); + } + } + *aKeepReflowGoing = PR_FALSE; + + // The bottom margin for a block is only applied on the last + // flow block. Since we just continued the child block frame, + // we know that line->mFirstChild is not the last flow block + // therefore zero out the running margin value. +#ifdef NOISY_VERTICAL_MARGINS + ListTag(stdout); + printf(": reflow incomplete, frame="); + nsFrame::ListTag(stdout, frame); + printf(" prevBottomMargin=%d, setting to zero\n", + aState.mPrevBottomMargin); +#endif + aState.mPrevBottomMargin.Zero(); + } + else { +#ifdef NOISY_VERTICAL_MARGINS + ListTag(stdout); + printf(": reflow complete for "); + nsFrame::ListTag(stdout, frame); + printf(" prevBottomMargin=%d collapsedBottomMargin=%d\n", + aState.mPrevBottomMargin, collapsedBottomMargin.get()); +#endif + aState.mPrevBottomMargin = collapsedBottomMargin; + } +#ifdef NOISY_VERTICAL_MARGINS + ListTag(stdout); + printf(": frame="); + nsFrame::ListTag(stdout, frame); + printf(" carriedOutBottomMargin=%d collapsedBottomMargin=%d => %d\n", + brc.GetCarriedOutBottomMargin(), collapsedBottomMargin.get(), + aState.mPrevBottomMargin); +#endif + + // Post-process the "line" + nscoord maxElementWidth = 0; + if (aState.GetFlag(BRS_COMPUTEMAXELEMENTWIDTH)) { + maxElementWidth = brc.GetMaxElementWidth(); + } + // If we asked the block to update its maximum width, then record the + // updated value in the line, and update the current maximum width + if (aState.GetFlag(BRS_COMPUTEMAXWIDTH)) { + aLine->mMaximumWidth = brc.GetMaximumWidth(); + aState.UpdateMaximumWidth(aLine->mMaximumWidth); + } + PostPlaceLine(aState, aLine, maxElementWidth); + + // If the block frame that we just reflowed happens to be our + // first block, then its computed ascent is ours + if (frame == GetTopBlockChild(aState.mPresContext)) { + const nsHTMLReflowMetrics& metrics = brc.GetMetrics(); + mAscent = metrics.ascent; + } + + // Place the "marker" (bullet) frame. + // + // According to the CSS2 spec, section 12.6.1, the "marker" box + // participates in the height calculation of the list-item box's + // first line box. + // + // There are exactly two places a bullet can be placed: near the + // first or second line. Its only placed on the second line in a + // rare case: an empty first line followed by a second line that + // contains a block (example:

  • \n

    ... ). This is where + // the second case can happen. + if (mBullet && HaveOutsideBullet() && + ((aLine == mLines.front()) || + ((0 == mLines.front()->mBounds.height) && + (aLine == begin_lines().next())))) { + // Reflow the bullet + nsHTMLReflowMetrics metrics(nsnull); + ReflowBullet(aState, metrics); + + // Doing the alignment using |mAscent| will also cater for bullets + // that are placed next to a child block (bug 92896) + // (Note that mAscent should be set by now, otherwise why would + // we be placing the bullet yet?) + + // Tall bullets won't look particularly nice here... + nsRect bbox = mBullet->GetRect(); + nscoord bulletTopMargin = applyTopMargin + ? collapsedBottomMargin.get() + : 0; + bbox.y = aState.BorderPadding().top + mAscent - + metrics.ascent + bulletTopMargin; + mBullet->SetRect(bbox); + } } else { - // Push the line that didn't fit and any lines that follow it - // to our next-in-flow. - UndoSplitPlaceholders(aState, lastPlaceholder); - PushLines(aState, aLine.prev()); - aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; + // None of the block fits. Determine the correct reflow status. + if (aLine == mLines.front()) { + // If it's our very first line then we need to be pushed to + // our parents next-in-flow. Therefore, return break-before + // status for our reflow status. + aState.mReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); + } + else { + // Push the line that didn't fit and any lines that follow it + // to our next-in-flow. + UndoSplitPlaceholders(aState, lastPlaceholder); + PushLines(aState, aLine.prev()); + aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; + } } } + break; // out of the reflow retry loop } + #ifdef DEBUG VerifyLines(PR_TRUE); #endif @@ -3795,7 +3979,7 @@ nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState, // break-after-not-complete. There are two situations: we are a // block or we are an inline. This makes a total of 10 cases // (fortunately, there is some overlap). - aLine->SetBreakType(NS_STYLE_CLEAR_NONE); + aLine->SetBreakTypeAfter(NS_STYLE_CLEAR_NONE); if (NS_INLINE_IS_BREAK(frameReflowStatus) || (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType)) { // Always abort the line reflow (because a line break is the @@ -3838,7 +4022,8 @@ nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState, // the
    's break type with the inline's break type (the inline will be the very // next frame after the split float). if (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType) { - breakType = ::CombineBreakType(breakType, aState.mFloatBreakType); + breakType = nsLayoutUtils::CombineBreakType(breakType, + aState.mFloatBreakType); aState.mFloatBreakType = NS_STYLE_CLEAR_NONE; } // Break-after cases @@ -3847,7 +4032,7 @@ nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState, breakType = NS_STYLE_CLEAR_NONE; } } - aLine->SetBreakType(breakType); + aLine->SetBreakTypeAfter(breakType); if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) { // Create a continuation for the incomplete frame. Note that the // frame may already have a continuation. @@ -4335,8 +4520,8 @@ nsBlockFrame::PlaceLine(nsBlockReflowState& aState, // Apply break-after clearing if necessary // This must stay in sync with |ReflowDirtyLines|. - if (aLine->HasFloatBreak()) { - aState.ClearFloats(aState.mY, aLine->GetBreakType()); + if (aLine->HasFloatBreakAfter()) { + aState.mY = aState.ClearFloats(aState.mY, aLine->GetBreakTypeAfter()); } return PR_FALSE; @@ -5228,6 +5413,30 @@ nsBlockFrame::DeleteNextInFlowChild(nsPresContext* aPresContext, //////////////////////////////////////////////////////////////////////// // Float support +static void InitReflowStateForFloat(nsHTMLReflowState* aState, nsPresContext* aPresContext) +{ + /* We build a different reflow context based on the width attribute of the block + * when it's a float. + * Auto-width floats need to have their containing-block size set explicitly. + * factoring in other floats that impact it. + * It's possible this should be quirks-only. + */ + // XXXldb We should really fix this in nsHTMLReflowState::InitConstraints instead. + const nsStylePosition* position = aState->frame->GetStylePosition(); + nsStyleUnit widthUnit = position->mWidth.GetUnit(); + + if (eStyleUnit_Auto == widthUnit) { + // Initialize the reflow state and constrain the containing block's + // width and height to the available width and height. + aState->Init(aPresContext, aState->availableWidth, aState->availableHeight); + } else { + // Initialize the reflow state and use the containing block's + // computed width and height (or derive appropriate values for an + // absolutely positioned frame). + aState->Init(aPresContext); + } +} + nsresult nsBlockFrame::ReflowFloat(nsBlockReflowState& aState, nsPlaceholderFrame* aPlaceholder, @@ -5298,11 +5507,14 @@ nsBlockFrame::ReflowFloat(nsBlockReflowState& aState, availWidth, availHeight); // construct the html reflow state for the float. ReflowBlock will - // initialize it and set its reason. + // initialize it. nsHTMLReflowState floatRS(aState.mPresContext, aState.mReflowState, floatFrame, nsSize(availSpace.width, availSpace.height), aState.mReflowState.reason, PR_FALSE); + + InitReflowStateForFloat(&floatRS, aState.mPresContext); + // Setup a block reflow state to reflow the float. nsBlockReflowContext brc(aState.mPresContext, aState.mReflowState, computeMaxElementWidth, @@ -5311,10 +5523,28 @@ nsBlockFrame::ReflowFloat(nsBlockReflowState& aState, // Reflow the float PRBool isAdjacentWithTop = aState.IsAdjacentWithTop(); - nsCollapsingMargin margin; - nsresult rv = brc.ReflowBlock(availSpace, PR_TRUE, margin, isAdjacentWithTop, - aFloatCache->mOffsets, floatRS, - aReflowStatus); + nsIFrame* clearanceFrame = nsnull; + nsresult rv; + do { + nsCollapsingMargin margin; + PRBool mayNeedRetry = PR_FALSE; + nsBlockReflowContext::ComputeCollapsedTopMargin(floatRS, &margin, + clearanceFrame, &mayNeedRetry); + + if (mayNeedRetry && !clearanceFrame) { + floatRS.mDiscoveredClearance = &clearanceFrame; + // We don't need to push the space manager state because the the block has its own + // space manager that will be destroyed and recreated + } else { + floatRS.mDiscoveredClearance = nsnull; + } + + rv = brc.ReflowBlock(availSpace, PR_TRUE, margin, + 0, isAdjacentWithTop, + aFloatCache->mOffsets, floatRS, + aReflowStatus); + } while (NS_SUCCEEDED(rv) && clearanceFrame); + // An incomplete reflow status means we should split the float // if the height is constrained (bug 145305). if (NS_FRAME_IS_NOT_COMPLETE(aReflowStatus) && (NS_UNCONSTRAINEDSIZE == availHeight)) @@ -5341,14 +5571,34 @@ nsBlockFrame::ReflowFloat(nsBlockReflowState& aState, availSpace.width = maxElementWidth; nsCollapsingMargin marginMEW; // construct the html reflow state for the float. - // ReflowBlock will initialize it and set its reason. + // ReflowBlock will initialize it. nsHTMLReflowState redoFloatRS(aState.mPresContext, aState.mReflowState, floatFrame, nsSize(availSpace.width, availSpace.height), aState.mReflowState.reason, PR_FALSE); - rv = brc.ReflowBlock(availSpace, PR_TRUE, marginMEW, isAdjacentWithTop, - aFloatCache->mOffsets, redoFloatRS, - aReflowStatus); + + InitReflowStateForFloat(&redoFloatRS, aState.mPresContext); + + clearanceFrame = nsnull; + do { + nsCollapsingMargin marginMEW; + PRBool mayNeedRetry = PR_FALSE; + nsBlockReflowContext::ComputeCollapsedTopMargin(redoFloatRS, &marginMEW, clearanceFrame, &mayNeedRetry); + + if (mayNeedRetry && !clearanceFrame) { + redoFloatRS.mDiscoveredClearance = &clearanceFrame; + // We don't need to push the space manager state because the + // the block has its own space manager that will be + // destroyed and recreated + } else { + redoFloatRS.mDiscoveredClearance = nsnull; + } + + rv = brc.ReflowBlock(availSpace, PR_TRUE, marginMEW, + 0, isAdjacentWithTop, + aFloatCache->mOffsets, redoFloatRS, + aReflowStatus); + } while (NS_SUCCEEDED(rv) && clearanceFrame); } } @@ -5432,8 +5682,8 @@ nsBlockFrame::ReflowFloat(nsBlockReflowState& aState, if (mPrevInFlow) { // get the break type of the last line in mPrevInFlow line_iterator endLine = --((nsBlockFrame*)mPrevInFlow)->end_lines(); - if (endLine->HasFloatBreak()) { - aState.mFloatBreakType = endLine->GetBreakType(); + if (endLine->HasFloatBreakAfter()) { + aState.mFloatBreakType = endLine->GetBreakTypeAfter(); } } else NS_ASSERTION(PR_FALSE, "no prev in flow"); diff --git a/mozilla/layout/generic/nsBlockFrame.h b/mozilla/layout/generic/nsBlockFrame.h index fa83bf8cded..f84b60b4ed5 100644 --- a/mozilla/layout/generic/nsBlockFrame.h +++ b/mozilla/layout/generic/nsBlockFrame.h @@ -69,6 +69,11 @@ class nsIntervalSet; #define NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS 0x04000000 #define NS_BLOCK_HAS_OVERFLOW_PLACEHOLDERS 0x08000000 +// Set on any block that has descendant frames in the normal +// flow with 'clear' set to something other than 'none' +// (including
    frames) +#define NS_BLOCK_HAS_CLEAR_CHILDREN 0x10000000 + #define nsBlockFrameSuper nsHTMLContainerFrame #define NS_BLOCK_FRAME_CID \ @@ -224,6 +229,11 @@ public: virtual void DeleteNextInFlowChild(nsPresContext* aPresContext, nsIFrame* aNextInFlow); + // Determines whether the collapsed margin carried out of the last + // line includes the margin-top of a line with clearance (in which + // case we must avoid collapsing that margin with our bottom margin) + PRBool CheckForCollapsedBottomMarginFromClearanceLine(); + /** return the topmost block child based on y-index. * almost always the first or second line, if there is one. * accounts for lines that hold only compressed white space, etc. diff --git a/mozilla/layout/generic/nsBlockReflowContext.cpp b/mozilla/layout/generic/nsBlockReflowContext.cpp index 021b9a25b6a..dc7f828f3c4 100644 --- a/mozilla/layout/generic/nsBlockReflowContext.cpp +++ b/mozilla/layout/generic/nsBlockReflowContext.cpp @@ -52,6 +52,7 @@ #include "nsIDOMHTMLBodyElement.h" #include "nsLayoutAtoms.h" #include "nsCOMPtr.h" +#include "nsLayoutUtils.h" #ifdef NS_DEBUG #undef NOISY_MAX_ELEMENT_SIZE @@ -79,13 +80,12 @@ nsBlockReflowContext::nsBlockReflowContext(nsPresContext* aPresContext, mMetrics.mFlags |= NS_REFLOW_CALC_MAX_WIDTH; } -void -nsBlockReflowContext::ComputeCollapsedTopMargin(nsPresContext* aPresContext, - nsHTMLReflowState& aRS, - /* inout */ nsCollapsingMargin& aMargin) +PRBool +nsBlockReflowContext::ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS, + nsCollapsingMargin* aMargin, nsIFrame* aClearanceFrame, PRBool* aMayNeedRetry) { - // Get aFrame's top margin - aMargin.Include(aRS.mComputedMargin.top); + // Include frame's top margin + aMargin->Include(aRS.mComputedMargin.top); // The inclusion of the bottom margin when empty is done by the caller // since it doesn't need to be done by the top-level (non-recursive) @@ -93,48 +93,77 @@ nsBlockReflowContext::ComputeCollapsedTopMargin(nsPresContext* aPresContext, #ifdef NOISY_VERTICAL_MARGINS nsFrame::ListTag(stdout, aRS.frame); - printf(": %d => %d\n", aRS.mComputedMargin.top, aMargin.get()); + printf(": %d => %d\n", aRS.mComputedMargin.top, aMargin->get()); #endif - // Calculate aFrame's generational top-margin from its child - // blocks. Note that if aFrame has a non-zero top-border or + PRBool dirtiedLine = PR_FALSE; + + // Calculate the frame's generational top-margin from its child + // blocks. Note that if the frame has a non-zero top-border or // top-padding then this step is skipped because it will be a margin // root. It is also skipped if the frame is a margin root for other // reasons. + void* bf; if (0 == aRS.mComputedBorderPadding.top && - !(aRS.frame->GetStateBits() & NS_BLOCK_MARGIN_ROOT)) { - nsBlockFrame* bf; - if (NS_SUCCEEDED(aRS.frame->QueryInterface(kBlockFrameCID, - NS_REINTERPRET_CAST(void**, &bf)))) { - for (nsBlockFrame::line_iterator line = bf->begin_lines(), - line_end = bf->end_lines(); - line != line_end; ++line) { - PRBool isEmpty = line->IsEmpty(); - if (line->IsBlock()) { - // Here is where we recur. Now that we have determined that a - // generational collapse is required we need to compute the - // child blocks margin and so in so that we can look into - // it. For its margins to be computed we need to have a reflow - // state for it. Since the reflow reason is irrelevant, we'll - // arbitrarily make it a `resize' to avoid the path-plucking - // behavior if we're in an incremental reflow. - nsSize availSpace(aRS.mComputedWidth, aRS.mComputedHeight); - nsHTMLReflowState reflowState(aPresContext, aRS, line->mFirstChild, - availSpace, eReflowReason_Resize); - ComputeCollapsedTopMargin(aPresContext, reflowState, aMargin); - if (isEmpty) - aMargin.Include(reflowState.mComputedMargin.bottom); - } - if (!isEmpty) - break; + !(aRS.frame->GetStateBits() & NS_BLOCK_MARGIN_ROOT) && + NS_SUCCEEDED(aRS.frame->QueryInterface(kBlockFrameCID, &bf))) { + nsBlockFrame* block = NS_STATIC_CAST(nsBlockFrame*, aRS.frame); + + for (nsBlockFrame::line_iterator line = block->begin_lines(), + line_end = block->end_lines(); + line != line_end; ++line) { + if (!aClearanceFrame && line->HasClearance()) { + // If we don't have a clearance frame, then we're computing + // the collapsed margin in the first pass, assuming that all + // lines have no clearance. So clear their clearance flags. + line->ClearHasClearance(); + line->MarkDirty(); + dirtiedLine = PR_TRUE; } + + PRBool isEmpty = line->IsEmpty(); + if (line->IsBlock()) { + nsBlockFrame* kidBlock = NS_STATIC_CAST(nsBlockFrame*, line->mFirstChild); + if (kidBlock == aClearanceFrame) { + line->SetHasClearance(); + line->MarkDirty(); + dirtiedLine = PR_TRUE; + break; + } + // Here is where we recur. Now that we have determined that a + // generational collapse is required we need to compute the + // child blocks margin and so in so that we can look into + // it. For its margins to be computed we need to have a reflow + // state for it. Since the reflow reason is irrelevant, we'll + // arbitrarily make it a `resize' to avoid the path-plucking + // behavior if we're in an incremental reflow. + nsSize availSpace(aRS.mComputedWidth, aRS.mComputedHeight); + nsHTMLReflowState reflowState(kidBlock->GetPresContext(), + aRS, kidBlock, + availSpace, eReflowReason_Resize); + // Record that we're being optimistic by assuming the kid + // has no clearance + if (kidBlock->GetStyleDisplay()->mBreakType != NS_STYLE_CLEAR_NONE) { + *aMayNeedRetry = PR_TRUE; + } + if (ComputeCollapsedTopMargin(reflowState, aMargin, aClearanceFrame, aMayNeedRetry)) { + line->MarkDirty(); + dirtiedLine = PR_TRUE; + } + if (isEmpty) + aMargin->Include(reflowState.mComputedMargin.bottom); + } + if (!isEmpty) + break; } } - + #ifdef NOISY_VERTICAL_MARGINS nsFrame::ListTag(stdout, aRS.frame); printf(": => %d\n", aMargin.get()); #endif + + return dirtiedLine; } struct nsBlockHorizontalAlign { @@ -302,7 +331,8 @@ nsPointDtor(void *aFrame, nsIAtom *aPropertyName, nsresult nsBlockReflowContext::ReflowBlock(const nsRect& aSpace, PRBool aApplyTopMargin, - nsCollapsingMargin& aPrevBottomMargin, + nsCollapsingMargin& aPrevMargin, + nscoord aClearance, PRBool aIsAdjacentWithTop, nsMargin& aComputedOffsets, nsHTMLReflowState& aFrameRS, @@ -354,32 +384,8 @@ nsBlockReflowContext::ReflowBlock(const nsRect& aSpace, aFrameRS.reason = eReflowReason_Dirty; } - /* We build a different reflow context based on the width attribute of the block, - * if it's a float. - * Auto-width floats need to have their containing-block size set explicitly, - * factoring in other floats that impact it. - * It's possible this should be quirks-only. - * All other blocks proceed normally. - */ - // XXXldb We should really fix this in nsHTMLReflowState::InitConstraints instead. - const nsStylePosition* position = mFrame->GetStylePosition(); - nsStyleUnit widthUnit = position->mWidth.GetUnit(); const nsStyleDisplay* display = mFrame->GetStyleDisplay(); - if ((eStyleUnit_Auto == widthUnit) && - ((NS_STYLE_FLOAT_LEFT == display->mFloats) || - (NS_STYLE_FLOAT_RIGHT == display->mFloats))) { - // Initialize the reflow state and constrain the containing block's - // width and height to the available width and height. - aFrameRS.Init(mPresContext, mSpace.width, mSpace.height); - } - else { - // Initialize the reflow state and use the containing block's computed - // width and height (or derive appropriate values for an - // absolutely positioned frame). - aFrameRS.Init(mPresContext); - } - aComputedOffsets = aFrameRS.mComputedOffsets; if (NS_STYLE_POSITION_RELATIVE == display->mPosition) { nsPropertyTable *propTable = mPresContext->PropertyTable(); @@ -404,23 +410,20 @@ nsBlockReflowContext::ReflowBlock(const nsRect& aSpace, mComputedWidth = aFrameRS.mComputedWidth; if (aApplyTopMargin) { - // Compute the childs collapsed top margin (its margin collpased - // with its first childs top-margin -- recursively). - ComputeCollapsedTopMargin(mPresContext, aFrameRS, aPrevBottomMargin); + mTopMargin = aPrevMargin; #ifdef NOISY_VERTICAL_MARGINS nsFrame::ListTag(stdout, mOuterReflowState.frame); printf(": reflowing "); nsFrame::ListTag(stdout, mFrame); - printf(" margin => %d\n", aPrevBottomMargin.get()); + printf(" margin => %d, clearance => %d\n", mTopMargin.get(), aClearance); #endif // Adjust the available height if its constrained so that the // child frame doesn't think it can reflow into its margin area. if (NS_UNCONSTRAINEDSIZE != aFrameRS.availableHeight) { - aFrameRS.availableHeight -= aPrevBottomMargin.get(); + aFrameRS.availableHeight -= mTopMargin.get() + aClearance; } - mTopMargin = aPrevBottomMargin; } // Compute x/y coordinate where reflow will begin. Use the rules @@ -431,7 +434,7 @@ nsBlockReflowContext::ReflowBlock(const nsRect& aSpace, mStyleMargin = aFrameRS.mStyleMargin; mStylePadding = aFrameRS.mStylePadding; nscoord x; - nscoord y = mSpace.y + mTopMargin.get(); + nscoord y = mSpace.y + mTopMargin.get() + aClearance; // If it's a right floated element, then calculate the x-offset // differently @@ -647,6 +650,7 @@ nsBlockReflowContext::ReflowBlock(const nsRect& aSpace, PRBool nsBlockReflowContext::PlaceBlock(const nsHTMLReflowState& aReflowState, PRBool aForceFit, + nsLineBox* aLine, const nsMargin& aComputedOffsets, nsCollapsingMargin& aBottomMarginResult, nsRect& aInFlowBounds, @@ -660,15 +664,13 @@ nsBlockReflowContext::PlaceBlock(const nsHTMLReflowState& aReflowState, PRBool fits = PR_TRUE; nscoord x = mX; nscoord y = mY; - // When deciding whether it's empty we also need to take into - // account the overflow area - // XXXldb What should really matter is whether there exist non- - // empty frames in the block (with appropriate whitespace munging). - // Consider the case where we clip off the overflow with - // 'overflow: -moz-hidden-unscrollable' (which doesn't currently - // affect mOverflowArea, but probably should. - if ((0 == mMetrics.height) && (0 == mMetrics.mOverflowArea.height)) + // Check whether the block's bottom margin collapses with its top + // margin. See CSS 2.1 section 8.3.1; those rules seem to match + // nsBlockFrame::IsEmpty(). Any such block must have zero height so + // check that first. + if (0 == mMetrics.height && !aLine->HasClearance() && + aLine->CachedIsEmpty()) { // Collapse the bottom margin with the top margin that was already // applied. diff --git a/mozilla/layout/generic/nsBlockReflowContext.h b/mozilla/layout/generic/nsBlockReflowContext.h index 6868a664510..a6aaaec7536 100644 --- a/mozilla/layout/generic/nsBlockReflowContext.h +++ b/mozilla/layout/generic/nsBlockReflowContext.h @@ -39,10 +39,12 @@ #define nsBlockReflowContext_h___ #include "nsIFrame.h" -#include "nsHTMLReflowState.h" #include "nsHTMLReflowMetrics.h" class nsBlockFrame; +class nsBlockReflowState; +class nsHTMLReflowState; +class nsLineBox; class nsIFrame; class nsPresContext; class nsLineLayout; @@ -62,7 +64,8 @@ public: nsresult ReflowBlock(const nsRect& aSpace, PRBool aApplyTopMargin, - nsCollapsingMargin& aPrevBottomMargin, + nsCollapsingMargin& aPrevMargin, + nscoord aClearance, PRBool aIsAdjacentWithTop, nsMargin& aComputedOffsets, nsHTMLReflowState& aReflowState, @@ -70,6 +73,7 @@ public: PRBool PlaceBlock(const nsHTMLReflowState& aReflowState, PRBool aForceFit, + nsLineBox* aLine, const nsMargin& aComputedOffsets, nsCollapsingMargin& aBottomMarginResult /* out */, nsRect& aInFlowBounds, @@ -101,9 +105,23 @@ public: return mMetrics.mMaximumWidth; } - static void ComputeCollapsedTopMargin(nsPresContext* aPresContext, - nsHTMLReflowState& aRS, - /* inout */ nsCollapsingMargin& aMargin); + /** + * Computes the collapsed top margin for a block whose reflow state is in aRS. + * The computed margin is added into aMargin. + * If aClearanceFrame is null then this is the first optimistic pass which shall assume + * that no frames have clearance, and we clear the HasClearance on all frames encountered. + * If non-null, this is the second pass and + * the caller has decided aClearanceFrame needs clearance (and we will + * therefore stop collapsing there); also, this function is responsible for marking + * it with SetHasClearance. + * If in the optimistic pass any frame is encountered that might possibly need + * clearance (i.e., if we really needed the optimism assumption) then we set aMayNeedRetry + * to true. + * We return PR_TRUE if we changed the clearance state of any line and marked it dirty. + */ + static PRBool ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS, + nsCollapsingMargin* aMargin, nsIFrame* aClearanceFrame, + PRBool* aMayNeedRetry); protected: nsPresContext* mPresContext; diff --git a/mozilla/layout/generic/nsBlockReflowState.cpp b/mozilla/layout/generic/nsBlockReflowState.cpp index 9554d37d99e..d8a1323dbab 100644 --- a/mozilla/layout/generic/nsBlockReflowState.cpp +++ b/mozilla/layout/generic/nsBlockReflowState.cpp @@ -60,7 +60,8 @@ nsBlockReflowState::nsBlockReflowState(const nsHTMLReflowState& aReflowState, nsPresContext* aPresContext, nsBlockFrame* aFrame, const nsHTMLReflowMetrics& aMetrics, - PRBool aBlockMarginRoot) + PRBool aTopMarginRoot, + PRBool aBottomMarginRoot) : mBlock(aFrame), mPresContext(aPresContext), mReflowState(aReflowState), @@ -71,14 +72,10 @@ nsBlockReflowState::nsBlockReflowState(const nsHTMLReflowState& aReflowState, { const nsMargin& borderPadding = BorderPadding(); - if (aBlockMarginRoot) { - SetFlag(BRS_ISTOPMARGINROOT, PR_TRUE); - SetFlag(BRS_ISBOTTOMMARGINROOT, PR_TRUE); - } - if (0 != aReflowState.mComputedBorderPadding.top) { + if (aTopMarginRoot || 0 != aReflowState.mComputedBorderPadding.top) { SetFlag(BRS_ISTOPMARGINROOT, PR_TRUE); } - if (0 != aReflowState.mComputedBorderPadding.bottom) { + if (aBottomMarginRoot || 0 != aReflowState.mComputedBorderPadding.bottom) { SetFlag(BRS_ISBOTTOMMARGINROOT, PR_TRUE); } if (GetFlag(BRS_ISTOPMARGINROOT)) { @@ -338,68 +335,6 @@ nsBlockReflowState::GetAvailableSpace(nscoord aY) #endif } -PRBool -nsBlockReflowState::ClearPastFloats(PRUint8 aBreakType) -{ - nscoord saveY, deltaY; - - PRBool applyTopMargin = PR_FALSE; - switch (aBreakType) { - case NS_STYLE_CLEAR_LEFT: - case NS_STYLE_CLEAR_RIGHT: - case NS_STYLE_CLEAR_LEFT_AND_RIGHT: - // Apply the previous margin before clearing - saveY = mY + mPrevBottomMargin.get(); - ClearFloats(saveY, aBreakType); -#ifdef NOISY_FLOAT_CLEARING - nsFrame::ListTag(stdout, mBlock); - printf(": ClearPastFloats: mPrevBottomMargin=%d saveY=%d oldY=%d newY=%d deltaY=%d\n", - mPrevBottomMargin.get(), saveY, saveY - mPrevBottomMargin.get(), mY, - mY - saveY); -#endif - - // Determine how far we just moved. If we didn't move then there - // was nothing to clear to don't mess with the normal margin - // collapsing behavior. In either case we need to restore the Y - // coordinate to what it was before the clear. - deltaY = mY - saveY; - if (0 != deltaY) { - // Pretend that the distance we just moved is a previous - // blocks bottom margin. Note that GetAvailableSpace has been - // done so that the available space calculations will be done - // after clearing the appropriate floats. - // - // What we are doing here is applying CSS2 section 9.5.2's - // rules for clearing - "The top margin of the generated box - // is increased enough that the top border edge is below the - // bottom outer edge of the floating boxes..." - // - // What this will do is cause the top-margin of the block - // frame we are about to reflow to be collapsed with that - // distance. - - // XXXldb This doesn't handle collapsing with negative margins - // correctly, although it's arguable what "correct" is. - - // XXX Are all the other margins included by this point? - mPrevBottomMargin.Zero(); - mPrevBottomMargin.Include(deltaY); - mY = saveY; - - // Force margin to be applied in this circumstance - applyTopMargin = PR_TRUE; - } - else { - // Put mY back to its original value since no clearing - // happened. That way the previous blocks bottom margin will - // be applied properly. - mY = saveY - mPrevBottomMargin.get(); - } - break; - } - return applyTopMargin; -} - /* * Reconstruct the vertical margin before the line |aLine| in order to * do an incremental reflow that begins with |aLine| without reflowing @@ -851,12 +786,10 @@ nsBlockReflowState::FlowAndPlaceFloat(nsFloatCache* aFloatCache, // See if the float should clear any preceeding floats... if (NS_STYLE_CLEAR_NONE != floatDisplay->mBreakType) { // XXXldb Does this handle vertical margins correctly? - ClearFloats(mY, floatDisplay->mBreakType); + mY = ClearFloats(mY, floatDisplay->mBreakType); } - else { // Get the band of available space GetAvailableSpace(); - } NS_ASSERTION(floatFrame->GetParent() == mBlock, "Float frame has wrong parent"); @@ -1154,14 +1087,14 @@ nsBlockReflowState::PlaceBelowCurrentLineFloats(nsFloatCacheList& aList) return PR_TRUE; } -void +nscoord nsBlockReflowState::ClearFloats(nscoord aY, PRUint8 aBreakType) { #ifdef DEBUG if (nsBlockFrame::gNoisyReflow) { nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent); - printf("clear floats: in: mY=%d aY=%d(%d)\n", - mY, aY, aY - BorderPadding().top); + printf("clear floats: in: aY=%d(%d)\n", + aY, aY - BorderPadding().top); } #endif @@ -1173,14 +1106,15 @@ nsBlockReflowState::ClearFloats(nscoord aY, PRUint8 aBreakType) const nsMargin& bp = BorderPadding(); nscoord newY = mSpaceManager->ClearFloats(aY - bp.top, aBreakType); - mY = newY + bp.top; - GetAvailableSpace(); + newY += bp.top; #ifdef DEBUG if (nsBlockFrame::gNoisyReflow) { nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent); - printf("clear floats: out: mY=%d(%d)\n", mY, mY - bp.top); + printf("clear floats: out: y=%d(%d)\n", newY, newY - bp.top); } #endif + + return newY; } diff --git a/mozilla/layout/generic/nsBlockReflowState.h b/mozilla/layout/generic/nsBlockReflowState.h index 97939580a78..f5c35b5b4b9 100644 --- a/mozilla/layout/generic/nsBlockReflowState.h +++ b/mozilla/layout/generic/nsBlockReflowState.h @@ -53,7 +53,7 @@ public: nsPresContext* aPresContext, nsBlockFrame* aFrame, const nsHTMLReflowMetrics& aMetrics, - PRBool aBlockMarginRoot); + PRBool aTopMarginRoot, PRBool aBottomMarginRoot); ~nsBlockReflowState(); @@ -85,12 +85,9 @@ public: PRBool PlaceBelowCurrentLineFloats(nsFloatCacheList& aFloats); - // called when clearing a line with a break type caused by a BR past - // floats, and also used internally by ClearPastFloats - void ClearFloats(nscoord aY, PRUint8 aBreakType); - - // called when clearing a block past floats - PRBool ClearPastFloats(PRUint8 aBreakType); + // Returns the first coordinate >= aY that clears the + // indicated floats. + nscoord ClearFloats(nscoord aY, PRUint8 aBreakType); PRBool IsAdjacentWithTop() const { return mY == mReflowState.mComputedBorderPadding.top; diff --git a/mozilla/layout/generic/nsHTMLReflowState.cpp b/mozilla/layout/generic/nsHTMLReflowState.cpp index cf63950398f..c655f0932f6 100644 --- a/mozilla/layout/generic/nsHTMLReflowState.cpp +++ b/mozilla/layout/generic/nsHTMLReflowState.cpp @@ -111,6 +111,8 @@ nsHTMLReflowState::nsHTMLReflowState(nsPresContext* aPresContext, mFlags.mSpecialHeightReflow = PR_FALSE; mFlags.mIsTopOfPage = PR_FALSE; mFlags.mNextInFlowUntouched = PR_FALSE; + mFlags.mHasClearance = PR_FALSE; + mDiscoveredClearance = nsnull; mPercentHeightObserver = nsnull; mPercentHeightReflowInitiator = nsnull; Init(aPresContext); @@ -143,6 +145,8 @@ nsHTMLReflowState::nsHTMLReflowState(nsPresContext* aPresContext, mFlags.mSpecialHeightReflow = PR_FALSE; mFlags.mIsTopOfPage = PR_FALSE; mFlags.mNextInFlowUntouched = PR_FALSE; + mFlags.mHasClearance = PR_FALSE; + mDiscoveredClearance = nsnull; mPercentHeightObserver = nsnull; mPercentHeightReflowInitiator = nsnull; Init(aPresContext); @@ -193,6 +197,8 @@ nsHTMLReflowState::nsHTMLReflowState(nsPresContext* aPresContext, mFlags.mIsTopOfPage = aParentReflowState.mFlags.mIsTopOfPage; mFlags.mNextInFlowUntouched = aParentReflowState.mFlags.mNextInFlowUntouched && CheckNextInFlowParenthood(aFrame, aParentReflowState.frame); + mFlags.mHasClearance = PR_FALSE; + mDiscoveredClearance = nsnull; mPercentHeightObserver = (aParentReflowState.mPercentHeightObserver && aParentReflowState.mPercentHeightObserver->NeedsToObserve(*this)) ? aParentReflowState.mPercentHeightObserver : nsnull; @@ -240,6 +246,8 @@ nsHTMLReflowState::nsHTMLReflowState(nsPresContext* aPresContext, mFlags.mIsTopOfPage = aParentReflowState.mFlags.mIsTopOfPage; mFlags.mNextInFlowUntouched = aParentReflowState.mFlags.mNextInFlowUntouched && CheckNextInFlowParenthood(aFrame, aParentReflowState.frame); + mFlags.mHasClearance = PR_FALSE; + mDiscoveredClearance = nsnull; mPercentHeightObserver = (aParentReflowState.mPercentHeightObserver && aParentReflowState.mPercentHeightObserver->NeedsToObserve(*this)) ? aParentReflowState.mPercentHeightObserver : nsnull; @@ -287,6 +295,8 @@ nsHTMLReflowState::nsHTMLReflowState(nsPresContext* aPresContext, mFlags.mIsTopOfPage = aParentReflowState.mFlags.mIsTopOfPage; mFlags.mNextInFlowUntouched = aParentReflowState.mFlags.mNextInFlowUntouched && CheckNextInFlowParenthood(aFrame, aParentReflowState.frame); + mFlags.mHasClearance = PR_FALSE; + mDiscoveredClearance = nsnull; mPercentHeightObserver = (aParentReflowState.mPercentHeightObserver && aParentReflowState.mPercentHeightObserver->NeedsToObserve(*this)) ? aParentReflowState.mPercentHeightObserver : nsnull; diff --git a/mozilla/layout/generic/nsHTMLReflowState.h b/mozilla/layout/generic/nsHTMLReflowState.h index 5cb0de47324..39d93fcda2d 100644 --- a/mozilla/layout/generic/nsHTMLReflowState.h +++ b/mozilla/layout/generic/nsHTMLReflowState.h @@ -237,6 +237,13 @@ struct nsHTMLReflowState { // a frame (e.g. nsTableFrame) which initiates a special reflow for percent height calculations nsIFrame* mPercentHeightReflowInitiator; + // CSS margin collapsing sometimes requires us to reflow + // optimistically assuming that margins collapse to see if clearance + // is required. When we discover that clearance is required, we + // store the frame in which clearance was discovered to the location + // requested here. + nsIFrame** mDiscoveredClearance; + // This value keeps track of how deeply nested a given reflow state // is from the top of the frame tree. PRInt16 mReflowDepth; @@ -249,6 +256,7 @@ struct nsHTMLReflowState { PRUint16 mIsTopOfPage:1; // is the current context at the top of a page? PRUint16 mBlinks:1; // Keep track of text-decoration: blink PRUint16 mVisualBidiFormControl:1; // Keep track of descendants of form controls on Visual Bidi pages + PRUint16 mHasClearance:1; // Block has clearance } mFlags; #ifdef IBMBIDI diff --git a/mozilla/layout/generic/nsLineBox.cpp b/mozilla/layout/generic/nsLineBox.cpp index 7959bf7e09b..b0f78795bd6 100644 --- a/mozilla/layout/generic/nsLineBox.cpp +++ b/mozilla/layout/generic/nsLineBox.cpp @@ -181,13 +181,14 @@ BreakTypeToString(PRUint8 aBreakType) char* nsLineBox::StateToString(char* aBuf, PRInt32 aBufSize) const { - PR_snprintf(aBuf, aBufSize, "%s,%s,%s,%s,%s,%s[0x%x]", + PR_snprintf(aBuf, aBufSize, "%s,%s,%s,%s,%s,before:%s,after:%s[0x%x]", IsBlock() ? "block" : "inline", IsDirty() ? "dirty" : "clean", IsPreviousMarginDirty() ? "prevmargindirty" : "prevmarginclean", IsImpactedByFloat() ? "impacted" : "not impacted", IsLineWrapped() ? "wrapped" : "not wrapped", - BreakTypeToString(GetBreakType()), + BreakTypeToString(GetBreakTypeBefore()), + BreakTypeToString(GetBreakTypeAfter()), mAllFlags); return aBuf; } @@ -604,7 +605,7 @@ nsLineIterator::GetLine(PRInt32 aLineNumber, flags |= NS_LINE_FLAG_IS_BLOCK; } else { - if (line->HasBreak()) + if (line->HasBreakAfter()) flags |= NS_LINE_FLAG_ENDS_IN_BREAK; } *aLineFlags = flags; diff --git a/mozilla/layout/generic/nsLineBox.h b/mozilla/layout/generic/nsLineBox.h index 9f79292ebd1..dc2eee87e84 100644 --- a/mozilla/layout/generic/nsLineBox.h +++ b/mozilla/layout/generic/nsLineBox.h @@ -246,6 +246,17 @@ public: return mFlags.mPreviousMarginDirty; } + // mHasClearance bit + void SetHasClearance() { + mFlags.mHasClearance = 1; + } + void ClearHasClearance() { + mFlags.mHasClearance = 0; + } + PRBool HasClearance() const { + return mFlags.mHasClearance; + } + // mImpactedByFloat bit void SetLineIsImpactedByFloat(PRBool aValue) { NS_ASSERTION((PR_FALSE==aValue || PR_TRUE==aValue), "somebody is playing fast and loose with bools and bits!"); @@ -300,20 +311,37 @@ public: } // mBreakType value - PRBool HasBreak() const { - return NS_STYLE_CLEAR_NONE != mFlags.mBreakType; + // Break information is applied *before* the line if the line is a block, + // or *after* the line if the line is an inline. Confusing, I know, but + // using different names should help. + PRBool HasBreakBefore() const { + return IsBlock() && NS_STYLE_CLEAR_NONE != mFlags.mBreakType; } - PRBool HasFloatBreak() const { - return NS_STYLE_CLEAR_LEFT == mFlags.mBreakType || - NS_STYLE_CLEAR_RIGHT == mFlags.mBreakType || - NS_STYLE_CLEAR_LEFT_AND_RIGHT == mFlags.mBreakType; - } - void SetBreakType(PRUint8 aBreakType) { - NS_WARN_IF_FALSE(aBreakType <= LINE_MAX_BREAK_TYPE, "bad break type"); + void SetBreakTypeBefore(PRUint8 aBreakType) { + NS_ASSERTION(IsBlock(), "Only blocks have break-before"); + NS_ASSERTION(aBreakType <= NS_STYLE_CLEAR_LEFT_AND_RIGHT, + "Only float break types are allowed before a line"); mFlags.mBreakType = aBreakType; } - PRUint8 GetBreakType() const { - return mFlags.mBreakType; + PRUint8 GetBreakTypeBefore() const { + return IsBlock() ? mFlags.mBreakType : NS_STYLE_CLEAR_NONE; + } + + PRBool HasBreakAfter() const { + return !IsBlock() && NS_STYLE_CLEAR_NONE != mFlags.mBreakType; + } + void SetBreakTypeAfter(PRUint8 aBreakType) { + NS_ASSERTION(!IsBlock(), "Only inlines have break-after"); + NS_ASSERTION(aBreakType <= LINE_MAX_BREAK_TYPE, "bad break type"); + mFlags.mBreakType = aBreakType; + } + PRBool HasFloatBreakAfter() const { + return !IsBlock() && (NS_STYLE_CLEAR_LEFT == mFlags.mBreakType || + NS_STYLE_CLEAR_RIGHT == mFlags.mBreakType || + NS_STYLE_CLEAR_LEFT_AND_RIGHT == mFlags.mBreakType); + } + PRUint8 GetBreakTypeAfter() const { + return !IsBlock() ? mFlags.mBreakType : NS_STYLE_CLEAR_NONE; } // mCarriedOutBottomMargin value @@ -427,6 +455,7 @@ public: struct FlagBits { PRUint32 mDirty : 1; PRUint32 mPreviousMarginDirty : 1; + PRUint32 mHasClearance : 1; PRUint32 mBlock : 1; PRUint32 mImpactedByFloat : 1; PRUint32 mHasPercentageChild : 1; @@ -436,7 +465,7 @@ public: PRUint32 mEmptyCacheState: 1; PRUint32 mBreakType : 4; - PRUint32 mChildCount : 19; + PRUint32 mChildCount : 18; }; struct ExtraData { diff --git a/mozilla/layout/generic/nsSpaceManager.cpp b/mozilla/layout/generic/nsSpaceManager.cpp index 6b79a1890b6..3e50acf7289 100644 --- a/mozilla/layout/generic/nsSpaceManager.cpp +++ b/mozilla/layout/generic/nsSpaceManager.cpp @@ -1080,6 +1080,22 @@ nsSpaceManager::PopState() } } +void +nsSpaceManager::DiscardState() +{ + NS_ASSERTION(mSavedStates, "Invalid call to DiscardState()!"); + + if (!mSavedStates) { + return; + } + + SpaceManagerState *state = mSavedStates; + mSavedStates = mSavedStates->mNext; + if(state != &mAutoState) { + delete state; + } +} + nscoord nsSpaceManager::GetLowestRegionTop() { diff --git a/mozilla/layout/generic/nsSpaceManager.h b/mozilla/layout/generic/nsSpaceManager.h index e825ad9cd0d..f903308497c 100644 --- a/mozilla/layout/generic/nsSpaceManager.h +++ b/mozilla/layout/generic/nsSpaceManager.h @@ -311,6 +311,12 @@ public: */ void PopState(); + /** + * Pops the state off the stack without restoring it. Useful for speculative + * reflow where we're not sure if we're going to keep the result. + */ + void DiscardState(); + /** * Get the top of the last region placed into the space manager, to * enforce the rule that a float can't be above an earlier float. diff --git a/mozilla/layout/html/base/src/nsBlockFrame.cpp b/mozilla/layout/html/base/src/nsBlockFrame.cpp index 49eae095f6a..b7d327a7176 100644 --- a/mozilla/layout/html/base/src/nsBlockFrame.cpp +++ b/mozilla/layout/html/base/src/nsBlockFrame.cpp @@ -695,7 +695,8 @@ nsBlockFrame::Reflow(nsPresContext* aPresContext, } nsBlockReflowState state(aReflowState, aPresContext, this, aMetrics, - NS_BLOCK_MARGIN_ROOT & mState); + aReflowState.mFlags.mHasClearance || (NS_BLOCK_MARGIN_ROOT & mState), + (NS_BLOCK_MARGIN_ROOT & mState)); // The condition for doing Bidi resolutions includes a test for the // dirtiness flags, because blocks sometimes send a resize reflow @@ -1133,6 +1134,27 @@ IsPercentageAwareChild(const nsIFrame* aFrame) return PR_FALSE; } +PRBool +nsBlockFrame::CheckForCollapsedBottomMarginFromClearanceLine() +{ + line_iterator begin = begin_lines(); + line_iterator line = end_lines(); + + while (PR_TRUE) { + if (begin == line) { + return PR_FALSE; + } + --line; + if (line->mBounds.height != 0 || !line->CachedIsEmpty()) { + return PR_FALSE; + } + if (line->HasClearance()) { + return PR_TRUE; + } + } + // not reached +} + void nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, nsBlockReflowState& aState, @@ -1260,7 +1282,9 @@ nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, nscoord oldDesiredWidth = aMetrics.width; #endif nsBlockReflowState state(reflowState, aState.mPresContext, this, - aMetrics, NS_BLOCK_MARGIN_ROOT & mState); + aMetrics, + aReflowState.mFlags.mHasClearance || (NS_BLOCK_MARGIN_ROOT & mState), + (NS_BLOCK_MARGIN_ROOT & mState)); ReflowDirtyLines(state); aState.mY = state.mY; NS_ASSERTION(oldDesiredWidth == aMetrics.width, "bad desired width"); @@ -1268,6 +1292,29 @@ nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, } } + // Return bottom margin information + // rbs says he hit this assertion occasionally (see bug 86947), so + // just set the margin to zero and we'll figure out why later + //NS_ASSERTION(aMetrics.mCarriedOutBottomMargin.IsZero(), + // "someone else set the margin"); + nscoord nonCarriedOutVerticalMargin = 0; + if (!aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) { + // Apply rule from CSS 2.1 section 8.3.1. If we have some empty + // line with clearance and a non-zero top margin and all + // subsequent lines are empty, then we do not allow our childrens' + // carried out bottom margin to be carried out of us and collapse + // with our own bottom margin. + if (CheckForCollapsedBottomMarginFromClearanceLine()) { + // Convert the children's carried out margin to something that + // we will include in our height + nonCarriedOutVerticalMargin = aState.mPrevBottomMargin.get(); + aState.mPrevBottomMargin.Zero(); + } + aMetrics.mCarriedOutBottomMargin = aState.mPrevBottomMargin; + } else { + aMetrics.mCarriedOutBottomMargin.Zero(); + } + // Compute final height if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedHeight) { if (NS_FRAME_IS_COMPLETE(aState.mReflowStatus)) { @@ -1296,14 +1343,14 @@ nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, } else { // Use the current height; continuations will take up the rest. - aMetrics.height = aState.mY; + aMetrics.height = aState.mY + nonCarriedOutVerticalMargin; } // Don't carry out a bottom margin when our height is fixed. - aState.mPrevBottomMargin.Zero(); + aMetrics.mCarriedOutBottomMargin.Zero(); } else { - nscoord autoHeight = aState.mY; + nscoord autoHeight = aState.mY + nonCarriedOutVerticalMargin; // Shrink wrap our height around our contents. if (aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) { @@ -1357,16 +1404,6 @@ nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, #endif } - // Return bottom margin information - // rbs says he hit this assertion occasionally (see bug 86947), so - // just set the margin to zero and we'll figure out why later - //NS_ASSERTION(aMetrics.mCarriedOutBottomMargin.IsZero(), - // "someone else set the margin"); - if (!aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) - aMetrics.mCarriedOutBottomMargin = aState.mPrevBottomMargin; - else - aMetrics.mCarriedOutBottomMargin.Zero(); - #ifdef DEBUG_blocks if (CRAZY_WIDTH(aMetrics.width) || CRAZY_HEIGHT(aMetrics.height)) { ListTag(stdout); @@ -1537,7 +1574,7 @@ nsBlockFrame::RetargetInlineIncrementalReflow(nsReflowPath::iterator &aTarget, // continuations will be preserved during an unconstrained reflow. // XXXwaterson should this be `!= NS_STYLE_CLEAR_NONE'? --aLine; - if (aLine->GetBreakType() == NS_STYLE_CLEAR_LINE) + if (aLine->GetBreakTypeAfter() == NS_STYLE_CLEAR_LINE) break; *aTarget = aPrevInFlow; @@ -1793,7 +1830,7 @@ nsBlockFrame::PrepareResizeReflow(nsBlockReflowState& aState) if (line->IsBlock() || line->HasPercentageChild() || line->HasFloats() || - (line != mLines.back() && !line->HasBreak()) || + (line != mLines.back() && !line->HasBreakAfter()) || line->ResizeReflowOptimizationDisabled() || line->IsImpactedByFloat() || (line->mBounds.XMost() > newAvailWidth)) { @@ -1809,14 +1846,14 @@ nsBlockFrame::PrepareResizeReflow(nsBlockReflowState& aState) #ifdef DEBUG if (gNoisyReflow && !line->IsDirty()) { IndentBy(stdout, gNoiseIndent + 1); - printf("skipped: line=%p next=%p %s %s%s%s breakType=%d xmost=%d\n", + printf("skipped: line=%p next=%p %s %s%s%s breakTypeBefore/After=%d/%d xmost=%d\n", NS_STATIC_CAST(void*, line.get()), NS_STATIC_CAST(void*, (line.next() != end_lines() ? line.next().get() : nsnull)), line->IsBlock() ? "block" : "inline", - line->HasBreak() ? "has-break " : "", + line->HasBreakAfter() ? "has-break-after " : "", line->HasFloats() ? "has-floats " : "", line->IsImpactedByFloat() ? "impacted " : "", - line->GetBreakType(), + line->GetBreakTypeBefore(), line->GetBreakTypeAfter(), line->mBounds.XMost()); } #endif @@ -1970,10 +2007,15 @@ DirtyLineIfWrappedLinesAreDirty(const nsLineList::iterator& aLine, } static void PlaceFrameView(nsPresContext* aPresContext, nsIFrame* aFrame); -static PRUint8 CombineBreakType(PRUint8 aOrigBreakType, PRUint8 aNewBreakType); static void CollectFloats(nsIFrame* aFrame, nsIFrame* aBlockParent, nsIFrame** aHead, nsIFrame** aTail); +static PRBool LineHasClear(nsLineBox* aLine) { + return aLine->GetBreakTypeBefore() || aLine->HasFloatBreakAfter() + || (aLine->IsBlock() && (aLine->mFirstChild->GetStateBits() & NS_BLOCK_HAS_CLEAR_CHILDREN)); +} + + static void ReparentFrame(nsIFrame* aFrame, nsIFrame* aOldParent, nsIFrame* aNewParent) { aFrame->SetParent(aNewParent); @@ -2026,6 +2068,7 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) nsresult rv = NS_OK; PRBool keepGoing = PR_TRUE; PRBool repositionViews = PR_FALSE; // should we really need this? + PRBool foundAnyClears = PR_FALSE; #ifdef DEBUG if (gNoisyReflow) { @@ -2066,8 +2109,9 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) // reflow it or if its previous margin is dirty PRBool needToRecoverState = PR_FALSE; PRBool lastLineMovedUp = PR_FALSE; - PRUint8 floatBreakType = NS_STYLE_CLEAR_NONE; - + // We save up information about BR-clearance here + PRUint8 inlineFloatBreakType = NS_STYLE_CLEAR_NONE; + // Reflow the lines that are already ours line_iterator line = begin_lines(), line_end = end_lines(); for ( ; line != line_end; ++line, aState.AdvanceToNextLine()) { @@ -2097,15 +2141,59 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) ::DirtyLineIfWrappedLinesAreDirty(line, line_end); } + // This really sucks, but we have to look inside any blocks that have clear + // elements inside them. + // XXX what can we do smarter here? + if (line->IsBlock() && + (line->mFirstChild->GetStateBits() & NS_BLOCK_HAS_CLEAR_CHILDREN)) { + line->MarkDirty(); + } + + // We have to reflow the line if it's a block whose clearance + // might have changed, so detect that. + if (!line->IsDirty() && line->GetBreakTypeBefore() != NS_STYLE_CLEAR_NONE) { + nscoord curY = aState.mY; + // See where we would be after applying any clearance due to + // BRs. + if (inlineFloatBreakType != NS_STYLE_CLEAR_NONE) { + curY = aState.ClearFloats(curY, inlineFloatBreakType); + } + + nscoord newY = aState.ClearFloats(curY, line->GetBreakTypeBefore()); + + if (line->HasClearance()) { + // Reflow the line if it might not have clearance anymore. + if (newY == curY + // aState.mY is the clearance point which should be the + // top border-edge of the block frame. If sliding the + // block by deltaY isn't going to put it in the predicted + // position, then we'd better reflow the line. + || newY != line->mBounds.y + deltaY) { + line->MarkDirty(); + } + } else { + // Reflow the line if the line might have clearance now. + if (curY != newY) { + line->MarkDirty(); + } + } + } + + // We might have to reflow a line that is after a clearing BR. + if (inlineFloatBreakType != NS_STYLE_CLEAR_NONE) { + aState.mY = aState.ClearFloats(aState.mY, inlineFloatBreakType); + if (aState.mY != line->mBounds.y + deltaY) { + // SlideLine is not going to put the line where the clearance + // put it. Reflow the line to be sure. + line->MarkDirty(); + } + inlineFloatBreakType = NS_STYLE_CLEAR_NONE; + } + // Make sure |aState.mPrevBottomMargin| is at the correct position // before calling PropagateFloatDamage. if (needToRecoverState && (line->IsDirty() || line->IsPreviousMarginDirty())) { - if (floatBreakType != NS_STYLE_CLEAR_NONE) { - aState.ClearFloats(aState.mY, floatBreakType); - floatBreakType = NS_STYLE_CLEAR_NONE; - } - // We need to reconstruct the bottom margin only if we didn't // reflow the previous line and we do need to reflow (or repair // the top position of) the next line. @@ -2218,25 +2306,29 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) aState.RecoverStateFrom(line, deltaY); // Keep mY up to date in case we're propagating reflow damage - // and also because our final height may depend on it. Only - // update mY if the line is not empty, because that's what - // PlaceLine does. - if (!line->CachedIsEmpty()) { + // and also because our final height may depend on it. If the + // line is inlines, then only update mY if the line is not + // empty, because that's what PlaceLine does. (Empty blocks may + // want to update mY, e.g. if they have clearance.) + if (line->IsBlock() || !line->CachedIsEmpty()) { aState.mY = line->mBounds.YMost(); - // This will include any pending float clearing height, so - // don't bother clearing previous lines' floats - floatBreakType = NS_STYLE_CLEAR_NONE; } // Record if we need to clear floats before reflowing the next - // line - if (line->HasFloatBreak()) { - floatBreakType = ::CombineBreakType(floatBreakType, line->GetBreakType()); + // line. Note that inlineFloatBreakType will be handled and + // cleared before the next line is processed, so there is no + // need to combine break types here. + if (line->HasFloatBreakAfter()) { + inlineFloatBreakType = line->GetBreakTypeAfter(); } needToRecoverState = PR_TRUE; } + if (LineHasClear(line.get())) { + foundAnyClears = PR_TRUE; + } + #ifdef DEBUG if (gNoisyReflow) { gNoiseIndent--; @@ -2399,6 +2491,10 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) break; } + if (LineHasClear(line.get())) { + foundAnyClears = PR_TRUE; + } + // If this is an inline frame then its time to stop ++line; aState.AdvanceToNextLine(); @@ -2420,6 +2516,12 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) aState.mY += metrics.height; } + if (foundAnyClears) { + AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN); + } else { + RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN); + } + #ifdef DEBUG if (gNoisyReflow) { gNoiseIndent--; @@ -3101,38 +3203,6 @@ nsBlockFrame::UndoSplitPlaceholders(nsBlockReflowState& aState, } } -// Combine aNewBreakType with aOrigBreakType, but limit the break types -// to NS_STYLE_CLEAR_LEFT, RIGHT, LEFT_AND_RIGHT. When there is a
    right -// after a float and the float splits, then the
    's break type is combined -// with the break type of the frame right after the floats next-in-flow. -static PRUint8 -CombineBreakType(PRUint8 aOrigBreakType, - PRUint8 aNewBreakType) -{ - PRUint8 breakType = aOrigBreakType; - switch(breakType) { - case NS_STYLE_CLEAR_LEFT: - if ((NS_STYLE_CLEAR_RIGHT == aNewBreakType) || - (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) { - breakType = NS_STYLE_CLEAR_LEFT_AND_RIGHT; - } - break; - case NS_STYLE_CLEAR_RIGHT: - if ((NS_STYLE_CLEAR_LEFT == aNewBreakType) || - (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) { - breakType = NS_STYLE_CLEAR_LEFT_AND_RIGHT; - } - break; - case NS_STYLE_CLEAR_NONE: - if ((NS_STYLE_CLEAR_LEFT == aNewBreakType) || - (NS_STYLE_CLEAR_RIGHT == aNewBreakType) || - (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aNewBreakType)) { - breakType = aNewBreakType; - } - } - return breakType; -} - nsresult nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState, line_iterator aLine, @@ -3154,320 +3224,434 @@ nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState, aState.GetFlag(BRS_COMPUTEMAXELEMENTWIDTH), aState.GetFlag(BRS_COMPUTEMAXWIDTH)); - // See if we should apply the top margin. If the block frame being - // reflowed is a continuation (non-null prev-in-flow) then we don't - // apply its top margin because its not significant. Otherwise, dig - // deeper. - PRBool applyTopMargin = PR_FALSE; - nsIFrame* framePrevInFlow = frame->GetPrevInFlow(); - if (nsnull == framePrevInFlow) { - applyTopMargin = ShouldApplyTopMargin(aState, aLine); - } - PRUint8 breakType = display->mBreakType; // If a float split and its prev-in-flow was followed by a
    , then combine // the
    's break type with the block's break type (the block will be the very // next frame after the split float). if (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType) { - breakType = ::CombineBreakType(breakType, aState.mFloatBreakType); + breakType = nsLayoutUtils::CombineBreakType(breakType, + aState.mFloatBreakType); aState.mFloatBreakType = NS_STYLE_CLEAR_NONE; } + // Clear past floats before the block if the clear style is not none - aLine->SetBreakType(breakType); - if (NS_STYLE_CLEAR_NONE != breakType) { - PRBool alsoApplyTopMargin = aState.ClearPastFloats(breakType); - if (alsoApplyTopMargin) { - applyTopMargin = PR_TRUE; - } -#ifdef NOISY_VERTICAL_MARGINS - ListTag(stdout); - printf(": y=%d child ", aState.mY); - ListTag(stdout, frame); - printf(" has clear of %d => %s, mPrevBottomMargin=%d\n", - breakType, - applyTopMargin ? "applyTopMargin" : "nope", - aState.mPrevBottomMargin); -#endif - } + aLine->SetBreakTypeBefore(breakType); + + // See if we should apply the top margin. If the block frame being + // reflowed is a continuation (non-null prev-in-flow) then we don't + // apply its top margin because its not significant. Otherwise, dig + // deeper. + PRBool applyTopMargin = + !frame->GetPrevInFlow() && ShouldApplyTopMargin(aState, aLine); - nscoord topMargin = 0; if (applyTopMargin) { - // Precompute the blocks top margin value so that we can get the - // correct available space (there might be a float that's - // already been placed below the aState.mPrevBottomMargin - - // Setup a reflowState to get the style computed margin-top - // value. We'll use a reason of `resize' so that we don't fudge - // any incremental reflow state. - - // The availSpace here is irrelevant to our needs - all we want - // out if this setup is the margin-top value which doesn't depend - // on the childs available space. - nsSize availSpace(aState.mContentArea.width, NS_UNCONSTRAINEDSIZE); - nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState, - frame, availSpace, eReflowReason_Resize); - - // Now compute the collapsed margin-top value into aState.mPrevBottomMargin - nsCollapsingMargin oldPrevBottomMargin = aState.mPrevBottomMargin; - nsBlockReflowContext::ComputeCollapsedTopMargin(aState.mPresContext, - reflowState, - aState.mPrevBottomMargin); - topMargin = aState.mPrevBottomMargin.get(); - aState.mPrevBottomMargin = oldPrevBottomMargin; // perhaps not needed - - // Temporarily advance the running Y value so that the - // GetAvailableSpace method will return the right available - // space. This undone as soon as the margin is computed. - aState.mY += topMargin; + // The HasClearance setting is only valid if ShouldApplyTopMargin + // returned PR_FALSE (in which case the top-margin-root set our + // clearance flag). Otherwise clear it now. We'll set it later on + // ourselves if necessary. + aLine->ClearHasClearance(); + } + PRBool treatWithClearance = aLine->HasClearance(); + // If our top margin was counted as part of some parents top-margin + // collapse and we are being speculatively reflowed assuming this + // frame DID NOT need clearance, then we need to check that + // assumption. + if (!treatWithClearance && !applyTopMargin && breakType != NS_STYLE_CLEAR_NONE && + aState.mReflowState.mDiscoveredClearance) { + nscoord curY = aState.mY + aState.mPrevBottomMargin.get(); + nscoord clearY = aState.ClearFloats(curY, breakType); + if (clearY != curY) { + // Looks like that assumption was invalid, we do need + // clearance. Tell our ancestor so it can reflow again. It is + // responsible for actually setting our clearance flag before + // the next reflow. + treatWithClearance = PR_TRUE; + // Only record the first frame that requires clearance + if (!*aState.mReflowState.mDiscoveredClearance) { + *aState.mReflowState.mDiscoveredClearance = frame; + } + // Exactly what we do now is flexible since we'll definitely be + // reflowed. + } + } + if (treatWithClearance) { + applyTopMargin = PR_TRUE; } - // Compute the available space for the block - aState.GetAvailableSpace(); -#ifdef REALLY_NOISY_REFLOW - printf("setting line %p isImpacted to %s\n", aLine, aState.IsImpactedByFloat()?"true":"false"); -#endif - PRBool isImpacted = aState.IsImpactedByFloat() ? PR_TRUE : PR_FALSE; - aLine->SetLineIsImpactedByFloat(isImpacted); - nsSplittableType splitType = NS_FRAME_NOT_SPLITTABLE; - frame->IsSplittable(splitType); - nsRect availSpace; - aState.ComputeBlockAvailSpace(frame, splitType, display, availSpace); + nsIFrame* clearanceFrame = nsnull; + nscoord startingY = aState.mY; + nsCollapsingMargin incomingMargin = aState.mPrevBottomMargin; + while (PR_TRUE) { + nscoord clearance = 0; + nscoord topMargin = 0; + PRBool mayNeedRetry = PR_FALSE; + if (applyTopMargin) { + // Precompute the blocks top margin value so that we can get the + // correct available space (there might be a float that's + // already been placed below the aState.mPrevBottomMargin - // Now put the Y coordinate back and flow the block letting the - // block reflow context compute the same top margin value we just - // computed (sigh). - if (topMargin) { + // Setup a reflowState to get the style computed margin-top + // value. We'll use a reason of `resize' so that we don't fudge + // any incremental reflow state. + + // The availSpace here is irrelevant to our needs - all we want + // out if this setup is the margin-top value which doesn't depend + // on the childs available space. + // XXX building a complete nsHTMLReflowState just to get the margin-top + // seems like a waste. And we do this for almost every block! + nsSize availSpace(aState.mContentArea.width, NS_UNCONSTRAINEDSIZE); + nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState, + frame, availSpace, eReflowReason_Resize); + + if (treatWithClearance) { + aState.mY += aState.mPrevBottomMargin.get(); + aState.mPrevBottomMargin.Zero(); + } + + // Now compute the collapsed margin-top value into aState.mPrevBottomMargin, assuming + // that all child margins collapse down to clearanceFrame. + nsBlockReflowContext::ComputeCollapsedTopMargin(reflowState, + &aState.mPrevBottomMargin, clearanceFrame, &mayNeedRetry); + + // XXX optimization; we could check the collapsing children to see if they are sure + // to require clearance, and so avoid retrying them + + if (clearanceFrame) { + // Don't allow retries on the second pass. The clearance decisions for the + // blocks whose top-margins collapse with ours are now fixed. + mayNeedRetry = PR_FALSE; + } + + if (!treatWithClearance && !clearanceFrame && breakType != NS_STYLE_CLEAR_NONE) { + // We don't know if we need clearance and this is the first, + // optimistic pass. So determine whether *this block* needs + // clearance. Note that we do not allow the decision for whether + // this block has clearance to change on the second pass; that + // decision is only allowed to be made under the optimistic + // first pass. + nscoord curY = aState.mY + aState.mPrevBottomMargin.get(); + nscoord clearY = aState.ClearFloats(curY, breakType); + if (clearY != curY) { + // Looks like we need clearance and we didn't know about it already. So + // recompute collapsed margin + treatWithClearance = PR_TRUE; + // Remember this decision, needed for incremental reflow + aLine->SetHasClearance(); + + // Apply incoming margins + aState.mY += aState.mPrevBottomMargin.get(); + aState.mPrevBottomMargin.Zero(); + + // Compute the collapsed margin again, ignoring the incoming margin this time + mayNeedRetry = PR_FALSE; + nsBlockReflowContext::ComputeCollapsedTopMargin(reflowState, + &aState.mPrevBottomMargin, clearanceFrame, &mayNeedRetry); + } + } + + // Temporarily advance the running Y value so that the + // GetAvailableSpace method will return the right available + // space. This undone as soon as the horizontal margins are + // computed. + topMargin = aState.mPrevBottomMargin.get(); + + if (treatWithClearance) { + nscoord currentY = aState.mY; + // advance mY to the clear position. + aState.mY = aState.ClearFloats(aState.mY, breakType); + + // Compute clearance. It's the amount we need to add to the top + // border-edge of the frame, after applying collapsed margins + // from the frame and its children, to get it to line up with + // the bottom of the floats. The former is currentY + topMargin, + // the latter is the current aState.mY. + // Note that negative clearance is possible + clearance = aState.mY - (currentY + topMargin); + + // Add clearance to our top margin while we compute available + // space for the frame + topMargin += clearance; + + // Note that aState.mY should stay where it is: at the top + // border-edge of the frame + } else { + // Advance aState.mY to the top border-edge of the frame. + aState.mY += topMargin; + } + } + + // Here aState.mY is the top border-edge of the block. + // Compute the available space for the block + aState.GetAvailableSpace(); +#ifdef REALLY_NOISY_REFLOW + printf("setting line %p isImpacted to %s\n", aLine, aState.IsImpactedByFloat()?"true":"false"); +#endif + PRBool isImpacted = aState.IsImpactedByFloat() ? PR_TRUE : PR_FALSE; + aLine->SetLineIsImpactedByFloat(isImpacted); + nsSplittableType splitType = NS_FRAME_NOT_SPLITTABLE; + frame->IsSplittable(splitType); + nsRect availSpace; + aState.ComputeBlockAvailSpace(frame, splitType, display, availSpace); + + // Now put the Y coordinate back to the top of the top-margin + + // clearance, and flow the block. aState.mY -= topMargin; availSpace.y -= topMargin; if (NS_UNCONSTRAINEDSIZE != availSpace.height) { availSpace.height += topMargin; } - } - - // keep track of the last overflow float in case we need to undo any new additions - nsFrameList* overflowPlace = GetOverflowPlaceholders(); - nsIFrame* lastPlaceholder = (overflowPlace) ? overflowPlace->LastChild() : nsnull; - - // Reflow the block into the available space - nsReflowStatus frameReflowStatus=NS_FRAME_COMPLETE; - nsMargin computedOffsets; - // construct the html reflow state for the block. ReflowBlock - // will initialize it and set its reason. - nsHTMLReflowState blockHtmlRS(aState.mPresContext, aState.mReflowState, frame, - nsSize(availSpace.width, availSpace.height), - aState.mReflowState.reason, PR_FALSE); - rv = brc.ReflowBlock(availSpace, applyTopMargin, aState.mPrevBottomMargin, - aState.IsAdjacentWithTop(), computedOffsets, - blockHtmlRS, frameReflowStatus); - + + // keep track of the last overflow float in case we need to undo any new additions + nsFrameList* overflowPlace = GetOverflowPlaceholders(); + nsIFrame* lastPlaceholder = (overflowPlace) ? overflowPlace->LastChild() : nsnull; + + // Reflow the block into the available space + nsMargin computedOffsets; + // construct the html reflow state for the block. ReflowBlock + // will initialize it + nsHTMLReflowState blockHtmlRS(aState.mPresContext, aState.mReflowState, frame, + nsSize(availSpace.width, availSpace.height), + aState.mReflowState.reason, PR_TRUE); + blockHtmlRS.mFlags.mHasClearance = aLine->HasClearance(); + + if (mayNeedRetry) { + blockHtmlRS.mDiscoveredClearance = &clearanceFrame; + aState.mSpaceManager->PushState(); + } else if (!applyTopMargin) { + blockHtmlRS.mDiscoveredClearance = aState.mReflowState.mDiscoveredClearance; + } + + nsReflowStatus frameReflowStatus = NS_FRAME_COMPLETE; + rv = brc.ReflowBlock(availSpace, applyTopMargin, aState.mPrevBottomMargin, + clearance, aState.IsAdjacentWithTop(), computedOffsets, + blockHtmlRS, frameReflowStatus); + // Remove the frame from the reflow tree. - if (aState.mReflowState.path) - aState.mReflowState.path->RemoveChild(frame); - - if (NS_FAILED(rv)) { - return rv; - } - aState.mPrevChild = frame; - + if (aState.mReflowState.path) + aState.mReflowState.path->RemoveChild(frame); + if (NS_FAILED(rv)) { + return rv; + } + + if (mayNeedRetry) { + if (clearanceFrame) { + aState.mSpaceManager->PopState(); + aState.mY = startingY; + aState.mPrevBottomMargin = incomingMargin; + continue; + } else { + // pop the saved state off the stack and discard it, because we + // want to keep the current state, since our speculation + // succeeded + aState.mSpaceManager->DiscardState(); + } + } + + aState.mPrevChild = frame; + #if defined(REFLOW_STATUS_COVERAGE) - RecordReflowStatus(PR_TRUE, frameReflowStatus); + RecordReflowStatus(PR_TRUE, frameReflowStatus); #endif - - if (NS_INLINE_IS_BREAK_BEFORE(frameReflowStatus)) { - // None of the child block fits. - UndoSplitPlaceholders(aState, lastPlaceholder); - PushLines(aState, aLine.prev()); - *aKeepReflowGoing = PR_FALSE; - aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; - } - else { - // Note: line-break-after a block is a nop - - // Try to place the child block - PRBool isAdjacentWithTop = aState.IsAdjacentWithTop(); - nsCollapsingMargin collapsedBottomMargin; - nsRect combinedArea(0,0,0,0); - *aKeepReflowGoing = brc.PlaceBlock(blockHtmlRS, isAdjacentWithTop, - computedOffsets, collapsedBottomMargin, - aLine->mBounds, combinedArea); - if (aLine->SetCarriedOutBottomMargin(collapsedBottomMargin)) { - line_iterator nextLine = aLine; - ++nextLine; - if (nextLine != end_lines()) { - nextLine->MarkPreviousMarginDirty(); - } - } - - if (aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { - // Mark the line as dirty so once we known the final shrink wrap width - // we can reflow the block to the correct size - // XXX We don't always need to do this... - aLine->MarkDirty(); - aState.SetFlag(BRS_NEEDRESIZEREFLOW, PR_TRUE); - } - if (aState.GetFlag(BRS_UNCONSTRAINEDWIDTH) || aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { - // Add the right margin to the line's bounds. That way it will be - // taken into account when we compute our shrink wrap size. - nscoord marginRight = brc.GetMargin().right; - if (marginRight != NS_UNCONSTRAINEDSIZE) { - aLine->mBounds.width += marginRight; - } - } - aLine->SetCombinedArea(combinedArea); - if (*aKeepReflowGoing) { - // Some of the child block fit - - // Advance to new Y position - nscoord newY = aLine->mBounds.YMost(); - aState.mY = newY; - - // Continue the block frame now if it didn't completely fit in - // the available space. - if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) { - PRBool madeContinuation; - rv = CreateContinuationFor(aState, nsnull, frame, madeContinuation); - if (NS_FAILED(rv)) - return rv; - - nsIFrame* nextFrame = frame->GetNextInFlow(); - - // Push continuation to a new line, but only if we actually made one. - if (madeContinuation) { - nsLineBox* line = aState.NewLineBox(nextFrame, 1, PR_TRUE); - if (nsnull == line) { - return NS_ERROR_OUT_OF_MEMORY; - } - mLines.after_insert(aLine, line); - } - - // Advance to next line since some of the block fit. That way - // only the following lines will be pushed. - PushLines(aState, aLine); - aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; - // If we need to reflow the continuation of the block child, - // then we'd better reflow our continuation - if (frameReflowStatus & NS_FRAME_REFLOW_NEXTINFLOW) { - aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW; - // We also need to make that continuation's line dirty so it - // gets reflowed when we reflow our next in flow. The - // nif's line must always be either the first line - // of the nif's parent block or else one of our own overflow - // lines. In the latter case the line is already marked dirty, - // so just detect and handle the first case. - nsBlockFrame* nifBlock = NS_STATIC_CAST(nsBlockFrame*, nextFrame->GetParent()); - NS_ASSERTION(nifBlock->GetType() == nsLayoutAtoms::blockFrame - || nifBlock->GetType() == nsLayoutAtoms::areaFrame, - "A block's child's next in flow's parent must be a block!"); - line_iterator firstLine = nifBlock->begin_lines(); - if (firstLine != nifBlock->end_lines() && firstLine->Contains(nextFrame)) { - firstLine->MarkDirty(); - } - } - *aKeepReflowGoing = PR_FALSE; - - // The bottom margin for a block is only applied on the last - // flow block. Since we just continued the child block frame, - // we know that line->mFirstChild is not the last flow block - // therefore zero out the running margin value. -#ifdef NOISY_VERTICAL_MARGINS - ListTag(stdout); - printf(": reflow incomplete, frame="); - nsFrame::ListTag(stdout, frame); - printf(" prevBottomMargin=%d, setting to zero\n", - aState.mPrevBottomMargin); -#endif - aState.mPrevBottomMargin.Zero(); - } - else { -#ifdef NOISY_VERTICAL_MARGINS - ListTag(stdout); - printf(": reflow complete for "); - nsFrame::ListTag(stdout, frame); - printf(" prevBottomMargin=%d collapsedBottomMargin=%d\n", - aState.mPrevBottomMargin, collapsedBottomMargin.get()); -#endif - aState.mPrevBottomMargin = collapsedBottomMargin; - } -#ifdef NOISY_VERTICAL_MARGINS - ListTag(stdout); - printf(": frame="); - nsFrame::ListTag(stdout, frame); - printf(" carriedOutBottomMargin=%d collapsedBottomMargin=%d => %d\n", - brc.GetCarriedOutBottomMargin(), collapsedBottomMargin.get(), - aState.mPrevBottomMargin); -#endif - - // Post-process the "line" - nscoord maxElementWidth = 0; - if (aState.GetFlag(BRS_COMPUTEMAXELEMENTWIDTH)) { - maxElementWidth = brc.GetMaxElementWidth(); - } - // If we asked the block to update its maximum width, then record the - // updated value in the line, and update the current maximum width - if (aState.GetFlag(BRS_COMPUTEMAXWIDTH)) { - aLine->mMaximumWidth = brc.GetMaximumWidth(); - aState.UpdateMaximumWidth(aLine->mMaximumWidth); - } - PostPlaceLine(aState, aLine, maxElementWidth); - - // If the block frame that we just reflowed happens to be our - // first block, then its computed ascent is ours - if (frame == GetTopBlockChild(aState.mPresContext)) { - const nsHTMLReflowMetrics& metrics = brc.GetMetrics(); - mAscent = metrics.ascent; - } - - // Place the "marker" (bullet) frame. - // - // According to the CSS2 spec, section 12.6.1, the "marker" box - // participates in the height calculation of the list-item box's - // first line box. - // - // There are exactly two places a bullet can be placed: near the - // first or second line. Its only placed on the second line in a - // rare case: an empty first line followed by a second line that - // contains a block (example:

  • \n

    ... ). This is where - // the second case can happen. - if (mBullet && HaveOutsideBullet() && - ((aLine == mLines.front()) || - ((0 == mLines.front()->mBounds.height) && - (aLine == begin_lines().next())))) { - // Reflow the bullet - nsHTMLReflowMetrics metrics(nsnull); - ReflowBullet(aState, metrics); - - // Doing the alignment using |mAscent| will also cater for bullets - // that are placed next to a child block (bug 92896) - // (Note that mAscent should be set by now, otherwise why would - // we be placing the bullet yet?) - - // Tall bullets won't look particularly nice here... - nsRect bbox = mBullet->GetRect(); - nscoord bulletTopMargin = applyTopMargin - ? collapsedBottomMargin.get() - : 0; - bbox.y = aState.BorderPadding().top + mAscent - - metrics.ascent + bulletTopMargin; - mBullet->SetRect(bbox); - } + + if (NS_INLINE_IS_BREAK_BEFORE(frameReflowStatus)) { + // None of the child block fits. + UndoSplitPlaceholders(aState, lastPlaceholder); + PushLines(aState, aLine.prev()); + *aKeepReflowGoing = PR_FALSE; + aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; } else { - // None of the block fits. Determine the correct reflow status. - if (aLine == mLines.front()) { - // If it's our very first line then we need to be pushed to - // our parents next-in-flow. Therefore, return break-before - // status for our reflow status. - aState.mReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); + // Note: line-break-after a block is a nop + + // Try to place the child block + PRBool isAdjacentWithTop = aState.IsAdjacentWithTop(); + nsCollapsingMargin collapsedBottomMargin; + nsRect combinedArea(0,0,0,0); + *aKeepReflowGoing = brc.PlaceBlock(blockHtmlRS, isAdjacentWithTop, + aLine.get(), + computedOffsets, collapsedBottomMargin, + aLine->mBounds, combinedArea); + if (aLine->SetCarriedOutBottomMargin(collapsedBottomMargin)) { + line_iterator nextLine = aLine; + ++nextLine; + if (nextLine != end_lines()) { + nextLine->MarkPreviousMarginDirty(); + } + } + + if (aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { + // Mark the line as dirty so once we known the final shrink wrap width + // we can reflow the block to the correct size + // XXX We don't always need to do this... + aLine->MarkDirty(); + aState.SetFlag(BRS_NEEDRESIZEREFLOW, PR_TRUE); + } + if (aState.GetFlag(BRS_UNCONSTRAINEDWIDTH) || aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { + // Add the right margin to the line's bounds. That way it will be + // taken into account when we compute our shrink wrap size. + nscoord marginRight = brc.GetMargin().right; + if (marginRight != NS_UNCONSTRAINEDSIZE) { + aLine->mBounds.width += marginRight; + } + } + aLine->SetCombinedArea(combinedArea); + if (*aKeepReflowGoing) { + // Some of the child block fit + + // Advance to new Y position + nscoord newY = aLine->mBounds.YMost(); + aState.mY = newY; + + // Continue the block frame now if it didn't completely fit in + // the available space. + if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) { + PRBool madeContinuation; + rv = CreateContinuationFor(aState, nsnull, frame, madeContinuation); + if (NS_FAILED(rv)) + return rv; + + nsIFrame* nextFrame = frame->GetNextInFlow(); + + // Push continuation to a new line, but only if we actually made one. + if (madeContinuation) { + nsLineBox* line = aState.NewLineBox(nextFrame, 1, PR_TRUE); + if (nsnull == line) { + return NS_ERROR_OUT_OF_MEMORY; + } + mLines.after_insert(aLine, line); + } + + // Advance to next line since some of the block fit. That way + // only the following lines will be pushed. + PushLines(aState, aLine); + aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; + // If we need to reflow the continuation of the block child, + // then we'd better reflow our continuation + if (frameReflowStatus & NS_FRAME_REFLOW_NEXTINFLOW) { + aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW; + // We also need to make that continuation's line dirty so it + // gets reflowed when we reflow our next in flow. The + // nif's line must always be either the first line + // of the nif's parent block or else one of our own overflow + // lines. In the latter case the line is already marked dirty, + // so just detect and handle the first case. + nsBlockFrame* nifBlock = NS_STATIC_CAST(nsBlockFrame*, nextFrame->GetParent()); + NS_ASSERTION(nifBlock->GetType() == nsLayoutAtoms::blockFrame + || nifBlock->GetType() == nsLayoutAtoms::areaFrame, + "A block's child's next in flow's parent must be a block!"); + line_iterator firstLine = nifBlock->begin_lines(); + if (firstLine != nifBlock->end_lines() && firstLine->Contains(nextFrame)) { + firstLine->MarkDirty(); + } + } + *aKeepReflowGoing = PR_FALSE; + + // The bottom margin for a block is only applied on the last + // flow block. Since we just continued the child block frame, + // we know that line->mFirstChild is not the last flow block + // therefore zero out the running margin value. +#ifdef NOISY_VERTICAL_MARGINS + ListTag(stdout); + printf(": reflow incomplete, frame="); + nsFrame::ListTag(stdout, frame); + printf(" prevBottomMargin=%d, setting to zero\n", + aState.mPrevBottomMargin); +#endif + aState.mPrevBottomMargin.Zero(); + } + else { +#ifdef NOISY_VERTICAL_MARGINS + ListTag(stdout); + printf(": reflow complete for "); + nsFrame::ListTag(stdout, frame); + printf(" prevBottomMargin=%d collapsedBottomMargin=%d\n", + aState.mPrevBottomMargin, collapsedBottomMargin.get()); +#endif + aState.mPrevBottomMargin = collapsedBottomMargin; + } +#ifdef NOISY_VERTICAL_MARGINS + ListTag(stdout); + printf(": frame="); + nsFrame::ListTag(stdout, frame); + printf(" carriedOutBottomMargin=%d collapsedBottomMargin=%d => %d\n", + brc.GetCarriedOutBottomMargin(), collapsedBottomMargin.get(), + aState.mPrevBottomMargin); +#endif + + // Post-process the "line" + nscoord maxElementWidth = 0; + if (aState.GetFlag(BRS_COMPUTEMAXELEMENTWIDTH)) { + maxElementWidth = brc.GetMaxElementWidth(); + } + // If we asked the block to update its maximum width, then record the + // updated value in the line, and update the current maximum width + if (aState.GetFlag(BRS_COMPUTEMAXWIDTH)) { + aLine->mMaximumWidth = brc.GetMaximumWidth(); + aState.UpdateMaximumWidth(aLine->mMaximumWidth); + } + PostPlaceLine(aState, aLine, maxElementWidth); + + // If the block frame that we just reflowed happens to be our + // first block, then its computed ascent is ours + if (frame == GetTopBlockChild(aState.mPresContext)) { + const nsHTMLReflowMetrics& metrics = brc.GetMetrics(); + mAscent = metrics.ascent; + } + + // Place the "marker" (bullet) frame. + // + // According to the CSS2 spec, section 12.6.1, the "marker" box + // participates in the height calculation of the list-item box's + // first line box. + // + // There are exactly two places a bullet can be placed: near the + // first or second line. Its only placed on the second line in a + // rare case: an empty first line followed by a second line that + // contains a block (example:

  • \n

    ... ). This is where + // the second case can happen. + if (mBullet && HaveOutsideBullet() && + ((aLine == mLines.front()) || + ((0 == mLines.front()->mBounds.height) && + (aLine == begin_lines().next())))) { + // Reflow the bullet + nsHTMLReflowMetrics metrics(nsnull); + ReflowBullet(aState, metrics); + + // Doing the alignment using |mAscent| will also cater for bullets + // that are placed next to a child block (bug 92896) + // (Note that mAscent should be set by now, otherwise why would + // we be placing the bullet yet?) + + // Tall bullets won't look particularly nice here... + nsRect bbox = mBullet->GetRect(); + nscoord bulletTopMargin = applyTopMargin + ? collapsedBottomMargin.get() + : 0; + bbox.y = aState.BorderPadding().top + mAscent - + metrics.ascent + bulletTopMargin; + mBullet->SetRect(bbox); + } } else { - // Push the line that didn't fit and any lines that follow it - // to our next-in-flow. - UndoSplitPlaceholders(aState, lastPlaceholder); - PushLines(aState, aLine.prev()); - aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; + // None of the block fits. Determine the correct reflow status. + if (aLine == mLines.front()) { + // If it's our very first line then we need to be pushed to + // our parents next-in-flow. Therefore, return break-before + // status for our reflow status. + aState.mReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); + } + else { + // Push the line that didn't fit and any lines that follow it + // to our next-in-flow. + UndoSplitPlaceholders(aState, lastPlaceholder); + PushLines(aState, aLine.prev()); + aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; + } } } + break; // out of the reflow retry loop } + #ifdef DEBUG VerifyLines(PR_TRUE); #endif @@ -3795,7 +3979,7 @@ nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState, // break-after-not-complete. There are two situations: we are a // block or we are an inline. This makes a total of 10 cases // (fortunately, there is some overlap). - aLine->SetBreakType(NS_STYLE_CLEAR_NONE); + aLine->SetBreakTypeAfter(NS_STYLE_CLEAR_NONE); if (NS_INLINE_IS_BREAK(frameReflowStatus) || (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType)) { // Always abort the line reflow (because a line break is the @@ -3838,7 +4022,8 @@ nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState, // the
    's break type with the inline's break type (the inline will be the very // next frame after the split float). if (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType) { - breakType = ::CombineBreakType(breakType, aState.mFloatBreakType); + breakType = nsLayoutUtils::CombineBreakType(breakType, + aState.mFloatBreakType); aState.mFloatBreakType = NS_STYLE_CLEAR_NONE; } // Break-after cases @@ -3847,7 +4032,7 @@ nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState, breakType = NS_STYLE_CLEAR_NONE; } } - aLine->SetBreakType(breakType); + aLine->SetBreakTypeAfter(breakType); if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) { // Create a continuation for the incomplete frame. Note that the // frame may already have a continuation. @@ -4335,8 +4520,8 @@ nsBlockFrame::PlaceLine(nsBlockReflowState& aState, // Apply break-after clearing if necessary // This must stay in sync with |ReflowDirtyLines|. - if (aLine->HasFloatBreak()) { - aState.ClearFloats(aState.mY, aLine->GetBreakType()); + if (aLine->HasFloatBreakAfter()) { + aState.mY = aState.ClearFloats(aState.mY, aLine->GetBreakTypeAfter()); } return PR_FALSE; @@ -5228,6 +5413,30 @@ nsBlockFrame::DeleteNextInFlowChild(nsPresContext* aPresContext, //////////////////////////////////////////////////////////////////////// // Float support +static void InitReflowStateForFloat(nsHTMLReflowState* aState, nsPresContext* aPresContext) +{ + /* We build a different reflow context based on the width attribute of the block + * when it's a float. + * Auto-width floats need to have their containing-block size set explicitly. + * factoring in other floats that impact it. + * It's possible this should be quirks-only. + */ + // XXXldb We should really fix this in nsHTMLReflowState::InitConstraints instead. + const nsStylePosition* position = aState->frame->GetStylePosition(); + nsStyleUnit widthUnit = position->mWidth.GetUnit(); + + if (eStyleUnit_Auto == widthUnit) { + // Initialize the reflow state and constrain the containing block's + // width and height to the available width and height. + aState->Init(aPresContext, aState->availableWidth, aState->availableHeight); + } else { + // Initialize the reflow state and use the containing block's + // computed width and height (or derive appropriate values for an + // absolutely positioned frame). + aState->Init(aPresContext); + } +} + nsresult nsBlockFrame::ReflowFloat(nsBlockReflowState& aState, nsPlaceholderFrame* aPlaceholder, @@ -5298,11 +5507,14 @@ nsBlockFrame::ReflowFloat(nsBlockReflowState& aState, availWidth, availHeight); // construct the html reflow state for the float. ReflowBlock will - // initialize it and set its reason. + // initialize it. nsHTMLReflowState floatRS(aState.mPresContext, aState.mReflowState, floatFrame, nsSize(availSpace.width, availSpace.height), aState.mReflowState.reason, PR_FALSE); + + InitReflowStateForFloat(&floatRS, aState.mPresContext); + // Setup a block reflow state to reflow the float. nsBlockReflowContext brc(aState.mPresContext, aState.mReflowState, computeMaxElementWidth, @@ -5311,10 +5523,28 @@ nsBlockFrame::ReflowFloat(nsBlockReflowState& aState, // Reflow the float PRBool isAdjacentWithTop = aState.IsAdjacentWithTop(); - nsCollapsingMargin margin; - nsresult rv = brc.ReflowBlock(availSpace, PR_TRUE, margin, isAdjacentWithTop, - aFloatCache->mOffsets, floatRS, - aReflowStatus); + nsIFrame* clearanceFrame = nsnull; + nsresult rv; + do { + nsCollapsingMargin margin; + PRBool mayNeedRetry = PR_FALSE; + nsBlockReflowContext::ComputeCollapsedTopMargin(floatRS, &margin, + clearanceFrame, &mayNeedRetry); + + if (mayNeedRetry && !clearanceFrame) { + floatRS.mDiscoveredClearance = &clearanceFrame; + // We don't need to push the space manager state because the the block has its own + // space manager that will be destroyed and recreated + } else { + floatRS.mDiscoveredClearance = nsnull; + } + + rv = brc.ReflowBlock(availSpace, PR_TRUE, margin, + 0, isAdjacentWithTop, + aFloatCache->mOffsets, floatRS, + aReflowStatus); + } while (NS_SUCCEEDED(rv) && clearanceFrame); + // An incomplete reflow status means we should split the float // if the height is constrained (bug 145305). if (NS_FRAME_IS_NOT_COMPLETE(aReflowStatus) && (NS_UNCONSTRAINEDSIZE == availHeight)) @@ -5341,14 +5571,34 @@ nsBlockFrame::ReflowFloat(nsBlockReflowState& aState, availSpace.width = maxElementWidth; nsCollapsingMargin marginMEW; // construct the html reflow state for the float. - // ReflowBlock will initialize it and set its reason. + // ReflowBlock will initialize it. nsHTMLReflowState redoFloatRS(aState.mPresContext, aState.mReflowState, floatFrame, nsSize(availSpace.width, availSpace.height), aState.mReflowState.reason, PR_FALSE); - rv = brc.ReflowBlock(availSpace, PR_TRUE, marginMEW, isAdjacentWithTop, - aFloatCache->mOffsets, redoFloatRS, - aReflowStatus); + + InitReflowStateForFloat(&redoFloatRS, aState.mPresContext); + + clearanceFrame = nsnull; + do { + nsCollapsingMargin marginMEW; + PRBool mayNeedRetry = PR_FALSE; + nsBlockReflowContext::ComputeCollapsedTopMargin(redoFloatRS, &marginMEW, clearanceFrame, &mayNeedRetry); + + if (mayNeedRetry && !clearanceFrame) { + redoFloatRS.mDiscoveredClearance = &clearanceFrame; + // We don't need to push the space manager state because the + // the block has its own space manager that will be + // destroyed and recreated + } else { + redoFloatRS.mDiscoveredClearance = nsnull; + } + + rv = brc.ReflowBlock(availSpace, PR_TRUE, marginMEW, + 0, isAdjacentWithTop, + aFloatCache->mOffsets, redoFloatRS, + aReflowStatus); + } while (NS_SUCCEEDED(rv) && clearanceFrame); } } @@ -5432,8 +5682,8 @@ nsBlockFrame::ReflowFloat(nsBlockReflowState& aState, if (mPrevInFlow) { // get the break type of the last line in mPrevInFlow line_iterator endLine = --((nsBlockFrame*)mPrevInFlow)->end_lines(); - if (endLine->HasFloatBreak()) { - aState.mFloatBreakType = endLine->GetBreakType(); + if (endLine->HasFloatBreakAfter()) { + aState.mFloatBreakType = endLine->GetBreakTypeAfter(); } } else NS_ASSERTION(PR_FALSE, "no prev in flow"); diff --git a/mozilla/layout/html/base/src/nsBlockFrame.h b/mozilla/layout/html/base/src/nsBlockFrame.h index fa83bf8cded..f84b60b4ed5 100644 --- a/mozilla/layout/html/base/src/nsBlockFrame.h +++ b/mozilla/layout/html/base/src/nsBlockFrame.h @@ -69,6 +69,11 @@ class nsIntervalSet; #define NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS 0x04000000 #define NS_BLOCK_HAS_OVERFLOW_PLACEHOLDERS 0x08000000 +// Set on any block that has descendant frames in the normal +// flow with 'clear' set to something other than 'none' +// (including
    frames) +#define NS_BLOCK_HAS_CLEAR_CHILDREN 0x10000000 + #define nsBlockFrameSuper nsHTMLContainerFrame #define NS_BLOCK_FRAME_CID \ @@ -224,6 +229,11 @@ public: virtual void DeleteNextInFlowChild(nsPresContext* aPresContext, nsIFrame* aNextInFlow); + // Determines whether the collapsed margin carried out of the last + // line includes the margin-top of a line with clearance (in which + // case we must avoid collapsing that margin with our bottom margin) + PRBool CheckForCollapsedBottomMarginFromClearanceLine(); + /** return the topmost block child based on y-index. * almost always the first or second line, if there is one. * accounts for lines that hold only compressed white space, etc. diff --git a/mozilla/layout/html/base/src/nsBlockReflowContext.cpp b/mozilla/layout/html/base/src/nsBlockReflowContext.cpp index 021b9a25b6a..dc7f828f3c4 100644 --- a/mozilla/layout/html/base/src/nsBlockReflowContext.cpp +++ b/mozilla/layout/html/base/src/nsBlockReflowContext.cpp @@ -52,6 +52,7 @@ #include "nsIDOMHTMLBodyElement.h" #include "nsLayoutAtoms.h" #include "nsCOMPtr.h" +#include "nsLayoutUtils.h" #ifdef NS_DEBUG #undef NOISY_MAX_ELEMENT_SIZE @@ -79,13 +80,12 @@ nsBlockReflowContext::nsBlockReflowContext(nsPresContext* aPresContext, mMetrics.mFlags |= NS_REFLOW_CALC_MAX_WIDTH; } -void -nsBlockReflowContext::ComputeCollapsedTopMargin(nsPresContext* aPresContext, - nsHTMLReflowState& aRS, - /* inout */ nsCollapsingMargin& aMargin) +PRBool +nsBlockReflowContext::ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS, + nsCollapsingMargin* aMargin, nsIFrame* aClearanceFrame, PRBool* aMayNeedRetry) { - // Get aFrame's top margin - aMargin.Include(aRS.mComputedMargin.top); + // Include frame's top margin + aMargin->Include(aRS.mComputedMargin.top); // The inclusion of the bottom margin when empty is done by the caller // since it doesn't need to be done by the top-level (non-recursive) @@ -93,48 +93,77 @@ nsBlockReflowContext::ComputeCollapsedTopMargin(nsPresContext* aPresContext, #ifdef NOISY_VERTICAL_MARGINS nsFrame::ListTag(stdout, aRS.frame); - printf(": %d => %d\n", aRS.mComputedMargin.top, aMargin.get()); + printf(": %d => %d\n", aRS.mComputedMargin.top, aMargin->get()); #endif - // Calculate aFrame's generational top-margin from its child - // blocks. Note that if aFrame has a non-zero top-border or + PRBool dirtiedLine = PR_FALSE; + + // Calculate the frame's generational top-margin from its child + // blocks. Note that if the frame has a non-zero top-border or // top-padding then this step is skipped because it will be a margin // root. It is also skipped if the frame is a margin root for other // reasons. + void* bf; if (0 == aRS.mComputedBorderPadding.top && - !(aRS.frame->GetStateBits() & NS_BLOCK_MARGIN_ROOT)) { - nsBlockFrame* bf; - if (NS_SUCCEEDED(aRS.frame->QueryInterface(kBlockFrameCID, - NS_REINTERPRET_CAST(void**, &bf)))) { - for (nsBlockFrame::line_iterator line = bf->begin_lines(), - line_end = bf->end_lines(); - line != line_end; ++line) { - PRBool isEmpty = line->IsEmpty(); - if (line->IsBlock()) { - // Here is where we recur. Now that we have determined that a - // generational collapse is required we need to compute the - // child blocks margin and so in so that we can look into - // it. For its margins to be computed we need to have a reflow - // state for it. Since the reflow reason is irrelevant, we'll - // arbitrarily make it a `resize' to avoid the path-plucking - // behavior if we're in an incremental reflow. - nsSize availSpace(aRS.mComputedWidth, aRS.mComputedHeight); - nsHTMLReflowState reflowState(aPresContext, aRS, line->mFirstChild, - availSpace, eReflowReason_Resize); - ComputeCollapsedTopMargin(aPresContext, reflowState, aMargin); - if (isEmpty) - aMargin.Include(reflowState.mComputedMargin.bottom); - } - if (!isEmpty) - break; + !(aRS.frame->GetStateBits() & NS_BLOCK_MARGIN_ROOT) && + NS_SUCCEEDED(aRS.frame->QueryInterface(kBlockFrameCID, &bf))) { + nsBlockFrame* block = NS_STATIC_CAST(nsBlockFrame*, aRS.frame); + + for (nsBlockFrame::line_iterator line = block->begin_lines(), + line_end = block->end_lines(); + line != line_end; ++line) { + if (!aClearanceFrame && line->HasClearance()) { + // If we don't have a clearance frame, then we're computing + // the collapsed margin in the first pass, assuming that all + // lines have no clearance. So clear their clearance flags. + line->ClearHasClearance(); + line->MarkDirty(); + dirtiedLine = PR_TRUE; } + + PRBool isEmpty = line->IsEmpty(); + if (line->IsBlock()) { + nsBlockFrame* kidBlock = NS_STATIC_CAST(nsBlockFrame*, line->mFirstChild); + if (kidBlock == aClearanceFrame) { + line->SetHasClearance(); + line->MarkDirty(); + dirtiedLine = PR_TRUE; + break; + } + // Here is where we recur. Now that we have determined that a + // generational collapse is required we need to compute the + // child blocks margin and so in so that we can look into + // it. For its margins to be computed we need to have a reflow + // state for it. Since the reflow reason is irrelevant, we'll + // arbitrarily make it a `resize' to avoid the path-plucking + // behavior if we're in an incremental reflow. + nsSize availSpace(aRS.mComputedWidth, aRS.mComputedHeight); + nsHTMLReflowState reflowState(kidBlock->GetPresContext(), + aRS, kidBlock, + availSpace, eReflowReason_Resize); + // Record that we're being optimistic by assuming the kid + // has no clearance + if (kidBlock->GetStyleDisplay()->mBreakType != NS_STYLE_CLEAR_NONE) { + *aMayNeedRetry = PR_TRUE; + } + if (ComputeCollapsedTopMargin(reflowState, aMargin, aClearanceFrame, aMayNeedRetry)) { + line->MarkDirty(); + dirtiedLine = PR_TRUE; + } + if (isEmpty) + aMargin->Include(reflowState.mComputedMargin.bottom); + } + if (!isEmpty) + break; } } - + #ifdef NOISY_VERTICAL_MARGINS nsFrame::ListTag(stdout, aRS.frame); printf(": => %d\n", aMargin.get()); #endif + + return dirtiedLine; } struct nsBlockHorizontalAlign { @@ -302,7 +331,8 @@ nsPointDtor(void *aFrame, nsIAtom *aPropertyName, nsresult nsBlockReflowContext::ReflowBlock(const nsRect& aSpace, PRBool aApplyTopMargin, - nsCollapsingMargin& aPrevBottomMargin, + nsCollapsingMargin& aPrevMargin, + nscoord aClearance, PRBool aIsAdjacentWithTop, nsMargin& aComputedOffsets, nsHTMLReflowState& aFrameRS, @@ -354,32 +384,8 @@ nsBlockReflowContext::ReflowBlock(const nsRect& aSpace, aFrameRS.reason = eReflowReason_Dirty; } - /* We build a different reflow context based on the width attribute of the block, - * if it's a float. - * Auto-width floats need to have their containing-block size set explicitly, - * factoring in other floats that impact it. - * It's possible this should be quirks-only. - * All other blocks proceed normally. - */ - // XXXldb We should really fix this in nsHTMLReflowState::InitConstraints instead. - const nsStylePosition* position = mFrame->GetStylePosition(); - nsStyleUnit widthUnit = position->mWidth.GetUnit(); const nsStyleDisplay* display = mFrame->GetStyleDisplay(); - if ((eStyleUnit_Auto == widthUnit) && - ((NS_STYLE_FLOAT_LEFT == display->mFloats) || - (NS_STYLE_FLOAT_RIGHT == display->mFloats))) { - // Initialize the reflow state and constrain the containing block's - // width and height to the available width and height. - aFrameRS.Init(mPresContext, mSpace.width, mSpace.height); - } - else { - // Initialize the reflow state and use the containing block's computed - // width and height (or derive appropriate values for an - // absolutely positioned frame). - aFrameRS.Init(mPresContext); - } - aComputedOffsets = aFrameRS.mComputedOffsets; if (NS_STYLE_POSITION_RELATIVE == display->mPosition) { nsPropertyTable *propTable = mPresContext->PropertyTable(); @@ -404,23 +410,20 @@ nsBlockReflowContext::ReflowBlock(const nsRect& aSpace, mComputedWidth = aFrameRS.mComputedWidth; if (aApplyTopMargin) { - // Compute the childs collapsed top margin (its margin collpased - // with its first childs top-margin -- recursively). - ComputeCollapsedTopMargin(mPresContext, aFrameRS, aPrevBottomMargin); + mTopMargin = aPrevMargin; #ifdef NOISY_VERTICAL_MARGINS nsFrame::ListTag(stdout, mOuterReflowState.frame); printf(": reflowing "); nsFrame::ListTag(stdout, mFrame); - printf(" margin => %d\n", aPrevBottomMargin.get()); + printf(" margin => %d, clearance => %d\n", mTopMargin.get(), aClearance); #endif // Adjust the available height if its constrained so that the // child frame doesn't think it can reflow into its margin area. if (NS_UNCONSTRAINEDSIZE != aFrameRS.availableHeight) { - aFrameRS.availableHeight -= aPrevBottomMargin.get(); + aFrameRS.availableHeight -= mTopMargin.get() + aClearance; } - mTopMargin = aPrevBottomMargin; } // Compute x/y coordinate where reflow will begin. Use the rules @@ -431,7 +434,7 @@ nsBlockReflowContext::ReflowBlock(const nsRect& aSpace, mStyleMargin = aFrameRS.mStyleMargin; mStylePadding = aFrameRS.mStylePadding; nscoord x; - nscoord y = mSpace.y + mTopMargin.get(); + nscoord y = mSpace.y + mTopMargin.get() + aClearance; // If it's a right floated element, then calculate the x-offset // differently @@ -647,6 +650,7 @@ nsBlockReflowContext::ReflowBlock(const nsRect& aSpace, PRBool nsBlockReflowContext::PlaceBlock(const nsHTMLReflowState& aReflowState, PRBool aForceFit, + nsLineBox* aLine, const nsMargin& aComputedOffsets, nsCollapsingMargin& aBottomMarginResult, nsRect& aInFlowBounds, @@ -660,15 +664,13 @@ nsBlockReflowContext::PlaceBlock(const nsHTMLReflowState& aReflowState, PRBool fits = PR_TRUE; nscoord x = mX; nscoord y = mY; - // When deciding whether it's empty we also need to take into - // account the overflow area - // XXXldb What should really matter is whether there exist non- - // empty frames in the block (with appropriate whitespace munging). - // Consider the case where we clip off the overflow with - // 'overflow: -moz-hidden-unscrollable' (which doesn't currently - // affect mOverflowArea, but probably should. - if ((0 == mMetrics.height) && (0 == mMetrics.mOverflowArea.height)) + // Check whether the block's bottom margin collapses with its top + // margin. See CSS 2.1 section 8.3.1; those rules seem to match + // nsBlockFrame::IsEmpty(). Any such block must have zero height so + // check that first. + if (0 == mMetrics.height && !aLine->HasClearance() && + aLine->CachedIsEmpty()) { // Collapse the bottom margin with the top margin that was already // applied. diff --git a/mozilla/layout/html/base/src/nsBlockReflowContext.h b/mozilla/layout/html/base/src/nsBlockReflowContext.h index 6868a664510..a6aaaec7536 100644 --- a/mozilla/layout/html/base/src/nsBlockReflowContext.h +++ b/mozilla/layout/html/base/src/nsBlockReflowContext.h @@ -39,10 +39,12 @@ #define nsBlockReflowContext_h___ #include "nsIFrame.h" -#include "nsHTMLReflowState.h" #include "nsHTMLReflowMetrics.h" class nsBlockFrame; +class nsBlockReflowState; +class nsHTMLReflowState; +class nsLineBox; class nsIFrame; class nsPresContext; class nsLineLayout; @@ -62,7 +64,8 @@ public: nsresult ReflowBlock(const nsRect& aSpace, PRBool aApplyTopMargin, - nsCollapsingMargin& aPrevBottomMargin, + nsCollapsingMargin& aPrevMargin, + nscoord aClearance, PRBool aIsAdjacentWithTop, nsMargin& aComputedOffsets, nsHTMLReflowState& aReflowState, @@ -70,6 +73,7 @@ public: PRBool PlaceBlock(const nsHTMLReflowState& aReflowState, PRBool aForceFit, + nsLineBox* aLine, const nsMargin& aComputedOffsets, nsCollapsingMargin& aBottomMarginResult /* out */, nsRect& aInFlowBounds, @@ -101,9 +105,23 @@ public: return mMetrics.mMaximumWidth; } - static void ComputeCollapsedTopMargin(nsPresContext* aPresContext, - nsHTMLReflowState& aRS, - /* inout */ nsCollapsingMargin& aMargin); + /** + * Computes the collapsed top margin for a block whose reflow state is in aRS. + * The computed margin is added into aMargin. + * If aClearanceFrame is null then this is the first optimistic pass which shall assume + * that no frames have clearance, and we clear the HasClearance on all frames encountered. + * If non-null, this is the second pass and + * the caller has decided aClearanceFrame needs clearance (and we will + * therefore stop collapsing there); also, this function is responsible for marking + * it with SetHasClearance. + * If in the optimistic pass any frame is encountered that might possibly need + * clearance (i.e., if we really needed the optimism assumption) then we set aMayNeedRetry + * to true. + * We return PR_TRUE if we changed the clearance state of any line and marked it dirty. + */ + static PRBool ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS, + nsCollapsingMargin* aMargin, nsIFrame* aClearanceFrame, + PRBool* aMayNeedRetry); protected: nsPresContext* mPresContext; diff --git a/mozilla/layout/html/base/src/nsBlockReflowState.cpp b/mozilla/layout/html/base/src/nsBlockReflowState.cpp index 9554d37d99e..d8a1323dbab 100644 --- a/mozilla/layout/html/base/src/nsBlockReflowState.cpp +++ b/mozilla/layout/html/base/src/nsBlockReflowState.cpp @@ -60,7 +60,8 @@ nsBlockReflowState::nsBlockReflowState(const nsHTMLReflowState& aReflowState, nsPresContext* aPresContext, nsBlockFrame* aFrame, const nsHTMLReflowMetrics& aMetrics, - PRBool aBlockMarginRoot) + PRBool aTopMarginRoot, + PRBool aBottomMarginRoot) : mBlock(aFrame), mPresContext(aPresContext), mReflowState(aReflowState), @@ -71,14 +72,10 @@ nsBlockReflowState::nsBlockReflowState(const nsHTMLReflowState& aReflowState, { const nsMargin& borderPadding = BorderPadding(); - if (aBlockMarginRoot) { - SetFlag(BRS_ISTOPMARGINROOT, PR_TRUE); - SetFlag(BRS_ISBOTTOMMARGINROOT, PR_TRUE); - } - if (0 != aReflowState.mComputedBorderPadding.top) { + if (aTopMarginRoot || 0 != aReflowState.mComputedBorderPadding.top) { SetFlag(BRS_ISTOPMARGINROOT, PR_TRUE); } - if (0 != aReflowState.mComputedBorderPadding.bottom) { + if (aBottomMarginRoot || 0 != aReflowState.mComputedBorderPadding.bottom) { SetFlag(BRS_ISBOTTOMMARGINROOT, PR_TRUE); } if (GetFlag(BRS_ISTOPMARGINROOT)) { @@ -338,68 +335,6 @@ nsBlockReflowState::GetAvailableSpace(nscoord aY) #endif } -PRBool -nsBlockReflowState::ClearPastFloats(PRUint8 aBreakType) -{ - nscoord saveY, deltaY; - - PRBool applyTopMargin = PR_FALSE; - switch (aBreakType) { - case NS_STYLE_CLEAR_LEFT: - case NS_STYLE_CLEAR_RIGHT: - case NS_STYLE_CLEAR_LEFT_AND_RIGHT: - // Apply the previous margin before clearing - saveY = mY + mPrevBottomMargin.get(); - ClearFloats(saveY, aBreakType); -#ifdef NOISY_FLOAT_CLEARING - nsFrame::ListTag(stdout, mBlock); - printf(": ClearPastFloats: mPrevBottomMargin=%d saveY=%d oldY=%d newY=%d deltaY=%d\n", - mPrevBottomMargin.get(), saveY, saveY - mPrevBottomMargin.get(), mY, - mY - saveY); -#endif - - // Determine how far we just moved. If we didn't move then there - // was nothing to clear to don't mess with the normal margin - // collapsing behavior. In either case we need to restore the Y - // coordinate to what it was before the clear. - deltaY = mY - saveY; - if (0 != deltaY) { - // Pretend that the distance we just moved is a previous - // blocks bottom margin. Note that GetAvailableSpace has been - // done so that the available space calculations will be done - // after clearing the appropriate floats. - // - // What we are doing here is applying CSS2 section 9.5.2's - // rules for clearing - "The top margin of the generated box - // is increased enough that the top border edge is below the - // bottom outer edge of the floating boxes..." - // - // What this will do is cause the top-margin of the block - // frame we are about to reflow to be collapsed with that - // distance. - - // XXXldb This doesn't handle collapsing with negative margins - // correctly, although it's arguable what "correct" is. - - // XXX Are all the other margins included by this point? - mPrevBottomMargin.Zero(); - mPrevBottomMargin.Include(deltaY); - mY = saveY; - - // Force margin to be applied in this circumstance - applyTopMargin = PR_TRUE; - } - else { - // Put mY back to its original value since no clearing - // happened. That way the previous blocks bottom margin will - // be applied properly. - mY = saveY - mPrevBottomMargin.get(); - } - break; - } - return applyTopMargin; -} - /* * Reconstruct the vertical margin before the line |aLine| in order to * do an incremental reflow that begins with |aLine| without reflowing @@ -851,12 +786,10 @@ nsBlockReflowState::FlowAndPlaceFloat(nsFloatCache* aFloatCache, // See if the float should clear any preceeding floats... if (NS_STYLE_CLEAR_NONE != floatDisplay->mBreakType) { // XXXldb Does this handle vertical margins correctly? - ClearFloats(mY, floatDisplay->mBreakType); + mY = ClearFloats(mY, floatDisplay->mBreakType); } - else { // Get the band of available space GetAvailableSpace(); - } NS_ASSERTION(floatFrame->GetParent() == mBlock, "Float frame has wrong parent"); @@ -1154,14 +1087,14 @@ nsBlockReflowState::PlaceBelowCurrentLineFloats(nsFloatCacheList& aList) return PR_TRUE; } -void +nscoord nsBlockReflowState::ClearFloats(nscoord aY, PRUint8 aBreakType) { #ifdef DEBUG if (nsBlockFrame::gNoisyReflow) { nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent); - printf("clear floats: in: mY=%d aY=%d(%d)\n", - mY, aY, aY - BorderPadding().top); + printf("clear floats: in: aY=%d(%d)\n", + aY, aY - BorderPadding().top); } #endif @@ -1173,14 +1106,15 @@ nsBlockReflowState::ClearFloats(nscoord aY, PRUint8 aBreakType) const nsMargin& bp = BorderPadding(); nscoord newY = mSpaceManager->ClearFloats(aY - bp.top, aBreakType); - mY = newY + bp.top; - GetAvailableSpace(); + newY += bp.top; #ifdef DEBUG if (nsBlockFrame::gNoisyReflow) { nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent); - printf("clear floats: out: mY=%d(%d)\n", mY, mY - bp.top); + printf("clear floats: out: y=%d(%d)\n", newY, newY - bp.top); } #endif + + return newY; } diff --git a/mozilla/layout/html/base/src/nsBlockReflowState.h b/mozilla/layout/html/base/src/nsBlockReflowState.h index 97939580a78..f5c35b5b4b9 100644 --- a/mozilla/layout/html/base/src/nsBlockReflowState.h +++ b/mozilla/layout/html/base/src/nsBlockReflowState.h @@ -53,7 +53,7 @@ public: nsPresContext* aPresContext, nsBlockFrame* aFrame, const nsHTMLReflowMetrics& aMetrics, - PRBool aBlockMarginRoot); + PRBool aTopMarginRoot, PRBool aBottomMarginRoot); ~nsBlockReflowState(); @@ -85,12 +85,9 @@ public: PRBool PlaceBelowCurrentLineFloats(nsFloatCacheList& aFloats); - // called when clearing a line with a break type caused by a BR past - // floats, and also used internally by ClearPastFloats - void ClearFloats(nscoord aY, PRUint8 aBreakType); - - // called when clearing a block past floats - PRBool ClearPastFloats(PRUint8 aBreakType); + // Returns the first coordinate >= aY that clears the + // indicated floats. + nscoord ClearFloats(nscoord aY, PRUint8 aBreakType); PRBool IsAdjacentWithTop() const { return mY == mReflowState.mComputedBorderPadding.top; diff --git a/mozilla/layout/html/base/src/nsHTMLReflowState.cpp b/mozilla/layout/html/base/src/nsHTMLReflowState.cpp index cf63950398f..c655f0932f6 100644 --- a/mozilla/layout/html/base/src/nsHTMLReflowState.cpp +++ b/mozilla/layout/html/base/src/nsHTMLReflowState.cpp @@ -111,6 +111,8 @@ nsHTMLReflowState::nsHTMLReflowState(nsPresContext* aPresContext, mFlags.mSpecialHeightReflow = PR_FALSE; mFlags.mIsTopOfPage = PR_FALSE; mFlags.mNextInFlowUntouched = PR_FALSE; + mFlags.mHasClearance = PR_FALSE; + mDiscoveredClearance = nsnull; mPercentHeightObserver = nsnull; mPercentHeightReflowInitiator = nsnull; Init(aPresContext); @@ -143,6 +145,8 @@ nsHTMLReflowState::nsHTMLReflowState(nsPresContext* aPresContext, mFlags.mSpecialHeightReflow = PR_FALSE; mFlags.mIsTopOfPage = PR_FALSE; mFlags.mNextInFlowUntouched = PR_FALSE; + mFlags.mHasClearance = PR_FALSE; + mDiscoveredClearance = nsnull; mPercentHeightObserver = nsnull; mPercentHeightReflowInitiator = nsnull; Init(aPresContext); @@ -193,6 +197,8 @@ nsHTMLReflowState::nsHTMLReflowState(nsPresContext* aPresContext, mFlags.mIsTopOfPage = aParentReflowState.mFlags.mIsTopOfPage; mFlags.mNextInFlowUntouched = aParentReflowState.mFlags.mNextInFlowUntouched && CheckNextInFlowParenthood(aFrame, aParentReflowState.frame); + mFlags.mHasClearance = PR_FALSE; + mDiscoveredClearance = nsnull; mPercentHeightObserver = (aParentReflowState.mPercentHeightObserver && aParentReflowState.mPercentHeightObserver->NeedsToObserve(*this)) ? aParentReflowState.mPercentHeightObserver : nsnull; @@ -240,6 +246,8 @@ nsHTMLReflowState::nsHTMLReflowState(nsPresContext* aPresContext, mFlags.mIsTopOfPage = aParentReflowState.mFlags.mIsTopOfPage; mFlags.mNextInFlowUntouched = aParentReflowState.mFlags.mNextInFlowUntouched && CheckNextInFlowParenthood(aFrame, aParentReflowState.frame); + mFlags.mHasClearance = PR_FALSE; + mDiscoveredClearance = nsnull; mPercentHeightObserver = (aParentReflowState.mPercentHeightObserver && aParentReflowState.mPercentHeightObserver->NeedsToObserve(*this)) ? aParentReflowState.mPercentHeightObserver : nsnull; @@ -287,6 +295,8 @@ nsHTMLReflowState::nsHTMLReflowState(nsPresContext* aPresContext, mFlags.mIsTopOfPage = aParentReflowState.mFlags.mIsTopOfPage; mFlags.mNextInFlowUntouched = aParentReflowState.mFlags.mNextInFlowUntouched && CheckNextInFlowParenthood(aFrame, aParentReflowState.frame); + mFlags.mHasClearance = PR_FALSE; + mDiscoveredClearance = nsnull; mPercentHeightObserver = (aParentReflowState.mPercentHeightObserver && aParentReflowState.mPercentHeightObserver->NeedsToObserve(*this)) ? aParentReflowState.mPercentHeightObserver : nsnull; diff --git a/mozilla/layout/html/base/src/nsLineBox.cpp b/mozilla/layout/html/base/src/nsLineBox.cpp index 7959bf7e09b..b0f78795bd6 100644 --- a/mozilla/layout/html/base/src/nsLineBox.cpp +++ b/mozilla/layout/html/base/src/nsLineBox.cpp @@ -181,13 +181,14 @@ BreakTypeToString(PRUint8 aBreakType) char* nsLineBox::StateToString(char* aBuf, PRInt32 aBufSize) const { - PR_snprintf(aBuf, aBufSize, "%s,%s,%s,%s,%s,%s[0x%x]", + PR_snprintf(aBuf, aBufSize, "%s,%s,%s,%s,%s,before:%s,after:%s[0x%x]", IsBlock() ? "block" : "inline", IsDirty() ? "dirty" : "clean", IsPreviousMarginDirty() ? "prevmargindirty" : "prevmarginclean", IsImpactedByFloat() ? "impacted" : "not impacted", IsLineWrapped() ? "wrapped" : "not wrapped", - BreakTypeToString(GetBreakType()), + BreakTypeToString(GetBreakTypeBefore()), + BreakTypeToString(GetBreakTypeAfter()), mAllFlags); return aBuf; } @@ -604,7 +605,7 @@ nsLineIterator::GetLine(PRInt32 aLineNumber, flags |= NS_LINE_FLAG_IS_BLOCK; } else { - if (line->HasBreak()) + if (line->HasBreakAfter()) flags |= NS_LINE_FLAG_ENDS_IN_BREAK; } *aLineFlags = flags; diff --git a/mozilla/layout/html/base/src/nsLineBox.h b/mozilla/layout/html/base/src/nsLineBox.h index 9f79292ebd1..dc2eee87e84 100644 --- a/mozilla/layout/html/base/src/nsLineBox.h +++ b/mozilla/layout/html/base/src/nsLineBox.h @@ -246,6 +246,17 @@ public: return mFlags.mPreviousMarginDirty; } + // mHasClearance bit + void SetHasClearance() { + mFlags.mHasClearance = 1; + } + void ClearHasClearance() { + mFlags.mHasClearance = 0; + } + PRBool HasClearance() const { + return mFlags.mHasClearance; + } + // mImpactedByFloat bit void SetLineIsImpactedByFloat(PRBool aValue) { NS_ASSERTION((PR_FALSE==aValue || PR_TRUE==aValue), "somebody is playing fast and loose with bools and bits!"); @@ -300,20 +311,37 @@ public: } // mBreakType value - PRBool HasBreak() const { - return NS_STYLE_CLEAR_NONE != mFlags.mBreakType; + // Break information is applied *before* the line if the line is a block, + // or *after* the line if the line is an inline. Confusing, I know, but + // using different names should help. + PRBool HasBreakBefore() const { + return IsBlock() && NS_STYLE_CLEAR_NONE != mFlags.mBreakType; } - PRBool HasFloatBreak() const { - return NS_STYLE_CLEAR_LEFT == mFlags.mBreakType || - NS_STYLE_CLEAR_RIGHT == mFlags.mBreakType || - NS_STYLE_CLEAR_LEFT_AND_RIGHT == mFlags.mBreakType; - } - void SetBreakType(PRUint8 aBreakType) { - NS_WARN_IF_FALSE(aBreakType <= LINE_MAX_BREAK_TYPE, "bad break type"); + void SetBreakTypeBefore(PRUint8 aBreakType) { + NS_ASSERTION(IsBlock(), "Only blocks have break-before"); + NS_ASSERTION(aBreakType <= NS_STYLE_CLEAR_LEFT_AND_RIGHT, + "Only float break types are allowed before a line"); mFlags.mBreakType = aBreakType; } - PRUint8 GetBreakType() const { - return mFlags.mBreakType; + PRUint8 GetBreakTypeBefore() const { + return IsBlock() ? mFlags.mBreakType : NS_STYLE_CLEAR_NONE; + } + + PRBool HasBreakAfter() const { + return !IsBlock() && NS_STYLE_CLEAR_NONE != mFlags.mBreakType; + } + void SetBreakTypeAfter(PRUint8 aBreakType) { + NS_ASSERTION(!IsBlock(), "Only inlines have break-after"); + NS_ASSERTION(aBreakType <= LINE_MAX_BREAK_TYPE, "bad break type"); + mFlags.mBreakType = aBreakType; + } + PRBool HasFloatBreakAfter() const { + return !IsBlock() && (NS_STYLE_CLEAR_LEFT == mFlags.mBreakType || + NS_STYLE_CLEAR_RIGHT == mFlags.mBreakType || + NS_STYLE_CLEAR_LEFT_AND_RIGHT == mFlags.mBreakType); + } + PRUint8 GetBreakTypeAfter() const { + return !IsBlock() ? mFlags.mBreakType : NS_STYLE_CLEAR_NONE; } // mCarriedOutBottomMargin value @@ -427,6 +455,7 @@ public: struct FlagBits { PRUint32 mDirty : 1; PRUint32 mPreviousMarginDirty : 1; + PRUint32 mHasClearance : 1; PRUint32 mBlock : 1; PRUint32 mImpactedByFloat : 1; PRUint32 mHasPercentageChild : 1; @@ -436,7 +465,7 @@ public: PRUint32 mEmptyCacheState: 1; PRUint32 mBreakType : 4; - PRUint32 mChildCount : 19; + PRUint32 mChildCount : 18; }; struct ExtraData {