/* -*- 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" // XXX what do we do with catastrophic errors (rv < 0)? What is the // state of the reflow world after such an error? #undef NOISY_REFLOW static NS_DEFINE_IID(kStyleDisplaySID, NS_STYLEDISPLAY_SID); static NS_DEFINE_IID(kStyleSpacingSID, NS_STYLESPACING_SID); 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::smAvailable) { nsStyleDisplay* display; // XXX Handle the case of multiple frames trapezoid->frame->GetStyleData(kStyleDisplaySID, (nsStyleStruct*&)display); if (NS_STYLE_FLOAT_RIGHT == display->mFloats) { break; } } } if (i > 0) { trapezoid = &data[i - 1]; } } if (nsBandTrapezoid::smAvailable == 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(kStyleDisplaySID, (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; mPrevMaxPosBottomMargin = 0; mPrevMaxNegBottomMargin = 0; mNextListOrdinal = -1; mFirstChildIsInsideBullet = PR_FALSE; return rv; } //---------------------------------------------------------------------- nsresult nsBlockFrame::NewFrame(nsIFrame** aInstancePtrResult, nsIContent* aContent, PRInt32 aIndexInParent, nsIFrame* aParent) { NS_PRECONDITION(nsnull != aInstancePtrResult, "null ptr"); if (nsnull == aInstancePtrResult) { return NS_ERROR_NULL_POINTER; } nsIFrame* it = new nsBlockFrame(aContent, aIndexInParent, aParent); if (nsnull == it) { return NS_ERROR_OUT_OF_MEMORY; } *aInstancePtrResult = it; return NS_OK; } nsBlockFrame::nsBlockFrame(nsIContent* aContent, PRInt32 aIndexInParent, nsIFrame* aParent) : nsHTMLContainerFrame(aContent, aIndexInParent, aParent) { } nsBlockFrame::~nsBlockFrame() { DestroyLines(); } void nsBlockFrame::DestroyLines() { } 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(SplittableType& aIsSplittable) const { aIsSplittable = frSplittableNonRectangular; return NS_OK; } NS_METHOD nsBlockFrame::CreateContinuingFrame(nsIPresContext* aCX, nsIFrame* aParent, nsIFrame*& aContinuingFrame) { nsBlockFrame* cf = new nsBlockFrame(mContent, mIndexInParent, aParent); PrepareContinuingFrame(aCX, aParent, 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); } fprintf(out, ">(%d)@%p", mIndexInParent, 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] pif=%p nif=%p", mFirstContentOffset, mLastContentOffset, (mLastContentIsComplete ? 'T' : 'F'), mPrevInFlow, mNextInFlow); // Output the rect out << mRect; // Output the children, one line at a time if (nsnull != mLines) { 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 { fputs("<>\n", out); } return NS_OK; } NS_METHOD nsBlockFrame::VerifyTree() const { nsresult rv = nsHTMLContainerFrame::VerifyTree(); if (NS_OK != rv) { return rv; } rv = VerifyLines(PR_TRUE); return rv; } 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; } //---------------------------------------------------------------------- // 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->GetIndexInParent(line->mFirstContentOffset); } break; } line = line->mNextLine; } } nsresult nsBlockFrame::ReflowInlineChild(nsIFrame* aKidFrame, nsIPresContext* aPresContext, nsReflowMetrics& aDesiredSize, const nsSize& aMaxSize, nsSize* aMaxElementSize, ReflowStatus& 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, ReflowStatus& 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->GetIndexInParent(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 VerifyLines(PR_FALSE); #endif } // XXX add in code here that notices if margin's were not provided by // the style system and when that is the case to apply the old layout // engines margin calculations. nsresult nsBlockFrame::PlaceLine(nsBlockReflowState& aState, nsLineLayout& aLineLayout, nsLineData* aLine) { nsresult rv = NS_LINE_LAYOUT_COMPLETE; nscoord topMargin = 0; nscoord bottomMargin = 0; nscoord maxNegBottomMargin = 0; nscoord maxPosBottomMargin = 0; // See if block margins apply to this line or not PRBool isBlockLine = PR_FALSE; if (1 == aLine->mChildCount) { nsIStyleContextPtr kidSC; nsIFrame* kid = aLine->mFirstChild; rv = kid->GetStyleContext(aState.mPresContext, kidSC.AssignRef()); if (NS_OK != rv) return rv; nsStyleDisplay* display = (nsStyleDisplay*) kidSC->GetData(kStyleDisplaySID); switch (display->mDisplay) { case NS_STYLE_DISPLAY_BLOCK: case NS_STYLE_DISPLAY_LIST_ITEM: isBlockLine = PR_TRUE; nsStyleSpacing* spacing = (nsStyleSpacing*) kidSC->GetData(kStyleSpacingSID); // Calculate top margin by collapsing with previous bottom margin // if any. nscoord maxNegTopMargin = 0; nscoord maxPosTopMargin = 0; if (spacing->mMargin.top < 0) { maxNegTopMargin = -spacing->mMargin.top; } else { maxPosTopMargin = spacing->mMargin.top; } nscoord maxPos = PR_MAX(aState.mPrevMaxPosBottomMargin, maxPosTopMargin); nscoord maxNeg = PR_MAX(aState.mPrevMaxNegBottomMargin, maxNegTopMargin); topMargin = maxPos - maxNeg; // Save away bottom information for later promotion into aState if (spacing->mMargin.bottom < 0) { maxNegBottomMargin = -spacing->mMargin.bottom; } else { maxPosBottomMargin = spacing->mMargin.bottom; } break; } } // Before we move 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. nscoord totalHeight = topMargin + aLine->mBounds.height; if (!aState.mUnconstrainedHeight && (aLine != mLines)) { if (aState.mY + totalHeight > aState.mAvailSize.height) { // The line will not fit rv = PushLines(aState, aLine); goto done; } } if (isBlockLine) { if (0 != topMargin) { // We have to move the line now that we know the top margin // for it. aLine->MoveLineBy(0, topMargin); aState.mY += topMargin; } } else { // Apply previous line's bottom margin before the inline-line. nscoord bottomMargin = aState.mPrevMaxPosBottomMargin - aState.mPrevMaxNegBottomMargin; if (0 != bottomMargin) { aLine->MoveLineBy(0, bottomMargin); aState.mY += topMargin; } } // Consume space and advance running values aState.mY += aLine->mBounds.height; aState.mPrevMaxNegBottomMargin = maxNegBottomMargin; aState.mPrevMaxPosBottomMargin = maxPosBottomMargin; 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? if (aState.mPendingFloaters.Count() > 0) { PlaceBelowCurrentLineFloaters(aState, 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); } done: return rv; } // 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->GetIndexInParent(nextInFlow->mFirstContentOffset); #ifdef NS_DEBUG nextInFlow->VerifyLines(PR_FALSE); #endif } mChildCount -= pushCount; #ifdef NS_DEBUG VerifyLines(PR_TRUE); #endif return NS_LINE_LAYOUT_NOT_COMPLETE; } nsresult nsBlockFrame::ReflowMapped(nsBlockReflowState& aState) { nsresult rv = NS_OK; // Get some space to start reflowing with GetAvailableSpace(aState, aState.mY); nsLineData* prevLine = nsnull; 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 (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); prevLine = line; line = line->mNextLine; aState.mPrevKidFrame = lineLayout.mPrevKidFrame; } done: aState.mCurrentLine = nsnull; #ifdef NS_DEBUG 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; // If the last line is not complete then kidPrevInFlow should be // set to the last-line's last child. if (!prevLine->mLastContentIsComplete) { kidPrevInFlow = prevLine->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 (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 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); nsStyleSpacing* mySpacing = (nsStyleSpacing*) mStyleContext->GetData(kStyleSpacingSID); // Apply border and padding adjustments for regular frames only if (!aState.mBlockIsPseudo) { aState.mY = mySpacing->mBorderPadding.top; aState.mX = mySpacing->mBorderPadding.left; aState.mAvailSize.width -= (mySpacing->mBorderPadding.left + mySpacing->mBorderPadding.right); aState.mAvailSize.height -= (mySpacing->mBorderPadding.top + mySpacing->mBorderPadding.bottom); aState.mBorderPadding = mySpacing->mBorderPadding; } else { aState.mBorderPadding.SizeTo(0, 0, 0, 0); } // 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; } #if XXX NS_METHOD nsBlockFrame::SizeTo(nscoord aWidth, nscoord aHeight) { printf("size to %g,%g\n", NS_TWIPS_TO_POINTS_FLOAT(aWidth), NS_TWIPS_TO_POINTS_FLOAT(aHeight)); return nsHTMLContainerFrame::SizeTo(aWidth, aHeight); } NS_METHOD nsBlockFrame::ResizeReflow(nsIPresContext* aPresContext, nsReflowMetrics& aDesiredSize, const nsSize& aMaxSize, nsSize* aMaxElementSize, ReflowStatus& aStatus) { nsresult rv = NS_OK; aStatus = frComplete; nsBlockReflowState state; rv = InitializeState(aPresContext, aMaxSize, aMaxElementSize, state); if (NS_OK == rv) { nsRect desiredRect; rv = DoResizeReflow(state, desiredRect, aStatus); aDesiredSize.width = desiredRect.width; aDesiredSize.height = desiredRect.height; aDesiredSize.ascent = aDesiredSize.height; aDesiredSize.descent = 0; } return rv; } #endif PRBool nsBlockFrame::MoreToReflow(nsBlockReflowState& aState) { PRBool rv = PR_FALSE; if (aState.mBlockIsPseudo) { // Get the next content object that we would like to reflow PRInt32 kidIndex = NextChildOffset(); nsIContentPtr kid = mContent->ChildAt(kidIndex); if (kid.IsNotNull()) { // Resolve style for the kid nsIStyleContextPtr kidSC = aState.mPresContext->ResolveStyleContextFor(kid, this); nsStyleDisplay* kidStyleDisplay = (nsStyleDisplay*) kidSC->GetData(kStyleDisplaySID); switch (kidStyleDisplay->mDisplay) { case NS_STYLE_DISPLAY_BLOCK: case NS_STYLE_DISPLAY_LIST_ITEM: // Block pseudo-frames do not contain other block elements break; default: rv = PR_TRUE; break; } } } else { 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, ReflowStatus& aStatus) { #ifdef NS_DEBUG VerifyLines(PR_TRUE); PreReflowCheck(); #endif #ifdef NOISY_REFLOW ListTag(stdout); printf(": Before:\n"); List(stdout, 1); #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 and our status 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.mPrevMaxPosBottomMargin - aState.mPrevMaxNegBottomMargin; 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; // Set return status aStatus = frComplete; if (NS_LINE_LAYOUT_NOT_COMPLETE == rv) { rv = NS_OK; aStatus = frNotComplete; } #ifdef NS_DEBUG // Verify that the line layout code pulled everything up when it // indicates a complete reflow. if (frComplete == 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 VerifyLines(PR_TRUE); PostReflowCheck(aStatus); #endif #ifdef NOISY_REFLOW ListTag(stdout); printf(": After:\n"); List(stdout, 1); #endif return rv; } //---------------------------------------------------------------------- NS_METHOD nsBlockFrame::ContentAppended(nsIPresShell* aShell, nsIPresContext* aPresContext, nsIContent* aContainer) { nsresult rv = NS_OK; return rv; } NS_METHOD nsBlockFrame::ContentInserted(nsIPresShell* aShell, nsIPresContext* aPresContext, nsIContent* aContainer, nsIContent* aChild, PRInt32 aIndexInParent) { nsresult rv = NS_OK; return rv; } 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; } NS_METHOD nsBlockFrame::GetReflowMetrics(nsIPresContext* aPresContext, nsReflowMetrics& aMetrics) { nsresult rv = NS_OK; return rv; } //---------------------------------------------------------------------- NS_METHOD nsBlockFrame::ResizeReflow(nsIPresContext* aPresContext, nsISpaceManager* aSpaceManager, const nsSize& aMaxSize, nsRect& aDesiredRect, nsSize* aMaxElementSize, nsIFrame::ReflowStatus& aStatus) { nsresult rv = NS_OK; aStatus = frComplete; nsBlockReflowState state; rv = InitializeState(aPresContext, aSpaceManager, aMaxSize, aMaxElementSize, state); if (NS_OK == rv) { nsRect desiredRect; rv = DoResizeReflow(state, aMaxSize, aDesiredRect, aStatus); } return rv; } NS_METHOD nsBlockFrame::IncrementalReflow(nsIPresContext* aPresContext, nsISpaceManager* aSpaceManager, const nsSize& aMaxSize, nsRect& aDesiredRect, nsReflowCommand& aReflowCommand, nsIFrame::ReflowStatus& aStatus) { nsresult rv = NS_OK; return rv; } //---------------------------------------------------------------------- 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); PlaceFloater(aPresContext, aFloater, aPlaceholder, *state); return PR_TRUE; } } return PR_FALSE; } void nsBlockFrame::PlaceFloater(nsIPresContext* aPresContext, nsIFrame* aFloater, PlaceholderFrame* aPlaceholder) { nsIPresShell* shell = aPresContext->GetShell(); nsBlockReflowState* state = (nsBlockReflowState*) shell->GetCachedData(this); NS_RELEASE(shell); if (nsnull != state) { PlaceFloater(aPresContext, aFloater, aPlaceholder, *state); } } 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; } void nsBlockFrame::PlaceFloater(nsIPresContext* aPresContext, nsIFrame* aFloater, PlaceholderFrame* aPlaceholder, nsBlockReflowState& aState) { // If the floater is the left-most non-zero size child frame then insert // it before the current line; otherwise add it to the below-current-line // todo list and we'll handle it when we flush out the line if (IsLeftMostChild(aPlaceholder)) { nsISpaceManager* sm = aState.mSpaceManager; // Get the type of floater nsIStyleContextPtr styleContext; aFloater->GetStyleContext(aPresContext, styleContext.AssignRef()); nsStyleDisplay* floaterDisplay = (nsStyleDisplay*) styleContext->GetData(kStyleDisplaySID); // 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(region, aFloater); // 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); } } else { // Add the floater to our to-do list aState.mPendingFloaters.AppendElement(aFloater); } } void nsBlockFrame::PlaceBelowCurrentLineFloaters(nsBlockReflowState& aState, nscoord aY) { NS_PRECONDITION(aState.mPendingFloaters.Count() > 0, "no floaters"); nsISpaceManager* sm = aState.mSpaceManager; nsBlockBandData* bd = &aState.mCurrentBand; // XXX Factor this code with PlaceFloater()... PRInt32 numFloaters = aState.mPendingFloaters.Count(); for (PRInt32 i = 0; i < numFloaters; i++) { nsIFrame* floater = (nsIFrame*) aState.mPendingFloaters[i]; 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 nsIStyleContextPtr styleContext; floater->GetStyleContext(aState.mPresContext, styleContext.AssignRef()); nsStyleDisplay* sd = (nsStyleDisplay*) styleContext->GetData(kStyleDisplaySID); floater->GetRect(region); region.y = bd->availSpace.y; 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); sm->AddRectRegion(region, floater); // 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); } aState.mPendingFloaters.Clear(); // 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; }