/* -*- 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.1 (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. * * Contributor(s): * Steve Clark * Robert O'Callahan */ #include "nsCOMPtr.h" #include "nsBlockFrame.h" #include "nsBlockReflowContext.h" #include "nsBlockBandData.h" #include "nsBulletFrame.h" #include "nsLineBox.h" #include "nsInlineFrame.h" #include "nsLineLayout.h" #include "nsPlaceholderFrame.h" #include "nsStyleConsts.h" #include "nsHTMLIIDs.h" #include "nsCSSRendering.h" #include "nsIFrameManager.h" #include "nsIPresContext.h" #include "nsIPresShell.h" #include "nsIReflowCommand.h" #include "nsISpaceManager.h" #include "nsIStyleContext.h" #include "nsIView.h" #include "nsIFontMetrics.h" #include "nsHTMLParts.h" #include "nsHTMLAtoms.h" #include "nsHTMLValue.h" #include "nsDOMEvent.h" #include "nsIHTMLContent.h" #include "prprf.h" #include "nsLayoutAtoms.h" #include "nsITextContent.h" #include "nsStyleChangeList.h" #include "nsISizeOfHandler.h" #include "nsIFocusTracker.h" #include "nsIFrameSelection.h" #include "nsSpaceManager.h" #include "prenv.h" #include "plstr.h" #ifdef DEBUG static PRBool gLamePaintMetrics; static PRBool gLameReflowMetrics; static PRBool gNoisy; static PRBool gNoisyDamageRepair; static PRBool gNoisyMaxElementSize; static PRBool gNoisyReflow; static PRBool gReallyNoisyReflow; static PRBool gNoisySpaceManager; static PRBool gVerifyLines; static PRBool gDisableResizeOpt; struct BlockDebugFlags { const char* name; PRBool* on; }; static BlockDebugFlags gFlags[] = { { "reflow", &gNoisyReflow }, { "really-noisy-reflow", &gReallyNoisyReflow }, { "max-element-size", &gNoisyMaxElementSize }, { "space-manager", &gNoisySpaceManager }, { "verify-lines", &gVerifyLines }, { "damage-repair", &gNoisyDamageRepair }, { "lame-paint-metrics", &gLamePaintMetrics }, { "lame-reflow-metrics", &gLameReflowMetrics }, { "disable-resize-opt", &gDisableResizeOpt }, }; #define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0])) static void ShowDebugFlags() { printf("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n"); BlockDebugFlags* bdf = gFlags; BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS; for (; bdf < end; bdf++) { printf(" %s\n", bdf->name); } printf("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma seperated list of flag\n"); printf("names (no whitespace)\n"); } static void InitDebugFlags() { static PRBool firstTime = PR_TRUE; if (firstTime) { firstTime = PR_FALSE; char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS"); if (flags) { PRBool error = PR_FALSE; for (;;) { char* cm = PL_strchr(flags, ','); if (cm) *cm = '\0'; PRBool found = PR_FALSE; BlockDebugFlags* bdf = gFlags; BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS; for (; bdf < end; bdf++) { if (PL_strcasecmp(bdf->name, flags) == 0) { *(bdf->on) = PR_TRUE; printf("nsBlockFrame: setting %s debug flag on\n", bdf->name); gNoisy = PR_TRUE; found = PR_TRUE; break; } } if (!found) { error = PR_TRUE; } if (!cm) break; *cm = ','; flags = cm + 1; } if (error) { ShowDebugFlags(); } } } } #undef NOISY_FIRST_LINE // enables debug output for first-line specific layout #undef REALLY_NOISY_FIRST_LINE // enables extra debug output for first-line specific layout #undef NOISY_FIRST_LETTER // enables debug output for first-letter specific layout #undef NOISY_MAX_ELEMENT_SIZE // enables debug output for max element size computation #undef NOISY_MAXIMUM_WIDTH // enables debug output for max width computation #undef NOISY_KIDXMOST // enables debug output for aState.mKidXMost computation #undef NOISY_FLOATER // enables debug output for floater reflow (the in/out metrics for the floated block) #undef NOISY_FLOATER_CLEARING #undef NOISY_FINAL_SIZE // enables debug output for desired width/height computation, once all children have been reflowed #undef NOISY_REMOVE_FRAME #undef NOISY_COMBINED_AREA // enables debug output for combined area computation #undef NOISY_VERTICAL_MARGINS #undef NOISY_REFLOW_REASON // gives a little info about why each reflow was requested #undef REFLOW_STATUS_COVERAGE // I think this is most useful for printing, to see which frames return "incomplete" #undef NOISY_SPACEMANAGER // enables debug output for space manager use, useful for analysing reflow of floaters and positioned elements #undef NOISY_BLOCK_INVALIDATE // enables debug output for all calls to invalidate #endif #define FIX_BUG_38157 #define FIX_BUG_37657 // add in a sanity check for absurdly deep frame trees. See bug 42138 // can't just use IsFrameTreeTooDeep() because that method has side effects we don't want #define MAX_DEPTH_FOR_LIST_RENUMBERING 200 // 200 open displayable tags is pretty unrealistic //---------------------------------------------------------------------- // Debugging support code #ifdef DEBUG static PRInt32 gNoiseIndent; static const char* kReflowCommandType[] = { "ContentChanged", "StyleChanged", "PullupReflow", "PushReflow", "CheckPullupReflow", "ReflowDirty", "UserDefined", }; #endif #ifdef REALLY_NOISY_FIRST_LINE static void DumpStyleGeneaology(nsIFrame* aFrame, const char* gap) { fputs(gap, stdout); nsFrame::ListTag(stdout, aFrame); printf(": "); nsIStyleContext* sc; aFrame->GetStyleContext(&sc); while (nsnull != sc) { nsIStyleContext* psc; printf("%p ", sc); psc = sc->GetParent(); NS_RELEASE(sc); sc = psc; } printf("\n"); } #endif #ifdef REFLOW_STATUS_COVERAGE static void RecordReflowStatus(PRBool aChildIsBlock, nsReflowStatus aFrameReflowStatus) { static PRUint32 record[2]; // 0: child-is-block // 1: child-is-inline PRIntn index = 0; if (!aChildIsBlock) index |= 1; // Compute new status PRUint32 newS = record[index]; if (NS_INLINE_IS_BREAK(aFrameReflowStatus)) { if (NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus)) { newS |= 1; } else if (NS_FRAME_IS_NOT_COMPLETE(aFrameReflowStatus)) { newS |= 2; } else { newS |= 4; } } else if (NS_FRAME_IS_NOT_COMPLETE(aFrameReflowStatus)) { newS |= 8; } else { newS |= 16; } // Log updates to the status that yield different values if (record[index] != newS) { record[index] = newS; printf("record(%d): %02x %02x\n", index, record[0], record[1]); } } #endif //---------------------------------------------------------------------- inline void CombineRects(const nsRect& r1, nsRect& r2) { nscoord xa = r2.x; nscoord ya = r2.y; nscoord xb = xa + r2.width; nscoord yb = ya + r2.height; nscoord x = r1.x; nscoord y = r1.y; nscoord xmost = x + r1.width; nscoord ymost = y + r1.height; if (x < xa) { xa = x; } if (xmost > xb) { xb = xmost; } if (y < ya) { ya = y; } if (ymost > yb) { yb = ymost; } r2.x = xa; r2.y = ya; r2.width = xb - xa; r2.height = yb - ya; } //---------------------------------------------------------------------- class nsBlockReflowState { public: nsBlockReflowState(const nsHTMLReflowState& aReflowState, nsIPresContext* aPresContext, nsBlockFrame* aFrame, const nsHTMLReflowMetrics& aMetrics, PRBool aBlockMarginRoot); ~nsBlockReflowState(); /** * Update our state when aLine is skipped over during incremental * reflow. */ void RecoverStateFrom(nsLineBox* aLine, PRBool aPrevLineWasClean); /** * Get the available reflow space for the current y coordinate. The * available space is relative to our coordinate system (0,0) is our * upper left corner. */ void GetAvailableSpace() { #ifdef DEBUG // Verify that the caller setup the coordinate system properly nscoord wx, wy; mSpaceManager->GetTranslation(wx, wy); NS_ASSERTION((wx == mSpaceManagerX) && (wy == mSpaceManagerY), "bad coord system"); #endif mBand.GetAvailableSpace(mY - BorderPadding().top, mAvailSpaceRect); #ifdef DEBUG if (gNoisyReflow) { nsFrame::IndentBy(stdout, gNoiseIndent); printf("GetAvailableSpace: band=%d,%d,%d,%d count=%d\n", mAvailSpaceRect.x, mAvailSpaceRect.y, mAvailSpaceRect.width, mAvailSpaceRect.height, mBand.GetTrapezoidCount()); } #endif } void GetAvailableSpace(nscoord aY) { #ifdef DEBUG // Verify that the caller setup the coordinate system properly nscoord wx, wy; mSpaceManager->GetTranslation(wx, wy); NS_ASSERTION((wx == mSpaceManagerX) && (wy == mSpaceManagerY), "bad coord system"); #endif mBand.GetAvailableSpace(aY - BorderPadding().top, mAvailSpaceRect); #ifdef DEBUG if (gNoisyReflow) { nsFrame::IndentBy(stdout, gNoiseIndent); printf("GetAvailableSpace: band=%d,%d,%d,%d count=%d\n", mAvailSpaceRect.x, mAvailSpaceRect.y, mAvailSpaceRect.width, mAvailSpaceRect.height, mBand.GetTrapezoidCount()); } #endif } void InitFloater(nsLineLayout& aLineLayout, nsPlaceholderFrame* aPlaceholderFrame); void AddFloater(nsLineLayout& aLineLayout, nsPlaceholderFrame* aPlaceholderFrame, PRBool aInitialReflow); PRBool CanPlaceFloater(const nsRect& aFloaterRect, PRUint8 aFloats); void PlaceFloater(nsFloaterCache* aFloaterCache, PRBool* aIsLeftFloater); void PlaceBelowCurrentLineFloaters(nsFloaterCacheList& aFloaters); void ClearFloaters(nscoord aY, PRUint8 aBreakType); PRBool ClearPastFloaters(PRUint8 aBreakType); PRBool IsLeftMostChild(nsIPresContext* aPresContext, nsIFrame* aFrame); PRBool IsAdjacentWithTop() const { return mY == mReflowState.mComputedBorderPadding.top; } const nsMargin& BorderPadding() const { return mReflowState.mComputedBorderPadding; } const nsMargin& Margin() const { return mReflowState.mComputedMargin; } void UpdateMaxElementSize(const nsSize& aMaxElementSize) { #ifdef NOISY_MAX_ELEMENT_SIZE nsSize oldSize = mMaxElementSize; #endif if (aMaxElementSize.width > mMaxElementSize.width) { mMaxElementSize.width = aMaxElementSize.width; } if (aMaxElementSize.height > mMaxElementSize.height) { mMaxElementSize.height = aMaxElementSize.height; } #ifdef NOISY_MAX_ELEMENT_SIZE if ((mMaxElementSize.width != oldSize.width) || (mMaxElementSize.height != oldSize.height)) { nsFrame::IndentBy(stdout, mBlock->GetDepth()); if (NS_UNCONSTRAINEDSIZE == mReflowState.availableWidth) { printf("PASS1 "); } nsFrame::ListTag(stdout, mBlock); printf(": old max-element-size=%d,%d new=%d,%d\n", oldSize.width, oldSize.height, mMaxElementSize.width, mMaxElementSize.height); } #endif } void UpdateMaximumWidth(nscoord aMaximumWidth) { if (aMaximumWidth > mMaximumWidth) { #ifdef NOISY_MAXIMUM_WIDTH printf("nsBlockReflowState::UpdateMaximumWidth block %p caching max width %d\n", mBlock, aMaximumWidth); #endif mMaximumWidth = aMaximumWidth; } } void RecoverVerticalMargins(nsLineBox* aLine, PRBool aApplyTopMargin, nscoord* aTopMarginResult, nscoord* aBottomMarginResult); void ComputeBlockAvailSpace(nsIFrame* aFrame, nsSplittableType aSplitType, const nsStyleDisplay* aDisplay, nsRect& aResult); void RecoverStateFrom(nsLineBox* aLine, PRBool aApplyTopMargin, nscoord aDeltaY, nsRect* aDamageRect); void AdvanceToNextLine() { mLineNumber++; } PRBool IsImpactedByFloater() { #ifdef REALLY_NOISY_REFLOW printf("nsBlockReflowState::IsImpactedByFloater %p returned %d\n", this, mBand.GetFloaterCount()); #endif return mBand.GetFloaterCount(); } nsLineBox* NewLineBox(nsIFrame* aFrame, PRInt32 aCount, PRBool aIsBlock); void FreeLineBox(nsLineBox* aLine); void StoreMaxElementSize(nsIFrame* aFloater, const nsSize& aSize) { mBand.StoreMaxElementSize(mPresContext, aFloater, aSize); } //---------------------------------------- // This state is the "global" state computed once for the reflow of // the block. // The block frame that is using this object nsBlockFrame* mBlock; nsIPresContext* mPresContext; const nsHTMLReflowState& mReflowState; nsISpaceManager* mSpaceManager; // The coordinates within the spacemanager where the block is being // placed after taking into account the blocks border and // padding. This, therefore, represents the inner "content area" (in // spacemanager coordinates) where child frames will be placed, // including child blocks and floaters. nscoord mSpaceManagerX, mSpaceManagerY; // XXX get rid of this nsReflowStatus mReflowStatus; nscoord mBottomEdge; // The content area to reflow child frames within. The x/y // coordinates are known to be mBorderPadding.left and // mBorderPadding.top. The width/height may be NS_UNCONSTRAINEDSIZE // if the container reflowing this frame has given the frame an // unconstrained area. nsSize mContentArea; //---------------------------------------- // This state is "running" state updated by the reflow of each line // in the block. This same state is "recovered" when a line is not // dirty and is passed over during incremental reflow. // The current line being reflowed nsLineBox* mCurrentLine; // The previous line just reflowed nsLineBox* mPrevLine; // The current Y coordinate in the block nscoord mY; // The available space within the current band. nsRect mAvailSpaceRect; // The maximum x-most of each line nscoord mKidXMost; // The combined area of all floaters placed so far nsRect mFloaterCombinedArea; // For unconstained-width reflow, we keep the right floaters // combined area stored seperately. PRBool mHaveRightFloaters; nsRect mRightFloaterCombinedArea; nsFloaterCacheFreeList mFloaterCacheFreeList; // Previous child. This is used when pulling up a frame to update // the sibling list. nsIFrame* mPrevChild; // The next immediate child frame that is the target of an // incremental reflow command. Once that child has been reflowed we // null this slot out. nsIFrame* mNextRCFrame; // The previous child frames collapsed bottom margin value. nscoord mPrevBottomMargin; // The current next-in-flow for the block. When lines are pulled // from a next-in-flow, this is used to know which next-in-flow to // pull from. When a next-in-flow is emptied of lines, we advance // this to the next next-in-flow. nsBlockFrame* mNextInFlow; // The current band data for the current Y coordinate nsBlockBandData mBand; //---------------------------------------- // Temporary line-reflow state. This state is used during the reflow // of a given line, but doesn't have meaning before or after. // The list of floaters that are "current-line" floaters. These are // added to the line after the line has been reflowed, to keep the // list fiddling from being N^2. nsFloaterCacheFreeList mCurrentLineFloaters; // The list of floaters which are "below current-line" // floaters. These are reflowed/placed after the line is reflowed // and placed. Again, this is done to keep the list fiddling from // being N^2. nsFloaterCacheFreeList mBelowCurrentLineFloaters; nsSize mMaxElementSize; nscoord mMaximumWidth; nscoord mMinLineHeight; PRInt32 mLineNumber; // block reflow state flags #define BRS_UNCONSTRAINEDWIDTH 0x00000001 #define BRS_UNCONSTRAINEDHEIGHT 0x00000002 #define BRS_SHRINKWRAPWIDTH 0x00000004 #define BRS_NEEDRESIZEREFLOW 0x00000008 #define BRS_ISINLINEINCRREFLOW 0x00000010 #define BRS_NOWRAP 0x00000020 #define BRS_ISTOPMARGINROOT 0x00000040 // Is this frame a root for top/bottom margin collapsing? #define BRS_ISBOTTOMMARGINROOT 0x00000080 #define BRS_APPLYTOPMARGIN 0x00000100 // See ShouldApplyTopMargin #define BRS_COMPUTEMAXELEMENTSIZE 0x00000200 #define BRS_COMPUTEMAXWIDTH 0x00000400 #define BRS_LASTFLAG BRS_COMPUTEMAXWIDTH PRInt16 mFlags; void SetFlag(PRUint32 aFlag, PRBool aValue) { NS_ASSERTION(aFlag<=BRS_LASTFLAG, "bad flag"); NS_ASSERTION(aValue==PR_FALSE || aValue==PR_TRUE, "bad value"); if (aValue) { // set flag mFlags |= aFlag; } else { // unset flag mFlags &= ~aFlag; } } PRBool GetFlag(PRUint32 aFlag) const { NS_ASSERTION(aFlag<=BRS_LASTFLAG, "bad flag"); PRBool result = (mFlags & aFlag); if (result) return PR_TRUE; return PR_FALSE; } }; // XXX This is vile. Make it go away void nsLineLayout::InitFloater(nsPlaceholderFrame* aFrame) { mBlockRS->InitFloater(*this, aFrame); } void nsLineLayout::AddFloater(nsPlaceholderFrame* aFrame) { mBlockRS->AddFloater(*this, aFrame, PR_FALSE); } //---------------------------------------------------------------------- nsBlockReflowState::nsBlockReflowState(const nsHTMLReflowState& aReflowState, nsIPresContext* aPresContext, nsBlockFrame* aFrame, const nsHTMLReflowMetrics& aMetrics, PRBool aBlockMarginRoot) : mBlock(aFrame), mPresContext(aPresContext), mReflowState(aReflowState), mNextRCFrame(nsnull), mPrevBottomMargin(0), mLineNumber(0), mFlags(0) { const nsMargin& borderPadding = BorderPadding(); if (aBlockMarginRoot) { SetFlag(BRS_ISTOPMARGINROOT, PR_TRUE); SetFlag(BRS_ISBOTTOMMARGINROOT, PR_TRUE); } if (0 != aReflowState.mComputedBorderPadding.top) { SetFlag(BRS_ISTOPMARGINROOT, PR_TRUE); } if (0 != aReflowState.mComputedBorderPadding.bottom) { SetFlag(BRS_ISBOTTOMMARGINROOT, PR_TRUE); } if (GetFlag(BRS_ISTOPMARGINROOT)) { SetFlag(BRS_APPLYTOPMARGIN, PR_TRUE); } mSpaceManager = aReflowState.mSpaceManager; NS_ASSERTION( nsnull != mSpaceManager, "SpaceManager should be set in nsBlockReflowState" ); if( nsnull != mSpaceManager ) { // Translate into our content area and then save the // coordinate system origin for later. mSpaceManager->Translate(borderPadding.left, borderPadding.top); mSpaceManager->GetTranslation(mSpaceManagerX, mSpaceManagerY); } mReflowStatus = NS_FRAME_COMPLETE; mPresContext = aPresContext; mBlock->GetNextInFlow((nsIFrame**)&mNextInFlow); mKidXMost = 0; // Compute content area width (the content area is inside the border // and padding) if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedWidth) { mContentArea.width = aReflowState.mComputedWidth; } else { if (NS_UNCONSTRAINEDSIZE == aReflowState.availableWidth) { mContentArea.width = NS_UNCONSTRAINEDSIZE; SetFlag(BRS_UNCONSTRAINEDWIDTH, PR_TRUE); } else if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxWidth) { // Choose a width based on the content (shrink wrap width) up // to the maximum width // Part 2 of a possible fix for 38157 #ifdef FIX_BUG_38157 const nsMargin& margin = Margin(); nscoord availContentWidth = aReflowState.availableWidth; if (NS_UNCONSTRAINEDSIZE != availContentWidth) { availContentWidth -= (borderPadding.left + borderPadding.right) + (margin.left + margin.right); } mContentArea.width = PR_MIN(aReflowState.mComputedMaxWidth, availContentWidth); #else mContentArea.width = aReflowState.mComputedMaxWidth; #endif SetFlag(BRS_SHRINKWRAPWIDTH, PR_TRUE); } else { nscoord lr = borderPadding.left + borderPadding.right; mContentArea.width = aReflowState.availableWidth - lr; } } mHaveRightFloaters = PR_FALSE; // Compute content area height. Unlike the width, if we have a // specified style height we ignore it since extra content is // managed by the "overflow" property. When we don't have a // specified style height then we may end up limiting our height if // the availableHeight is constrained (this situation occurs when we // are paginated). if (NS_UNCONSTRAINEDSIZE != aReflowState.availableHeight) { // We are in a paginated situation. The bottom edge is just inside // the bottom border and padding. The content area height doesn't // include either border or padding edge. mBottomEdge = aReflowState.availableHeight - borderPadding.bottom; mContentArea.height = mBottomEdge - borderPadding.top; } else { // When we are not in a paginated situation then we always use // an constrained height. SetFlag(BRS_UNCONSTRAINEDHEIGHT, PR_TRUE); mContentArea.height = mBottomEdge = NS_UNCONSTRAINEDSIZE; } mY = borderPadding.top; mBand.Init(mSpaceManager, mContentArea); mPrevChild = nsnull; mCurrentLine = nsnull; mPrevLine = nsnull; const nsStyleText* styleText; mBlock->GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&) styleText); switch (styleText->mWhiteSpace) { case NS_STYLE_WHITESPACE_PRE: case NS_STYLE_WHITESPACE_NOWRAP: SetFlag(BRS_NOWRAP, PR_TRUE); break; default: SetFlag(BRS_NOWRAP, PR_FALSE); break; } SetFlag(BRS_COMPUTEMAXELEMENTSIZE, (nsnull != aMetrics.maxElementSize)); #ifdef NOISY_MAX_ELEMENT_SIZE printf("BRS: setting compute-MES to %d\n", (nsnull != aMetrics.maxElementSize)); #endif mMaxElementSize.SizeTo(0, 0); SetFlag(BRS_COMPUTEMAXWIDTH, (NS_REFLOW_CALC_MAX_WIDTH == (aMetrics.mFlags & NS_REFLOW_CALC_MAX_WIDTH))); mMaximumWidth = 0; mMinLineHeight = nsHTMLReflowState::CalcLineHeight(mPresContext, aReflowState.rendContext, aReflowState.frame); } nsBlockReflowState::~nsBlockReflowState() { // Restore the coordinate system const nsMargin& borderPadding = BorderPadding(); mSpaceManager->Translate(-borderPadding.left, -borderPadding.top); } nsLineBox* nsBlockReflowState::NewLineBox(nsIFrame* aFrame, PRInt32 aCount, PRBool aIsBlock) { nsCOMPtr shell; mPresContext->GetShell(getter_AddRefs(shell)); return NS_NewLineBox(shell, aFrame, aCount, aIsBlock); } void nsBlockReflowState::FreeLineBox(nsLineBox* aLine) { if (aLine) { nsCOMPtr presShell; mPresContext->GetShell(getter_AddRefs(presShell)); aLine->Destroy(presShell); } } // Compute the amount of available space for reflowing a block frame // at the current Y coordinate. This method assumes that // GetAvailableSpace has already been called. void nsBlockReflowState::ComputeBlockAvailSpace(nsIFrame* aFrame, nsSplittableType aSplitType, const nsStyleDisplay* aDisplay, nsRect& aResult) { #ifdef REALLY_NOISY_REFLOW printf("CBAS frame=%p has floater count %d\n", aFrame, mBand.GetFloaterCount()); mBand.List(); #endif aResult.y = mY; aResult.height = GetFlag(BRS_UNCONSTRAINEDHEIGHT) ? NS_UNCONSTRAINEDSIZE : mBottomEdge - mY; const nsMargin& borderPadding = BorderPadding(); if (NS_FRAME_SPLITTABLE_NON_RECTANGULAR == aSplitType) { if (mBand.GetFloaterCount()) { // Use the float-edge property to determine how the child block // will interact with the floater. const nsStyleSpacing* spacing; aFrame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&) spacing); switch (spacing->mFloatEdge) { default: case NS_STYLE_FLOAT_EDGE_CONTENT: // content and only content does runaround of floaters // The child block will flow around the floater. Therefore // give it all of the available space. aResult.x = borderPadding.left; aResult.width = GetFlag(BRS_UNCONSTRAINEDWIDTH) ? NS_UNCONSTRAINEDSIZE : mContentArea.width; break; case NS_STYLE_FLOAT_EDGE_BORDER: case NS_STYLE_FLOAT_EDGE_PADDING: { // The child block's border should be placed adjacent to, // but not overlap the floater(s). nsMargin m(0, 0, 0, 0); spacing->GetMargin(m); // XXX percentage margins if (NS_STYLE_FLOAT_EDGE_PADDING == spacing->mFloatEdge) { // Add in border too nsMargin b; spacing->GetBorder(b); m += b; } // determine left edge if (mBand.GetLeftFloaterCount()) { aResult.x = mAvailSpaceRect.x + borderPadding.left - m.left; } else { aResult.x = borderPadding.left; } // determine width if (GetFlag(BRS_UNCONSTRAINEDWIDTH)) { aResult.width = NS_UNCONSTRAINEDSIZE; } else { if (mBand.GetRightFloaterCount()) { if (mBand.GetLeftFloaterCount()) { aResult.width = mAvailSpaceRect.width + m.left + m.right; } else { aResult.width = mAvailSpaceRect.width + m.right; } } else { aResult.width = mAvailSpaceRect.width + m.left; } } } break; case NS_STYLE_FLOAT_EDGE_MARGIN: { // The child block's margins should be placed adjacent to, // but not overlap the floater. aResult.x = mAvailSpaceRect.x + borderPadding.left; aResult.width = mAvailSpaceRect.width; } break; } } else { // Since there are no floaters present the float-edge property // doesn't matter therefore give the block element all of the // available space since it will flow around the floater itself. aResult.x = borderPadding.left; aResult.width = GetFlag(BRS_UNCONSTRAINEDWIDTH) ? NS_UNCONSTRAINEDSIZE : mContentArea.width; } } else { // The frame is clueless about the space manager and therefore we // only give it free space. An example is a table frame - the // tables do not flow around floaters. aResult.x = mAvailSpaceRect.x + borderPadding.left; aResult.width = mAvailSpaceRect.width; } #ifdef REALLY_NOISY_REFLOW printf(" CBAS: result %d %d %d %d\n", aResult.x, aResult.y, aResult.width, aResult.height); #endif } PRBool nsBlockReflowState::ClearPastFloaters(PRUint8 aBreakType) { nscoord saveY, deltaY; PRBool applyTopMargin = PR_FALSE; switch (aBreakType) { case NS_STYLE_CLEAR_LEFT: case NS_STYLE_CLEAR_RIGHT: case NS_STYLE_CLEAR_LEFT_AND_RIGHT: // Apply the previous margin before clearing saveY = mY + mPrevBottomMargin; ClearFloaters(saveY, aBreakType); #ifdef NOISY_FLOATER_CLEARING nsFrame::ListTag(stdout, mBlock); printf(": ClearPastFloaters: mPrevBottomMargin=%d saveY=%d oldY=%d newY=%d deltaY=%d\n", mPrevBottomMargin, saveY, saveY - mPrevBottomMargin, mY, mY - saveY); #endif // Determine how far we just moved. If we didn't move then there // was nothing to clear to don't mess with the normal margin // collapsing behavior. In either case we need to restore the Y // coordinate to what it was before the clear. deltaY = mY - saveY; if (0 != deltaY) { // Pretend that the distance we just moved is a previous // blocks bottom margin. Note that GetAvailableSpace has been // done so that the available space calculations will be done // after clearing the appropriate floaters. // // What we are doing here is applying CSS2 section 9.5.2's // rules for clearing - "The top margin of the generated box // is increased enough that the top border edge is below the // bottom outer edge of the floating boxes..." // // What this will do is cause the top-margin of the block // frame we are about to reflow to be collapsed with that // distance. mPrevBottomMargin = deltaY; mY = saveY; // Force margin to be applied in this circumstance applyTopMargin = PR_TRUE; } else { // Put aState.mY back to its original value since no clearing // happened. That way the previous blocks bottom margin will // be applied properly. mY = saveY - mPrevBottomMargin; } break; } return applyTopMargin; } // Recover the collapsed vertical margin values for aLine. Note that // the values are not collapsed with aState.mPrevBottomMargin, nor are // they collapsed with each other when the line height is zero. void nsBlockReflowState::RecoverVerticalMargins(nsLineBox* aLine, PRBool aApplyTopMargin, nscoord* aTopMarginResult, nscoord* aBottomMarginResult) { if (aLine->IsBlock()) { // Update band data GetAvailableSpace(); // Setup reflow state to compute the block childs top and bottom // margins nsIFrame* frame = aLine->mFirstChild; nsRect availSpaceRect; const nsStyleDisplay* display; frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display); nsSplittableType splitType = NS_FRAME_NOT_SPLITTABLE; frame->IsSplittable(splitType); ComputeBlockAvailSpace(frame, splitType, display, availSpaceRect); nsSize availSpace(availSpaceRect.width, availSpaceRect.height); nsHTMLReflowState reflowState(mPresContext, mReflowState, frame, availSpace); // Compute collapsed top margin nscoord topMargin = 0; if (aApplyTopMargin) { topMargin = nsBlockReflowContext::ComputeCollapsedTopMargin(mPresContext, reflowState); } // Compute collapsed bottom margin nscoord bottomMargin = reflowState.mComputedMargin.bottom; bottomMargin = nsBlockReflowContext::MaxMargin(bottomMargin, aLine->GetCarriedOutBottomMargin()); *aTopMarginResult = topMargin; *aBottomMarginResult = bottomMargin; } else { // XXX_ib *aTopMarginResult = 0; *aBottomMarginResult = 0; } } void nsBlockReflowState::RecoverStateFrom(nsLineBox* aLine, PRBool aApplyTopMargin, nscoord aDeltaY, nsRect* aDamageRect) { // Make the line being recovered the current line mCurrentLine = aLine; // Update aState.mPrevChild as if we had reflowed all of the frames // in this line. mPrevChild = aLine->LastChild(); // Recover mKidXMost and mMaxElementSize nscoord xmost = aLine->mBounds.XMost(); if (xmost > mKidXMost) { #ifdef DEBUG if (CRAZY_WIDTH(xmost)) { nsFrame::ListTag(stdout, mBlock); printf(": WARNING: xmost:%d\n", xmost); } #endif #ifdef NOISY_KIDXMOST printf("%p RecoverState block %p aState.mKidXMost=%d\n", this, mBlock, xmost); #endif mKidXMost = xmost; } if (GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) { #ifdef NOISY_MAX_ELEMENT_SIZE printf("nsBlockReflowState::RecoverStateFrom block %p caching max width %d\n", mBlock, aLine->mMaxElementWidth); #endif UpdateMaxElementSize(nsSize(aLine->mMaxElementWidth, aLine->mBounds.height)); } // If computing the maximum width, then update mMaximumWidth if (GetFlag(BRS_COMPUTEMAXWIDTH)) { #ifdef NOISY_MAXIMUM_WIDTH printf("nsBlockReflowState::RecoverStateFrom block %p caching max width %d\n", mBlock, aLine->mMaximumWidth); #endif UpdateMaximumWidth(aLine->mMaximumWidth); } // The line may have clear before semantics. if (aLine->IsBlock() && aLine->HasBreak()) { // Clear past floaters before the block if the clear style is not none aApplyTopMargin = ClearPastFloaters(aLine->GetBreakType()); #ifdef NOISY_VERTICAL_MARGINS nsFrame::ListTag(stdout, mBlock); printf(": RecoverStateFrom: y=%d child ", mY); nsFrame::ListTag(stdout, aLine->mFirstChild); printf(" has clear of %d => %s, mPrevBottomMargin=%d\n", aLine->mBreakType, aApplyTopMargin ? "applyTopMargin" : "nope", mPrevBottomMargin); #endif } // Recover mPrevBottomMargin and calculate the line's new Y // coordinate (newLineY) nscoord newLineY = mY; nsRect lineCombinedArea; aLine->GetCombinedArea(&lineCombinedArea); if (aLine->IsBlock()) { if ((0 == aLine->mBounds.height) && (0 == lineCombinedArea.height)) { // The line's top and bottom margin values need to be collapsed // with the mPrevBottomMargin to determine a new // mPrevBottomMargin value. nscoord topMargin, bottomMargin; RecoverVerticalMargins(aLine, aApplyTopMargin, &topMargin, &bottomMargin); nscoord m = nsBlockReflowContext::MaxMargin(bottomMargin, mPrevBottomMargin); m = nsBlockReflowContext::MaxMargin(m, topMargin); mPrevBottomMargin = m; } else { // Recover the top and bottom margins for this line nscoord topMargin, bottomMargin; RecoverVerticalMargins(aLine, aApplyTopMargin, &topMargin, &bottomMargin); // Compute the collapsed top margin value nscoord collapsedTopMargin = nsBlockReflowContext::MaxMargin(topMargin, mPrevBottomMargin); // The lineY is just below the collapsed top margin value. The // mPrevBottomMargin gets set to the bottom margin value for the // line. newLineY += collapsedTopMargin; mPrevBottomMargin = bottomMargin; } } else if (0 == aLine->GetHeight()) { // For empty inline lines we leave the previous bottom margin // alone so that it's collpased with the next line. } else { // For non-empty inline lines the previous margin is applied // before the line. Therefore apply it now and zero it out. newLineY += mPrevBottomMargin; mPrevBottomMargin = 0; } // Save away the old combined area for later nsRect oldCombinedArea = lineCombinedArea; // Slide the frames in the line by the computed delta. This also // updates the lines Y coordinate and the combined area's Y // coordinate. nscoord finalDeltaY = newLineY - aLine->mBounds.y; mBlock->SlideLine(*this, aLine, finalDeltaY); #ifdef MOZ_MATHML // aLine has been slided, but... // XXX it is not necessary to worry about the ascent of mBlock here, right? // Indeed, depending on the status of the first line of mBlock, we can either have: // case first line of mBlock is dirty : it will be reflowed by mBlock and so // mBlock->mAscent will be recomputed by the block frame, and we will // never enter into this RecoverStateFrom(aLine) function. // case first line of mBlock is clean : it is untouched by the incremental reflow. // In other words, aLine is never equals to mBlock->mLines in this function. // so mBlock->mAscent will remain unchanged. #endif // Place floaters for this line into the space manager if (aLine->HasFloaters()) { // Undo border/padding translation since the nsFloaterCache's // coordinates are relative to the frame not relative to the // border/padding. const nsMargin& bp = BorderPadding(); mSpaceManager->Translate(-bp.left, -bp.top); // Place the floaters into the space-manager again. Also slide // them, just like the regular frames on the line. nsRect r; nsFloaterCache* fc = aLine->GetFirstFloater(); while (fc) { fc->mRegion.y += finalDeltaY; fc->mCombinedArea.y += finalDeltaY; nsIFrame* floater = fc->mPlaceholder->GetOutOfFlowFrame(); floater->GetRect(r); floater->MoveTo(mPresContext, r.x, r.y + finalDeltaY); #ifdef DEBUG if (gNoisyReflow || gNoisySpaceManager) { nscoord tx, ty; mSpaceManager->GetTranslation(tx, ty); nsFrame::IndentBy(stdout, gNoiseIndent); printf("RecoverState: txy=%d,%d (%d,%d) ", tx, ty, mSpaceManagerX, mSpaceManagerY); nsFrame::ListTag(stdout, floater); printf(" r.y=%d finalDeltaY=%d (sum=%d) region={%d,%d,%d,%d}\n", r.y, finalDeltaY, r.y + finalDeltaY, fc->mRegion.x, fc->mRegion.y, fc->mRegion.width, fc->mRegion.height); } #endif mSpaceManager->AddRectRegion(floater, fc->mRegion); fc = fc->Next(); } #ifdef DEBUG if (gNoisyReflow || gNoisySpaceManager) { mSpaceManager->List(stdout); } #endif // And then put the translation back again mSpaceManager->Translate(bp.left, bp.top); } // Recover mY mY = aLine->mBounds.YMost(); // Compute the damage area if (aDamageRect) { if (0 == finalDeltaY) { aDamageRect->Empty(); } else { aLine->GetCombinedArea(&lineCombinedArea); aDamageRect->UnionRect(oldCombinedArea, lineCombinedArea); } } // It's possible that the line has clear after semantics if (!aLine->IsBlock() && aLine->HasBreak()) { PRUint8 breakType = aLine->GetBreakType(); switch (breakType) { case NS_STYLE_CLEAR_LEFT: case NS_STYLE_CLEAR_RIGHT: case NS_STYLE_CLEAR_LEFT_AND_RIGHT: ClearFloaters(mY, breakType); break; } } } //---------------------------------------------------------------------- const nsIID kBlockFrameCID = NS_BLOCK_FRAME_CID; nsresult NS_NewBlockFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame, PRUint32 aFlags) { NS_PRECONDITION(aNewFrame, "null OUT ptr"); if (nsnull == aNewFrame) { return NS_ERROR_NULL_POINTER; } nsBlockFrame* it = new (aPresShell) nsBlockFrame; if (nsnull == it) { return NS_ERROR_OUT_OF_MEMORY; } it->SetFlags(aFlags); *aNewFrame = it; return NS_OK; } nsBlockFrame::nsBlockFrame() { #ifdef DEBUG InitDebugFlags(); #endif } nsBlockFrame::~nsBlockFrame() { } NS_IMETHODIMP nsBlockFrame::Destroy(nsIPresContext* aPresContext) { mAbsoluteContainer.DestroyFrames(this, aPresContext); // Outside bullets are not in our child-list so check for them here // and delete them when present. if (HaveOutsideBullet()) { mBullet->Destroy(aPresContext); mBullet = nsnull; } mFloaters.DestroyFrames(aPresContext); nsLineBox::DeleteLineList(aPresContext, mLines); return nsBlockFrameSuper::Destroy(aPresContext); } NS_IMETHODIMP nsBlockFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr) { if (NULL == aInstancePtr) { return NS_ERROR_NULL_POINTER; } if (aIID.Equals(kBlockFrameCID)) { nsBlockFrame* tmp = this; *aInstancePtr = (void*) tmp; return NS_OK; } if ( aIID.Equals(NS_GET_IID(nsILineIterator)) || aIID.Equals(NS_GET_IID(nsILineIteratorNavigator)) ) { nsLineIterator* it = new nsLineIterator; if (!it) { *aInstancePtr = nsnull; return NS_ERROR_OUT_OF_MEMORY; } const nsStyleDisplay* display; GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display); nsresult rv = it->Init(mLines, display->mDirection == NS_STYLE_DIRECTION_RTL); if (NS_FAILED(rv)) { delete it; return rv; } NS_ADDREF((nsILineIterator *) (*aInstancePtr = (void *) it)); return NS_OK; } return nsBlockFrameSuper::QueryInterface(aIID, aInstancePtr); } NS_IMETHODIMP nsBlockFrame::IsSplittable(nsSplittableType& aIsSplittable) const { aIsSplittable = NS_FRAME_SPLITTABLE_NON_RECTANGULAR; return NS_OK; } #ifdef DEBUG NS_METHOD nsBlockFrame::List(nsIPresContext* aPresContext, FILE* out, PRInt32 aIndent) const { IndentBy(out, aIndent); ListTag(out); #ifdef DEBUG_waterson fprintf(out, " [parent=%p]", mParent); #endif nsIView* view; GetView(aPresContext, &view); if (nsnull != view) { fprintf(out, " [view=%p]", view); } if (nsnull != mNextSibling) { fprintf(out, " next=%p", mNextSibling); } // Output the flow linkage if (nsnull != mPrevInFlow) { fprintf(out, " prev-in-flow=%p", mPrevInFlow); } if (nsnull != mNextInFlow) { fprintf(out, " next-in-flow=%p", mNextInFlow); } // Output the rect and state fprintf(out, " {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, mRect.height); if (0 != mState) { fprintf(out, " [state=%08x]", mState); } PRInt32 numInlineLines = 0; PRInt32 numBlockLines = 0; if (nsnull != mLines) { nsLineBox* line = mLines; while (nsnull != line) { if (line->IsBlock()) { numBlockLines++; } else { numInlineLines++; } line = line->mNext; } } fprintf(out, " sc=%p(i=%d,b=%d)<\n", mStyleContext, numInlineLines, numBlockLines); aIndent++; // Output the lines if (nsnull != mLines) { nsLineBox* line = mLines; while (nsnull != line) { line->List(aPresContext, out, aIndent); line = line->mNext; } } nsIAtom* listName = nsnull; PRInt32 listIndex = 0; for (;;) { nsIFrame* kid; GetAdditionalChildListName(listIndex++, &listName); if (nsnull == listName) { break; } FirstChild(aPresContext, listName, &kid); if (nsnull != kid) { IndentBy(out, aIndent); nsAutoString tmp; if (nsnull != listName) { listName->ToString(tmp); fputs(tmp, out); } fputs("<\n", out); while (nsnull != kid) { nsIFrameDebug* frameDebug; if (NS_SUCCEEDED(kid->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**)&frameDebug))) { frameDebug->List(aPresContext, out, aIndent + 1); } kid->GetNextSibling(&kid); } IndentBy(out, aIndent); fputs(">\n", out); } NS_IF_RELEASE(listName); } aIndent--; IndentBy(out, aIndent); fputs(">\n", out); return NS_OK; } NS_IMETHODIMP nsBlockFrame::GetFrameName(nsString& aResult) const { return MakeFrameName("Block", aResult); } #endif NS_IMETHODIMP nsBlockFrame::GetFrameType(nsIAtom** aType) const { NS_PRECONDITION(nsnull != aType, "null OUT parameter pointer"); *aType = nsLayoutAtoms::blockFrame; NS_ADDREF(*aType); return NS_OK; } ///////////////////////////////////////////////////////////////////////////// // Child frame enumeration NS_IMETHODIMP nsBlockFrame::FirstChild(nsIPresContext* aPresContext, nsIAtom* aListName, nsIFrame** aFirstChild) const { NS_PRECONDITION(nsnull != aFirstChild, "null OUT parameter pointer"); if (aListName == nsLayoutAtoms::absoluteList) { return mAbsoluteContainer.FirstChild(this, aListName, aFirstChild); } else if (nsnull == aListName) { *aFirstChild = (nsnull != mLines) ? mLines->mFirstChild : nsnull; return NS_OK; } else if (aListName == nsLayoutAtoms::overflowList) { nsLineBox* overflowLines = GetOverflowLines(aPresContext, PR_FALSE); *aFirstChild = overflowLines ? overflowLines->mFirstChild : nsnull; return NS_OK; } else if (aListName == nsLayoutAtoms::floaterList) { *aFirstChild = mFloaters.FirstChild(); return NS_OK; } else if (aListName == nsLayoutAtoms::bulletList) { if (HaveOutsideBullet()) { *aFirstChild = mBullet; } else { *aFirstChild = nsnull; } return NS_OK; } *aFirstChild = nsnull; return NS_ERROR_INVALID_ARG; } NS_IMETHODIMP nsBlockFrame::GetAdditionalChildListName(PRInt32 aIndex, nsIAtom** aListName) const { NS_PRECONDITION(nsnull != aListName, "null OUT parameter pointer"); if (aIndex < 0) { return NS_ERROR_INVALID_ARG; } *aListName = nsnull; switch (aIndex) { case NS_BLOCK_FRAME_FLOATER_LIST_INDEX: *aListName = nsLayoutAtoms::floaterList; NS_ADDREF(*aListName); break; case NS_BLOCK_FRAME_BULLET_LIST_INDEX: *aListName = nsLayoutAtoms::bulletList; NS_ADDREF(*aListName); break; case NS_BLOCK_FRAME_ABSOLUTE_LIST_INDEX: *aListName = nsLayoutAtoms::absoluteList; NS_ADDREF(*aListName); break; } return NS_OK; } NS_IMETHODIMP nsBlockFrame::IsPercentageBase(PRBool& aBase) const { aBase = PR_TRUE; return NS_OK; } ////////////////////////////////////////////////////////////////////// // Frame structure methods ////////////////////////////////////////////////////////////////////// // Reflow methods #ifdef MOZ_MATHML inline nscoord nsBlockFrame::GetAscent() const { return mAscent; } #endif static void CalculateContainingBlock(const nsHTMLReflowState& aReflowState, nscoord aFrameWidth, nscoord aFrameHeight, nscoord& aContainingBlockWidth, nscoord& aContainingBlockHeight) { aContainingBlockWidth = -1; // have reflow state calculate aContainingBlockHeight = -1; // have reflow state calculate // The issue there is that for a 'height' of 'auto' the reflow state code // won't know how to calculate the containing block height because it's // calculated bottom up. We don't really want to do this for the initial // containing block so that's why we have the check for if the element // is absolutely or relatively positioned if (aReflowState.mStylePosition->IsAbsolutelyPositioned() || (NS_STYLE_POSITION_RELATIVE == aReflowState.mStylePosition->mPosition)) { aContainingBlockWidth = aFrameWidth; aContainingBlockHeight = aFrameHeight; // Containing block is relative to the padding edge nsMargin border; if (!aReflowState.mStyleSpacing->GetBorder(border)) { NS_NOTYETIMPLEMENTED("percentage border"); } aContainingBlockWidth -= border.left + border.right; aContainingBlockHeight -= border.top + border.bottom; } } NS_IMETHODIMP nsBlockFrame::Reflow(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aMetrics, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { /*ListTag(stdout); printf(": begin reflow type %d availSize=%d,%d computedSize=%d,%d\n", aReflowState.reason, aReflowState.availableWidth, aReflowState.availableHeight, aReflowState.mComputedWidth, aReflowState.mComputedHeight);*/ DO_GLOBAL_REFLOW_COUNT("nsBlockFrame", aReflowState.reason); #ifdef DEBUG if (gNoisyReflow) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": begin reflow type %d availSize=%d,%d computedSize=%d,%d\n", aReflowState.reason, aReflowState.availableWidth, aReflowState.availableHeight, aReflowState.mComputedWidth, aReflowState.mComputedHeight); } if (gNoisy) { gNoiseIndent++; } PRTime start; PRInt32 ctc; if (gLameReflowMetrics) { start = PR_Now(); ctc = nsLineBox::GetCtorCount(); } #endif // See if it's an incremental reflow command if (eReflowReason_Incremental == aReflowState.reason) { // Give the absolute positioning code a chance to handle it nscoord containingBlockWidth; nscoord containingBlockHeight; PRBool handled; nsRect childBounds; CalculateContainingBlock(aReflowState, mRect.width, mRect.height, containingBlockWidth, containingBlockHeight); mAbsoluteContainer.IncrementalReflow(this, aPresContext, aReflowState, containingBlockWidth, containingBlockHeight, handled, childBounds); // If the incremental reflow command was handled by the absolute positioning // code, then we're all done if (handled) { // Just return our current size as our desired size. // XXX We need to know the overflow area for the flowed content, and // we don't have a way to get that currently so for the time being pretend // a resize reflow occured #if 0 aMetrics.width = mRect.width; aMetrics.height = mRect.height; aMetrics.ascent = mRect.height; aMetrics.descent = 0; // Whether or not we're complete hasn't changed aStatus = (nsnull != mNextInFlow) ? NS_FRAME_NOT_COMPLETE : NS_FRAME_COMPLETE; #else nsHTMLReflowState reflowState(aReflowState); reflowState.reason = eReflowReason_Resize; reflowState.reflowCommand = nsnull; nsBlockFrame::Reflow(aPresContext, aMetrics, reflowState, aStatus); #endif // Factor the absolutely positioned child bounds into the overflow area aMetrics.mOverflowArea.UnionRect(aMetrics.mOverflowArea, childBounds); // Make sure the NS_FRAME_OUTSIDE_CHILDREN flag is set correctly if ((aMetrics.mOverflowArea.x < 0) || (aMetrics.mOverflowArea.y < 0) || (aMetrics.mOverflowArea.XMost() > aMetrics.width) || (aMetrics.mOverflowArea.YMost() > aMetrics.height)) { mState |= NS_FRAME_OUTSIDE_CHILDREN; } else { mState &= ~NS_FRAME_OUTSIDE_CHILDREN; } return NS_OK; } } if (IsFrameTreeTooDeep(aReflowState, aMetrics)) { #ifdef DEBUG_kipp { extern char* nsPresShell_ReflowStackPointerTop; char marker; char* newsp = (char*) ▮ printf("XXX: frame tree is too deep; approx stack size = %d\n", nsPresShell_ReflowStackPointerTop - newsp); } #endif aStatus = NS_FRAME_COMPLETE; return NS_OK; } // Should we create a space manager? nsCOMPtr spaceManager; nsISpaceManager* oldSpaceManager = aReflowState.mSpaceManager; if (NS_BLOCK_SPACE_MGR & mState) { nsSpaceManager* rawPtr = new nsSpaceManager(this); if (!rawPtr) { return NS_ERROR_OUT_OF_MEMORY; } spaceManager = do_QueryInterface(rawPtr); // Set the space manager in the existing reflow state nsHTMLReflowState& reflowState = (nsHTMLReflowState&)aReflowState; reflowState.mSpaceManager = spaceManager.get(); #ifdef NOISY_SPACEMANAGER printf("constructed new space manager %p\n", reflowState.mSpaceManager); #endif } nsBlockReflowState state(aReflowState, aPresContext, this, aMetrics, NS_BLOCK_MARGIN_ROOT & mState); if (eReflowReason_Resize != aReflowState.reason) { RenumberLists(aPresContext); } nsresult rv = NS_OK; PRBool isStyleChange = PR_FALSE; state.SetFlag(BRS_ISINLINEINCRREFLOW, PR_FALSE); nsIFrame* target; switch (aReflowState.reason) { case eReflowReason_Initial: #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": reflow=initial\n"); #endif DrainOverflowLines(aPresContext); rv = PrepareInitialReflow(state); mState &= ~NS_FRAME_FIRST_REFLOW; break; case eReflowReason_Dirty: break; case eReflowReason_Incremental: // should call GetNext() ? aReflowState.reflowCommand->GetTarget(target); if (this == target) { nsIReflowCommand::ReflowType type; aReflowState.reflowCommand->GetType(type); #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": reflow=incremental type=%d\n", type); #endif switch (type) { case nsIReflowCommand::StyleChanged: rv = PrepareStyleChangedReflow(state); isStyleChange = PR_TRUE; break; case nsIReflowCommand::ReflowDirty: break; default: // Map any other incremental operations into full reflows rv = PrepareResizeReflow(state); break; } } else { // Get next frame in reflow command chain aReflowState.reflowCommand->GetNext(state.mNextRCFrame); #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": reflow=incremental"); if (state.mNextRCFrame) { printf(" next="); nsFrame::ListTag(stdout, state.mNextRCFrame); } printf("\n"); #endif // this code does a correct job of propogating incremental reflows (bug 25510) // and has the potential to be very efficient. we should be able to // terminate reflow after the incremental reflow if we can detect that // nothing significant has changed. PRBool isFloater; nsLineBox* prevLine; nsLineBox* line = FindLineFor(state.mNextRCFrame, &prevLine, &isFloater); if (line && (PR_FALSE==line->IsBlock())) { if (!isFloater) // punt if isFloater! { // reflow the line containing the target of the incr. reflow // first mark the line dirty and set up the state object rv = PrepareChildIncrementalReflow(state); state.SetFlag(BRS_ISINLINEINCRREFLOW, PR_TRUE); state.mPrevLine = prevLine; state.mCurrentLine = line; state.mNextRCFrame = state.mNextRCFrame; // let ReflowDirtyLines do all the work rv = ReflowDirtyLines(state); if (NS_FAILED(rv)) { NS_ASSERTION(0, "Reflow failed\n"); return rv; } // compute the final size ComputeFinalSize(aReflowState, state, aMetrics); // finally, mark this block frame as having a dirty child and return // XXX: we should be able to optimize this so we only call ReflowDirtyChild // if it's absolutely necessary: something on the line changed size. if (!IsIncrementalDamageConstrained(state)) { nsCOMPtr shell; aPresContext->GetShell(getter_AddRefs(shell)); rv = ReflowDirtyChild(shell, state.mNextRCFrame); //XXX: it's possible we need to do some work regarding incremental painting // here, see code below "ReflowDirtyLines() after this switch statement. // It might be right to factor the tail end of this method into a new method // and call that here before calling ReflowDirtyChild(). } return rv; } } rv = PrepareChildIncrementalReflow(state); } break; case eReflowReason_StyleChange: rv = PrepareStyleChangedReflow(state); break; case eReflowReason_Resize: default: #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": reflow=resize (%d)\n", aReflowState.reason); #endif DrainOverflowLines(aPresContext); rv = PrepareResizeReflow(state); break; } if (NS_FAILED(rv)) { NS_ASSERTION(0, "setting up reflow failed.\n"); return rv; } // Now reflow... rv = ReflowDirtyLines(state); if (NS_FAILED(rv)) { NS_ASSERTION(0, "reflow dirty lines failed.\n"); return rv; } aStatus = state.mReflowStatus; if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) { if (NS_STYLE_OVERFLOW_HIDDEN == aReflowState.mStyleDisplay->mOverflow) { aStatus = NS_FRAME_COMPLETE; } else { #ifdef DEBUG_kipp ListTag(stdout); printf(": block is not complete\n"); #endif } } // XXX_pref get rid of this! BuildFloaterList(); // Compute our final size ComputeFinalSize(aReflowState, state, aMetrics); if (NS_BLOCK_WRAP_SIZE & mState) { // When the area frame is supposed to wrap around all in-flow // children, make sure its big enough to include those that stick // outside the box. if (NS_FRAME_OUTSIDE_CHILDREN & mState) { nscoord xMost = aMetrics.mOverflowArea.XMost(); if (xMost > aMetrics.width) { #ifdef NOISY_FINAL_SIZE ListTag(stdout); printf(": changing desired width from %d to %d\n", aMetrics.width, xMost); #endif aMetrics.width = xMost; } nscoord yMost = aMetrics.mOverflowArea.YMost(); if (yMost > aMetrics.height) { #ifdef NOISY_FINAL_SIZE ListTag(stdout); printf(": changing desired height from %d to %d\n", aMetrics.height, yMost); #endif aMetrics.height = yMost; } } } // see if verifyReflow is enabled, and if so store off the space manager pointer #ifdef DEBUG PRInt32 verifyReflowFlags = nsIPresShell::GetVerifyReflowFlags(); if (VERIFY_REFLOW_INCLUDE_SPACE_MANAGER & verifyReflowFlags) { // this is a leak of the space manager, but it's only in debug if verify reflow is enabled, so not a big deal nsCOMPtr shell; aPresContext->GetShell(getter_AddRefs(shell)); if (shell) { nsCOMPtr frameManager; shell->GetFrameManager(getter_AddRefs(frameManager)); if (frameManager) { nsHTMLReflowState& reflowState = (nsHTMLReflowState&)aReflowState; NS_ADDREF(reflowState.mSpaceManager); rv = frameManager->SetFrameProperty(this, nsLayoutAtoms::spaceManagerProperty, reflowState.mSpaceManager, nsnull /* should be nsSpaceManagerDestroyer*/); } } } #endif // If we set the space manager, then restore the old space manager now that we're // going out of scope if (NS_BLOCK_SPACE_MGR & mState) { nsHTMLReflowState& reflowState = (nsHTMLReflowState&)aReflowState; #ifdef NOISY_SPACEMANAGER printf("restoring old space manager %p\n", oldSpaceManager); #endif reflowState.mSpaceManager = oldSpaceManager; } #ifdef NOISY_SPACEMANAGER nsHTMLReflowState& reflowState = (nsHTMLReflowState&)aReflowState; if (reflowState.mSpaceManager) { ListTag(stdout); printf(": space-manager %p after reflow\n", reflowState.mSpaceManager); reflowState.mSpaceManager->List(stdout); } #endif // If this is an incremental reflow and we changed size, then make sure our // border is repainted if necessary if (eReflowReason_Incremental == aReflowState.reason || eReflowReason_Dirty == aReflowState.reason) { if (isStyleChange) { // Lots of things could have changed so damage our entire // bounds #ifdef NOISY_BLOCK_INVALIDATE printf("%p invalidate 1 (%d, %d, %d, %d)\n", this, 0, 0, mRect.width, mRect.height); #endif Invalidate(aPresContext, nsRect(0, 0, mRect.width, mRect.height)); } else { nsMargin border = aReflowState.mComputedBorderPadding - aReflowState.mComputedPadding; // See if our width changed if ((aMetrics.width != mRect.width) && (border.right > 0)) { nsRect damageRect; if (aMetrics.width < mRect.width) { // Our new width is smaller, so we need to make sure that // we paint our border in its new position damageRect.x = aMetrics.width - border.right; damageRect.width = border.right; damageRect.y = 0; damageRect.height = aMetrics.height; } else { // Our new width is larger, so we need to erase our border in its // old position damageRect.x = mRect.width - border.right; damageRect.width = border.right; damageRect.y = 0; damageRect.height = mRect.height; } #ifdef NOISY_BLOCK_INVALIDATE printf("%p invalidate 2 (%d, %d, %d, %d)\n", this, damageRect.x, damageRect.y, damageRect.width, damageRect.height); #endif Invalidate(aPresContext, damageRect); } // See if our height changed if ((aMetrics.height != mRect.height) && (border.bottom > 0)) { nsRect damageRect; if (aMetrics.height < mRect.height) { // Our new height is smaller, so we need to make sure that // we paint our border in its new position damageRect.x = 0; damageRect.width = aMetrics.width; damageRect.y = aMetrics.height - border.bottom; damageRect.height = border.bottom; } else { // Our new height is larger, so we need to erase our border in its // old position damageRect.x = 0; damageRect.width = mRect.width; damageRect.y = mRect.height - border.bottom; damageRect.height = border.bottom; } #ifdef NOISY_BLOCK_INVALIDATE printf("%p invalidate 3 (%d, %d, %d, %d)\n", this, damageRect.x, damageRect.y, damageRect.width, damageRect.height); #endif Invalidate(aPresContext, damageRect); } } } // Let the absolutely positioned container reflow any absolutely positioned // child frames that need to be reflowed, e.g., elements with a percentage // based width/height if (NS_SUCCEEDED(rv) && mAbsoluteContainer.HasAbsoluteFrames()) { nscoord containingBlockWidth; nscoord containingBlockHeight; nsRect childBounds; CalculateContainingBlock(aReflowState, aMetrics.width, aMetrics.height, containingBlockWidth, containingBlockHeight); rv = mAbsoluteContainer.Reflow(this, aPresContext, aReflowState, containingBlockWidth, containingBlockHeight, childBounds); // Factor the absolutely positioned child bounds into the overflow area aMetrics.mOverflowArea.UnionRect(aMetrics.mOverflowArea, childBounds); // Make sure the NS_FRAME_OUTSIDE_CHILDREN flag is set correctly if ((aMetrics.mOverflowArea.x < 0) || (aMetrics.mOverflowArea.y < 0) || (aMetrics.mOverflowArea.XMost() > aMetrics.width) || (aMetrics.mOverflowArea.YMost() > aMetrics.height)) { mState |= NS_FRAME_OUTSIDE_CHILDREN; } else { mState &= ~NS_FRAME_OUTSIDE_CHILDREN; } } #ifdef DEBUG if (gNoisy) { gNoiseIndent--; } if (gNoisyReflow) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": status=%x (%scomplete) metrics=%d,%d carriedMargin=%d", aStatus, NS_FRAME_IS_COMPLETE(aStatus) ? "" : "not ", aMetrics.width, aMetrics.height, aMetrics.mCarriedOutBottomMargin); if (mState & NS_FRAME_OUTSIDE_CHILDREN) { printf(" combinedArea={%d,%d,%d,%d}", aMetrics.mOverflowArea.x, aMetrics.mOverflowArea.y, aMetrics.mOverflowArea.width, aMetrics.mOverflowArea.height); } if (aMetrics.maxElementSize) { printf(" maxElementSize=%d,%d", aMetrics.maxElementSize->width, aMetrics.maxElementSize->height); } printf("\n"); } if (gLameReflowMetrics) { PRTime end = PR_Now(); PRInt32 ectc = nsLineBox::GetCtorCount(); PRInt32 numLines = nsLineBox::ListLength(mLines); if (!numLines) numLines = 1; PRTime delta, perLineDelta, lines; LL_I2L(lines, numLines); LL_SUB(delta, end, start); LL_DIV(perLineDelta, delta, lines); ListTag(stdout); char buf[400]; PR_snprintf(buf, sizeof(buf), ": %lld elapsed (%lld per line) (%d lines; %d new lines)", delta, perLineDelta, numLines, ectc - ctc); printf("%s\n", buf); } #endif #ifdef NOISY_MAX_ELEMENT_SIZE if (aMetrics.maxElementSize) { printf("block %p returning with maxElementSize=%d,%d\n", this, aMetrics.maxElementSize->width, aMetrics.maxElementSize->height); } #endif return rv; } static PRBool HaveAutoWidth(const nsHTMLReflowState& aReflowState) { const nsHTMLReflowState* rs = &aReflowState; if (NS_UNCONSTRAINEDSIZE == rs->mComputedWidth) { return PR_TRUE; } const nsStylePosition* pos = rs->mStylePosition; for (;;) { if (!pos) { return PR_TRUE; } nsStyleUnit widthUnit = pos->mWidth.GetUnit(); if (eStyleUnit_Auto == widthUnit) { return PR_TRUE; } if (eStyleUnit_Inherit != widthUnit) { break; } const nsHTMLReflowState* prs = (const nsHTMLReflowState*) rs->parentReflowState; if (!prs) { return PR_TRUE; } rs = prs; pos = prs->mStylePosition; } return PR_FALSE; } static PRBool IsPercentageUnitSides(const nsStyleSides* aSides) { return eStyleUnit_Percent == aSides->GetLeftUnit() || eStyleUnit_Percent == aSides->GetRightUnit() || eStyleUnit_Percent == aSides->GetTopUnit() || eStyleUnit_Percent == aSides->GetBottomUnit(); } static PRBool IsPercentageAwareChild(const nsIFrame* aFrame) { const nsStyleSpacing* space; nsresult rv = aFrame->GetStyleData(eStyleStruct_Spacing,(const nsStyleStruct*&) space); if (NS_FAILED(rv)) { return PR_TRUE; // just to be on the safe side } if (IsPercentageUnitSides(&space->mMargin) || IsPercentageUnitSides(&space->mPadding) || IsPercentageUnitSides(&space->mBorderRadius)) { return PR_TRUE; } const nsStylePosition* pos; rv = aFrame->GetStyleData(eStyleStruct_Position,(const nsStyleStruct*&) pos); if (NS_FAILED(rv)) { return PR_TRUE; // just to be on the safe side } if (eStyleUnit_Percent == pos->mWidth.GetUnit() || eStyleUnit_Percent == pos->mMaxWidth.GetUnit() || eStyleUnit_Percent == pos->mMinWidth.GetUnit() || eStyleUnit_Percent == pos->mHeight.GetUnit() || eStyleUnit_Percent == pos->mMinHeight.GetUnit() || eStyleUnit_Percent == pos->mMaxHeight.GetUnit() || IsPercentageUnitSides(&pos->mOffset)) { // XXX need more here!!! return PR_TRUE; } return PR_FALSE; } void nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, nsBlockReflowState& aState, nsHTMLReflowMetrics& aMetrics) { const nsMargin& borderPadding = aState.BorderPadding(); #ifdef NOISY_FINAL_SIZE ListTag(stdout); printf(": mY=%d mIsBottomMarginRoot=%s mPrevBottomMargin=%d bp=%d,%d\n", aState.mY, aState.GetFlag(BRS_ISBOTTOMMARGINROOT) ? "yes" : "no", aState.mPrevBottomMargin, borderPadding.top, borderPadding.bottom); #endif // Compute final width nscoord maxWidth = 0, maxHeight = 0; #ifdef NOISY_KIDXMOST printf("%p aState.mKidXMost=%d\n", this, aState.mKidXMost); #endif nscoord minWidth = aState.mKidXMost + borderPadding.right; if (!HaveAutoWidth(aReflowState)) { // Use style defined width aMetrics.width = borderPadding.left + aReflowState.mComputedWidth + borderPadding.right; // XXX quote css1 section here if ((0 == aReflowState.mComputedWidth) && (aMetrics.width < minWidth)) { aMetrics.width = minWidth; } // When style defines the width use it for the max-element-size // because we can't shrink any smaller. maxWidth = aMetrics.width; } else { nscoord computedWidth = minWidth; PRBool compact = PR_FALSE; #if 0 if (NS_STYLE_DISPLAY_COMPACT == aReflowState.mStyleDisplay->mDisplay) { // If we are display: compact AND we have no lines or we have // exactly one line and that line is not a block line AND that // line doesn't end in a BR of any sort THEN we remain a compact // frame. if ((nsnull == mLines) || ((nsnull == mLines->mNext) && !mLines->IsBlock() && (NS_STYLE_CLEAR_NONE == mLines->GetBreakType()) /*XXX && (computedWidth <= aState.mCompactMarginWidth) */ )) { compact = PR_TRUE; } } #endif // There are two options here. We either shrink wrap around our // contents or we fluff out to the maximum block width. Note: // We always shrink wrap when given an unconstrained width. if ((0 == (NS_BLOCK_SHRINK_WRAP & mState)) && !aState.GetFlag(BRS_UNCONSTRAINEDWIDTH) && !aState.GetFlag(BRS_SHRINKWRAPWIDTH) && !compact) { // Set our width to the max width if we aren't already that // wide. Note that the max-width has nothing to do with our // contents (CSS2 section XXX) computedWidth = borderPadding.left + aState.mContentArea.width + borderPadding.right; } // See if we should compute our max element size if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) { // Adjust the computedWidth if (aState.GetFlag(BRS_NOWRAP)) { // When no-wrap is true the max-element-size.width is the // width of the widest line plus the right border. Note that // aState.mKidXMost already has the left border factored in //maxWidth = aState.mKidXMost + borderPadding.right; maxWidth = aState.mMaxElementSize.width + borderPadding.left + borderPadding.right; } else { // Add in border and padding dimensions to already computed // max-element-size values. maxWidth = aState.mMaxElementSize.width + borderPadding.left + borderPadding.right; } if (computedWidth < maxWidth) { computedWidth = maxWidth; } } // Apply min/max values if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxWidth) { nscoord computedMaxWidth = aReflowState.mComputedMaxWidth + borderPadding.left + borderPadding.right; if (computedWidth > computedMaxWidth) { computedWidth = computedMaxWidth; } } if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinWidth) { nscoord computedMinWidth = aReflowState.mComputedMinWidth + borderPadding.left + borderPadding.right; if (computedWidth < computedMinWidth) { computedWidth = computedMinWidth; } } aMetrics.width = computedWidth; // If we're shrink wrapping, then now that we know our final width we // need to do horizontal alignment of the inline lines and make sure // blocks are correctly sized and positioned. Any lines that need // final adjustment will have been marked as dirty if (aState.GetFlag(BRS_SHRINKWRAPWIDTH) && aState.GetFlag(BRS_NEEDRESIZEREFLOW)) { // If the parent reflow state is also shrink wrap width, then // we don't need to do this, because it will reflow us after it // calculates the final width PRBool parentIsShrinkWrapWidth = PR_FALSE; if (aReflowState.parentReflowState) { if (NS_SHRINKWRAPWIDTH == aReflowState.parentReflowState->mComputedWidth) { parentIsShrinkWrapWidth = PR_TRUE; } } if (!parentIsShrinkWrapWidth) { nsHTMLReflowState reflowState(aReflowState); reflowState.mComputedWidth = aMetrics.width - borderPadding.left - borderPadding.right; reflowState.reason = eReflowReason_Resize; reflowState.mSpaceManager->ClearRegions(); nscoord oldDesiredWidth = aMetrics.width; nsBlockReflowState state(reflowState, aState.mPresContext, this, aMetrics, NS_BLOCK_MARGIN_ROOT & mState); ReflowDirtyLines(state); aState.mY = state.mY; NS_ASSERTION(oldDesiredWidth == aMetrics.width, "bad desired width"); } } } if (aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { PRBool parentIsShrinkWrapWidth = PR_FALSE; if (aReflowState.parentReflowState) { if (NS_SHRINKWRAPWIDTH == aReflowState.parentReflowState->mComputedWidth) { parentIsShrinkWrapWidth = PR_TRUE; } } } // Compute final height if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedHeight) { // Use style defined height aMetrics.height = borderPadding.top + aReflowState.mComputedHeight + borderPadding.bottom; // When style defines the height use it for the max-element-size // because we can't shrink any smaller. maxHeight = aMetrics.height; // Don't carry out a bottom margin when our height is fixed // unless the bottom of the last line adjoins the bottom of our // content area. if (!aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) { if (aState.mY + aState.mPrevBottomMargin != aMetrics.height) { aState.mPrevBottomMargin = 0; } } } else { nscoord autoHeight = aState.mY; // Shrink wrap our height around our contents. if (aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) { // When we are a bottom-margin root make sure that our last // childs bottom margin is fully applied. // XXX check for a fit autoHeight += aState.mPrevBottomMargin; } autoHeight += borderPadding.bottom; // Apply min/max values #ifdef MOZ_MATHML // XXX Here in ComputeFinalSize() // XXX What to do when min/max values are applied to the height? // How do all this impact on the first line of the block? #endif if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxHeight) { nscoord computedMaxHeight = aReflowState.mComputedMaxHeight + borderPadding.top + borderPadding.bottom; if (autoHeight > computedMaxHeight) { autoHeight = computedMaxHeight; } } if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinHeight) { nscoord computedMinHeight = aReflowState.mComputedMinHeight + borderPadding.top + borderPadding.bottom; if (autoHeight < computedMinHeight) { autoHeight = computedMinHeight; } } aMetrics.height = autoHeight; if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) { maxHeight = aState.mMaxElementSize.height + borderPadding.top + borderPadding.bottom; } } #ifndef MOZ_MATHML aMetrics.ascent = aMetrics.height; aMetrics.descent = 0; #else if (mLines && mLines->mFirstChild && mLines->IsBlock()) { // mAscent is not yet set because we didn't call VerticalAlignFrames() // on mLines. So we need to fetch the ascent of the first child of mLines nsBlockFrame* bf; nsresult res = mLines->mFirstChild->QueryInterface(kBlockFrameCID, (void**)&bf); if (NS_SUCCEEDED(res) && bf) { mAscent = bf->GetAscent(); } } aMetrics.ascent = mAscent; aMetrics.descent = aMetrics.height - aMetrics.ascent; #endif if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) { // Store away the final value aMetrics.maxElementSize->width = maxWidth; aMetrics.maxElementSize->height = maxHeight; #ifdef NOISY_MAX_ELEMENT_SIZE printf ("nsBlockFrame::CFS: %p returning MES %d\n", this, aMetrics.maxElementSize->width); #endif } // Return bottom margin information aMetrics.mCarriedOutBottomMargin = aState.GetFlag(BRS_ISBOTTOMMARGINROOT) ? 0 : aState.mPrevBottomMargin; #ifdef DEBUG_blocks if (CRAZY_WIDTH(aMetrics.width) || CRAZY_HEIGHT(aMetrics.height)) { ListTag(stdout); printf(": WARNING: desired:%d,%d\n", aMetrics.width, aMetrics.height); } if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE) && ((maxWidth > aMetrics.width) || (maxHeight > aMetrics.height))) { ListTag(stdout); printf(": WARNING: max-element-size:%d,%d desired:%d,%d maxSize:%d,%d\n", maxWidth, maxHeight, aMetrics.width, aMetrics.height, aState.mReflowState.availableWidth, aState.mReflowState.availableHeight); } #endif #ifdef NOISY_MAX_ELEMENT_SIZE if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) { IndentBy(stdout, GetDepth()); if (NS_UNCONSTRAINEDSIZE == aState.mReflowState.availableWidth) { printf("PASS1 "); } ListTag(stdout); printf(": max-element-size:%d,%d desired:%d,%d maxSize:%d,%d\n", maxWidth, maxHeight, aMetrics.width, aMetrics.height, aState.mReflowState.availableWidth, aState.mReflowState.availableHeight); } #endif // If we're requested to update our maximum width, then compute it if (aState.GetFlag(BRS_COMPUTEMAXWIDTH)) { // We need to add in for the right border/padding aMetrics.mMaximumWidth = aState.mMaximumWidth + borderPadding.right; #ifdef NOISY_MAXIMUM_WIDTH printf("nsBlockFrame::ComputeFinalSize block %p setting aMetrics.mMaximumWidth to %d\n", this, aMetrics.mMaximumWidth); #endif } // Compute the combined area of our children // XXX_perf: This can be done incrementally nscoord xa = 0, ya = 0, xb = aMetrics.width, yb = aMetrics.height; if (NS_STYLE_OVERFLOW_HIDDEN != aReflowState.mStyleDisplay->mOverflow) { nsLineBox* line = mLines; while (nsnull != line) { // Compute min and max x/y values for the reflowed frame's // combined areas nsRect lineCombinedArea; line->GetCombinedArea(&lineCombinedArea); nscoord x = lineCombinedArea.x; nscoord y = lineCombinedArea.y; nscoord xmost = x + lineCombinedArea.width; nscoord ymost = y + lineCombinedArea.height; if (x < xa) { xa = x; } if (xmost > xb) { xb = xmost; } if (y < ya) { ya = y; } if (ymost > yb) { yb = ymost; } line = line->mNext; } // Factor the bullet in; normally the bullet will be factored into // the line-box's combined area. However, if the line is a block // line then it won't; if there are no lines, it won't. So just // factor it in anyway (it can't hurt if it was already done). if (mBullet) { nsRect r; mBullet->GetRect(r); if (r.x < xa) xa = r.x; if (r.y < ya) ya = r.y; nscoord xmost = r.XMost(); if (xmost > xb) xb = xmost; nscoord ymost = r.YMost(); if (ymost > yb) yb = ymost; } } #ifdef NOISY_COMBINED_AREA ListTag(stdout); printf(": ca=%d,%d,%d,%d\n", xa, ya, xb-xa, yb-ya); #endif // If the combined area of our children exceeds our bounding box // then set the NS_FRAME_OUTSIDE_CHILDREN flag, otherwise clear it. aMetrics.mOverflowArea.x = xa; aMetrics.mOverflowArea.y = ya; aMetrics.mOverflowArea.width = xb - xa; aMetrics.mOverflowArea.height = yb - ya; if ((aMetrics.mOverflowArea.x < 0) || (aMetrics.mOverflowArea.y < 0) || (aMetrics.mOverflowArea.XMost() > aMetrics.width) || (aMetrics.mOverflowArea.YMost() > aMetrics.height)) { mState |= NS_FRAME_OUTSIDE_CHILDREN; } else { mState &= ~NS_FRAME_OUTSIDE_CHILDREN; } } nsresult nsBlockFrame::PrepareInitialReflow(nsBlockReflowState& aState) { PrepareResizeReflow(aState); return NS_OK; } nsresult nsBlockFrame::PrepareChildIncrementalReflow(nsBlockReflowState& aState) { // Determine the line being impacted PRBool isFloater; nsLineBox* prevLine; nsLineBox* line = FindLineFor(aState.mNextRCFrame, &prevLine, &isFloater); if (nsnull == line) { // This can't happen, but just in case it does... return PrepareResizeReflow(aState); } // XXX: temporary: If the child frame is a floater then punt if (isFloater) { return PrepareResizeReflow(aState); } // Figure out which line to mark dirty. MarkLineDirty(line, prevLine); return NS_OK; } nsresult nsBlockFrame::MarkLineDirty(nsLineBox* aLine, nsLineBox* aPrevLine) { // Mark aLine dirty aLine->MarkDirty(); #ifdef DEBUG if (gNoisyReflow) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": mark line %p dirty\n", aLine); } #endif // Mark previous line dirty if its an inline line so that it can // maybe pullup something from the line just affected. // XXX We don't need to do this if aPrevLine ends in a break-after... if (aPrevLine && !aPrevLine->IsBlock()) { aPrevLine->MarkDirty(); #ifdef DEBUG if (gNoisyReflow) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": mark prev-line %p dirty\n", aPrevLine); } #endif } return NS_OK; } nsresult nsBlockFrame::UpdateBulletPosition(nsBlockReflowState& aState) { if (nsnull == mBullet) { // Don't bother if there is no bullet return NS_OK; } const nsStyleList* styleList; GetStyleData(eStyleStruct_List, (const nsStyleStruct*&) styleList); if (NS_STYLE_LIST_STYLE_POSITION_INSIDE == styleList->mListStylePosition) { if (HaveOutsideBullet()) { // We now have an inside bullet, but used to have an outside // bullet. Adjust the frame line list nsLineBox* line = aState.NewLineBox(mBullet, 1, PR_FALSE); if (!line) { return NS_ERROR_OUT_OF_MEMORY; } line->mNext = mLines; mLines = line; } mState &= ~NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET; } else { if (!HaveOutsideBullet()) { // We now have an outside bullet, but used to have an inside // bullet. Take the bullet frame out of the first lines frame // list. if ((nsnull != mLines) && (mBullet == mLines->mFirstChild)) { nsIFrame* next; mBullet->GetNextSibling(&next); mBullet->SetNextSibling(nsnull); PRInt32 count = mLines->GetChildCount() - 1; NS_ASSERTION(count >= 0, "empty line w/o bullet"); mLines->SetChildCount(count); if (0 == count) { nsLineBox* nextLine = mLines->mNext; aState.FreeLineBox(mLines); mLines = nextLine; if (nsnull != nextLine) { nextLine->MarkDirty(); } } else { mLines->mFirstChild = next; mLines->MarkDirty(); } } } mState |= NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET; } #ifdef DEBUG VerifyLines(PR_TRUE); #endif return NS_OK; } nsresult nsBlockFrame::PrepareStyleChangedReflow(nsBlockReflowState& aState) { nsresult rv = UpdateBulletPosition(aState); // Mark everything dirty nsLineBox* line = mLines; while (nsnull != line) { line->MarkDirty(); line = line->mNext; } return rv; } nsresult nsBlockFrame::PrepareResizeReflow(nsBlockReflowState& aState) { // See if we can try and avoid marking all the lines as dirty PRBool tryAndSkipLines = PR_FALSE; // we need to calculate if any part of then block itself // is impacted by a floater (bug 19579) aState.GetAvailableSpace(); // See if this is this a constrained resize reflow that is not impacted by floaters if ((PR_FALSE==aState.IsImpactedByFloater()) && (aState.mReflowState.reason == eReflowReason_Resize) && (NS_UNCONSTRAINEDSIZE != aState.mReflowState.availableWidth)) { // If the text is left-aligned, then we try and avoid reflowing the lines const nsStyleText* styleText = (const nsStyleText*) mStyleContext->GetStyleData(eStyleStruct_Text); if ((NS_STYLE_TEXT_ALIGN_LEFT == styleText->mTextAlign) || ((NS_STYLE_TEXT_ALIGN_DEFAULT == styleText->mTextAlign) && (NS_STYLE_DIRECTION_LTR == aState.mReflowState.mStyleDisplay->mDirection))) { tryAndSkipLines = PR_TRUE; } } #ifdef DEBUG if (gDisableResizeOpt) { tryAndSkipLines = PR_FALSE; } if (gNoisyReflow) { if (!tryAndSkipLines) { const nsStyleText* mStyleText = (const nsStyleText*) mStyleContext->GetStyleData(eStyleStruct_Text); IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": marking all lines dirty: reason=%d availWidth=%d textAlign=%d\n", aState.mReflowState.reason, aState.mReflowState.availableWidth, mStyleText->mTextAlign); } } #endif nsLineBox* line = mLines; if (tryAndSkipLines) { // The line's bounds are relative to the border edge of the frame nscoord newAvailWidth = aState.mReflowState.mComputedBorderPadding.left; if (NS_SHRINKWRAPWIDTH == aState.mReflowState.mComputedWidth) { newAvailWidth += aState.mReflowState.mComputedMaxWidth; } else { newAvailWidth += aState.mReflowState.mComputedWidth; } #ifdef DEBUG if (gNoisyReflow) { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": trying to avoid marking all lines dirty\n"); } #endif PRBool notWrapping = aState.GetFlag(BRS_NOWRAP); while (nsnull != line) { if (line->IsBlock()) { // We have to let child blocks make their own decisions. line->MarkDirty(); } else { // We can avoid reflowing *some* inline lines in some cases. #ifdef REALLY_NOISY_REFLOW printf("PrepareResizeReflow thinks line %p is %simpacted by floaters\n", line, line->IsImpactedByFloater() ? "" : "not "); #endif if (notWrapping) { // When no-wrap is set then the only line-breaking that // occurs for inline lines is triggered by BR elements or by // newlines. Therefore, we don't need to reflow the line. } else if ((line->mNext && !line->HasBreak()) || line->HasFloaters() || line->IsImpactedByFloater() || line->HasPercentageChild() || (line->mBounds.XMost() > newAvailWidth)) { // When an inline line has: // // - a next line and it doesn't end in a break, or // - floaters, or // - is impacted by a floater, or // - is wider than the new available space // // Then we must reflow it. line->MarkDirty(); } #ifdef DEBUG if (gNoisyReflow && !line->IsDirty() && !notWrapping) { IndentBy(stdout, gNoiseIndent + 1); printf("skipped: line=%p next=%p %s %s %s%s%s breakType=%d xmost=%d\n", line, line->mNext, line->IsBlock() ? "block" : "inline", aState.GetFlag(BRS_NOWRAP) ? "no-wrap" : "wrapping", line->HasBreak() ? "has-break " : "", line->HasFloaters() ? "has-floaters " : "", line->IsImpactedByFloater() ? "impacted " : "", line->GetBreakType(), line->mBounds.XMost()); } #endif } line = line->mNext; } } else { // Mark everything dirty while (nsnull != line) { line->MarkDirty(); line = line->mNext; } } return NS_OK; } //---------------------------------------- nsLineBox* nsBlockFrame::FindLineFor(nsIFrame* aFrame, nsLineBox** aPrevLineResult, PRBool* aIsFloaterResult) { nsLineBox* prevLine = nsnull; nsLineBox* line = mLines; PRBool isFloater = PR_FALSE; while (nsnull != line) { if (line->Contains(aFrame)) { break; } if (line->HasFloaters()) { nsFloaterCache* fc = line->GetFirstFloater(); while (fc) { if (aFrame == fc->mPlaceholder->GetOutOfFlowFrame()) { isFloater = PR_TRUE; goto done; } fc = fc->Next(); } } prevLine = line; line = line->mNext; } done: *aIsFloaterResult = isFloater; *aPrevLineResult = prevLine; return line; } void nsBlockFrame::RecoverStateFrom(nsBlockReflowState& aState, nsLineBox* aLine, nscoord aDeltaY, nsRect* aDamageRect) { PRBool applyTopMargin = PR_FALSE; if (aLine->IsBlock()) { nsIFrame* framePrevInFlow; aLine->mFirstChild->GetPrevInFlow(&framePrevInFlow); if (nsnull == framePrevInFlow) { applyTopMargin = ShouldApplyTopMargin(aState, aLine); } } aState.RecoverStateFrom(aLine, applyTopMargin, aDeltaY, aDamageRect); } /** * Propogate reflow "damage" from the just reflowed line (aLine) to * any subsequent lines that were affected. The only thing that causes * damage is a change to the impact that floaters make. */ void nsBlockFrame::PropogateReflowDamage(nsBlockReflowState& aState, nsLineBox* aLine, const nsRect& aOldCombinedArea, nscoord aDeltaY) { // See if the line has a relevant combined area, and if it does if // the combined area has changed. nsRect lineCombinedArea; aLine->GetCombinedArea(&lineCombinedArea); if (lineCombinedArea != aLine->mBounds) { if (lineCombinedArea != aOldCombinedArea) { // The line's combined-area changed. Therefore we need to damage // the lines below that were previously (or are now) impacted by // the change. It's possible that a floater shrunk or grew so // use the larger of the impacted area. nscoord newYMost = lineCombinedArea.YMost(); nscoord oldYMost = aOldCombinedArea.YMost(); nscoord impactYB = newYMost < oldYMost ? oldYMost : newYMost; nscoord impactYA = lineCombinedArea.y; // Loop over each subsequent line and mark them dirty if they // intersect the impacted area. Note: we cannot stop after the // first non-intersecting line because lines might be // overlapping because of negative margins. nsLineBox* next = aLine->mNext; while (nsnull != next) { nscoord lineYA = next->mBounds.y + aDeltaY; nscoord lineYB = lineYA + next->mBounds.height; if ((lineYB >= impactYA) && (lineYA < impactYB)) { next->MarkDirty(); } next = next->mNext; } } else { // The line's combined area didn't change from last // time. Therefore just sliding subsequent lines will work. return; } } if (aDeltaY) { nsLineBox* next = aLine->mNext; while (nsnull != next) { if (!next->IsDirty()) { // Cases we need to find: // // 1. the line was impacted by a floater and now isn't // 2. the line wasn't impacted by a floater and now is // //XXXPerf: An optimization: if the line was and is completely //impacted by a floater and the floater hasn't changed size, //then we don't need to mark the line dirty. aState.GetAvailableSpace(next->mBounds.y + aDeltaY); PRBool wasImpactedByFloater = next->IsImpactedByFloater(); PRBool isImpactedByFloater = aState.IsImpactedByFloater() ? PR_TRUE : PR_FALSE; #ifdef REALLY_NOISY_REFLOW printf("nsBlockFrame::PropogateReflowDamage %p was = %d, is=%d\n", this, wasImpactedByFloater, isImpactedByFloater); #endif if (wasImpactedByFloater != isImpactedByFloater) { next->MarkDirty(); } else if (isImpactedByFloater) { //XXX: Maybe the floater itself changed size? if (next->IsBlock()) { //XXXPerf // Case: // It's possible that more/less of the line is impacted by // the floater than last time. So reflow. next->MarkDirty(); } } } next = next->mNext; } } } static PRBool WrappedLinesAreDirty(nsLineBox* aLine) { if (aLine->IsInline()) { while (aLine->IsLineWrapped()) { aLine = aLine->mNext; if (!aLine) { break; } NS_ASSERTION(!aLine->IsBlock(), "didn't expect a block line"); if (aLine->IsDirty()) { // we found a continuing line that is dirty return PR_TRUE; } } } return PR_FALSE; } PRBool nsBlockFrame::IsIncrementalDamageConstrained(const nsBlockReflowState& aState) const { // see if the reflow will go through a text control. if so, we can optimize // because we know the text control won't change size. if (aState.mReflowState.reflowCommand) { nsIFrame *target; aState.mReflowState.reflowCommand->GetTarget(target); while (target) { // starting with the target's parent, scan for a text control nsIFrame *parent; target->GetParent(&parent); if ((nsIFrame*)this==parent || !parent) // the null check is paranoia, it should never happen break; // we found ourself, so we know there's no text control between us and target nsCOMPtr frameType; parent->GetFrameType(getter_AddRefs(frameType)); if (frameType) { if (nsLayoutAtoms::textInputFrame == frameType.get()) return PR_TRUE; // damage is constrained to the text control innards } target = parent; // advance the loop up the frame tree } } return PR_FALSE; // default case, damage is not constrained (or unknown) } /** * Reflow the dirty lines */ nsresult nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) { nsresult rv = NS_OK; PRBool keepGoing = PR_TRUE; #ifdef DEBUG if (gNoisyReflow) { if (aState.mReflowState.reason == eReflowReason_Incremental) { nsIReflowCommand::ReflowType type; aState.mReflowState.reflowCommand->GetType(type); IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": incrementally reflowing dirty lines: type=%s(%d) isInline=%s", kReflowCommandType[type], type, aState.GetFlag(BRS_ISINLINEINCRREFLOW) ? "true" : "false"); } else { IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": reflowing dirty lines"); } printf(" computedWidth=%d\n", aState.mReflowState.mComputedWidth); gNoiseIndent++; } #endif // Check whether this is an incremental reflow PRBool incrementalReflow = aState.mReflowState.reason == eReflowReason_Incremental || aState.mReflowState.reason == eReflowReason_Dirty; nscoord deltaY = 0; // Reflow the lines that are already ours aState.mPrevLine = nsnull; nsLineBox* line = mLines; if (aState.GetFlag(BRS_ISINLINEINCRREFLOW) && aState.mNextRCFrame) { const nsLineBox* incrTargetLine = aState.mCurrentLine; aState.mCurrentLine = line; aState.mPrevLine = nsnull; while (line && (line != incrTargetLine)) { nsRect damageRect; RecoverStateFrom(aState, line, deltaY, incrementalReflow ? &damageRect : 0); if (incrementalReflow && !damageRect.IsEmpty()) { #ifdef NOISY_BLOCK_INVALIDATE printf("%p invalidate 4 (%d, %d, %d, %d)\n", this, damageRect.x, damageRect.y, damageRect.width, damageRect.height); #endif Invalidate(aState.mPresContext, damageRect); } aState.mPrevLine = line; line = line->mNext; aState.AdvanceToNextLine(); } } while (nsnull != line) { #ifdef DEBUG if (gNoisyReflow) { nsRect lca; line->GetCombinedArea(&lca); IndentBy(stdout, gNoiseIndent); printf("line=%p mY=%d dirty=%s oldBounds={%d,%d,%d,%d} oldCombinedArea={%d,%d,%d,%d} deltaY=%d mPrevBottomMargin=%d\n", line, aState.mY, line->IsDirty() ? "yes" : "no", line->mBounds.x, line->mBounds.y, line->mBounds.width, line->mBounds.height, lca.x, lca.y, lca.width, lca.height, deltaY, aState.mPrevBottomMargin); gNoiseIndent++; } #endif // If we're supposed to update our maximum width, then we'll also need to // reflow this line if it's line wrapped and any of the continuing lines // are dirty if (line->IsDirty() || (aState.GetFlag(BRS_COMPUTEMAXWIDTH) && ::WrappedLinesAreDirty(line))) { // Compute the dirty lines "before" YMost, after factoring in // the running deltaY value - the running value is implicit in // aState.mY. nscoord oldHeight = line->mBounds.height; nsRect oldCombinedArea; line->GetCombinedArea(&oldCombinedArea); // Reflow the dirty line. If it's an incremental reflow, then force // it to invalidate the dirty area if necessary PRBool forceInvalidate = PR_FALSE; if (incrementalReflow) { forceInvalidate = !IsIncrementalDamageConstrained(aState); } rv = ReflowLine(aState, line, &keepGoing, forceInvalidate); if (NS_FAILED(rv)) { return rv; } if (!keepGoing) { if (0 == line->GetChildCount()) { DeleteLine(aState, line); } break; } nscoord newHeight = line->mBounds.height; deltaY += newHeight - oldHeight; // If the next line is clean then check and see if reflowing the // current line "damaged" the next line. Damage occurs when the // current line contains floaters that intrude upon the // subsequent lines. nsLineBox* next = line->mNext; if ((nsnull != next) && !next->IsDirty()) { PropogateReflowDamage(aState, line, oldCombinedArea, deltaY); } } else { // XXX what if the slid line doesn't fit because we are in a // vertically constrained situation? // Recover state as if we reflowed this line nsRect damageRect; RecoverStateFrom(aState, line, deltaY, incrementalReflow ? &damageRect : 0); if (incrementalReflow && !damageRect.IsEmpty()) { #ifdef NOISY_BLOCK_INVALIDATE printf("%p invalidate 5 (%d, %d, %d, %d)\n", this, damageRect.x, damageRect.y, damageRect.width, damageRect.height); #endif Invalidate(aState.mPresContext, damageRect); } } #ifdef DEBUG if (gNoisyReflow) { gNoiseIndent--; nsRect lca; line->GetCombinedArea(&lca); IndentBy(stdout, gNoiseIndent); printf("line=%p mY=%d newBounds={%d,%d,%d,%d} newCombinedArea={%d,%d,%d,%d} deltaY=%d mPrevBottomMargin=%d\n", line, aState.mY, line->mBounds.x, line->mBounds.y, line->mBounds.width, line->mBounds.height, lca.x, lca.y, lca.width, lca.height, deltaY, aState.mPrevBottomMargin); } #endif // If this is an inline frame then its time to stop aState.mPrevLine = line; line = line->mNext; aState.AdvanceToNextLine(); } // Pull data from a next-in-flow if we can while (keepGoing && (nsnull != aState.mNextInFlow)) { // Grab first line from our next-in-flow line = aState.mNextInFlow->mLines; if (nsnull == line) { aState.mNextInFlow = (nsBlockFrame*) aState.mNextInFlow->mNextInFlow; continue; } // XXX See if the line is not dirty; if it's not maybe we can // avoid the pullup if it can't fit? aState.mNextInFlow->mLines = line->mNext; line->mNext = nsnull; if (0 == line->GetChildCount()) { // The line is empty. Try the next one. NS_ASSERTION(nsnull == line->mFirstChild, "bad empty line"); aState.FreeLineBox(line); continue; } // XXX move to a subroutine: run-in, overflow, pullframe and this do this // Make the children in the line ours. nsIFrame* frame = line->mFirstChild; nsIFrame* lastFrame = nsnull; PRInt32 n = line->GetChildCount(); while (--n >= 0) { frame->SetParent(this); // When pushing and pulling frames we need to check for whether any // views need to be reparented nsHTMLContainerFrame::ReparentFrameView(aState.mPresContext, frame, mNextInFlow, this); lastFrame = frame; frame->GetNextSibling(&frame); } lastFrame->SetNextSibling(nsnull); // Add line to our line list if (nsnull == aState.mPrevLine) { NS_ASSERTION(nsnull == mLines, "bad aState.mPrevLine"); mLines = line; } else { NS_ASSERTION(nsnull == aState.mPrevLine->mNext, "bad aState.mPrevLine"); aState.mPrevLine->mNext = line; aState.mPrevChild->SetNextSibling(line->mFirstChild); } // Now reflow it and any lines that it makes during it's reflow // (we have to loop here because reflowing the line may case a new // line to be created; see SplitLine's callers for examples of // when this happens). while (nsnull != line) { rv = ReflowLine(aState, line, &keepGoing, incrementalReflow ? PR_TRUE : PR_FALSE); if (NS_FAILED(rv)) { return rv; } if (!keepGoing) { if (0 == line->GetChildCount()) { DeleteLine(aState, line); } break; } // If this is an inline frame then its time to stop aState.mPrevLine = line; line = line->mNext; aState.AdvanceToNextLine(); } } // Handle an odd-ball case: a list-item with no lines if (mBullet && HaveOutsideBullet() && !mLines) { nsHTMLReflowMetrics metrics(nsnull); ReflowBullet(aState, metrics); // There are no lines so we have to fake up some y motion so that // we end up with *some* height. aState.mY += metrics.height; } #ifdef DEBUG if (gNoisyReflow) { gNoiseIndent--; IndentBy(stdout, gNoiseIndent); ListTag(stdout); printf(": done reflowing dirty lines (status=%x)\n", aState.mReflowStatus); } #endif return rv; } void nsBlockFrame::DeleteLine(nsBlockReflowState& aState, nsLineBox* aLine) { NS_PRECONDITION(0 == aLine->GetChildCount(), "can't delete !empty line"); if (0 == aLine->GetChildCount()) { if (nsnull == aState.mPrevLine) { NS_ASSERTION(aLine == mLines, "huh"); mLines = nsnull; } else { NS_ASSERTION(aState.mPrevLine->mNext == aLine, "bad prev-line"); aState.mPrevLine->mNext = aLine->mNext; } aState.FreeLineBox(aLine); } } /** * Reflow a line. The line will either contain a single block frame * or contain 1 or more inline frames. aKeepReflowGoing indicates * whether or not the caller should continue to reflow more lines. */ nsresult nsBlockFrame::ReflowLine(nsBlockReflowState& aState, nsLineBox* aLine, PRBool* aKeepReflowGoing, PRBool aDamageDirtyArea) { nsresult rv = NS_OK; NS_ABORT_IF_FALSE(aLine->GetChildCount(), "reflowing empty line"); // Setup the line-layout for the new line aState.mCurrentLine = aLine; aLine->ClearDirty(); // Now that we know what kind of line we have, reflow it nsRect oldCombinedArea; aLine->GetCombinedArea(&oldCombinedArea); if (aLine->IsBlock()) { rv = ReflowBlockFrame(aState, aLine, aKeepReflowGoing); // We expect blocks to damage any area inside their bounds that is // dirty; however, if the frame changes size or position then we // need to do some repainting if (aDamageDirtyArea) { nsRect lineCombinedArea; aLine->GetCombinedArea(&lineCombinedArea); if ((oldCombinedArea.x != lineCombinedArea.x) || (oldCombinedArea.y != lineCombinedArea.y)) { // The block has moved, and so to be safe we need to repaint // XXX We need to improve on this... nsRect dirtyRect; dirtyRect.UnionRect(oldCombinedArea, lineCombinedArea); #ifdef NOISY_BLOCK_INVALIDATE printf("%p invalidate 6 (%d, %d, %d, %d)\n", this, dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height); #endif Invalidate(aState.mPresContext, dirtyRect); } else { if (oldCombinedArea.width != lineCombinedArea.width) { nsRect dirtyRect; // Just damage the vertical strip that was either added or went // away dirtyRect.x = PR_MIN(oldCombinedArea.XMost(), lineCombinedArea.XMost()); dirtyRect.y = lineCombinedArea.y; dirtyRect.width = PR_MAX(oldCombinedArea.XMost(), lineCombinedArea.XMost()) - dirtyRect.x; dirtyRect.height = PR_MAX(oldCombinedArea.height, lineCombinedArea.height); #ifdef NOISY_BLOCK_INVALIDATE printf("%p invalidate 7 (%d, %d, %d, %d)\n", this, dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height); #endif Invalidate(aState.mPresContext, dirtyRect); } if (oldCombinedArea.height != lineCombinedArea.height) { nsRect dirtyRect; // Just damage the horizontal strip that was either added or went // away dirtyRect.x = lineCombinedArea.x; dirtyRect.y = PR_MIN(oldCombinedArea.YMost(), lineCombinedArea.YMost()); dirtyRect.width = PR_MAX(oldCombinedArea.width, lineCombinedArea.width); dirtyRect.height = PR_MAX(oldCombinedArea.YMost(), lineCombinedArea.YMost()) - dirtyRect.y; #ifdef NOISY_BLOCK_INVALIDATE printf("%p invalidate 8 (%d, %d, %d, %d)\n", this, dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height); #endif Invalidate(aState.mPresContext, dirtyRect); } } } } else { aLine->SetLineWrapped(PR_FALSE); // If we're supposed to update the maximum width, then we'll need to reflow // the line with an unconstrained width (which will give us the new maximum // width), then we'll reflow it again with the constrained width. // We only do this if this is a beginning line, i.e., don't do this for // lines associated with content that line wrapped (see ReflowDirtyLines() // for details). // XXX This approach doesn't work when floaters are involved in which case // we'll either need to recover the floater state that applies to the // unconstrained reflow or keep it around in a separate space manager... PRBool isBeginningLine = !aState.mPrevLine || !aState.mPrevLine->IsLineWrapped(); if (aState.GetFlag(BRS_COMPUTEMAXWIDTH) && isBeginningLine) { nscoord oldY = aState.mY; nscoord oldPrevBottomMargin = aState.mPrevBottomMargin; PRBool oldUnconstrainedWidth = aState.GetFlag(BRS_UNCONSTRAINEDWIDTH); // First reflow the line with an unconstrained width. When doing this // we need to set the block reflow state's "mUnconstrainedWidth" variable // to PR_TRUE so if we encounter a placeholder and then reflow its // associated floater we don't end up resetting the line's right edge and // have it think the width is unconstrained... aState.SetFlag(BRS_UNCONSTRAINEDWIDTH, PR_TRUE); ReflowInlineFrames(aState, aLine, aKeepReflowGoing, aDamageDirtyArea, PR_TRUE); aState.mY = oldY; aState.mPrevBottomMargin = oldPrevBottomMargin; aState.SetFlag(BRS_UNCONSTRAINEDWIDTH, oldUnconstrainedWidth); // Update the line's maximum width aLine->mMaximumWidth = aLine->mBounds.XMost(); #ifdef NOISY_MAXIMUM_WIDTH printf("nsBlockFrame::ReflowLine block %p line %p setting aLine.mMaximumWidth to %d\n", this, aLine, aLine->mMaximumWidth); #endif aState.UpdateMaximumWidth(aLine->mMaximumWidth); // Remove any floaters associated with the line from the space // manager aLine->RemoveFloatersFromSpaceManager(aState.mSpaceManager); // Now reflow the line again this time without having it compute // the maximum width or max-element-size. // Note: we need to reset both member variables, because the inline // code examines mComputeMaxElementSize and if there is a placeholder // on this line the code to reflow the floater looks at both... nscoord oldComputeMaxElementSize = aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE); nscoord oldComputeMaximumWidth = aState.GetFlag(BRS_COMPUTEMAXWIDTH); aState.SetFlag(BRS_COMPUTEMAXELEMENTSIZE, PR_FALSE); aState.SetFlag(BRS_COMPUTEMAXWIDTH, PR_FALSE); rv = ReflowInlineFrames(aState, aLine, aKeepReflowGoing, aDamageDirtyArea); aState.SetFlag(BRS_COMPUTEMAXELEMENTSIZE, oldComputeMaxElementSize); aState.SetFlag(BRS_COMPUTEMAXWIDTH, oldComputeMaximumWidth); } else { rv = ReflowInlineFrames(aState, aLine, aKeepReflowGoing, aDamageDirtyArea); if (NS_SUCCEEDED(rv)) { if (aState.GetFlag(BRS_COMPUTEMAXWIDTH)) { #ifdef NOISY_MAXIMUM_WIDTH printf("nsBlockFrame::ReflowLine block %p line %p setting aLine.mMaximumWidth to %d\n", this, aLine, aLine->mMaximumWidth); #endif aState.UpdateMaximumWidth(aLine->mMaximumWidth); } if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) { #ifdef NOISY_MAX_ELEMENT_SIZE printf("nsBlockFrame::ReflowLine block %p line %p setting aLine.mMaxElementWidth to %d\n", this, aLine, aLine->mMaxElementWidth); #endif aState.UpdateMaxElementSize(nsSize(aLine->mMaxElementWidth, aLine->mBounds.height)); } } } // We don't really know what changed in the line, so use the union // of the old and new combined areas if (aDamageDirtyArea) { nsRect combinedArea; aLine->GetCombinedArea(&combinedArea); nsRect dirtyRect; dirtyRect.UnionRect(oldCombinedArea, combinedArea); #ifdef NOISY_BLOCK_INVALIDATE printf("%p invalidate because aDamageDirtyArea is true (%d, %d, %d, %d)\n", this, dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height); #endif Invalidate(aState.mPresContext, dirtyRect); } } return rv; } /** * Pull frame from the next available location (one of our lines or * one of our next-in-flows lines). */ nsresult nsBlockFrame::PullFrame(nsBlockReflowState& aState, nsLineBox* aLine, PRBool aDamageDeletedLines, nsIFrame*& aFrameResult) { nsresult rv = NS_OK; PRBool stopPulling; aFrameResult = nsnull; // First check our remaining lines while (nsnull != aLine->mNext) { rv = PullFrame(aState, aLine, &aLine->mNext, PR_FALSE, aDamageDeletedLines, aFrameResult, stopPulling); if (NS_FAILED(rv) || stopPulling) { return rv; } } // Pull frames from the next-in-flow(s) until we can't nsBlockFrame* nextInFlow = aState.mNextInFlow; while (nsnull != nextInFlow) { nsLineBox* line = nextInFlow->mLines; if (nsnull == line) { nextInFlow = (nsBlockFrame*) nextInFlow->mNextInFlow; aState.mNextInFlow = nextInFlow; continue; } rv = PullFrame(aState, aLine, &nextInFlow->mLines, PR_TRUE, aDamageDeletedLines, aFrameResult, stopPulling); if (NS_FAILED(rv) || stopPulling) { return rv; } } return rv; } /** * Try to pull a frame out of a line pointed at by aFromList. If a * frame is pulled then aPulled will be set to PR_TRUE. In addition, * if aUpdateGeometricParent is set then the pulled frames geometric * parent will be updated (e.g. when pulling from a next-in-flows line * list). * * Note: pulling a frame from a line that is a place-holder frame * doesn't automatically remove the corresponding floater from the * line's floater array. This happens indirectly: either the line gets * emptied (and destroyed) or the line gets reflowed (because we mark * it dirty) and the code at the top of ReflowLine empties the * array. So eventually, it will be removed, just not right away. */ nsresult nsBlockFrame::PullFrame(nsBlockReflowState& aState, nsLineBox* aLine, nsLineBox** aFromList, PRBool aUpdateGeometricParent, PRBool aDamageDeletedLines, nsIFrame*& aFrameResult, PRBool& aStopPulling) { nsLineBox* fromLine = *aFromList; NS_ABORT_IF_FALSE(fromLine, "bad line to pull from"); NS_ABORT_IF_FALSE(fromLine->GetChildCount(), "empty line"); NS_ABORT_IF_FALSE(aLine->GetChildCount(), "empty line"); if (fromLine->IsBlock()) { // If our line is not empty and the child in aFromLine is a block // then we cannot pull up the frame into this line. In this case // we stop pulling. aStopPulling = PR_TRUE; aFrameResult = nsnull; } else { // Take frame from fromLine nsIFrame* frame = fromLine->mFirstChild; aLine->SetChildCount(aLine->GetChildCount() + 1); PRInt32 fromLineChildCount = fromLine->GetChildCount(); if (0 != --fromLineChildCount) { // Mark line dirty now that we pulled a child fromLine->SetChildCount(fromLineChildCount); fromLine->MarkDirty(); frame->GetNextSibling(&fromLine->mFirstChild); } else { // Free up the fromLine now that it's empty // Its bounds might need to be redrawn, though. if (aDamageDeletedLines) { Invalidate(aState.mPresContext, fromLine->mBounds); } *aFromList = fromLine->mNext; aState.FreeLineBox(fromLine); } // Change geometric parents if (aUpdateGeometricParent) { // Before we set the new parent frame get the current parent nsIFrame* oldParentFrame; frame->GetParent(&oldParentFrame); frame->SetParent(this); // When pushing and pulling frames we need to check for whether any // views need to be reparented NS_ASSERTION(oldParentFrame != this, "unexpected parent frame"); nsHTMLContainerFrame::ReparentFrameView(aState.mPresContext, frame, oldParentFrame, this); // The frame is being pulled from a next-in-flow; therefore we // need to add it to our sibling list. if (nsnull != aState.mPrevChild) { aState.mPrevChild->SetNextSibling(frame); } frame->SetNextSibling(nsnull); } // Stop pulling because we found a frame to pull aStopPulling = PR_TRUE; aFrameResult = frame; #ifdef DEBUG VerifyLines(PR_TRUE); #endif } return NS_OK; } static void PlaceFrameView(nsIPresContext* aPresContext, nsIFrame* aFrame) { nsIView* view; aFrame->GetView(aPresContext, &view); if (view) { nsContainerFrame::SyncFrameViewAfterReflow(aPresContext, aFrame, view, nsnull); } else { nsContainerFrame::PositionChildViews(aPresContext, aFrame); } } void nsBlockFrame::SlideLine(nsBlockReflowState& aState, nsLineBox* aLine, nscoord aDY) { // Adjust line state aLine->SlideBy(aDY); // Adjust the frames in the line nsIFrame* kid = aLine->mFirstChild; if (!kid) { return; } if (aLine->IsBlock()) { nsRect r; kid->GetRect(r); if (aDY) { r.y += aDY; kid->SetRect(aState.mPresContext, r); } // Make sure the frame's view and any child views are updated ::PlaceFrameView(aState.mPresContext, kid); // If the child has any floaters that impact the space-manager, // place them now so that they are present in the space-manager // again (they were removed by the space-manager's frame when // the reflow began). nsBlockFrame* bf; nsresult rv = kid->QueryInterface(kBlockFrameCID, (void**) &bf); if (NS_SUCCEEDED(rv)) { // Translate spacemanager to the child blocks upper-left corner // so that when it places its floaters (which are relative to // it) the right coordinates are used. Note that we have already // been translated by our border+padding so factor that in to // get the right translation. const nsMargin& bp = aState.BorderPadding(); nscoord dx = r.x - bp.left; nscoord dy = r.y - bp.top; aState.mSpaceManager->Translate(dx, dy); bf->UpdateSpaceManager(aState.mPresContext, aState.mSpaceManager); aState.mSpaceManager->Translate(-dx, -dy); } } else { // Adjust the Y coordinate of the frames in the line. // Note: we need to re-position views even if aDY is 0, because // one of our parent frames may have moved and so the view's position // relative to its parent may have changed nsRect r; PRInt32 n = aLine->GetChildCount(); while (--n >= 0) { if (aDY) { kid->GetRect(r); r.y += aDY; kid->SetRect(aState.mPresContext, r); } // Make sure the frame's view and any child views are updated ::PlaceFrameView(aState.mPresContext, kid); kid->GetNextSibling(&kid); } } } nsresult nsBlockFrame::UpdateSpaceManager(nsIPresContext* aPresContext, nsISpaceManager* aSpaceManager) { nsLineBox* line = mLines; while (nsnull != line) { // Place the floaters in the spacemanager if (line->HasFloaters()) { nsFloaterCache* fc = line->GetFirstFloater(); while (fc) { nsIFrame* floater = fc->mPlaceholder->GetOutOfFlowFrame(); aSpaceManager->AddRectRegion(floater, fc->mRegion); #ifdef NOISY_SPACEMANAGER nscoord tx, ty; aSpaceManager->GetTranslation(tx, ty); nsFrame::ListTag(stdout, this); printf(": UpdateSpaceManager: AddRectRegion: txy=%d,%d {%d,%d,%d,%d}\n", tx, ty, fc->mRegion.x, fc->mRegion.y, fc->mRegion.width, fc->mRegion.height); #endif fc = fc->Next(); } } // Tell kids about the move too if (line->mFirstChild && line->IsBlock()) { // If the child has any floaters that impact the space-manager, // place them now so that they are present in the space-manager // again (they were removed by the space-manager's frame when // the reflow began). nsBlockFrame* bf; nsresult rv = line->mFirstChild->QueryInterface(kBlockFrameCID, (void**) &bf); if (NS_SUCCEEDED(rv)) { nsPoint origin; bf->GetOrigin(origin); // Translate spacemanager to the child blocks upper-left // corner so that when it places its floaters (which are // relative to it) the right coordinates are used. aSpaceManager->Translate(origin.x, origin.y); bf->UpdateSpaceManager(aPresContext, aSpaceManager); aSpaceManager->Translate(-origin.x, -origin.y); } } line = line->mNext; } return NS_OK; } NS_IMETHODIMP nsBlockFrame::AttributeChanged(nsIPresContext* aPresContext, nsIContent* aChild, PRInt32 aNameSpaceID, nsIAtom* aAttribute, PRInt32 aHint) { nsresult rv = nsBlockFrameSuper::AttributeChanged(aPresContext, aChild, aNameSpaceID, aAttribute, aHint); if (NS_OK != rv) { return rv; } if (nsHTMLAtoms::start == aAttribute) { // XXX Not sure if this is necessary anymore RenumberLists(aPresContext); nsCOMPtr shell; aPresContext->GetShell(getter_AddRefs(shell)); nsIReflowCommand* reflowCmd; rv = NS_NewHTMLReflowCommand(&reflowCmd, this, nsIReflowCommand::ContentChanged, nsnull, aAttribute); if (NS_SUCCEEDED(rv)) { shell->AppendReflowCommand(reflowCmd); NS_RELEASE(reflowCmd); } } else if (nsHTMLAtoms::value == aAttribute) { const nsStyleDisplay* styleDisplay; GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) styleDisplay); if (NS_STYLE_DISPLAY_LIST_ITEM == styleDisplay->mDisplay) { nsIFrame* nextAncestor = mParent; nsBlockFrame* blockParent = nsnull; // Search for the closest ancestor that's a block frame. We // make the assumption that all related list items share a // common block parent. while (nextAncestor != nsnull) { if (NS_OK == nextAncestor->QueryInterface(kBlockFrameCID, (void**)&blockParent)) { break; } nextAncestor->GetParent(&nextAncestor); } // Tell the enclosing block frame to renumber list items within // itself if (nsnull != blockParent) { // XXX Not sure if this is necessary anymore blockParent->RenumberLists(aPresContext); nsCOMPtr shell; aPresContext->GetShell(getter_AddRefs(shell)); nsIReflowCommand* reflowCmd; rv = NS_NewHTMLReflowCommand(&reflowCmd, blockParent, nsIReflowCommand::ContentChanged, nsnull, aAttribute); if (NS_SUCCEEDED(rv)) { shell->AppendReflowCommand(reflowCmd); NS_RELEASE(reflowCmd); } } } } return rv; } nsBlockFrame* nsBlockFrame::FindFollowingBlockFrame(nsIFrame* aFrame) { nsBlockFrame* followingBlockFrame = nsnull; nsIFrame* frame = aFrame; for (;;) { nsIFrame* nextFrame; frame->GetNextSibling(&nextFrame); if (nsnull != nextFrame) { const nsStyleDisplay* display; nextFrame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display); if (NS_STYLE_DISPLAY_BLOCK == display->mDisplay) { followingBlockFrame = (nsBlockFrame*) nextFrame; break; } else if (NS_STYLE_DISPLAY_INLINE == display->mDisplay) { // If it's a text-frame and it's just whitespace and we are // in a normal whitespace situation THEN skip it and keep // going... // XXX WRITE ME! } frame = nextFrame; } else break; } return followingBlockFrame; } PRBool nsBlockFrame::ShouldApplyTopMargin(nsBlockReflowState& aState, nsLineBox* aLine) { if (aState.GetFlag(BRS_APPLYTOPMARGIN)) { // Apply short-circuit check to avoid searching the line list return PR_TRUE; } if (!aState.IsAdjacentWithTop()) { // If we aren't at the top Y coordinate then something of non-zero // height must have been placed. Therefore the childs top-margin // applies. aState.SetFlag(BRS_APPLYTOPMARGIN, PR_TRUE); return PR_TRUE; } // Determine if this line is "essentially" the first line nsLineBox* line = mLines; while (line != aLine) { if (line->IsBlock()) { // A line which preceeds aLine contains a block; therefore the // top margin applies. aState.SetFlag(BRS_APPLYTOPMARGIN, PR_TRUE); return PR_TRUE; } else if (line->HasFloaters()) { // A line which preceeds aLine is not empty therefore the top // margin applies. aState.SetFlag(BRS_APPLYTOPMARGIN, PR_TRUE); return PR_TRUE; } line = line->mNext; } // The line being reflowed is "essentially" the first line in the // block. Therefore its top-margin will be collapsed by the // generational collapsing logic with its parent (us). return PR_FALSE; } nsIFrame* nsBlockFrame::GetTopBlockChild() { nsIFrame* firstChild = mLines ? mLines->mFirstChild : nsnull; if (firstChild) { if (mLines->IsBlock()) { // Winner return firstChild; } // If the first line is not a block line then the second line must // be a block line otherwise the top child can't be a block. nsLineBox* next = mLines->mNext; if ((nsnull == next) || !next->IsBlock()) { // There is no line after the first line or its not a block so // don't bother trying to skip over the first line. return nsnull; } // The only time we can skip over the first line and pretend its // not there is if the line contains only compressed // whitespace. If white-space is significant to this frame then we // can't skip over the line. const nsStyleText* styleText; GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&) styleText); if ((NS_STYLE_WHITESPACE_PRE == styleText->mWhiteSpace) || (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == styleText->mWhiteSpace)) { // Whitespace is significant return nsnull; } // See if each frame is a text frame that contains nothing but // whitespace. PRInt32 n = mLines->GetChildCount(); while (--n >= 0) { nsIContent* content; nsresult rv = firstChild->GetContent(&content); if (NS_FAILED(rv) || (nsnull == content)) { return nsnull; } nsITextContent* tc; rv = content->QueryInterface(kITextContentIID, (void**) &tc); NS_RELEASE(content); if (NS_FAILED(rv) || (nsnull == tc)) { return nsnull; } PRBool isws = PR_FALSE; tc->IsOnlyWhitespace(&isws); NS_RELEASE(tc); if (!isws) { return nsnull; } firstChild->GetNextSibling(&firstChild); } // If we make it to this point then every frame on the first line // was compressible white-space. Since we already know that the // second line contains a block, that block is the // top-block-child. return next->mFirstChild; } return nsnull; } nsresult nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState, nsLineBox* aLine, PRBool* aKeepReflowGoing) { NS_PRECONDITION(*aKeepReflowGoing, "bad caller"); nsresult rv = NS_OK; nsIFrame* frame = aLine->mFirstChild; // Prepare the block reflow engine const nsStyleDisplay* display; frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display); nsBlockReflowContext brc(aState.mPresContext, aState.mReflowState, aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE), aState.GetFlag(BRS_COMPUTEMAXWIDTH)); brc.SetNextRCFrame(aState.mNextRCFrame); // See if we should apply the top margin. If the block frame being // reflowed is a continuation (non-null prev-in-flow) then we don't // apply its top margin because its not significant. Otherwise, dig // deeper. PRBool applyTopMargin = PR_FALSE; nsIFrame* framePrevInFlow; frame->GetPrevInFlow(&framePrevInFlow); if (nsnull == framePrevInFlow) { applyTopMargin = ShouldApplyTopMargin(aState, aLine); } // Clear past floaters before the block if the clear style is not none PRUint8 breakType = display->mBreakType; aLine->SetBreakType(breakType); if (NS_STYLE_CLEAR_NONE != breakType) { PRBool alsoApplyTopMargin = aState.ClearPastFloaters(breakType); if (alsoApplyTopMargin) { applyTopMargin = PR_TRUE; } #ifdef NOISY_VERTICAL_MARGINS ListTag(stdout); printf(": y=%d child ", aState.mY); ListTag(stdout, frame); printf(" has clear of %d => %s, mPrevBottomMargin=%d\n", breakType, applyTopMargin ? "applyTopMargin" : "nope", aState.mPrevBottomMargin); #endif } nscoord topMargin = 0; if (applyTopMargin) { // Precompute the blocks top margin value so that we can get the // correct available space (there might be a floater that's // already been placed below the aState.mPrevBottomMargin // Setup a reflowState to get the style computed margin-top value // The availSpace here is irrelevant to our needs - all we want // out if this setup is the margin-top value which doesn't depend // on the childs available space. nsSize availSpace(aState.mContentArea.width, NS_UNCONSTRAINEDSIZE); nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState, frame, availSpace); // Now compute the collapsed margin-top value topMargin = nsBlockReflowContext::ComputeCollapsedTopMargin(aState.mPresContext, reflowState); // And collapse it with the previous bottom margin to get the final value topMargin = nsBlockReflowContext::MaxMargin(topMargin, aState.mPrevBottomMargin); // Temporarily advance the running Y value so that the // GetAvailableSpace method will return the right available // space. This undone as soon as the margin is computed. aState.mY += topMargin; } // Compute the available space for the block aState.GetAvailableSpace(); #ifdef REALLY_NOISY_REFLOW printf("setting line %p isImpacted to %s\n", aLine, aState.IsImpactedByFloater()?"true":"false"); #endif PRBool isImpacted = aState.IsImpactedByFloater() ? PR_TRUE : PR_FALSE; aLine->SetLineIsImpactedByFloater(isImpacted); nsSplittableType splitType = NS_FRAME_NOT_SPLITTABLE; frame->IsSplittable(splitType); nsRect availSpace; aState.ComputeBlockAvailSpace(frame, splitType, display, availSpace); // Now put the Y coordinate back and flow the block letting the // block reflow context compute the same top margin value we just // computed (sigh). if (topMargin) { aState.mY -= topMargin; availSpace.y -= topMargin; if (NS_UNCONSTRAINEDSIZE != availSpace.height) { availSpace.height += topMargin; } } // Reflow the block into the available space nsReflowStatus frameReflowStatus=NS_FRAME_COMPLETE; nsMargin computedOffsets; rv = brc.ReflowBlock(frame, availSpace, applyTopMargin, aState.mPrevBottomMargin, aState.IsAdjacentWithTop(), computedOffsets, frameReflowStatus); if (PR_TRUE==brc.BlockShouldInvalidateItself()) { Invalidate(aState.mPresContext, mRect); } if (frame == aState.mNextRCFrame) { // NULL out mNextRCFrame so if we reflow it again we don't think it's still // an incremental reflow aState.mNextRCFrame = nsnull; } if (NS_FAILED(rv)) { return rv; } aState.mPrevChild = frame; #if defined(REFLOW_STATUS_COVERAGE) RecordReflowStatus(PR_TRUE, frameReflowStatus); #endif if (NS_INLINE_IS_BREAK_BEFORE(frameReflowStatus)) { // None of the child block fits. PushLines(aState); *aKeepReflowGoing = PR_FALSE; aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; } else { // Note: line-break-after a block is a nop // Try to place the child block PRBool isAdjacentWithTop = aState.IsAdjacentWithTop(); nscoord collapsedBottomMargin; nsRect combinedArea(0,0,0,0); *aKeepReflowGoing = brc.PlaceBlock(isAdjacentWithTop, computedOffsets, &collapsedBottomMargin, aLine->mBounds, combinedArea); if (aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { // Mark the line as block so once we known the final shrink wrap width // we can reflow the block to the correct size // XXX We don't always need to do this... aLine->MarkDirty(); aState.SetFlag(BRS_NEEDRESIZEREFLOW, PR_TRUE); } if (aState.GetFlag(BRS_UNCONSTRAINEDWIDTH) || aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { // Add the right margin to the line's bounnds. That way it will be taken into // account when we compute our shrink wrap size nscoord marginRight = brc.GetMargin().right; if (marginRight != NS_UNCONSTRAINEDSIZE) { aLine->mBounds.width += marginRight; } } aLine->SetCombinedArea(combinedArea); if (*aKeepReflowGoing) { // Some of the child block fit // Advance to new Y position nscoord newY = aLine->mBounds.YMost(); aState.mY = newY; aLine->SetCarriedOutBottomMargin(brc.GetCarriedOutBottomMargin()); // Continue the block frame now if it didn't completely fit in // the available space. if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) { PRBool madeContinuation; rv = CreateContinuationFor(aState, aLine, frame, madeContinuation); if (NS_FAILED(rv)) { return rv; } // Push continuation to a new line, but only if we actually // made one. if (madeContinuation) { frame->GetNextSibling(&frame); nsLineBox* line = aState.NewLineBox(frame, 1, PR_TRUE); if (nsnull == line) { return NS_ERROR_OUT_OF_MEMORY; } line->mNext = aLine->mNext; aLine->mNext = line; // Do not count the continuation child on the line it used // to be on aLine->SetChildCount(aLine->GetChildCount() - 1); } // Advance to next line since some of the block fit. That way // only the following lines will be pushed. aState.mPrevLine = aLine; PushLines(aState); aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; *aKeepReflowGoing = PR_FALSE; // The bottom margin for a block is only applied on the last // flow block. Since we just continued the child block frame, // we know that line->mFirstChild is not the last flow block // therefore zero out the running margin value. #ifdef NOISY_VERTICAL_MARGINS ListTag(stdout); printf(": reflow incomplete, frame="); nsFrame::ListTag(stdout, frame); printf(" prevBottomMargin=%d, setting to zero\n", aState.mPrevBottomMargin); #endif aState.mPrevBottomMargin = 0; } else { #ifdef NOISY_VERTICAL_MARGINS ListTag(stdout); printf(": reflow complete for "); nsFrame::ListTag(stdout, frame); printf(" prevBottomMargin=%d collapsedBottomMargin=%d\n", aState.mPrevBottomMargin, collapsedBottomMargin); #endif aState.mPrevBottomMargin = collapsedBottomMargin; } #ifdef NOISY_VERTICAL_MARGINS ListTag(stdout); printf(": frame="); nsFrame::ListTag(stdout, frame); printf(" carriedOutBottomMargin=%d collapsedBottomMargin=%d => %d\n", aLine->GetCarriedOutBottomMargin(), collapsedBottomMargin, aState.mPrevBottomMargin); #endif // Post-process the "line" nsSize maxElementSize(0, 0); if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) { maxElementSize = brc.GetMaxElementSize(); if (aState.IsImpactedByFloater() && (NS_FRAME_SPLITTABLE_NON_RECTANGULAR != splitType)) { // Add in floater impacts to the lines max-element-size, but // only if the block element isn't one of us (otherwise the // floater impacts will be counted twice). ComputeLineMaxElementSize(aState, aLine, &maxElementSize); } } // If we asked the block to update its maximum width, then record the // updated value in the line, and update the current maximum width if (aState.GetFlag(BRS_COMPUTEMAXWIDTH)) { aLine->mMaximumWidth = brc.GetMaximumWidth(); // need to add in margin on block's reported max width (see bug 35964) const nsMargin& margin = brc.GetMargin(); aLine->mMaximumWidth += margin.left + margin.right; #ifdef NOISY_MAXIMUM_WIDTH printf("nsBlockFrame::ReflowBlockFrame parent block %p line %p aLine->mMaximumWidth set to brc.GetMaximumWidth %d, updating aState.mMaximumWidth\n", this, aLine, aLine->mMaximumWidth); #endif aState.UpdateMaximumWidth(aLine->mMaximumWidth); } PostPlaceLine(aState, aLine, maxElementSize); // Place the "marker" (bullet) frame. // // According to the CSS2 spec, section 12.6.1, the "marker" box // participates in the height calculation of the list-item box's // first line box. // // There are exactly two places a bullet can be placed: near the // first or second line. Its only placed on the second line in a // rare case: an empty first line followed by a second line that // contains a block (example:
  • \n

    ... ). This is where // the second case can happen. if (HaveOutsideBullet() && ((aLine == mLines) || ((0 == mLines->mBounds.height) && (aLine == mLines->mNext)))) { // Reflow the bullet nsHTMLReflowMetrics metrics(nsnull); ReflowBullet(aState, metrics); // For bullets that are placed next to a child block, there will // be no correct ascent value. Therefore, make one up... nscoord ascent = 0; const nsStyleFont* font; frame->GetStyleData(eStyleStruct_Font, (const nsStyleStruct*&) font); nsIRenderingContext& rc = *aState.mReflowState.rendContext; rc.SetFont(font->mFont); nsIFontMetrics* fm; rv = rc.GetFontMetrics(fm); if (NS_SUCCEEDED(rv) && (nsnull != fm)) { fm->GetMaxAscent(ascent); NS_RELEASE(fm); } rv = NS_OK; // Tall bullets won't look particularly nice here... nsRect bbox; mBullet->GetRect(bbox); nscoord bulletTopMargin = applyTopMargin ? collapsedBottomMargin : 0; bbox.y = aState.BorderPadding().top + ascent - metrics.ascent + bulletTopMargin; mBullet->SetRect(aState.mPresContext, bbox); } } else { // None of the block fits. Determine the correct reflow status. if (aLine == mLines) { // If it's our very first line then we need to be pushed to // our parents next-in-flow. Therefore, return break-before // status for our reflow status. aState.mReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); } else { // Push the line that didn't fit and any lines that follow it // to our next-in-flow. PushLines(aState); aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; } } } #ifdef DEBUG VerifyLines(PR_TRUE); #endif return rv; } #define LINE_REFLOW_OK 0 #define LINE_REFLOW_STOP 1 #define LINE_REFLOW_REDO 2 nsresult nsBlockFrame::ReflowInlineFrames(nsBlockReflowState& aState, nsLineBox* aLine, PRBool* aKeepReflowGoing, PRBool aDamageDirtyArea, PRBool aUpdateMaximumWidth) { nsresult rv = NS_OK; *aKeepReflowGoing = PR_TRUE; #ifdef DEBUG PRInt32 spins = 0; #endif PRUint8 lineReflowStatus = LINE_REFLOW_REDO; while (LINE_REFLOW_REDO == lineReflowStatus) { // Prevent overflowing limited thread stacks by creating // nsLineLayout from the heap when the frame tree depth gets // large. if (aState.mReflowState.mReflowDepth > 30) {//XXX layout-tune.h? rv = DoReflowInlineFramesMalloc(aState, aLine, aKeepReflowGoing, &lineReflowStatus, aUpdateMaximumWidth, aDamageDirtyArea); } else { rv = DoReflowInlineFramesAuto(aState, aLine, aKeepReflowGoing, &lineReflowStatus, aUpdateMaximumWidth, aDamageDirtyArea); } if (NS_FAILED(rv)) { break; } #ifdef DEBUG spins++; if (1000 == spins) { ListTag(stdout); printf(": yikes! spinning on a line over 1000 times!\n"); NS_ABORT(); } #endif } return rv; } nsresult nsBlockFrame::DoReflowInlineFramesMalloc(nsBlockReflowState& aState, nsLineBox* aLine, PRBool* aKeepReflowGoing, PRUint8* aLineReflowStatus, PRBool aUpdateMaximumWidth, PRBool aDamageDirtyArea) { nsLineLayout* ll = new nsLineLayout(aState.mPresContext, aState.mReflowState.mSpaceManager, &aState.mReflowState, aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)); if (!ll) { return NS_ERROR_OUT_OF_MEMORY; } ll->Init(&aState, aState.mMinLineHeight, aState.mLineNumber); nsresult rv = DoReflowInlineFrames(aState, *ll, aLine, aKeepReflowGoing, aLineReflowStatus, aUpdateMaximumWidth, aDamageDirtyArea); ll->EndLineReflow(); delete ll; return rv; } nsresult nsBlockFrame::DoReflowInlineFramesAuto(nsBlockReflowState& aState, nsLineBox* aLine, PRBool* aKeepReflowGoing, PRUint8* aLineReflowStatus, PRBool aUpdateMaximumWidth, PRBool aDamageDirtyArea) { nsLineLayout lineLayout(aState.mPresContext, aState.mReflowState.mSpaceManager, &aState.mReflowState, aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)); lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber); nsresult rv = DoReflowInlineFrames(aState, lineLayout, aLine, aKeepReflowGoing, aLineReflowStatus, aUpdateMaximumWidth, aDamageDirtyArea); lineLayout.EndLineReflow(); return rv; } nsresult nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState, nsLineLayout& aLineLayout, nsLineBox* aLine, PRBool* aKeepReflowGoing, PRUint8* aLineReflowStatus, PRBool aUpdateMaximumWidth, PRBool aDamageDirtyArea) { // Forget all of the floaters on the line aLine->FreeFloaters(aState.mFloaterCacheFreeList); aState.mFloaterCombinedArea.SetRect(0, 0, 0, 0); aState.mRightFloaterCombinedArea.SetRect(0, 0, 0, 0); // Setup initial coordinate system for reflowing the inline frames // into. Apply a previous block frame's bottom margin first. aState.mY += aState.mPrevBottomMargin; aState.GetAvailableSpace(); PRBool impactedByFloaters = aState.IsImpactedByFloater() ? PR_TRUE : PR_FALSE; aLine->SetLineIsImpactedByFloater(impactedByFloaters); #ifdef REALLY_NOISY_REFLOW printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n", this, impactedByFloaters); #endif const nsMargin& borderPadding = aState.BorderPadding(); nscoord x = aState.mAvailSpaceRect.x + borderPadding.left; nscoord availWidth = aState.mAvailSpaceRect.width; nscoord availHeight; if (aState.GetFlag(BRS_UNCONSTRAINEDHEIGHT)) { availHeight = NS_UNCONSTRAINEDSIZE; } else { /* XXX get the height right! */ availHeight = aState.mAvailSpaceRect.height; } if (aUpdateMaximumWidth) { availWidth = NS_UNCONSTRAINEDSIZE; } aLineLayout.BeginLineReflow(x, aState.mY, availWidth, availHeight, impactedByFloaters, PR_FALSE /*XXX isTopOfPage*/); // XXX Unfortunately we need to know this before reflowing the first // inline frame in the line. FIX ME. if ((0 == aLineLayout.GetLineNumber()) && (NS_BLOCK_HAS_FIRST_LETTER_STYLE & mState)) { aLineLayout.SetFirstLetterStyleOK(PR_TRUE); } // Reflow the frames that are already on the line first nsresult rv = NS_OK; PRUint8 lineReflowStatus = LINE_REFLOW_OK; PRInt32 i; nsIFrame* frame = aLine->mFirstChild; aLine->SetHasPercentageChild(PR_FALSE); // To be set by ReflowInlineFrame below for (i = 0; i < aLine->GetChildCount(); i++) { rv = ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus); if (NS_FAILED(rv)) { return rv; } if (LINE_REFLOW_OK != lineReflowStatus) { // It is possible that one or more of next lines are empty // (because of DeleteChildsNextInFlow). If so, delete them now // in case we are finished. nsLineBox* nextLine = aLine->mNext; while ((nsnull != nextLine) && (0 == nextLine->GetChildCount())) { // XXX Is this still necessary now that DeleteChildsNextInFlow // uses DoRemoveFrame? aLine->mNext = nextLine->mNext; NS_ASSERTION(nsnull == nextLine->mFirstChild, "bad empty line"); aState.FreeLineBox(nextLine); nextLine = aLine->mNext; } break; } frame->GetNextSibling(&frame); } // Pull frames and reflow them until we can't while (LINE_REFLOW_OK == lineReflowStatus) { rv = PullFrame(aState, aLine, aDamageDirtyArea, frame); if (NS_FAILED(rv)) { return rv; } if (nsnull == frame) { break; } while (LINE_REFLOW_OK == lineReflowStatus) { PRInt32 oldCount = aLine->GetChildCount(); rv = ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus); if (NS_FAILED(rv)) { return rv; } if (aLine->GetChildCount() != oldCount) { // We just created a continuation for aFrame AND its going // to end up on this line (e.g. :first-letter // situation). Therefore we have to loop here before trying // to pull another frame. frame->GetNextSibling(&frame); } else { break; } } } if (LINE_REFLOW_REDO == lineReflowStatus) { // This happens only when we have a line that is impacted by // floaters and the first element in the line doesn't fit with // the floaters. // // What we do is to advance past the first floater we find and // then reflow the line all over again. NS_ASSERTION(aState.IsImpactedByFloater(), "redo line on totally empty line"); NS_ASSERTION(NS_UNCONSTRAINEDSIZE != aState.mAvailSpaceRect.height, "unconstrained height on totally empty line"); aState.mY += aState.mAvailSpaceRect.height; // XXX: a small optimization can be done here when paginating: // if the new Y coordinate is past the end of the block then // push the line and return now instead of later on after we are // past the floater. } else { // If we are propogating out a break-before status then there is // no point in placing the line. if (!NS_INLINE_IS_BREAK_BEFORE(aState.mReflowStatus)) { rv = PlaceLine(aState, aLineLayout, aLine, aKeepReflowGoing, aUpdateMaximumWidth); } } *aLineReflowStatus = lineReflowStatus; return rv; } /** * Reflow an inline frame. The reflow status is mapped from the frames * reflow status to the lines reflow status (not to our reflow status). * The line reflow status is simple: PR_TRUE means keep placing frames * on the line; PR_FALSE means don't (the line is done). If the line * has some sort of breaking affect then aLine's break-type will be set * to something other than NS_STYLE_CLEAR_NONE. */ nsresult nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState, nsLineLayout& aLineLayout, nsLineBox* aLine, nsIFrame* aFrame, PRUint8* aLineReflowStatus) { *aLineReflowStatus = LINE_REFLOW_OK; // If it's currently ok to be reflowing in first-letter style then // we must be about to reflow a frame that has first-letter style. PRBool reflowingFirstLetter = aLineLayout.GetFirstLetterStyleOK(); #ifdef NOISY_FIRST_LETTER ListTag(stdout); printf(": reflowing "); nsFrame::ListTag(stdout, aFrame); printf(" reflowingFirstLetter=%s\n", reflowingFirstLetter ? "on" : "off"); #endif // Remember if we have a percentage aware child on this line if (IsPercentageAwareChild(aFrame)) { aLine->SetHasPercentageChild(PR_TRUE); } // Reflow the inline frame nsReflowStatus frameReflowStatus; PRBool pushedFrame; nsresult rv = aLineLayout.ReflowFrame(aFrame, &aState.mNextRCFrame, frameReflowStatus, nsnull, pushedFrame); if (aFrame == aState.mNextRCFrame) { // NULL out mNextRCFrame so if we reflow it again we don't think it's still // an incremental reflow aState.mNextRCFrame = nsnull; } if (NS_FAILED(rv)) { return rv; } #ifdef REALLY_NOISY_REFLOW_CHILD nsFrame::ListTag(stdout, aFrame); printf(": status=%x\n", frameReflowStatus); #endif #if defined(REFLOW_STATUS_COVERAGE) RecordReflowStatus(PR_FALSE, frameReflowStatus); #endif // Send post-reflow notification aState.mPrevChild = aFrame; /* XXX This is where we need to add logic to handle some odd behavior. For one thing, we should usually place at least one thing next to a left floater, even when that floater takes up all the width on a line. see bug 22496 */ // Process the child frames reflow status. There are 5 cases: // complete, not-complete, break-before, break-after-complete, // break-after-not-complete. There are two situations: we are a // block or we are an inline. This makes a total of 10 cases // (fortunately, there is some overlap). aLine->SetBreakType(NS_STYLE_CLEAR_NONE); if (NS_INLINE_IS_BREAK(frameReflowStatus)) { // Always abort the line reflow (because a line break is the // minimal amount of break we do). *aLineReflowStatus = LINE_REFLOW_STOP; // XXX what should aLine's break-type be set to in all these cases? PRUint8 breakType = NS_INLINE_GET_BREAK_TYPE(frameReflowStatus); NS_ASSERTION(breakType != NS_STYLE_CLEAR_NONE, "bad break type"); NS_ASSERTION(NS_STYLE_CLEAR_PAGE != breakType, "no page breaks yet"); if (NS_INLINE_IS_BREAK_BEFORE(frameReflowStatus)) { // Break-before cases. if (aFrame == aLine->mFirstChild) { // If we break before the first frame on the line then we must // be trying to place content where theres no room (e.g. on a // line with wide floaters). Inform the caller to reflow the // line after skipping past a floater. *aLineReflowStatus = LINE_REFLOW_REDO; } else { // It's not the first child on this line so go ahead and split // the line. We will see the frame again on the next-line. rv = SplitLine(aState, aLineLayout, aLine, aFrame); if (NS_FAILED(rv)) { return rv; } // If we're splitting the line because the frame didn't fit and it // was pushed, then mark the line as having word wrapped. We need to // know that if we're shrink wrapping our width if (pushedFrame) { aLine->SetLineWrapped(PR_TRUE); } } } else { // Break-after cases if (breakType == NS_STYLE_CLEAR_LINE) { if (!aLineLayout.GetLineEndsInBR()) { breakType = NS_STYLE_CLEAR_NONE; } } aLine->SetBreakType(breakType); if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) { // Create a continuation for the incomplete frame. Note that the // frame may already have a continuation. PRBool madeContinuation; rv = CreateContinuationFor(aState, aLine, aFrame, madeContinuation); if (NS_FAILED(rv)) { return rv; } // Remember that the line has wrapped aLine->SetLineWrapped(PR_TRUE); } // Split line, but after the frame just reflowed nsIFrame* nextFrame; aFrame->GetNextSibling(&nextFrame); rv = SplitLine(aState, aLineLayout, aLine, nextFrame); if (NS_FAILED(rv)) { return rv; } // Mark next line dirty in case SplitLine didn't end up // pushing any frames. nsLineBox* next = aLine->mNext; if ((nsnull != next) && !next->IsBlock()) { next->MarkDirty(); } } } else if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) { // Frame is not-complete, no special breaking status // Create a continuation for the incomplete frame. Note that the // frame may already have a continuation. PRBool madeContinuation; rv = CreateContinuationFor(aState, aLine, aFrame, madeContinuation); if (NS_FAILED(rv)) { return rv; } // Remember that the line has wrapped aLine->SetLineWrapped(PR_TRUE); // If we are reflowing the first letter frame then don't split the // line and don't stop the line reflow... PRBool splitLine = !reflowingFirstLetter; if (reflowingFirstLetter) { nsCOMPtr frameType; aFrame->GetFrameType(getter_AddRefs(frameType)); if ((nsLayoutAtoms::inlineFrame == frameType.get()) || (nsLayoutAtoms::lineFrame == frameType.get())) { splitLine = PR_TRUE; } } if (splitLine) { // Split line after the current frame *aLineReflowStatus = LINE_REFLOW_STOP; aFrame->GetNextSibling(&aFrame); rv = SplitLine(aState, aLineLayout, aLine, aFrame); if (NS_FAILED(rv)) { return rv; } // Mark next line dirty in case SplitLine didn't end up // pushing any frames. nsLineBox* next = aLine->mNext; if ((nsnull != next) && !next->IsBlock()) { next->MarkDirty(); } } } return NS_OK; } /** * Create a continuation, if necessary, for aFrame. Place it on the * same line that aFrame is on. Set aMadeNewFrame to PR_TRUE if a * new frame is created. */ nsresult nsBlockFrame::CreateContinuationFor(nsBlockReflowState& aState, nsLineBox* aLine, nsIFrame* aFrame, PRBool& aMadeNewFrame) { aMadeNewFrame = PR_FALSE; nsresult rv; nsIFrame* nextInFlow; rv = CreateNextInFlow(aState.mPresContext, this, aFrame, nextInFlow); if (NS_FAILED(rv)) { return rv; } if (nsnull != nextInFlow) { aMadeNewFrame = PR_TRUE; aLine->SetChildCount(aLine->GetChildCount() + 1); } #ifdef DEBUG VerifyLines(PR_FALSE); #endif return rv; } nsresult nsBlockFrame::SplitLine(nsBlockReflowState& aState, nsLineLayout& aLineLayout, nsLineBox* aLine, nsIFrame* aFrame) { NS_ABORT_IF_FALSE(aLine->IsInline(), "illegal SplitLine on block line"); PRInt32 pushCount = aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount(); NS_ABORT_IF_FALSE(pushCount >= 0, "bad push count"); #ifdef DEBUG if (gNoisyReflow) { nsFrame::IndentBy(stdout, gNoiseIndent); printf("split line: from line=%p pushCount=%d aFrame=", aLine, pushCount); if (aFrame) { nsFrame::ListTag(stdout, aFrame); } else { printf("(null)"); } printf("\n"); if (gReallyNoisyReflow) { aLine->List(aState.mPresContext, stdout, gNoiseIndent+1); } } #endif if (0 != pushCount) { NS_ABORT_IF_FALSE(aLine->GetChildCount() > pushCount, "bad push"); NS_ABORT_IF_FALSE(nsnull != aFrame, "whoops"); // Put frames being split out into their own line nsLineBox* newLine = aState.NewLineBox(aFrame, pushCount, PR_FALSE); if (!newLine) { return NS_ERROR_OUT_OF_MEMORY; } newLine->mNext = aLine->mNext; aLine->mNext = newLine; aLine->SetChildCount(aLine->GetChildCount() - pushCount); #ifdef DEBUG if (gReallyNoisyReflow) { newLine->List(aState.mPresContext, stdout, gNoiseIndent+1); } #endif // Let line layout know that some frames are no longer part of its // state. aLineLayout.SplitLineTo(aLine->GetChildCount()); #ifdef DEBUG VerifyLines(PR_TRUE); #endif } return NS_OK; } PRBool nsBlockFrame::ShouldJustifyLine(nsBlockReflowState& aState, nsLineBox* aLine) { nsLineBox* next = aLine->mNext; while (nsnull != next) { // There is another line if (0 != next->GetChildCount()) { // If the next line is a block line then we must not justify // this line because it means that this line is the last in a // group of inline lines. return !next->IsBlock(); } // The next line is empty, try the next one next = next->mNext; } // XXX Not sure about this part // Try our next-in-flows lines to answer the question nsBlockFrame* nextInFlow = (nsBlockFrame*) mNextInFlow; while (nsnull != nextInFlow) { nsLineBox* line = nextInFlow->mLines; while (nsnull != line) { if (0 != line->GetChildCount()) { return !line->IsBlock(); } line = line->mNext; } nextInFlow = (nsBlockFrame*) nextInFlow->mNextInFlow; } // This is the last line - so don't allow justification return PR_FALSE; } nsresult nsBlockFrame::PlaceLine(nsBlockReflowState& aState, nsLineLayout& aLineLayout, nsLineBox* aLine, PRBool* aKeepReflowGoing, PRBool aUpdateMaximumWidth) { nsresult rv = NS_OK; // Trim extra white-space from the line before placing the frames PRBool trimmed = aLineLayout.TrimTrailingWhiteSpace(); aLine->SetTrimmed(trimmed); // Vertically align the frames on this line. // // According to the CSS2 spec, section 12.6.1, the "marker" box // participates in the height calculation of the list-item box's // first line box. // // There are exactly two places a bullet can be placed: near the // first or second line. Its only placed on the second line in a // rare case: an empty first line followed by a second line that // contains a block (example:

  • \n

    ... ). // // For this code, only the first case is possible because this // method is used for placing a line of inline frames. If the rare // case is happening then the worst that will happen is that the // bullet frame will be reflowed twice. PRBool addedBullet = PR_FALSE; if (HaveOutsideBullet() && (aLine == mLines) && (!aLineLayout.IsZeroHeight() || !aLine->mNext)) { nsHTMLReflowMetrics metrics(nsnull); ReflowBullet(aState, metrics); aLineLayout.AddBulletFrame(mBullet, metrics); addedBullet = PR_TRUE; } nsSize maxElementSize; #ifndef MOZ_MATHML aLineLayout.VerticalAlignFrames(aLine, maxElementSize); #else nscoord lineAscent; aLineLayout.VerticalAlignFrames(aLine, maxElementSize, lineAscent); // Our ascent is the ascent of our first line if (aLine == mLines) { mAscent = lineAscent; } #endif // See if we're shrink wrapping the width if (aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { // When determining the line's width we also need to include any // right floaters that impact us. This represents the shrink wrap // width of the line if (aState.IsImpactedByFloater() && !aLine->IsLineWrapped()) { NS_ASSERTION(aState.mContentArea.width >= aState.mAvailSpaceRect.XMost(), "bad state"); aLine->mBounds.width += aState.mContentArea.width - aState.mAvailSpaceRect.XMost(); } } #ifdef DEBUG { static nscoord lastHeight = 0; if (CRAZY_HEIGHT(aLine->mBounds.y)) { lastHeight = aLine->mBounds.y; if (abs(aLine->mBounds.y - lastHeight) > CRAZY_H/10) { nsFrame::ListTag(stdout); printf(": line=%p y=%d line.bounds.height=%d\n", aLine, aLine->mBounds.y, aLine->mBounds.height); } } else { lastHeight = 0; } } #endif // Only block frames horizontally align their children because // inline frames "shrink-wrap" around their children (therefore // there is no extra horizontal space). const nsStyleText* styleText = (const nsStyleText*) mStyleContext->GetStyleData(eStyleStruct_Text); PRBool allowJustify = NS_STYLE_TEXT_ALIGN_JUSTIFY == styleText->mTextAlign && !aLineLayout.GetLineEndsInBR() && ShouldJustifyLine(aState, aLine); PRBool successful = aLineLayout.HorizontalAlignFrames(aLine->mBounds, allowJustify, aState.GetFlag(BRS_SHRINKWRAPWIDTH)); if (!successful) { // Mark the line dirty and then later once we've determined the width // we can do the horizontal alignment aLine->MarkDirty(); aState.SetFlag(BRS_NEEDRESIZEREFLOW, PR_TRUE); } nsRect combinedArea; aLineLayout.RelativePositionFrames(combinedArea); aLine->SetCombinedArea(combinedArea); if (addedBullet) { aLineLayout.RemoveBulletFrame(mBullet); } // Inline lines do not have margins themselves; however they are // impacted by prior block margins. If this line ends up having some // height then we zero out the previous bottom margin value that was // already applied to the line's starting Y coordinate. Otherwise we // leave it be so that the previous blocks bottom margin can be // collapsed with a block that follows. nscoord newY; if (aLine->mBounds.height > 0) { // This line has some height. Therefore the application of the // previous-bottom-margin should stick. aState.mPrevBottomMargin = 0; newY = aLine->mBounds.YMost(); } else { // Don't let the previous-bottom-margin value affect the newY // coordinate (it was applied in ReflowInlineFrames speculatively) // since the line is empty. nscoord dy = -aState.mPrevBottomMargin; newY = aState.mY + dy; aLine->SlideBy(dy); #ifdef MOZ_MATHML // keep our ascent in sync if (mLines == aLine) { mAscent += dy; } #endif } // See if the line fit. If it doesn't we need to push it. Our first // line will always fit. if ((mLines != aLine) && (newY > aState.mBottomEdge)) { // Push this line and all of it's children and anything else that // follows to our next-in-flow PushLines(aState); // Stop reflow and whack the reflow status if reflow hasn't // already been stopped. if (*aKeepReflowGoing) { NS_ASSERTION(NS_FRAME_COMPLETE == aState.mReflowStatus, "lost reflow status"); aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; *aKeepReflowGoing = PR_FALSE; } return rv; } aState.mY = newY; if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) { #ifdef NOISY_MAX_ELEMENT_SIZE IndentBy(stdout, GetDepth()); if (NS_UNCONSTRAINEDSIZE == aState.mReflowState.availableWidth) { printf("PASS1 "); } ListTag(stdout); printf(": line.floaters=%s band.floaterCount=%d\n", //aLine->mFloaters.NotEmpty() ? "yes" : "no", aState.mHaveRightFloaters ? "(have right floaters)" : "", aState.mBand.GetFloaterCount()); #endif if (0 != aState.mBand.GetFloaterCount()) { // Add in floater impacts to the lines max-element-size ComputeLineMaxElementSize(aState, aLine, &maxElementSize); } } // If we're reflowing the line just to incrementally update the // maximum width, then don't post-place the line. It's doing work we // don't need, and it will update things like aState.mKidXMost that // we don't want updated... if (aUpdateMaximumWidth) { // However, we do need to update the max-element-size if requested if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) { aState.UpdateMaxElementSize(maxElementSize); // We also cache the max element width in the line. This is needed for // incremental reflow aLine->mMaxElementWidth = maxElementSize.width; #ifdef NOISY_MAX_ELEMENT_SIZE printf ("nsBlockFrame::PlaceLine: %p setting MES for line %p to %d\n", this, aLine, maxElementSize.width); #endif } } else { PostPlaceLine(aState, aLine, maxElementSize); } // Add the already placed current-line floaters to the line aLine->AppendFloaters(aState.mCurrentLineFloaters); // Any below current line floaters to place? if (aState.mBelowCurrentLineFloaters.NotEmpty()) { // Reflow the below-current-line floaters, then add them to the // lines floater list. aState.PlaceBelowCurrentLineFloaters(aState.mBelowCurrentLineFloaters); aLine->AppendFloaters(aState.mBelowCurrentLineFloaters); } // When a line has floaters, factor them into the combined-area // computations. if (aLine->HasFloaters()) { // Combine the floater combined area (stored in aState) and the // value computed by the line layout code. nsRect lineCombinedArea; aLine->GetCombinedArea(&lineCombinedArea); #ifdef NOISY_COMBINED_AREA ListTag(stdout); printf(": lineCA=%d,%d,%d,%d floaterCA=%d,%d,%d,%d\n", lineCombinedArea.x, lineCombinedArea.y, lineCombinedArea.width, lineCombinedArea.height, aState.mFloaterCombinedArea.x, aState.mFloaterCombinedArea.y, aState.mFloaterCombinedArea.width, aState.mFloaterCombinedArea.height); #endif CombineRects(aState.mFloaterCombinedArea, lineCombinedArea); if (aState.mHaveRightFloaters && (aState.GetFlag(BRS_UNCONSTRAINEDWIDTH) || aState.GetFlag(BRS_SHRINKWRAPWIDTH))) { // We are reflowing in an unconstrained situation or shrink wrapping and // have some right floaters. They were placed at the infinite right edge // which will cause the combined area to be unusable. // // To solve this issue, we pretend that the right floaters ended up just // past the end of the line. Note that the right floater combined area // we computed as we were going will have as its X coordinate the left // most edge of all the right floaters. Therefore, to accomplish our goal // all we do is set that X value to the lines XMost value. #ifdef NOISY_COMBINED_AREA printf(" ==> rightFloaterCA=%d,%d,%d,%d lineXMost=%d\n", aState.mRightFloaterCombinedArea.x, aState.mRightFloaterCombinedArea.y, aState.mRightFloaterCombinedArea.width, aState.mRightFloaterCombinedArea.height, aLine->mBounds.XMost()); #endif aState.mRightFloaterCombinedArea.x = aLine->mBounds.XMost(); CombineRects(aState.mRightFloaterCombinedArea, lineCombinedArea); if (aState.GetFlag(BRS_SHRINKWRAPWIDTH)) { // Mark the line dirty so we come back and re-place the floater once // the shrink wrap width is determined aLine->MarkDirty(); aState.SetFlag(BRS_NEEDRESIZEREFLOW, PR_TRUE); } } aLine->SetCombinedArea(lineCombinedArea); #ifdef NOISY_COMBINED_AREA printf(" ==> final lineCA=%d,%d,%d,%d\n", lineCombinedArea.x, lineCombinedArea.y, lineCombinedArea.width, lineCombinedArea.height); #endif aState.mHaveRightFloaters = PR_FALSE; } // Apply break-after clearing if necessary PRUint8 breakType = aLine->GetBreakType(); switch (breakType) { case NS_STYLE_CLEAR_LEFT: case NS_STYLE_CLEAR_RIGHT: case NS_STYLE_CLEAR_LEFT_AND_RIGHT: aState.ClearFloaters(aState.mY, breakType); break; } return rv; } // Compute the line's max-element-size by adding into the raw value // computed by reflowing the contents of the line (aMaxElementSize) // the impact of floaters on this line or the preceeding lines. void nsBlockFrame::ComputeLineMaxElementSize(nsBlockReflowState& aState, nsLineBox* aLine, nsSize* aMaxElementSize) { nscoord maxWidth, maxHeight; aState.mBand.GetMaxElementSize(aState.mPresContext, &maxWidth, &maxHeight); #ifdef NOISY_MAX_ELEMENT_SIZE IndentBy(stdout, GetDepth()); if (NS_UNCONSTRAINEDSIZE == aState.mReflowState.availableWidth) { printf("PASS1 "); } ListTag(stdout); printf(": maxFloaterSize=%d,%d\n", maxWidth, maxHeight); #endif // If the floaters are wider than the content, then use the maximum // floater width as the maximum width. // // It used to be the case that we would always place some content // next to a floater, regardless of the amount of available space // after subtracing off the floaters sizes. This can lead to content // overlapping floaters, so we no longer do this (and pass CSS2's // conformance tests). This is not how navigator 4-1 used to do // things. if (maxWidth > aMaxElementSize->width) { aMaxElementSize->width = maxWidth; } // Only update the max-element-size's height value if the floater is // part of the current line. if (aLine->HasFloaters()) { // If the maximum-height of the tallest floater is larger than the // maximum-height of the content then update the max-element-size // height if (maxHeight > aMaxElementSize->height) { aMaxElementSize->height = maxHeight; } } #ifdef NOISY_MAX_ELEMENT_SIZE printf ("nsBlockFrame::ComputeLineMaxElementSize: %p returning MES %d\n", this, aMaxElementSize->width); #endif } void nsBlockFrame::PostPlaceLine(nsBlockReflowState& aState, nsLineBox* aLine, const nsSize& aMaxElementSize) { // If it's inline elements, then make sure the views are correctly // positioned and sized if (aLine->IsInline()) { nsIFrame* frame = aLine->mFirstChild; for (PRInt32 i = 0; i < aLine->GetChildCount(); i++) { ::PlaceFrameView(aState.mPresContext, frame); frame->GetNextSibling(&frame); } } // Update max-element-size if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) { aState.UpdateMaxElementSize(aMaxElementSize); // We also cache the max element width in the line. This is needed for // incremental reflow aLine->mMaxElementWidth = aMaxElementSize.width; #ifdef NOISY_MAX_ELEMENT_SIZE printf ("nsBlockFrame::PostPlaceLine: %p setting line %p MES %d\n", this, aLine, aMaxElementSize.width); #endif } // If this is an unconstrained reflow, then cache the line width in the // line. We'll need this during incremental reflow if we're asked to // calculate the maximum width if (aState.GetFlag(BRS_UNCONSTRAINEDWIDTH)) { #ifdef NOISY_MAXIMUM_WIDTH printf("nsBlockFrame::PostPlaceLine during UC Reflow of block %p line %p caching max width %d\n", this, aLine, aLine->mBounds.XMost()); #endif aLine->mMaximumWidth = aLine->mBounds.XMost(); } // Update xmost nscoord xmost = aLine->mBounds.XMost(); #ifdef DEBUG if (CRAZY_WIDTH(xmost)) { ListTag(stdout); printf(": line=%p xmost=%d\n", aLine, xmost); } #endif // If we're shrink wrapping our width and the line was wrapped, // then make sure we take up all of the available width if (aState.GetFlag(BRS_SHRINKWRAPWIDTH) && aLine->IsLineWrapped()) { aState.mKidXMost = aState.BorderPadding().left + aState.mContentArea.width; #ifdef NOISY_KIDXMOST printf("%p PostPlaceLine A aState.mKidXMost=%d\n", this, aState.mKidXMost); #endif } else if (xmost > aState.mKidXMost) { aState.mKidXMost = xmost; #ifdef NOISY_KIDXMOST printf("%p PostPlaceLine B aState.mKidXMost=%d\n", this, aState.mKidXMost); #endif } } void nsBlockFrame::PushLines(nsBlockReflowState& aState) { NS_ASSERTION(nsnull != aState.mPrevLine, "bad push"); nsLineBox* lastLine = aState.mPrevLine; nsLineBox* nextLine = lastLine->mNext; if (nextLine) { lastLine->mNext = nsnull; SetOverflowLines(aState.mPresContext, nextLine); // Mark all the overflow lines dirty so that they get reflowed when // they are pulled up by our next-in-flow. while (nsnull != nextLine) { nextLine->MarkDirty(); nextLine = nextLine->mNext; } } // Break frame sibling list nsIFrame* lastFrame = lastLine->LastChild(); lastFrame->SetNextSibling(nsnull); #ifdef DEBUG VerifyOverflowSituation(aState.mPresContext); #endif } PRBool nsBlockFrame::DrainOverflowLines(nsIPresContext* aPresContext) { #ifdef DEBUG VerifyOverflowSituation(aPresContext); #endif PRBool drained = PR_FALSE; nsLineBox* overflowLines; // First grab the prev-in-flows overflow lines nsBlockFrame* prevBlock = (nsBlockFrame*) mPrevInFlow; if (nsnull != prevBlock) { overflowLines = prevBlock->GetOverflowLines(aPresContext, PR_TRUE); if (nsnull != overflowLines) { drained = PR_TRUE; // Make all the frames on the overflow line list mine nsIFrame* lastFrame = nsnull; nsIFrame* frame = overflowLines->mFirstChild; while (nsnull != frame) { frame->SetParent(this); // When pushing and pulling frames we need to check for whether any // views need to be reparented nsHTMLContainerFrame::ReparentFrameView(aPresContext, frame, prevBlock, this); // Get the next frame lastFrame = frame; frame->GetNextSibling(&frame); } // Join the line lists if (nsnull == mLines) { mLines = overflowLines; } else { // Join the sibling lists together lastFrame->SetNextSibling(mLines->mFirstChild); // Place overflow lines at the front of our line list nsLineBox* lastLine = nsLineBox::LastLine(overflowLines); lastLine->mNext = mLines; mLines = overflowLines; } } } // Now grab our own overflow lines overflowLines = GetOverflowLines(aPresContext, PR_TRUE); if (overflowLines) { // This can happen when we reflow and not everything fits and then // we are told to reflow again before a next-in-flow is created // and reflows. nsLineBox* lastLine = nsLineBox::LastLine(mLines); if (nsnull == lastLine) { // if we had no lines before the drain operation mLines = overflowLines; // set our mLines to the overflow } else { // otherwise, append the overflow to the mLines list lastLine->mNext = overflowLines; nsIFrame* lastFrame = lastLine->LastChild(); lastFrame->SetNextSibling(overflowLines->mFirstChild); } drained = PR_TRUE; } return drained; } nsLineBox* nsBlockFrame::GetOverflowLines(nsIPresContext* aPresContext, PRBool aRemoveProperty) const { nsCOMPtr presShell; aPresContext->GetShell(getter_AddRefs(presShell)); if (presShell) { nsCOMPtr frameManager; presShell->GetFrameManager(getter_AddRefs(frameManager)); if (frameManager) { PRUint32 options = 0; void* value; if (aRemoveProperty) { options |= NS_IFRAME_MGR_REMOVE_PROP; } frameManager->GetFrameProperty((nsIFrame*)this, nsLayoutAtoms::overflowLinesProperty, options, &value); return (nsLineBox*)value; } } return nsnull; } // Destructor function for the overflowLines frame property static void DestroyOverflowLines(nsIPresContext* aPresContext, nsIFrame* aFrame, nsIAtom* aPropertyName, void* aPropertyValue) { if (aPropertyValue) { nsLineBox* lines = (nsLineBox*) aPropertyValue; nsLineBox::DeleteLineList(aPresContext, lines); } } nsresult nsBlockFrame::SetOverflowLines(nsIPresContext* aPresContext, nsLineBox* aOverflowFrames) { nsCOMPtr presShell; nsresult rv = NS_ERROR_FAILURE; aPresContext->GetShell(getter_AddRefs(presShell)); if (presShell) { nsCOMPtr frameManager; presShell->GetFrameManager(getter_AddRefs(frameManager)); if (frameManager) { rv = frameManager->SetFrameProperty(this, nsLayoutAtoms::overflowLinesProperty, aOverflowFrames, DestroyOverflowLines); // Verify that we didn't overwrite an existing overflow list NS_ASSERTION(rv != NS_IFRAME_MGR_PROP_OVERWRITTEN, "existing overflow list"); } } return rv; } ////////////////////////////////////////////////////////////////////// // Frame list manipulation routines nsIFrame* nsBlockFrame::LastChild() { if (mLines) { nsLineBox* line = nsLineBox::LastLine(mLines); return line->LastChild(); } return nsnull; } NS_IMETHODIMP nsBlockFrame::AppendFrames(nsIPresContext* aPresContext, nsIPresShell& aPresShell, nsIAtom* aListName, nsIFrame* aFrameList) { if (nsnull == aFrameList) { return NS_OK; } if (nsLayoutAtoms::absoluteList == aListName) { return mAbsoluteContainer.AppendFrames(this, aPresContext, aPresShell, aListName, aFrameList); } else if (nsLayoutAtoms::floaterList == aListName) { // XXX we don't *really* care about this right now because we are // BuildFloaterList ing still mFloaters.AppendFrames(nsnull, aFrameList); return NS_OK; } else if (nsnull != aListName) { return NS_ERROR_INVALID_ARG; } // Find the proper last-child for where the append should go nsIFrame* lastKid = nsnull; nsLineBox* lastLine = nsLineBox::LastLine(mLines); if (lastLine) { lastKid = lastLine->LastChild(); } // Add frames after the last child #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": append "); nsFrame::ListTag(stdout, aFrameList); if (lastKid) { printf(" after "); nsFrame::ListTag(stdout, lastKid); } printf("\n"); #endif nsresult rv = AddFrames(aPresContext, aFrameList, lastKid); if (NS_SUCCEEDED(rv)) { // Ask the parent frame to reflow me. ReflowDirtyChild(&aPresShell, nsnull); } return rv; } NS_IMETHODIMP nsBlockFrame::InsertFrames(nsIPresContext* aPresContext, nsIPresShell& aPresShell, nsIAtom* aListName, nsIFrame* aPrevFrame, nsIFrame* aFrameList) { if (nsLayoutAtoms::absoluteList == aListName) { return mAbsoluteContainer.InsertFrames(this, aPresContext, aPresShell, aListName, aPrevFrame, aFrameList); } else if (nsLayoutAtoms::floaterList == aListName) { // XXX we don't *really* care about this right now because we are // BuildFloaterList'ing still mFloaters.AppendFrames(nsnull, aFrameList); return NS_OK; } else if (nsnull != aListName) { return NS_ERROR_INVALID_ARG; } #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": insert "); nsFrame::ListTag(stdout, aFrameList); if (aPrevFrame) { printf(" after "); nsFrame::ListTag(stdout, aPrevFrame); } printf("\n"); #endif nsresult rv = AddFrames(aPresContext, aFrameList, aPrevFrame); if (NS_SUCCEEDED(rv)) { // Ask the parent frame to reflow me. ReflowDirtyChild(&aPresShell, nsnull); } return rv; } nsresult nsBlockFrame::AddFrames(nsIPresContext* aPresContext, nsIFrame* aFrameList, nsIFrame* aPrevSibling) { if (nsnull == aFrameList) { return NS_OK; } nsCOMPtr presShell; aPresContext->GetShell(getter_AddRefs(presShell)); // Attempt to find the line that contains the previous sibling nsLineBox* prevSibLine = nsnull; PRInt32 prevSiblingIndex = -1; if (aPrevSibling) { // Find the line that contains the previous sibling prevSibLine = nsLineBox::FindLineContaining(mLines, aPrevSibling, &prevSiblingIndex); NS_ASSERTION(nsnull != prevSibLine, "prev sibling not in line list"); if (nsnull == prevSibLine) { // Note: defensive code! FindLineContaining must not return // null in this case, so if it does... aPrevSibling = nsnull; } } // Find the frame following aPrevSibling so that we can join up the // two lists of frames. nsIFrame* prevSiblingNextFrame = nsnull; if (aPrevSibling) { aPrevSibling->GetNextSibling(&prevSiblingNextFrame); // Split line containing aPrevSibling in two if the insertion // point is somewhere in the middle of the line. PRInt32 rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1; if (rem) { // Split the line in two where the frame(s) are being inserted. nsLineBox* line = NS_NewLineBox(presShell, prevSiblingNextFrame, rem, PR_FALSE); if (!line) { return NS_ERROR_OUT_OF_MEMORY; } line->mNext = prevSibLine->mNext; prevSibLine->mNext = line; prevSibLine->SetChildCount(prevSibLine->GetChildCount() - rem); prevSibLine->MarkDirty(); } // Now (partially) join the sibling lists together aPrevSibling->SetNextSibling(aFrameList); } else if (mLines) { prevSiblingNextFrame = mLines->mFirstChild; } // Walk through the new frames being added and update the line data // structures to fit. nsIFrame* newFrame = aFrameList; while (newFrame) { PRBool isBlock = nsLineLayout::TreatFrameAsBlock(newFrame); // If the frame is a block frame, or if there is no previous line // or if the previous line is a block line then make a new line. if (isBlock || !prevSibLine || prevSibLine->IsBlock()) { // Create a new line for the frame and add its line to the line // list. nsLineBox* line = NS_NewLineBox(presShell, newFrame, 1, isBlock); if (!line) { return NS_ERROR_OUT_OF_MEMORY; } if (prevSibLine) { // Append new line after prevSibLine line->mNext = prevSibLine->mNext; prevSibLine->mNext = line; } else { // New line is going before the other lines line->mNext = mLines; mLines = line; } prevSibLine = line; } else { prevSibLine->SetChildCount(prevSibLine->GetChildCount() + 1); prevSibLine->MarkDirty(); } aPrevSibling = newFrame; newFrame->GetNextSibling(&newFrame); } if (prevSiblingNextFrame) { // Connect the last new frame to the remainder of the sibling list aPrevSibling->SetNextSibling(prevSiblingNextFrame); } #ifdef DEBUG VerifyLines(PR_TRUE); #endif return NS_OK; } void nsBlockFrame::FixParentAndView(nsIPresContext* aPresContext, nsIFrame* aFrame) { while (aFrame) { nsIFrame* oldParent; aFrame->GetParent(&oldParent); aFrame->SetParent(this); if (this != oldParent) { nsHTMLContainerFrame::ReparentFrameView(aPresContext, aFrame, oldParent, this); aPresContext->ReParentStyleContext(aFrame, mStyleContext); } aFrame->GetNextSibling(&aFrame); } } NS_IMETHODIMP nsBlockFrame::RemoveFrame(nsIPresContext* aPresContext, nsIPresShell& aPresShell, nsIAtom* aListName, nsIFrame* aOldFrame) { nsresult rv = NS_OK; #ifdef NOISY_REFLOW_REASON ListTag(stdout); printf(": remove "); nsFrame::ListTag(stdout, aOldFrame); printf("\n"); #endif if (nsLayoutAtoms::absoluteList == aListName) { return mAbsoluteContainer.RemoveFrame(this, aPresContext, aPresShell, aListName, aOldFrame); } else if (nsLayoutAtoms::floaterList == aListName) { // Remove floater from the floater list first mFloaters.RemoveFrame(aOldFrame); // Find which line contains the floater nsLineBox* line = mLines; while (nsnull != line) { if (line->IsInline() && line->RemoveFloater(aOldFrame)) { aOldFrame->Destroy(aPresContext); goto found_it; } line = line->mNext; } found_it: // Mark every line at and below the line where the floater was dirty while (nsnull != line) { line->MarkDirty(); line = line->mNext; } } else if (nsnull != aListName) { rv = NS_ERROR_INVALID_ARG; } else { rv = DoRemoveFrame(aPresContext, aOldFrame); } if (NS_SUCCEEDED(rv)) { // Ask the parent frame to reflow me. ReflowDirtyChild(&aPresShell, nsnull); } return rv; } nsresult nsBlockFrame::DoRemoveFrame(nsIPresContext* aPresContext, nsIFrame* aDeletedFrame) { nsCOMPtr presShell; aPresContext->GetShell(getter_AddRefs(presShell)); // Find the line and the previous sibling that contains // deletedFrame; we also find the pointer to the line. nsBlockFrame* flow = this; nsLineBox** linep = &flow->mLines; nsLineBox* line = flow->mLines; nsLineBox* prevLine = nsnull; nsIFrame* prevSibling = nsnull; while (nsnull != line) { nsIFrame* frame = line->mFirstChild; PRInt32 n = line->GetChildCount(); while (--n >= 0) { if (frame == aDeletedFrame) { goto found_frame; } prevSibling = frame; frame->GetNextSibling(&frame); } linep = &line->mNext; prevLine = line; line = line->mNext; } found_frame:; #ifdef NS_DEBUG NS_ASSERTION(nsnull != line, "can't find deleted frame in lines"); if (nsnull != prevSibling) { nsIFrame* tmp; prevSibling->GetNextSibling(&tmp); NS_ASSERTION(tmp == aDeletedFrame, "bad prevSibling"); } #endif // Remove frame and all of its continuations while (nsnull != aDeletedFrame) { while ((nsnull != line) && (nsnull != aDeletedFrame)) { #ifdef NS_DEBUG nsIFrame* parent; aDeletedFrame->GetParent(&parent); NS_ASSERTION(flow == parent, "messed up delete code"); NS_ASSERTION(line->Contains(aDeletedFrame), "frame not in line"); #endif // See if the frame being deleted is the last one on the line PRBool isLastFrameOnLine = PR_FALSE; if (1 == line->GetChildCount()) { isLastFrameOnLine = PR_TRUE; } else if (line->LastChild() == aDeletedFrame) { isLastFrameOnLine = PR_TRUE; } // Remove aDeletedFrame from the line nsIFrame* nextFrame; aDeletedFrame->GetNextSibling(&nextFrame); if (line->mFirstChild == aDeletedFrame) { line->mFirstChild = nextFrame; } if (prevLine && !prevLine->IsBlock()) { // Since we just removed a frame that follows some inline // frames, we need to reflow the previous line. prevLine->MarkDirty(); } // Take aDeletedFrame out of the sibling list. Note that // prevSibling will only be nsnull when we are deleting the very // first frame. if (nsnull != prevSibling) { prevSibling->SetNextSibling(nextFrame); } // Destroy frame; capture its next-in-flow first in case we need // to destroy that too. nsIFrame* nextInFlow; aDeletedFrame->GetNextInFlow(&nextInFlow); nsSplittableType st; aDeletedFrame->IsSplittable(st); if (NS_FRAME_NOT_SPLITTABLE != st) { nsSplittableFrame::RemoveFromFlow(aDeletedFrame); } #ifdef NOISY_REMOVE_FRAME printf("DoRemoveFrame: prevLine=%p line=%p frame=", prevLine, line); nsFrame::ListTag(stdout, aDeletedFrame); printf(" prevSibling=%p nextInFlow=%p\n", prevSibling, nextInFlow); #endif aDeletedFrame->Destroy(aPresContext); aDeletedFrame = nextInFlow; // If line is empty, remove it now nsLineBox* next = line->mNext; PRInt32 lineChildCount = line->GetChildCount(); if (0 == --lineChildCount) { *linep = next; line->mNext = nsnull; // Invalidate the space taken up by the line. // XXX We need to do this if we're removing a frame as a result of // a call to RemoveFrame(), but we may not need to do this in all // cases... nsRect lineCombinedArea; line->GetCombinedArea(&lineCombinedArea); #ifdef NOISY_BLOCK_INVALIDATE printf("%p invalidate 10 (%d, %d, %d, %d)\n", this, lineCombinedArea.x, lineCombinedArea.y, lineCombinedArea.width, lineCombinedArea.height); #endif Invalidate(aPresContext, lineCombinedArea); line->Destroy(presShell); line = next; } else { // Make the line that just lost a frame dirty line->SetChildCount(lineChildCount); line->MarkDirty(); // If we just removed the last frame on the line then we need // to advance to the next line. if (isLastFrameOnLine) { prevLine = line; linep = &line->mNext; line = next; } } // See if we should keep looking in the current flow's line list. if (nsnull != aDeletedFrame) { if (aDeletedFrame != nextFrame) { // The deceased frames continuation is not the next frame in // the current flow's frame list. Therefore we know that the // continuation is in a different parent. So break out of // the loop so that we advance to the next parent. #ifdef NS_DEBUG nsIFrame* checkParent; aDeletedFrame->GetParent(&checkParent); NS_ASSERTION(checkParent != flow, "strange continuation"); #endif break; } } } // Advance to next flow block if the frame has more continuations if (nsnull != aDeletedFrame) { flow = (nsBlockFrame*) flow->mNextInFlow; NS_ASSERTION(nsnull != flow, "whoops, continuation without a parent"); prevLine = nsnull; line = flow->mLines; linep = &flow->mLines; prevSibling = nsnull; } } #ifdef DEBUG VerifyLines(PR_TRUE); #endif return NS_OK; } void nsBlockFrame::DeleteChildsNextInFlow(nsIPresContext* aPresContext, nsIFrame* aChild) { NS_PRECONDITION(IsChild(aPresContext, aChild), "bad geometric parent"); nsIFrame* nextInFlow; aChild->GetNextInFlow(&nextInFlow); NS_PRECONDITION(nsnull != nextInFlow, "null next-in-flow"); nsBlockFrame* parent; nextInFlow->GetParent((nsIFrame**)&parent); NS_PRECONDITION(nsnull != parent, "next-in-flow with no parent"); NS_PRECONDITION(nsnull != parent->mLines, "next-in-flow with weird parent"); // NS_PRECONDITION(nsnull == parent->mOverflowLines, "parent with overflow"); parent->DoRemoveFrame(aPresContext, nextInFlow); } //////////////////////////////////////////////////////////////////////// // Floater support nsresult nsBlockFrame::ReflowFloater(nsBlockReflowState& aState, nsPlaceholderFrame* aPlaceholder, nsRect& aCombinedRect, nsMargin& aMarginResult, nsMargin& aComputedOffsetsResult) { #ifdef NOISY_FLOATER printf("Reflow Floater %p in parent %p, availSpace(%d,%d,%d,%d)\n", aPlaceholder->GetOutOfFlowFrame(), this, aState.mAvailSpaceRect.x, aState.mAvailSpaceRect.y, aState.mAvailSpaceRect.width, aState.mAvailSpaceRect.height ); #endif // XXX update this just aState.GetAvailableSpace(); // Reflow the floater. Since floaters are continued we given them an // unbounded height. Floaters with an auto width are sized to zero // according to the css2 spec. // XXX We also need to take into account whether we should clear any // preceeding floaters... // XXX Why do we have to add in our border/padding? nsRect availSpace(aState.mAvailSpaceRect.x + aState.BorderPadding().left, aState.mAvailSpaceRect.y + aState.BorderPadding().top, aState.mAvailSpaceRect.width, NS_UNCONSTRAINEDSIZE); nsIFrame* floater = aPlaceholder->GetOutOfFlowFrame(); PRBool isAdjacentWithTop = aState.IsAdjacentWithTop(); // Setup block reflow state to reflow the floater nsBlockReflowContext brc(aState.mPresContext, aState.mReflowState, aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE), aState.GetFlag(BRS_COMPUTEMAXWIDTH)); brc.SetNextRCFrame(aState.mNextRCFrame); // Reflow the floater nsReflowStatus frameReflowStatus; nsresult rv = brc.ReflowBlock(floater, availSpace, PR_TRUE, 0, isAdjacentWithTop, aComputedOffsetsResult, frameReflowStatus); if (PR_TRUE==brc.BlockShouldInvalidateItself()) { Invalidate(aState.mPresContext, mRect); } if (floater == aState.mNextRCFrame) { // NULL out mNextRCFrame so if we reflow it again we don't think it's still // an incremental reflow aState.mNextRCFrame = nsnull; } if (NS_FAILED(rv)) { return rv; } // Capture the margin information for the caller const nsMargin& m = brc.GetMargin(); aMarginResult.top = brc.GetTopMargin(); aMarginResult.right = m.right; aMarginResult.bottom = nsBlockReflowContext::MaxMargin(brc.GetCarriedOutBottomMargin(), m.bottom); aMarginResult.left = m.left; const nsHTMLReflowMetrics& metrics = brc.GetMetrics(); aCombinedRect = metrics.mOverflowArea; // Set the rect, make sure the view is properly sized and positioned, // and tell the frame we're done reflowing it floater->SizeTo(aState.mPresContext, metrics.width, metrics.height); nsIView* view; floater->GetView(aState.mPresContext, &view); if (view) { nsContainerFrame::SyncFrameViewAfterReflow(aState.mPresContext, floater, view, &metrics.mOverflowArea, NS_FRAME_NO_MOVE_VIEW); } floater->DidReflow(aState.mPresContext, NS_FRAME_REFLOW_FINISHED); // If we computed it, then stash away the max-element-size for later if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) { nsSize mes = brc.GetMaxElementSize(); mes.SizeBy(aMarginResult.left + aMarginResult.right, aMarginResult.top + aMarginResult.bottom); aState.StoreMaxElementSize(floater, mes); } #ifdef NOISY_FLOATER printf("end ReflowFloater %p, sized to %d,%d\n", floater, metrics.width, metrics.height); #endif return NS_OK; } void nsBlockReflowState::InitFloater(nsLineLayout& aLineLayout, nsPlaceholderFrame* aPlaceholder) { // Set the geometric parent of the floater nsIFrame* floater = aPlaceholder->GetOutOfFlowFrame(); floater->SetParent(mBlock); // Then add the floater to the current line and place it when // appropriate AddFloater(aLineLayout, aPlaceholder, PR_TRUE); } // This is called by the line layout's AddFloater method when a // place-holder frame is reflowed in a line. If the floater is a // left-most child (it's x coordinate is at the line's left margin) // then the floater is place immediately, otherwise the floater // placement is deferred until the line has been reflowed. void nsBlockReflowState::AddFloater(nsLineLayout& aLineLayout, nsPlaceholderFrame* aPlaceholder, PRBool aInitialReflow) { NS_PRECONDITION(nsnull != mCurrentLine, "null ptr"); // Allocate a nsFloaterCache for the floater nsFloaterCache* fc = mFloaterCacheFreeList.Alloc(); fc->mPlaceholder = aPlaceholder; fc->mIsCurrentLineFloater = aLineLayout.CanPlaceFloaterNow(); // Now place the floater immediately if possible. Otherwise stash it // away in mPendingFloaters and place it later. if (fc->mIsCurrentLineFloater) { // Record this floater in the current-line list mCurrentLineFloaters.Append(fc); // Because we are in the middle of reflowing a placeholder frame // within a line (and possibly nested in an inline frame or two // that's a child of our block) we need to restore the space // manager's translation to the space that the block resides in // before placing the floater. nscoord ox, oy; mSpaceManager->GetTranslation(ox, oy); nscoord dx = ox - mSpaceManagerX; nscoord dy = oy - mSpaceManagerY; mSpaceManager->Translate(-dx, -dy); // Reflow the floater mBlock->ReflowFloater(*this, aPlaceholder, fc->mCombinedArea, fc->mMargins, fc->mOffsets); // And then place it PRBool isLeftFloater; PlaceFloater(fc, &isLeftFloater); // Pass on updated available space to the current inline reflow engine GetAvailableSpace(); aLineLayout.UpdateBand(mAvailSpaceRect.x + BorderPadding().left, mY, GetFlag(BRS_UNCONSTRAINEDWIDTH) ? NS_UNCONSTRAINEDSIZE : mAvailSpaceRect.width, mAvailSpaceRect.height, isLeftFloater, aPlaceholder->GetOutOfFlowFrame()); // Restore coordinate system mSpaceManager->Translate(dx, dy); } else { // This floater will be placed after the line is done (it is a // below-current-line floater). mBelowCurrentLineFloaters.Append(fc); } } PRBool nsBlockReflowState::IsLeftMostChild(nsIPresContext* aPresContext, nsIFrame* aFrame) { for (;;) { nsIFrame* parent; aFrame->GetParent(&parent); if (parent == mBlock) { nsIFrame* child = mCurrentLine->mFirstChild; PRInt32 n = mCurrentLine->GetChildCount(); 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(aPresContext, nsnull, &child); while ((nsnull != child) && (aFrame != child)) { nsSize size; // Is the child zero-sized? child->GetSize(size); if (size.width > 0) { // We found a non-zero sized child frame that precedes aFrame return PR_FALSE; } child->GetNextSibling(&child); } } // aFrame is the left-most non-zero sized frame in its geometric parent. // Walk up one level and check that its parent is left-most as well aFrame = parent; } return PR_TRUE; } PRBool nsBlockReflowState::CanPlaceFloater(const nsRect& aFloaterRect, PRUint8 aFloats) { // If the current Y coordinate is not impacted by any floaters // then by definition the floater fits. PRBool result = PR_TRUE; if (0 != mBand.GetFloaterCount()) { if (mAvailSpaceRect.width < aFloaterRect.width) { // The available width is too narrow (and its been impacted by a // prior floater) result = PR_FALSE; } else { // At this point we know that there is enough horizontal space for // the floater (somewhere). Lets see if there is enough vertical // space. if (mAvailSpaceRect.height < aFloaterRect.height) { // The available height is too short. However, its possible that // there is enough open space below which is not impacted by a // floater. // // Compute the X coordinate for the floater based on its float // type, assuming its placed on the current line. This is // where the floater will be placed horizontally if it can go // here. nscoord xa; if (NS_STYLE_FLOAT_LEFT == aFloats) { xa = mAvailSpaceRect.x; } else { xa = mAvailSpaceRect.XMost() - aFloaterRect.width; // In case the floater is too big, don't go past the left edge if (xa < mAvailSpaceRect.x) { xa = mAvailSpaceRect.x; } } nscoord xb = xa + aFloaterRect.width; // Calculate the top and bottom y coordinates, again assuming // that the floater is placed on the current line. const nsMargin& borderPadding = BorderPadding(); nscoord ya = mY - borderPadding.top; if (ya < 0) { // CSS2 spec, 9.5.1 rule [4]: A floating box's outer top may not // be higher than the top of its containing block. // XXX It's not clear if it means the higher than the outer edge // or the border edge or the inner edge? ya = 0; } nscoord yb = ya + aFloaterRect.height; nscoord saveY = mY; for (;;) { // Get the available space at the new Y coordinate mY += mAvailSpaceRect.height; GetAvailableSpace(); if (0 == mBand.GetFloaterCount()) { // Winner. This band has no floaters on it, therefore // there can be no overlap. break; } // Check and make sure the floater won't intersect any // floaters on this band. The floaters starting and ending // coordinates must be entirely in the available space. if ((xa < mAvailSpaceRect.x) || (xb > mAvailSpaceRect.XMost())) { // The floater can't go here. result = PR_FALSE; break; } // See if there is now enough height for the floater. if (yb < mY + mAvailSpaceRect.height) { // Winner. The bottom Y coordinate of the floater is in // this band. break; } } // Restore Y coordinate and available space information // regardless of the outcome. mY = saveY; GetAvailableSpace(); } } } return result; } void nsBlockReflowState::PlaceFloater(nsFloaterCache* aFloaterCache, PRBool* aIsLeftFloater) { // Save away the Y coordinate before placing the floater. We will // restore mY at the end after placing the floater. This is // necessary because any adjustments to mY during the floater // placement are for the floater only, not for any non-floating // content. nscoord saveY = mY; nsIFrame* floater = aFloaterCache->mPlaceholder->GetOutOfFlowFrame(); // Get the type of floater const nsStyleDisplay* floaterDisplay; const nsStyleSpacing* floaterSpacing; const nsStylePosition* floaterPosition; floater->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)floaterDisplay); floater->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&)floaterSpacing); floater->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&)floaterPosition); // See if the floater should clear any preceeding floaters... if (NS_STYLE_CLEAR_NONE != floaterDisplay->mBreakType) { ClearFloaters(mY, floaterDisplay->mBreakType); } else { // Get the band of available space GetAvailableSpace(); } // Get the floaters bounding box and margin information nsRect region; floater->GetRect(region); // Adjust the floater size by its margin. That's the area that will // impact the space manager. region.width += aFloaterCache->mMargins.left + aFloaterCache->mMargins.right; region.height += aFloaterCache->mMargins.top + aFloaterCache->mMargins.bottom; // Find a place to place the floater. The CSS2 spec doesn't want // floaters overlapping each other or sticking out of the containing // block if possible (CSS2 spec section 9.5.1, see the rule list). NS_ASSERTION((NS_STYLE_FLOAT_LEFT == floaterDisplay->mFloats) || (NS_STYLE_FLOAT_RIGHT == floaterDisplay->mFloats), "invalid float type"); // While there is not enough room for the floater, clear past other // floaters until there is room (or the band is not impacted by a // floater). // // Note: The CSS2 spec says that floaters should be placed as high // as possible. // #ifdef FIX_BUG_37657 // Also note that in backwards compatibility mode, we skip this step // In old browsers, floaters are horizontally stacked regardless of // available space nsCompatibility mode; mPresContext->GetCompatibilityMode(&mode); if (eCompatibility_NavQuirks != mode) { while (!CanPlaceFloater(region, floaterDisplay->mFloats)) { mY += mAvailSpaceRect.height; GetAvailableSpace(); } } #else while (!CanPlaceFloater(region, floaterDisplay->mFloats)) { mY += mAvailSpaceRect.height; GetAvailableSpace(); } #endif // Assign an x and y coordinate to the floater. Note that the x,y // coordinates are computed relative to the translation in the // spacemanager which means that the impacted region will be // inside the border/padding area. PRBool okToAddRectRegion = PR_TRUE; PRBool isLeftFloater; if (NS_STYLE_FLOAT_LEFT == floaterDisplay->mFloats) { isLeftFloater = PR_TRUE; region.x = mAvailSpaceRect.x; } else { isLeftFloater = PR_FALSE; if (NS_UNCONSTRAINEDSIZE != mAvailSpaceRect.XMost()) region.x = mAvailSpaceRect.XMost() - region.width; else { okToAddRectRegion = PR_FALSE; region.x = mAvailSpaceRect.x; } } *aIsLeftFloater = isLeftFloater; const nsMargin& borderPadding = BorderPadding(); region.y = mY - borderPadding.top; if (region.y < 0) { // CSS2 spec, 9.5.1 rule [4]: A floating box's outer top may not // be higher than the top of its containing block. // XXX It's not clear if it means the higher than the outer edge // or the border edge or the inner edge? region.y = 0; } // Place the floater in the space manager if (okToAddRectRegion) { #ifdef DEBUG nsresult rv = #endif mSpaceManager->AddRectRegion(floater, region); NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "bad floater placement"); } // Save away the floaters region in the spacemanager, after making // it relative to the containing block's frame instead of relative // to the spacemanager translation (which is inset by the // border+padding). aFloaterCache->mRegion.x = region.x + borderPadding.left; aFloaterCache->mRegion.y = region.y + borderPadding.top; aFloaterCache->mRegion.width = region.width; aFloaterCache->mRegion.height = region.height; #ifdef NOISY_SPACEMANAGER nscoord tx, ty; mSpaceManager->GetTranslation(tx, ty); nsFrame::ListTag(stdout, mBlock); printf(": PlaceFloater: AddRectRegion: txy=%d,%d (%d,%d) {%d,%d,%d,%d}\n", tx, ty, mSpaceManagerX, mSpaceManagerY, aFloaterCache->mRegion.x, aFloaterCache->mRegion.y, aFloaterCache->mRegion.width, aFloaterCache->mRegion.height); #endif // Set the origin of the floater frame, in frame coordinates. These // coordinates are not relative to the spacemanager // translation, therefore we have to factor in our border/padding. nscoord x = borderPadding.left + aFloaterCache->mMargins.left + region.x; nscoord y = borderPadding.top + aFloaterCache->mMargins.top + region.y; // If floater is relatively positioned, factor that in as well if (NS_STYLE_POSITION_RELATIVE == floaterPosition->mPosition) { x += aFloaterCache->mOffsets.left; y += aFloaterCache->mOffsets.top; } // Position the floater and make sure and views are properly positioned nsIView* view; floater->MoveTo(mPresContext, x, y); floater->GetView(mPresContext, &view); if (view) { nsContainerFrame::PositionFrameView(mPresContext, floater, view); } else { nsContainerFrame::PositionChildViews(mPresContext, floater); } // Update the floater combined area state nsRect combinedArea = aFloaterCache->mCombinedArea; combinedArea.x += x; combinedArea.y += y; if (!isLeftFloater && (GetFlag(BRS_UNCONSTRAINEDWIDTH) || GetFlag(BRS_SHRINKWRAPWIDTH))) { // When we are placing a right floater in an unconstrained situation or // when shrink wrapping, we don't apply it to the floater combined area // immediately. Otherwise we end up with an infinitely wide combined // area. Instead, we save it away in mRightFloaterCombinedArea so that // later on when we know the width of a line we can compute a better value. if (!mHaveRightFloaters) { mRightFloaterCombinedArea = combinedArea; mHaveRightFloaters = PR_TRUE; } else { CombineRects(combinedArea, mRightFloaterCombinedArea); } } else { CombineRects(combinedArea, mFloaterCombinedArea); } // Now restore mY mY = saveY; #ifdef DEBUG if (gNoisyReflow) { nsRect r; floater->GetRect(r); nsFrame::IndentBy(stdout, gNoiseIndent); printf("placed floater: "); ((nsFrame*)floater)->ListTag(stdout); printf(" %d,%d,%d,%d\n", r.x, r.y, r.width, r.height); } #endif } /** * Place below-current-line floaters. */ void nsBlockReflowState::PlaceBelowCurrentLineFloaters(nsFloaterCacheList& aList) { nsFloaterCache* fc = aList.Head(); while (fc) { if (!fc->mIsCurrentLineFloater) { #ifdef DEBUG if (gNoisyReflow) { nsFrame::IndentBy(stdout, gNoiseIndent); printf("placing bcl floater: "); nsFrame::ListTag(stdout, fc->mPlaceholder->GetOutOfFlowFrame()); printf("\n"); } #endif mBlock->ReflowFloater(*this, fc->mPlaceholder, fc->mCombinedArea, fc->mMargins, fc->mOffsets); // Place the floater PRBool isLeftFloater; PlaceFloater(fc, &isLeftFloater); } fc = fc->Next(); } } void nsBlockReflowState::ClearFloaters(nscoord aY, PRUint8 aBreakType) { #ifdef DEBUG if (gNoisyReflow) { nsFrame::IndentBy(stdout, gNoiseIndent); printf("clear floaters: in: mY=%d aY=%d(%d)\n", mY, aY, aY - BorderPadding().top); } #endif #ifdef NOISY_FLOATER_CLEARING printf("nsBlockReflowState::ClearFloaters: aY=%d breakType=%dn", aY, aBreakType); mSpaceManager->List(stdout); #endif const nsMargin& bp = BorderPadding(); nscoord newY = mBand.ClearFloaters(aY - bp.top, aBreakType); mY = newY + bp.top; GetAvailableSpace(); #ifdef DEBUG if (gNoisyReflow) { nsFrame::IndentBy(stdout, gNoiseIndent); printf("clear floaters: out: mY=%d(%d)\n", mY, mY - bp.top); } #endif } ////////////////////////////////////////////////////////////////////// // Painting, event handling PRIntn nsBlockFrame::GetSkipSides() const { PRIntn skip = 0; if (nsnull != mPrevInFlow) { skip |= 1 << NS_SIDE_TOP; } if (nsnull != mNextInFlow) { skip |= 1 << NS_SIDE_BOTTOM; } return skip; } #ifdef DEBUG static void ComputeCombinedArea(nsLineBox* aLine, nscoord aWidth, nscoord aHeight, nsRect& aResult) { nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight; while (nsnull != aLine) { // Compute min and max x/y values for the reflowed frame's // combined areas nsRect lineCombinedArea; aLine->GetCombinedArea(&lineCombinedArea); nscoord x = lineCombinedArea.x; nscoord y = lineCombinedArea.y; nscoord xmost = x + lineCombinedArea.width; nscoord ymost = y + lineCombinedArea.height; if (x < xa) { xa = x; } if (xmost > xb) { xb = xmost; } if (y < ya) { ya = y; } if (ymost > yb) { yb = ymost; } aLine = aLine->mNext; } aResult.x = xa; aResult.y = ya; aResult.width = xb - xa; aResult.height = yb - ya; } #endif NS_IMETHODIMP nsBlockFrame::Paint(nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect, nsFramePaintLayer aWhichLayer) { if (NS_FRAME_IS_UNFLOWABLE & mState) { return NS_OK; } #ifdef DEBUG if (gNoisyDamageRepair) { if (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) { PRInt32 depth = GetDepth(); nsRect ca; ComputeCombinedArea(mLines, mRect.width, mRect.height, ca); nsFrame::IndentBy(stdout, depth); ListTag(stdout); printf(": bounds=%d,%d,%d,%d dirty=%d,%d,%d,%d ca=%d,%d,%d,%d\n", mRect.x, mRect.y, mRect.width, mRect.height, aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height, ca.x, ca.y, ca.width, ca.height); } } #endif const nsStyleDisplay* disp = (const nsStyleDisplay*) mStyleContext->GetStyleData(eStyleStruct_Display); // Only paint the border and background if we're visible if (disp->IsVisible() && (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) && (0 != mRect.width) && (0 != mRect.height)) { PRIntn skipSides = GetSkipSides(); const nsStyleColor* color = (const nsStyleColor*) mStyleContext->GetStyleData(eStyleStruct_Color); const nsStyleSpacing* spacing = (const nsStyleSpacing*) mStyleContext->GetStyleData(eStyleStruct_Spacing); // Paint background, border and outline nsRect rect(0, 0, mRect.width, mRect.height); nsCSSRendering::PaintBackground(aPresContext, aRenderingContext, this, aDirtyRect, rect, *color, *spacing, 0, 0); nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, this, aDirtyRect, rect, *spacing, mStyleContext, skipSides); nsCSSRendering::PaintOutline(aPresContext, aRenderingContext, this, aDirtyRect, rect, *spacing, mStyleContext, 0); } // If overflow is hidden then set the clip rect so that children don't // leak out of us. Note that because overflow'-clip' only applies to // the content area we do this after painting the border and background if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) { aRenderingContext.PushState(); SetOverflowClipRect(aRenderingContext); } // Child elements have the opportunity to override the visibility // property and display even if the parent is hidden if (NS_FRAME_PAINT_LAYER_FLOATERS == aWhichLayer) { PaintFloaters(aPresContext, aRenderingContext, aDirtyRect); } PaintChildren(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer); if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) { PRBool clipState; aRenderingContext.PopState(clipState); } #if 0 if ((NS_FRAME_PAINT_LAYER_DEBUG == aWhichLayer) && GetShowFrameBorders()) { // Render the bands in the spacemanager nsISpaceManager* sm = mSpaceManager; if (nsnull != sm) { nsBlockBandData band; band.Init(sm, nsSize(mRect.width, mRect.height)); nscoord y = 0; while (y < mRect.height) { nsRect availArea; band.GetAvailableSpace(y, availArea); // Render a box and a diagonal line through the band aRenderingContext.SetColor(NS_RGB(0,255,0)); aRenderingContext.DrawRect(0, availArea.y, mRect.width, availArea.height); aRenderingContext.DrawLine(0, availArea.y, mRect.width, availArea.YMost()); // Render boxes and opposite diagonal lines around the // unavailable parts of the band. PRInt32 i; for (i = 0; i < band.GetTrapezoidCount(); i++) { const nsBandTrapezoid* trapezoid = band.GetTrapezoid(i); if (nsBandTrapezoid::Available != trapezoid->mState) { nsRect r; trapezoid->GetRect(r); if (nsBandTrapezoid::OccupiedMultiple == trapezoid->mState) { aRenderingContext.SetColor(NS_RGB(0,255,128)); } else { aRenderingContext.SetColor(NS_RGB(128,255,0)); } aRenderingContext.DrawRect(r); aRenderingContext.DrawLine(r.x, r.YMost(), r.XMost(), r.y); } } y = availArea.YMost(); } } } #endif return NS_OK; } void nsBlockFrame::PaintFloaters(nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect) { for (nsLineBox* line = mLines; nsnull != line; line = line->mNext) { if (!line->HasFloaters()) { continue; } nsFloaterCache* fc = line->GetFirstFloater(); while (fc) { nsIFrame* floater = fc->mPlaceholder->GetOutOfFlowFrame(); PaintChild(aPresContext, aRenderingContext, aDirtyRect, floater, NS_FRAME_PAINT_LAYER_BACKGROUND); PaintChild(aPresContext, aRenderingContext, aDirtyRect, floater, NS_FRAME_PAINT_LAYER_FLOATERS); PaintChild(aPresContext, aRenderingContext, aDirtyRect, floater, NS_FRAME_PAINT_LAYER_FOREGROUND); fc = fc->Next(); } } } void nsBlockFrame::PaintChildren(nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect, nsFramePaintLayer aWhichLayer) { #ifdef DEBUG PRInt32 depth = 0; if (gNoisyDamageRepair) { if (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) { depth = GetDepth(); } } PRTime start; PRInt32 drawnLines; if (gLamePaintMetrics) { start = PR_Now(); drawnLines = 0; } #endif for (nsLineBox* line = mLines; nsnull != line; line = line->mNext) { // If the line's combined area (which includes child frames that // stick outside of the line's bounding box or our bounding box) // intersects the dirty rect then paint the line. if (line->CombinedAreaIntersects(aDirtyRect)) { #ifdef DEBUG if (gNoisyDamageRepair && (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer)) { nsRect lineCombinedArea; line->GetCombinedArea(&lineCombinedArea); nsFrame::IndentBy(stdout, depth+1); printf("draw line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n", line, line->mBounds.x, line->mBounds.y, line->mBounds.width, line->mBounds.height, lineCombinedArea.x, lineCombinedArea.y, lineCombinedArea.width, lineCombinedArea.height); } if (gLamePaintMetrics) { drawnLines++; } #endif nsIFrame* kid = line->mFirstChild; PRInt32 n = line->GetChildCount(); while (--n >= 0) { PaintChild(aPresContext, aRenderingContext, aDirtyRect, kid, aWhichLayer); kid->GetNextSibling(&kid); } } #ifdef DEBUG else { if (gNoisyDamageRepair && (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer)) { nsRect lineCombinedArea; line->GetCombinedArea(&lineCombinedArea); nsFrame::IndentBy(stdout, depth+1); printf("skip line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n", line, line->mBounds.x, line->mBounds.y, line->mBounds.width, line->mBounds.height, lineCombinedArea.x, lineCombinedArea.y, lineCombinedArea.width, lineCombinedArea.height); } } #endif } if (NS_FRAME_PAINT_LAYER_FOREGROUND == aWhichLayer) { if ((nsnull != mBullet) && HaveOutsideBullet()) { // Paint outside bullets manually PaintChild(aPresContext, aRenderingContext, aDirtyRect, mBullet, aWhichLayer); } } #ifdef DEBUG if (gLamePaintMetrics) { PRTime end = PR_Now(); PRInt32 numLines = nsLineBox::ListLength(mLines); if (!numLines) numLines = 1; PRTime lines, deltaPerLine, delta; LL_I2L(lines, numLines); LL_SUB(delta, end, start); LL_DIV(deltaPerLine, delta, lines); ListTag(stdout); char buf[400]; PR_snprintf(buf, sizeof(buf), ": %lld elapsed (%lld per line) lines=%d drawn=%d skip=%d", delta, deltaPerLine, numLines, drawnLines, numLines - drawnLines); printf("%s\n", buf); } #endif } NS_IMETHODIMP nsBlockFrame::HandleEvent(nsIPresContext* aPresContext, nsGUIEvent* aEvent, nsEventStatus* aEventStatus) { nsresult result; nsCOMPtr shell; if (aEvent->message == NS_MOUSE_MOVE) { aPresContext->GetShell(getter_AddRefs(shell)); if (!shell) return NS_OK; nsCOMPtr frameSelection; PRBool mouseDown = PR_FALSE; //check to see if we need to ask the selection controller.. if (mState & NS_FRAME_INDEPENDENT_SELECTION) { nsCOMPtr selCon; result = GetSelectionController(aPresContext, getter_AddRefs(selCon)); if (NS_FAILED(result) || !selCon) return result?result:NS_ERROR_FAILURE; frameSelection = do_QueryInterface(selCon); } else shell->GetFrameSelection(getter_AddRefs(frameSelection)); if (!frameSelection || NS_FAILED(frameSelection->GetMouseDownState(&mouseDown)) || !mouseDown) return NS_OK;//do not handle } if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN || aEvent->message == NS_MOUSE_MOVE || aEvent->message == NS_MOUSE_LEFT_DOUBLECLICK ) { nsIFrame *resultFrame = nsnull;//this will be passed the handle event when we //can tell who to pass it to nsCOMPtr it; nsIFrame *mainframe = this; nsCOMPtr tracker; aPresContext->GetShell(getter_AddRefs(shell)); if (!shell) return NS_OK; result = shell->QueryInterface(NS_GET_IID(nsIFocusTracker),getter_AddRefs(tracker)); result = mainframe->QueryInterface(NS_GET_IID(nsILineIterator),getter_AddRefs(it)); nsIView* parentWithView; nsPoint origin; while(NS_SUCCEEDED(result)) { //we are starting aloop to allow us to "drill down to the one we want" mainframe->GetOffsetFromView(aPresContext, origin, &parentWithView); if (NS_FAILED(result)) return NS_OK;//do not handle PRInt32 countLines; result = it->GetNumLines(&countLines); if (NS_FAILED(result)) return NS_OK;//do not handle PRInt32 i; PRInt32 lineFrameCount; nsIFrame *firstFrame; nsRect rect; PRInt32 closestLine = 0; PRInt32 closestDistance = 999999; //some HUGE number that will always fail first comparison //incase we hit another block frame. for (i = 0; i< countLines;i++) { PRUint32 flags; result = it->GetLine(i, &firstFrame, &lineFrameCount,rect,&flags); if (NS_FAILED(result)) continue;//do not handle rect+=origin; rect.width = aEvent->point.x - rect.x+1;//EXTEND RECT TO REACH POINT if (rect.Contains(aEvent->point.x, aEvent->point.y)) { closestLine = i; break; } else { PRInt32 distance = PR_MIN(abs(rect.y - aEvent->point.y),abs((rect.y + rect.height) - aEvent->point.y)); if (distance < closestDistance) { closestDistance = distance; closestLine = i; } else if (distance > closestDistance) break;//done } } //we will now ask where to go. if we cant find what we want"aka another block frame" //we drill down again nsPeekOffsetStruct pos; pos.mTracker = tracker; pos.mDirection = eDirNext; pos.mDesiredX = aEvent->point.x; result = nsFrame::GetNextPrevLineFromeBlockFrame(aPresContext, &pos, mainframe, closestLine-1, 0 ); if (NS_SUCCEEDED(result) && pos.mResultFrame){ result = pos.mResultFrame->QueryInterface(NS_GET_IID(nsILineIterator),getter_AddRefs(it));//if this fails thats ok resultFrame = pos.mResultFrame; mainframe = resultFrame; } else break;//time to go nothing was found } //end while loop. if nssucceeded resutl then keep going that means //we have successfully hit another block frame and we should keep going. if (resultFrame) { result = resultFrame->HandleEvent(aPresContext, aEvent, aEventStatus); if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN && !IsMouseCaptured(aPresContext)) CaptureMouse(aPresContext, PR_TRUE); return result; } else { /*we have to add this because any frame that overrides nsFrame::HandleEvent for mouse down MUST capture the mouse events!! if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN && !IsMouseCaptured(aPresContext)) CaptureMouse(aPresContext, PR_TRUE);*/ return NS_OK; //just stop it } } return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); } NS_IMETHODIMP nsBlockFrame::GetFrameForPoint(nsIPresContext* aPresContext, const nsPoint& aPoint, nsFramePaintLayer aWhichLayer, nsIFrame** aFrame) { nsresult rv; switch (aWhichLayer) { case NS_FRAME_PAINT_LAYER_FOREGROUND: rv = GetFrameForPointUsing(aPresContext, aPoint, nsnull, NS_FRAME_PAINT_LAYER_FOREGROUND, PR_FALSE, aFrame); if (NS_OK == rv) { return NS_OK; } if (nsnull != mBullet) { rv = GetFrameForPointUsing(aPresContext, aPoint, nsLayoutAtoms::bulletList, NS_FRAME_PAINT_LAYER_FOREGROUND, PR_FALSE, aFrame); } return rv; break; case NS_FRAME_PAINT_LAYER_FLOATERS: // we painted our floaters before our children, and thus // we should check floaters within children first rv = GetFrameForPointUsing(aPresContext, aPoint, nsnull, NS_FRAME_PAINT_LAYER_FLOATERS, PR_FALSE, aFrame); if (NS_OK == rv) { return NS_OK; } if (mFloaters.NotEmpty()) { rv = GetFrameForPointUsing(aPresContext, aPoint, nsLayoutAtoms::floaterList, NS_FRAME_PAINT_LAYER_FOREGROUND, PR_FALSE, aFrame); if (NS_OK == rv) { return NS_OK; } rv = GetFrameForPointUsing(aPresContext, aPoint, nsLayoutAtoms::floaterList, NS_FRAME_PAINT_LAYER_FLOATERS, PR_FALSE, aFrame); if (NS_OK == rv) { return NS_OK; } return GetFrameForPointUsing(aPresContext, aPoint, nsLayoutAtoms::floaterList, NS_FRAME_PAINT_LAYER_BACKGROUND, PR_FALSE, aFrame); } else { return NS_ERROR_FAILURE; } break; case NS_FRAME_PAINT_LAYER_BACKGROUND: // we're a block, so PR_TRUE for consider self return GetFrameForPointUsing(aPresContext, aPoint, nsnull, NS_FRAME_PAINT_LAYER_BACKGROUND, PR_TRUE, aFrame); break; } // we shouldn't get here NS_ASSERTION(PR_FALSE, "aWhichLayer was not understood"); return NS_ERROR_FAILURE; } NS_IMETHODIMP nsBlockFrame::ReflowDirtyChild(nsIPresShell* aPresShell, nsIFrame* aChild) { if (aChild) { // See if the child is absolutely positioned nsFrameState childState; aChild->GetFrameState(&childState); if (childState & NS_FRAME_OUT_OF_FLOW) { const nsStylePosition* position; aChild->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&)position); if (position->IsAbsolutelyPositioned()) { // Generate a reflow command to reflow our dirty absolutely // positioned child frames. // XXX Note that we don't currently try and coalesce the reflow commands, // although we should. We can't use the NS_FRAME_HAS_DIRTY_CHILDREN // flag, because that's used to indicate whether in-flow children are // dirty... nsIReflowCommand* reflowCmd; nsresult rv = NS_NewHTMLReflowCommand(&reflowCmd, this, nsIReflowCommand::ReflowDirty); if (NS_SUCCEEDED(rv)) { reflowCmd->SetChildListName(nsLayoutAtoms::absoluteList); aPresShell->AppendReflowCommand(reflowCmd); NS_RELEASE(reflowCmd); } return rv; } } } // Mark the line containing the child frame dirty. if (aChild) { PRBool isFloater; nsLineBox* prevLine; nsLineBox* line = FindLineFor(aChild, &prevLine, &isFloater); if (!isFloater) { if (line) MarkLineDirty(line, prevLine); } else { line = mLines; while (nsnull != line) { line->MarkDirty(); line = line->mNext; } } } // Either generate a reflow command to reflow the dirty child or // coalesce this reflow request with an existing reflow command if (!(mState & NS_FRAME_HAS_DIRTY_CHILDREN)) { // If this is the first dirty child, // post a dirty children reflow command targeted at yourself mState |= NS_FRAME_HAS_DIRTY_CHILDREN; nsFrame::CreateAndPostReflowCommand(aPresShell, this, nsIReflowCommand::ReflowDirty, nsnull, nsnull, nsnull); } else { if (!(mState & NS_FRAME_IS_DIRTY)) { // Mark yourself as dirty mState |= NS_FRAME_IS_DIRTY; // Cancel the dirty children reflow command you posted earlier nsIReflowCommand::ReflowType type = nsIReflowCommand::ReflowDirty; aPresShell->CancelReflowCommand(this, &type); // Pass up the reflow request to the parent frame. mParent->ReflowDirtyChild(aPresShell, this); } } return NS_OK; } ////////////////////////////////////////////////////////////////////// // Debugging #ifdef NS_DEBUG static PRBool InLineList(nsLineBox* aLines, nsIFrame* aFrame) { while (nsnull != aLines) { nsIFrame* frame = aLines->mFirstChild; PRInt32 n = aLines->GetChildCount(); while (--n >= 0) { if (frame == aFrame) { return PR_TRUE; } frame->GetNextSibling(&frame); } aLines = aLines->mNext; } return PR_FALSE; } static PRBool InSiblingList(nsLineBox* 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(nsIPresContext* aPresContext, nsIFrame* aFrame) { nsIFrame* parent; aFrame->GetParent(&parent); if (parent != (nsIFrame*)this) { return PR_FALSE; } if (InLineList(mLines, aFrame) && InSiblingList(mLines, aFrame)) { return PR_TRUE; } nsLineBox* overflowLines = GetOverflowLines(aPresContext, PR_FALSE); if (InLineList(overflowLines, aFrame) && InSiblingList(overflowLines, aFrame)) { return PR_TRUE; } return PR_FALSE; } NS_IMETHODIMP nsBlockFrame::VerifyTree() const { // XXX rewrite this return NS_OK; } NS_IMETHODIMP nsBlockFrame::SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const { if (!aHandler || !aResult) { return NS_ERROR_NULL_POINTER; } PRUint32 sum = sizeof(*this); // Add in size of each line object nsLineBox* line = mLines; while (line) { PRUint32 lineBoxSize; nsIAtom* atom = line->SizeOf(aHandler, &lineBoxSize); aHandler->AddSize(atom, lineBoxSize); line = line->mNext; } *aResult = sum; return NS_OK; } #endif //---------------------------------------------------------------------- NS_IMETHODIMP nsBlockFrame::Init(nsIPresContext* aPresContext, nsIContent* aContent, nsIFrame* aParent, nsIStyleContext* aContext, nsIFrame* aPrevInFlow) { if (aPrevInFlow) { // Copy over the block/area frame type flags nsBlockFrame* blockFrame = (nsBlockFrame*)aPrevInFlow; SetFlags(blockFrame->mState & NS_BLOCK_FLAGS_MASK); } nsresult rv = nsBlockFrameSuper::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow); return rv; } nsIStyleContext* nsBlockFrame::GetFirstLetterStyle(nsIPresContext* aPresContext) { nsIStyleContext* fls; aPresContext->ProbePseudoStyleContextFor(mContent, nsHTMLAtoms::firstLetterPseudo, mStyleContext, PR_FALSE, &fls); return fls; } NS_IMETHODIMP nsBlockFrame::SetInitialChildList(nsIPresContext* aPresContext, nsIAtom* aListName, nsIFrame* aChildList) { nsresult rv = NS_OK; if (nsLayoutAtoms::absoluteList == aListName) { mAbsoluteContainer.SetInitialChildList(this, aPresContext, aListName, aChildList); } else if (nsLayoutAtoms::floaterList == aListName) { mFloaters.SetFrames(aChildList); } else { // Lookup up the two pseudo style contexts if (nsnull == mPrevInFlow) { nsIStyleContext* firstLetterStyle = GetFirstLetterStyle(aPresContext); if (nsnull != firstLetterStyle) { mState |= NS_BLOCK_HAS_FIRST_LETTER_STYLE; #ifdef NOISY_FIRST_LETTER ListTag(stdout); printf(": first-letter style found\n"); #endif NS_RELEASE(firstLetterStyle); } } rv = AddFrames(aPresContext, aChildList, nsnull); if (NS_FAILED(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)) { // Resolve style for the bullet frame nsIStyleContext* kidSC; aPresContext->ResolvePseudoStyleContextFor(mContent, nsHTMLAtoms::mozListBulletPseudo, mStyleContext, PR_FALSE, &kidSC); // Create bullet frame nsCOMPtr shell; aPresContext->GetShell(getter_AddRefs(shell)); mBullet = new (shell.get()) nsBulletFrame; if (nsnull == mBullet) { NS_RELEASE(kidSC); return NS_ERROR_OUT_OF_MEMORY; } mBullet->Init(aPresContext, mContent, this, kidSC, nsnull); 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) { AddFrames(aPresContext, mBullet, nsnull); mState &= ~NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET; } else { mState |= NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET; } } } return NS_OK; } PRBool nsBlockFrame::FrameStartsCounterScope(nsIFrame* aFrame) { const nsStyleContent* styleContent; aFrame->GetStyleData(eStyleStruct_Content, (const nsStyleStruct*&) styleContent); if (0 != styleContent->CounterResetCount()) { // Winner return PR_TRUE; } return PR_FALSE; } void nsBlockFrame::RenumberLists(nsIPresContext* aPresContext) { if (!FrameStartsCounterScope(this)) { // If this frame doesn't start a counter scope then we don't need // to renumber child list items. return; } // Setup initial list ordinal value // XXX Map html's start property to counter-reset style PRInt32 ordinal = 1; nsIHTMLContent* hc; if (mContent && (NS_OK == mContent->QueryInterface(kIHTMLContentIID, (void**) &hc))) { nsHTMLValue value; if (NS_CONTENT_ATTR_HAS_VALUE == hc->GetHTMLAttribute(nsHTMLAtoms::start, value)) { if (eHTMLUnit_Integer == value.GetUnit()) { ordinal = value.GetIntValue(); if (ordinal <= 0) { ordinal = 1; } } } NS_RELEASE(hc); } // Get to first-in-flow nsBlockFrame* block = (nsBlockFrame*) GetFirstInFlow(); RenumberListsInBlock(aPresContext, block, &ordinal, 0); } PRBool nsBlockFrame::RenumberListsInBlock(nsIPresContext* aPresContext, nsBlockFrame* aBlockFrame, PRInt32* aOrdinal, PRInt32 aDepth) { PRBool renumberedABullet = PR_FALSE; while (nsnull != aBlockFrame) { // Examine each line in the block nsLineBox* line = aBlockFrame->mLines; while (line) { nsIFrame* kid = line->mFirstChild; PRInt32 n = line->GetChildCount(); while (--n >= 0) { PRBool kidRenumberedABullet = RenumberListsFor(aPresContext, kid, aOrdinal, aDepth); if (kidRenumberedABullet) { line->MarkDirty(); renumberedABullet = PR_TRUE; } kid->GetNextSibling(&kid); } line = line->mNext; } // Advance to the next continuation aBlockFrame->GetNextInFlow((nsIFrame**) &aBlockFrame); } return renumberedABullet; } // XXX temporary code: after ib work is done in frame construction // code this can be removed. PRBool nsBlockFrame::RenumberListsIn(nsIPresContext* aPresContext, nsIFrame* aContainerFrame, PRInt32* aOrdinal, PRInt32 aDepth) { PRBool renumberedABullet = PR_FALSE; // For each flow-block... while (nsnull != aContainerFrame) { // For each frame in the flow-block... nsIFrame* kid; aContainerFrame->FirstChild(aPresContext, nsnull, &kid); while (nsnull != kid) { PRBool kidRenumberedABullet = RenumberListsFor(aPresContext, kid, aOrdinal, aDepth); if (kidRenumberedABullet) { renumberedABullet = PR_TRUE; } kid->GetNextSibling(&kid); } aContainerFrame->GetNextInFlow(&aContainerFrame); } return renumberedABullet; } PRBool nsBlockFrame::RenumberListsFor(nsIPresContext* aPresContext, nsIFrame* aKid, PRInt32* aOrdinal, PRInt32 aDepth) { // add in a sanity check for absurdly deep frame trees. See bug 42138 if (MAX_DEPTH_FOR_LIST_RENUMBERING < aDepth) return PR_FALSE; PRBool kidRenumberedABullet = PR_FALSE; // 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; aKid->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 foreign has crept in. nsBlockFrame* listItem; nsresult rv = aKid->QueryInterface(kBlockFrameCID, (void**)&listItem); if (NS_SUCCEEDED(rv)) { if (nsnull != listItem->mBullet) { PRBool changed; *aOrdinal = listItem->mBullet->SetListItemOrdinal(*aOrdinal, &changed); if (changed) { kidRenumberedABullet = PR_TRUE; } } // XXX temporary? if the list-item has child list-items they // should be numbered too; especially since the list-item is // itself (ASSUMED!) not to be a counter-reseter. PRBool meToo = RenumberListsInBlock(aPresContext, listItem, aOrdinal, aDepth + 1); if (meToo) { kidRenumberedABullet = PR_TRUE; } } } else if (NS_STYLE_DISPLAY_BLOCK == display->mDisplay) { if (FrameStartsCounterScope(aKid)) { // Don't bother recursing into a block frame that is a new // counter scope. Any list-items in there will be handled by // it. } else { // If the display=block element ISA block-frame then go // ahead and recurse into it as it might have child // list-items. nsBlockFrame* kidBlock; nsresult rv = aKid->QueryInterface(kBlockFrameCID, (void**) &kidBlock); if (NS_SUCCEEDED(rv)) { kidRenumberedABullet = RenumberListsInBlock(aPresContext, kidBlock, aOrdinal, aDepth + 1); } } } else if (NS_STYLE_DISPLAY_INLINE == display->mDisplay) { // XXX temporary code: after ib work is done in frame construction // code this can be removed. // If the display=inline element ISA nsInlineFrame then go // ahead and recurse into it as it might have child // list-items. nsInlineFrame* kidInline; nsresult rv = aKid->QueryInterface(nsInlineFrame::kInlineFrameCID, (void**) &kidInline); if (NS_SUCCEEDED(rv)) { kidRenumberedABullet = RenumberListsIn(aPresContext, aKid, aOrdinal, aDepth + 1); } } return kidRenumberedABullet; } void nsBlockFrame::ReflowBullet(nsBlockReflowState& aState, nsHTMLReflowMetrics& aMetrics) { // Reflow the bullet now nsSize availSize; availSize.width = NS_UNCONSTRAINEDSIZE; availSize.height = NS_UNCONSTRAINEDSIZE; nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState, mBullet, availSize); nsReflowStatus status; mBullet->WillReflow(aState.mPresContext); mBullet->Reflow(aState.mPresContext, aMetrics, reflowState, status); // Place the bullet now; use its right margin to distance it // from the rest of the frames in the line nscoord x = - reflowState.mComputedMargin.right - aMetrics.width; // Approximate the bullets position; vertical alignment will provide // the final vertical location. const nsMargin& bp = aState.BorderPadding(); nscoord y = bp.top; mBullet->SetRect(aState.mPresContext, nsRect(x, y, aMetrics.width, aMetrics.height)); mBullet->DidReflow(aState.mPresContext, NS_FRAME_REFLOW_FINISHED); } //XXX get rid of this -- its slow void nsBlockFrame::BuildFloaterList() { nsIFrame* head = nsnull; nsIFrame* current = nsnull; nsLineBox* line = mLines; while (nsnull != line) { if (line->HasFloaters()) { nsFloaterCache* fc = line->GetFirstFloater(); while (fc) { nsIFrame* floater = fc->mPlaceholder->GetOutOfFlowFrame(); if (nsnull == head) { current = head = floater; } else { current->SetNextSibling(floater); current = floater; } fc = fc->Next(); } } line = line->mNext; } // Terminate end of floater list just in case a floater was removed if (nsnull != current) { current->SetNextSibling(nsnull); } mFloaters.SetFrames(head); } // XXX keep the text-run data in the first-in-flow of the block #ifdef DEBUG void nsBlockFrame::VerifyLines(PRBool aFinalCheckOK) { if (!gVerifyLines) { return; } nsLineBox* line = mLines; if (!line) { return; } // Add up the counts on each line. Also validate that IsFirstLine is // set properly. PRInt32 count = 0; PRBool seenBlock = PR_FALSE; while (nsnull != line) { if (aFinalCheckOK) { NS_ABORT_IF_FALSE(line->GetChildCount(), "empty line"); if (line->IsBlock()) { seenBlock = PR_TRUE; } if (line->IsBlock()) { NS_ASSERTION(1 == line->GetChildCount(), "bad first line"); } } count += line->GetChildCount(); line = line->mNext; } // Then count the frames PRInt32 frameCount = 0; nsIFrame* frame = mLines->mFirstChild; while (nsnull != frame) { frameCount++; frame->GetNextSibling(&frame); } NS_ASSERTION(count == frameCount, "bad line list"); // Next: test that each line has right number of frames on it line = mLines; nsLineBox* prevLine = nsnull; while (nsnull != line) { count = line->GetChildCount(); frame = line->mFirstChild; while (--count >= 0) { frame->GetNextSibling(&frame); } prevLine = line; line = line->mNext; if ((nsnull != line) && (0 != line->GetChildCount())) { NS_ASSERTION(frame == line->mFirstChild, "bad line list"); } } } // Its possible that a frame can have some frames on an overflow // list. But its never possible for multiple frames to have overflow // lists. Check that this fact is actually true. void nsBlockFrame::VerifyOverflowSituation(nsIPresContext* aPresContext) { nsBlockFrame* flow = (nsBlockFrame*) GetFirstInFlow(); while (nsnull != flow) { nsLineBox* overflowLines = GetOverflowLines(aPresContext, PR_FALSE); if (nsnull != overflowLines) { NS_ASSERTION(nsnull != overflowLines->mFirstChild, "bad overflow list"); } flow = (nsBlockFrame*) flow->mNextInFlow; } } PRInt32 nsBlockFrame::GetDepth() const { PRInt32 depth = 0; nsIFrame* parent = mParent; while (nsnull != parent) { parent->GetParent(&parent); depth++; } return depth; } #endif