/* -*- 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 "nsHTMLContainerFrame.h" #include "nsFrameReflowState.h" #include "nsLineLayout.h" #include "nsInlineReflow.h" #include "nsAbsoluteFrame.h" #include "nsPlaceholderFrame.h" #include "nsStyleConsts.h" #include "nsHTMLIIDs.h" #include "nsCSSRendering.h" #include "nsIAnchoredItems.h" #include "nsIFloaterContainer.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 "js/jsapi.h" //#include "nsDOMEvent.h" #define DOM_EVENT_INIT 0x0001 #include "prprf.h" // XXX These are unfortunate dependencies #include "nsIHTMLContent.h" #include "nsHTMLImage.h" /* 52b33130-0b99-11d2-932e-00805f8add32 */ #define NS_BLOCK_FRAME_CID \ { 0x52b33130, 0x0b99, 0x11d2, {0x93, 0x2e, 0x00, 0x80, 0x5f, 0x8a, 0xdd, 0x32}} const nsIID kBlockFrameCID = NS_BLOCK_FRAME_CID; // 09-15-98: make sure that the outer container of the block (e.g. the // body sets up the outer top margin feed in properly so that the top // margin collapses properly with the body margin. Note that for the // block that is reflowing a body's children the body will have a // padding value that wants to be collapsed with the blocks // first-child's top-margin (e.g. BODY P foo needs to collapse P's margin // with the BODY's padding // 09-17-98: I don't like keeping mInnerBottomMargin // 09-18-98: I think I can get rid of the distinction in this code // between blocks and inlines. The only real issue remaining is that // real blocks (that implement nsIRunaround) require a slightly // different coordinate system to begin reflow at (x/y/widht/height // values to aInlineReflow.Init) than do regular frames that do not // implement nsIRunaround. Factoring break-before/break-after should // be easy and handle the other 90% difference. // 09-18-98: floating block elements don't size quite right because we // wrap them in a body frame and the body frame doesn't honor the css // width/height properties (among others!). The body code needs // updating. //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX //XXX below this line is dubious (the entire file? :-) // XXX MULTICOL support; note that multicol will end up using the // equivalent of pagination! Therefore we should probably make sure // the pagination code isn't completely stupid. // XXX page-breaks // XXX out of memory checks are missing // XXX Tuneup: if mNoWrap is true and we are given a ResizeReflow we // can just return because there's nothing to do!; this is true in // nsInlineFrame too! // Except that noWrap is ignored if the containers width is too small // (like a table cell with a fixed width.) //---------------------------------------------------------------------- // XXX It's really important that blocks strip out extra whitespace; // otherwise we will see ALOT of this, which will waste memory big time: // // // // // // ... //---------------------------------------------------------------------- // XXX I don't want mFirstChild, mChildCount, mOverflowList, //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX class BulletFrame; struct LineData; class nsBlockFrame; /* 52b33130-0b99-11d2-932e-00805f8add32 */ #define NS_BLOCK_FRAME_CID \ { 0x52b33130, 0x0b99, 0x11d2, {0x93, 0x2e, 0x00, 0x80, 0x5f, 0x8a, 0xdd, 0x32}} // XXX hide this as soon as list bullet code is cleaned up struct nsBlockReflowState : public nsFrameReflowState { nsBlockReflowState(nsIPresContext& aPresContext, const nsHTMLReflowState& aReflowState, const nsHTMLReflowMetrics& aMetrics); ~nsBlockReflowState(); /** * Update the mCurrentBand data based on the current mY position. */ void GetAvailableSpace(); void AddFloater(nsPlaceholderFrame* aPlaceholderFrame); void PlaceFloater(nsPlaceholderFrame* aFloater, PRBool& aIsLeftFloater); void PlaceFloaters(nsVoidArray* aFloaters); void ClearFloaters(PRUint8 aBreakType); PRBool IsLeftMostChild(nsIFrame* aFrame); nsLineLayout mLineLayout; nsInlineReflow* mInlineReflow; nsISpaceManager* mSpaceManager; nscoord mSpaceManagerX, mSpaceManagerY; nsBlockFrame* mBlock; nsBlockFrame* mNextInFlow; PRBool mInlineReflowPrepared; PRUint8 mTextAlign; PRUint8 mPrevMarginFlags; nscoord mBottomEdge; // maximum Y PRBool mUnconstrainedWidth; PRBool mUnconstrainedHeight; nscoord mY; nscoord mKidXMost; nsIFrame* mPrevChild; LineData* mFreeList; nsVoidArray mPendingFloaters; LineData* mCurrentLine; LineData* mPrevLine; // The next list ordinal for counting list bullets PRInt32 mNextListOrdinal; // XXX what happens if we need more than 12 trapezoids? struct BlockBandData : public nsBandData { // Trapezoids used during band processing nsBandTrapezoid data[12]; // Bounding rect of available space between any left and right floaters nsRect availSpace; BlockBandData() { size = 12; trapezoids = data; } /** * Computes the bounding rect of the available space, i.e. space * between any left and right floaters Uses the current trapezoid * data, see nsISpaceManager::GetBandData(). Also updates member * data "availSpace". */ void ComputeAvailSpaceRect(); /** * Compute the height of the shortest floater on the current line. * This is actually pretty easy because if there are any floaters * on the line then the shortest if the height of the band. */ PRBool FindShortestFloaterHeight(nscoord& aHeight); }; BlockBandData mCurrentBand; }; // XXX This is vile. Make it go away void nsLineLayout::AddFloater(nsPlaceholderFrame* aFrame) { mBlockReflowState->AddFloater(aFrame); } //---------------------------------------------------------------------- #define nsBlockFrameSuper nsHTMLContainerFrame class nsBlockFrame : public nsBlockFrameSuper, public nsIFloaterContainer { public: nsBlockFrame(nsIContent* aContent, nsIFrame* aParent); ~nsBlockFrame(); // nsISupports NS_IMETHOD QueryInterface(const nsIID& aIID, void** aInstancePtr); // nsIFrame NS_IMETHOD Init(nsIPresContext& aPresContext, nsIFrame* aChildList); NS_IMETHOD FirstChild(nsIFrame*& aFirstChild) const; NS_IMETHOD DeleteFrame(nsIPresContext& aPresContext); NS_IMETHOD IsSplittable(nsSplittableType& aIsSplittable) const; NS_IMETHOD CreateContinuingFrame(nsIPresContext& aPresContext, nsIFrame* aParent, nsIStyleContext* aStyleContext, nsIFrame*& aContinuingFrame); NS_IMETHOD Paint(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect); NS_IMETHOD IsPercentageBase(PRBool& aBase) const { aBase = PR_TRUE; return NS_OK; } NS_IMETHOD List(FILE* out, PRInt32 aIndent, nsIListFilter *aFilter) const; NS_IMETHOD ListTag(FILE* out) const; NS_IMETHOD VerifyTree() const; // nsIHTMLReflow NS_IMETHOD Reflow(nsIPresContext& aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus); // nsIFloaterContainer virtual PRBool AddFloater(nsIPresContext* aPresContext, const nsHTMLReflowState& aPlaceholderReflowState, nsIFrame* aFloater, nsPlaceholderFrame* aPlaceholder); #ifdef DO_SELECTION NS_IMETHOD HandleEvent(nsIPresContext& aPresContext, nsGUIEvent* aEvent, nsEventStatus& aEventStatus); NS_IMETHOD HandleDrag(nsIPresContext& aPresContext, nsGUIEvent* aEvent, nsEventStatus& aEventStatus); nsIFrame * FindHitFrame(nsBlockFrame * aBlockFrame, const nscoord aX, const nscoord aY, const nsPoint & aPoint); #endif virtual PRBool DeleteChildsNextInFlow(nsIPresContext& aPresContext, nsIFrame* aNextInFlow); void SetFlags(PRUint32 aFlags) { mFlags = aFlags; } void RecoverLineMargins(nsBlockReflowState& aState, LineData* aPrevLine, nscoord& aTopMarginResult, nscoord& aBottomMarginResult); PRUintn CalculateMargins(nsBlockReflowState& aState, LineData* aLine, PRBool aInlineContext, nscoord& aTopMarginResult, nscoord& aBottomMarginResult); void SlideFrames(LineData* aLine, nscoord aDY); PRBool DrainOverflowLines(); PRBool RemoveChild(LineData* aLines, nsIFrame* aChild); PRIntn GetSkipSides() const; PRBool IsPseudoFrame() const; nsresult InitialReflow(nsBlockReflowState& aState); nsresult FrameAppendedReflow(nsBlockReflowState& aState); nsresult InsertNewFrame(nsIPresContext& aPresContext, nsBlockFrame* aParentFrame, nsIFrame* aNewFrame, nsIFrame* aPrevSibling); nsresult FrameInsertedReflow(nsBlockReflowState& aState); nsresult FrameRemovedReflow(nsBlockReflowState& aState); nsresult StyleChangedReflow(nsBlockReflowState& aState); nsresult FindTextRuns(nsBlockReflowState& aState); nsresult ChildIncrementalReflow(nsBlockReflowState& aState); nsresult ResizeReflow(nsBlockReflowState& aState); void ComputeFinalSize(nsBlockReflowState& aState, nsHTMLReflowMetrics& aMetrics); nsresult ReflowLinesAt(nsBlockReflowState& aState, LineData* aLine); PRBool ReflowLine(nsBlockReflowState& aState, LineData* aLine, nsReflowStatus& aReflowResult); PRBool PlaceLine(nsBlockReflowState& aState, LineData* aLine, nsReflowStatus aReflowStatus); void FindFloaters(LineData* aLine); void PrepareInlineReflow(nsBlockReflowState& aState, nsIFrame* aFrame, PRBool aIsBlock); PRBool ReflowInlineFrame(nsBlockReflowState& aState, LineData* aLine, nsIFrame* aFrame, nsReflowStatus& aResult); nsresult SplitLine(nsBlockReflowState& aState, LineData* aLine, nsIFrame* aFrame, PRBool aLineWasComplete); PRBool ReflowBlockFrame(nsBlockReflowState& aState, LineData* aLine, nsIFrame* aFrame, nsReflowStatus& aResult); PRBool PullFrame(nsBlockReflowState& aState, LineData* aToLine, LineData** aFromList, PRBool aUpdateGeometricParent, nsReflowStatus& aResult); void PushLines(nsBlockReflowState& aState); void ReflowFloater(nsIPresContext& aPresContext, nsBlockReflowState& aState, nsIFrame* aFloaterFrame); void PaintChildren(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect); void PaintChild(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect, nsIFrame* aFrame, PRBool aAlwaysRender); nsresult AppendNewFrames(nsIPresContext& aPresContext, nsIFrame*); void RenumberLists(nsBlockReflowState& aState); #ifdef NS_DEBUG PRBool IsChild(nsIFrame* aFrame); #endif LineData* mLines; LineData* mOverflowLines; // Text run information nsTextRun* mTextRuns; // For list-item frames, this is the bullet frame. BulletFrame* mBullet; // Body configuration flags passed into this block when this block // is used by the body. PRUint32 mFlags; }; //---------------------------------------------------------------------- class BulletFrame : public nsFrame { public: BulletFrame(nsIContent* aContent, nsIFrame* aParentFrame); virtual ~BulletFrame(); // nsIFrame NS_IMETHOD DeleteFrame(nsIPresContext& aPresContext); NS_IMETHOD Paint(nsIPresContext &aCX, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect); NS_IMETHOD ListTag(FILE* out) const; NS_IMETHOD List(FILE* out, PRInt32 aIndent) const; // nsIHTMLReflow NS_IMETHOD Reflow(nsIPresContext& aPresContext, nsHTMLReflowMetrics& aMetrics, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus); void SetListItemOrdinal(nsBlockReflowState& aBlockState); void GetDesiredSize(nsIPresContext* aPresContext, const nsHTMLReflowState& aReflowState, nsHTMLReflowMetrics& aMetrics); void GetListItemText(nsIPresContext& aCX, const nsStyleList& aMol, nsString& aResult); PRInt32 mOrdinal; nsMargin mPadding; nsHTMLImageLoader mImageLoader; }; BulletFrame::BulletFrame(nsIContent* aContent, nsIFrame* aParentFrame) : nsFrame(aContent, aParentFrame) { } BulletFrame::~BulletFrame() { } NS_IMETHODIMP BulletFrame::DeleteFrame(nsIPresContext& aPresContext) { // Release image loader first so that it's refcnt can go to zero mImageLoader.DestroyLoader(); return nsFrame::DeleteFrame(aPresContext); } NS_IMETHODIMP BulletFrame::ListTag(FILE* out) const { fprintf(out, "Bullet(%d)@%p", ContentIndexInContainer(this), this); return NS_OK; } NS_IMETHODIMP BulletFrame::List(FILE* out, PRInt32 aIndent) const { PRInt32 i; for (i = aIndent; --i >= 0; ) fputs(" ", out); fprintf(out, "Bullet(%d)@%p ", ContentIndexInContainer(this), this); nsIView* view; GetView(view); if (nsnull != view) { fprintf(out, " [view=%p]", view); } out << mRect; if (0 != mState) { fprintf(out, " [state=%08x]", mState); } fputs("<>\n", out); return NS_OK; } NS_METHOD BulletFrame::Paint(nsIPresContext& aCX, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect) { const nsStyleDisplay* disp = (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); nscoord width; if (disp->mVisible) { const nsStyleList* myList = (const nsStyleList*)mStyleContext->GetStyleData(eStyleStruct_List); if (myList->mListStyleImage.Length() > 0) { nsIImage* image = mImageLoader.GetImage(); if (nsnull == image) { if (!mImageLoader.GetLoadImageFailed()) { // No image yet return NS_OK; } } else { nsRect innerArea(mPadding.left, mPadding.top, mRect.width - (mPadding.left + mPadding.right), mRect.height - (mPadding.top + mPadding.bottom)); aRenderingContext.DrawImage(image, innerArea); return NS_OK; } } const nsStyleFont* myFont = (const nsStyleFont*)mStyleContext->GetStyleData(eStyleStruct_Font); const nsStyleColor* myColor = (const nsStyleColor*)mStyleContext->GetStyleData(eStyleStruct_Color); nsIFontMetrics* fm; aRenderingContext.SetColor(myColor->mColor); nsAutoString text; switch (myList->mListStyleType) { case NS_STYLE_LIST_STYLE_NONE: break; default: case NS_STYLE_LIST_STYLE_BASIC: case NS_STYLE_LIST_STYLE_DISC: aRenderingContext.FillEllipse(mPadding.left, mPadding.top, mRect.width - (mPadding.left + mPadding.right), mRect.height - (mPadding.top + mPadding.bottom)); break; case NS_STYLE_LIST_STYLE_CIRCLE: aRenderingContext.DrawEllipse(mPadding.left, mPadding.top, mRect.width - (mPadding.left + mPadding.right), mRect.height - (mPadding.top + mPadding.bottom)); break; case NS_STYLE_LIST_STYLE_SQUARE: aRenderingContext.FillRect(mPadding.left, mPadding.top, mRect.width - (mPadding.left + mPadding.right), mRect.height - (mPadding.top + mPadding.bottom)); break; case NS_STYLE_LIST_STYLE_DECIMAL: case NS_STYLE_LIST_STYLE_LOWER_ROMAN: case NS_STYLE_LIST_STYLE_UPPER_ROMAN: case NS_STYLE_LIST_STYLE_LOWER_ALPHA: case NS_STYLE_LIST_STYLE_UPPER_ALPHA: fm = aCX.GetMetricsFor(myFont->mFont); GetListItemText(aCX, *myList, text); aRenderingContext.SetFont(fm); aRenderingContext.GetWidth(text, width); aRenderingContext.DrawString(text, mPadding.left, mPadding.top, width); NS_RELEASE(fm); break; } } return NS_OK; } void BulletFrame::SetListItemOrdinal(nsBlockReflowState& aReflowState) { // Assume that the ordinal comes from the block reflow state mOrdinal = aReflowState.mNextListOrdinal; // Try to get value directly from the list-item, if it specifies a // value attribute. Note: we do this with our parent's content // because our parent is the list-item. nsHTMLValue value; nsIContent* parentContent; mContentParent->GetContent(parentContent); nsIHTMLContent* hc; if (NS_OK == parentContent->QueryInterface(kIHTMLContentIID, (void**) &hc)) { if (NS_CONTENT_ATTR_HAS_VALUE == hc->GetAttribute(nsHTMLAtoms::value, value)) { if (eHTMLUnit_Integer == value.GetUnit()) { // Use ordinal specified by the value attribute mOrdinal = value.GetIntValue(); if (mOrdinal <= 0) { mOrdinal = 1; } } } NS_RELEASE(hc); } NS_RELEASE(parentContent); aReflowState.mNextListOrdinal = mOrdinal + 1; } static const char* gLowerRomanCharsA = "ixcm"; static const char* gUpperRomanCharsA = "IXCM"; static const char* gLowerRomanCharsB = "vld?"; static const char* gUpperRomanCharsB = "VLD?"; static const char* gLowerAlphaChars = "abcdefghijklmnopqrstuvwxyz"; static const char* gUpperAlphaChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // XXX change roman/alpha to use unsigned math so that maxint and // maxnegint will work void BulletFrame::GetListItemText(nsIPresContext& aCX, const nsStyleList& aListStyle, nsString& result) { PRInt32 ordinal = mOrdinal; char cbuf[40]; switch (aListStyle.mListStyleType) { case NS_STYLE_LIST_STYLE_DECIMAL: PR_snprintf(cbuf, sizeof(cbuf), "%ld", ordinal); result.Append(cbuf); break; case NS_STYLE_LIST_STYLE_LOWER_ROMAN: case NS_STYLE_LIST_STYLE_UPPER_ROMAN: { if (ordinal <= 0) { ordinal = 1; } nsAutoString addOn, decStr; decStr.Append(ordinal, 10); PRIntn len = decStr.Length(); const PRUnichar* dp = decStr.GetUnicode(); const PRUnichar* end = dp + len; PRIntn romanPos = len; PRIntn n; const char* achars; const char* bchars; if (aListStyle.mListStyleType == NS_STYLE_LIST_STYLE_LOWER_ROMAN) { achars = gLowerRomanCharsA; bchars = gLowerRomanCharsB; } else { achars = gUpperRomanCharsA; bchars = gUpperRomanCharsB; } for (; dp < end; dp++) { romanPos--; addOn.SetLength(0); switch(*dp) { case '3': addOn.Append(achars[romanPos]); case '2': addOn.Append(achars[romanPos]); case '1': addOn.Append(achars[romanPos]); break; case '4': addOn.Append(achars[romanPos]); // FALLTHROUGH case '5': case '6': case '7': case '8': addOn.Append(bchars[romanPos]); for(n=0;n<(*dp-'5');n++) { addOn.Append(achars[romanPos]); } break; case '9': addOn.Append(achars[romanPos]); addOn.Append(achars[romanPos+1]); break; default: break; } result.Append(addOn); } } break; case NS_STYLE_LIST_STYLE_LOWER_ALPHA: case NS_STYLE_LIST_STYLE_UPPER_ALPHA: { PRInt32 anOffset = -1; PRInt32 aBase = 26; PRInt32 ndex=0; PRInt32 root=1; PRInt32 next=aBase; PRInt32 expn=1; const char* chars = (aListStyle.mListStyleType == NS_STYLE_LIST_STYLE_LOWER_ALPHA) ? gLowerAlphaChars : gUpperAlphaChars; // must be positive here... if (ordinal <= 0) { ordinal = 1; } ordinal--; // a == 0 // scale up in baseN; exceed current value. while (next<=ordinal) { root=next; next*=aBase; expn++; } while (0!=(expn--)) { ndex = ((root<=ordinal) && (0!=root)) ? (ordinal/root): 0; ordinal %= root; if (root>1) result.Append(chars[ndex+anOffset]); else result.Append(chars[ndex]); root /= aBase; } } break; } result.Append("."); } #define MIN_BULLET_SIZE 5 // from laytext.c static nsresult UpdateBulletCB(nsIPresContext& aPresContext, nsIFrame* aFrame, PRIntn aStatus) { nsresult rv = NS_OK; if (NS_IMAGE_LOAD_STATUS_SIZE_AVAILABLE & aStatus) { // Now that the size is available, trigger a reflow of the bullet // frame. nsIPresShell* shell; shell = aPresContext.GetShell(); if (nsnull != shell) { nsIReflowCommand* cmd; rv = NS_NewHTMLReflowCommand(&cmd, aFrame, nsIReflowCommand::ContentChanged); if (NS_OK == rv) { shell->EnterReflowLock(); shell->AppendReflowCommand(cmd); NS_RELEASE(cmd); shell->ExitReflowLock(); } NS_RELEASE(shell); } } return rv; } void BulletFrame::GetDesiredSize(nsIPresContext* aCX, const nsHTMLReflowState& aReflowState, nsHTMLReflowMetrics& aMetrics) { const nsStyleList* myList = (const nsStyleList*)mStyleContext->GetStyleData(eStyleStruct_List); nscoord ascent; if (myList->mListStyleImage.Length() > 0) { mImageLoader.SetURL(myList->mListStyleImage); mImageLoader.GetDesiredSize(aCX, aReflowState, this, UpdateBulletCB, aMetrics); if (!mImageLoader.GetLoadImageFailed()) { nsHTMLContainerFrame::CreateViewForFrame(*aCX, this, mStyleContext, PR_FALSE); aMetrics.ascent = aMetrics.height; aMetrics.descent = 0; return; } } const nsStyleFont* myFont = (const nsStyleFont*)mStyleContext->GetStyleData(eStyleStruct_Font); nsIFontMetrics* fm = aCX->GetMetricsFor(myFont->mFont); nscoord bulletSize; float p2t; float t2p; nsAutoString text; switch (myList->mListStyleType) { case NS_STYLE_LIST_STYLE_NONE: aMetrics.width = 0; aMetrics.height = 0; aMetrics.ascent = 0; aMetrics.descent = 0; break; default: case NS_STYLE_LIST_STYLE_DISC: case NS_STYLE_LIST_STYLE_CIRCLE: case NS_STYLE_LIST_STYLE_BASIC: case NS_STYLE_LIST_STYLE_SQUARE: t2p = aCX->GetTwipsToPixels(); fm->GetMaxAscent(ascent); bulletSize = NSTwipsToIntPixels((nscoord)NSToIntRound(0.8f * (float(ascent) / 2.0f)), t2p); if (bulletSize < 1) { bulletSize = MIN_BULLET_SIZE; } p2t = aCX->GetPixelsToTwips(); bulletSize = NSIntPixelsToTwips(bulletSize, p2t); mPadding.bottom = ascent / 8; if (NS_STYLE_LIST_STYLE_POSITION_INSIDE == myList->mListStylePosition) { mPadding.right = bulletSize / 2; } aMetrics.width = mPadding.right + bulletSize; aMetrics.height = mPadding.bottom + bulletSize; aMetrics.ascent = mPadding.bottom + bulletSize; aMetrics.descent = 0; break; case NS_STYLE_LIST_STYLE_DECIMAL: case NS_STYLE_LIST_STYLE_LOWER_ROMAN: case NS_STYLE_LIST_STYLE_UPPER_ROMAN: case NS_STYLE_LIST_STYLE_LOWER_ALPHA: case NS_STYLE_LIST_STYLE_UPPER_ALPHA: GetListItemText(*aCX, *myList, text); fm->GetHeight(aMetrics.height); if (NS_STYLE_LIST_STYLE_POSITION_INSIDE == myList->mListStylePosition) { // Inside bullets need some extra width to get the padding // between the list item and the content that follows. mPadding.right = aMetrics.height / 2; // From old layout engine } aReflowState.rendContext->SetFont(fm); aReflowState.rendContext->GetWidth(text, aMetrics.width); aMetrics.width += mPadding.right; fm->GetMaxAscent(aMetrics.ascent); fm->GetMaxDescent(aMetrics.descent); break; } NS_RELEASE(fm); } NS_IMETHODIMP BulletFrame::Reflow(nsIPresContext& aPresContext, nsHTMLReflowMetrics& aMetrics, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { // Get the base size GetDesiredSize(&aPresContext, aReflowState, aMetrics); // Add in the border and padding; split the top/bottom between the // ascent and descent to make things look nice const nsStyleSpacing* space =(const nsStyleSpacing*) mStyleContext->GetStyleData(eStyleStruct_Spacing); nsMargin borderPadding; space->CalcBorderPaddingFor(this, borderPadding); aMetrics.width += borderPadding.left + borderPadding.right; aMetrics.height += borderPadding.top + borderPadding.bottom; aMetrics.ascent += borderPadding.top; aMetrics.descent += borderPadding.bottom; if (nsnull != aMetrics.maxElementSize) { aMetrics.maxElementSize->width = aMetrics.width; aMetrics.maxElementSize->height = aMetrics.height; } aStatus = NS_FRAME_COMPLETE; return NS_OK; } //---------------------------------------------------------------------- #define LINE_IS_DIRTY 0x1 #define LINE_IS_BLOCK 0x2 #define LINE_LAST_CONTENT_IS_COMPLETE 0x4 #define LINE_NEED_DID_REFLOW 0x8 #define LINE_TOP_MARGIN_IS_AUTO 0x10 #define LINE_BOTTOM_MARGIN_IS_AUTO 0x20 struct LineData { LineData(nsIFrame* aFrame, PRInt32 aCount, PRUint16 flags) { mFirstChild = aFrame; mChildCount = aCount; mState = LINE_IS_DIRTY | LINE_NEED_DID_REFLOW | flags; mFloaters = nsnull; mNext = nsnull; mBounds.SetRect(0,0,0,0); mCarriedOutTopMargin = 0; mCarriedOutBottomMargin = 0; } ~LineData(); void List(FILE* out, PRInt32 aIndent, nsIListFilter *aFilter = nsnull, PRBool aOutputMe=PR_TRUE) const; nsIFrame* LastChild() const; PRBool IsLastChild(nsIFrame* aFrame) const; void SetLastContentIsComplete() { mState |= LINE_LAST_CONTENT_IS_COMPLETE; } void ClearLastContentIsComplete() { mState &= ~LINE_LAST_CONTENT_IS_COMPLETE; } void SetLastContentIsComplete(PRBool aValue) { if (aValue) { SetLastContentIsComplete(); } else { ClearLastContentIsComplete(); } } PRBool GetLastContentIsComplete() { return 0 != (LINE_LAST_CONTENT_IS_COMPLETE & mState); } PRBool IsBlock() const { return 0 != (LINE_IS_BLOCK & mState); } void SetIsBlock() { mState |= LINE_IS_BLOCK; } void ClearIsBlock() { mState &= ~LINE_IS_BLOCK; } void SetIsBlock(PRBool aValue) { if (aValue) { SetIsBlock(); } else { ClearIsBlock(); } } void MarkDirty() { mState |= LINE_IS_DIRTY; } void ClearDirty() { mState &= ~LINE_IS_DIRTY; } void SetNeedDidReflow() { mState |= LINE_NEED_DID_REFLOW; } void ClearNeedDidReflow() { mState &= ~LINE_NEED_DID_REFLOW; } PRBool NeedsDidReflow() { return 0 != (LINE_NEED_DID_REFLOW & mState); } PRBool IsDirty() const { return 0 != (LINE_IS_DIRTY & mState); } PRUint16 GetState() const { return mState; } char* StateToString(char* aBuf, PRInt32 aBufSize) const; PRBool Contains(nsIFrame* aFrame) const; void SetMarginFlags(PRUintn aFlags) { mState &= ~(LINE_TOP_MARGIN_IS_AUTO|LINE_BOTTOM_MARGIN_IS_AUTO); if (NS_CARRIED_TOP_MARGIN_IS_AUTO & aFlags) { mState |= LINE_TOP_MARGIN_IS_AUTO; } if (NS_CARRIED_BOTTOM_MARGIN_IS_AUTO & aFlags) { mState |= LINE_BOTTOM_MARGIN_IS_AUTO; } } PRUintn GetMarginFlags() { return ((LINE_TOP_MARGIN_IS_AUTO & mState) ? NS_CARRIED_TOP_MARGIN_IS_AUTO : 0) | ((LINE_BOTTOM_MARGIN_IS_AUTO & mState) ? NS_CARRIED_BOTTOM_MARGIN_IS_AUTO : 0); } void UnplaceFloaters(nsISpaceManager* aSpaceManager) { if (nsnull != mFloaters) { PRInt32 i, n = mFloaters->Count(); for (i = 0; i < n; i++) { nsPlaceholderFrame* pf = (nsPlaceholderFrame*) mFloaters->ElementAt(i); nsIFrame* floater = pf->GetAnchoredItem(); aSpaceManager->RemoveRegion(floater); } } } #ifdef NS_DEBUG void Verify(); #endif nsIFrame* mFirstChild; PRUint16 mChildCount; PRUint16 mState; nsRect mBounds; nscoord mCarriedOutTopMargin; nscoord mCarriedOutBottomMargin; nsVoidArray* mFloaters; LineData* mNext; }; LineData::~LineData() { if (nsnull != mFloaters) { delete mFloaters; } } static void ListFloaters(FILE* out, nsVoidArray* aFloaters) { PRInt32 i, n = aFloaters->Count(); for (i = 0; i < n; i++) { nsIFrame* frame = (nsIFrame*) aFloaters->ElementAt(i); frame->ListTag(out); if (i < n - 1) fputs(" ", out); } } static void ListTextRuns(FILE* out, PRInt32 aIndent, nsTextRun* aRuns) { while (nsnull != aRuns) { aRuns->List(out, aIndent); aRuns = aRuns->mNext; } } char* LineData::StateToString(char* aBuf, PRInt32 aBufSize) const { PR_snprintf(aBuf, aBufSize, "%s,%s,%scomplete", (mState & LINE_IS_DIRTY) ? "dirty" : "clean", (mState & LINE_IS_BLOCK) ? "block" : "inline", (mState & LINE_LAST_CONTENT_IS_COMPLETE) ? "" : "!"); return aBuf; } void LineData::List(FILE* out, PRInt32 aIndent, nsIListFilter *aFilter, PRBool aOutputMe) const { PRInt32 i; if (aOutputMe) { for (i = aIndent; --i >= 0; ) fputs(" ", out); char cbuf[100]; fprintf(out, "line %p: count=%d state=%s", this, mChildCount, StateToString(cbuf, sizeof(cbuf))); if (0 != mCarriedOutTopMargin) { fprintf(out, " tm=%d", mCarriedOutTopMargin); } if (0 != mCarriedOutBottomMargin) { fprintf(out, " bm=%d", mCarriedOutBottomMargin); } out << mBounds; fprintf(out, "<\n"); } nsIFrame* frame = mFirstChild; PRInt32 n = mChildCount; while (--n >= 0) { frame->List(out, aIndent + 1, aFilter); frame->GetNextSibling(frame); } if (aOutputMe) { for (i = aIndent; --i >= 0; ) fputs(" ", out); if (nsnull != mFloaters) { fputs("> bcl-floaters=<", out); ListFloaters(out, mFloaters); } fputs(">\n", out); } } nsIFrame* LineData::LastChild() const { nsIFrame* frame = mFirstChild; PRInt32 n = mChildCount - 1; while (--n >= 0) { frame->GetNextSibling(frame); } return frame; } PRBool LineData::IsLastChild(nsIFrame* aFrame) const { nsIFrame* lastFrame = LastChild(); return aFrame == lastFrame; } PRBool LineData::Contains(nsIFrame* aFrame) const { PRInt32 n = mChildCount; nsIFrame* frame = mFirstChild; while (--n >= 0) { if (frame == aFrame) { return PR_TRUE; } frame->GetNextSibling(frame); } return PR_FALSE; } static PRInt32 LengthOf(nsIFrame* aFrame) { PRInt32 result = 0; while (nsnull != aFrame) { result++; aFrame->GetNextSibling(aFrame); } return result; } #ifdef NS_DEBUG void LineData::Verify() { nsIFrame* lastFrame = LastChild(); if (nsnull != lastFrame) { nsIFrame* nextInFlow; lastFrame->GetNextInFlow(nextInFlow); if (GetLastContentIsComplete()) { NS_ASSERTION(nsnull == nextInFlow, "bad mState"); } if (nsnull != mNext) { nsIFrame* nextSibling; lastFrame->GetNextSibling(nextSibling); NS_ASSERTION(mNext->mFirstChild == nextSibling, "bad line list"); } } PRInt32 len = LengthOf(mFirstChild); NS_ASSERTION(len >= mChildCount, "bad mChildCount"); } static void VerifyLines(LineData* aLine) { while (nsnull != aLine) { aLine->Verify(); aLine = aLine->mNext; } } static void VerifyChildCount(LineData* aLines, PRBool aEmptyOK = PR_FALSE) { if (nsnull != aLines) { PRInt32 childCount = LengthOf(aLines->mFirstChild); PRInt32 sum = 0; LineData* line = aLines; while (nsnull != line) { if (!aEmptyOK) { NS_ASSERTION(0 != line->mChildCount, "empty line left in line list"); } sum += line->mChildCount; line = line->mNext; } if (sum != childCount) { printf("Bad sibling list/line mChildCount's\n"); LineData* line = aLines; while (nsnull != line) { line->List(stdout, 1); if (nsnull != line->mNext) { nsIFrame* lastFrame = line->LastChild(); if (nsnull != lastFrame) { nsIFrame* nextSibling; lastFrame->GetNextSibling(nextSibling); if (line->mNext->mFirstChild != nextSibling) { printf(" [list broken: nextSibling=%p mNext->mFirstChild=%p]\n", nextSibling, line->mNext->mFirstChild); } } } line = line->mNext; } NS_ASSERTION(sum == childCount, "bad sibling list/line mChildCount's"); } } } #endif static void DeleteLineList(nsIPresContext& aPresContext, LineData* aLine) { if (nsnull != aLine) { // Delete our child frames before doing anything else. In particular // we do all of this before our base class releases it's hold on the // view. for (nsIFrame* child = aLine->mFirstChild; child; ) { nsIFrame* nextChild; child->GetNextSibling(nextChild); child->DeleteFrame(aPresContext); child = nextChild; } while (nsnull != aLine) { LineData* next = aLine->mNext; delete aLine; aLine = next; } } } static LineData* LastLine(LineData* aLine) { if (nsnull != aLine) { while (nsnull != aLine->mNext) { aLine = aLine->mNext; } } return aLine; } static LineData* FindLineContaining(LineData* aLine, nsIFrame* aFrame) { while (nsnull != aLine) { if (aLine->Contains(aFrame)) { return aLine; } aLine = aLine->mNext; } return nsnull; } //---------------------------------------------------------------------- void nsBlockReflowState::BlockBandData::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, or when we've // looked at every trapezoid and none are right floaters for (i = 0; i < count; i++) { nsBandTrapezoid* trapezoid = &data[i]; if (trapezoid->state != nsBandTrapezoid::Available) { const nsStyleDisplay* display; if (nsBandTrapezoid::OccupiedMultiple == trapezoid->state) { PRInt32 j, numFrames = trapezoid->frames->Count(); NS_ASSERTION(numFrames > 0, "bad trapezoid frame list"); for (j = 0; j < numFrames; j++) { nsIFrame* f = (nsIFrame*)trapezoid->frames->ElementAt(j); f->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); if (NS_STYLE_FLOAT_RIGHT == display->mFloats) { goto foundRightFloater; } } } else { trapezoid->frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); if (NS_STYLE_FLOAT_RIGHT == display->mFloats) { break; } } } } foundRightFloater: if (i > 0) { trapezoid = &data[i - 1]; } } if (nsBandTrapezoid::Available == trapezoid->state) { // The trapezoid is available trapezoid->GetRect(availSpace); } else { const nsStyleDisplay* display; // The trapezoid is occupied. That means there's no available space trapezoid->GetRect(availSpace); // XXX Better handle the case of multiple frames if (nsBandTrapezoid::Occupied == trapezoid->state) { trapezoid->frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); if (NS_STYLE_FLOAT_LEFT == display->mFloats) { availSpace.x = availSpace.XMost(); } } availSpace.width = 0; } } //---------------------------------------------------------------------- nsBlockReflowState::nsBlockReflowState(nsIPresContext& aPresContext, const nsHTMLReflowState& aReflowState, const nsHTMLReflowMetrics& aMetrics) : nsFrameReflowState(aPresContext, aReflowState, aMetrics), mLineLayout(aPresContext, aReflowState.spaceManager) { mInlineReflow = nsnull; mLineLayout.Init(this); mSpaceManager = aReflowState.spaceManager; // Translate into our content area and then save the // coordinate system origin for later. mSpaceManager->Translate(mBorderPadding.left, mBorderPadding.top); mSpaceManager->GetTranslation(mSpaceManagerX, mSpaceManagerY); mPresContext = aPresContext; mBlock = (nsBlockFrame*) frame; mBlock->GetNextInFlow((nsIFrame*&)mNextInFlow); mKidXMost = 0; mY = 0; mUnconstrainedWidth = maxSize.width == NS_UNCONSTRAINEDSIZE; mUnconstrainedHeight = maxSize.height == NS_UNCONSTRAINEDSIZE; #ifdef NS_DEBUG if (!mUnconstrainedWidth && (maxSize.width > 100000)) { mBlock->ListTag(stdout); printf(": bad parent: maxSize WAS %d,%d\n", maxSize.width, maxSize.height); maxSize.width = NS_UNCONSTRAINEDSIZE; mUnconstrainedWidth = PR_TRUE; } if (!mUnconstrainedHeight && (maxSize.height > 100000)) { mBlock->ListTag(stdout); printf(": bad parent: maxSize WAS %d,%d\n", maxSize.width, maxSize.height); maxSize.height = NS_UNCONSTRAINEDSIZE; mUnconstrainedHeight = PR_TRUE; } #endif mTextAlign = mStyleText->mTextAlign; mPrevMarginFlags = 0; nscoord lr = mBorderPadding.left + mBorderPadding.right; mY = mBorderPadding.top; if (eHTMLFrameConstraint_Unconstrained != widthConstraint) { // The CSS2 spec says that the width attribute defines the width // of the "content area" which does not include the border // padding. So we add those back in. mBorderArea.width = minWidth + lr; mContentArea.width = minWidth; } else { if (mUnconstrainedWidth) { mBorderArea.width = NS_UNCONSTRAINEDSIZE; mContentArea.width = NS_UNCONSTRAINEDSIZE; } else { mBorderArea.width = maxSize.width; mContentArea.width = maxSize.width - lr; } } mBorderArea.height = maxSize.height; mContentArea.height = maxSize.height; mBottomEdge = maxSize.height; if (!mUnconstrainedHeight) { mBottomEdge -= mBorderPadding.bottom; } mPrevChild = nsnull; mFreeList = nsnull; mPrevLine = nsnull; } nsBlockReflowState::~nsBlockReflowState() { // Restore the coordinate system mSpaceManager->Translate(-mBorderPadding.left, -mBorderPadding.top); LineData* line = mFreeList; while (nsnull != line) { NS_ASSERTION((0 == line->mChildCount) && (nsnull == line->mFirstChild), "bad free line"); LineData* next = line->mNext; delete line; line = next; } } // 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 nsBlockReflowState::GetAvailableSpace() { nsISpaceManager* sm = mSpaceManager; #ifdef NS_DEBUG // Verify that the caller setup the coordinate system properly nscoord wx, wy; sm->GetTranslation(wx, wy); NS_ASSERTION((wx == mSpaceManagerX) && (wy == mSpaceManagerY), "bad coord system"); #endif // Fill in band data for the specific Y coordinate. Because the // space manager is pre-translated into our content-area (so that // nested blocks/inlines will line up properly), we have to remove // the Y translation to find the band coordinates relative to our // inner (content area) upper left corner (0,0). sm->GetBandData(mY - mBorderPadding.top, mContentArea, mCurrentBand); // Compute the bounding rect of the available space, i.e. space // between any left and right floaters. mCurrentBand.ComputeAvailSpaceRect(); NS_FRAME_LOG(NS_FRAME_TRACE_CALLS, ("nsBlockReflowState::GetAvailableSpace: band={%d,%d,%d,%d} count=%d", mCurrentBand.availSpace.x, mCurrentBand.availSpace.y, mCurrentBand.availSpace.width, mCurrentBand.availSpace.height, mCurrentBand.count)); } //---------------------------------------------------------------------- nsresult NS_NewBlockFrame(nsIContent* aContent, nsIFrame* aParentFrame, nsIFrame*& aNewFrame, PRUint32 aFlags) { nsBlockFrame* it = new nsBlockFrame(aContent, aParentFrame); if (nsnull == it) { return NS_ERROR_OUT_OF_MEMORY; } aNewFrame = it; it->SetFlags(aFlags); return NS_OK; } nsBlockFrame::nsBlockFrame(nsIContent* aContent, nsIFrame* aParent) : nsBlockFrameSuper(aContent, aParent) { } nsBlockFrame::~nsBlockFrame() { nsTextRun::DeleteTextRuns(mTextRuns); } NS_IMETHODIMP 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; } if (aIID.Equals(kIFloaterContainerIID)) { *aInstancePtr = (void*) ((nsIFloaterContainer*) this); return NS_OK; } return nsBlockFrameSuper::QueryInterface(aIID, aInstancePtr); } NS_IMETHODIMP nsBlockFrame::Init(nsIPresContext& aPresContext, nsIFrame* aChildList) { nsresult rv = AppendNewFrames(aPresContext, aChildList); if (NS_OK != rv) { return rv; } // Create list bullet if this is a list-item. Note that this is done // here so that RenumberLists will work (it needs the bullets to // store the bullet numbers). const nsStyleDisplay* styleDisplay; GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) styleDisplay); if ((nsnull == mPrevInFlow) && (NS_STYLE_DISPLAY_LIST_ITEM == styleDisplay->mDisplay) && (nsnull == mBullet)) { // Create bullet frame mBullet = new BulletFrame(mContent, this); if (nsnull == mBullet) { return NS_ERROR_OUT_OF_MEMORY; } // Resolve style for the bullet frame nsIStyleContext* kidSC; kidSC = aPresContext.ResolvePseudoStyleContextFor(nsHTMLAtoms::bulletPseudo, this); mBullet->SetStyleContext(&aPresContext, kidSC); NS_RELEASE(kidSC); // If the list bullet frame should be positioned inside then add // it to the flow now. const nsStyleList* styleList; GetStyleData(eStyleStruct_List, (const nsStyleStruct*&) styleList); if (NS_STYLE_LIST_STYLE_POSITION_INSIDE == styleList->mListStylePosition) { InsertNewFrame(aPresContext, this, mBullet, nsnull); } } return NS_OK; } NS_IMETHODIMP nsBlockFrame::DeleteFrame(nsIPresContext& aPresContext) { // When we have a bullet frame and it's not in our child list then // we need to delete it ourselves (this is the common case for // list-item's that have outside bullets). if ((nsnull != mBullet) && ((nsnull == mLines) || (mBullet != mLines->mFirstChild))) { mBullet->DeleteFrame(aPresContext); } DeleteLineList(aPresContext, mLines); DeleteLineList(aPresContext, mOverflowLines); nsBlockFrameSuper::DeleteFrame(aPresContext); return NS_OK; } PRBool nsBlockFrame::IsPseudoFrame() const { PRBool result = PR_FALSE; if (nsnull != mGeometricParent) { nsIContent* parentContent; mGeometricParent->GetContent(parentContent); if (parentContent == mContent) { result = PR_TRUE; } NS_RELEASE(parentContent); } return result; } NS_IMETHODIMP nsBlockFrame::IsSplittable(nsSplittableType& aIsSplittable) const { aIsSplittable = NS_FRAME_SPLITTABLE_NON_RECTANGULAR; return NS_OK; } NS_IMETHODIMP 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; NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsBlockFrame::CreateContinuingFrame: newFrame=%p", cf)); return NS_OK; } NS_IMETHODIMP nsBlockFrame::ListTag(FILE* out) const { if ((nsnull != mGeometricParent) && IsPseudoFrame()) { fprintf(out, "*"); } fprintf(out, "Block<"); nsIAtom* atom; mContent->GetTag(atom); if (nsnull != atom) { nsAutoString tmp; atom->ToString(tmp); fputs(tmp, out); } fprintf(out, ">(%d)@%p", ContentIndexInContainer(this), this); return NS_OK; } NS_METHOD nsBlockFrame::List(FILE* out, PRInt32 aIndent, nsIListFilter *aFilter) const { // if a filter is present, only output this frame if the filter says we should nsIAtom* tag; nsAutoString tagString; mContent->GetTag(tag); if (tag != nsnull) { tag->ToString(tagString); NS_RELEASE(tag); } PRInt32 i; PRBool outputMe = (PRBool)((nsnull==aFilter) || ((PR_TRUE==aFilter->OutputTag(&tagString)) && (!IsPseudoFrame()))); if (PR_TRUE==outputMe) { // Indent for (i = aIndent; --i >= 0; ) fputs(" ", out); // Output the tag ListTag(out); nsIView* view; GetView(view); if (nsnull != view) { fprintf(out, " [view=%p]", view); } // 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 out << mRect; if (0 != mState) { fprintf(out, " [state=%08x]", mState); } } // Output the children, one line at a time if (nsnull != mLines) { if (PR_TRUE==outputMe) fputs("<\n", out); aIndent++; LineData* line = mLines; while (nsnull != line) { line->List(out, aIndent, aFilter, outputMe); line = line->mNext; } aIndent--; if (PR_TRUE==outputMe) { for (i = aIndent; --i >= 0; ) fputs(" ", out); fputs(">", out); } } else { if (PR_TRUE==outputMe) fputs("<>", out); } if (PR_TRUE==outputMe) { // Output the text-runs if (nsnull != mTextRuns) { fputs(" text-runs=<\n", out); ListTextRuns(out, aIndent + 1, mTextRuns); for (i = aIndent; --i >= 0; ) fputs(" ", out); fputs(">", out); } fputs("\n", out); } return NS_OK; } ///////////////////////////////////////////////////////////////////////////// // Child frame enumeration NS_IMETHODIMP nsBlockFrame::FirstChild(nsIFrame*& aFirstChild) const { aFirstChild = (nsnull != mLines) ? mLines->mFirstChild : nsnull; return NS_OK; } ////////////////////////////////////////////////////////////////////// // Reflow methods NS_IMETHODIMP nsBlockFrame::Reflow(nsIPresContext& aPresContext, nsHTMLReflowMetrics& aMetrics, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("enter nsBlockFrame::Reflow: maxSize=%d,%d reason=%d", aReflowState.maxSize.width, aReflowState.maxSize.height, aReflowState.reason)); // If this is the initial reflow, generate any synthetic content // that needs generating. if (eReflowReason_Initial == aReflowState.reason) { NS_ASSERTION(0 != (NS_FRAME_FIRST_REFLOW & mState), "bad mState"); } else { NS_ASSERTION(0 == (NS_FRAME_FIRST_REFLOW & mState), "bad mState"); } // Replace parent provided reflow state with our own significantly // more extensive version. nsBlockReflowState state(aPresContext, aReflowState, aMetrics); if (NS_BODY_NO_AUTO_MARGINS & mFlags) { // Pretend that there is a really big bottom margin preceeding the // first line so that it's margin is never applied. } if (NS_BODY_THE_BODY & mFlags) { state.mIsMarginRoot = PR_TRUE; } // Prepare inline-reflow engine nsInlineReflow inlineReflow(state.mLineLayout, state, this, PR_TRUE); state.mInlineReflow = &inlineReflow; state.mLineLayout.PushInline(&inlineReflow); // ListTag(stdout); printf(": enter isMarginRoot=%c\n", state.mIsMarginRoot?'T':'F'); nsresult rv = NS_OK; if (eReflowReason_Initial == state.reason) { RenumberLists(state); if (!DrainOverflowLines()) { rv = InitialReflow(state); } else { rv = ResizeReflow(state); } mState &= ~NS_FRAME_FIRST_REFLOW; } else if (eReflowReason_Incremental == state.reason) { #if XXX // We can have an overflow here if our parent doesn't bother to // continue us DrainOverflowLines(); #endif nsIFrame* target; state.reflowCommand->GetTarget(target); if (this == target) { RenumberLists(state); nsIReflowCommand::ReflowType type; state.reflowCommand->GetType(type); switch (type) { case nsIReflowCommand::FrameAppended: rv = FrameAppendedReflow(state); break; case nsIReflowCommand::FrameInserted: rv = FrameInsertedReflow(state); break; case nsIReflowCommand::FrameRemoved: rv = FrameRemovedReflow(state); break; case nsIReflowCommand::StyleChanged: rv = StyleChangedReflow(state); break; default: // Map any other incremental operations into full reflows rv = ResizeReflow(state); break; } } else { // Get next frame in reflow command chain state.reflowCommand->GetNext(state.mNextRCFrame); // Now do the reflow rv = ChildIncrementalReflow(state); } } else if (eReflowReason_Resize == state.reason) { DrainOverflowLines(); rv = ResizeReflow(state); } // Compute our final size ComputeFinalSize(state, aMetrics); // ListTag(stdout); printf(": exit carriedMargins=%d,%d\n", aMetrics.mCarriedOutTopMargin, aMetrics.mCarriedOutBottomMargin); #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyChildCount(mLines); VerifyLines(mLines); } #endif aStatus = rv; NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsBlockFrame::Reflow: size=%d,%d rv=%x", aMetrics.width, aMetrics.height, rv)); return NS_OK; } void nsBlockFrame::RenumberLists(nsBlockReflowState& aState) { // Setup initial list ordinal value PRInt32 ordinal = 1; nsIHTMLContent* hc; if (NS_OK == mContent->QueryInterface(kIHTMLContentIID, (void**) &hc)) { nsHTMLValue value; if (NS_CONTENT_ATTR_HAS_VALUE == hc->GetAttribute(nsHTMLAtoms::start, value)) { if (eHTMLUnit_Integer == value.GetUnit()) { ordinal = value.GetIntValue(); if (ordinal <= 0) { ordinal = 1; } } } NS_RELEASE(hc); } aState.mNextListOrdinal = ordinal; // Get to first-in-flow nsBlockFrame* block = this; while (nsnull != block->mPrevInFlow) { block = (nsBlockFrame*) block->mPrevInFlow; } // For each flow-block... while (nsnull != block) { // For each frame in the flow-block... nsIFrame* frame = block->mLines ? block->mLines->mFirstChild : nsnull; while (nsnull != frame) { // If the frame is a list-item and the frame implements our // block frame API then get it's bullet and set the list item // ordinal. const nsStyleDisplay* display; frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display); if (NS_STYLE_DISPLAY_LIST_ITEM == display->mDisplay) { // Make certain that the frame isa block-frame in case // something foriegn has crept in. nsBlockFrame* listItem; if (NS_OK == frame->QueryInterface(kBlockFrameCID, (void**) &listItem)) { if (nsnull != listItem->mBullet) { listItem->mBullet->SetListItemOrdinal(aState); } } } frame->GetNextSibling(frame); } block = (nsBlockFrame*) block->mNextInFlow; } } void nsBlockFrame::ComputeFinalSize(nsBlockReflowState& aState, nsHTMLReflowMetrics& aMetrics) { // Compute final width if (eHTMLFrameConstraint_Unconstrained != aState.widthConstraint) { // Use style defined width aMetrics.width = aState.mBorderPadding.left + aState.minWidth + aState.mBorderPadding.right; } else { // There are two options here. We either shrink wrap around our // contents or we fluff out to the maximum available width. Note: // We always shrink wrap when given an unconstrained width. nscoord contentWidth = aState.mKidXMost + aState.mBorderPadding.right; if ((0 == (NS_BODY_SHRINK_WRAP & mFlags)) && !aState.mUnconstrainedWidth) { // Fluff out to the max width if we aren't already that wide if (contentWidth < aState.maxSize.width) { contentWidth = aState.maxSize.width; } } aMetrics.width = contentWidth; } // Compute final height if (eHTMLFrameConstraint_Unconstrained != aState.heightConstraint) { // Use style defined height aMetrics.height = aState.mBorderPadding.top + aState.minHeight + aState.mBorderPadding.bottom; } else { // Shrink wrap our height around our contents. if (aState.mIsMarginRoot) { // When we are a margin root make sure that our last childs // bottom margin is fully applied. if ((NS_BODY_NO_AUTO_MARGINS & mFlags) && (NS_CARRIED_BOTTOM_MARGIN_IS_AUTO & aState.mPrevMarginFlags)) { // Do not apply last auto margin when the last childs margin // is auto and we are configured to ignore them. } else { aState.mY += aState.mPrevBottomMargin; } } aState.mY += aState.mBorderPadding.bottom; aMetrics.height = aState.mY; } // Return top and bottom margin information if (aState.mIsMarginRoot) { aMetrics.mCarriedOutTopMargin = 0; aMetrics.mCarriedOutBottomMargin = 0; aMetrics.mCarriedOutMarginFlags = 0; } else { aMetrics.mCarriedOutTopMargin = aState.mCollapsedTopMargin; aMetrics.mCarriedOutBottomMargin = aState.mPrevBottomMargin; aMetrics.mCarriedOutMarginFlags = aState.mCarriedOutMarginFlags; if (NS_CARRIED_BOTTOM_MARGIN_IS_AUTO & aState.mPrevMarginFlags) { aMetrics.mCarriedOutMarginFlags |= NS_CARRIED_BOTTOM_MARGIN_IS_AUTO; } } // Special check for zero sized content: If our content is zero // sized then we collapse into nothingness. if ((eHTMLFrameConstraint_Unconstrained == aState.widthConstraint) && (eHTMLFrameConstraint_Unconstrained == aState.heightConstraint) && ((0 == aState.mKidXMost - aState.mBorderPadding.left) || (0 == aState.mY - aState.mBorderPadding.top))) { aMetrics.width = 0; aMetrics.height = 0; } aMetrics.ascent = aMetrics.height; aMetrics.descent = 0; // XXX this needs reworking I suppose if (aState.mComputeMaxElementSize) { *aMetrics.maxElementSize = aState.mMaxElementSize; // Add in our border and padding to the max-element-size so that // we don't shrink too far. aMetrics.maxElementSize->width += aState.mBorderPadding.left + aState.mBorderPadding.right; aMetrics.maxElementSize->height += aState.mBorderPadding.top + aState.mBorderPadding.bottom; // Factor in any left and right floaters as well LineData* line = mLines; PRInt32 maxLeft = 0, maxRight = 0; while (nsnull != line) { if (nsnull != line->mFloaters) { nsRect r; nsMargin floaterMargin; PRInt32 leftSum = 0, rightSum = 0; PRInt32 n = line->mFloaters->Count(); for (PRInt32 i = 0; i < n; i++) { nsPlaceholderFrame* placeholder = (nsPlaceholderFrame*) line->mFloaters->ElementAt(i); nsIFrame* floater = placeholder->GetAnchoredItem(); floater->GetRect(r); const nsStyleDisplay* floaterDisplay; const nsStyleSpacing* floaterSpacing; floater->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)floaterDisplay); floater->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&)floaterSpacing); floaterSpacing->CalcMarginFor(floater, floaterMargin); nscoord width = r.width + floaterMargin.left + floaterMargin.right; NS_ASSERTION((NS_STYLE_FLOAT_LEFT == floaterDisplay->mFloats) || (NS_STYLE_FLOAT_RIGHT == floaterDisplay->mFloats), "invalid float type"); if (NS_STYLE_FLOAT_LEFT == floaterDisplay->mFloats) { leftSum += width; } else { rightSum += width; } } if (leftSum > maxLeft) maxLeft = leftSum; if (rightSum > maxRight) maxRight = rightSum; } line = line->mNext; } // XXX what to do??? if (maxLeft > aMetrics.maxElementSize->width) { aMetrics.maxElementSize->width = maxLeft; } if (maxRight > aMetrics.maxElementSize->width) { aMetrics.maxElementSize->width = maxRight; } } } nsresult nsBlockFrame::AppendNewFrames(nsIPresContext& aPresContext, nsIFrame* aNewFrame) { // Get our last line and then get its last child nsIFrame* lastFrame; LineData* lastLine = LastLine(mLines); if (nsnull != lastLine) { lastFrame = lastLine->LastChild(); } else { lastFrame = nsnull; } // Add the new frames to the sibling list; wrap any frames that // require wrapping if (nsnull != lastFrame) { lastFrame->SetNextSibling(aNewFrame); } nsresult rv; // Make sure that new inlines go onto the end of the lastLine when // the lastLine is mapping inline frames. PRInt32 pendingInlines = 0; if (nsnull != lastLine) { if (!lastLine->IsBlock()) { pendingInlines = 1; } } // Now create some lines for the new frames nsIFrame* prevFrame = lastFrame; for (nsIFrame* frame = aNewFrame; nsnull != frame; frame->GetNextSibling(frame)) { // See if the child is a block or non-block const nsStyleDisplay* kidDisplay; rv = frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) kidDisplay); if (NS_OK != rv) { return rv; } const nsStylePosition* kidPosition; rv = frame->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&) kidPosition); if (NS_OK != rv) { return rv; } PRBool isBlock = nsLineLayout::TreatFrameAsBlock(kidDisplay, kidPosition); // See if we need to move the frame outside of the flow, and insert a // placeholder frame in its place nsIFrame* placeholder; if (MoveFrameOutOfFlow(aPresContext, frame, kidDisplay, kidPosition, placeholder)) { // Reset the previous frame's next sibling pointer if (nsnull != prevFrame) { prevFrame->SetNextSibling(placeholder); } // The placeholder frame is always inline frame = placeholder; isBlock = PR_FALSE; } else { // Wrap the frame in a view if necessary nsIStyleContext* kidSC; frame->GetStyleContext(&aPresContext, kidSC); rv = CreateViewForFrame(aPresContext, frame, kidSC, PR_FALSE); NS_RELEASE(kidSC); if (NS_OK != rv) { return rv; } } // If the child is an inline then add it to the lastLine (if it's // an inline line, otherwise make a new line). If the child is a // block then make a new line and put the child in that line. if (isBlock) { // If the previous line has pending inline data to be reflowed, // do so now. if (0 != pendingInlines) { // Set this to true in case we don't end up reflowing all of the // frames on the line (because they end up being pushed). lastLine->SetLastContentIsComplete(); lastLine->MarkDirty(); pendingInlines = 0; } // Create a line for the block LineData* line = new LineData(frame, 1, (LINE_IS_BLOCK | LINE_LAST_CONTENT_IS_COMPLETE)); if (nsnull == line) { return NS_ERROR_OUT_OF_MEMORY; } if (nsnull == lastLine) { mLines = line; } else { lastLine->mNext = line; } lastLine = line; } else { // Queue up the inlines for reflow later on if (0 == pendingInlines) { LineData* line = new LineData(frame, 0, 0); if (nsnull == line) { return NS_ERROR_OUT_OF_MEMORY; } if (nsnull == lastLine) { mLines = line; } else { lastLine->mNext = line; } lastLine = line; } lastLine->mChildCount++; pendingInlines++; } // Remember the previous frame prevFrame = frame; } if (0 != pendingInlines) { // Set this to true in case we don't end up reflowing all of the // frames on the line (because they end up being pushed). lastLine->SetLastContentIsComplete(); lastLine->MarkDirty(); } return NS_OK; } nsresult nsBlockFrame::InitialReflow(nsBlockReflowState& aState) { // Generate text-run information nsresult rv = FindTextRuns(aState); if (NS_OK != rv) { return rv; } // Reflow everything aState.GetAvailableSpace(); return ResizeReflow(aState); } void nsBlockFrame::RecoverLineMargins(nsBlockReflowState& aState, LineData* aLine, nscoord& aTopMarginResult, nscoord& aBottomMarginResult) { nscoord childsCarriedOutTopMargin = aLine->mCarriedOutTopMargin; nscoord childsCarriedOutBottomMargin = aLine->mCarriedOutBottomMargin; nscoord childsTopMargin = 0; nscoord childsBottomMargin = 0; if (aLine->IsBlock()) { // Recover the block frames computed bottom margin value nsIFrame* frame = aLine->mFirstChild; if (nsnull != frame) { const nsStyleSpacing* spacing; frame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&) spacing); if (nsnull != spacing) { nsMargin margin; nsInlineReflow::CalculateBlockMarginsFor(aState.mPresContext, frame, spacing, margin); childsTopMargin = margin.top; childsBottomMargin = margin.bottom; } } } // Collapse the carried-out-margins with the childs margins aBottomMarginResult = nsInlineReflow::MaxMargin(childsCarriedOutBottomMargin, childsBottomMargin); aTopMarginResult = nsInlineReflow::MaxMargin(childsCarriedOutTopMargin, childsTopMargin); } nsresult nsBlockFrame::FrameAppendedReflow(nsBlockReflowState& aState) { nsresult rv = NS_OK; // Get the first of the newly appended frames nsIFrame* firstAppendedFrame; aState.reflowCommand->GetChildFrame(firstAppendedFrame); // Add the new frames to the child list, and create new lines. Each // impacted line will be marked dirty AppendNewFrames(aState.mPresContext, firstAppendedFrame); // Generate text-run information rv = FindTextRuns(aState); if (NS_OK != rv) { return rv; } // Recover our reflow state LineData* firstDirtyLine = mLines; LineData* lastCleanLine = nsnull; while (nsnull != firstDirtyLine) { if (firstDirtyLine->IsDirty()) { break; } // Recover xmost nscoord xmost = firstDirtyLine->mBounds.XMost(); if (xmost > aState.mKidXMost) { aState.mKidXMost = xmost; } // Recover the mPrevBottomMargin and the mCollapsedTopMargin values nscoord topMargin, bottomMargin; RecoverLineMargins(aState, firstDirtyLine, topMargin, bottomMargin); if (0 == firstDirtyLine->mBounds.height) { // For zero height lines, collapse the lines top and bottom // margins together to produce the effective bottomMargin value. bottomMargin = nsInlineReflow::MaxMargin(topMargin, bottomMargin); bottomMargin = nsInlineReflow::MaxMargin(aState.mPrevBottomMargin, bottomMargin); } else { aState.mPrevMarginFlags = firstDirtyLine->GetMarginFlags(); } aState.mPrevBottomMargin = bottomMargin; // Advance Y to be below the line. aState.mY = firstDirtyLine->mBounds.YMost(); // Advance to the next line lastCleanLine = firstDirtyLine; firstDirtyLine = firstDirtyLine->mNext; } if (nsnull != lastCleanLine) { // Place any floaters the line has if (nsnull != lastCleanLine->mFloaters) { aState.mCurrentLine = lastCleanLine; aState.PlaceFloaters(lastCleanLine->mFloaters); } } aState.GetAvailableSpace(); NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsBlockReflowState::FrameAppendedReflow: y=%d firstDirtyLine=%p", aState.mY, firstDirtyLine)); // Reflow lines from there forward aState.mPrevLine = lastCleanLine; return ReflowLinesAt(aState, firstDirtyLine); } // XXX keep the text-run data in the first-in-flow of the block nsresult nsBlockFrame::FindTextRuns(nsBlockReflowState& aState) { // Destroy old run information first nsTextRun::DeleteTextRuns(mTextRuns); mTextRuns = nsnull; aState.mLineLayout.ResetTextRuns(); // Ask each child that implements nsIInlineReflow to find its text runs LineData* line = mLines; while (nsnull != line) { if (!line->IsBlock()) { nsIFrame* frame = line->mFirstChild; PRInt32 n = line->mChildCount; while (--n >= 0) { nsIHTMLReflow* hr; if (NS_OK == frame->QueryInterface(kIHTMLReflowIID, (void**)&hr)) { nsresult rv = hr->FindTextRuns(aState.mLineLayout); if (NS_OK != rv) { return rv; } } else { // A frame that doesn't implement nsIHTMLReflow isn't text // therefore it will end an open text run. aState.mLineLayout.EndTextRun(); } frame->GetNextSibling(frame); } } else { // A frame that doesn't implement nsIInlineReflow isn't text // therefore it will end an open text run. aState.mLineLayout.EndTextRun(); } line = line->mNext; } aState.mLineLayout.EndTextRun(); // Now take the text-runs away from the line layout engine. mTextRuns = aState.mLineLayout.TakeTextRuns(); return NS_OK; } nsresult nsBlockFrame::FrameInsertedReflow(nsBlockReflowState& aState) { // Get the inserted frame nsIFrame* newFrame; aState.reflowCommand->GetChildFrame(newFrame); // Get the previous sibling frame nsIFrame* prevSibling; aState.reflowCommand->GetPrevSiblingFrame(prevSibling); // Insert the frame. This marks the line dirty... InsertNewFrame(aState.mPresContext, this, newFrame, prevSibling); LineData* line = mLines; while (nsnull != line->mNext) { if (line->IsDirty()) { break; } line = line->mNext; } NS_ASSERTION(nsnull != line, "bad inserted reflow"); //XXX return ReflowDirtyLines(aState, line); // XXX Correct implementation: reflow the dirty lines only; all // other lines can be moved; recover state before first dirty line. // XXX temporary aState.GetAvailableSpace(); aState.mPrevLine = nsnull; return ReflowLinesAt(aState, mLines); } nsresult nsBlockFrame::FrameRemovedReflow(nsBlockReflowState& aState) { if (nsnull == mLines) { return NS_OK; } // Get the deleted frame nsIFrame* deletedFrame; aState.reflowCommand->GetChildFrame(deletedFrame); // Find the previous sibling frame nsIFrame* prevSibling = nsnull; for (nsIFrame* f = mLines->mFirstChild; f != deletedFrame; f->GetNextSibling(f)) { if (nsnull == f) { // We didn't find the deleted frame in our child list NS_WARNING("Can't find deleted frame"); return NS_OK; } prevSibling = f; } // Find the line that contains deletedFrame; we also find the pointer to // the line. nsBlockFrame* flow = this; LineData** linep = &flow->mLines; LineData* line = flow->mLines; while (nsnull != line) { if (line->Contains(deletedFrame)) { break; } linep = &line->mNext; line = line->mNext; } // Remove frame and its continuations while (nsnull != deletedFrame) { while ((nsnull != line) && (nsnull != deletedFrame)) { #ifdef NS_DEBUG nsIFrame* parent; deletedFrame->GetGeometricParent(parent); NS_ASSERTION(flow == parent, "messed up delete code"); #endif NS_FRAME_TRACE(NS_FRAME_TRACE_CHILD_REFLOW, ("nsBlockFrame::ContentDeleted: deadFrame=%p", deletedFrame)); // Remove deletedFrame from the line if (line->mFirstChild == deletedFrame) { nsIFrame* nextFrame; deletedFrame->GetNextSibling(nextFrame); line->mFirstChild = nextFrame; } else { nsIFrame* lastFrame = line->LastChild(); if (lastFrame == deletedFrame) { line->SetLastContentIsComplete(); } } // Take deletedFrame out of the sibling list if (nsnull != prevSibling) { nsIFrame* nextFrame; deletedFrame->GetNextSibling(nextFrame); prevSibling->SetNextSibling(nextFrame); } // Destroy frame; capture its next-in-flow first in case we need // to destroy that too. nsIFrame* nextInFlow; deletedFrame->GetNextInFlow(nextInFlow); if (nsnull != nextInFlow) { deletedFrame->BreakFromNextFlow(); } deletedFrame->DeleteFrame(aState.mPresContext); deletedFrame = nextInFlow; // If line is empty, remove it now LineData* next = line->mNext; if (0 == --line->mChildCount) { *linep = next; line->mNext = nsnull; delete line; } else { linep = &line->mNext; } line = next; } // Advance to next flow block if the frame has more continuations if (nsnull != deletedFrame) { flow = (nsBlockFrame*) flow->mNextInFlow; NS_ASSERTION(nsnull != flow, "whoops, continuation without a parent"); line = flow->mLines; prevSibling = nsnull; } } // Find the first dirty line. That's where we start to reflow line = mLines; while (nsnull != line->mNext) { if (line->IsDirty()) { break; } line = line->mNext; } NS_ASSERTION(nsnull != line, "bad inserted reflow"); //XXX return ReflowDirtyLines(aState, line); // XXX Correct implementation: reflow the dirty lines only; all // other lines can be moved; recover state before first dirty line. // XXX temporary aState.GetAvailableSpace(); aState.mPrevLine = nsnull; return ReflowLinesAt(aState, mLines); } // XXX Todo: some incremental reflows are passing through this block // and into a child block; those cannot impact our text-runs. In that // case skip the FindTextRuns work. // XXX easy optimizations: find the line that contains the next child // in the reflow-command path and mark it dirty and only reflow it; // recover state before it, slide lines down after it. nsresult nsBlockFrame::ChildIncrementalReflow(nsBlockReflowState& aState) { #if 0 // Generate text-run information; this will also "fluff out" any // inline children's frame tree. nsresult rv = FindTextRuns(aState); if (NS_OK != rv) { return rv; } #endif // XXX temporary aState.GetAvailableSpace(); aState.mPrevLine = nsnull; return ReflowLinesAt(aState, mLines); } nsresult nsBlockFrame::StyleChangedReflow(nsBlockReflowState& aState) { // XXX temporary aState.GetAvailableSpace(); aState.mPrevLine = nsnull; return ReflowLinesAt(aState, mLines); } nsresult nsBlockFrame::ResizeReflow(nsBlockReflowState& aState) { // Mark everything dirty LineData* line = mLines; while (nsnull != line) { line->MarkDirty(); line = line->mNext; } // Reflow all of our lines aState.GetAvailableSpace(); aState.mPrevLine = nsnull; return ReflowLinesAt(aState, mLines); } nsresult nsBlockFrame::ReflowLinesAt(nsBlockReflowState& aState, LineData* aLine) { // Inform line layout of where the text runs are aState.mLineLayout.SetReflowTextRuns(mTextRuns); // Reflow the lines that are already ours while (nsnull != aLine) { nsReflowStatus rs; if (!ReflowLine(aState, aLine, rs)) { if (NS_IS_REFLOW_ERROR(rs)) { return nsresult(rs); } return NS_FRAME_NOT_COMPLETE; } aState.mLineLayout.NextLine(); aState.mPrevLine = aLine; aLine = aLine->mNext; } // Pull data from a next-in-flow if we can while (nsnull != aState.mNextInFlow) { // Grab first line from our next-in-flow aLine = aState.mNextInFlow->mLines; if (nsnull == aLine) { 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 = aLine->mNext; aLine->mNext = nsnull; if (0 == aLine->mChildCount) { // The line is empty. Try the next one. NS_ASSERTION(nsnull == aLine->mChildCount, "bad empty line"); aLine->mNext = aState.mFreeList; aState.mFreeList = aLine; continue; } // Make the children in the line ours. nsIFrame* frame = aLine->mFirstChild; nsIFrame* lastFrame = nsnull; PRInt32 n = aLine->mChildCount; while (--n >= 0) { nsIFrame* geometricParent; nsIFrame* contentParent; frame->GetGeometricParent(geometricParent); frame->GetContentParent(contentParent); if (contentParent == geometricParent) { frame->SetContentParent(this); } frame->SetGeometricParent(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 = aLine; } else { NS_ASSERTION(nsnull == aState.mPrevLine->mNext, "bad aState.mPrevLine"); aState.mPrevLine->mNext = aLine; aState.mPrevChild->SetNextSibling(aLine->mFirstChild); } #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyChildCount(mLines); } #endif // Now reflow it and any lines that it makes during it's reflow. while (nsnull != aLine) { nsReflowStatus rs; if (!ReflowLine(aState, aLine, rs)) { if (NS_IS_REFLOW_ERROR(rs)) { return nsresult(rs); } return NS_FRAME_NOT_COMPLETE; } aState.mLineLayout.NextLine(); aState.mPrevLine = aLine; aLine = aLine->mNext; } } return NS_FRAME_COMPLETE; } /** * Reflow a line. The line will either contain a single block frame * or contain 1 or more inline frames. */ PRBool nsBlockFrame::ReflowLine(nsBlockReflowState& aState, LineData* aLine, nsReflowStatus& aReflowResult) { NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsBlockFrame::ReflowLine: line=%p", aLine)); // Setup the line-layout for the new line aState.mLineLayout.Reset(); aState.mCurrentLine = aLine; aState.mInlineReflowPrepared = PR_FALSE; // If the line already has floaters on it from last time, remove // them from the spacemanager now. if (nsnull != aLine->mFloaters) { if (eReflowReason_Resize != aState.reason) { aLine->UnplaceFloaters(aState.mSpaceManager); } delete aLine->mFloaters; aLine->mFloaters = nsnull; } aLine->ClearDirty(); aLine->SetNeedDidReflow(); // Reflow mapped frames in the line nsBlockFrame* nextInFlow; PRBool keepGoing = PR_FALSE; PRInt32 n = aLine->mChildCount; if (0 != n) { nsIFrame* frame = aLine->mFirstChild; #ifdef NS_DEBUG const nsStyleDisplay* display; frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display); const nsStylePosition* position; frame->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&) position); PRBool isBlock = nsLineLayout::TreatFrameAsBlock(display, position); NS_ASSERTION(isBlock == aLine->IsBlock(), "bad line isBlock"); #endif if (aLine->IsBlock()) { keepGoing = ReflowBlockFrame(aState, aLine, frame, aReflowResult); return keepGoing; } else { while (--n >= 0) { keepGoing = ReflowInlineFrame(aState, aLine, frame, aReflowResult); if (!keepGoing) { // It is possible that one or more of next lines are empty // (because of DeleteNextInFlowsFor). If so, delete them now // in case we are finished. LineData* nextLine = aLine->mNext; while ((nsnull != nextLine) && (0 == nextLine->mChildCount)) { // Discard empty lines immediately. Empty lines can happen // here because of DeleteNextInFlowsFor not being able to // delete lines. aLine->mNext = nextLine->mNext; NS_ASSERTION(nsnull == nextLine->mFirstChild, "bad empty line"); nextLine->mNext = aState.mFreeList; aState.mFreeList = nextLine; nextLine = aLine->mNext; } goto done; } frame->GetNextSibling(frame); } } } // Pull frames from the next line until we can't while (nsnull != aLine->mNext) { keepGoing = PullFrame(aState, aLine, &aLine->mNext, PR_FALSE, aReflowResult); if (!keepGoing) { goto done; } } // Pull frames from the next-in-flow(s) until we can't nextInFlow = aState.mNextInFlow; while (nsnull != nextInFlow) { LineData* line = nextInFlow->mLines; if (nsnull == line) { nextInFlow = (nsBlockFrame*) nextInFlow->mNextInFlow; aState.mNextInFlow = nextInFlow; continue; } keepGoing = PullFrame(aState, aLine, &nextInFlow->mLines, PR_TRUE, aReflowResult); if (!keepGoing) { goto done; } } keepGoing = PR_TRUE; done:; if (!aLine->IsBlock()) { return PlaceLine(aState, aLine, aReflowResult); } return keepGoing; } // block's vs. inlines's: // 1. break-before/break-after (is handled by nsInlineReflow) // 2. pull-up: can't pull a block onto a non-empty line // 3. blocks require vertical margin handling // 4. left/right margins have to be figured differently for blocks // Assume that container is aware of block/inline differences of the // child frame and handles them through different pathways OR that the // nsInlineReflow does and reports back the different results // Here is how we format B0[ I0[ I1[ text0 B1 text1] ] ] // B0 reflows I0 // I0 formats I1 // I1 reflows text0 // I1 hits B1 and notices it can't be placed // I1 pushes B1 to overflow list // I1 returns not-complete // I0 creates I1' // I0 returns not-complete // B0 creates I0' // B0 flushes out line // B0 creates new line // B0 puts I0' on new line // B0 reflows I0' // I0' reflows I1' // I1' reflows B1 // I1' returns not-complete // I0' creates I1'' // I0' returns not-complete // B0 creates I0'' // B0 flushes out line // B0 creates new line // B0 puts I0'' on new line // B0 reflows I0'' // I0'' reflows I1'' // I1'' reflows text1 // I1'' returns complete // I0'' returns complete // B0 flushes out line // B0 returns complete // Here is how we format B0[ I0[ text0 I1[ B1 text1] ] ] // This is harder; I1 doesn't have text0 before B1 to know that we // have to stop reflowing I1 before reflowing B1; and worse yet, we // only have to stop if I1 begins on the same line that contains text0 // (it might not depending on the size of text0 and the size given to // I0 to reflow into). // To solve this we need to know when we hit B1 whether or not a break // is required. To answer this question we need to be able to walk // backwards up the nsReflowState's and discover where B1 will // actually end up. Can't use the X coordinate because of // border/padding. // Since B0 will define this what we do is put a counter into the // nsLineLayout structure. As the reflow recurses down the tree we // will bump the counter whenever a non-inline frame is seen and // placed (one that has a non-zero area too). When I1 goes to place B1 // it will see that the count is 1 (because of text0) and know to // return a "break-before" status (instead of a not-complete; we // return not-complete if we have placed ANY children and break-before // when we have placed NO children). // XXX factor this some more so that nsInlineFrame can use it too. // The post-reflow-success code that computes the final margin // values and carries them forward, plus the code that factors in // the max-element size. // XXX inline frames need this too when they wrap up blocks, right?? // Otherwise blocks in inlines won't interact with floaters properly. void nsBlockFrame::PrepareInlineReflow(nsBlockReflowState& aState, nsIFrame* aFrame, PRBool aIsBlock) { // Setup initial coordinate system for reflowing the frame into nscoord x, availWidth, availHeight; if (aIsBlock) { // XXX Child needs to apply OUR border-padding and IT's left // margin and right margin! How should this be done? x = aState.mBorderPadding.left; if (aState.mUnconstrainedWidth) { availWidth = NS_UNCONSTRAINEDSIZE; } else { availWidth = aState.mContentArea.width; } if (aState.mUnconstrainedHeight) { availHeight = NS_UNCONSTRAINEDSIZE; } else { /* XXX get the height right! */ availHeight = aState.mContentArea.height - aState.mY; } } else { // If the child doesn't handle run-around then we give it the band // adjusted data. The band aligned data doesn't have our // border/padding applied to it so add that in. x = aState.mCurrentBand.availSpace.x + aState.mBorderPadding.left; availWidth = aState.mCurrentBand.availSpace.width; if (aState.mUnconstrainedHeight) { availHeight = NS_UNCONSTRAINEDSIZE; } else { /* XXX get the height right! */ availHeight = aState.mCurrentBand.availSpace.height; } } aState.mInlineReflow->Init(x, aState.mY, availWidth, availHeight); aState.mInlineReflowPrepared = PR_TRUE; } #define HAVE_CARRIED_MARGIN 0x100 // XXX switch to two versions: inline vs. block? PRUintn nsBlockFrame::CalculateMargins(nsBlockReflowState& aState, LineData* aLine, PRBool aInlineContext, nscoord& aTopMarginResult, nscoord& aBottomMarginResult) { PRUintn result = 0; nsInlineReflow& ir = *aState.mInlineReflow; // First get the childs top and bottom margins. For inline // situations the child frame(s) margins were applied during line // layout, therefore we consider them zero here. nscoord childsTopMargin, childsBottomMargin; PRUintn marginFlags; if (aInlineContext) { childsTopMargin = 0; childsBottomMargin = 0; marginFlags = ir.GetCarriedOutMarginFlags(); } else { childsTopMargin = ir.GetTopMargin(); childsBottomMargin = ir.GetBottomMargin(); marginFlags = ir.GetMarginFlags(); } // Get the carried margin values. Note that even in an inline // situation there may be a carried margin (e.g. an inline frame // contains a block frame and the block frame has top/bottom // margins). While CSS doesn't specify what this means, we do to // improve compatability with poorly formed HTML (and commonly used // HTML). nscoord childsCarriedOutTopMargin = ir.GetCarriedOutTopMargin(); nscoord childsCarriedOutBottomMargin = ir.GetCarriedOutBottomMargin(); if (childsCarriedOutTopMargin || childsCarriedOutBottomMargin) { result |= HAVE_CARRIED_MARGIN; } // Compute the collapsed top margin value (this is a generational // margin collapse not a sibling margin collapse). nscoord collapsedTopMargin = nsInlineReflow::MaxMargin(childsCarriedOutBottomMargin, childsTopMargin); // Now perform sibling to sibling margin collapsing. collapsedTopMargin = nsInlineReflow::MaxMargin(aState.mPrevBottomMargin, collapsedTopMargin); PRBool topMarginIsAuto = PR_FALSE; if (NS_CARRIED_TOP_MARGIN_IS_AUTO & marginFlags) { if (aInlineContext) { topMarginIsAuto = collapsedTopMargin == childsCarriedOutTopMargin; } else { topMarginIsAuto = collapsedTopMargin == childsTopMargin; } } // Compute the collapsed bottom margin value (this is a generational // margin collapse not a sibling margin collapse). nscoord collapsedBottomMargin = nsInlineReflow::MaxMargin(childsCarriedOutBottomMargin, childsBottomMargin); if (NS_CARRIED_BOTTOM_MARGIN_IS_AUTO & marginFlags) { if (aInlineContext) { if (collapsedBottomMargin == childsCarriedOutBottomMargin) { result |= NS_CARRIED_BOTTOM_MARGIN_IS_AUTO; } } else if (collapsedBottomMargin == childsBottomMargin) { result |= NS_CARRIED_BOTTOM_MARGIN_IS_AUTO; } } // Now that we have the collapsed margin values, address any special // situations that limit or change how the margins are applied. if (0 == aLine->mBounds.height) { // When a line is empty, we collapse its top and bottom margins to // a single bottom margin value. // XXX This is not obviously a part of the css2 box model. collapsedBottomMargin = nsInlineReflow::MaxMargin(collapsedTopMargin, collapsedBottomMargin); collapsedTopMargin = 0; } else { PRBool isTopLine = (aState.mY == aState.mBorderPadding.top); if (isTopLine) { if (!aState.mIsMarginRoot) { // We are not a root for margin collapsing and this is our first // non-empty-line (with a block child). if (topMarginIsAuto) { // Propogate auto-margin flag outward aState.mCarriedOutMarginFlags |= NS_CARRIED_TOP_MARGIN_IS_AUTO; result |= NS_CARRIED_TOP_MARGIN_IS_AUTO; } // Keep the collapsed margin value around to pass out to our // parent. We don't apply its top margin (our parent will) so // zero it out. aState.mCollapsedTopMargin = collapsedTopMargin; collapsedTopMargin = 0; } else if (topMarginIsAuto && (NS_BODY_NO_AUTO_MARGINS & mFlags)) { // Our top margin is an auto margin and we are supposed to // ignore them. Change it to zero. collapsedTopMargin = 0; } } } aTopMarginResult = collapsedTopMargin; aBottomMarginResult = collapsedBottomMargin; return result; } void nsBlockFrame::SlideFrames(LineData* aLine, nscoord aDY) { // Adjust the Y coordinate of the frames in the line nsIFrame* kid = aLine->mFirstChild; PRIntn n = aLine->mChildCount; while (--n >= 0) { nsRect r; kid->GetRect(r); r.y += aDY; kid->SetRect(r); kid->GetNextSibling(kid); } // Slide line box too aLine->mBounds.y += aDY; } PRBool nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState, LineData* aLine, nsIFrame* aFrame, nsReflowStatus& aReflowResult) { NS_PRECONDITION(0 == aState.mLineLayout.GetPlacedFrames(), "non-empty line with a block"); nsInlineReflow& ir = *aState.mInlineReflow; // Prepare the inline reflow engine PRBool asBlock = PR_TRUE; const nsStyleDisplay* display; nsresult rv = aFrame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display); if ((NS_OK == rv) && (nsnull != display)) { switch (display->mDisplay) { case NS_STYLE_DISPLAY_TABLE: asBlock = PR_FALSE; break; } // clear floaters if the clear style is not none if (NS_STYLE_CLEAR_NONE != display->mBreakType) { aState.ClearFloaters(display->mBreakType); } } PrepareInlineReflow(aState, aFrame, asBlock); ir.SetIsFirstChild((aLine == mLines) && (aFrame == aLine->mFirstChild)); // Reflow the block frame nsReflowStatus reflowStatus = ir.ReflowFrame(aFrame); if (NS_IS_REFLOW_ERROR(reflowStatus)) { aReflowResult = reflowStatus; return PR_FALSE; } aReflowResult = reflowStatus; if (NS_FRAME_IS_COMPLETE(reflowStatus)) { aLine->SetLastContentIsComplete(); } else { aLine->ClearLastContentIsComplete(); } // XXX We need to check the *type* of break and if it's a column/page // break apply and cause the block to be split (assuming we are // laying out in a column). #if XXX if (NS_INLINE_IS_BREAK(reflowStatus)) { // XXX For now we ignore it } #endif // Align the frame nscoord maxAscent, maxDescent; ir.VerticalAlignFrames(aLine->mBounds, maxAscent, maxDescent); ir.HorizontalAlignFrames(aLine->mBounds); ir.RelativePositionFrames(); // Calculate margins nscoord topMargin, bottomMargin; PRUintn marginFlags = CalculateMargins(aState, aLine, PR_FALSE, topMargin, bottomMargin); // Try to place the frame nscoord newY = aLine->mBounds.YMost() + topMargin; if ((mLines != aLine) && (newY > aState.mBottomEdge)) { // The frame doesn't fit inside our available space. Push the // line to the next-in-flow and return our incomplete status to // our parent. PushLines(aState); aReflowResult = NS_FRAME_NOT_COMPLETE; return PR_FALSE; } aState.mY = newY; // Apply collapsed top-margin value if (0 != topMargin) { SlideFrames(aLine, topMargin); } // Record bottom margin value for sibling to sibling compression or // for returning as our carried out bottom margin. Adjust running // margin value when either we have carried margins from the line or // we have a non-zero height line. if ((HAVE_CARRIED_MARGIN & marginFlags) || (0 != aLine->mBounds.height)) { aState.mPrevBottomMargin = bottomMargin; aState.mPrevMarginFlags = marginFlags; } aLine->mCarriedOutTopMargin = ir.GetCarriedOutTopMargin(); aLine->mCarriedOutBottomMargin = ir.GetCarriedOutBottomMargin(); aLine->SetMarginFlags(marginFlags); nscoord xmost = aLine->mBounds.XMost(); if (xmost > aState.mKidXMost) { aState.mKidXMost = xmost; } // Update max-element-size if (aState.mComputeMaxElementSize) { const nsSize& kidMaxElementSize = ir.GetMaxElementSize(); if (kidMaxElementSize.width > aState.mMaxElementSize.width) { aState.mMaxElementSize.width = kidMaxElementSize.width; } if (kidMaxElementSize.height > aState.mMaxElementSize.height) { aState.mMaxElementSize.height = kidMaxElementSize.height; } } aState.mPrevChild = aFrame; // Refresh our available space in case a floater was placed by our // child. // XXX expensive! aState.GetAvailableSpace(); if (NS_FRAME_IS_NOT_COMPLETE(reflowStatus)) { // Some of the block fit. We need to have the block frame // continued, so we make sure that it has a next-in-flow now. nsIFrame* nextInFlow; nsresult rv; rv = nsHTMLContainerFrame::CreateNextInFlow(aState.mPresContext, this, aFrame, nextInFlow); if (NS_OK != rv) { aReflowResult = rv; return PR_FALSE; } if (nsnull != nextInFlow) { // We made a next-in-flow for the block child frame. Create a // line to map the block childs next-in-flow. LineData* line = new LineData(nextInFlow, 1, (LINE_IS_BLOCK | LINE_LAST_CONTENT_IS_COMPLETE)); if (nsnull == line) { aReflowResult = NS_ERROR_OUT_OF_MEMORY; return PR_FALSE; } line->mNext = aLine->mNext; aLine->mNext = line; } // Advance mPrevLine because we are keeping aLine (since some of // the child block frame fit). Then push any remaining lines to // our next-in-flow aState.mPrevLine = aLine; if (nsnull != aLine->mNext) { PushLines(aState); } aReflowResult = NS_INLINE_LINE_BREAK_AFTER(reflowStatus); return PR_FALSE; } return PR_TRUE; } /** * Reflow an inline frame. Returns PR_FALSE if the frame won't fit * on the line and the line should be flushed. */ PRBool nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState, LineData* aLine, nsIFrame* aFrame, nsReflowStatus& aReflowResult) { nsresult rv; nsIFrame* nextInFlow; if (!aState.mInlineReflowPrepared) { PrepareInlineReflow(aState, aFrame, PR_FALSE); } PRBool isFirstChild = (aLine == mLines) && (aFrame == aLine->mFirstChild); aState.mInlineReflow->SetIsFirstChild(isFirstChild); aReflowResult = aState.mInlineReflow->ReflowFrame(aFrame); if (NS_IS_REFLOW_ERROR(aReflowResult)) { return PR_FALSE; } PRBool lineWasComplete = aLine->GetLastContentIsComplete(); if (!NS_INLINE_IS_BREAK(aReflowResult)) { aState.mPrevChild = aFrame; if (NS_FRAME_IS_COMPLETE(aReflowResult)) { aFrame->GetNextSibling(aFrame); aLine->SetLastContentIsComplete(); return PR_TRUE; } // Create continuation frame (if necessary); add it to the end of // the current line so that it can be pushed to the next line // properly. aLine->ClearLastContentIsComplete(); rv = nsHTMLContainerFrame::CreateNextInFlow(aState.mPresContext, this, aFrame, nextInFlow); if (NS_OK != rv) { aReflowResult = rv; return PR_FALSE; } if (nsnull != nextInFlow) { // Add new child to the line aLine->mChildCount++; } aFrame->GetNextSibling(aFrame); } else { if (NS_INLINE_IS_BREAK_AFTER(aReflowResult)) { aState.mPrevChild = aFrame; if (NS_FRAME_IS_COMPLETE(aReflowResult)) { aLine->SetLastContentIsComplete(); } else { // Create continuation frame (if necessary); add it to the end of // the current line so that it can be pushed to the next line // properly. aLine->ClearLastContentIsComplete(); rv = nsHTMLContainerFrame::CreateNextInFlow(aState.mPresContext, this, aFrame, nextInFlow); if (NS_OK != rv) { aReflowResult = rv; return PR_FALSE; } if (nsnull != nextInFlow) { // Add new child to the line aLine->mChildCount++; } } aFrame->GetNextSibling(aFrame); } else { NS_ASSERTION(aLine->GetLastContentIsComplete(), "bad mState"); } } // Split line since we aren't going to keep going rv = SplitLine(aState, aLine, aFrame, lineWasComplete); if (NS_IS_REFLOW_ERROR(rv)) { aReflowResult = rv; } return PR_FALSE; } // XXX alloc lines using free-list in aState // XXX refactor this since the split NEVER has to deal with blocks nsresult nsBlockFrame::SplitLine(nsBlockReflowState& aState, LineData* aLine, nsIFrame* aFrame, PRBool aLineWasComplete) { PRInt32 pushCount = aLine->mChildCount - aState.mInlineReflow->GetCurrentFrameNum(); NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsBlockFrame::SplitLine: pushing %d frames", pushCount)); if (0 != pushCount) { NS_ASSERTION(nsnull != aFrame, "whoops"); LineData* 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 (to->mChildCount != 0) { LineData* insertedLine = new LineData(aFrame, pushCount, 0); aLine->mNext = insertedLine; insertedLine->mNext = to; to = insertedLine; } else { to->mFirstChild = aFrame; to->mChildCount += pushCount; } } else { to = new LineData(aFrame, pushCount, 0); aLine->mNext = to; } if (nsnull == to) { return NS_ERROR_OUT_OF_MEMORY; } to->SetLastContentIsComplete(aLineWasComplete); aLine->mChildCount -= pushCount; #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { aLine->Verify(); } #endif NS_ASSERTION(0 != aLine->mChildCount, "bad push"); // Let inline reflow know that some frames are no longer part of // its state. aState.mInlineReflow->ChangeFrameCount(aLine->mChildCount); } return NS_OK; } PRBool nsBlockFrame::PullFrame(nsBlockReflowState& aState, LineData* aLine, LineData** aFromList, PRBool aUpdateGeometricParent, nsReflowStatus& aReflowResult) { LineData* fromLine = *aFromList; NS_ASSERTION(nsnull != fromLine, "bad line to pull from"); if (0 == fromLine->mChildCount) { // Discard empty lines immediately. Empty lines can happen here // because of DeleteChildsNextInFlow not being able to delete // lines. *aFromList = fromLine->mNext; NS_ASSERTION(nsnull == fromLine->mFirstChild, "bad empty line"); fromLine->mNext = aState.mFreeList; aState.mFreeList = fromLine; return PR_TRUE; } // If our line is not empty and the child in aFromLine is a block // then we cannot pull up the frame into this line. if ((0 != aLine->mChildCount) && fromLine->IsBlock()) { aReflowResult = NS_INLINE_LINE_BREAK_BEFORE(); return PR_FALSE; } // Take frame from fromLine nsIFrame* frame = fromLine->mFirstChild; if (0 == aLine->mChildCount++) { aLine->mFirstChild = frame; aLine->SetIsBlock(fromLine->IsBlock()); #ifdef NS_DEBUG const nsStyleDisplay* display; frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display); const nsStylePosition* position; frame->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&) position); PRBool isBlock = nsLineLayout::TreatFrameAsBlock(display, position); NS_ASSERTION(isBlock == aLine->IsBlock(), "bad line isBlock"); #endif } if (0 != --fromLine->mChildCount) { frame->GetNextSibling(fromLine->mFirstChild); } else { // Free up the fromLine now that it's empty *aFromList = fromLine->mNext; fromLine->mFirstChild = nsnull; fromLine->mNext = aState.mFreeList; aState.mFreeList = fromLine; } // Change geometric parents if (aUpdateGeometricParent) { nsIFrame* geometricParent; nsIFrame* contentParent; frame->GetGeometricParent(geometricParent); frame->GetContentParent(contentParent); if (contentParent == geometricParent) { frame->SetContentParent(this); } frame->SetGeometricParent(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); } // Reflow the frame if (aLine->IsBlock()) { return ReflowBlockFrame(aState, aLine, frame, aReflowResult); } else { return ReflowInlineFrame(aState, aLine, frame, aReflowResult); } } // XXX This is identical to the back end of the block reflow code, not // counting the continuation of block frames part. Factor this! PRBool nsBlockFrame::PlaceLine(nsBlockReflowState& aState, LineData* aLine, nsReflowStatus aReflowStatus) { // Align the children. This also determines the actual height and // width of the line. nsInlineReflow& ir = *aState.mInlineReflow; nscoord maxAscent, maxDescent; ir.VerticalAlignFrames(aLine->mBounds, maxAscent, maxDescent); ir.HorizontalAlignFrames(aLine->mBounds); ir.RelativePositionFrames(); // Calculate the bottom margin for the line. nscoord lineBottomMargin = 0; if (0 == aLine->mBounds.height) { nsIFrame* brFrame = aState.mLineLayout.GetBRFrame(); if (nsnull != brFrame) { // If a line ends in a BR and the line is empty of height then we // make sure that the line ends up with some height anyway. Note // that the height looks like vertical margin so that it can // compress with other block margins. nsIStyleContext* brSC; nsIPresContext& px = aState.mPresContext; nsresult rv = brFrame->GetStyleContext(&px, brSC); if ((NS_OK == rv) && (nsnull != brSC)) { const nsStyleFont* font = (const nsStyleFont*) brSC->GetStyleData(eStyleStruct_Font); nsIFontMetrics* fm = px.GetMetricsFor(font->mFont); if (nsnull != fm) { fm->GetHeight(lineBottomMargin); NS_RELEASE(fm); } NS_RELEASE(brSC); } } } // Calculate the lines top and bottom margin values. The margin will // come from an embedded block frame, not from inline frames. nscoord topMargin, bottomMargin; PRUintn marginFlags = CalculateMargins(aState, aLine, PR_TRUE, topMargin, bottomMargin); // See if the line fit. If it doesn't we need to push it. Our first // line will always fit. // XXX This is a good place to check and see if we have // below-current-line floaters, and if we do make sure that they fit // too. // XXX don't forget to factor in the top/bottom margin when sharing // this with the block code nscoord newY = aLine->mBounds.YMost() + topMargin + lineBottomMargin; NS_FRAME_TRACE(NS_FRAME_TRACE_CHILD_REFLOW, ("nsBlockFrame::PlaceLine: newY=%d limit=%d lineHeight=%d", newY, aState.mBottomEdge, aLine->mBounds.height)); 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); return PR_FALSE; } // Apply collapsed top-margin value // XXX I bet the bullet placement just got broken by this code if (0 != topMargin) { SlideFrames(aLine, topMargin); } // Adjust running margin value when either we have carried margins // from the line or we have a non-zero height line. if ((HAVE_CARRIED_MARGIN & marginFlags) || (0 != aLine->mBounds.height)) { aState.mPrevBottomMargin = bottomMargin; aState.mPrevMarginFlags = marginFlags; } aLine->mCarriedOutTopMargin = ir.GetCarriedOutTopMargin(); aLine->mCarriedOutBottomMargin = ir.GetCarriedOutBottomMargin(); aLine->SetMarginFlags(marginFlags); // Now that we know the line is staying put, put in the outside // bullet if we have one. if ((nsnull == mPrevInFlow) && (aLine == mLines) && (nsnull != mBullet)) { const nsStyleList* list; GetStyleData(eStyleStruct_List, (const nsStyleStruct*&)list); if (NS_STYLE_LIST_STYLE_POSITION_OUTSIDE == list->mListStylePosition) { // Reflow the bullet now nsSize availSize; availSize.width = NS_UNCONSTRAINEDSIZE; availSize.height = NS_UNCONSTRAINEDSIZE; nsHTMLReflowState reflowState(aState.mPresContext, mBullet, aState, availSize, &aState.mLineLayout); nsHTMLReflowMetrics metrics(nsnull); nsIHTMLReflow* htmlReflow; if (NS_OK == mBullet->QueryInterface(kIHTMLReflowIID, (void**) &htmlReflow)) { nsReflowStatus status; htmlReflow->WillReflow(aState.mPresContext); htmlReflow->Reflow(aState.mPresContext, metrics, reflowState, status); htmlReflow->DidReflow(aState.mPresContext, NS_FRAME_REFLOW_FINISHED); } // Place the bullet now; use its right margin to distance it // from the rest of the frames in the line nsMargin margin; const nsStyleSpacing* spacing; mBullet->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&) spacing); spacing->CalcMarginFor(mBullet, margin); nscoord x = aState.mBorderPadding.left - margin.right - metrics.width; // XXX This calculation is wrong, especially if // vertical-alignment occurs on the line! nscoord y = aState.mBorderPadding.top + maxAscent - metrics.ascent + topMargin; mBullet->SetRect(nsRect(x, y, metrics.width, metrics.height)); } } // Update max-element-size // Update max-element-size if (aState.mComputeMaxElementSize) { const nsSize& kidMaxElementSize = ir.GetMaxElementSize(); if (kidMaxElementSize.width > aState.mMaxElementSize.width) { aState.mMaxElementSize.width = kidMaxElementSize.width; } if (kidMaxElementSize.height > aState.mMaxElementSize.height) { aState.mMaxElementSize.height = kidMaxElementSize.height; } } nscoord xmost = aLine->mBounds.XMost(); if (xmost > aState.mKidXMost) { aState.mKidXMost = xmost; } aState.mY = newY; // Any below current line floaters to place? if (0 != aState.mPendingFloaters.Count()) { aState.PlaceFloaters(&aState.mPendingFloaters); aState.mPendingFloaters.Clear(); // 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... } // Based on the last child we reflowed reflow status, we may need to // clear past any floaters. if (NS_INLINE_IS_BREAK_AFTER(aReflowStatus)) { PRUint8 breakType = NS_INLINE_GET_BREAK_TYPE(aReflowStatus); // Apply break to the line switch (breakType) { default: break; case NS_STYLE_CLEAR_LEFT: case NS_STYLE_CLEAR_RIGHT: case NS_STYLE_CLEAR_LEFT_AND_RIGHT: NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsBlockFrame::PlaceLine: clearing floaters=%d", breakType)); aState.ClearFloaters(breakType); break; } // XXX page breaks, etc, need to be passed upwards too! } // Update available space after placing line in case below current // line floaters were placed or in case we just used up the space in // the current band and are ready to move into a new band. aState.GetAvailableSpace(); return PR_TRUE; } static nsresult FindFloatersIn(nsIFrame* aFrame, nsVoidArray*& aArray) { const nsStyleDisplay* display; aFrame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display); if (NS_STYLE_FLOAT_NONE != display->mFloats) { if (nsnull == aArray) { aArray = new nsVoidArray(); if (nsnull == aArray) { return NS_ERROR_OUT_OF_MEMORY; } } aArray->AppendElement(aFrame); } if (NS_STYLE_DISPLAY_INLINE == display->mDisplay) { nsIFrame* kid; aFrame->FirstChild(kid); while (nsnull != kid) { nsresult rv = FindFloatersIn(kid, aArray); if (NS_OK != rv) { return rv; } kid->GetNextSibling(kid); } } return NS_OK; } void nsBlockFrame::FindFloaters(LineData* aLine) { nsVoidArray* floaters = aLine->mFloaters; if (nsnull != floaters) { // Empty floater array before proceeding floaters->Clear(); } nsIFrame* frame = aLine->mFirstChild; PRInt32 n = aLine->mChildCount; while (--n >= 0) { FindFloatersIn(frame, floaters); frame->GetNextSibling(frame); } aLine->mFloaters = floaters; // Get rid of floater array if we don't need it if (nsnull != floaters) { if (0 == floaters->Count()) { delete floaters; aLine->mFloaters = nsnull; } } } void nsBlockFrame::PushLines(nsBlockReflowState& aState) { NS_ASSERTION(nsnull != aState.mPrevLine, "bad push"); LineData* lastLine = aState.mPrevLine; LineData* nextLine = lastLine->mNext; lastLine->mNext = nsnull; mOverflowLines = nextLine; // Break frame sibling list nsIFrame* lastFrame = lastLine->LastChild(); lastFrame->SetNextSibling(nsnull); NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsBlockFrame::PushLines: line=%p prevInFlow=%p nextInFlow=%p", mOverflowLines, mPrevInFlow, mNextInFlow)); #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyChildCount(mLines); VerifyChildCount(mOverflowLines, PR_TRUE); } #endif } PRBool nsBlockFrame::DrainOverflowLines() { PRBool drained = PR_FALSE; // First grab the prev-in-flows overflow lines nsBlockFrame* prevBlock = (nsBlockFrame*) mPrevInFlow; if (nsnull != prevBlock) { LineData* line = prevBlock->mOverflowLines; if (nsnull != line) { drained = PR_TRUE; NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsBlockFrame::DrainOverflowLines: line=%p prevInFlow=%p", line, prevBlock)); prevBlock->mOverflowLines = nsnull; // Make all the frames on the mOverflowLines list mine nsIFrame* lastFrame = nsnull; nsIFrame* frame = line->mFirstChild; while (nsnull != frame) { nsIFrame* geometricParent; nsIFrame* contentParent; frame->GetGeometricParent(geometricParent); frame->GetContentParent(contentParent); if (contentParent == geometricParent) { frame->SetContentParent(this); } frame->SetGeometricParent(this); 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 LineData* lastLine = 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. NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsBlockFrame::DrainOverflowLines: from me, line=%p", mOverflowLines)); LineData* lastLine = LastLine(mLines); if (nsnull == lastLine) { mLines = mOverflowLines; } else { lastLine->mNext = mOverflowLines; nsIFrame* lastFrame = lastLine->LastChild(); lastFrame->SetNextSibling(mOverflowLines->mFirstChild); // Update our last-content-index now that we have a new last child lastLine = LastLine(mOverflowLines); } mOverflowLines = nsnull; drained = PR_TRUE; } #ifdef NS_DEBUG if (GetVerifyTreeEnable()) { VerifyChildCount(mLines, PR_TRUE); } #endif return drained; } nsresult nsBlockFrame::InsertNewFrame(nsIPresContext& aPresContext, nsBlockFrame* aParentFrame, nsIFrame* aNewFrame, nsIFrame* aPrevSibling) { const nsStyleDisplay* display; aNewFrame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display); const nsStylePosition* position; aNewFrame->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&) position); PRUint16 newFrameIsBlock = nsLineLayout::TreatFrameAsBlock(display, position) ? LINE_IS_BLOCK : 0; // See if we need to move the frame outside of the flow, and insert a // placeholder frame in its place nsIFrame* placeholder; if (MoveFrameOutOfFlow(aPresContext, aNewFrame, display, position, placeholder)) { // Add the placeholder frame to the flow aNewFrame = placeholder; newFrameIsBlock = PR_FALSE; // placeholder frame is always inline } else { // Wrap the frame in a view if necessary nsIStyleContext* kidSC; aNewFrame->GetStyleContext(&aPresContext, kidSC); nsresult rv = CreateViewForFrame(aPresContext, aNewFrame, kidSC, PR_FALSE); NS_RELEASE(kidSC); if (NS_OK != rv) { return rv; } } // Insert/append the frame into flows line list at the right spot LineData* newLine; LineData* line = aParentFrame->mLines; if (nsnull == aPrevSibling) { // Insert new frame into the sibling list aNewFrame->SetNextSibling(line->mFirstChild); if (line->IsBlock() || newFrameIsBlock) { // Create a new line newLine = new LineData(aNewFrame, 1, LINE_LAST_CONTENT_IS_COMPLETE | newFrameIsBlock); if (nsnull == newLine) { return NS_ERROR_OUT_OF_MEMORY; } newLine->mNext = aParentFrame->mLines; aParentFrame->mLines = newLine; } else { // Insert frame at the front of the line line->mFirstChild = aNewFrame; line->mChildCount++; line->MarkDirty(); } } else { // Find line containing the previous sibling to the new frame line = FindLineContaining(line, aPrevSibling); NS_ASSERTION(nsnull != line, "no line contains the previous sibling"); if (nsnull != line) { if (line->IsBlock()) { // Create a new line just after line newLine = new LineData(aNewFrame, 1, LINE_LAST_CONTENT_IS_COMPLETE | newFrameIsBlock); if (nsnull == newLine) { return NS_ERROR_OUT_OF_MEMORY; } newLine->mNext = line->mNext; line->mNext = newLine; } else if (newFrameIsBlock) { // Split line in two, if necessary. We can't allow a block to // end up in an inline line. if (line->IsLastChild(aPrevSibling)) { // The new frame goes after prevSibling and prevSibling is // the last frame on the line. Therefore we don't need to // split the line, just create a new line. newLine = new LineData(aNewFrame, 1, LINE_LAST_CONTENT_IS_COMPLETE | newFrameIsBlock); if (nsnull == newLine) { return NS_ERROR_OUT_OF_MEMORY; } newLine->mNext = line->mNext; line->mNext = newLine; } else { // The new frame goes after prevSibling and prevSibling is // somewhere in the line, but not at the end. Split the line // just after prevSibling. PRInt32 i, n = line->mChildCount; nsIFrame* frame = line->mFirstChild; for (i = 0; i < n; i++) { if (frame == aPrevSibling) { nsIFrame* nextSibling; aPrevSibling->GetNextSibling(nextSibling); // Create new line to hold the remaining frames NS_ASSERTION(n - i - 1 > 0, "bad line count"); newLine = new LineData(nextSibling, n - i - 1, line->mState & LINE_LAST_CONTENT_IS_COMPLETE); if (nsnull == newLine) { return NS_ERROR_OUT_OF_MEMORY; } newLine->mNext = line->mNext; line->mNext = newLine; line->MarkDirty(); line->SetLastContentIsComplete(); line->mChildCount = i + 1; break; } frame->GetNextSibling(frame); } // Now create a new line to hold the block newLine = new LineData(aNewFrame, 1, newFrameIsBlock | LINE_LAST_CONTENT_IS_COMPLETE); if (nsnull == newLine) { return NS_ERROR_OUT_OF_MEMORY; } newLine->mNext = line->mNext; line->mNext = newLine; } } else { // Insert frame into the line. //XXX NS_ASSERTION(line->GetLastContentIsComplete(), "bad line LCIC"); line->mChildCount++; line->MarkDirty(); } } // Insert new frame into the sibling list; note: this must be done // after the above logic because the above logic depends on the // sibling list being in the "before insertion" state. nsIFrame* nextSibling; aPrevSibling->GetNextSibling(nextSibling); aNewFrame->SetNextSibling(nextSibling); aPrevSibling->SetNextSibling(aNewFrame); } return NS_OK; } PRBool nsBlockFrame::DeleteChildsNextInFlow(nsIPresContext& aPresContext, nsIFrame* aChild) { NS_PRECONDITION(IsChild(aChild), "bad geometric parent"); nsIFrame* nextInFlow; nsBlockFrame* parent; aChild->GetNextInFlow(nextInFlow); NS_PRECONDITION(nsnull != nextInFlow, "null next-in-flow"); nextInFlow->GetGeometricParent((nsIFrame*&)parent); // If the next-in-flow has a next-in-flow then delete it, too (and // delete it first). nsIFrame* nextNextInFlow; nextInFlow->GetNextInFlow(nextNextInFlow); if (nsnull != nextNextInFlow) { parent->DeleteChildsNextInFlow(aPresContext, nextInFlow); } #ifdef NS_DEBUG PRInt32 childCount; nsIFrame* firstChild; nextInFlow->FirstChild(firstChild); childCount = LengthOf(firstChild); NS_ASSERTION((0 == childCount) && (nsnull == firstChild), "deleting !empty next-in-flow"); #endif // Disconnect the next-in-flow from the flow list nextInFlow->BreakFromPrevFlow(); // Remove nextInFlow from the parents line list. Also remove it from // the sibling list. if (RemoveChild(parent->mLines, nextInFlow)) { NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsBlockFrame::DeleteNextInFlowsFor: frame=%p (from mLines)", nextInFlow)); goto done; } // If we get here then we didn't find the child on the line list. If // it's not there then it has to be on the overflow lines list. if (nsnull != mOverflowLines) { if (RemoveChild(parent->mOverflowLines, nextInFlow)) { NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsBlockFrame::DeleteNextInFlowsFor: frame=%p (from overflow)", nextInFlow)); goto done; } } NS_NOTREACHED("can't find next-in-flow in overflow list"); done:; // If the parent is us then we will finish reflowing and update the // content offsets of our parents when we are a pseudo-frame; if the // parent is not us then it's a next-in-flow which means it will get // reflowed by our parent and fix its content offsets. So there. // Delete the next-in-flow frame and adjust its parents child count nextInFlow->DeleteFrame(aPresContext); #ifdef NS_DEBUG aChild->GetNextInFlow(nextInFlow); NS_POSTCONDITION(nsnull == nextInFlow, "non null next-in-flow"); #endif return PR_TRUE; } PRBool nsBlockFrame::RemoveChild(LineData* aLines, nsIFrame* aChild) { LineData* line = aLines; nsIFrame* prevChild = nsnull; while (nsnull != line) { nsIFrame* child = line->mFirstChild; PRInt32 n = line->mChildCount; while (--n >= 0) { nsIFrame* nextChild; child->GetNextSibling(nextChild); if (child == aChild) { NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("nsBlockFrame::RemoveChild: line=%p frame=%p", line, aChild)); // Continuations HAVE to be at the start of a line NS_ASSERTION(child == line->mFirstChild, "bad continuation"); line->mFirstChild = nextChild; if (0 == --line->mChildCount) { line->mFirstChild = nsnull; } if (nsnull != prevChild) { // When nextInFlow and it's continuation are in the same // container then we remove the nextInFlow from the sibling // list. prevChild->SetNextSibling(nextChild); } return PR_TRUE; } prevChild = child; child = nextChild; } line = line->mNext; } return PR_FALSE; } //////////////////////////////////////////////////////////////////////// // Floater support void nsBlockFrame::ReflowFloater(nsIPresContext& aPresContext, nsBlockReflowState& aState, nsIFrame* aFloaterFrame) { // Prepare the reflow state for the floater frame. Note that // initially it's maxSize will be 0,0 until we compute it. nsSize kidAvailSize(0, 0); nsHTMLReflowState reflowState(aPresContext, aFloaterFrame, aState, kidAvailSize, eReflowReason_Initial); // If either dimension is constrained then get the border and // padding values in advance. nsMargin bp(0, 0, 0, 0); if (reflowState.HaveConstrainedWidth() || reflowState.HaveConstrainedHeight()) { const nsStyleSpacing* spacing; if (NS_OK == aFloaterFrame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&)spacing)) { spacing->CalcBorderPaddingFor(aFloaterFrame, bp); } } // Compute the available width for the floater if (reflowState.HaveConstrainedWidth()) { // When the floater has a contrained width, give it just enough // space for its styled width plus its borders and paddings. kidAvailSize.width = reflowState.minWidth + bp.left + bp.right; } else { // If we are floating something and we don't know the width then // find a maximum width for it to reflow into. Walk upwards until // we find something with an unconstrained width. const nsHTMLReflowState* rsp = &aState; kidAvailSize.width = 0; while (nsnull != rsp) { if (eHTMLFrameConstraint_Unconstrained != rsp->widthConstraint) { kidAvailSize.width = rsp->minWidth; break; } else if (NS_UNCONSTRAINEDSIZE != rsp->widthConstraint) { kidAvailSize.width = rsp->maxSize.width; if (kidAvailSize.width > 0) { break; } } // XXX This cast is unfortunate! rsp = (const nsHTMLReflowState*) rsp->parentReflowState; } } // Compute the available height for the floater if (reflowState.HaveConstrainedHeight()) { kidAvailSize.height = reflowState.minHeight + bp.top + bp.bottom; } else { kidAvailSize.height = NS_UNCONSTRAINEDSIZE; } reflowState.maxSize = kidAvailSize; // Resize reflow the anchored item into the available space nsIHTMLReflow* floaterReflow; if (NS_OK == aFloaterFrame->QueryInterface(kIHTMLReflowIID, (void**)&floaterReflow)) { nsHTMLReflowMetrics desiredSize(nsnull); nsReflowStatus status; floaterReflow->WillReflow(aPresContext); floaterReflow->Reflow(aPresContext, desiredSize, reflowState, status); aFloaterFrame->SizeTo(desiredSize.width, desiredSize.height); } } PRBool nsBlockFrame::AddFloater(nsIPresContext* aPresContext, const nsHTMLReflowState& aReflowState, nsIFrame* aFloater, nsPlaceholderFrame* aPlaceholder) { // Walk up reflow state chain, looking for ourself const nsReflowState* rs = &aReflowState; while (nsnull != rs) { if (rs->frame == this) { break; } rs = rs->parentReflowState; } if (nsnull == rs) { // Never mind return PR_FALSE; } nsBlockReflowState* state = (nsBlockReflowState*) rs; // 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); // Reflow the floater (the first time we do it here; later on it's // done during the reflow of the line that contains the floater) ReflowFloater(*aPresContext, *state, aFloater); return PR_TRUE; } return PR_FALSE; } // 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(nsPlaceholderFrame* aPlaceholder) { // Update the current line's floater array NS_ASSERTION(nsnull != mCurrentLine, "null ptr"); if (nsnull == mCurrentLine->mFloaters) { mCurrentLine->mFloaters = new nsVoidArray(); } mCurrentLine->mFloaters->AppendElement(aPlaceholder); // Now place the floater immediately if possible. Otherwise stash it // away in mPendingFloaters and place it later. if (0 == mLineLayout.GetPlacedFrames()) { NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsBlockReflowState::AddFloater: IsLeftMostChild, placeHolder=%p", aPlaceholder)); // 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. PRBool isLeftFloater; nscoord ox, oy; mSpaceManager->GetTranslation(ox, oy); nscoord dx = ox - mSpaceManagerX; nscoord dy = oy - mSpaceManagerY; mSpaceManager->Translate(-dx, -dy); PlaceFloater(aPlaceholder, isLeftFloater); // Pass on updated available space to the current inline reflow engine GetAvailableSpace(); mLineLayout.UpdateInlines(mCurrentBand.availSpace.x + mBorderPadding.left, mY, mCurrentBand.availSpace.width, mCurrentBand.availSpace.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). NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsBlockReflowState::AddFloater: pending, placeHolder=%p", aPlaceholder)); mPendingFloaters.AppendElement(aPlaceholder); } } PRBool nsBlockReflowState::IsLeftMostChild(nsIFrame* aFrame) { for (;;) { nsIFrame* parent; aFrame->GetGeometricParent(parent); if (parent == mBlock) { nsIFrame* child = mCurrentLine->mFirstChild; PRInt32 n = mCurrentLine->mChildCount; 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(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; } void nsBlockReflowState::PlaceFloater(nsPlaceholderFrame* aPlaceholder, PRBool& aIsLeftFloater) { nsIFrame* floater = aPlaceholder->GetAnchoredItem(); // Reflow the floater if it's targetted for a reflow if (nsnull != reflowCommand) { nsIFrame* target; reflowCommand->GetTarget(target); if (floater == target) { mBlock->ReflowFloater(mPresContext, *this, floater); } } // Get the type of floater const nsStyleDisplay* floaterDisplay; const nsStyleSpacing* floaterSpacing; floater->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)floaterDisplay); floater->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&)floaterSpacing); // See if the floater should clear any preceeding floaters... if (NS_STYLE_CLEAR_NONE != floaterDisplay->mBreakType) { ClearFloaters(floaterDisplay->mBreakType); } else { // Get the band of available space GetAvailableSpace(); } // Get the floaters bounding box and margin information nsRect region; floater->GetRect(region); nsMargin floaterMargin; floaterSpacing->CalcMarginFor(floater, floaterMargin); // Adjust the floater size by its margin. That's the area that will // impact the space manager. region.width += floaterMargin.left + floaterMargin.right; region.height += floaterMargin.top + floaterMargin.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 (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). while ((mCurrentBand.availSpace.width < region.width) && (mCurrentBand.availSpace.width < mContentArea.width)) { // The CSS2 spec says that floaters should be placed as high as // possible. We accomodate this easily by noting that if the band // is not the full width of the content area then it must have // been impacted by a floater. And we know that the height of the // band will be the height of the shortest floater, therefore we // adjust mY by that distance and keep trying until we have enough // space for this floater. mY += mCurrentBand.availSpace.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. if (NS_STYLE_FLOAT_LEFT == floaterDisplay->mFloats) { aIsLeftFloater = PR_TRUE; region.x = mCurrentBand.availSpace.x; } else { aIsLeftFloater = PR_FALSE; region.x = mCurrentBand.availSpace.XMost() - region.width; } region.y = mY - mBorderPadding.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 mSpaceManager->AddRectRegion(floater, region); // 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. floater->MoveTo(mSpaceManagerX + floaterMargin.left + region.x, mSpaceManagerY + floaterMargin.top + region.y); } /** * Place below-current-line floaters. */ void nsBlockReflowState::PlaceFloaters(nsVoidArray* aFloaters) { NS_PRECONDITION(aFloaters->Count() > 0, "no floaters"); PRInt32 numFloaters = aFloaters->Count(); for (PRInt32 i = 0; i < numFloaters; i++) { nsPlaceholderFrame* placeholderFrame = (nsPlaceholderFrame*) aFloaters->ElementAt(i); if (IsLeftMostChild(placeholderFrame)) { // Left-most children are placed during the line's reflow continue; } PRBool isLeftFloater; PlaceFloater(placeholderFrame, isLeftFloater); } // Update available spcae now that the floaters have been placed GetAvailableSpace(); } void nsBlockReflowState::ClearFloaters(PRUint8 aBreakType) { // Update band information based on current mY before clearing GetAvailableSpace(); for (;;) { PRBool haveFloater = PR_FALSE; // Find the Y coordinate to clear to. Note that the band trapezoid // coordinates are relative to the our spacemanager translation // (which means the band coordinates are inside the border+padding // area of this block frame). NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsBlockReflowState::ClearFloaters: mY=%d trapCount=%d", mY, mCurrentBand.count)); nscoord clearYMost = mY - mBorderPadding.top; nsRect tmp; PRInt32 i; for (i = 0; i < mCurrentBand.count; i++) { const nsStyleDisplay* display; nsBandTrapezoid* trapezoid = &mCurrentBand.data[i]; NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsBlockReflowState::ClearFloaters: trap=%d state=%d", i, trapezoid->state)); if (nsBandTrapezoid::Available != trapezoid->state) { haveFloater = PR_TRUE; if (nsBandTrapezoid::OccupiedMultiple == trapezoid->state) { PRInt32 fn, numFrames = trapezoid->frames->Count(); NS_ASSERTION(numFrames > 0, "bad trapezoid frame list"); for (fn = 0; fn < numFrames; fn++) { nsIFrame* frame = (nsIFrame*) trapezoid->frames->ElementAt(fn); frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsBlockReflowState::ClearFloaters: frame[%d]=%p floats=%d", fn, frame, display->mFloats)); switch (display->mFloats) { case NS_STYLE_FLOAT_LEFT: if ((NS_STYLE_CLEAR_LEFT == aBreakType) || (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aBreakType)) { trapezoid->GetRect(tmp); nscoord ym = tmp.YMost(); if (ym > clearYMost) { clearYMost = ym; } } break; case NS_STYLE_FLOAT_RIGHT: if ((NS_STYLE_CLEAR_RIGHT == aBreakType) || (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aBreakType)) { trapezoid->GetRect(tmp); nscoord ym = tmp.YMost(); if (ym > clearYMost) { clearYMost = ym; NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsBlockReflowState::ClearFloaters: right clearYMost=%d", clearYMost)); } } break; } } } else { trapezoid->frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsBlockReflowState::ClearFloaters: frame=%p floats=%d", trapezoid->frame, display->mFloats)); switch (display->mFloats) { case NS_STYLE_FLOAT_LEFT: if ((NS_STYLE_CLEAR_LEFT == aBreakType) || (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aBreakType)) { trapezoid->GetRect(tmp); nscoord ym = tmp.YMost(); if (ym > clearYMost) { clearYMost = ym; NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsBlockReflowState::ClearFloaters: left clearYMost=%d", clearYMost)); } } break; case NS_STYLE_FLOAT_RIGHT: if ((NS_STYLE_CLEAR_RIGHT == aBreakType) || (NS_STYLE_CLEAR_LEFT_AND_RIGHT == aBreakType)) { trapezoid->GetRect(tmp); nscoord ym = tmp.YMost(); if (ym > clearYMost) { clearYMost = ym; NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsBlockReflowState::ClearFloaters: right clearYMost=%d", clearYMost)); } } break; } } } } // Nothing to clear if (!haveFloater || (clearYMost == mY - mBorderPadding.top)) { break; } NS_FRAME_LOG(NS_FRAME_TRACE_CHILD_REFLOW, ("nsBlockReflowState::ClearFloaters: mY=%d clearYMost=%d", mY, clearYMost)); mY = mBorderPadding.top + clearYMost + 1; // Get a new band GetAvailableSpace(); } } ////////////////////////////////////////////////////////////////////// // 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; } NS_IMETHODIMP nsBlockFrame::Paint(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect) { // Paint our background and border const nsStyleDisplay* disp = (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); if (disp->mVisible && mRect.width && mRect.height) { PRIntn skipSides = GetSkipSides(); const nsStyleColor* color = (const nsStyleColor*)mStyleContext->GetStyleData(eStyleStruct_Color); const nsStyleSpacing* spacing = (const nsStyleSpacing*)mStyleContext->GetStyleData(eStyleStruct_Spacing); nsRect rect(0, 0, mRect.width, mRect.height); nsCSSRendering::PaintBackground(aPresContext, aRenderingContext, this, aDirtyRect, rect, *color, 0, 0); nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, this, aDirtyRect, rect, *spacing, skipSides); } PaintChildren(aPresContext, aRenderingContext, aDirtyRect); if (nsIFrame::GetShowFrameBorders()) { nsIView* view; GetView(view); if (nsnull != view) { aRenderingContext.SetColor(NS_RGB(0,0,255)); } else { aRenderingContext.SetColor(NS_RGB(255,0,0)); } aRenderingContext.DrawRect(0, 0, mRect.width, mRect.height); } return NS_OK; } // aDirtyRect is in our coordinate system // child rect's are also in our coordinate system void nsBlockFrame::PaintChildren(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect) { // Set clip rect so that children don't leak out of us, but only if // we are supposed to. const nsStyleDisplay* disp = (const nsStyleDisplay*) mStyleContext->GetStyleData(eStyleStruct_Display); PRBool hidden = PR_FALSE; if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) { aRenderingContext.PushState(); aRenderingContext.SetClipRect(nsRect(0, 0, mRect.width, mRect.height), nsClipCombine_kIntersect); hidden = PR_TRUE; } // See if we should render everything, or just what can be seen PRBool renderEverything = PR_TRUE; if (NS_STYLE_OVERFLOW_VISIBLE != disp->mOverflow) { renderEverything = PR_FALSE; } renderEverything = PR_FALSE; if ((nsnull == mPrevInFlow) && (nsnull != mBullet)) { const nsStyleList* list; GetStyleData(eStyleStruct_List, (const nsStyleStruct*&)list); if (NS_STYLE_LIST_STYLE_POSITION_OUTSIDE == list->mListStylePosition) { // Paint the bullet too PaintChild(aPresContext, aRenderingContext, aDirtyRect, mBullet, PR_TRUE); } } //XXX ListTag(stdout); printf(": overflow=%d dirtyRect={%d,%d,%d,%d}\n", disp->mOverflow, aDirtyRect); // Iterate the lines looking for lines that intersect the dirty rect for (LineData* line = mLines; nsnull != line; line = line->mNext) { // Stop when we get to a line that's below the dirty rect if (line->mBounds.y >= aDirtyRect.YMost()) { break; } // If the line overlaps the dirty rect then iterate the child frames // and paint those frames that intersect the dirty rect if (line->mBounds.YMost() > aDirtyRect.y) { nsIFrame* kid = line->mFirstChild; for (PRUint16 i = 0; i < line->mChildCount; i++) { PaintChild(aPresContext, aRenderingContext, aDirtyRect, kid, renderEverything); kid->GetNextSibling(kid); } } } if (hidden) { aRenderingContext.PopState(); } } void nsBlockFrame::PaintChild(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect, nsIFrame* aFrame, PRBool aAlwaysRender) { nsIView *pView; aFrame->GetView(pView); if (nsnull == pView) { nsRect kidRect; aFrame->GetRect(kidRect); nsRect damageArea; PRBool overlap; if (aAlwaysRender) { overlap = PR_TRUE; damageArea = aDirtyRect; } else { overlap = damageArea.IntersectRect(aDirtyRect, kidRect); #ifdef NS_DEBUG if (!overlap && (0 == kidRect.width) && (0 == kidRect.height)) { overlap = PR_TRUE; } #endif } if (overlap || aAlwaysRender) { // Translate damage area into kid's coordinate system nsRect kidDamageArea(damageArea.x - kidRect.x, damageArea.y - kidRect.y, damageArea.width, damageArea.height); aRenderingContext.PushState(); aRenderingContext.Translate(kidRect.x, kidRect.y); aFrame->Paint(aPresContext, aRenderingContext, kidDamageArea); #ifdef NS_DEBUG if (nsIFrame::GetShowFrameBorders() && (0 != kidRect.width) && (0 != kidRect.height)) { nsIView* view; GetView(view); if (nsnull != view) { aRenderingContext.SetColor(NS_RGB(0,0,255)); } else { aRenderingContext.SetColor(NS_RGB(255,0,0)); } aRenderingContext.DrawRect(0, 0, kidRect.width, kidRect.height); } #endif aRenderingContext.PopState(); } } } ////////////////////////////////////////////////////////////////////// // Debugging #ifdef NS_DEBUG static PRBool InLineList(LineData* aLines, nsIFrame* aFrame) { while (nsnull != aLines) { nsIFrame* frame = aLines->mFirstChild; PRInt32 n = aLines->mChildCount; while (--n >= 0) { if (frame == aFrame) { return PR_TRUE; } frame->GetNextSibling(frame); } aLines = aLines->mNext; } return PR_FALSE; } static PRBool InSiblingList(LineData* aLine, nsIFrame* aFrame) { if (nsnull != aLine) { nsIFrame* frame = aLine->mFirstChild; while (nsnull != frame) { if (frame == aFrame) { return PR_TRUE; } frame->GetNextSibling(frame); } } return PR_FALSE; } PRBool nsBlockFrame::IsChild(nsIFrame* aFrame) { nsIFrame* parent; aFrame->GetGeometricParent(parent); if (parent != (nsIFrame*)this) { return PR_FALSE; } if (InLineList(mLines, aFrame) && InSiblingList(mLines, aFrame)) { return PR_TRUE; } if (InLineList(mOverflowLines, aFrame) && InSiblingList(mOverflowLines, aFrame)) { return PR_TRUE; } return PR_FALSE; } #endif #define VERIFY_ASSERT(_expr, _msg) \ if (!(_expr)) { \ DumpTree(); \ } \ NS_ASSERTION(_expr, _msg) NS_IMETHODIMP nsBlockFrame::VerifyTree() const { // XXX rewrite this return NS_OK; } #ifdef DO_SELECTION nsIFrame * nsBlockFrame::FindHitFrame(nsBlockFrame * aBlockFrame, const nscoord aX, const nscoord aY, const nsPoint & aPoint) { nsPoint mousePoint(aPoint.x-aX, aPoint.y-aY); nsIFrame * contentFrame = nsnull; LineData * line = aBlockFrame->mLines; if (nsnull != line) { // First find the line that contains the aIndex while (nsnull != line && contentFrame == nsnull) { nsIFrame* frame = line->mFirstChild; PRInt32 n = line->mChildCount; while (--n >= 0) { nsRect bounds; frame->GetRect(bounds); if (bounds.Contains(mousePoint)) { nsBlockFrame * blockFrame; if (NS_OK == frame->QueryInterface(kBlockFrameCID, (void**)&blockFrame)) { frame = FindHitFrame(blockFrame, bounds.x, bounds.y, aPoint); //NS_RELEASE(blockFrame); return frame; } else { return frame; } } frame->GetNextSibling(frame); } line = line->mNext; } } return aBlockFrame; } NS_IMETHODIMP nsBlockFrame::HandleEvent(nsIPresContext& aPresContext, nsGUIEvent* aEvent, nsEventStatus& aEventStatus) { if (0) { nsHTMLContainerFrame::HandleEvent(aPresContext, aEvent, aEventStatus); } //return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); aEventStatus = nsEventStatus_eIgnore; //if (nsnull != mContent && (aEvent->message != NS_MOUSE_LEFT_BUTTON_UP || // (aEvent->message == NS_MOUSE_LEFT_BUTTON_UP && !mDoingSelection))) { if (nsnull != mContent) { mContent->HandleDOMEvent(aPresContext, (nsEvent*)aEvent, nsnull, DOM_EVENT_INIT, aEventStatus); } if (DisplaySelection(aPresContext) == PR_FALSE) { if (aEvent->message != NS_MOUSE_LEFT_BUTTON_DOWN) { return NS_OK; } } if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN) { int x = 0; } //nsRect bounds; //GetRect(bounds); //nsIFrame * contentFrame = FindHitFrame(this, bounds.x, bounds.y, aEvent->point); nsIFrame * contentFrame = FindHitFrame(this, 0,0, aEvent->point); if (contentFrame == nsnull) { return NS_OK; } if(nsEventStatus_eConsumeNoDefault != aEventStatus) { if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN) { } else if (aEvent->message == NS_MOUSE_MOVE && mDoingSelection || aEvent->message == NS_MOUSE_LEFT_BUTTON_UP) { // no-op } else { return NS_OK; } if (aEvent->message == NS_MOUSE_LEFT_BUTTON_UP) { if (mDoingSelection) { contentFrame->HandleRelease(aPresContext, aEvent, aEventStatus); } } else if (aEvent->message == NS_MOUSE_MOVE) { mDidDrag = PR_TRUE; contentFrame->HandleDrag(aPresContext, aEvent, aEventStatus); } else if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN) { contentFrame->HandlePress(aPresContext, aEvent, aEventStatus); } } return NS_OK; } nsIFrame * gNearByFrame = nsnull; NS_METHOD nsBlockFrame::HandleDrag(nsIPresContext& aPresContext, nsGUIEvent* aEvent, nsEventStatus& aEventStatus) { if (DisplaySelection(aPresContext) == PR_FALSE) { aEventStatus = nsEventStatus_eIgnore; return NS_OK; } // Keep old start and end //nsIContent * startContent = mSelectionRange->GetStartContent(); // ref counted //nsIContent * endContent = mSelectionRange->GetEndContent(); // ref counted mDidDrag = PR_TRUE; nsIFrame * contentFrame = nsnull; LineData* line = mLines; if (nsnull != line) { // First find the line that contains the aIndex while (nsnull != line && contentFrame == nsnull) { nsIFrame* frame = line->mFirstChild; PRInt32 n = line->mChildCount; while (--n >= 0) { nsRect bounds; frame->GetRect(bounds); if (aEvent->point.y >= bounds.y && aEvent->point.y < bounds.y+bounds.height) { contentFrame = frame; if (frame != gNearByFrame) { if (gNearByFrame != nsnull) { int x = 0; } aEvent->point.x = bounds.x+bounds.width-50; gNearByFrame = frame; return contentFrame->HandleDrag(aPresContext, aEvent, aEventStatus); } else { return NS_OK; } //break; } frame->GetNextSibling(frame); } line = line->mNext; } } return NS_OK; } #endif