/* -*- 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 "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ #include "nsBlockFrame.h" #include "nsIStyleContext.h" #include "nsStyleConsts.h" #include "nsIHTMLContent.h" #include "nsIPresContext.h" #include "nsIPresShell.h" #include "nsIAnchoredItems.h" #include "nsPlaceholderFrame.h" #include "nsIPtr.h" #include "nsHTMLAtoms.h" #include "nsHTMLIIDs.h" #include "nsHTMLValue.h" #include "nsReflowCommand.h" #include "nsCSSLayout.h" // XXX what do we do with catastrophic errors (rv < 0)? What is the // state of the reflow world after such an error? static NS_DEFINE_IID(kIAnchoredItemsIID, NS_IANCHOREDITEMS_IID); static NS_DEFINE_IID(kIRunaroundIID, NS_IRUNAROUND_IID); static NS_DEFINE_IID(kIFloaterContainerIID, NS_IFLOATERCONTAINER_IID); NS_DEF_PTR(nsIContent); NS_DEF_PTR(nsIStyleContext); //---------------------------------------------------------------------- void nsBlockBandData::ComputeAvailSpaceRect() { nsBandTrapezoid* trapezoid = data; if (count > 1) { // If there's more than one trapezoid that means there are floaters PRInt32 i; // Stop when we get to space occupied by a right floater for (i = 0; i < count; i++) { nsBandTrapezoid* trapezoid = &data[i]; if (trapezoid->state != nsBandTrapezoid::Available) { nsStyleDisplay* display; // XXX Handle the case of multiple frames trapezoid->frame->GetStyleData(eStyleStruct_Display, (nsStyleStruct*&)display); if (NS_STYLE_FLOAT_RIGHT == display->mFloats) { break; } } } if (i > 0) { trapezoid = &data[i - 1]; } } if (nsBandTrapezoid::Available == trapezoid->state) { // The trapezoid is available trapezoid->GetRect(availSpace); } else { nsStyleDisplay* display; // The trapezoid is occupied. That means there's no available space trapezoid->GetRect(availSpace); // XXX Handle the case of multiple frames trapezoid->frame->GetStyleData(eStyleStruct_Display, (nsStyleStruct*&)display); if (NS_STYLE_FLOAT_LEFT == display->mFloats) { availSpace.x = availSpace.XMost(); } availSpace.width = 0; } } //---------------------------------------------------------------------- nsBlockReflowState::nsBlockReflowState() { } nsBlockReflowState::~nsBlockReflowState() { } nsresult nsBlockReflowState::Initialize(nsIPresContext* aPresContext, nsISpaceManager* aSpaceManager, const nsSize& aMaxSize, nsSize* aMaxElementSize, nsBlockFrame* aBlock) { nsresult rv = NS_OK; mPresContext = aPresContext; mBlock = aBlock; mSpaceManager = aSpaceManager; mBlockIsPseudo = aBlock->IsPseudoFrame(); mCurrentLine = nsnull; mPrevKidFrame = nsnull; mX = 0; mY = 0; mAvailSize = aMaxSize; mUnconstrainedWidth = PRBool(mAvailSize.width == NS_UNCONSTRAINEDSIZE); mUnconstrainedHeight = PRBool(mAvailSize.height == NS_UNCONSTRAINEDSIZE); mMaxElementSizePointer = aMaxElementSize; if (nsnull != aMaxElementSize) { aMaxElementSize->width = 0; aMaxElementSize->height = 0; } mKidXMost = 0; mPrevPosBottomMargin = 0; mPrevNegBottomMargin = 0; mNextListOrdinal = -1; mFirstChildIsInsideBullet = PR_FALSE; return rv; } // Recover the block reflow state to what it should be if aLine is about // to be reflowed. aLine should not be nsnull nsresult nsBlockReflowState::RecoverState(nsLineData* aLine) { NS_PRECONDITION(nsnull != aLine, "null parameter"); nsLineData* prevLine = aLine->mPrevLine; if (nsnull != prevLine) { // Compute the running y-offset, and the available height mY = prevLine->mBounds.YMost(); if (!mUnconstrainedHeight) { mAvailSize.height -= mY; } // Compute the kid x-most for (nsLineData* l = prevLine; nsnull != l; l = l->mPrevLine) { nscoord xmost = l->mBounds.XMost(); if (xmost > mKidXMost) { mKidXMost = xmost; } } // If the previous line is a block, then factor in its bottom margin if (prevLine->mIsBlock) { nsStyleSpacing* kidSpacing; nsIFrame* kid = prevLine->mFirstChild; kid->GetStyleData(eStyleStruct_Spacing, (nsStyleStruct*&)kidSpacing); nsMargin kidMargin; kidSpacing->CalcMarginFor(kid, kidMargin); if (kidMargin.bottom < 0) { mPrevPosBottomMargin = 0; mPrevNegBottomMargin = -kidMargin.bottom; } else { mPrevPosBottomMargin = kidMargin.bottom; mPrevNegBottomMargin = 0; } } } return NS_OK; } //---------------------------------------------------------------------- nsresult nsBlockFrame::NewFrame(nsIFrame** aInstancePtrResult, nsIContent* aContent, nsIFrame* aParent) { NS_PRECONDITION(nsnull != aInstancePtrResult, "null ptr"); if (nsnull == aInstancePtrResult) { return NS_ERROR_NULL_POINTER; } nsIFrame* it = new nsBlockFrame(aContent, aParent); if (nsnull == it) { return NS_ERROR_OUT_OF_MEMORY; } *aInstancePtrResult = it; return NS_OK; } nsBlockFrame::nsBlockFrame(nsIContent* aContent, nsIFrame* aParent) : nsHTMLContainerFrame(aContent, aParent) { } nsBlockFrame::~nsBlockFrame() { DestroyLines(); delete mRunInFloaters; } void nsBlockFrame::DestroyLines() { nsLineData* line = mLines; while (nsnull != line) { nsLineData* next = line->mNextLine; delete line; line = next; } mLines = nsnull; } NS_METHOD nsBlockFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr) { NS_PRECONDITION(0 != aInstancePtr, "null ptr"); if (NULL == aInstancePtr) { return NS_ERROR_NULL_POINTER; } if (aIID.Equals(kBlockFrameCID)) { *aInstancePtr = (void*) (this); return NS_OK; } else if (aIID.Equals(kIRunaroundIID)) { *aInstancePtr = (void*) ((nsIRunaround*) this); return NS_OK; } else if (aIID.Equals(kIFloaterContainerIID)) { *aInstancePtr = (void*) ((nsIFloaterContainer*) this); return NS_OK; } return nsHTMLContainerFrame::QueryInterface(aIID, aInstancePtr); } NS_METHOD nsBlockFrame::IsSplittable(nsSplittableType& aIsSplittable) const { aIsSplittable = NS_FRAME_SPLITTABLE_NON_RECTANGULAR; return NS_OK; } NS_METHOD nsBlockFrame::CreateContinuingFrame(nsIPresContext* aCX, nsIFrame* aParent, nsIStyleContext* aStyleContext, nsIFrame*& aContinuingFrame) { nsBlockFrame* cf = new nsBlockFrame(mContent, aParent); if (nsnull == cf) { return NS_ERROR_OUT_OF_MEMORY; } PrepareContinuingFrame(aCX, aParent, aStyleContext, cf); aContinuingFrame = cf; return NS_OK; } NS_METHOD nsBlockFrame::ListTag(FILE* out) const { if ((nsnull != mGeometricParent) && IsPseudoFrame()) { fprintf(out, "*block<"); nsIAtom* atom = mContent->GetTag(); if (nsnull != atom) { nsAutoString tmp; atom->ToString(tmp); fputs(tmp, out); } PRInt32 contentIndex; GetContentIndex(contentIndex); fprintf(out, ">(%d)@%p", contentIndex, this); } else { nsHTMLContainerFrame::ListTag(out); } return NS_OK; } NS_METHOD nsBlockFrame::List(FILE* out, PRInt32 aIndent) const { // Indent for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out); // Output the tag ListTag(out); // Output the first/last content offset fprintf(out, "[%d,%d,%c] ", mFirstContentOffset, mLastContentOffset, (mLastContentIsComplete ? 'T' : 'F')); if (nsnull != mPrevInFlow) { fprintf(out, "prev-in-flow=%p ", mPrevInFlow); } if (nsnull != mNextInFlow) { fprintf(out, "next-in-flow=%p ", mNextInFlow); } // Output the rect out << mRect; // Output the children, one line at a time if (nsnull != mLines) { if (0 != mState) { fprintf(out, " [state=%08x]", mState); } fputs("<\n", out); aIndent++; nsLineData* line = mLines; while (nsnull != line) { line->List(out, aIndent); line = line->mNextLine; } aIndent--; for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out); fputs(">\n", out); } else { if (0 != mState) { fprintf(out, " [state=%08x]", mState); } fputs("<>\n", out); } return NS_OK; } NS_METHOD nsBlockFrame::VerifyTree() const { #ifdef NS_DEBUG nsresult rv = nsHTMLContainerFrame::VerifyTree(); if (NS_OK != rv) { return rv; } rv = VerifyLines(PR_TRUE); return rv; #else return NS_OK; #endif } #ifdef NS_DEBUG nsresult nsBlockFrame::VerifyLines(PRBool aFinalCheck) const { nsresult rv = NS_OK; // Make sure that the list of children agrees with our child count. // If this is not the case then the child list and the line list are // not properly arranged. PRInt32 len = LengthOf(mFirstChild); NS_ASSERTION(mChildCount == len, "bad child list"); // Verify that our lines are correctly setup PRInt32 offset = mFirstContentOffset; PRInt32 lineChildCount = 0; nsLineData* line = mLines; nsLineData* prevLine = nsnull; while (nsnull != line) { if (aFinalCheck) { NS_ASSERTION(((offset == line->mFirstContentOffset) && (line->mFirstContentOffset <= line->mLastContentOffset)), "bad line mFirstContentOffset"); NS_ASSERTION(line->mLastContentOffset <= mLastContentOffset, "bad line mLastContentOffset"); offset = line->mLastContentOffset; if (line->mLastContentIsComplete) { offset++; } } lineChildCount += line->mChildCount; rv = line->Verify(aFinalCheck); if (NS_OK != rv) { return rv; } prevLine = line; line = line->mNextLine; } if (aFinalCheck && (nsnull != prevLine)) { NS_ASSERTION(prevLine->mLastContentOffset == mLastContentOffset, "bad mLastContentOffset"); NS_ASSERTION(prevLine->mLastContentIsComplete == mLastContentIsComplete, "bad mLastContentIsComplete"); } NS_ASSERTION(lineChildCount == mChildCount, "bad line counts"); return rv; } #endif //---------------------------------------------------------------------- // Remove a next-in-flow from from this block's list of lines // XXX problems here: // 1. we always have to start from the first line: slow! // 2. we can avoid this when the child is not the last child in a line void nsBlockFrame::WillDeleteNextInFlowFrame(nsIFrame* aNextInFlow) { // When a reflow indicates completion it's possible that // next-in-flows were just removed. We have to remove them from any // nsLineData's that follow the current line. nsLineData* line = mLines; while (nsnull != line) { if (line->mFirstChild == aNextInFlow) { // Remove child from line. if (0 == --line->mChildCount) { line->mFirstChild = nsnull; } else { // Fixup the line nsIFrame* nextKid; aNextInFlow->GetNextSibling(nextKid); line->mFirstChild = nextKid; nextKid->GetContentIndex(line->mFirstContentOffset); } break; } line = line->mNextLine; } } nsresult nsBlockFrame::ReflowInlineChild(nsIFrame* aKidFrame, nsIPresContext* aPresContext, nsReflowMetrics& aDesiredSize, const nsSize& aMaxSize, nsSize* aMaxElementSize, nsReflowStatus& aStatus) { aStatus = ReflowChild(aKidFrame, aPresContext, aDesiredSize, aMaxSize, aMaxElementSize); return NS_OK; } nsresult nsBlockFrame::ReflowBlockChild(nsIFrame* aKidFrame, nsIPresContext* aPresContext, nsISpaceManager* aSpaceManager, const nsSize& aMaxSize, nsRect& aDesiredRect, nsSize* aMaxElementSize, nsReflowStatus& aStatus) { aStatus = ReflowChild(aKidFrame, aPresContext, aSpaceManager, aMaxSize, aDesiredRect, aMaxElementSize); return NS_OK; } nsLineData* nsBlockFrame::CreateLineForOverflowList(nsIFrame* aOverflowList) { nsLineData* newLine = new nsLineData(); if (nsnull != newLine) { nsIFrame* kid = aOverflowList; newLine->mFirstChild = kid; kid->GetContentIndex(newLine->mFirstContentOffset); newLine->mLastContentOffset = -1; newLine->mLastContentIsComplete = PRPackedBool(0x255); PRInt32 kids = 0; while (nsnull != kid) { kids++; kid->GetNextSibling(kid); } newLine->mChildCount = kids; } return newLine; } void nsBlockFrame::DrainOverflowList() { nsBlockFrame* prevBlock = (nsBlockFrame*) mPrevInFlow; if (nsnull != prevBlock) { nsIFrame* overflowList = prevBlock->mOverflowList; if (nsnull != overflowList) { NS_ASSERTION(nsnull == mFirstChild, "bad overflow list"); NS_ASSERTION(nsnull == mLines, "bad overflow list"); // Create a line to hold the entire overflow list nsLineData* newLine = CreateLineForOverflowList(overflowList); // Place the children on our child list; this also reassigns // their geometric parent and updates our mChildCount. AppendChildren(overflowList); prevBlock->mOverflowList = nsnull; // The new line is the first line mLines = newLine; } } if (nsnull != mOverflowList) { NS_ASSERTION(nsnull != mFirstChild, "overflow list but no mapped children"); // Create a line to hold the overflow list nsLineData* newLine = CreateLineForOverflowList(mOverflowList); // Place the children on our child list; this also reassigns // their geometric parent and updates our mChildCount. AppendChildren(mOverflowList, PR_FALSE); mOverflowList = nsnull; // The new line is appended after our other lines nsLineData* prevLine = nsnull; nsLineData* line = mLines; while (nsnull != line) { prevLine = line; line = line->mNextLine; } if (nsnull == prevLine) { mLines = newLine; } else { prevLine->mNextLine = newLine; newLine->mPrevLine = prevLine; } } #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyLines(PR_FALSE); } #endif } nsresult nsBlockFrame::PlaceLine(nsBlockReflowState& aState, nsLineLayout& aLineLayout, nsLineData* aLine) { // Before we place the line, make sure that it will fit in it's new // location. It always fits if the height isn't constrained or it's // the first line. if (!aState.mUnconstrainedHeight && (aLine != mLines)) { if (aState.mY + aLine->mBounds.height > aState.mAvailSize.height) { // The line will not fit return PushLines(aState, aLine); } } // Consume space and advance running values aState.mY += aLine->mBounds.height; if (nsnull != aState.mMaxElementSizePointer) { nsSize* maxSize = aState.mMaxElementSizePointer; if (aLineLayout.mReflowData.mMaxElementSize.width > maxSize->width) { maxSize->width = aLineLayout.mReflowData.mMaxElementSize.width; } if (aLineLayout.mReflowData.mMaxElementSize.height > maxSize->height) { maxSize->height = aLineLayout.mReflowData.mMaxElementSize.height; } } nscoord xmost = aLine->mBounds.XMost(); if (xmost > aState.mKidXMost) { aState.mKidXMost = xmost; } // Any below current line floaters to place? // XXX We really want to know whether this is the initial reflow (reflow // unmapped) or a subsequent reflow in which case we only need to offset // the existing floaters... if (aState.mPendingFloaters.Count() > 0) { if (nsnull == aLine->mFloaters) { aLine->mFloaters = new nsVoidArray; } aLine->mFloaters->operator=(aState.mPendingFloaters); aState.mPendingFloaters.Clear(); } if (nsnull != aLine->mFloaters) { PlaceBelowCurrentLineFloaters(aState, aLine->mFloaters, aState.mY); // XXX Factor in the height of the floaters as well when considering // whether the line fits. // The default policy is that if there isn't room for the floaters then // both the line and the floaters are pushed to the next-in-flow... } if (aState.mY >= aState.mCurrentBand.availSpace.YMost()) { // The current y coordinate is now past our available space // rectangle. Get a new band of space. GetAvailableSpace(aState, aState.mY); } return NS_LINE_LAYOUT_COMPLETE; } // aY has borderpadding.top already factored in // aResult is relative to left,aY nsresult nsBlockFrame::GetAvailableSpace(nsBlockReflowState& aState, nscoord aY) { nsresult rv = NS_OK; nsISpaceManager* sm = aState.mSpaceManager; // Fill in band data for the specific Y coordinate sm->Translate(aState.mBorderPadding.left, 0); sm->GetBandData(aY, aState.mAvailSize, aState.mCurrentBand); sm->Translate(-aState.mBorderPadding.left, 0); // Compute the bounding rect of the available space, i.e. space // between any left and right floaters aState.mCurrentBand.ComputeAvailSpaceRect(); #if 0 // XXX For now we assume that there are no height restrictions // (e.g. no "float to bottom of column/page") nsRect& availSpace = aState.mCurrentBand.availSpace; aResult.x = availSpace.x; aResult.y = availSpace.y; aResult.width = availSpace.width; aResult.height = aState.mAvailSize.height; #else aState.mCurrentBand.availSpace.MoveBy(aState.mBorderPadding.left, aState.mY); #endif return rv; } // Give aLine and any successive lines to the block's next-in-flow; if // we don't have a next-in-flow then push all the children onto our // overflow list. nsresult nsBlockFrame::PushLines(nsBlockReflowState& aState, nsLineData* aLine) { PRInt32 i; // Split our child-list in two; revert our last content offset and // completion status to the previous line. nsLineData* prevLine = aLine->mPrevLine; NS_PRECONDITION(nsnull != prevLine, "pushing first line"); nsIFrame* prevKidFrame = prevLine->mFirstChild; for (i = prevLine->mChildCount - 1; --i >= 0; ) { prevKidFrame->GetNextSibling(prevKidFrame); } #ifdef NS_DEBUG nsIFrame* nextFrame; prevKidFrame->GetNextSibling(nextFrame); NS_ASSERTION(nextFrame == aLine->mFirstChild, "bad line list"); #endif prevKidFrame->SetNextSibling(nsnull); prevLine->mNextLine = nsnull; mLastContentOffset = prevLine->mLastContentOffset; mLastContentIsComplete = prevLine->mLastContentIsComplete; // Push children to our next-in-flow if we have, or to our overflow list nsBlockFrame* nextInFlow = (nsBlockFrame*) mNextInFlow; PRInt32 pushCount = 0; if (nsnull == nextInFlow) { // Place children on the overflow list mOverflowList = aLine->mFirstChild; // Destroy the lines nsLineData* line = aLine; while (nsnull != line) { pushCount += line->mChildCount; nsLineData* next = line->mNextLine; delete line; line = next; } } else { aLine->mPrevLine = nsnull; // Pass on the children to our next-in-flow nsLineData* line = aLine; prevLine = line; nsIFrame* lastKid = aLine->mFirstChild; nsIFrame* kid = lastKid; while (nsnull != line) { i = line->mChildCount; pushCount += i; NS_ASSERTION(kid == line->mFirstChild, "bad line list"); while (--i >= 0) { kid->SetGeometricParent(nextInFlow); nsIFrame* contentParent; kid->GetContentParent(contentParent); if (this == contentParent) { kid->SetContentParent(nextInFlow); } lastKid = kid; kid->GetNextSibling(kid); } prevLine = line; line = line->mNextLine; } // Join the two line lists nsLineData* nextInFlowLine = nextInFlow->mLines; if (nsnull != nextInFlowLine) { lastKid->SetNextSibling(nextInFlowLine->mFirstChild); nextInFlowLine->mPrevLine = prevLine; } nsIFrame* firstKid = aLine->mFirstChild; prevLine->mNextLine = nextInFlowLine; nextInFlow->mLines = aLine; nextInFlow->mFirstChild = firstKid; nextInFlow->mChildCount += pushCount; firstKid->GetContentIndex(nextInFlow->mFirstContentOffset); #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { nextInFlow->VerifyLines(PR_FALSE); } #endif } mChildCount -= pushCount; #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyLines(PR_TRUE); } #endif return NS_LINE_LAYOUT_NOT_COMPLETE; } nsresult nsBlockFrame::ReflowMapped(nsBlockReflowState& aState) { nsresult rv = NS_OK; // See if we have any run-in floaters to place if (nsnull != mRunInFloaters) { PlaceBelowCurrentLineFloaters(aState, mRunInFloaters, aState.mY); } // Get some space to start reflowing with GetAvailableSpace(aState, aState.mY); nsLineData* line = mLines; nsLineLayout lineLayout(aState); aState.mCurrentLine = &lineLayout; while (nsnull != line) { // Initialize the line layout for this line rv = lineLayout.Initialize(aState, line); if (NS_OK != rv) { goto done; } lineLayout.mPrevKidFrame = aState.mPrevKidFrame; // Reflow the line nsresult lineReflowStatus = lineLayout.ReflowLine(); if (PRInt32(lineReflowStatus) < 0) { // Some kind of hard error rv = lineReflowStatus; goto done; } mChildCount += lineLayout.mNewFrames; // Now place it. It's possible it won't fit. rv = PlaceLine(aState, lineLayout, line); if (NS_LINE_LAYOUT_COMPLETE != rv) { goto done; } mLastContentOffset = line->mLastContentOffset; mLastContentIsComplete = PRBool(line->mLastContentIsComplete); line = line->mNextLine; aState.mPrevKidFrame = lineLayout.mPrevKidFrame; } done: aState.mCurrentLine = nsnull; #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyLines(PR_TRUE); } #endif return rv; } // XXX This is a short-term hack. It assumes that the caller has already recovered // the state, and that some space has been retrieved from the space manager... nsresult nsBlockFrame::ReflowMappedFrom(nsBlockReflowState& aState, nsLineData* aLine) { nsresult rv = NS_OK; nsLineLayout lineLayout(aState); aState.mCurrentLine = &lineLayout; while (nsnull != aLine) { // Initialize the line layout for this line rv = lineLayout.Initialize(aState, aLine); if (NS_OK != rv) { goto done; } lineLayout.mPrevKidFrame = aState.mPrevKidFrame; // Reflow the line nsresult lineReflowStatus = lineLayout.ReflowLine(); if (PRInt32(lineReflowStatus) < 0) { // Some kind of hard error rv = lineReflowStatus; goto done; } mChildCount += lineLayout.mNewFrames; // Now place it. It's possible it won't fit. rv = PlaceLine(aState, lineLayout, aLine); if (NS_LINE_LAYOUT_COMPLETE != rv) { goto done; } mLastContentOffset = aLine->mLastContentOffset; mLastContentIsComplete = PRBool(aLine->mLastContentIsComplete); aLine = aLine->mNextLine; aState.mPrevKidFrame = lineLayout.mPrevKidFrame; } done: aState.mCurrentLine = nsnull; #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyLines(PR_TRUE); } #endif return rv; } nsresult nsBlockFrame::ReflowUnmapped(nsBlockReflowState& aState) { nsresult rv = NS_OK; // If we have no children and we have a prev-in-flow then we need to // pick up where it left off. If we have children, e.g. we're being // resized, then our content offset will have already been set // correctly. nsIFrame* kidPrevInFlow = nsnull; if ((nsnull == mFirstChild) && (nsnull != mPrevInFlow)) { nsBlockFrame* prev = (nsBlockFrame*) mPrevInFlow; mFirstContentOffset = prev->NextChildOffset();// XXX Is this necessary? if (PR_FALSE == prev->mLastContentIsComplete) { // Our prev-in-flow's last child is not complete prev->LastChild(kidPrevInFlow); } } // Get to the last line where the new content may be added nsLineData* line = nsnull; nsLineData* prevLine = nsnull; if (nsnull != mLines) { line = mLines; while (nsnull != line->mNextLine) { line = line->mNextLine; } prevLine = line->mPrevLine; // If the last line is not complete then kidPrevInFlow should be // set to the last-line's last child. if (!line->mLastContentIsComplete) { kidPrevInFlow = line->GetLastChild(); } } // Get some space to start reflowing with GetAvailableSpace(aState, aState.mY); // Now reflow the new content until we are out of new content or out // of vertical space. PRInt32 kidIndex = NextChildOffset(); nsLineLayout lineLayout(aState); aState.mCurrentLine = &lineLayout; lineLayout.mKidPrevInFlow = kidPrevInFlow; PRInt32 contentChildCount = mContent->ChildCount(); while (kidIndex < contentChildCount) { if (nsnull == line) { if (!MoreToReflow(aState)) { break; } line = new nsLineData(); if (nsnull == line) { rv = NS_ERROR_OUT_OF_MEMORY; goto done; } line->mFirstContentOffset = kidIndex; } // Initialize the line layout for this line rv = lineLayout.Initialize(aState, line); if (NS_OK != rv) { goto done; } lineLayout.mKidPrevInFlow = kidPrevInFlow; lineLayout.mPrevKidFrame = aState.mPrevKidFrame; // Reflow the line nsresult lineReflowStatus = lineLayout.ReflowLine(); if (PRInt32(lineReflowStatus) < 0) { // Some kind of hard error rv = lineReflowStatus; goto done; } mChildCount += lineLayout.mNewFrames; // Add line to the block; do this before placing the line in case // PushLines is needed. if (nsnull == prevLine) { // For the first line, initialize mFirstContentOffset mFirstContentOffset = line->mFirstContentOffset; mFirstChild = line->mFirstChild; mLines = line; } else { prevLine->mNextLine = line; line->mPrevLine = prevLine; } // Now place it. It's possible it won't fit. rv = PlaceLine(aState, lineLayout, line); if (NS_LINE_LAYOUT_COMPLETE != rv) { goto done; } kidIndex = lineLayout.mKidIndex; kidPrevInFlow = lineLayout.mKidPrevInFlow; mLastContentOffset = line->mLastContentOffset; mLastContentIsComplete = PRBool(line->mLastContentIsComplete); prevLine = line; line = line->mNextLine; aState.mPrevKidFrame = lineLayout.mPrevKidFrame; } done: aState.mCurrentLine = nsnull; if (aState.mBlockIsPseudo) { PropagateContentOffsets(); } #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyLines(PR_TRUE); } #endif return rv; } nsresult nsBlockFrame::InitializeState(nsIPresContext* aPresContext, nsISpaceManager* aSpaceManager, const nsSize& aMaxSize, nsSize* aMaxElementSize, nsBlockReflowState& aState) { nsresult rv; rv = aState.Initialize(aPresContext, aSpaceManager, aMaxSize, aMaxElementSize, this); // Apply border and padding adjustments for regular frames only if (!aState.mBlockIsPseudo) { nsStyleSpacing* mySpacing = (nsStyleSpacing*) mStyleContext->GetData(eStyleStruct_Spacing); nsStylePosition* myPosition = (nsStylePosition*) mStyleContext->GetData(eStyleStruct_Position); mySpacing->CalcBorderPaddingFor(this, aState.mBorderPadding); aState.mY = aState.mBorderPadding.top; aState.mX = aState.mBorderPadding.left; if (aState.mUnconstrainedWidth) { // If our width is unconstrained don't bother futzing with the // available width/height because they don't matter - we are // going to get reflowed again. aState.mDeltaWidth = NS_UNCONSTRAINEDSIZE; } else { // When we are constrained we need to apply the width/height // style properties. When we have a width/height it applies to // the content width/height of our box. The content width/height // doesn't include the border+padding so we have to add that in // instead of subtracting it out of our maxsize. nscoord lr = aState.mBorderPadding.left + aState.mBorderPadding.right; // Get and apply the stylistic size. Note: do not limit the // height until we are done reflowing. PRIntn ss = aState.mStyleSizeFlags = nsCSSLayout::GetStyleSize(aPresContext, this, aState.mStyleSize); if (0 != (ss & NS_SIZE_HAS_WIDTH)) { aState.mAvailSize.width = aState.mStyleSize.width + lr; } else { aState.mAvailSize.width -= lr; } aState.mDeltaWidth = aState.mAvailSize.width - mRect.width; } } else { aState.mBorderPadding.SizeTo(0, 0, 0, 0); aState.mDeltaWidth = aState.mAvailSize.width - mRect.width; } // Setup initial list ordinal value nsIAtom* tag = mContent->GetTag(); if ((tag == nsHTMLAtoms::ul) || (tag == nsHTMLAtoms::ol) || (tag == nsHTMLAtoms::menu) || (tag == nsHTMLAtoms::dir)) { nsHTMLValue value; if (eContentAttr_HasValue == ((nsIHTMLContent*)mContent)->GetAttribute(nsHTMLAtoms::start, value)) { if (eHTMLUnit_Integer == value.GetUnit()) { aState.mNextListOrdinal = value.GetIntValue(); } } } NS_RELEASE(tag); return rv; } PRBool nsBlockFrame::MoreToReflow(nsBlockReflowState& aState) { PRBool rv = PR_FALSE; if (NextChildOffset() < mContent->ChildCount()) { rv = PR_TRUE; } return rv; } nsBlockReflowState* nsBlockFrame::FindBlockReflowState(nsIPresContext* aPresContext, nsIFrame* aFrame) { nsBlockReflowState* state = nsnull; if (nsnull != aFrame) { nsIFrame* parent; aFrame->GetGeometricParent(parent); while (nsnull != parent) { nsBlockFrame* block; nsresult rv = parent->QueryInterface(kBlockFrameCID, (void**) &block); if (NS_OK == rv) { nsIPresShell* shell = aPresContext->GetShell(); state = (nsBlockReflowState*) shell->GetCachedData(block); NS_RELEASE(shell); break; } parent->GetGeometricParent(parent); } } return state; } nsresult nsBlockFrame::DoResizeReflow(nsBlockReflowState& aState, const nsSize& aMaxSize, nsRect& aDesiredRect, nsReflowStatus& aStatus) { NS_FRAME_TRACE_MSG(("enter nsBlockFrame::DoResizeReflow: deltaWidth=%d", aState.mDeltaWidth)); #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyLines(PR_TRUE); PreReflowCheck(); } #endif nsresult rv = NS_OK; nsIPresShell* shell = aState.mPresContext->GetShell(); shell->PutCachedData(this, &aState); // Check for an overflow list DrainOverflowList(); if (nsnull != mLines) { rv = ReflowMapped(aState); } if (NS_OK == rv) { if ((nsnull != mLines) && (aState.mAvailSize.height <= 0)) { // We are out of space } if (MoreToReflow(aState)) { rv = ReflowUnmapped(aState); } } // Return our desired rect ComputeDesiredRect(aState, aMaxSize, aDesiredRect); // Set return status aStatus = NS_FRAME_COMPLETE; if (NS_LINE_LAYOUT_NOT_COMPLETE == rv) { rv = NS_OK; aStatus = NS_FRAME_NOT_COMPLETE; } #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { // Verify that the line layout code pulled everything up when it // indicates a complete reflow. if (NS_FRAME_IS_COMPLETE(aStatus)) { nsBlockFrame* nextBlock = (nsBlockFrame*) mNextInFlow; while (nsnull != nextBlock) { NS_ASSERTION((nsnull == nextBlock->mLines) && (nextBlock->mChildCount == 0) && (nsnull == nextBlock->mFirstChild), "bad completion status"); nextBlock = (nsBlockFrame*) nextBlock->mNextInFlow; } #if XXX // We better not be in the same parent frame as our prev-in-flow. // If we are it means that we were continued instead of pulling up // children. if (nsnull != mPrevInFlow) { nsIFrame* ourParent = mGeometricParent; nsIFrame* prevParent = ((nsBlockFrame*)mPrevInFlow)->mGeometricParent; NS_ASSERTION(ourParent != prevParent, "bad continuation"); } #endif } } #endif // Now that reflow has finished, remove the cached pointer shell->RemoveCachedData(this); NS_RELEASE(shell); #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyLines(PR_TRUE); PostReflowCheck(aStatus); } #endif NS_FRAME_TRACE_REFLOW_OUT("nsBlockFrame::DoResizeReflow", aStatus); return rv; } //---------------------------------------------------------------------- NS_METHOD nsBlockFrame::ContentAppended(nsIPresShell* aShell, nsIPresContext* aPresContext, nsIContent* aContainer) { return nsHTMLContainerFrame::ContentAppended(aShell, aPresContext, aContainer); } NS_METHOD nsBlockFrame::ContentInserted(nsIPresShell* aShell, nsIPresContext* aPresContext, nsIContent* aContainer, nsIContent* aChild, PRInt32 aIndexInParent) { return nsHTMLContainerFrame::ContentInserted(aShell, aPresContext, aContainer, aChild, aIndexInParent); } NS_METHOD nsBlockFrame::ContentReplaced(nsIPresShell* aShell, nsIPresContext* aPresContext, nsIContent* aContainer, nsIContent* aOldChild, nsIContent* aNewChild, PRInt32 aIndexInParent) { nsresult rv = NS_OK; return rv; } NS_METHOD nsBlockFrame::ContentDeleted(nsIPresShell* aShell, nsIPresContext* aPresContext, nsIContent* aContainer, nsIContent* aChild, PRInt32 aIndexInParent) { nsresult rv = NS_OK; return rv; } // XXX if there is nothing special going on here, then remove this // implementation and let nsFrame do it. NS_METHOD nsBlockFrame::GetReflowMetrics(nsIPresContext* aPresContext, nsReflowMetrics& aMetrics) { nsresult rv = NS_OK; aMetrics.width = mRect.width; aMetrics.height = mRect.height; aMetrics.ascent = mRect.height; aMetrics.descent = 0; return rv; } //---------------------------------------------------------------------- NS_METHOD nsBlockFrame::ResizeReflow(nsIPresContext* aPresContext, nsISpaceManager* aSpaceManager, const nsSize& aMaxSize, nsRect& aDesiredRect, nsSize* aMaxElementSize, nsReflowStatus& aStatus) { nsresult rv = NS_OK; aStatus = NS_FRAME_COMPLETE; nsBlockReflowState state; rv = InitializeState(aPresContext, aSpaceManager, aMaxSize, aMaxElementSize, state); if (NS_OK == rv) { nsRect desiredRect; rv = DoResizeReflow(state, aMaxSize, aDesiredRect, aStatus); } return rv; } nsLineData* nsBlockFrame::FindLine(nsIFrame* aFrame) { // Find the line that contains the aFrame nsLineData* line = mLines; while (nsnull != line) { nsIFrame* child = line->mFirstChild; for (PRInt32 count = line->mChildCount; count > 0; count--) { if (child == aFrame) { return line; } child->GetNextSibling(child); } // Move to the next line line = line->mNextLine; } return nsnull; } nsLineData* nsBlockFrame::LastLine() { nsLineData* lastLine = mLines; // Get the last line if (nsnull != lastLine) { while (nsnull != lastLine->mNextLine) { lastLine = lastLine->mNextLine; } } return lastLine; } nsresult nsBlockFrame::IncrementalReflowAfter(nsBlockReflowState& aState, nsLineData* aLine, nsresult aReflowStatus, const nsRect& aOldBounds) { // Now just reflow all the lines that follow... // XXX Obviously this needs to be more efficient return ReflowMappedFrom(aState, aLine->mNextLine); } NS_METHOD nsBlockFrame::IncrementalReflow(nsIPresContext* aPresContext, nsISpaceManager* aSpaceManager, const nsSize& aMaxSize, nsRect& aDesiredRect, nsReflowCommand& aReflowCommand, nsReflowStatus& aStatus) { NS_FRAME_TRACE_REFLOW_IN("nsBlockFrame::IncrementalReflow"); #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyLines(PR_TRUE); PreReflowCheck(); } #endif nsresult rv = NS_OK; aStatus = NS_FRAME_COMPLETE; nsBlockReflowState state; rv = InitializeState(aPresContext, aSpaceManager, aMaxSize, nsnull, state); nsIPresShell* shell = state.mPresContext->GetShell(); shell->PutCachedData(this, &state); // Is the reflow command target at us? if (this == aReflowCommand.GetTarget()) { if (aReflowCommand.GetType() == nsReflowCommand::FrameAppended) { nsLineData* lastLine = LastLine(); // Restore the state if (nsnull != lastLine) { state.RecoverState(lastLine); } // Reflow unmapped children PRInt32 kidIndex = NextChildOffset(); PRInt32 contentChildCount = mContent->ChildCount(); if (kidIndex == contentChildCount) { // There is nothing to do here if (nsnull != lastLine) { state.mY = lastLine->mBounds.YMost(); } } else { rv = ReflowUnmapped(state); } #if 0 } else if (aReflowCommand.GetType() == nsReflowCommand::ContentChanged) { // Restore our state as if the child that changed is the next frame to reflow nsLineData* line = FindLine(aReflowCommand.GetChildFrame()); state.RecoverState(line); // Get some available space to start reflowing with GetAvailableSpace(state, state.mY); // Reflow the affected line, and all the lines that follow... // XXX Obviously this needs to be more efficient rv = ReflowMappedFrom(state, line); #endif } else { NS_NOTYETIMPLEMENTED("unexpected reflow command"); } } else { // The command is passing through us. Get the next frame in the reflow chain nsIFrame* nextFrame = aReflowCommand.GetNext(); // Restore our state as if nextFrame is the next frame to reflow nsLineData* line = FindLine(nextFrame); state.RecoverState(line); // Get some available space to start reflowing with GetAvailableSpace(state, state.mY); // Reflow the affected line nsLineLayout lineLayout(state); state.mCurrentLine = &lineLayout; lineLayout.Initialize(state, line); // Have the line handle the incremental reflow nsRect oldBounds = line->mBounds; rv = lineLayout.IncrementalReflowFromChild(aReflowCommand, nextFrame); // Now place the line. It's possible it won't fit rv = PlaceLine(state, lineLayout, line); // XXX The way NS_LINE_LAYOUT_COMPLETE is being used is very confusing... if (NS_LINE_LAYOUT_COMPLETE == rv) { mLastContentOffset = line->mLastContentOffset; mLastContentIsComplete = PRBool(line->mLastContentIsComplete); state.mPrevKidFrame = lineLayout.mPrevKidFrame; // Now figure out what to do with the frames that follow rv = IncrementalReflowAfter(state, line, rv, oldBounds); } } // Return our desired rect ComputeDesiredRect(state, aMaxSize, aDesiredRect); // Set return status aStatus = NS_FRAME_COMPLETE; if (NS_LINE_LAYOUT_NOT_COMPLETE == rv) { rv = NS_OK; aStatus = NS_FRAME_NOT_COMPLETE; } // Now that reflow has finished, remove the cached pointer shell->RemoveCachedData(this); NS_RELEASE(shell); #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyLines(PR_TRUE); PostReflowCheck(aStatus); } #endif NS_FRAME_TRACE_REFLOW_OUT("nsBlockFrame::IncrementalReflow", aStatus); return rv; } void nsBlockFrame::ComputeDesiredRect(nsBlockReflowState& aState, const nsSize& aMaxSize, nsRect& aDesiredRect) { aDesiredRect.x = 0; aDesiredRect.y = 0; aDesiredRect.width = aState.mKidXMost + aState.mBorderPadding.right; if (!aState.mUnconstrainedWidth) { // Make sure we're at least as wide as the max size we were given nscoord maxWidth = aState.mAvailSize.width + aState.mBorderPadding.left + aState.mBorderPadding.right; if (aDesiredRect.width < maxWidth) { aDesiredRect.width = maxWidth; } } aState.mY += aState.mBorderPadding.bottom; nscoord lastBottomMargin = aState.mPrevPosBottomMargin - aState.mPrevNegBottomMargin; if (!aState.mUnconstrainedHeight && (lastBottomMargin > 0)) { // It's possible that we don't have room for the last bottom // margin (the last bottom margin is the margin following a block // element that we contain; it isn't applied immediately because // of the margin collapsing logic). This can happen when we are // reflowed in a limited amount of space because we don't know in // advance what the last bottom margin will be. nscoord maxY = aMaxSize.height; if (aState.mY + lastBottomMargin > maxY) { lastBottomMargin = maxY - aState.mY; if (lastBottomMargin < 0) { lastBottomMargin = 0; } } } aState.mY += lastBottomMargin; aDesiredRect.height = aState.mY; if (!aState.mBlockIsPseudo) { // Clamp the desired rect height when style height applies PRIntn ss = aState.mStyleSizeFlags; if (0 != (ss & NS_SIZE_HAS_HEIGHT)) { aDesiredRect.height = aState.mBorderPadding.top + aState.mStyleSize.height + aState.mBorderPadding.bottom; } } } //---------------------------------------------------------------------- PRBool nsBlockFrame::AddFloater(nsIPresContext* aPresContext, nsIFrame* aFloater, PlaceholderFrame* aPlaceholder) { nsIPresShell* shell = aPresContext->GetShell(); nsBlockReflowState* state = (nsBlockReflowState*) shell->GetCachedData(this); NS_RELEASE(shell); if (nsnull != state) { // Get the frame associated with the space manager, and get its // nsIAnchoredItems interface nsIFrame* frame = state->mSpaceManager->GetFrame(); nsIAnchoredItems* anchoredItems = nsnull; frame->QueryInterface(kIAnchoredItemsIID, (void**)&anchoredItems); NS_ASSERTION(nsnull != anchoredItems, "no anchored items interface"); if (nsnull != anchoredItems) { anchoredItems->AddAnchoredItem(aFloater, nsIAnchoredItems::anHTMLFloater, this); // Determine whether we place it at the top or we place it below the // current line if (IsLeftMostChild(aPlaceholder)) { if (nsnull == mRunInFloaters) { mRunInFloaters = new nsVoidArray; } mRunInFloaters->AppendElement(aPlaceholder); PlaceFloater(aPresContext, aFloater, aPlaceholder, *state); } else { // Add the placeholder to our to-do list state->mPendingFloaters.AppendElement(aPlaceholder); } return PR_TRUE; } } return PR_FALSE; } // XXX Deprecated void nsBlockFrame::PlaceFloater(nsIPresContext* aPresContext, nsIFrame* aFloater, PlaceholderFrame* aPlaceholder) { #if 0 nsIPresShell* shell = aPresContext->GetShell(); nsBlockReflowState* state = (nsBlockReflowState*) shell->GetCachedData(this); NS_RELEASE(shell); if (nsnull != state) { PlaceFloater(aPresContext, aFloater, aPlaceholder, *state); } #endif } PRBool nsBlockFrame::IsLeftMostChild(nsIFrame* aFrame) { do { nsIFrame* parent; aFrame->GetGeometricParent(parent); // See if there are any non-zero sized child frames that precede // aFrame in the child list nsIFrame* child; parent->FirstChild(child); while ((nsnull != child) && (aFrame != child)) { nsSize size; // Is the child zero-sized? child->GetSize(size); if ((size.width > 0) || (size.height > 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; } while (aFrame != this); return PR_TRUE; } // Used when placing run-in floaters (floaters displayed at the top of the // block as supposed to below the current line) void nsBlockFrame::PlaceFloater(nsIPresContext* aPresContext, nsIFrame* aFloater, PlaceholderFrame* aPlaceholder, nsBlockReflowState& aState) { nsISpaceManager* sm = aState.mSpaceManager; // Get the type of floater nsStyleDisplay* floaterDisplay; aFloater->GetStyleData(eStyleStruct_Display, (nsStyleStruct*&)floaterDisplay); // Commit some space in the space manager, and adjust our current // band of available space. nsRect region; aFloater->GetRect(region); region.y = aState.mY; if (NS_STYLE_FLOAT_LEFT == floaterDisplay->mFloats) { region.x = aState.mX; } else { NS_ASSERTION(NS_STYLE_FLOAT_RIGHT == floaterDisplay->mFloats, "bad float type"); region.x = aState.mCurrentBand.availSpace.XMost() - region.width; } // XXX Don't forget the floater's margins... sm->Translate(aState.mBorderPadding.left, 0); sm->AddRectRegion(aFloater, region); // Set the origin of the floater in world coordinates nscoord worldX, worldY; sm->GetTranslation(worldX, worldY); aFloater->MoveTo(region.x + worldX, region.y + worldY); sm->Translate(-aState.mBorderPadding.left, 0); // Update the band of available space to reflect space taken up by // the floater GetAvailableSpace(aState, aState.mY); if (nsnull != aState.mCurrentLine) { nsLineLayout& lineLayout = *aState.mCurrentLine; lineLayout.SetReflowSpace(aState.mCurrentBand.availSpace); } } // XXX It's unclear what coordinate space aY is in. Is it relative to the // upper-left origin of the containing block, or relative to aState.mY? void nsBlockFrame::PlaceBelowCurrentLineFloaters(nsBlockReflowState& aState, nsVoidArray* aFloaterList, nscoord aY) { NS_PRECONDITION(aFloaterList->Count() > 0, "no floaters"); nsISpaceManager* sm = aState.mSpaceManager; nsBlockBandData* bd = &aState.mCurrentBand; // XXX Factor this code with PlaceFloater()... PRInt32 numFloaters = aFloaterList->Count(); for (PRInt32 i = 0; i < numFloaters; i++) { PlaceholderFrame* placeholderFrame = (PlaceholderFrame*)aFloaterList->ElementAt(i); nsIFrame* floater = placeholderFrame->GetAnchoredItem(); nsRect region; // Get the band of available space // XXX This is inefficient to do this inside the loop... GetAvailableSpace(aState, aY); // Get the type of floater nsStyleDisplay* sd; floater->GetStyleData(eStyleStruct_Display, (nsStyleStruct*&)sd); floater->GetRect(region); // XXX GetAvailableSpace() is translating availSpace by aState.mY... region.y = bd->availSpace.y - aState.mY; if (NS_STYLE_FLOAT_LEFT == sd->mFloats) { region.x = bd->availSpace.x; } else { NS_ASSERTION(NS_STYLE_FLOAT_RIGHT == sd->mFloats, "bad float type"); region.x = bd->availSpace.XMost() - region.width; } // XXX Don't forget the floater's margins... sm->Translate(aState.mBorderPadding.left, 0); // XXX Temporary incremental hack sm->RemoveRegion(floater); sm->AddRectRegion(floater, region); // Set the origin of the floater in world coordinates nscoord worldX, worldY; sm->GetTranslation(worldX, worldY); floater->MoveTo(region.x + worldX, region.y + worldY); sm->Translate(-aState.mBorderPadding.left, 0); } // Pass on updated available space to the current line if (nsnull != aState.mCurrentLine) { nsLineLayout& lineLayout = *aState.mCurrentLine; lineLayout.SetReflowSpace(aState.mCurrentBand.availSpace); } } //---------------------------------------------------------------------- nsLineData* nsBlockFrame::GetFirstLine() { return mLines; } 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; }