/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is Mozilla Communicator client code. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are Copyright (C) 1998 * Netscape Communications Corporation. All Rights Reserved. */ #include "nsCOMPtr.h" #include "nsBlockFrame.h" #include "nsBlockReflowContext.h" #include "nsBlockBandData.h" #include "nsBulletFrame.h" #include "nsLineBox.h" #include "nsInlineFrame.h" #include "nsLineLayout.h" #include "nsPlaceholderFrame.h" #include "nsStyleConsts.h" #include "nsHTMLIIDs.h" #include "nsCSSRendering.h" #include "nsIPresContext.h" #include "nsIPresShell.h" #include "nsIReflowCommand.h" #include "nsISpaceManager.h" #include "nsIStyleContext.h" #include "nsIView.h" #include "nsIFontMetrics.h" #include "nsHTMLParts.h" #include "nsHTMLAtoms.h" #include "nsHTMLValue.h" #include "nsDOMEvent.h" #include "nsIHTMLContent.h" #include "prprf.h" #include "nsLayoutAtoms.h" #include "nsITextContent.h" #include "nsStyleChangeList.h" #include "nsISizeOfHandler.h" #include "nsIFocusTracker.h" #include "nsIFrameSelection.h" #include "nsIAreaFrame.h" #define MAX_LINE_COUNT 50000 // XXX HTML:P's that are empty yet have style indicating they should // clear floaters - we need to ignore the clear behavior. #ifdef NS_DEBUG #undef NOISY_FIRST_LINE #undef REALLY_NOISY_FIRST_LINE #undef NOISY_FIRST_LETTER #undef NOISY_MAX_ELEMENT_SIZE #undef NOISY_FLOATER_CLEARING #undef NOISY_INCREMENTAL_REFLOW #undef NOISY_FINAL_SIZE #undef NOISY_REMOVE_FRAME #undef NOISY_DAMAGE_REPAIR #undef NOISY_COMBINED_AREA #undef NOISY_VERTICAL_MARGINS #undef NOISY_REFLOW_REASON #undef REFLOW_STATUS_COVERAGE #undef NOISY_SPACEMANAGER #else #undef NOISY_FIRST_LINE #undef REALLY_NOISY_FIRST_LINE #undef NOISY_FIRST_LETTER #undef NOISY_MAX_ELEMENT_SIZE #undef NOISY_FLOATER_CLEARING #undef NOISY_INCREMENTAL_REFLOW #undef NOISY_FINAL_SIZE #undef NOISY_REMOVE_FRAME #undef NOISY_DAMAGE_REPAIR #undef NOISY_COMBINED_AREA #undef NOISY_VERTICAL_MARGINS #undef NOISY_REFLOW_REASON #undef REFLOW_STATUS_COVERAGE #undef NOISY_SPACEMANAGER #endif //---------------------------------------------------------------------- // Debugging support code #ifdef DEBUG static PRBool gShowDirtyLines = PR_FALSE; #endif #ifdef NOISY_INCREMENTAL_REFLOW static PRInt32 gNoiseIndent; static const char* kReflowCommandType[] = { "ContentChanged", "StyleChanged", "PullupReflow", "PushReflow", "CheckPullupReflow", "ReflowDirty", "UserDefined", }; #endif #ifdef REALLY_NOISY_FIRST_LINE static void DumpStyleGeneaology(nsIFrame* aFrame, const char* gap) { fputs(gap, stdout); nsFrame::ListTag(stdout, aFrame); printf(": "); nsIStyleContext* sc; aFrame->GetStyleContext(&sc); while (nsnull != sc) { nsIStyleContext* psc; printf("%p ", sc); psc = sc->GetParent(); NS_RELEASE(sc); sc = psc; } printf("\n"); } #endif #ifdef REFLOW_STATUS_COVERAGE static void RecordReflowStatus(PRBool aChildIsBlock, nsReflowStatus aFrameReflowStatus) { static PRUint32 record[2]; // 0: child-is-block // 1: child-is-inline PRIntn index = 0; if (!aChildIsBlock) index |= 1; // Compute new status PRUint32 newS = record[index]; if (NS_INLINE_IS_BREAK(aFrameReflowStatus)) { if (NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus)) { newS |= 1; } else if (NS_FRAME_IS_NOT_COMPLETE(aFrameReflowStatus)) { newS |= 2; } else { newS |= 4; } } else if (NS_FRAME_IS_NOT_COMPLETE(aFrameReflowStatus)) { newS |= 8; } else { newS |= 16; } // Log updates to the status that yield different values if (record[index] != newS) { record[index] = newS; printf("record(%d): %02x %02x\n", index, record[0], record[1]); } } #endif //---------------------------------------------------------------------- inline void CombineRects(const nsRect& r1, nsRect& r2) { nscoord xa = r2.x; nscoord ya = r2.y; nscoord xb = xa + r2.width; nscoord yb = ya + r2.height; nscoord x = r1.x; nscoord y = r1.y; nscoord xmost = x + r1.width; nscoord ymost = y + r1.height; if (x < xa) { xa = x; } if (xmost > xb) { xb = xmost; } if (y < ya) { ya = y; } if (ymost > yb) { yb = ymost; } r2.x = xa; r2.y = ya; r2.width = xb - xa; r2.height = yb - ya; } //---------------------------------------------------------------------- class nsBlockReflowState { public: nsBlockReflowState(const nsHTMLReflowState& aReflowState, nsIPresContext* aPresContext, nsBlockFrame* aFrame, const nsHTMLReflowMetrics& aMetrics); ~nsBlockReflowState(); /** * Update our state when aLine is skipped over during incremental * reflow. */ void RecoverStateFrom(nsLineBox* aLine, PRBool aPrevLineWasClean); /** * Get the available reflow space for the current y coordinate. The * available space is relative to our coordinate system (0,0) is our * upper left corner. */ void GetAvailableSpace() { #ifdef DEBUG // Verify that the caller setup the coordinate system properly nscoord wx, wy; mSpaceManager->GetTranslation(wx, wy); NS_ASSERTION((wx == mSpaceManagerX) && (wy == mSpaceManagerY), "bad coord system"); #endif mBand.GetAvailableSpace(mY - BorderPadding().top, mAvailSpaceRect); #ifdef NOISY_INCREMENTAL_REFLOW nsFrame::IndentBy(stdout, gNoiseIndent); printf("GetAvailableSpace: band=%d,%d,%d,%d count=%d\n", mAvailSpaceRect.x, mAvailSpaceRect.y, mAvailSpaceRect.width, mAvailSpaceRect.height, mBand.GetTrapezoidCount()); #endif } void GetAvailableSpace(nscoord aY) { #ifdef DEBUG // Verify that the caller setup the coordinate system properly nscoord wx, wy; mSpaceManager->GetTranslation(wx, wy); NS_ASSERTION((wx == mSpaceManagerX) && (wy == mSpaceManagerY), "bad coord system"); #endif mBand.GetAvailableSpace(aY - BorderPadding().top, mAvailSpaceRect); #ifdef NOISY_INCREMENTAL_REFLOW nsFrame::IndentBy(stdout, gNoiseIndent); printf("GetAvailableSpace: band=%d,%d,%d,%d count=%d\n", mAvailSpaceRect.x, mAvailSpaceRect.y, mAvailSpaceRect.width, mAvailSpaceRect.height, mBand.GetTrapezoidCount()); #endif } void InitFloater(nsLineLayout& aLineLayout, nsPlaceholderFrame* aPlaceholderFrame); void AddFloater(nsLineLayout& aLineLayout, nsPlaceholderFrame* aPlaceholderFrame, PRBool aInitialReflow); PRBool CanPlaceFloater(const nsRect& aFloaterRect, PRUint8 aFloats); void PlaceFloater(nsFloaterCache* aFloaterCache, PRBool* aIsLeftFloater, nsPoint* aNewOrigin); void PlaceBelowCurrentLineFloaters(nsFloaterCacheList& aFloaters); void ClearFloaters(nscoord aY, PRUint8 aBreakType); PRBool ClearPastFloaters(PRUint8 aBreakType); PRBool IsLeftMostChild(nsIFrame* aFrame); PRBool IsAdjacentWithTop() const { return mY == mReflowState.mComputedBorderPadding.top; } const nsMargin& BorderPadding() const { return mReflowState.mComputedBorderPadding; } void UpdateMaxElementSize(const nsSize& aMaxElementSize) { #ifdef NOISY_MAX_ELEMENT_SIZE nsSize oldSize = mMaxElementSize; #endif if (aMaxElementSize.width > mMaxElementSize.width) { mMaxElementSize.width = aMaxElementSize.width; } if (aMaxElementSize.height > mMaxElementSize.height) { mMaxElementSize.height = aMaxElementSize.height; } #ifdef NOISY_MAX_ELEMENT_SIZE if ((mMaxElementSize.width != oldSize.width) || (mMaxElementSize.height != oldSize.height)) { nsFrame::IndentBy(stdout, mBlock->GetDepth()); if (NS_UNCONSTRAINEDSIZE == mReflowState.availableWidth) { printf("PASS1 "); } nsFrame::ListTag(stdout, mBlock); printf(": old max-element-size=%d,%d new=%d,%d\n", oldSize.width, oldSize.height, mMaxElementSize.width, mMaxElementSize.height); } #endif } void RecoverVerticalMargins(nsLineBox* aLine, PRBool aApplyTopMargin, nscoord* aTopMarginResult, nscoord* aBottomMarginResult); void ComputeBlockAvailSpace(nsIFrame* aFrame, nsSplittableType aSplitType, const nsStyleDisplay* aDisplay, nsRect& aResult); void RecoverStateFrom(nsLineBox* aLine, PRBool aApplyTopMargin, nscoord aDeltaY, nsRect* aDamageRect); void AdvanceToNextLine() { mLineNumber++; } //---------------------------------------- // This state is the "global" state computed once for the reflow of // the block. // The block frame that is using this object nsBlockFrame* mBlock; nsIPresContext* mPresContext; const nsHTMLReflowState& mReflowState; // nsLineLayout* mLineLayout; nsISpaceManager* mSpaceManager; // The coordinates within the spacemanager where the block is being // placed after taking into account the blocks border and // padding. This, therefore, represents the inner "content area" (in // spacemanager coordinates) where child frames will be placed, // including child blocks and floaters. nscoord mSpaceManagerX, mSpaceManagerY; // XXX get rid of this nsReflowStatus mReflowStatus; nscoord mBottomEdge; PRBool mUnconstrainedWidth; PRBool mUnconstrainedHeight; PRBool mSkippedRightFloaters; nsSize mSkippedMaxElementSize; // The content area to reflow child frames within. The x/y // coordinates are known to be mBorderPadding.left and // mBorderPadding.top. The width/height may be NS_UNCONSTRAINEDSIZE // if the container reflowing this frame has given the frame an // unconstrained area. nsSize mContentArea; // Our wrapping behavior PRBool mNoWrap; // Is this frame a root for top/bottom margin collapsing? PRBool mIsTopMarginRoot, mIsBottomMarginRoot; // See ShouldApplyTopMargin PRBool mApplyTopMargin; //---------------------------------------- // This state is "running" state updated by the reflow of each line // in the block. This same state is "recovered" when a line is not // dirty and is passed over during incremental reflow. // The current line being reflowed nsLineBox* mCurrentLine; // The previous line just reflowed nsLineBox* mPrevLine; // The current Y coordinate in the block nscoord mY; // The available space within the current band. nsRect mAvailSpaceRect; // The maximum x-most of each line nscoord mKidXMost; // The combined area of all floaters placed so far nsRect mFloaterCombinedArea; nsFloaterCacheFreeList mFloaterCacheFreeList; // Previous child. This is used when pulling up a frame to update // the sibling list. nsIFrame* mPrevChild; // The next immediate child frame that is the target of an // incremental reflow command. Once that child has been reflowed we // null this slot out. nsIFrame* mNextRCFrame; // The previous child frames collapsed bottom margin value. nscoord mPrevBottomMargin; // The current next-in-flow for the block. When lines are pulled // from a next-in-flow, this is used to know which next-in-flow to // pull from. When a next-in-flow is emptied of lines, we advance // this to the next next-in-flow. nsBlockFrame* mNextInFlow; // The current band data for the current Y coordinate nsBlockBandData mBand; //---------------------------------------- // Temporary line-reflow state. This state is used during the reflow // of a given line, but doesn't have meaning before or after. // The list of floaters that are "current-line" floaters. These are // added to the line after the line has been reflowed, to keep the // list fiddling from being N^2. nsFloaterCacheFreeList mCurrentLineFloaters; // The list of floaters which are "below current-line" // floaters. These are reflowed/placed after the line is reflowed // and placed. Again, this is done to keep the list fiddling from // being N^2. nsFloaterCacheFreeList mBelowCurrentLineFloaters; PRBool mComputeMaxElementSize; nsSize mMaxElementSize; nscoord mMinLineHeight; PRInt32 mLineNumber; }; // XXX This is vile. Make it go away void nsLineLayout::InitFloater(nsPlaceholderFrame* aFrame) { mBlockRS->InitFloater(*this, aFrame); } void nsLineLayout::AddFloater(nsPlaceholderFrame* aFrame) { mBlockRS->AddFloater(*this, aFrame, PR_FALSE); } //---------------------------------------------------------------------- nsBlockReflowState::nsBlockReflowState(const nsHTMLReflowState& aReflowState, nsIPresContext* aPresContext, nsBlockFrame* aFrame, const nsHTMLReflowMetrics& aMetrics) : mBlock(aFrame), mPresContext(aPresContext), mReflowState(aReflowState), mIsTopMarginRoot(PR_FALSE), mIsBottomMarginRoot(PR_FALSE), mApplyTopMargin(PR_FALSE), mNextRCFrame(nsnull), mPrevBottomMargin(0), mLineNumber(0) { mSpaceManager = aReflowState.mSpaceManager; // Translate into our content area and then save the // coordinate system origin for later. const nsMargin& borderPadding = BorderPadding(); mSpaceManager->Translate(borderPadding.left, borderPadding.top); mSpaceManager->GetTranslation(mSpaceManagerX, mSpaceManagerY); mReflowStatus = NS_FRAME_COMPLETE; mPresContext = aPresContext; mBlock->GetNextInFlow((nsIFrame**)&mNextInFlow); mKidXMost = 0; // Compute content area width (the content area is inside the border // and padding) mUnconstrainedWidth = PR_FALSE; if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedWidth) { mContentArea.width = aReflowState.mComputedWidth; } else { if (NS_UNCONSTRAINEDSIZE == aReflowState.availableWidth) { mContentArea.width = NS_UNCONSTRAINEDSIZE; mUnconstrainedWidth = PR_TRUE; } else { nscoord lr = borderPadding.left + borderPadding.right; mContentArea.width = aReflowState.availableWidth - lr; } } mSkippedRightFloaters = PR_FALSE; mSkippedMaxElementSize.SizeTo(0, 0); // Compute content area height. Unlike the width, if we have a // specified style height we ignore it since extra content is // managed by the "overflow" property. When we don't have a // specified style height then we may end up limiting our height if // the availableHeight is constrained (this situation occurs when we // are paginated). mUnconstrainedHeight = PR_FALSE; if (NS_UNCONSTRAINEDSIZE != aReflowState.availableHeight) { // We are in a paginated situation. The bottom edge is just inside // the bottom border and padding. The content area height doesn't // include either border or padding edge. mBottomEdge = aReflowState.availableHeight - borderPadding.bottom; mContentArea.height = mBottomEdge - borderPadding.top; } else { // When we are not in a paginated situation then we always use // an constrained height. mUnconstrainedHeight = PR_TRUE; mContentArea.height = mBottomEdge = NS_UNCONSTRAINEDSIZE; } mY = borderPadding.top; mBand.Init(mSpaceManager, mContentArea); mPrevChild = nsnull; mCurrentLine = nsnull; mPrevLine = nsnull; const nsStyleText* styleText; mBlock->GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&) styleText); switch (styleText->mWhiteSpace) { case NS_STYLE_WHITESPACE_PRE: case NS_STYLE_WHITESPACE_NOWRAP: mNoWrap = PR_TRUE; break; default: mNoWrap = PR_FALSE; break; } mComputeMaxElementSize = nsnull != aMetrics.maxElementSize; mMaxElementSize.SizeTo(0, 0); if (0 != borderPadding.top) { mIsTopMarginRoot = PR_TRUE; } if (0 != borderPadding.bottom) { mIsBottomMarginRoot = PR_TRUE; } mMinLineHeight = nsHTMLReflowState::CalcLineHeight(*mPresContext, aReflowState.rendContext, aReflowState.frame); } nsBlockReflowState::~nsBlockReflowState() { // Restore the coordinate system const nsMargin& borderPadding = BorderPadding(); mSpaceManager->Translate(-borderPadding.left, -borderPadding.top); } // Compute the amount of available space for reflowing a block frame // at the current Y coordinate. This method assumes that // GetAvailableSpace has already been called. void nsBlockReflowState::ComputeBlockAvailSpace(nsIFrame* aFrame, nsSplittableType aSplitType, const nsStyleDisplay* aDisplay, nsRect& aResult) { aResult.y = mY; aResult.height = mUnconstrainedHeight ? NS_UNCONSTRAINEDSIZE : mBottomEdge - mY; const nsMargin& borderPadding = BorderPadding(); if (NS_FRAME_SPLITTABLE_NON_RECTANGULAR == aSplitType) { if (mBand.GetFloaterCount()) { // Use the float-edge property to determine how the child block // will interact with the floater. const nsStyleSpacing* spacing; aFrame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&) spacing); switch (spacing->mFloatEdge) { default: case NS_STYLE_FLOAT_EDGE_CONTENT: // The child block will flow around the floater. Therefore // give it all of the available space. aResult.x = borderPadding.left; aResult.width = mUnconstrainedWidth ? NS_UNCONSTRAINEDSIZE : mContentArea.width; break; case NS_STYLE_FLOAT_EDGE_BORDER: case NS_STYLE_FLOAT_EDGE_PADDING: { // The child block's border should be placed adjacent to, // but not overlap the floater(s). nsMargin m(0, 0, 0, 0); spacing->GetMargin(m); // XXX percentage margins if (NS_STYLE_FLOAT_EDGE_PADDING == spacing->mFloatEdge) { // Add in border too nsMargin b; spacing->GetBorder(b); m += b; } // determine left edge if (mBand.GetLeftFloaterCount()) { aResult.x = mAvailSpaceRect.x + borderPadding.left - m.left; } else { aResult.x = borderPadding.left; } // determine width if (mUnconstrainedWidth) { aResult.width = NS_UNCONSTRAINEDSIZE; } else { if (mBand.GetRightFloaterCount()) { if (mBand.GetLeftFloaterCount()) { aResult.width = mAvailSpaceRect.width + m.left + m.right; } else { aResult.width = mAvailSpaceRect.width + m.right; } } else { aResult.width = mAvailSpaceRect.width + m.left; } } } break; case NS_STYLE_FLOAT_EDGE_MARGIN: { // The child block's margins should be placed adjacent to, // but not overlap the floater. aResult.x = mAvailSpaceRect.x + borderPadding.left; aResult.width = mAvailSpaceRect.width; } break; } } else { // Since there are no floaters present the float-edge property // doesn't matter therefore give the block element all of the // available space since it will flow around the floater itself. aResult.x = borderPadding.left; aResult.width = mUnconstrainedWidth ? NS_UNCONSTRAINEDSIZE : mContentArea.width; } } else { // The frame is clueless about the space manager and therefore we // only give it free space. An example is a table frame - the // tables do not flow around floaters. aResult.x = mAvailSpaceRect.x + borderPadding.left; aResult.width = mAvailSpaceRect.width; } } PRBool nsBlockReflowState::ClearPastFloaters(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; ClearFloaters(saveY, aBreakType); #ifdef NOISY_FLOATER_CLEARING nsFrame::ListTag(stdout, mBlock); printf(": ClearPastFloaters: mPrevBottomMargin=%d saveY=%d oldY=%d newY=%d deltaY=%d\n", mPrevBottomMargin, saveY, saveY - mPrevBottomMargin, 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 floaters. // // 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. mPrevBottomMargin = deltaY; mY = saveY; // Force margin to be applied in this circumstance applyTopMargin = PR_TRUE; } else { // Put aState.mY back to its original value since no clearing // happened. That way the previous blocks bottom margin will // be applied properly. mY = saveY - mPrevBottomMargin; } break; } return applyTopMargin; } // Recover the collapsed vertical margin values for aLine. Note that // the values are not collapsed with aState.mPrevBottomMargin, nor are // they collapsed with each other when the line height is zero. void nsBlockReflowState::RecoverVerticalMargins(nsLineBox* aLine, PRBool aApplyTopMargin, nscoord* aTopMarginResult, nscoord* aBottomMarginResult) { if (aLine->IsBlock()) { // Update band data GetAvailableSpace(); // Setup reflow state to compute the block childs top and bottom // margins nsIFrame* frame = aLine->mFirstChild; nsRect availSpaceRect; const nsStyleDisplay* display; frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display); nsSplittableType splitType = NS_FRAME_NOT_SPLITTABLE; frame->IsSplittable(splitType); ComputeBlockAvailSpace(frame, splitType, display, availSpaceRect); nsSize availSpace(availSpaceRect.width, availSpaceRect.height); nsHTMLReflowState reflowState(*mPresContext, mReflowState, frame, availSpace); // Compute collapsed top margin nscoord topMargin = 0; if (aApplyTopMargin) { topMargin = nsBlockReflowContext::ComputeCollapsedTopMargin(mPresContext, reflowState); } // Compute collapsed bottom margin nscoord bottomMargin = reflowState.mComputedMargin.bottom; bottomMargin = nsBlockReflowContext::MaxMargin(bottomMargin, aLine->mCarriedOutBottomMargin); *aTopMarginResult = topMargin; *aBottomMarginResult = bottomMargin; } else { // XXX_ib *aTopMarginResult = 0; *aBottomMarginResult = 0; } } void nsBlockReflowState::RecoverStateFrom(nsLineBox* aLine, PRBool aApplyTopMargin, nscoord aDeltaY, nsRect* aDamageRect) { // Make the line being recovered the current line mCurrentLine = aLine; // Update aState.mPrevChild as if we had reflowed all of the frames // in this line. mPrevChild = aLine->LastChild(); // Recover mKidXMost and max element width nscoord xmost = aLine->mBounds.XMost(); if (xmost > mKidXMost) { #ifdef DEBUG if (CRAZY_WIDTH(xmost)) { nsFrame::ListTag(stdout, mBlock); printf(": WARNING: xmost:%d\n", xmost); } #endif mKidXMost = xmost; } if (mComputeMaxElementSize) { UpdateMaxElementSize(nsSize(aLine->mMaxElementWidth, aLine->mBounds.height)); } // The line may have clear before semantics. if (aLine->IsBlock() && (NS_STYLE_CLEAR_NONE != aLine->mBreakType)) { // Clear past floaters before the block if the clear style is not none aApplyTopMargin = ClearPastFloaters(aLine->mBreakType); #ifdef NOISY_VERTICAL_MARGINS nsFrame::ListTag(stdout, mBlock); printf(": RecoverStateFrom: y=%d child ", mY); nsFrame::ListTag(stdout, aLine->mFirstChild); printf(" has clear of %d => %s, mPrevBottomMargin=%d\n", aLine->mBreakType, aApplyTopMargin ? "applyTopMargin" : "nope", mPrevBottomMargin); #endif } // Recover mPrevBottomMargin and calculate the line's new Y // coordinate (newLineY) nscoord newLineY = mY; if ((0 == aLine->mBounds.height) && (0 == aLine->mCombinedArea.height)) { if (aLine->IsBlock() && nsBlockReflowContext::IsHTMLParagraph(aLine->mFirstChild)) { // Empty HTML paragraphs disappear entirely - their margins go // to zero. } else { // The line's top and bottom margin values need to be collapsed // with the mPrevBottomMargin to determine a new // mPrevBottomMargin value. nscoord topMargin, bottomMargin; RecoverVerticalMargins(aLine, aApplyTopMargin, &topMargin, &bottomMargin); nscoord m = nsBlockReflowContext::MaxMargin(bottomMargin, mPrevBottomMargin); m = nsBlockReflowContext::MaxMargin(m, topMargin); mPrevBottomMargin = m; } } else { // Recover the top and bottom margins for this line nscoord topMargin, bottomMargin; RecoverVerticalMargins(aLine, aApplyTopMargin, &topMargin, &bottomMargin); // Compute the collapsed top margin value nscoord collapsedTopMargin = nsBlockReflowContext::MaxMargin(topMargin, mPrevBottomMargin); // The lineY is just below the collapsed top margin value. The // mPrevBottomMargin gets set to the bottom margin value for the // line. newLineY += collapsedTopMargin; mPrevBottomMargin = bottomMargin; } // Save away the old combined area for later nsRect oldCombinedArea = aLine->mCombinedArea; // Slide the frames in the line by the computed delta. This also // updates the lines Y coordinate and the combined area's Y // coordinate. nscoord finalDeltaY = newLineY - aLine->mBounds.y; mBlock->SlideLine(*this, aLine, finalDeltaY); // Place floaters for this line into the space manager if (aLine->mFloaters.NotEmpty()) { // Undo border/padding translation since the nsFloaterCache's // coordinates are relative to the frame not relative to the // border/padding. const nsMargin& bp = BorderPadding(); mSpaceManager->Translate(-bp.left, -bp.top); // Place the floaters into the space-manager again. Also slide // them, just like the regular frames on the line. nsRect r; nsFloaterCache* fc = aLine->mFloaters.Head(); while (fc) { fc->mRegion.y += finalDeltaY; fc->mCombinedArea.y += finalDeltaY; nsIFrame* floater = fc->mPlaceholder->GetOutOfFlowFrame(); floater->GetRect(r); floater->MoveTo(r.x, r.y + finalDeltaY); #ifdef NOISY_SPACEMANAGER nscoord tx, ty; mSpaceManager->GetTranslation(tx, ty); nsFrame::ListTag(stdout, mBlock); printf(": RecoverStateFrom: AddRectRegion: txy=%d,%d (%d,%d) {%d,%d,%d,%d}\n", tx, ty, mSpaceManagerX, mSpaceManagerY, fc->mRegion.x, fc->mRegion.y, fc->mRegion.width, fc->mRegion.height); #endif mSpaceManager->AddRectRegion(floater, fc->mRegion); fc = fc->Next(); } // And then put the translation back again mSpaceManager->Translate(bp.left, bp.top); } // Recover mY mY = aLine->mBounds.YMost(); // Compute the damage area if (aDamageRect) { if (0 == finalDeltaY) { aDamageRect->Empty(); } else { aDamageRect->UnionRect(oldCombinedArea, aLine->mCombinedArea); } } // It's possible that the line has clear after semantics if (!aLine->IsBlock() && (NS_STYLE_CLEAR_NONE != aLine->mBreakType)) { switch (aLine->mBreakType) { case NS_STYLE_CLEAR_LEFT: case NS_STYLE_CLEAR_RIGHT: case NS_STYLE_CLEAR_LEFT_AND_RIGHT: ClearFloaters(mY, aLine->mBreakType); break; } } } //---------------------------------------------------------------------- const nsIID kBlockFrameCID = NS_BLOCK_FRAME_CID; nsresult NS_NewBlockFrame(nsIFrame** aNewFrame) { NS_PRECONDITION(aNewFrame, "null OUT ptr"); if (nsnull == aNewFrame) { return NS_ERROR_NULL_POINTER; } nsBlockFrame* it = new nsBlockFrame; if (nsnull == it) { return NS_ERROR_OUT_OF_MEMORY; } *aNewFrame = it; return NS_OK; } nsBlockFrame::nsBlockFrame() { } nsBlockFrame::~nsBlockFrame() { nsTextRun::DeleteTextRuns(mTextRuns); } NS_IMETHODIMP nsBlockFrame::Destroy(nsIPresContext& aPresContext) { // Outside bullets are not in our child-list so check for them here // and delete them when present. if (HaveOutsideBullet()) { mBullet->Destroy(aPresContext); mBullet = nsnull; } mFloaters.DestroyFrames(aPresContext); nsLineBox::DeleteLineList(aPresContext, mLines); nsLineBox::DeleteLineList(aPresContext, mOverflowLines); return nsBlockFrameSuper::Destroy(aPresContext); } NS_IMETHODIMP nsBlockFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr) { if (NULL == aInstancePtr) { return NS_ERROR_NULL_POINTER; } if (aIID.Equals(kBlockFrameCID)) { nsBlockFrame* tmp = this; *aInstancePtr = (void*) tmp; return NS_OK; } if (aIID.Equals(nsILineIterator::GetIID())) { nsLineIterator* it = new nsLineIterator; if (!it) { *aInstancePtr = nsnull; return NS_ERROR_OUT_OF_MEMORY; } const nsStyleDisplay* display; GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display); nsresult rv = it->Init(mLines, display->mDirection == NS_STYLE_DIRECTION_RTL); if (NS_FAILED(rv)) { delete it; return rv; } return it->QueryInterface(aIID, aInstancePtr); } return nsBlockFrameSuper::QueryInterface(aIID, aInstancePtr); } NS_IMETHODIMP nsBlockFrame::IsSplittable(nsSplittableType& aIsSplittable) const { aIsSplittable = NS_FRAME_SPLITTABLE_NON_RECTANGULAR; return NS_OK; } static void ListTextRuns(FILE* out, PRInt32 aIndent, nsTextRun* aRuns) { while (nsnull != aRuns) { aRuns->List(out, aIndent); aRuns = aRuns->GetNext(); } } NS_METHOD nsBlockFrame::List(FILE* out, PRInt32 aIndent) const { IndentBy(out, aIndent); ListTag(out); nsIView* view; GetView(&view); if (nsnull != view) { fprintf(out, " [view=%p]", view); } if (nsnull != mNextSibling) { fprintf(out, " next=%p", mNextSibling); } // Output the flow linkage if (nsnull != mPrevInFlow) { fprintf(out, " prev-in-flow=%p", mPrevInFlow); } if (nsnull != mNextInFlow) { fprintf(out, " next-in-flow=%p", mNextInFlow); } // Output the rect and state fprintf(out, " {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, mRect.height); if (0 != mState) { fprintf(out, " [state=%08x]", mState); } if (0 != mFlags) { fprintf(out, " [flags=%x]", mFlags); } PRInt32 numInlineLines = 0; PRInt32 numBlockLines = 0; if (nsnull != mLines) { nsLineBox* line = mLines; while (nsnull != line) { if (line->IsBlock()) { numBlockLines++; } else { numInlineLines++; } line = line->mNext; } } fprintf(out, " sc=%p(i=%d,b=%d)<\n", mStyleContext, numInlineLines, numBlockLines); aIndent++; // Output the lines if (nsnull != mLines) { nsLineBox* line = mLines; while (nsnull != line) { line->List(out, aIndent); line = line->mNext; } } nsIAtom* listName = nsnull; PRInt32 listIndex = 0; for (;;) { nsIFrame* kid; GetAdditionalChildListName(listIndex++, &listName); if (nsnull == listName) { break; } FirstChild(listName, &kid); if (nsnull != kid) { IndentBy(out, aIndent); nsAutoString tmp; if (nsnull != listName) { listName->ToString(tmp); fputs(tmp, out); } fputs("<\n", out); while (nsnull != kid) { kid->List(out, aIndent + 1); kid->GetNextSibling(&kid); } IndentBy(out, aIndent); fputs(">\n", out); } NS_IF_RELEASE(listName); } // Output the text-runs if (nsnull != mTextRuns) { IndentBy(out, aIndent); fputs("text-runs <\n", out); ListTextRuns(out, aIndent + 1, mTextRuns); IndentBy(out, aIndent); fputs(">\n", out); } aIndent--; IndentBy(out, aIndent); fputs(">\n", out); return NS_OK; } NS_IMETHODIMP nsBlockFrame::GetFrameName(nsString& aResult) const { return MakeFrameName("Block", aResult); } NS_IMETHODIMP nsBlockFrame::GetFrameType(nsIAtom** aType) const { NS_PRECONDITION(nsnull != aType, "null OUT parameter pointer"); *aType = nsLayoutAtoms::blockFrame; NS_ADDREF(*aType); return NS_OK; } ///////////////////////////////////////////////////////////////////////////// // Child frame enumeration NS_IMETHODIMP nsBlockFrame::FirstChild(nsIAtom* aListName, nsIFrame** aFirstChild) const { NS_PRECONDITION(nsnull != aFirstChild, "null OUT parameter pointer"); if (nsnull == aListName) { *aFirstChild = (nsnull != mLines) ? mLines->mFirstChild : nsnull; return NS_OK; } else if (aListName == nsLayoutAtoms::floaterList) { *aFirstChild = mFloaters.FirstChild(); return NS_OK; } else if (aListName == nsLayoutAtoms::bulletList) { if (HaveOutsideBullet()) { *aFirstChild = mBullet; } else { *aFirstChild = nsnull; } return NS_OK; } *aFirstChild = nsnull; return NS_ERROR_INVALID_ARG; } NS_IMETHODIMP nsBlockFrame::GetAdditionalChildListName(PRInt32 aIndex, nsIAtom** aListName) const { NS_PRECONDITION(nsnull != aListName, "null OUT parameter pointer"); if (aIndex < 0) { return NS_ERROR_INVALID_ARG; } *aListName = nsnull; switch (aIndex) { case NS_BLOCK_FRAME_FLOATER_LIST_INDEX: *aListName = nsLayoutAtoms::floaterList; NS_ADDREF(*aListName); break; case NS_BLOCK_FRAME_BULLET_LIST_INDEX: *aListName = nsLayoutAtoms::bulletList; NS_ADDREF(*aListName); break; } return NS_OK; } NS_IMETHODIMP nsBlockFrame::IsPercentageBase(PRBool& aBase) const { aBase = PR_TRUE; return NS_OK; } ////////////////////////////////////////////////////////////////////// // Frame structure methods ////////////////////////////////////////////////////////////////////// // Reflow methods NS_IMETHODIMP nsBlockFrame::Reflow(nsIPresContext& aPresContext, nsHTMLReflowMetrics& aMetrics, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { if (IsFrameTreeTooDeep(aReflowState, aMetrics)) { #ifdef DEBUG_kipp { extern char* nsPresShell_ReflowStackPointerTop; char marker; char* newsp = (char*) ▮ printf("XXX: frame tree is too deep; approx stack size = %d\n", nsPresShell_ReflowStackPointerTop - newsp); } #endif aStatus = NS_FRAME_COMPLETE; return NS_OK; } nsBlockReflowState state(aReflowState, &aPresContext, this, aMetrics); if (NS_BLOCK_MARGIN_ROOT & mFlags) { state.mIsTopMarginRoot = PR_TRUE; state.mIsBottomMarginRoot = PR_TRUE; } if (state.mIsTopMarginRoot) { state.mApplyTopMargin = PR_TRUE; } if (eReflowReason_Resize != aReflowState.reason) { RenumberLists(); ComputeTextRuns(&aPresContext); } nsresult rv = NS_OK; PRBool isStyleChange = PR_FALSE; nsIFrame* target; switch (aReflowState.reason) { case eReflowReason_Initial: #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": reflow=initial\n"); #endif DrainOverflowLines(); rv = PrepareInitialReflow(state); mState &= ~NS_FRAME_FIRST_REFLOW; break; case eReflowReason_Incremental: aReflowState.reflowCommand->GetTarget(target); if (this == target) { nsIReflowCommand::ReflowType type; aReflowState.reflowCommand->GetType(type); #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": reflow=incremental type=%d\n", type); #endif switch (type) { case nsIReflowCommand::StyleChanged: rv = PrepareStyleChangedReflow(state); isStyleChange = PR_TRUE; break; case nsIReflowCommand::ReflowDirty: break; default: // Map any other incremental operations into full reflows rv = PrepareResizeReflow(state); break; } } else { // Get next frame in reflow command chain aReflowState.reflowCommand->GetNext(state.mNextRCFrame); #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": reflow=incremental"); if (state.mNextRCFrame) { printf(" next="); nsFrame::ListTag(stdout, state.mNextRCFrame); } printf("\n"); #endif // Now do the reflow rv = PrepareChildIncrementalReflow(state); } break; case eReflowReason_Resize: default: #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": reflow=resize (%d)\n", aReflowState.reason); #endif DrainOverflowLines(); rv = PrepareResizeReflow(state); break; } // Now reflow... rv = ReflowDirtyLines(state); aStatus = state.mReflowStatus; if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) { if (NS_STYLE_OVERFLOW_HIDDEN == aReflowState.mStyleDisplay->mOverflow) { aStatus = NS_FRAME_COMPLETE; } else { #ifdef DEBUG_kipp ListTag(stdout); printf(": block is not complete\n"); #endif } } // XXX_pref get rid of this! BuildFloaterList(); // Compute our final size ComputeFinalSize(aReflowState, state, aMetrics); // If this is an incremental reflow and we changed size, then make sure our // border is repainted if necessary if (eReflowReason_Incremental == aReflowState.reason) { if (isStyleChange) { // Lots of things could have changed so damage our entire // bounds Invalidate(nsRect(0, 0, mRect.width, mRect.height)); } else { nsMargin border = aReflowState.mComputedBorderPadding - aReflowState.mComputedPadding; // See if our width changed if ((aMetrics.width != mRect.width) && (border.right > 0)) { nsRect damageRect; if (aMetrics.width < mRect.width) { // Our new width is smaller, so we need to make sure that // we paint our border in its new position damageRect.x = aMetrics.width - border.right; damageRect.width = border.right; damageRect.y = 0; damageRect.height = aMetrics.height; } else { // Our new width is larger, so we need to erase our border in its // old position damageRect.x = mRect.width - border.right; damageRect.width = border.right; damageRect.y = 0; damageRect.height = mRect.height; } Invalidate(damageRect); } // See if our height changed if ((aMetrics.height != mRect.height) && (border.bottom > 0)) { nsRect damageRect; if (aMetrics.height < mRect.height) { // Our new height is smaller, so we need to make sure that // we paint our border in its new position damageRect.x = 0; damageRect.width = aMetrics.width; damageRect.y = aMetrics.height - border.bottom; damageRect.height = border.bottom; } else { // Our new height is larger, so we need to erase our border in its // old position damageRect.x = 0; damageRect.width = mRect.width; damageRect.y = mRect.height - border.bottom; damageRect.height = border.bottom; } Invalidate(damageRect); } } } #ifdef DEBUG if (gShowDirtyLines && (eReflowReason_Resize == aReflowState.reason)) { nsLineBox* line = mLines; while (nsnull != line) { line->ClearWasDirty(); line = line->mNext; } } #endif #ifdef NOISY_FINAL_SIZE ListTag(stdout); printf(": availSize=%d,%d computed=%d,%d metrics=%d,%d carriedMargin=%d\n", aReflowState.availableWidth, aReflowState.availableHeight, aReflowState.mComputedWidth, aReflowState.mComputedHeight, aMetrics.width, aMetrics.height, aMetrics.mCarriedOutBottomMargin); #endif return rv; } static PRBool HaveAutoWidth(const nsHTMLReflowState& aReflowState) { const nsHTMLReflowState* rs = &aReflowState; if (NS_UNCONSTRAINEDSIZE == rs->mComputedWidth) { return PR_TRUE; } const nsStylePosition* pos = rs->mStylePosition; for (;;) { if (!pos) { return PR_TRUE; } nsStyleUnit widthUnit = pos->mWidth.GetUnit(); if (eStyleUnit_Auto == widthUnit) { return PR_TRUE; } if (eStyleUnit_Inherit != widthUnit) { break; } const nsHTMLReflowState* prs = (const nsHTMLReflowState*) rs->parentReflowState; if (!prs) { return PR_TRUE; } rs = prs; pos = prs->mStylePosition; } return PR_FALSE; } void nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, nsBlockReflowState& aState, nsHTMLReflowMetrics& aMetrics) { const nsMargin& borderPadding = aState.BorderPadding(); #ifdef NOISY_FINAL_SIZE ListTag(stdout); printf(": mY=%d mIsBottomMarginRoot=%s mPrevBottomMargin=%d bp=%d,%d\n", aState.mY, aState.mIsBottomMarginRoot ? "yes" : "no", aState.mPrevBottomMargin, borderPadding.top, borderPadding.bottom); #endif // Special check for zero sized content: If our content is zero // sized then we collapse into nothingness. // // Consensus after discussion with a few CSS folks is that html's // notion of collapsing
's should take precedence over non
// auto-sided block elements. Therefore we don't honor the width,
// height, border or padding attributes (the parent has to not apply
// a margin for us also).
//
// Note that this is only done for html paragraphs. Its not
// appropriate to apply it to other containers, especially XML
// content!
PRBool isHTMLParagraph = 0 != (mState & NS_BLOCK_IS_HTML_PARAGRAPH);
if (isHTMLParagraph &&
(aReflowState.mStyleDisplay->mDisplay == NS_STYLE_DISPLAY_BLOCK) &&
(((0 == aState.mKidXMost) ||
(0 == aState.mKidXMost - borderPadding.left)) &&
(0 == aState.mY - borderPadding.top))) {
// Zero out most everything
aMetrics.width = 0;
aMetrics.height = 0;
aMetrics.ascent = 0;
aMetrics.descent = 0;
aMetrics.mCarriedOutBottomMargin = 0;
// Note: Don't zero out the max-element-sizes: they will be zero
// if this is truly empty, otherwise they won't because of a
// floater.
if (nsnull != aMetrics.maxElementSize) {
aMetrics.maxElementSize->width = aState.mMaxElementSize.width;
aMetrics.maxElementSize->height = aState.mMaxElementSize.height;
}
}
else {
// Compute final width
nscoord maxWidth = 0, maxHeight = 0;
nscoord minWidth = aState.mKidXMost + borderPadding.right;
if (!HaveAutoWidth(aReflowState)) {
// Use style defined width
aMetrics.width = borderPadding.left + aReflowState.mComputedWidth +
borderPadding.right;
// XXX quote css1 section here
if ((0 == aReflowState.mComputedWidth) && (aMetrics.width < minWidth)) {
aMetrics.width = minWidth;
}
// When style defines the width use it for the max-element-size
// because we can't shrink any smaller.
maxWidth = aMetrics.width;
}
else {
nscoord computedWidth = minWidth;
PRBool compact = PR_FALSE;
#if 0
if (NS_STYLE_DISPLAY_COMPACT == aReflowState.mStyleDisplay->mDisplay) {
// If we are display: compact AND we have no lines or we have
// exactly one line and that line is not a block line AND that
// line doesn't end in a BR of any sort THEN we remain a compact
// frame.
if ((nsnull == mLines) ||
((nsnull == mLines->mNext) && !mLines->IsBlock() &&
(NS_STYLE_CLEAR_NONE == mLines->mBreakType)
/*XXX && (computedWidth <= aState.mCompactMarginWidth) */
)) {
compact = PR_TRUE;
}
}
#endif
// There are two options here. We either shrink wrap around our
// contents or we fluff out to the maximum block width. Note:
// We always shrink wrap when given an unconstrained width.
if ((0 == (NS_BLOCK_SHRINK_WRAP & mFlags)) &&
!aState.mUnconstrainedWidth &&
!compact) {
// Set our width to the max width if we aren't already that
// wide. Note that the max-width has nothing to do with our
// contents (CSS2 section XXX)
computedWidth = borderPadding.left + aState.mContentArea.width +
borderPadding.right;
}
// See if we should compute our max element size
if (aState.mComputeMaxElementSize) {
if (aState.mNoWrap) {
// When no-wrap is true the max-element-size.width is the
// width of the widest line plus the right border. Note that
// aState.mKidXMost already has the left border factored in
maxWidth = aState.mKidXMost + borderPadding.right;
}
else {
// Add in border and padding dimensions to already computed
// max-element-size values.
maxWidth = aState.mMaxElementSize.width +
borderPadding.left + borderPadding.right;
}
// See if our max-element-size width is larger than our
// computed-width. This happens when we are impacted by a
// floater. When this does happen, our desired size needs to
// include room for the floater.
if (computedWidth < maxWidth) {
#ifdef DEBUG_kipp
ListTag(stdout);
printf(": adjusting width from %d to %d\n", computedWidth,
maxWidth);
#endif
computedWidth = maxWidth;
}
}
// Apply min/max values
if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxWidth) {
nscoord computedMaxWidth = aReflowState.mComputedMaxWidth +
borderPadding.left + borderPadding.right;
if (computedWidth > computedMaxWidth) {
computedWidth = aReflowState.mComputedMaxWidth;
}
}
if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinWidth) {
nscoord computedMinWidth = aReflowState.mComputedMinWidth +
borderPadding.left + borderPadding.right;
if (computedWidth < computedMinWidth) {
computedWidth = computedMinWidth;
}
}
aMetrics.width = computedWidth;
}
// Compute final height
if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedHeight) {
// Use style defined height
aMetrics.height = borderPadding.top + aReflowState.mComputedHeight +
borderPadding.bottom;
// When style defines the height use it for the max-element-size
// because we can't shrink any smaller.
maxHeight = aMetrics.height;
// Don't carry out a bottom margin when our height is fixed
// unless the bottom of the last line adjoins the bottom of our
// content area.
if (!aState.mIsBottomMarginRoot) {
if (aState.mY + aState.mPrevBottomMargin != aMetrics.height) {
aState.mPrevBottomMargin = 0;
}
}
}
else {
nscoord autoHeight = aState.mY;
// Shrink wrap our height around our contents.
if (aState.mIsBottomMarginRoot) {
// When we are a bottom-margin root make sure that our last
// childs bottom margin is fully applied.
// XXX check for a fit
autoHeight += aState.mPrevBottomMargin;
}
autoHeight += borderPadding.bottom;
// Apply min/max values
if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxHeight) {
nscoord computedMaxHeight = aReflowState.mComputedMaxHeight +
borderPadding.top + borderPadding.bottom;
if (autoHeight > computedMaxHeight) {
autoHeight = computedMaxHeight;
}
}
if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinHeight) {
nscoord computedMinHeight = aReflowState.mComputedMinHeight +
borderPadding.top + borderPadding.bottom;
if (autoHeight < computedMinHeight) {
autoHeight = computedMinHeight;
}
}
aMetrics.height = autoHeight;
if (aState.mComputeMaxElementSize) {
maxHeight = aState.mMaxElementSize.height +
borderPadding.top + borderPadding.bottom;
}
}
aMetrics.ascent = aMetrics.height;
aMetrics.descent = 0;
if (aState.mComputeMaxElementSize) {
// Store away the final value
aMetrics.maxElementSize->width = maxWidth;
aMetrics.maxElementSize->height = maxHeight;
}
// Return bottom margin information
aMetrics.mCarriedOutBottomMargin =
aState.mIsBottomMarginRoot ? 0 : aState.mPrevBottomMargin;
#ifdef DEBUG
if (CRAZY_WIDTH(aMetrics.width) || CRAZY_HEIGHT(aMetrics.height)) {
ListTag(stdout);
printf(": WARNING: desired:%d,%d\n", aMetrics.width, aMetrics.height);
}
if (aState.mComputeMaxElementSize &&
((maxWidth > aMetrics.width) || (maxHeight > aMetrics.height))) {
ListTag(stdout);
printf(": WARNING: max-element-size:%d,%d desired:%d,%d maxSize:%d,%d\n",
maxWidth, maxHeight, aMetrics.width, aMetrics.height,
aState.mReflowState.availableWidth,
aState.mReflowState.availableHeight);
}
#endif
#ifdef NOISY_MAX_ELEMENT_SIZE
if (aState.mComputeMaxElementSize) {
IndentBy(stdout, GetDepth());
if (NS_UNCONSTRAINEDSIZE == aState.mReflowState.availableWidth) {
printf("PASS1 ");
}
ListTag(stdout);
printf(": max-element-size:%d,%d desired:%d,%d maxSize:%d,%d\n",
maxWidth, maxHeight, aMetrics.width, aMetrics.height,
aState.mReflowState.availableWidth,
aState.mReflowState.availableHeight);
}
#endif
}
// Compute the combined area of our children
// XXX_perf: This can be done incrementally
nscoord xa = 0, ya = 0, xb = aMetrics.width, yb = aMetrics.height;
if (NS_STYLE_OVERFLOW_HIDDEN != aReflowState.mStyleDisplay->mOverflow) {
nsLineBox* line = mLines;
while (nsnull != line) {
// Compute min and max x/y values for the reflowed frame's
// combined areas
nscoord x = line->mCombinedArea.x;
nscoord y = line->mCombinedArea.y;
nscoord xmost = x + line->mCombinedArea.width;
nscoord ymost = y + line->mCombinedArea.height;
if (x < xa) {
xa = x;
}
if (xmost > xb) {
xb = xmost;
}
if (y < ya) {
ya = y;
}
if (ymost > yb) {
yb = ymost;
}
line = line->mNext;
}
// Factor the bullet in; normally the bullet will be factored into
// the line-box's combined area. However, if the line is a block
// line then it won't; if there are no lines, it won't. So just
// factor it in anyway (it can't hurt if it was already done).
if (mBullet) {
nsRect r;
mBullet->GetRect(r);
if (r.x < xa) xa = r.x;
if (r.y < ya) ya = r.y;
nscoord xmost = r.XMost();
if (xmost > xb) xb = xmost;
nscoord ymost = r.YMost();
if (ymost > yb) yb = ymost;
}
}
#ifdef NOISY_COMBINED_AREA
ListTag(stdout);
printf(": ca=%d,%d,%d,%d\n", xa, ya, xb-xa, yb-ya);
#endif
// If the combined area of our children exceeds our bounding box
// then set the NS_FRAME_OUTSIDE_CHILDREN flag, otherwise clear it.
aMetrics.mCombinedArea.x = xa;
aMetrics.mCombinedArea.y = ya;
aMetrics.mCombinedArea.width = xb - xa;
aMetrics.mCombinedArea.height = yb - ya;
if ((aMetrics.mCombinedArea.x < 0) ||
(aMetrics.mCombinedArea.y < 0) ||
(aMetrics.mCombinedArea.XMost() > aMetrics.width) ||
(aMetrics.mCombinedArea.YMost() > aMetrics.height)) {
mState |= NS_FRAME_OUTSIDE_CHILDREN;
}
else {
mState &= ~NS_FRAME_OUTSIDE_CHILDREN;
}
}
nsresult
nsBlockFrame::PrepareInitialReflow(nsBlockReflowState& aState)
{
PrepareResizeReflow(aState);
return NS_OK;
}
nsresult
nsBlockFrame::PrepareChildIncrementalReflow(nsBlockReflowState& aState)
{
// If by chance we are inside a table, then give up and reflow
// everything because we don't cache max-element-size information in
// the lines.
if (aState.mComputeMaxElementSize) {
return PrepareResizeReflow(aState);
}
// Determine the line being impacted
PRBool isFloater;
nsLineBox* prevLine;
nsLineBox* line = FindLineFor(aState.mNextRCFrame, &prevLine, &isFloater);
if (nsnull == line) {
// This can't happen, but just in case it does...
return PrepareResizeReflow(aState);
}
// XXX: temporary: If the child frame is a floater then punt
if (isFloater) {
return PrepareResizeReflow(aState);
}
// If the line that was affected is a block then just mark it dirty
// so that we reflow it.
if (line->IsBlock()) {
line->MarkDirty();
#ifdef NOISY_INCREMENTAL_REFLOW
ListTag(stdout);
printf(": mark line %p dirty\n", line);
#endif
}
else {
// Mark previous line dirty if its an inline line so that it can
// maybe pullup something from the line just affected.
if (prevLine && !prevLine->IsBlock()) {
prevLine->MarkDirty();
#ifdef NOISY_INCREMENTAL_REFLOW
ListTag(stdout);
printf(": mark prev-line %p dirty\n", prevLine);
#endif
}
else {
line->MarkDirty();
#ifdef NOISY_INCREMENTAL_REFLOW
ListTag(stdout);
printf(": mark line %p dirty\n", line);
#endif
}
}
return NS_OK;
}
void
nsBlockFrame::UpdateBulletPosition()
{
if (nsnull == mBullet) {
// Don't bother if there is no bullet
return;
}
const nsStyleList* styleList;
GetStyleData(eStyleStruct_List, (const nsStyleStruct*&) styleList);
if (NS_STYLE_LIST_STYLE_POSITION_INSIDE == styleList->mListStylePosition) {
if (HaveOutsideBullet()) {
// We now have an inside bullet, but used to have an outside
// bullet. Adjust the frame lists and mark the first line
// dirty.
if (nsnull != mLines) {
mBullet->SetNextSibling(mLines->mFirstChild);
mLines->mFirstChild = mBullet;
mLines->mChildCount++;
mLines->MarkDirty();
}
}
mState &= ~NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET;
}
else {
if (!HaveOutsideBullet()) {
// We now have an outside bullet, but used to have an inside
// bullet. Take the bullet frame out of the first lines frame
// list.
if ((nsnull != mLines) && (mBullet == mLines->mFirstChild)) {
nsIFrame* next;
mBullet->GetNextSibling(&next);
mBullet->SetNextSibling(nsnull);
NS_ASSERTION(mLines->mChildCount > 0, "empty line w/o bullet");
if (--mLines->mChildCount == 0) {
nsLineBox* nextLine = mLines->mNext;
delete mLines;
mLines = nextLine;
if (nsnull != nextLine) {
nextLine->MarkDirty();
}
}
else {
mLines->mFirstChild = next;
mLines->MarkDirty();
}
}
}
mState |= NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET;
}
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
}
nsresult
nsBlockFrame::PrepareStyleChangedReflow(nsBlockReflowState& aState)
{
UpdateBulletPosition();
// Mark everything dirty
nsLineBox* line = mLines;
while (nsnull != line) {
line->MarkDirty();
line = line->mNext;
}
return NS_OK;
}
nsresult
nsBlockFrame::PrepareResizeReflow(nsBlockReflowState& aState)
{
// See if we can try and avoid marking all the lines as dirty
PRBool tryAndSkipLines = PR_FALSE;
// See if this is this a constrained resize reflow
if ((aState.mReflowState.reason == eReflowReason_Resize) &&
(NS_UNCONSTRAINEDSIZE != aState.mReflowState.availableWidth)) {
// If the text is left-aligned, then we try and avoid reflowing the lines
const nsStyleText* mStyleText = (const nsStyleText*)
mStyleContext->GetStyleData(eStyleStruct_Text);
if (NS_STYLE_TEXT_ALIGN_LEFT == mStyleText->mTextAlign) {
// If it's preformatted text, then we don't need to mark any lines
// as dirty
if (aState.mNoWrap) {
return NS_OK;
}
tryAndSkipLines = PR_TRUE;
}
}
nsLineBox* line = mLines;
if (tryAndSkipLines) {
// The line's bounds are relative to the border edge of the frame
nscoord newAvailWidth = aState.mReflowState.mComputedBorderPadding.left +
aState.mReflowState.mComputedWidth;
while (nsnull != line) {
// We don't have to mark the line dirty if:
// - the line fits within the new available space
// - it's inline (not a block)
// - it's either the last line in the block -or- it ended with a
// break after
// - there are no floaters associated with the line (reflowing the
// placeholder frame causes the floater to be reflowed)
if (line->IsBlock() ||
(line->mNext && (line->mBreakType == NS_STYLE_CLEAR_NONE)) ||
line->mFloaters.NotEmpty() ||
(line->mBounds.XMost() > newAvailWidth)) {
// We have to mark the line dirty
line->MarkDirty();
}
line = line->mNext;
}
}
else {
// Mark everything dirty
while (nsnull != line) {
line->MarkDirty();
line = line->mNext;
}
}
return NS_OK;
}
//----------------------------------------
nsLineBox*
nsBlockFrame::FindLineFor(nsIFrame* aFrame,
nsLineBox** aPrevLineResult,
PRBool* aIsFloaterResult)
{
nsLineBox* prevLine = nsnull;
nsLineBox* line = mLines;
PRBool isFloater = PR_FALSE;
while (nsnull != line) {
if (line->Contains(aFrame)) {
break;
}
if (line->mFloaters.NotEmpty()) {
nsFloaterCache* fc = line->mFloaters.Head();
while (fc) {
if (aFrame == fc->mPlaceholder->GetOutOfFlowFrame()) {
isFloater = PR_TRUE;
goto done;
}
fc = fc->Next();
}
}
prevLine = line;
line = line->mNext;
}
done:
*aIsFloaterResult = isFloater;
*aPrevLineResult = prevLine;
return line;
}
void
nsBlockFrame::RecoverStateFrom(nsBlockReflowState& aState,
nsLineBox* aLine,
nscoord aDeltaY,
nsRect* aDamageRect)
{
PRBool applyTopMargin = PR_FALSE;
if (aLine->IsBlock()) {
nsIFrame* framePrevInFlow;
aLine->mFirstChild->GetPrevInFlow(&framePrevInFlow);
if (nsnull == framePrevInFlow) {
applyTopMargin = ShouldApplyTopMargin(aState, aLine);
}
}
aState.RecoverStateFrom(aLine, applyTopMargin, aDeltaY, aDamageRect);
}
/**
* Propogate reflow "damage" from the just reflowed line (aLine) to
* any subsequent lines that were affected. The only thing that causes
* damage is a change to the impact that floaters make.
*/
void
nsBlockFrame::PropogateReflowDamage(nsBlockReflowState& aState,
nsLineBox* aLine,
const nsRect& aOldCombinedArea,
nscoord aDeltaY)
{
// See if the line has a relevant combined area, and if it does if
// the combined area has changed.
if (aLine->mCombinedArea != aLine->mBounds) {
if (aLine->mCombinedArea != aOldCombinedArea) {
// The line's combined-area changed. Therefore we need to damage
// the lines below that were previously (or are now) impacted by
// the change. It's possible that a floater shrunk or grew so
// use the larger of the impacted area.
nscoord newYMost = aLine->mCombinedArea.YMost();
nscoord oldYMost = aOldCombinedArea.YMost();
nscoord impactYB = newYMost < oldYMost ? oldYMost : newYMost;
nscoord impactYA = aLine->mCombinedArea.y;
// Loop over each subsequent line and mark them dirty if they
// intersect the impacted area. Note: we cannot stop after the
// first non-intersecting line because lines might be
// overlapping because of negative margins.
nsLineBox* next = aLine->mNext;
while (nsnull != next) {
nscoord lineYA = next->mBounds.y + aDeltaY;
nscoord lineYB = lineYA + next->mBounds.height;
if ((lineYB >= impactYA) && (lineYA < impactYB)) {
next->MarkDirty();
}
next = next->mNext;
}
}
else {
// The line's combined area didn't change from last
// time. Therefore just sliding subsequent lines will work.
return;
}
}
if (aDeltaY) {
nsLineBox* next = aLine->mNext;
while (nsnull != next) {
if (!next->IsDirty()) {
// Cases we need to find:
//
// 1. the line was impacted by a floater and now isn't
// 2. the line wasn't impacted by a floater and now is
//
//XXXPerf: An optimization: if the line was and is completely
//impacted by a floater and the floater hasn't changed size,
//then we don't need to mark the line dirty.
aState.GetAvailableSpace(next->mBounds.y + aDeltaY);
PRBool wasImpactedByFloater = next->IsImpactedByFloater();
PRBool isImpactedByFloater = 0 != aState.mBand.GetFloaterCount();
if (wasImpactedByFloater != isImpactedByFloater) {
next->MarkDirty();
}
else if (isImpactedByFloater) {
//XXX: Maybe the floater itself changed size?
if (next->IsBlock()) {
//XXXPerf
// Case:
// It's possible that more/less of the line is impacted by
// the floater than last time. So reflow.
next->MarkDirty();
}
}
}
next = next->mNext;
}
}
}
/**
* Reflow the dirty lines
*/
nsresult
nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState)
{
nsresult rv = NS_OK;
PRBool keepGoing = PR_TRUE;
#ifdef NOISY_INCREMENTAL_REFLOW
if (aState.mReflowState.reason == eReflowReason_Incremental) {
nsIReflowCommand::ReflowType type;
aState.mReflowState.reflowCommand->GetType(type);
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": incrementally reflowing dirty lines: type=%s(%d)",
kReflowCommandType[type], type);
}
else {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": reflowing dirty lines");
}
printf(" computedWidth=%d\n", aState.mReflowState.mComputedWidth);
gNoiseIndent++;
#endif
// Check whether this is an incremental reflow
PRBool incrementalReflow = aState.mReflowState.reason ==
eReflowReason_Incremental;
// Reflow the lines that are already ours
aState.mPrevLine = nsnull;
nsLineBox* line = mLines;
nscoord deltaY = 0;
while (nsnull != line) {
#ifdef NOISY_INCREMENTAL_REFLOW
IndentBy(stdout, gNoiseIndent);
printf("line=%p mY=%d dirty=%s oldBounds=%d,%d,%d,%d deltaY=%d mPrevBottomMargin=%d\n",
line, aState.mY, line->IsDirty() ? "yes" : "no",
line->mBounds.x, line->mBounds.y,
line->mBounds.width, line->mBounds.height,
deltaY, aState.mPrevBottomMargin);
gNoiseIndent++;
#endif
if (line->IsDirty()) {
// Compute the dirty lines "before" YMost, after factoring in
// the running deltaY value - the running value is implicit in
// aState.mY.
nscoord oldHeight = line->mBounds.height;
nsRect oldCombinedArea = line->mCombinedArea;
// Reflow the dirty line. If it's an incremental reflow, then have
// it invalidate the dirty area
rv = ReflowLine(aState, line, &keepGoing, incrementalReflow);
if (NS_FAILED(rv)) {
return rv;
}
if (!keepGoing) {
if (0 == line->ChildCount()) {
DeleteLine(aState, line);
}
break;
}
nscoord newHeight = line->mBounds.height;
deltaY += newHeight - oldHeight;
// If the next line is clean then check and see if reflowing the
// current line "damaged" the next line. Damage occurs when the
// current line contains floaters that intrude upon the
// subsequent lines.
nsLineBox* next = line->mNext;
if ((nsnull != next) && !next->IsDirty()) {
PropogateReflowDamage(aState, line, oldCombinedArea, deltaY);
}
}
else {
// XXX what if the slid line doesn't fit because we are in a
// vertically constrained situation?
// Recover state as if we reflowed this line
nsRect damageRect;
RecoverStateFrom(aState, line, deltaY, incrementalReflow ?
&damageRect : 0);
if (incrementalReflow && !damageRect.IsEmpty()) {
Invalidate(damageRect);
}
}
#ifdef NOISY_INCREMENTAL_REFLOW
gNoiseIndent--;
IndentBy(stdout, gNoiseIndent);
printf("line=%p mY=%d newBounds=%d,%d,%d,%d deltaY=%d mPrevBottomMargin=%d\n",
line, aState.mY,
line->mBounds.x, line->mBounds.y,
line->mBounds.width, line->mBounds.height,
deltaY, aState.mPrevBottomMargin);
#endif
// If this is an inline frame then its time to stop
aState.mPrevLine = line;
line = line->mNext;
aState.AdvanceToNextLine();
}
// Pull data from a next-in-flow if we can
while (keepGoing && (nsnull != aState.mNextInFlow)) {
// Grab first line from our next-in-flow
line = aState.mNextInFlow->mLines;
if (nsnull == line) {
aState.mNextInFlow = (nsBlockFrame*) aState.mNextInFlow->mNextInFlow;
continue;
}
// XXX See if the line is not dirty; if it's not maybe we can
// avoid the pullup if it can't fit?
aState.mNextInFlow->mLines = line->mNext;
line->mNext = nsnull;
if (0 == line->ChildCount()) {
// The line is empty. Try the next one.
NS_ASSERTION(nsnull == line->mFirstChild, "bad empty line");
delete line;
continue;
}
// XXX move to a subroutine: run-in, overflow, pullframe and this do this
// Make the children in the line ours.
nsIFrame* frame = line->mFirstChild;
nsIFrame* lastFrame = nsnull;
PRInt32 n = line->ChildCount();
while (--n >= 0) {
frame->SetParent(this);
// When pushing and pulling frames we need to check for whether any
// views need to be reparented
nsHTMLContainerFrame::ReparentFrameView(frame, mNextInFlow, this);
lastFrame = frame;
frame->GetNextSibling(&frame);
}
lastFrame->SetNextSibling(nsnull);
// Add line to our line list
if (nsnull == aState.mPrevLine) {
NS_ASSERTION(nsnull == mLines, "bad aState.mPrevLine");
mLines = line;
}
else {
NS_ASSERTION(nsnull == aState.mPrevLine->mNext, "bad aState.mPrevLine");
aState.mPrevLine->mNext = line;
aState.mPrevChild->SetNextSibling(line->mFirstChild);
}
// Now reflow it and any lines that it makes during it's reflow
// (we have to loop here because reflowing the line may case a new
// line to be created; see SplitLine's callers for examples of
// when this happens).
while (nsnull != line) {
rv = ReflowLine(aState, line, &keepGoing, incrementalReflow ?
PR_TRUE : PR_FALSE);
if (NS_FAILED(rv)) {
return rv;
}
if (!keepGoing) {
if (0 == line->ChildCount()) {
DeleteLine(aState, line);
}
break;
}
// If this is an inline frame then its time to stop
aState.mPrevLine = line;
line = line->mNext;
aState.AdvanceToNextLine();
}
}
// Handle an odd-ball case: a list-item with no lines
if (mBullet && HaveOutsideBullet() && !mLines) {
nsHTMLReflowMetrics metrics(nsnull);
ReflowBullet(aState, metrics);
// There are no lines so we have to fake up some y motion so that
// we end up with *some* height.
aState.mY += metrics.height;
}
#ifdef NOISY_INCREMENTAL_REFLOW
gNoiseIndent--;
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": done reflowing dirty lines (status=%x)\n",
aState.mReflowStatus);
#endif
return rv;
}
void
nsBlockFrame::DeleteLine(nsBlockReflowState& aState,
nsLineBox* aLine)
{
NS_PRECONDITION(0 == aLine->ChildCount(), "can't delete !empty line");
if (0 == aLine->ChildCount()) {
if (nsnull == aState.mPrevLine) {
NS_ASSERTION(aLine == mLines, "huh");
mLines = nsnull;
}
else {
NS_ASSERTION(aState.mPrevLine->mNext == aLine, "bad prev-line");
aState.mPrevLine->mNext = aLine->mNext;
}
delete aLine;
}
}
/**
* Reflow a line. The line will either contain a single block frame
* or contain 1 or more inline frames. aLineReflowStatus indicates
* whether or not the caller should continue to reflow more lines.
*/
nsresult
nsBlockFrame::ReflowLine(nsBlockReflowState& aState,
nsLineBox* aLine,
PRBool* aKeepReflowGoing,
PRBool aDamageDirtyArea)
{
nsresult rv = NS_OK;
#ifdef DEBUG
aLine->MarkWasDirty();
#endif
// If the line is empty then first pull a frame into it so that we
// know what kind of line it is (block or inline).
if (0 == aLine->ChildCount()) {
#ifdef DEBUG_kipp
printf("XXX: ");
ListTag(stdout);
printf(": huh? reflow-line with empty line!\n");
#endif
nsIFrame* frame;
rv = PullFrame(aState, aLine, frame);
if (NS_FAILED(rv)) {
return rv;
}
if (nsnull == frame) {
*aKeepReflowGoing = PR_FALSE;
return rv;
}
}
// Setup the line-layout for the new line
aState.mCurrentLine = aLine;
aLine->ClearDirty();
// Now that we know what kind of line we have, reflow it
nsRect oldCombinedArea = aLine->mCombinedArea;
if (aLine->IsBlock()) {
NS_ASSERTION(aLine->mFloaters.IsEmpty(), "bad line");
rv = ReflowBlockFrame(aState, aLine, aKeepReflowGoing);
// We expect blocks to damage any area inside their bounds that is
// dirty; however, if the frame changes size or position then we
// need to do some repainting
if (aDamageDirtyArea) {
if ((oldCombinedArea.x != aLine->mCombinedArea.x) ||
(oldCombinedArea.y != aLine->mCombinedArea.y)) {
// The block has moved, and so do be safe we need to repaint
// XXX We need to improve on this...
nsRect dirtyRect;
dirtyRect.UnionRect(oldCombinedArea, aLine->mCombinedArea);
Invalidate(dirtyRect);
} else {
if (oldCombinedArea.width != aLine->mCombinedArea.width) {
nsRect dirtyRect;
// Just damage the vertical strip that was either added or went
// away
dirtyRect.x = PR_MIN(oldCombinedArea.XMost(),
aLine->mCombinedArea.XMost());
dirtyRect.y = aLine->mCombinedArea.y;
dirtyRect.width = PR_MAX(oldCombinedArea.XMost(),
aLine->mCombinedArea.XMost()) -
dirtyRect.x;
dirtyRect.height = PR_MAX(oldCombinedArea.height,
aLine->mCombinedArea.height);
Invalidate(dirtyRect);
}
if (oldCombinedArea.height != aLine->mCombinedArea.height) {
nsRect dirtyRect;
// Just damage the horizontal strip that was either added or went
// away
dirtyRect.x = aLine->mCombinedArea.x;
dirtyRect.y = PR_MIN(oldCombinedArea.YMost(),
aLine->mCombinedArea.YMost());
dirtyRect.width = PR_MAX(oldCombinedArea.width,
aLine->mCombinedArea.width);
dirtyRect.height = PR_MAX(oldCombinedArea.YMost(),
aLine->mCombinedArea.YMost()) -
dirtyRect.y;
Invalidate(dirtyRect);
}
}
}
}
else {
rv = ReflowInlineFrames(aState, aLine, aKeepReflowGoing);
// We don't really know what changed in the line, so use the union
// of the old and new combined areas
if (aDamageDirtyArea) {
nsRect dirtyRect;
dirtyRect.UnionRect(oldCombinedArea, aLine->mCombinedArea);
Invalidate(dirtyRect);
}
}
return rv;
}
/**
* Pull frame from the next available location (one of our lines or
* one of our next-in-flows lines).
*/
nsresult
nsBlockFrame::PullFrame(nsBlockReflowState& aState,
nsLineBox* aLine,
nsIFrame*& aFrameResult)
{
nsresult rv = NS_OK;
PRBool stopPulling;
aFrameResult = nsnull;
// First check our remaining lines
while (nsnull != aLine->mNext) {
rv = PullFrame(aState, aLine, &aLine->mNext, PR_FALSE,
aFrameResult, stopPulling);
if (NS_FAILED(rv) || stopPulling) {
return rv;
}
}
// Pull frames from the next-in-flow(s) until we can't
nsBlockFrame* nextInFlow = aState.mNextInFlow;
while (nsnull != nextInFlow) {
nsLineBox* line = nextInFlow->mLines;
if (nsnull == line) {
nextInFlow = (nsBlockFrame*) nextInFlow->mNextInFlow;
aState.mNextInFlow = nextInFlow;
continue;
}
rv = PullFrame(aState, aLine, &nextInFlow->mLines, PR_TRUE,
aFrameResult, stopPulling);
if (NS_FAILED(rv) || stopPulling) {
return rv;
}
}
return rv;
}
/**
* Try to pull a frame out of a line pointed at by aFromList. If a
* frame is pulled then aPulled will be set to PR_TRUE. In addition,
* if aUpdateGeometricParent is set then the pulled frames geometric
* parent will be updated (e.g. when pulling from a next-in-flows line
* list).
*
* Note: pulling a frame from a line that is a place-holder frame
* doesn't automatically remove the corresponding floater from the
* line's floater array. This happens indirectly: either the line gets
* emptied (and destroyed) or the line gets reflowed (because we mark
* it dirty) and the code at the top of ReflowLine empties the
* array. So eventually, it will be removed, just not right away.
*/
nsresult
nsBlockFrame::PullFrame(nsBlockReflowState& aState,
nsLineBox* aLine,
nsLineBox** aFromList,
PRBool aUpdateGeometricParent,
nsIFrame*& aFrameResult,
PRBool& aStopPulling)
{
nsLineBox* fromLine = *aFromList;
NS_ASSERTION(nsnull != fromLine, "bad line to pull from");
if (0 == fromLine->ChildCount()) {
// Discard empty lines immediately. Empty lines can happen here
// because of DeleteChildsNextInFlow not being able to delete
// lines. Don't stop pulling - there may be more frames around.
*aFromList = fromLine->mNext;
NS_ASSERTION(nsnull == fromLine->mFirstChild, "bad empty line");
delete fromLine;
aStopPulling = PR_FALSE;
aFrameResult = nsnull;
}
else if ((0 != aLine->ChildCount()) && fromLine->IsBlock()) {
// If our line is not empty and the child in aFromLine is a block
// then we cannot pull up the frame into this line. In this case
// we stop pulling.
aStopPulling = PR_TRUE;
aFrameResult = nsnull;
}
else {
// Take frame from fromLine
nsIFrame* frame = fromLine->mFirstChild;
if (0 == aLine->mChildCount++) {
aLine->mFirstChild = frame;
aLine->SetIsBlock(fromLine->IsBlock());
NS_ASSERTION(aLine->CheckIsBlock(), "bad line isBlock");
NS_ASSERTION(aLine->mFloaters.IsEmpty(), "bad line floaters");
}
NS_ASSERTION(aLine->mChildCount < MAX_LINE_COUNT, "bad line child count");
if (0 != --fromLine->mChildCount) {
NS_ASSERTION(fromLine->mChildCount < MAX_LINE_COUNT, "bad line child count");
// Mark line dirty now that we pulled a child
fromLine->MarkDirty();
frame->GetNextSibling(&fromLine->mFirstChild);
}
else {
// Free up the fromLine now that it's empty
*aFromList = fromLine->mNext;
delete fromLine;
}
// Change geometric parents
if (aUpdateGeometricParent) {
// Before we set the new parent frame get the current parent
nsIFrame* oldParentFrame;
frame->GetParent(&oldParentFrame);
frame->SetParent(this);
// When pushing and pulling frames we need to check for whether any
// views need to be reparented
NS_ASSERTION(oldParentFrame != this, "unexpected parent frame");
nsHTMLContainerFrame::ReparentFrameView(frame, oldParentFrame, this);
// The frame is being pulled from a next-in-flow; therefore we
// need to add it to our sibling list.
if (nsnull != aState.mPrevChild) {
aState.mPrevChild->SetNextSibling(frame);
}
frame->SetNextSibling(nsnull);
}
// Stop pulling because we found a frame to pull
aStopPulling = PR_TRUE;
aFrameResult = frame;
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
}
return NS_OK;
}
void
nsBlockFrame::SlideLine(nsBlockReflowState& aState,
nsLineBox* aLine, nscoord aDY)
{
// Adjust line state
aLine->mBounds.y += aDY;
aLine->mCombinedArea.y += aDY;
// Adjust the frames in the line
nsIFrame* kid = aLine->mFirstChild;
if (!kid) {
return;
}
if (aLine->IsBlock()) {
nsRect r;
kid->GetRect(r);
if (aDY) {
r.y += aDY;
kid->SetRect(r);
}
// If the child has any floaters that impact the space-manager,
// place them now so that they are present in the space-manager
// again (they were removed by the space-manager's frame when
// the reflow began).
nsBlockFrame* bf;
nsresult rv = kid->QueryInterface(kBlockFrameCID, (void**) &bf);
if (NS_SUCCEEDED(rv)) {
// Translate spacemanager to the child blocks upper-left corner
// so that when it places its floaters (which are relative to
// it) the right coordinates are used. Note that we have already
// been translated by our border+padding so factor that in to
// get the right translation.
const nsMargin& bp = aState.BorderPadding();
nscoord dx = r.x - bp.left;
nscoord dy = r.y - bp.top;
aState.mSpaceManager->Translate(dx, dy);
bf->UpdateSpaceManager(aState.mPresContext, aState.mSpaceManager);
aState.mSpaceManager->Translate(-dx, -dy);
}
}
else {
if (aDY) {
// Adjust the Y coordinate of the frames in the line
nsRect r;
PRInt32 n = aLine->ChildCount();
while (--n >= 0) {
kid->GetRect(r);
r.y += aDY;
kid->SetRect(r);
kid->GetNextSibling(&kid);
}
}
}
}
nsresult
nsBlockFrame::UpdateSpaceManager(nsIPresContext* aPresContext,
nsISpaceManager* aSpaceManager)
{
nsLineBox* line = mLines;
while (nsnull != line) {
// Place the floaters in the spacemanager
if (line->mFloaters.NotEmpty()) {
nsFloaterCache* fc = line->mFloaters.Head();
while (fc) {
nsIFrame* floater = fc->mPlaceholder->GetOutOfFlowFrame();
aSpaceManager->AddRectRegion(floater, fc->mRegion);
#ifdef NOISY_SPACEMANAGER
nscoord tx, ty;
aSpaceManager->GetTranslation(tx, ty);
nsFrame::ListTag(stdout, this);
printf(": UpdateSpaceManager: AddRectRegion: txy=%d,%d {%d,%d,%d,%d}\n",
tx, ty,
fc->mRegion.x, fc->mRegion.y,
fc->mRegion.width, fc->mRegion.height);
#endif
fc = fc->Next();
}
}
// Tell kids about the move too
if (line->mFirstChild && line->IsBlock()) {
// If the child has any floaters that impact the space-manager,
// place them now so that they are present in the space-manager
// again (they were removed by the space-manager's frame when
// the reflow began).
nsBlockFrame* bf;
nsresult rv = line->mFirstChild->QueryInterface(kBlockFrameCID,
(void**) &bf);
if (NS_SUCCEEDED(rv)) {
nsPoint origin;
bf->GetOrigin(origin);
// Translate spacemanager to the child blocks upper-left
// corner so that when it places its floaters (which are
// relative to it) the right coordinates are used.
aSpaceManager->Translate(origin.x, origin.y);
bf->UpdateSpaceManager(aPresContext, aSpaceManager);
aSpaceManager->Translate(-origin.x, -origin.y);
}
}
line = line->mNext;
}
return NS_OK;
}
NS_IMETHODIMP
nsBlockFrame::AttributeChanged(nsIPresContext* aPresContext,
nsIContent* aChild,
nsIAtom* aAttribute,
PRInt32 aHint)
{
nsresult rv = nsBlockFrameSuper::AttributeChanged(aPresContext, aChild,
aAttribute, aHint);
if (NS_OK != rv) {
return rv;
}
if (nsHTMLAtoms::start == aAttribute) {
// XXX Not sure if this is necessary anymore
RenumberLists();
nsCOMPtr ... ). This is where
// the second case can happen.
if (HaveOutsideBullet() &&
((aLine == mLines) ||
((0 == mLines->mBounds.height) && (aLine == mLines->mNext)))) {
// Reflow the bullet
nsHTMLReflowMetrics metrics(nsnull);
ReflowBullet(aState, metrics);
// For bullets that are placed next to a child block, there will
// be no correct ascent value. Therefore, make one up...
nscoord ascent = 0;
const nsStyleFont* font;
frame->GetStyleData(eStyleStruct_Font,
(const nsStyleStruct*&) font);
nsIRenderingContext& rc = *aState.mReflowState.rendContext;
rc.SetFont(font->mFont);
nsIFontMetrics* fm;
rv = rc.GetFontMetrics(fm);
if (NS_SUCCEEDED(rv) && (nsnull != fm)) {
fm->GetMaxAscent(ascent);
NS_RELEASE(fm);
}
rv = NS_OK;
// Tall bullets won't look particularly nice here...
nsRect bbox;
mBullet->GetRect(bbox);
nscoord topMargin = applyTopMargin ? collapsedBottomMargin : 0;
bbox.y = aState.BorderPadding().top + ascent -
metrics.ascent + topMargin;
mBullet->SetRect(bbox);
}
}
else {
// None of the block fits. Determine the correct reflow status.
if (aLine == mLines) {
// 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.
PushLines(aState);
aState.mReflowStatus = NS_FRAME_NOT_COMPLETE;
}
}
}
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
return rv;
}
#define LINE_REFLOW_OK 0
#define LINE_REFLOW_STOP 1
#define LINE_REFLOW_REDO 2
nsresult
nsBlockFrame::ReflowInlineFrames(nsBlockReflowState& aState,
nsLineBox* aLine,
PRBool* aKeepReflowGoing)
{
nsresult rv = NS_OK;
*aKeepReflowGoing = PR_TRUE;
#ifdef DEBUG
PRInt32 spins = 0;
#endif
PRUint8 lineReflowStatus = LINE_REFLOW_REDO;
while (LINE_REFLOW_REDO == lineReflowStatus) {
// Prevent overflowing limited thread stacks by creating
// nsLineLayout from the heap when the frame tree depth gets
// large.
if (aState.mReflowState.mReflowDepth > 30) {//XXX layout-tune.h?
rv = DoReflowInlineFramesMalloc(aState, aLine, aKeepReflowGoing,
&lineReflowStatus);
}
else {
rv = DoReflowInlineFramesAuto(aState, aLine, aKeepReflowGoing,
&lineReflowStatus);
}
if (NS_FAILED(rv)) {
break;
}
#ifdef DEBUG
spins++;
if (1000 == spins) {
ListTag(stdout);
printf(": yikes! spinning on a line over 1000 times!\n");
NS_ABORT();
}
#endif
}
return rv;
}
nsresult
nsBlockFrame::DoReflowInlineFramesMalloc(nsBlockReflowState& aState,
nsLineBox* aLine,
PRBool* aKeepReflowGoing,
PRUint8* aLineReflowStatus)
{
nsLineLayout* ll = new nsLineLayout(*aState.mPresContext,
aState.mReflowState.mSpaceManager,
&aState.mReflowState,
aState.mComputeMaxElementSize);
if (!ll) {
return NS_ERROR_OUT_OF_MEMORY;
}
ll->Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
ll->SetReflowTextRuns(mTextRuns);
nsresult rv = DoReflowInlineFrames(aState, *ll, aLine, aKeepReflowGoing,
aLineReflowStatus);
ll->EndLineReflow();
delete ll;
return rv;
}
nsresult
nsBlockFrame::DoReflowInlineFramesAuto(nsBlockReflowState& aState,
nsLineBox* aLine,
PRBool* aKeepReflowGoing,
PRUint8* aLineReflowStatus)
{
nsLineLayout lineLayout(*aState.mPresContext,
aState.mReflowState.mSpaceManager,
&aState.mReflowState,
aState.mComputeMaxElementSize);
lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
lineLayout.SetReflowTextRuns(mTextRuns);
nsresult rv = DoReflowInlineFrames(aState, lineLayout, aLine,
aKeepReflowGoing, aLineReflowStatus);
lineLayout.EndLineReflow();
return rv;
}
nsresult
nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
nsLineBox* aLine,
PRBool* aKeepReflowGoing,
PRUint8* aLineReflowStatus)
{
// Forget all of the floaters on the line
aState.mFloaterCacheFreeList.Append(aLine->mFloaters);
aState.mFloaterCombinedArea.SetRect(0, 0, 0, 0);
// Setup initial coordinate system for reflowing the inline frames
// into. Apply a previous block frame's bottom margin first.
aState.mY += aState.mPrevBottomMargin;
aState.GetAvailableSpace();
aLine->SetLineIsImpactedByFloater(0 != aState.mBand.GetFloaterCount());
const nsMargin& borderPadding = aState.BorderPadding();
nscoord x = aState.mAvailSpaceRect.x + borderPadding.left;
nscoord availWidth = aState.mAvailSpaceRect.width;
nscoord availHeight;
if (aState.mUnconstrainedHeight) {
availHeight = NS_UNCONSTRAINEDSIZE;
}
else {
/* XXX get the height right! */
availHeight = aState.mAvailSpaceRect.height;
}
PRBool impactedByFloaters = 0 != aState.mBand.GetFloaterCount();
aLineLayout.BeginLineReflow(x, aState.mY,
availWidth, availHeight,
impactedByFloaters,
PR_FALSE /*XXX isTopOfPage*/);
// XXX Unfortunately we need to know this before reflowing the first
// inline frame in the line. FIX ME.
if ((0 == aLineLayout.GetLineNumber()) &&
(NS_BLOCK_HAS_FIRST_LETTER_STYLE & mState)) {
aLineLayout.SetFirstLetterStyleOK(PR_TRUE);
}
// Reflow the frames that are already on the line first
nsresult rv = NS_OK;
PRUint8 lineReflowStatus = LINE_REFLOW_OK;
PRInt32 i;
nsIFrame* frame = aLine->mFirstChild;
for (i = 0; i < aLine->ChildCount(); i++) {
rv = ReflowInlineFrame(aState, aLineLayout, aLine, frame,
&lineReflowStatus);
if (NS_FAILED(rv)) {
return rv;
}
if (LINE_REFLOW_OK != lineReflowStatus) {
// It is possible that one or more of next lines are empty
// (because of DeleteChildsNextInFlow). If so, delete them now
// in case we are finished.
nsLineBox* nextLine = aLine->mNext;
while ((nsnull != nextLine) && (0 == nextLine->ChildCount())) {
// XXX Is this still necessary now that DeleteChildsNextInFlow
// uses DoRemoveFrame?
aLine->mNext = nextLine->mNext;
NS_ASSERTION(nsnull == nextLine->mFirstChild, "bad empty line");
delete nextLine;
nextLine = aLine->mNext;
}
break;
}
frame->GetNextSibling(&frame);
}
// Pull frames and reflow them until we can't
while (LINE_REFLOW_OK == lineReflowStatus) {
rv = PullFrame(aState, aLine, frame);
if (NS_FAILED(rv)) {
return rv;
}
if (nsnull == frame) {
break;
}
while (LINE_REFLOW_OK == lineReflowStatus) {
PRInt32 oldCount = aLine->ChildCount();
rv = ReflowInlineFrame(aState, aLineLayout, aLine, frame,
&lineReflowStatus);
if (NS_FAILED(rv)) {
return rv;
}
if (aLine->ChildCount() != oldCount) {
// We just created a continuation for aFrame AND its going
// to end up on this line (e.g. :first-letter
// situation). Therefore we have to loop here before trying
// to pull another frame.
frame->GetNextSibling(&frame);
}
else {
break;
}
}
}
if (LINE_REFLOW_REDO == lineReflowStatus) {
// This happens only when we have a line that is impacted by
// floaters and the first element in the line doesn't fit with
// the floaters.
//
// What we do is to advance past the first floater we find and
// then reflow the line all over again.
NS_ASSERTION(aState.mBand.GetFloaterCount(),
"redo line on totally empty line");
NS_ASSERTION(NS_UNCONSTRAINEDSIZE != aState.mAvailSpaceRect.height,
"unconstrained height on totally empty line");
aState.mY += aState.mAvailSpaceRect.height;
// XXX: a small optimization can be done here when paginating:
// if the new Y coordinate is past the end of the block then
// push the line and return now instead of later on after we are
// past the floater.
}
else {
// If we are propogating out a break-before status then there is
// no point in placing the line.
NS_ASSERTION(aLine->mChildCount < MAX_LINE_COUNT, "bad line child count");
if (!NS_INLINE_IS_BREAK_BEFORE(aState.mReflowStatus)) {
rv = PlaceLine(aState, aLineLayout, aLine, aKeepReflowGoing);
}
}
*aLineReflowStatus = lineReflowStatus;
return rv;
}
/**
* Reflow an inline frame. The reflow status is mapped from the frames
* reflow status to the lines reflow status (not to our reflow status).
* The line reflow status is simple: PR_TRUE means keep placing frames
* on the line; PR_FALSE means don't (the line is done). If the line
* has some sort of breaking affect then aLine->mBreakType will be set
* to something other than NS_STYLE_CLEAR_NONE.
*/
nsresult
nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
nsLineBox* aLine,
nsIFrame* aFrame,
PRUint8* aLineReflowStatus)
{
*aLineReflowStatus = LINE_REFLOW_OK;
// If it's currently ok to be reflowing in first-letter style then
// we must be about to reflow a frame that has first-letter style.
PRBool reflowingFirstLetter = aLineLayout.GetFirstLetterStyleOK();
#ifdef NOISY_FIRST_LETTER
ListTag(stdout);
printf(": reflowing ");
nsFrame::ListTag(stdout, aFrame);
printf(" reflowingFirstLetter=%s\n", reflowingFirstLetter ? "on" : "off");
#endif
// Reflow the inline frame
nsReflowStatus frameReflowStatus;
nsresult rv = aLineLayout.ReflowFrame(aFrame, &aState.mNextRCFrame,
frameReflowStatus);
if (NS_FAILED(rv)) {
return rv;
}
#ifdef REALLY_NOISY_REFLOW_CHILD
nsFrame::ListTag(stdout, aFrame);
printf(": status=%x\n", frameReflowStatus);
#endif
#if defined(REFLOW_STATUS_COVERAGE)
RecordReflowStatus(PR_FALSE, frameReflowStatus);
#endif
// Send post-reflow notification
aState.mPrevChild = aFrame;
// Process the child frames reflow status. There are 5 cases:
// complete, not-complete, break-before, break-after-complete,
// 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->mBreakType = NS_STYLE_CLEAR_NONE;
if (NS_INLINE_IS_BREAK(frameReflowStatus)) {
// Always abort the line reflow (because a line break is the
// minimal amount of break we do).
*aLineReflowStatus = LINE_REFLOW_STOP;
// XXX what should aLine->mBreakType be set to in all these cases?
PRUint8 breakType = NS_INLINE_GET_BREAK_TYPE(frameReflowStatus);
NS_ASSERTION(breakType != NS_STYLE_CLEAR_NONE, "bad break type");
NS_ASSERTION(NS_STYLE_CLEAR_PAGE != breakType, "no page breaks yet");
if (NS_INLINE_IS_BREAK_BEFORE(frameReflowStatus)) {
// Break-before cases.
if (aFrame == aLine->mFirstChild) {
// If we break before the first frame on the line then we must
// be trying to place content where theres no room (e.g. on a
// line with wide floaters). Inform the caller to reflow the
// line after skipping past a floater.
*aLineReflowStatus = LINE_REFLOW_REDO;
}
else {
// It's not the first child on this line so go ahead and split
// the line. We will see the frame again on the next-line.
rv = SplitLine(aState, aLineLayout, aLine, aFrame);
if (NS_FAILED(rv)) {
return rv;
}
}
}
else {
// Break-after cases
aLine->mBreakType = breakType;
if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) {
// Create a continuation for the incomplete frame. Note that the
// frame may already have a continuation.
PRBool madeContinuation;
rv = CreateContinuationFor(aState, aLine, aFrame, madeContinuation);
if (NS_FAILED(rv)) {
return rv;
}
}
// Split line, but after the frame just reflowed
nsIFrame* nextFrame;
aFrame->GetNextSibling(&nextFrame);
rv = SplitLine(aState, aLineLayout, aLine, nextFrame);
if (NS_FAILED(rv)) {
return rv;
}
// Mark next line dirty in case SplitLine didn't end up
// pushing any frames.
nsLineBox* next = aLine->mNext;
if ((nsnull != next) && !next->IsBlock()) {
next->MarkDirty();
}
}
}
else if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) {
// Frame is not-complete, no special breaking status
// Create a continuation for the incomplete frame. Note that the
// frame may already have a continuation.
PRBool madeContinuation;
rv = CreateContinuationFor(aState, aLine, aFrame, madeContinuation);
if (NS_FAILED(rv)) {
return rv;
}
// If we are reflowing the first letter frame then don't split the
// line and don't stop the line reflow...
PRBool splitLine = !reflowingFirstLetter;
if (reflowingFirstLetter) {
#ifdef BLOCK_DOES_FIRST_LINE
if (aLine->IsFirstLine()) {
splitLine = PR_TRUE;
}
else
#endif
{
nsIAtom* frameType;
if (NS_SUCCEEDED(aFrame->GetFrameType(&frameType)) && frameType) {
if (frameType == nsLayoutAtoms::inlineFrame) {
splitLine = PR_TRUE;
}
NS_RELEASE(frameType);
}
}
}
if (splitLine) {
// Split line after the current frame
*aLineReflowStatus = LINE_REFLOW_STOP;
aFrame->GetNextSibling(&aFrame);
rv = SplitLine(aState, aLineLayout, aLine, aFrame);
if (NS_FAILED(rv)) {
return rv;
}
// Mark next line dirty in case SplitLine didn't end up
// pushing any frames.
nsLineBox* next = aLine->mNext;
if ((nsnull != next) && !next->IsBlock()) {
next->MarkDirty();
}
}
}
return NS_OK;
}
/**
* Create a continuation, if necessary, for aFrame. Place it on the
* same line that aFrame is on. Set aMadeNewFrame to PR_TRUE if a
* new frame is created.
*/
nsresult
nsBlockFrame::CreateContinuationFor(nsBlockReflowState& aState,
nsLineBox* aLine,
nsIFrame* aFrame,
PRBool& aMadeNewFrame)
{
aMadeNewFrame = PR_FALSE;
nsresult rv;
nsIFrame* nextInFlow;
rv = CreateNextInFlow(*aState.mPresContext, this, aFrame, nextInFlow);
if (NS_FAILED(rv)) {
return rv;
}
if (nsnull != nextInFlow) {
aMadeNewFrame = PR_TRUE;
aLine->mChildCount++;
NS_ASSERTION(aLine->mChildCount < MAX_LINE_COUNT, "bad line child count");
}
#ifdef DEBUG
VerifyLines(PR_FALSE);
#endif
return rv;
}
nsresult
nsBlockFrame::SplitLine(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
nsLineBox* aLine,
nsIFrame* aFrame)
{
PRInt32 pushCount = aLine->ChildCount() - aLineLayout.GetCurrentSpanCount();
NS_ASSERTION(pushCount >= 0, "bad push count");
//printf("BEFORE (pushCount=%d):\n", pushCount);
//aLine->List(stdout, 0);
if (0 != pushCount) {
NS_ASSERTION(aLine->ChildCount() > pushCount, "bad push");
NS_ASSERTION(nsnull != aFrame, "whoops");
nsLineBox* to = aLine->mNext;
if (nsnull != to) {
// Only push into the next line if it's empty; otherwise we can
// end up pushing a frame which is continued into the same frame
// as it's continuation. This causes all sorts of bad side
// effects so we don't allow it.
if (0 != to->ChildCount()) {
nsLineBox* insertedLine = new nsLineBox(aFrame, pushCount, 0);
if (nsnull == insertedLine) {
return NS_ERROR_OUT_OF_MEMORY;
}
aLine->mNext = insertedLine;
insertedLine->mNext = to;
to = insertedLine;
} else {
to->mFirstChild = aFrame;
to->mChildCount = pushCount;
to->MarkDirty();
}
} else {
to = new nsLineBox(aFrame, pushCount, 0);
if (nsnull == to) {
return NS_ERROR_OUT_OF_MEMORY;
}
aLine->mNext = to;
}
to->SetIsBlock(aLine->IsBlock());
#ifdef BLOCK_DOES_FIRST_LINE
to->SetIsFirstLine(aLine->IsFirstLine());
#endif
aLine->mChildCount -= pushCount;
// Let line layout know that some frames are no longer part of its
// state.
if (!aLine->IsBlock()) {
aLineLayout.SplitLineTo(aLine->ChildCount());
}
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
}
return NS_OK;
}
PRBool
nsBlockFrame::ShouldJustifyLine(nsBlockReflowState& aState, nsLineBox* aLine)
{
nsLineBox* next = aLine->mNext;
while (nsnull != next) {
// There is another line
if (0 != next->ChildCount()) {
// If the next line is a block line then we must not justify
// this line because it means that this line is the last in a
// group of inline lines.
return !next->IsBlock();
}
// The next line is empty, try the next one
next = next->mNext;
}
// XXX Not sure about this part
// Try our next-in-flows lines to answer the question
nsBlockFrame* nextInFlow = (nsBlockFrame*) mNextInFlow;
while (nsnull != nextInFlow) {
nsLineBox* line = nextInFlow->mLines;
while (nsnull != line) {
if (0 != line->ChildCount()) {
return !line->IsBlock();
}
line = line->mNext;
}
nextInFlow = (nsBlockFrame*) nextInFlow->mNextInFlow;
}
// This is the last line - so don't allow justification
return PR_FALSE;
}
nsresult
nsBlockFrame::PlaceLine(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
nsLineBox* aLine,
PRBool* aKeepReflowGoing)
{
nsresult rv = NS_OK;
// Trim extra white-space from the line before placing the frames
aLineLayout.TrimTrailingWhiteSpace();
// Vertically align the frames on this line.
//
// 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: ... ).
//
// For this code, only the first case is possible because this
// method is used for placing a line of inline frames. If the rare
// case is happening then the worst that will happen is that the
// bullet frame will be reflowed twice.
PRBool addedBullet = PR_FALSE;
if (HaveOutsideBullet() && (aLine == mLines) &&
(!aLineLayout.IsZeroHeight() || !aLine->mNext)) {
nsHTMLReflowMetrics metrics(nsnull);
ReflowBullet(aState, metrics);
aLineLayout.AddBulletFrame(mBullet, metrics);
addedBullet = PR_TRUE;
}
nsSize maxElementSize;
aLineLayout.VerticalAlignFrames(aLine->mBounds, maxElementSize);
#ifdef DEBUG
{
static nscoord lastHeight = 0;
if (CRAZY_HEIGHT(aLine->mBounds.y)) {
lastHeight = aLine->mBounds.y;
if (abs(aLine->mBounds.y - lastHeight) > CRAZY_H/10) {
nsFrame::ListTag(stdout);
printf(": line=%p y=%d line.bounds.height=%d\n",
aLine, aLine->mBounds.y, aLine->mBounds.height);
}
}
else {
lastHeight = 0;
}
}
#endif
// Only block frames horizontally align their children because
// inline frames "shrink-wrap" around their children (therefore
// there is no extra horizontal space).
#if XXX_fix_me
PRBool allowJustify = PR_TRUE;
if (NS_STYLE_TEXT_ALIGN_JUSTIFY == aState.mStyleText->mTextAlign) {
allowJustify = ShouldJustifyLine(aState, aLine);
}
#else
PRBool allowJustify = PR_FALSE;
#endif
aLineLayout.HorizontalAlignFrames(aLine->mBounds, allowJustify);
aLineLayout.RelativePositionFrames(aLine->mCombinedArea);
if (addedBullet) {
aLineLayout.RemoveBulletFrame(mBullet);
}
// Inline lines do not have margins themselves; however they are
// impacted by prior block margins. If this line ends up having some
// height then we zero out the previous bottom margin value that was
// already applied to the line's starting Y coordinate. Otherwise we
// leave it be so that the previous blocks bottom margin can be
// collapsed with a block that follows.
nscoord newY;
if (aLine->mBounds.height > 0) {
// This line has some height. Therefore the application of the
// previous-bottom-margin should stick.
aState.mPrevBottomMargin = 0;
newY = aLine->mBounds.YMost();
}
else {
// Don't let the previous-bottom-margin value affect the newY
// coordinate (it was applied in ReflowInlineFrames speculatively)
// since the line is empty.
nscoord dy = -aState.mPrevBottomMargin;
newY = aState.mY + dy;
aLine->mCombinedArea.y += dy;
aLine->mBounds.y += dy;
}
aLine->mCarriedOutBottomMargin = 0;/* XXX_ib */
// See if the line fit. If it doesn't we need to push it. Our first
// line will always fit.
if ((mLines != aLine) && (newY > aState.mBottomEdge)) {
// Push this line and all of it's children and anything else that
// follows to our next-in-flow
PushLines(aState);
// Stop reflow and whack the reflow status if reflow hasn't
// already been stopped.
if (*aKeepReflowGoing) {
NS_ASSERTION(NS_FRAME_COMPLETE == aState.mReflowStatus,
"lost reflow status");
aState.mReflowStatus = NS_FRAME_NOT_COMPLETE;
*aKeepReflowGoing = PR_FALSE;
}
return rv;
}
aState.mY = newY;
if (aState.mComputeMaxElementSize) {
#ifdef NOISY_MAX_ELEMENT_SIZE
IndentBy(stdout, GetDepth());
if (NS_UNCONSTRAINEDSIZE == aState.mReflowState.availableWidth) {
printf("PASS1 ");
}
ListTag(stdout);
printf(": line.floaters=%s band.floaterCount=%d\n",
aLine->mFloaters.NotEmpty() ? "yes" : "no",
aState.mBand.GetFloaterCount());
#endif
if (0 != aState.mBand.GetFloaterCount()) {
// Add in floater impacts to the lines max-element-size
ComputeLineMaxElementSize(aState, aLine, &maxElementSize);
}
// Factor in any skipped right floaters...
if (aState.mSkippedRightFloaters) {
if (aState.mSkippedMaxElementSize.width > maxElementSize.width) {
maxElementSize.width = aState.mSkippedMaxElementSize.width;
}
if (aState.mSkippedMaxElementSize.height > maxElementSize.height) {
maxElementSize.height = aState.mSkippedMaxElementSize.height;
}
aState.mSkippedMaxElementSize.SizeTo(0, 0);
aState.mSkippedRightFloaters = PR_FALSE;
}
}
PostPlaceLine(aState, aLine, maxElementSize);
// Add the already placed current-line floaters to the line
aLine->mFloaters.Append(aState.mCurrentLineFloaters);
// Any below current line floaters to place?
if (aState.mBelowCurrentLineFloaters.NotEmpty()) {
// Reflow the below-current-line floaters, then add them to the
// lines floater list.
aState.PlaceBelowCurrentLineFloaters(aState.mBelowCurrentLineFloaters);
aLine->mFloaters.Append(aState.mBelowCurrentLineFloaters);
}
// When a line has floaters, factor them into the combined-area
// computations.
if (aLine->mFloaters.NotEmpty()) {
// Combine the floater combined area (stored in aState) and the
// value computed by the line layout code.
CombineRects(aState.mFloaterCombinedArea, aLine->mCombinedArea);
}
// Apply break-after clearing if necessary
switch (aLine->mBreakType) {
case NS_STYLE_CLEAR_LEFT:
case NS_STYLE_CLEAR_RIGHT:
case NS_STYLE_CLEAR_LEFT_AND_RIGHT:
aState.ClearFloaters(aState.mY, aLine->mBreakType);
break;
}
return rv;
}
// Compute the line's max-element-size by adding into the raw value
// computed by reflowing the contents of the line (aMaxElementSize)
// the impact of floaters on this line or the preceeding lines.
void
nsBlockFrame::ComputeLineMaxElementSize(nsBlockReflowState& aState,
nsLineBox* aLine,
nsSize* aMaxElementSize)
{
nscoord maxWidth, maxHeight;
aState.mBand.GetMaxElementSize(&maxWidth, &maxHeight);
#ifdef NOISY_MAX_ELEMENT_SIZE
IndentBy(stdout, GetDepth());
if (NS_UNCONSTRAINEDSIZE == aState.mReflowState.availableWidth) {
printf("PASS1 ");
}
ListTag(stdout);
printf(": maxFloaterSize=%d,%d\n", maxWidth, maxHeight);
#endif
// If the floaters are wider than the content, then use the maximum
// floater width as the maximum width.
//
// It used to be the case that we would always place some content
// next to a floater, regardless of the amount of available space
// after subtracing off the floaters sizes. This can lead to content
// overlapping floaters, so we no longer do this (and pass CSS2's
// conformance tests). This is not how navigator 4-1 used to do
// things.
if (maxWidth > aMaxElementSize->width) {
aMaxElementSize->width = maxWidth;
}
// Only update the max-element-size's height value if the floater is
// part of the current line.
if (aLine->mFloaters.NotEmpty()) {
// If the maximum-height of the tallest floater is larger than the
// maximum-height of the content then update the max-element-size
// height
if (maxHeight > aMaxElementSize->height) {
aMaxElementSize->height = maxHeight;
}
}
}
void
nsBlockFrame::PostPlaceLine(nsBlockReflowState& aState,
nsLineBox* aLine,
const nsSize& aMaxElementSize)
{
// Update max-element-size
if (aState.mComputeMaxElementSize) {
aState.UpdateMaxElementSize(aMaxElementSize);
// We also cache the max element width in the line. This is needed for
// incremental reflow
aLine->mMaxElementWidth = aMaxElementSize.width;
}
// Update xmost
nscoord xmost = aLine->mBounds.XMost();
#ifdef DEBUG
if (CRAZY_WIDTH(xmost)) {
ListTag(stdout);
printf(": line=%p xmost=%d\n", aLine, xmost);
}
#endif
if (xmost > aState.mKidXMost) {
aState.mKidXMost = xmost;
}
}
void
nsBlockFrame::PushLines(nsBlockReflowState& aState)
{
NS_ASSERTION(nsnull != aState.mPrevLine, "bad push");
nsLineBox* lastLine = aState.mPrevLine;
nsLineBox* nextLine = lastLine->mNext;
lastLine->mNext = nsnull;
mOverflowLines = nextLine;
// Mark all the overflow lines dirty so that they get reflowed when
// they are pulled up by our next-in-flow.
while (nsnull != nextLine) {
nextLine->MarkDirty();
nextLine = nextLine->mNext;
}
// Break frame sibling list
nsIFrame* lastFrame = lastLine->LastChild();
lastFrame->SetNextSibling(nsnull);
#ifdef DEBUG
VerifyOverflowSituation();
#endif
}
PRBool
nsBlockFrame::DrainOverflowLines()
{
#ifdef DEBUG
VerifyOverflowSituation();
#endif
PRBool drained = PR_FALSE;
// First grab the prev-in-flows overflow lines
nsBlockFrame* prevBlock = (nsBlockFrame*) mPrevInFlow;
if (nsnull != prevBlock) {
nsLineBox* line = prevBlock->mOverflowLines;
if (nsnull != line) {
drained = PR_TRUE;
prevBlock->mOverflowLines = nsnull;
// Make all the frames on the mOverflowLines list mine
nsIFrame* lastFrame = nsnull;
nsIFrame* frame = line->mFirstChild;
while (nsnull != frame) {
frame->SetParent(this);
// When pushing and pulling frames we need to check for whether any
// views need to be reparented
nsHTMLContainerFrame::ReparentFrameView(frame, prevBlock, this);
// Get the next frame
lastFrame = frame;
frame->GetNextSibling(&frame);
}
// Join the line lists
if (nsnull == mLines) {
mLines = line;
}
else {
// Join the sibling lists together
lastFrame->SetNextSibling(mLines->mFirstChild);
// Place overflow lines at the front of our line list
nsLineBox* lastLine = nsLineBox::LastLine(line);
lastLine->mNext = mLines;
mLines = line;
}
}
}
// Now grab our own overflow lines
if (nsnull != mOverflowLines) {
// This can happen when we reflow and not everything fits and then
// we are told to reflow again before a next-in-flow is created
// and reflows.
nsLineBox* lastLine = nsLineBox::LastLine(mLines);
if (nsnull == lastLine) {
mLines = mOverflowLines;
}
else {
lastLine->mNext = mOverflowLines;
nsIFrame* lastFrame = lastLine->LastChild();
lastFrame->SetNextSibling(mOverflowLines->mFirstChild);
}
mOverflowLines = nsnull;
drained = PR_TRUE;
}
return drained;
}
//////////////////////////////////////////////////////////////////////
// Frame list manipulation routines
nsIFrame*
nsBlockFrame::LastChild()
{
if (mLines) {
nsLineBox* line = nsLineBox::LastLine(mLines);
return line->LastChild();
}
return nsnull;
}
#ifdef BLOCK_DOES_FIRST_LINE
nsresult
nsBlockFrame::WrapFramesInFirstLineFrame(nsIPresContext* aPresContext)
{
nsLineBox* line = mLines;
if (!line) {
return NS_OK;
}
// Find the first and last inline line that has frames that aren't
// yet in the first-line-frame.
nsLineBox* prevLine = nsnull;
nsIFrame* firstInlineFrame = nsnull;
nsFirstLineFrame* lineFrame = nsnull;
nsIFrame* nextSib = nsnull;
nsresult rv = NS_OK;
while (line) {
if (line->IsBlock()) {
nextSib = line->mFirstChild;
break;
}
if (line->IsFirstLine()) {
NS_ASSERTION(1 == line->mChildCount, "bad first line");
lineFrame = (nsFirstLineFrame*) line->mFirstChild;
prevLine = line;
line = line->mNext;
}
else {
if (!firstInlineFrame) {
firstInlineFrame = line->mFirstChild;
}
if (prevLine) {
prevLine->mNext = line->mNext;
delete line;
line = prevLine->mNext;
}
else {
prevLine = line;
line = line->mNext;
}
}
}
if (!firstInlineFrame) {
// All of the inline frames are already where they should be
return NS_OK;
}
// Make the last inline frame thats going into the first-line-frame
// have no next sibling.
if (line) {
nsFrameList frames(firstInlineFrame);
nsIFrame* lastInlineFrame = frames.GetPrevSiblingFor(line->mFirstChild);
lastInlineFrame->SetNextSibling(nsnull);
}
// If there is no first-line-frame currently, we create it
if (!lineFrame) {
nsIStyleContext* firstLineStyle = GetFirstLineStyle(aPresContext);
// Create line frame
rv = NS_NewFirstLineFrame((nsIFrame**) &lineFrame);
if (NS_FAILED(rv)) {
return rv;
}
rv = lineFrame->Init(*aPresContext, mContent, this,
firstLineStyle, nsnull);
if (NS_FAILED(rv)) {
return rv;
}
line = mLines;
line->mFirstChild = lineFrame;
line->mChildCount = 1;
line->SetIsFirstLine(PR_TRUE);
NS_RELEASE(firstLineStyle);
}
// Connect last first-line-frame to the remaining frames
lineFrame->SetNextSibling(nextSib);
// Put the new children into the line-frame
lineFrame->AppendFrames2(aPresContext, firstInlineFrame);
// Mark the first-line frames dirty
line = mLines;
while (line && line->IsFirstLine()) {
line->MarkDirty();
line = line->mNext;
}
return rv;
}
#endif
NS_IMETHODIMP
nsBlockFrame::AppendFrames(nsIPresContext& aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aFrameList)
{
if (nsnull == aFrameList) {
return NS_OK;
}
if (nsLayoutAtoms::floaterList == aListName) {
// XXX we don't *really* care about this right now because we are
// BuildFloaterList ing still
mFloaters.AppendFrames(nsnull, aFrameList);
return NS_OK;
}
else if (nsnull != aListName) {
return NS_ERROR_INVALID_ARG;
}
// Find the proper last-child for where the append should go
nsIFrame* lastKid = nsnull;
nsLineBox* lastLine = nsLineBox::LastLine(mLines);
if (lastLine) {
lastKid = lastLine->LastChild();
#ifdef BLOCK_DOES_FIRST_LINE
if (lastLine->IsFirstLine()) {
// Get last frame in the nsFirstLineFrame
lastKid->FirstChild(nsnull, &lastKid);
nsFrameList frames(lastKid);
lastKid = frames.LastChild();
}
#endif
}
// Add frames after the last child
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
printf(": append ");
nsFrame::ListTag(stdout, aFrameList);
if (lastKid) {
printf(" after ");
nsFrame::ListTag(stdout, lastKid);
}
printf("\n");
#endif
nsresult rv = AddFrames(&aPresContext, aFrameList, lastKid);
if (NS_SUCCEEDED(rv)) {
// Generate reflow command to reflow the dirty lines
nsIReflowCommand* reflowCmd = nsnull;
rv = NS_NewHTMLReflowCommand(&reflowCmd, this,
nsIReflowCommand::ReflowDirty,
nsnull);
if (NS_SUCCEEDED(rv)) {
if (nsnull != aListName) {
reflowCmd->SetChildListName(aListName);
}
aPresShell.AppendReflowCommand(reflowCmd);
NS_RELEASE(reflowCmd);
}
}
return rv;
}
NS_IMETHODIMP
nsBlockFrame::InsertFrames(nsIPresContext& aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aPrevFrame,
nsIFrame* aFrameList)
{
if (nsLayoutAtoms::floaterList == aListName) {
// XXX we don't *really* care about this right now because we are
// BuildFloaterList'ing still
mFloaters.AppendFrames(nsnull, aFrameList);
return NS_OK;
}
else if (nsnull != aListName) {
return NS_ERROR_INVALID_ARG;
}
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
printf(": insert ");
nsFrame::ListTag(stdout, aFrameList);
if (aPrevFrame) {
printf(" after ");
nsFrame::ListTag(stdout, aPrevFrame);
}
printf("\n");
#endif
nsresult rv = AddFrames(&aPresContext, aFrameList, aPrevFrame);
if (NS_SUCCEEDED(rv)) {
// Generate reflow command to reflow the dirty lines
nsIReflowCommand* reflowCmd = nsnull;
rv = NS_NewHTMLReflowCommand(&reflowCmd, this,
nsIReflowCommand::ReflowDirty,
nsnull);
if (NS_SUCCEEDED(rv)) {
if (nsnull != aListName) {
reflowCmd->SetChildListName(aListName);
}
aPresShell.AppendReflowCommand(reflowCmd);
NS_RELEASE(reflowCmd);
}
}
return rv;
}
nsresult
nsBlockFrame::AddFrames(nsIPresContext* aPresContext,
nsIFrame* aFrameList,
nsIFrame* aPrevSibling)
{
if (nsnull == aFrameList) {
return NS_OK;
}
// Attempt to find the line that contains the previous sibling
nsLineBox* prevSibLine = nsnull;
PRInt32 prevSiblingIndex = -1;
if (aPrevSibling) {
#ifdef BLOCK_DOES_FIRST_LINE
// Its possible we have an nsFirstLineFrame managing some of our
// child frames. If we do and the AddFrames is targetted at it,
// use AddFirstLineFrames to get the frames properly placed.
nsIFrame* prevSiblingParent;
aPrevSibling->GetParent(&prevSiblingParent);
if (prevSiblingParent != this) {
// We are attempting an insert into a nsFirstLineFrame. Mark the
// first-line's dirty. Not exactly optimial, but it will
// guarantee a correct reflow.
nsLineBox* line = mLines;
while (line) {
if (!line->IsFirstLine()) {
break;
}
line->MarkDirty();
line = line->mNext;
}
return AddFirstLineFrames(aPresContext,
(nsFirstLineFrame*)prevSiblingParent,
aFrameList, aPrevSibling);
}
else
#endif
{
// Find the line that contains the previous sibling
prevSibLine = nsLineBox::FindLineContaining(mLines, aPrevSibling,
&prevSiblingIndex);
NS_ASSERTION(nsnull != prevSibLine, "prev sibling not in line list");
if (nsnull == prevSibLine) {
// Note: defensive code! FindLineContaining must not return
// null in this case, so if it does...
aPrevSibling = nsnull;
}
}
}
#ifdef BLOCK_DOES_FIRST_LINE
else if (mLines && mLines->IsFirstLine()) {
mLines->MarkDirty();
return AddFirstLineFrames(aPresContext,
(nsFirstLineFrame*)mLines->mFirstChild,
aFrameList, nsnull);
}
#endif
// Find the frame following aPrevSibling so that we can join up the
// two lists of frames.
nsIFrame* prevSiblingNextFrame = nsnull;
if (aPrevSibling) {
aPrevSibling->GetNextSibling(&prevSiblingNextFrame);
// Split line containing aPrevSibling in two if the insertion
// point is somewhere in the middle of the line.
PRInt32 rem = prevSibLine->mChildCount - prevSiblingIndex - 1;
if (rem) {
// Split the line in two where the frame(s) are being inserted.
nsLineBox* line = new nsLineBox(prevSiblingNextFrame, rem, 0);
if (!line) {
return NS_ERROR_OUT_OF_MEMORY;
}
line->mNext = prevSibLine->mNext;
prevSibLine->mNext = line;
prevSibLine->mChildCount -= rem;
prevSibLine->MarkDirty();
}
// Now (partially) join the sibling lists together
aPrevSibling->SetNextSibling(aFrameList);
}
else if (mLines) {
prevSiblingNextFrame = mLines->mFirstChild;
}
// Walk through the new frames being added and update the line data
// structures to fit.
nsIFrame* newFrame = aFrameList;
while (newFrame) {
PRUint32 isBlock = nsLineLayout::TreatFrameAsBlock(newFrame)
? LINE_IS_BLOCK
: 0;
// If the frame is a block frame, or if there is no previous line
// or if the previous line is a block line then make a new line.
if (isBlock || !prevSibLine || prevSibLine->IsBlock()) {
// Create a new line for the frame and add its line to the line
// list.
nsLineBox* line = new nsLineBox(newFrame, 1, isBlock);
if (!line) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (prevSibLine) {
// Append new line after prevSibLine
line->mNext = prevSibLine->mNext;
prevSibLine->mNext = line;
}
else {
// New line is going before the other lines
line->mNext = mLines;
mLines = line;
}
prevSibLine = line;
}
else {
prevSibLine->mChildCount++;
prevSibLine->MarkDirty();
NS_ASSERTION(prevSibLine->mChildCount < MAX_LINE_COUNT, "bad line child count");
}
aPrevSibling = newFrame;
newFrame->GetNextSibling(&newFrame);
}
if (prevSiblingNextFrame) {
// Connect the last new frame to the remainder of the sibling list
aPrevSibling->SetNextSibling(prevSiblingNextFrame);
}
#ifdef BLOCK_DOES_FIRST_LINE
// Fixup any frames that should be in a first-line frame but aren't
if ((NS_BLOCK_HAS_FIRST_LINE_STYLE & mState) &&
(nsnull != mLines) && !mLines->IsBlock()) {
// We just added one or more frame(s) to the first line.
WrapFramesInFirstLineFrame(aPresContext);
}
#endif
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
return NS_OK;
}
#ifdef BLOCK_DOES_FIRST_LINE
nsresult
nsBlockFrame::AddFirstLineFrames(nsIPresContext* aPresContext,
nsFirstLineFrame* aLineFrame,
nsIFrame* aFrameList,
nsIFrame* aPrevSibling)
{
// The first run of inline frames being added always go into the
// line frame. If we hit a block frame then we need to chop the line
// frame into two pieces.
nsIFrame* frame = aFrameList;
nsIFrame* lastAddedFrame = frame;
nsIFrame* firstInlineFrame = nsnull;
PRInt32 pendingInlines = 0;
while (frame) {
if (nsLineLayout::TreatFrameAsBlock(frame)) {
// There are 3 cases. The block is going to the front of the
// line-frames children, in the middle or at the end.
if (aPrevSibling) {
nsIFrame* next;
aPrevSibling->GetNextSibling(&next);
// Take kids from the line frame starting at next.
nsIFrame* kids;
if (nsnull == next) {
// The block goes in front of aLineFrame's continuation, if
// it has one.
nsFirstLineFrame* nextLineFrame;
aLineFrame->GetNextInFlow((nsIFrame**) &nextLineFrame);
if (!nextLineFrame) {
// No continuation, therefore the block goes after aLineFrame
FixParentAndView(aPresContext, frame);
return AddFrames(aPresContext, frame, aLineFrame);
}
// Use nextLineFrame and take away all its kids
aLineFrame = nextLineFrame;
}
kids = TakeKidsFromLineFrame(aLineFrame, next);
// We will leave the line-frame and its continuations in
// place but mark the lines dirty so that they are reflowed
// and the empty line-frames (if any) are cleaned up.
nsLineBox* line = mLines;
nsIFrame* lastLineFrame = aLineFrame;
while (line && line->IsFirstLine()) {
line->MarkDirty();
lastLineFrame = line->mFirstChild;
line = line->mNext;
}
// Join the taken kids onto the *end* of the frames being
// added.
nsFrameList newFrames(frame);
newFrames.AppendFrames(this, kids);
FixParentAndView(aPresContext, frame);
return AddFrames(aPresContext, frame, lastLineFrame);
}
else {
// Block is trying to go to the front of the line frame (and
// therefore in front of all the line-frames children and its
// continuations children). Therefore, we don't need a line
// frame anymore.
nsIFrame* kids = TakeKidsFromLineFrame(aLineFrame, nsnull);
// Join the kids onto the end of the frames being added
nsFrameList newFrames(frame);
newFrames.AppendFrames(this, kids);
FixParentAndView(aPresContext, newFrames.FirstChild());
// Remove the line frame (and its continuations). This also
// removes the nsLineBox's that pointed to the line-frame. Do
// this after FixParentAndView because FixParentAndView needs
// a valid old-parent to work.
DoRemoveFrame(aPresContext, aLineFrame);
// Re-enter AddFrames, this time there won't be any first-line
// frames so we will use the normal path.
return AddFrames(aPresContext, newFrames.FirstChild(), nsnull);
}
}
else {
if (0 == pendingInlines) {
firstInlineFrame = frame;
}
pendingInlines++;
}
lastAddedFrame = frame;
frame->GetNextSibling(&frame);
}
// All of the frames being added are inline frames
if (pendingInlines) {
return aLineFrame->InsertFrames2(aPresContext, aPrevSibling, aFrameList);
}
return NS_OK;
}
nsIFrame*
nsBlockFrame::TakeKidsFromLineFrame(nsFirstLineFrame* aLineFrame,
nsIFrame* aFromKid)
{
nsFrameList kids;
nsIFrame* lastKid;
if (aFromKid) {
kids.SetFrames(aFromKid);
aLineFrame->RemoveFramesFrom(aFromKid);
}
else {
aLineFrame->FirstChild(nsnull, &lastKid);
kids.SetFrames(lastKid);
aLineFrame->RemoveAllFrames();
}
lastKid = kids.LastChild();
// Capture the next-in-flows kids as well.
for (;;) {
aLineFrame->GetNextInFlow((nsIFrame**) &aLineFrame);
if (!aLineFrame) {
break;
}
aLineFrame->FirstChild(nsnull, &aFromKid);
aLineFrame->RemoveAllFrames();
lastKid->SetNextSibling(aFromKid);
nsFrameList tmp(aFromKid);
lastKid = tmp.LastChild();
}
return kids.FirstChild();
}
#endif
void
nsBlockFrame::FixParentAndView(nsIPresContext* aPresContext, nsIFrame* aFrame)
{
while (aFrame) {
nsIFrame* oldParent;
aFrame->GetParent(&oldParent);
aFrame->SetParent(this);
if (this != oldParent) {
nsHTMLContainerFrame::ReparentFrameView(aFrame, oldParent, this);
aPresContext->ReParentStyleContext(aFrame, mStyleContext);
}
aFrame->GetNextSibling(&aFrame);
}
}
NS_IMETHODIMP
nsBlockFrame::RemoveFrame(nsIPresContext& aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aOldFrame)
{
nsresult rv = NS_OK;
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
printf(": remove ");
nsFrame::ListTag(stdout, aOldFrame);
printf("\n");
#endif
if (nsLayoutAtoms::floaterList == aListName) {
// Remove floater from the floater list first
mFloaters.RemoveFrame(aOldFrame);
// Find which line contains the floater
nsLineBox* line = mLines;
while (nsnull != line) {
nsFloaterCache* fc = line->mFloaters.Find(aOldFrame);
if (fc) {
// Note: the placeholder is part of the line's child list
// and will be removed later.
fc->mPlaceholder->SetOutOfFlowFrame(nsnull);
line->mFloaters.Remove(fc);
aOldFrame->Destroy(aPresContext);
goto found_it;
}
line = line->mNext;
}
found_it:
// Mark every line at and below the line where the floater was dirty
while (nsnull != line) {
line->MarkDirty();
line = line->mNext;
}
}
else if (nsnull != aListName) {
rv = NS_ERROR_INVALID_ARG;
}
else {
rv = DoRemoveFrame(&aPresContext, aOldFrame);
}
if (NS_SUCCEEDED(rv)) {
// Generate reflow command to reflow the dirty lines
nsIReflowCommand* reflowCmd = nsnull;
rv = NS_NewHTMLReflowCommand(&reflowCmd, this,
nsIReflowCommand::ReflowDirty,
nsnull);
if (NS_SUCCEEDED(rv)) {
if (nsnull != aListName) {
reflowCmd->SetChildListName(aListName);
}
aPresShell.AppendReflowCommand(reflowCmd);
NS_RELEASE(reflowCmd);
}
}
return rv;
}
nsresult
nsBlockFrame::DoRemoveFrame(nsIPresContext* aPresContext,
nsIFrame* aDeletedFrame)
{
#ifdef BLOCK_DOES_FIRST_LINE
nsIFrame* parent;
aDeletedFrame->GetParent(&parent);
if (parent != this) {
return RemoveFirstLineFrame(aPresContext, (nsFirstLineFrame*)parent,
aDeletedFrame);
}
#endif
// Find the line and the previous sibling that contains
// deletedFrame; we also find the pointer to the line.
nsBlockFrame* flow = this;
nsLineBox** linep = &flow->mLines;
nsLineBox* line = flow->mLines;
nsLineBox* prevLine = nsnull;
nsIFrame* prevSibling = nsnull;
while (nsnull != line) {
nsIFrame* frame = line->mFirstChild;
PRInt32 n = line->ChildCount();
while (--n >= 0) {
if (frame == aDeletedFrame) {
goto found_frame;
}
prevSibling = frame;
frame->GetNextSibling(&frame);
}
linep = &line->mNext;
prevLine = line;
line = line->mNext;
}
found_frame:;
#ifdef NS_DEBUG
NS_ASSERTION(nsnull != line, "can't find deleted frame in lines");
if (nsnull != prevSibling) {
nsIFrame* tmp;
prevSibling->GetNextSibling(&tmp);
NS_ASSERTION(tmp == aDeletedFrame, "bad prevSibling");
}
#endif
// Remove frame and all of its continuations
while (nsnull != aDeletedFrame) {
while ((nsnull != line) && (nsnull != aDeletedFrame)) {
#ifdef NS_DEBUG
#ifndef BLOCK_DOES_FIRST_LINE
nsIFrame* parent;
#endif
aDeletedFrame->GetParent(&parent);
NS_ASSERTION(flow == parent, "messed up delete code");
NS_ASSERTION(line->Contains(aDeletedFrame), "frame not in line");
#endif
// See if the frame being deleted is the last one on the line
PRBool isLastFrameOnLine = PR_FALSE;
if (1 == line->mChildCount) {
isLastFrameOnLine = PR_TRUE;
}
else if (line->LastChild() == aDeletedFrame) {
isLastFrameOnLine = PR_TRUE;
}
// Remove aDeletedFrame from the line
nsIFrame* nextFrame;
aDeletedFrame->GetNextSibling(&nextFrame);
if (line->mFirstChild == aDeletedFrame) {
line->mFirstChild = nextFrame;
}
if (prevLine && !prevLine->IsBlock()) {
// Since we just removed a frame that follows some inline
// frames, we need to reflow the previous line.
prevLine->MarkDirty();
}
// Take aDeletedFrame out of the sibling list. Note that
// prevSibling will only be nsnull when we are deleting the very
// first frame.
if (nsnull != prevSibling) {
prevSibling->SetNextSibling(nextFrame);
}
// Destroy frame; capture its next-in-flow first in case we need
// to destroy that too.
nsIFrame* nextInFlow;
aDeletedFrame->GetNextInFlow(&nextInFlow);
nsSplittableType st;
aDeletedFrame->IsSplittable(st);
if (NS_FRAME_NOT_SPLITTABLE != st) {
aDeletedFrame->RemoveFromFlow();
}
#ifdef NOISY_REMOVE_FRAME
printf("DoRemoveFrame: prevLine=%p line=%p frame=",
prevLine, line);
nsFrame::ListTag(stdout, aDeletedFrame);
printf(" prevSibling=%p nextInFlow=%p\n", prevSibling, nextInFlow);
#endif
aDeletedFrame->Destroy(*aPresContext);
aDeletedFrame = nextInFlow;
// If line is empty, remove it now
nsLineBox* next = line->mNext;
if (0 == --line->mChildCount) {
*linep = next;
line->mNext = nsnull;
// Invalidate the space taken up by the line.
// XXX We need to do this if we're removing a frame as a result of
// a call to RemoveFrame(), but we may not need to do this in all
// cases...
Invalidate(line->mCombinedArea);
delete line;
line = next;
}
else {
NS_ASSERTION(line->mChildCount < MAX_LINE_COUNT, "bad line count");
// Make the line that just lost a frame dirty
line->MarkDirty();
// If we just removed the last frame on the line then we need
// to advance to the next line.
if (isLastFrameOnLine) {
prevLine = line;
linep = &line->mNext;
line = next;
}
}
// See if we should keep looking in the current flow's line list.
if (nsnull != aDeletedFrame) {
if (aDeletedFrame != nextFrame) {
// The deceased frames continuation is not the next frame in
// the current flow's frame list. Therefore we know that the
// continuation is in a different parent. So break out of
// the loop so that we advance to the next parent.
#ifdef NS_DEBUG
nsIFrame* checkParent;
aDeletedFrame->GetParent(&checkParent);
NS_ASSERTION(checkParent != flow, "strange continuation");
#endif
break;
}
}
}
// Advance to next flow block if the frame has more continuations
if (nsnull != aDeletedFrame) {
flow = (nsBlockFrame*) flow->mNextInFlow;
NS_ASSERTION(nsnull != flow, "whoops, continuation without a parent");
prevLine = nsnull;
line = flow->mLines;
linep = &flow->mLines;
prevSibling = nsnull;
}
}
#ifdef BLOCK_DOES_FIRST_LINE
// Fixup any frames that should be in a first-line frame but aren't
if ((NS_BLOCK_HAS_FIRST_LINE_STYLE & mState) &&
(nsnull != mLines) && !mLines->IsBlock()) {
// We just added one or more frame(s) to the first line, or we
// removed a block that preceeded the first line.
WrapFramesInFirstLineFrame(aPresContext);
}
#endif
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
return NS_OK;
}
#ifdef BLOCK_DOES_FIRST_LINE
nsresult
nsBlockFrame::RemoveFirstLineFrame(nsIPresContext* aPresContext,
nsFirstLineFrame* aLineFrame,
nsIFrame* aDeletedFrame)
{
// Strip deleted frame out of the nsFirstLineFrame
aLineFrame->RemoveFrame2(aPresContext, aDeletedFrame);
aDeletedFrame->Destroy(*aPresContext);
// See if the line-frame and its continuations are now empty
nsFirstLineFrame* lf = (nsFirstLineFrame*) aLineFrame->GetFirstInFlow();
nsFirstLineFrame* lf0 = lf;
PRBool empty = PR_TRUE;
while (lf) {
nsIFrame* kids;
lf->FirstChild(nsnull, &kids);
if (kids) {
empty = PR_FALSE;
break;
}
lf->GetNextInFlow((nsIFrame**) &lf);
}
if (empty) {
return DoRemoveFrame(aPresContext, lf0);
}
// Mark first-line lines dirty
nsLineBox* line = mLines;
while (line && line->IsFirstLine()) {
line->MarkDirty();
line = line->mNext;
}
return NS_OK;
}
#endif
void
nsBlockFrame::DeleteChildsNextInFlow(nsIPresContext& aPresContext,
nsIFrame* aChild)
{
NS_PRECONDITION(IsChild(aChild), "bad geometric parent");
nsIFrame* nextInFlow;
aChild->GetNextInFlow(&nextInFlow);
NS_PRECONDITION(nsnull != nextInFlow, "null next-in-flow");
nsBlockFrame* parent;
nextInFlow->GetParent((nsIFrame**)&parent);
NS_PRECONDITION(nsnull != parent, "next-in-flow with no parent");
NS_PRECONDITION(nsnull != parent->mLines, "next-in-flow with weird parent");
NS_PRECONDITION(nsnull == parent->mOverflowLines, "parent with overflow");
parent->DoRemoveFrame(&aPresContext, nextInFlow);
}
////////////////////////////////////////////////////////////////////////
// Floater support
nsresult
nsBlockFrame::ReflowFloater(nsBlockReflowState& aState,
nsPlaceholderFrame* aPlaceholder,
nsRect& aCombinedRect,
nsMargin& aMarginResult,
nsMargin& aComputedOffsetsResult)
{
// XXX update this just
aState.GetAvailableSpace();
// Reflow the floater. Since floaters are continued we given them an
// unbounded height. Floaters with an auto width are sized to zero
// according to the css2 spec.
nsRect availSpace(0, 0, aState.mAvailSpaceRect.width, NS_UNCONSTRAINEDSIZE);
nsIFrame* floater = aPlaceholder->GetOutOfFlowFrame();
PRBool isAdjacentWithTop = aState.IsAdjacentWithTop();
// Setup block reflow state to reflow the floater
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowState,
aState.mComputeMaxElementSize);
brc.SetNextRCFrame(aState.mNextRCFrame);
// Reflow the floater
nsReflowStatus frameReflowStatus;
nsresult rv = brc.ReflowBlock(floater, availSpace, PR_TRUE,
0, isAdjacentWithTop,
aComputedOffsetsResult, frameReflowStatus);
if (NS_FAILED(rv)) {
return rv;
}
// Capture the margin information for the caller
const nsMargin& m = brc.GetMargin();
aMarginResult.top = brc.GetTopMargin();
aMarginResult.right = m.right;
aMarginResult.bottom =
nsBlockReflowContext::MaxMargin(brc.GetCarriedOutBottomMargin(),
m.bottom);
aMarginResult.left = m.left;
const nsHTMLReflowMetrics& metrics = brc.GetMetrics();
aCombinedRect = metrics.mCombinedArea;
floater->SizeTo(metrics.width, metrics.height);
return NS_OK;
}
void
nsBlockReflowState::InitFloater(nsLineLayout& aLineLayout,
nsPlaceholderFrame* aPlaceholder)
{
// Set the geometric parent of the floater
nsIFrame* floater = aPlaceholder->GetOutOfFlowFrame();
floater->SetParent(mBlock);
// Then add the floater to the current line and place it when
// appropriate
AddFloater(aLineLayout, aPlaceholder, PR_TRUE);
}
// This is called by the line layout's AddFloater method when a
// place-holder frame is reflowed in a line. If the floater is a
// left-most child (it's x coordinate is at the line's left margin)
// then the floater is place immediately, otherwise the floater
// placement is deferred until the line has been reflowed.
void
nsBlockReflowState::AddFloater(nsLineLayout& aLineLayout,
nsPlaceholderFrame* aPlaceholder,
PRBool aInitialReflow)
{
NS_PRECONDITION(nsnull != mCurrentLine, "null ptr");
// Allocate a nsFloaterCache for the floater
nsFloaterCache* fc = mFloaterCacheFreeList.Alloc();
fc->mPlaceholder = aPlaceholder;
fc->mIsCurrentLineFloater = aLineLayout.CanPlaceFloaterNow();
// Now place the floater immediately if possible. Otherwise stash it
// away in mPendingFloaters and place it later.
if (fc->mIsCurrentLineFloater) {
// Record this floater in the current-line list
mCurrentLineFloaters.Append(fc);
// Because we are in the middle of reflowing a placeholder frame
// within a line (and possibly nested in an inline frame or two
// that's a child of our block) we need to restore the space
// manager's translation to the space that the block resides in
// before placing the floater.
nscoord ox, oy;
mSpaceManager->GetTranslation(ox, oy);
nscoord dx = ox - mSpaceManagerX;
nscoord dy = oy - mSpaceManagerY;
mSpaceManager->Translate(-dx, -dy);
// Reflow the floater
mBlock->ReflowFloater(*this, aPlaceholder, fc->mCombinedArea,
fc->mMargins, fc->mOffsets);
// And then place it
PRBool isLeftFloater;
nsPoint origin;
PlaceFloater(fc, &isLeftFloater, &origin);
// Update the floater combined-area
nsRect combinedArea = fc->mCombinedArea;
combinedArea.x += origin.x;
combinedArea.y += origin.y;
CombineRects(combinedArea, mFloaterCombinedArea);
// Pass on updated available space to the current inline reflow engine
GetAvailableSpace();
aLineLayout.UpdateBand(mAvailSpaceRect.x + BorderPadding().left, mY,
mAvailSpaceRect.width,
mAvailSpaceRect.height,
isLeftFloater);
// Restore coordinate system
mSpaceManager->Translate(dx, dy);
}
else {
// This floater will be placed after the line is done (it is a
// below-current-line floater).
mBelowCurrentLineFloaters.Append(fc);
}
}
PRBool
nsBlockReflowState::IsLeftMostChild(nsIFrame* aFrame)
{
for (;;) {
nsIFrame* parent;
aFrame->GetParent(&parent);
if (parent == mBlock) {
nsIFrame* child = mCurrentLine->mFirstChild;
PRInt32 n = mCurrentLine->ChildCount();
while ((nsnull != child) && (aFrame != child) && (--n >= 0)) {
nsSize size;
// Is the child zero-sized?
child->GetSize(size);
if (size.width > 0) {
// We found a non-zero sized child frame that precedes aFrame
return PR_FALSE;
}
child->GetNextSibling(&child);
}
break;
}
else {
// See if there are any non-zero sized child frames that precede
// aFrame in the child list
nsIFrame* child;
parent->FirstChild(nsnull, &child);
while ((nsnull != child) && (aFrame != child)) {
nsSize size;
// Is the child zero-sized?
child->GetSize(size);
if (size.width > 0) {
// We found a non-zero sized child frame that precedes aFrame
return PR_FALSE;
}
child->GetNextSibling(&child);
}
}
// aFrame is the left-most non-zero sized frame in its geometric parent.
// Walk up one level and check that its parent is left-most as well
aFrame = parent;
}
return PR_TRUE;
}
PRBool
nsBlockReflowState::CanPlaceFloater(const nsRect& aFloaterRect,
PRUint8 aFloats)
{
// If the current Y coordinate is not impacted by any floaters
// then by definition the floater fits.
PRBool result = PR_TRUE;
if (0 != mBand.GetFloaterCount()) {
if (mAvailSpaceRect.width < aFloaterRect.width) {
// The available width is too narrow (and its been impacted by a
// prior floater)
result = PR_FALSE;
}
else {
// At this point we know that there is enough horizontal space for
// the floater (somewhere). Lets see if there is enough vertical
// space.
if (mAvailSpaceRect.height < aFloaterRect.height) {
// The available height is too short. However, its possible that
// there is enough open space below which is not impacted by a
// floater.
//
// Compute the X coordinate for the floater based on its float
// type, assuming its placed on the current line. This is
// where the floater will be placed horizontally if it can go
// here.
nscoord xa;
if (NS_STYLE_FLOAT_LEFT == aFloats) {
xa = mAvailSpaceRect.x;
}
else {
xa = mAvailSpaceRect.XMost() - aFloaterRect.width;
// In case the floater is too big, don't go past the left edge
if (xa < mAvailSpaceRect.x) {
xa = mAvailSpaceRect.x;
}
}
nscoord xb = xa + aFloaterRect.width;
// Calculate the top and bottom y coordinates, again assuming
// that the floater is placed on the current line.
const nsMargin& borderPadding = BorderPadding();
nscoord ya = mY - borderPadding.top;
if (ya < 0) {
// CSS2 spec, 9.5.1 rule [4]: A floating box's outer top may not
// be higher than the top of its containing block.
// XXX It's not clear if it means the higher than the outer edge
// or the border edge or the inner edge?
ya = 0;
}
nscoord yb = ya + aFloaterRect.height;
nscoord saveY = mY;
for (;;) {
// Get the available space at the new Y coordinate
mY += mAvailSpaceRect.height;
GetAvailableSpace();
if (0 == mBand.GetFloaterCount()) {
// Winner. This band has no floaters on it, therefore
// there can be no overlap.
break;
}
// Check and make sure the floater won't intersect any
// floaters on this band. The floaters starting and ending
// coordinates must be entirely in the available space.
if ((xa < mAvailSpaceRect.x) || (xb > mAvailSpaceRect.XMost())) {
// The floater can't go here.
result = PR_FALSE;
break;
}
// See if there is now enough height for the floater.
if (yb < mY + mAvailSpaceRect.height) {
// Winner. The bottom Y coordinate of the floater is in
// this band.
break;
}
}
// Restore Y coordinate and available space information
// regardless of the outcome.
mY = saveY;
GetAvailableSpace();
}
}
}
return result;
}
void
nsBlockReflowState::PlaceFloater(nsFloaterCache* aFloaterCache,
PRBool* aIsLeftFloater,
nsPoint* aNewOrigin)
{
// Save away the Y coordinate before placing the floater. We will
// restore mY at the end after placing the floater. This is
// necessary because any adjustments to mY during the floater
// placement are for the floater only, not for any non-floating
// content.
nscoord saveY = mY;
nsIFrame* floater = aFloaterCache->mPlaceholder->GetOutOfFlowFrame();
// Get the type of floater
const nsStyleDisplay* floaterDisplay;
const nsStyleSpacing* floaterSpacing;
const nsStylePosition* floaterPosition;
floater->GetStyleData(eStyleStruct_Display,
(const nsStyleStruct*&)floaterDisplay);
floater->GetStyleData(eStyleStruct_Spacing,
(const nsStyleStruct*&)floaterSpacing);
floater->GetStyleData(eStyleStruct_Position,
(const nsStyleStruct*&)floaterPosition);
// See if the floater should clear any preceeding floaters...
if (NS_STYLE_CLEAR_NONE != floaterDisplay->mBreakType) {
ClearFloaters(mY, floaterDisplay->mBreakType);
}
else {
// Get the band of available space
GetAvailableSpace();
}
// Get the floaters bounding box and margin information
nsRect region;
floater->GetRect(region);
// Adjust the floater size by its margin. That's the area that will
// impact the space manager.
region.width += aFloaterCache->mMargins.left + aFloaterCache->mMargins.right;
region.height += aFloaterCache->mMargins.top + aFloaterCache->mMargins.bottom;
// Find a place to place the floater. The CSS2 spec doesn't want
// floaters overlapping each other or sticking out of the containing
// block if possible (CSS2 spec section 9.5.1, see the rule list).
NS_ASSERTION((NS_STYLE_FLOAT_LEFT == floaterDisplay->mFloats) ||
(NS_STYLE_FLOAT_RIGHT == floaterDisplay->mFloats),
"invalid float type");
// While there is not enough room for the floater, clear past other
// floaters until there is room (or the band is not impacted by a
// floater).
//
// Note: The CSS2 spec says that floaters should be placed as high
// as possible.
while (!CanPlaceFloater(region, floaterDisplay->mFloats)) {
mY += mAvailSpaceRect.height;
GetAvailableSpace();
}
// Assign an x and y coordinate to the floater. Note that the x,y
// coordinates are computed relative to the translation in the
// spacemanager which means that the impacted region will be
// inside the border/padding area.
PRBool okToAddRectRegion = PR_TRUE;
if (NS_STYLE_FLOAT_LEFT == floaterDisplay->mFloats) {
*aIsLeftFloater = PR_TRUE;
region.x = mAvailSpaceRect.x;
}
else {
*aIsLeftFloater = PR_FALSE;
if (mUnconstrainedWidth) {
// When we are in an unconstrained width reflow (e.g. inside a
// table cell) there is no point in placing the right floaters
// because they will be placed inifinitely far to the right and
// therefore they will have no real effect on reflow. However,
// they will wreak havoc with the line's width and the
// combined-area so we just skip placing them there at all.
//
// XXX To keep the combined area in the ballpark, however, we
// pretend that the floater ended up at the left margin...
okToAddRectRegion = PR_FALSE;
region.x = mAvailSpaceRect.x;
mSkippedRightFloaters = PR_TRUE;
if (region.width > mSkippedMaxElementSize.width) {
mSkippedMaxElementSize.width = region.width;
}
if (region.height > mSkippedMaxElementSize.height) {
mSkippedMaxElementSize.height = region.height;
}
// region.x = mAvailSpaceRect.XMost() - region.width;
}
else {
region.x = mAvailSpaceRect.XMost() - region.width;
}
}
const nsMargin& borderPadding = BorderPadding();
region.y = mY - borderPadding.top;
if (region.y < 0) {
// CSS2 spec, 9.5.1 rule [4]: A floating box's outer top may not
// be higher than the top of its containing block.
// XXX It's not clear if it means the higher than the outer edge
// or the border edge or the inner edge?
region.y = 0;
}
// Place the floater in the space manager
if (okToAddRectRegion) {
mSpaceManager->AddRectRegion(floater, region);
}
// Save away the floaters region in the spacemanager, after making
// it relative to the containing block's frame instead of relative
// to the spacemanager translation (which is inset by the
// border+padding).
aFloaterCache->mRegion.x = region.x + borderPadding.left;
aFloaterCache->mRegion.y = region.y + borderPadding.top;
aFloaterCache->mRegion.width = region.width;
aFloaterCache->mRegion.height = region.height;
#ifdef NOISY_SPACEMANAGER
nscoord tx, ty;
mSpaceManager->GetTranslation(tx, ty);
nsFrame::ListTag(stdout, mBlock);
printf(": PlaceFloater: AddRectRegion: txy=%d,%d (%d,%d) {%d,%d,%d,%d}\n",
tx, ty, mSpaceManagerX, mSpaceManagerY,
aFloaterCache->mRegion.x, aFloaterCache->mRegion.y,
aFloaterCache->mRegion.width, aFloaterCache->mRegion.height);
#endif
// Set the origin of the floater frame, in frame coordinates. These
// coordinates are not relative to the spacemanager
// translation, therefore we have to factor in our border/padding.
nscoord x = borderPadding.left + aFloaterCache->mMargins.left + region.x;
nscoord y = borderPadding.top + aFloaterCache->mMargins.top + region.y;
// If floater is relatively positioned, factor that in as well
if (NS_STYLE_POSITION_RELATIVE == floaterPosition->mPosition) {
x += aFloaterCache->mOffsets.left;
y += aFloaterCache->mOffsets.top;
}
floater->MoveTo(x, y);
if (aNewOrigin) {
aNewOrigin->x = x;
aNewOrigin->y = y;
}
// Now restore mY
mY = saveY;
#ifdef NOISY_INCREMENTAL_REFLOW
{
nsRect r;
floater->GetRect(r);
nsFrame::IndentBy(stdout, gNoiseIndent);
printf("placed floater: ");
((nsFrame*)floater)->ListTag(stdout);
printf(" %d,%d,%d,%d\n", r.x, r.y, r.width, r.height);
}
#endif
}
/**
* Place below-current-line floaters.
*/
void
nsBlockReflowState::PlaceBelowCurrentLineFloaters(nsFloaterCacheList& aList)
{
nsFloaterCache* fc = aList.Head();
while (fc) {
if (!fc->mIsCurrentLineFloater) {
mBlock->ReflowFloater(*this, fc->mPlaceholder, fc->mCombinedArea,
fc->mMargins, fc->mOffsets);
// Place the floater
PRBool isLeftFloater;
nsPoint origin;
PlaceFloater(fc, &isLeftFloater, &origin);
// Update the floater combined-area
nsRect combinedArea = fc->mCombinedArea;
combinedArea.x += origin.x;
combinedArea.y += origin.y;
CombineRects(combinedArea, mFloaterCombinedArea);
}
fc = fc->Next();
}
}
void
nsBlockReflowState::ClearFloaters(nscoord aY, PRUint8 aBreakType)
{
#ifdef NOISY_INCREMENTAL_REFLOW
nsFrame::IndentBy(stdout, gNoiseIndent);
printf("clear floaters: in: mY=%d aY=%d(%d)\n",
mY, aY, aY - BorderPadding().top);
#endif
#ifdef NOISY_FLOATER_CLEARING
printf("nsBlockReflowState::ClearFloaters: aY=%d breakType=%dn",
aY, aBreakType);
mSpaceManager->List(stdout);
#endif
const nsMargin& bp = BorderPadding();
nscoord newY = mBand.ClearFloaters(aY - bp.top, aBreakType);
mY = newY + bp.top;
GetAvailableSpace();
#ifdef NOISY_INCREMENTAL_REFLOW
nsFrame::IndentBy(stdout, gNoiseIndent);
printf("clear floaters: out: mY=%d(%d)\n", mY, mY - bp.top);
#endif
}
//////////////////////////////////////////////////////////////////////
// Painting, event handling
PRIntn
nsBlockFrame::GetSkipSides() const
{
PRIntn skip = 0;
if (nsnull != mPrevInFlow) {
skip |= 1 << NS_SIDE_TOP;
}
if (nsnull != mNextInFlow) {
skip |= 1 << NS_SIDE_BOTTOM;
}
return skip;
}
#ifdef NOISY_DAMAGE_REPAIR
static void ComputeCombinedArea(nsLineBox* aLine,
nscoord aWidth, nscoord aHeight,
nsRect& aResult)
{
nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight;
while (nsnull != aLine) {
// Compute min and max x/y values for the reflowed frame's
// combined areas
nscoord x = aLine->mCombinedArea.x;
nscoord y = aLine->mCombinedArea.y;
nscoord xmost = x + aLine->mCombinedArea.width;
nscoord ymost = y + aLine->mCombinedArea.height;
if (x < xa) {
xa = x;
}
if (xmost > xb) {
xb = xmost;
}
if (y < ya) {
ya = y;
}
if (ymost > yb) {
yb = ymost;
}
aLine = aLine->mNext;
}
aResult.x = xa;
aResult.y = ya;
aResult.width = xb - xa;
aResult.height = yb - ya;
}
#endif
NS_IMETHODIMP
nsBlockFrame::Paint(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsFramePaintLayer aWhichLayer)
{
if (NS_FRAME_IS_UNFLOWABLE & mState) {
return NS_OK;
}
#ifdef NOISY_DAMAGE_REPAIR
if (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) {
PRInt32 depth = GetDepth();
nsRect ca;
ComputeCombinedArea(mLines, mRect.width, mRect.height, ca);
nsFrame::IndentBy(stdout, depth);
ListTag(stdout);
printf(": bounds=%d,%d,%d,%d dirty=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
mRect.x, mRect.y, mRect.width, mRect.height,
aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height,
ca.x, ca.y, ca.width, ca.height);
}
#endif
const nsStyleDisplay* disp = (const nsStyleDisplay*)
mStyleContext->GetStyleData(eStyleStruct_Display);
// If overflow is hidden then set the clip rect so that children
// don't leak out of us
if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) {
aRenderingContext.PushState();
SetClipRect(aRenderingContext);
}
// Only paint the border and background if we're visible
if (disp->mVisible && (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) &&
(0 != mRect.width) && (0 != mRect.height)) {
PRIntn skipSides = GetSkipSides();
const nsStyleColor* color = (const nsStyleColor*)
mStyleContext->GetStyleData(eStyleStruct_Color);
const nsStyleSpacing* spacing = (const nsStyleSpacing*)
mStyleContext->GetStyleData(eStyleStruct_Spacing);
// Paint background and border
nsRect rect(0, 0, mRect.width, mRect.height);
nsCSSRendering::PaintBackground(aPresContext, aRenderingContext, this,
aDirtyRect, rect, *color, *spacing, 0, 0);
nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, this,
aDirtyRect, rect, *spacing, mStyleContext,
skipSides);
nsCSSRendering::PaintOutline(aPresContext, aRenderingContext, this,
aDirtyRect, rect, *spacing, mStyleContext, 0);
}
// Child elements have the opportunity to override the visibility
// property and display even if the parent is hidden
if (NS_FRAME_PAINT_LAYER_FLOATERS == aWhichLayer) {
PaintFloaters(aPresContext, aRenderingContext, aDirtyRect);
}
PaintChildren(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer);
if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) {
PRBool clipState;
aRenderingContext.PopState(clipState);
}
return NS_OK;
}
void
nsBlockFrame::PaintFloaters(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect)
{
for (nsLineBox* line = mLines; nsnull != line; line = line->mNext) {
if (line->mFloaters.IsEmpty()) {
continue;
}
nsFloaterCache* fc = line->mFloaters.Head();
while (fc) {
nsIFrame* floater = fc->mPlaceholder->GetOutOfFlowFrame();
PaintChild(aPresContext, aRenderingContext, aDirtyRect,
floater, NS_FRAME_PAINT_LAYER_BACKGROUND);
PaintChild(aPresContext, aRenderingContext, aDirtyRect,
floater, NS_FRAME_PAINT_LAYER_FLOATERS);
PaintChild(aPresContext, aRenderingContext, aDirtyRect,
floater, NS_FRAME_PAINT_LAYER_FOREGROUND);
fc = fc->Next();
}
}
}
void
nsBlockFrame::PaintChildren(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsFramePaintLayer aWhichLayer)
{
#ifdef NOISY_DAMAGE_REPAIR
PRInt32 depth = 0;
if (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) {
depth = GetDepth();
}
#endif
for (nsLineBox* line = mLines; nsnull != line; line = line->mNext) {
// If the line has outside children or if the line intersects the
// dirty rect then paint the children in the line.
if ((NS_FRAME_OUTSIDE_CHILDREN & mState) ||
!((line->mCombinedArea.YMost() <= aDirtyRect.y) ||
(line->mCombinedArea.y >= aDirtyRect.YMost()))) {
#ifdef NOISY_DAMAGE_REPAIR
if (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) {
nsFrame::IndentBy(stdout, depth+1);
printf("draw line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
line, line->mBounds.x, line->mBounds.y,
line->mBounds.width, line->mBounds.height,
line->mCombinedArea.x, line->mCombinedArea.y,
line->mCombinedArea.width, line->mCombinedArea.height);
}
#endif
nsIFrame* kid = line->mFirstChild;
PRInt32 n = line->ChildCount();
while (--n >= 0) {
PaintChild(aPresContext, aRenderingContext, aDirtyRect, kid,
aWhichLayer);
kid->GetNextSibling(&kid);
}
#ifdef DEBUG
if (gShowDirtyLines) {
if (line->WasDirty()) {
aRenderingContext.SetColor(NS_RGB(128, 255, 128));
aRenderingContext.DrawRect(line->mBounds);
}
}
#endif
}
#ifdef NOISY_DAMAGE_REPAIR
else {
if (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) {
nsFrame::IndentBy(stdout, depth+1);
printf("skip line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
line, line->mBounds.x, line->mBounds.y,
line->mBounds.width, line->mBounds.height,
line->mCombinedArea.x, line->mCombinedArea.y,
line->mCombinedArea.width, line->mCombinedArea.height);
}
}
#endif
}
if (NS_FRAME_PAINT_LAYER_FOREGROUND == aWhichLayer) {
if ((nsnull != mBullet) && HaveOutsideBullet()) {
// Paint outside bullets manually
PaintChild(aPresContext, aRenderingContext, aDirtyRect, mBullet,
aWhichLayer);
}
}
}
NS_IMETHODIMP
nsBlockFrame::HandleEvent(nsIPresContext& aPresContext,
nsGUIEvent* aEvent,
nsEventStatus& aEventStatus)
{
if (aEvent->message == NS_MOUSE_MOVE) {
nsCOMPtr