From e8f95314d62355b9dc39cd7e71817f3375641bc9 Mon Sep 17 00:00:00 2001 From: "roc+%cs.cmu.edu" Date: Mon, 27 Feb 2006 02:51:57 +0000 Subject: [PATCH] Bug 256311. Improve block reflow performance in the presence of many empty lines, by being more aggressive about caching block and line emptiness state, and by stashing a reference to a line that we know has nothing but empty lines above it. r+sr=dbaron --- layout/generic/nsBlockFrame.cpp | 27 +++++++++++++++++++++- layout/generic/nsBlockFrame.h | 1 + layout/generic/nsBlockReflowContext.cpp | 30 ++++++++++++++++++++----- layout/generic/nsBlockReflowContext.h | 2 +- layout/generic/nsBlockReflowState.h | 10 ++++++++- layout/generic/nsFrame.cpp | 8 +++++++ layout/generic/nsIFrame.h | 6 ++++- layout/generic/nsLineBox.cpp | 19 +++++++++++++++- 8 files changed, 93 insertions(+), 10 deletions(-) diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp index 65fe2a6bb0c..18859749c57 100644 --- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -3124,6 +3124,24 @@ nsBlockFrame::IsSelfEmpty() return PR_TRUE; } +PRBool +nsBlockFrame::CachedIsEmpty() +{ + if (!IsSelfEmpty()) { + return PR_FALSE; + } + + for (line_iterator line = begin_lines(), line_end = end_lines(); + line != line_end; + ++line) + { + if (!line->CachedIsEmpty()) + return PR_FALSE; + } + + return PR_TRUE; +} + PRBool nsBlockFrame::IsEmpty() { @@ -3160,7 +3178,11 @@ nsBlockFrame::ShouldApplyTopMargin(nsBlockReflowState& aState, } // Determine if this line is "essentially" the first line - for (line_iterator line = begin_lines(); line != aLine; ++line) { + line_iterator line = begin_lines(); + if (aState.GetFlag(BRS_HAVELINEADJACENTTOTOP)) { + line = aState.mLineAdjacentToTop; + } + while (line != aLine) { if (!line->CachedIsEmpty() || line->HasClearance()) { // A line which precedes aLine is non-empty, or has clearance, // so therefore the top margin applies. @@ -3169,6 +3191,9 @@ nsBlockFrame::ShouldApplyTopMargin(nsBlockReflowState& aState, } // No need to apply the top margin if the line has floats. We // should collapse anyway (bug 44419) + ++line; + aState.SetFlag(BRS_HAVELINEADJACENTTOTOP, PR_TRUE); + aState.mLineAdjacentToTop = line; } // The line being reflowed is "essentially" the first line in the diff --git a/layout/generic/nsBlockFrame.h b/layout/generic/nsBlockFrame.h index 92eab14ce68..89b3ff7189c 100644 --- a/layout/generic/nsBlockFrame.h +++ b/layout/generic/nsBlockFrame.h @@ -203,6 +203,7 @@ public: virtual PRBool IsVisibleInSelection(nsISelection* aSelection); virtual PRBool IsEmpty(); + virtual PRBool CachedIsEmpty(); virtual PRBool IsSelfEmpty(); // nsIHTMLReflow diff --git a/layout/generic/nsBlockReflowContext.cpp b/layout/generic/nsBlockReflowContext.cpp index 40d99d4728a..e250c4023ee 100644 --- a/layout/generic/nsBlockReflowContext.cpp +++ b/layout/generic/nsBlockReflowContext.cpp @@ -90,7 +90,8 @@ static nsIFrame* DescendIntoBlockLevelFrame(nsIFrame* aFrame) PRBool nsBlockReflowContext::ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS, - nsCollapsingMargin* aMargin, nsIFrame* aClearanceFrame, PRBool* aMayNeedRetry) + nsCollapsingMargin* aMargin, nsIFrame* aClearanceFrame, + PRBool* aMayNeedRetry, PRBool* aBlockIsEmpty) { // Include frame's top margin aMargin->Include(aRS.mComputedMargin.top); @@ -105,6 +106,7 @@ nsBlockReflowContext::ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS, #endif PRBool dirtiedLine = PR_FALSE; + PRBool setBlockIsEmpty = PR_FALSE; // Calculate the frame's generational top-margin from its child // blocks. Note that if the frame has a non-zero top-border or @@ -151,8 +153,10 @@ nsBlockReflowContext::ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS, dirtiedLine = PR_TRUE; } - PRBool isEmpty = line->IsEmpty(); - if (line->IsBlock()) { + PRBool isEmpty; + if (line->IsInline()) { + isEmpty = line->IsEmpty(); + } else { nsIFrame* kid = line->mFirstChild; if (kid == aClearanceFrame) { line->SetHasClearance(); @@ -194,7 +198,7 @@ nsBlockReflowContext::ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS, if (kid->GetStyleDisplay()->mBreakType != NS_STYLE_CLEAR_NONE) { *aMayNeedRetry = PR_TRUE; } - if (ComputeCollapsedTopMargin(innerReflowState, aMargin, aClearanceFrame, aMayNeedRetry)) { + if (ComputeCollapsedTopMargin(innerReflowState, aMargin, aClearanceFrame, aMayNeedRetry, &isEmpty)) { line->MarkDirty(); dirtiedLine = PR_TRUE; } @@ -205,14 +209,30 @@ nsBlockReflowContext::ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS, delete NS_CONST_CAST(nsHTMLReflowState*, outerReflowState); } } - if (!isEmpty) + if (!isEmpty) { + if (!setBlockIsEmpty && aBlockIsEmpty) { + setBlockIsEmpty = PR_TRUE; + *aBlockIsEmpty = PR_FALSE; + } goto done; + } + } + if (!setBlockIsEmpty && aBlockIsEmpty) { + // The first time we reach here is when this is the first block + // and we have processed all its normal lines. + setBlockIsEmpty = PR_TRUE; + // All lines are empty, or we wouldn't be here! + *aBlockIsEmpty = aRS.frame->IsSelfEmpty(); } } } done: ; } + + if (!setBlockIsEmpty && aBlockIsEmpty) { + *aBlockIsEmpty = aRS.frame->IsEmpty(); + } #ifdef NOISY_VERTICAL_MARGINS nsFrame::ListTag(stdout, aRS.frame); diff --git a/layout/generic/nsBlockReflowContext.h b/layout/generic/nsBlockReflowContext.h index a547cc3ece0..05d9d6e946f 100644 --- a/layout/generic/nsBlockReflowContext.h +++ b/layout/generic/nsBlockReflowContext.h @@ -122,7 +122,7 @@ public: */ static PRBool ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS, nsCollapsingMargin* aMargin, nsIFrame* aClearanceFrame, - PRBool* aMayNeedRetry); + PRBool* aMayNeedRetry, PRBool* aIsEmpty = nsnull); protected: nsPresContext* mPresContext; diff --git a/layout/generic/nsBlockReflowState.h b/layout/generic/nsBlockReflowState.h index 0d97882ce2d..7c793be371b 100644 --- a/layout/generic/nsBlockReflowState.h +++ b/layout/generic/nsBlockReflowState.h @@ -58,7 +58,9 @@ class nsBlockFrame; #define BRS_COMPUTEMAXELEMENTWIDTH 0x00000100 #define BRS_COMPUTEMAXWIDTH 0x00000200 #define BRS_ISFIRSTINFLOW 0x00000400 -#define BRS_LASTFLAG BRS_ISFIRSTINFLOW +// Set when mLineAdjacentToTop is valid +#define BRS_HAVELINEADJACENTTOTOP 0x00000800 +#define BRS_LASTFLAG BRS_HAVELINEADJACENTTOTOP class nsBlockReflowState { public: @@ -198,6 +200,12 @@ public: // If it is mBlock->end_lines(), then it is invalid. nsLineList::iterator mCurrentLine; + // When BRS_HAVELINEADJACENTTOTOP is set, this refers to a line + // which we know is adjacent to the top of the block (in other words, + // all lines before it are empty and do not have clearance. This line is + // always before the current line. + nsLineList::iterator mLineAdjacentToTop; + // The current Y coordinate in the block nscoord mY; diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index a43d6e48067..42dc46fa223 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -3545,6 +3545,14 @@ nsFrame::IsEmpty() return PR_FALSE; } +PRBool +nsIFrame::CachedIsEmpty() +{ + NS_PRECONDITION(!(GetStateBits() & NS_FRAME_IS_DIRTY), + "Must only be called on reflowed lines"); + return IsEmpty(); +} + /* virtual */ PRBool nsFrame::IsSelfEmpty() { diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index 01dfa4a53eb..7316bf42204 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -1402,7 +1402,11 @@ public: * should return true. */ virtual PRBool IsEmpty() = 0; - + /** + * Return the same as IsEmpty(). This may only be called after the frame + * has been reflowed and before any further style or content changes. + */ + virtual PRBool CachedIsEmpty(); /** * Determine whether the frame is logically empty, assuming that all * its children are empty. diff --git a/layout/generic/nsLineBox.cpp b/layout/generic/nsLineBox.cpp index d680172d17b..92a1a1b04b8 100644 --- a/layout/generic/nsLineBox.cpp +++ b/layout/generic/nsLineBox.cpp @@ -307,7 +307,24 @@ nsLineBox::CachedIsEmpty() return mFlags.mEmptyCacheState; } - PRBool result = IsEmpty(); + PRBool result; + if (IsBlock()) { + result = mFirstChild->CachedIsEmpty(); + } else { + PRInt32 n; + nsIFrame *kid; + result = PR_TRUE; + for (n = GetChildCount(), kid = mFirstChild; + n > 0; + --n, kid = kid->GetNextSibling()) + { + if (!kid->CachedIsEmpty()) { + result = PR_FALSE; + break; + } + } + } + mFlags.mEmptyCacheValid = PR_TRUE; mFlags.mEmptyCacheState = result; return result;