Bug 730769 - Make nsLineBox use a frame hash table for lines with many frames. part=2/2 r=bz

This commit is contained in:
Mats Palmgren 2012-03-11 03:32:27 +01:00
Родитель 170ef239c4
Коммит 7213c84c26
4 изменённых файлов: 212 добавлений и 67 удалений

Просмотреть файл

@ -2671,16 +2671,14 @@ nsBlockFrame::PullFrameFrom(nsBlockReflowState& aState,
}
// when aFromContainer is 'this', then aLine->LastChild()'s next sibling
// is already set correctly.
aLine->SetChildCount(aLine->GetChildCount() + 1);
aLine->NoteFrameAdded(frame);
PRInt32 fromLineChildCount = fromLine->GetChildCount();
if (0 != --fromLineChildCount) {
if (fromLine->GetChildCount() > 1) {
// Mark line dirty now that we pulled a child
fromLine->SetChildCount(fromLineChildCount);
fromLine->NoteFrameRemoved(frame);
fromLine->MarkDirty();
fromLine->mFirstChild = newFirstChild;
}
else {
} else {
// Free up the fromLine now that it's empty
// Its bounds might need to be redrawn, though.
// XXX WHY do we invalidate the bounds AND the combined area? doesn't
@ -3305,7 +3303,7 @@ nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState,
// Push continuation to a new line, but only if we actually made one.
if (madeContinuation) {
nsLineBox* line = NewLineBox(nextFrame, 1, true);
nsLineBox* line = NewLineBox(nextFrame, true);
NS_ENSURE_TRUE(line, NS_ERROR_OUT_OF_MEMORY);
mLines.after_insert(aLine, line);
}
@ -3976,7 +3974,7 @@ nsBlockFrame::CreateContinuationFor(nsBlockReflowState& aState,
mFrames.InsertFrame(nsnull, aFrame, newFrame);
if (aLine) {
aLine->SetChildCount(aLine->GetChildCount() + 1);
aLine->NoteFrameAdded(newFrame);
}
aMadeNewFrame = true;
@ -4099,12 +4097,11 @@ nsBlockFrame::SplitLine(nsBlockReflowState& aState,
#endif
// Put frames being split out into their own line
nsLineBox* newLine = NewLineBox(aFrame, pushCount, false);
nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount);
if (!newLine) {
return NS_ERROR_OUT_OF_MEMORY;
}
mLines.after_insert(aLine, newLine);
aLine->SetChildCount(aLine->GetChildCount() - pushCount);
#ifdef DEBUG
if (gReallyNoisyReflow) {
newLine->List(stdout, gNoiseIndent+1);
@ -4920,12 +4917,11 @@ nsBlockFrame::AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling)
PRInt32 rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1;
if (rem) {
// Split the line in two where the frame(s) are being inserted.
nsLineBox* line = NewLineBox(aPrevSibling->GetNextSibling(), rem, false);
nsLineBox* line = NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem);
if (!line) {
return NS_ERROR_OUT_OF_MEMORY;
}
lineList->after_insert(prevSibLine, line);
prevSibLine->SetChildCount(prevSibLine->GetChildCount() - rem);
// Mark prevSibLine dirty and as needing textrun invalidation, since
// we may be breaking up text in the line. Its previous line may also
// need to be invalidated because it may be able to pull some text up.
@ -4966,7 +4962,7 @@ nsBlockFrame::AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling)
(aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) {
// Create a new line for the frame and add its line to the line
// list.
nsLineBox* line = NewLineBox(newFrame, 1, isBlock);
nsLineBox* line = NewLineBox(newFrame, isBlock);
if (!line) {
return NS_ERROR_OUT_OF_MEMORY;
}
@ -4982,7 +4978,7 @@ nsBlockFrame::AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling)
}
}
else {
prevSibLine->SetChildCount(prevSibLine->GetChildCount() + 1);
prevSibLine->NoteFrameAdded(newFrame);
// We're adding inline content to prevSibLine, so we need to mark it
// dirty, ensure its textruns are recomputed, and possibly do the same
// to its previous line since that line may be able to pull content up.
@ -5504,9 +5500,7 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aDeletedFrame, PRUint32 aFlags)
}
// Update the child count of the line to be accurate
PRInt32 lineChildCount = line->GetChildCount();
lineChildCount--;
line->SetChildCount(lineChildCount);
line->NoteFrameRemoved(aDeletedFrame);
// Destroy frame; capture its next continuation first in case we need
// to destroy that too.
@ -5533,7 +5527,7 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aDeletedFrame, PRUint32 aFlags)
bool haveAdvancedToNextLine = false;
// If line is empty, remove it now.
if (0 == lineChildCount) {
if (0 == line->GetChildCount()) {
#ifdef NOISY_REMOVE_FRAME
printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n",
searchingOverflowList?"overflow":"normal", line.get());
@ -5686,12 +5680,10 @@ nsBlockFrame::StealFrame(nsPresContext* aPresContext,
}
// Register removal with the line boxes
PRInt32 count = line->GetChildCount();
line->SetChildCount(--count);
if (count > 0) {
line->NoteFrameRemoved(frame);
if (line->GetChildCount() > 0) {
line->MarkDirty();
}
else {
} else {
// Remove the line box
nsLineBox* lineBox = line;
if (searchingOverflowList) {
@ -5710,8 +5702,7 @@ nsBlockFrame::StealFrame(nsPresContext* aPresContext,
line_end = mLines.end();
line = line_end;
}
}
else {
} else {
line = mLines.erase(line);
}
lineBox->Destroy(aPresContext->PresShell());

Просмотреть файл

@ -354,8 +354,11 @@ protected:
#endif
#endif
nsLineBox* NewLineBox(nsIFrame* aFrame, PRInt32 aCount, bool aIsBlock) {
return NS_NewLineBox(PresContext()->PresShell(), aFrame, aCount, aIsBlock);
nsLineBox* NewLineBox(nsIFrame* aFrame, bool aIsBlock) {
return NS_NewLineBox(PresContext()->PresShell(), aFrame, aIsBlock);
}
nsLineBox* NewLineBox(nsLineBox* aFromLine, nsIFrame* aFrame, PRInt32 aCount) {
return NS_NewLineBox(PresContext()->PresShell(), aFromLine, aFrame, aCount);
}
void FreeLineBox(nsLineBox* aLine) {
aLine->Destroy(PresContext()->PresShell());

Просмотреть файл

@ -55,6 +55,11 @@ static PRInt32 ctorCount;
PRInt32 nsLineBox::GetCtorCount() { return ctorCount; }
#endif
#ifndef _MSC_VER
// static nsLineBox constant; initialized in the header file.
const PRUint32 nsLineBox::kMinChildCountForHashtable;
#endif
nsLineBox::nsLineBox(nsIFrame* aFrame, PRInt32 aCount, bool aIsBlock)
: mFirstChild(aFrame),
mBounds(0, 0, 0, 0),
@ -76,7 +81,7 @@ nsLineBox::nsLineBox(nsIFrame* aFrame, PRInt32 aCount, bool aIsBlock)
#if NS_STYLE_CLEAR_NONE > 0
mFlags.mBreakType = NS_STYLE_CLEAR_NONE;
#endif
SetChildCount(aCount);
mChildCount = aCount;
MarkDirty();
mFlags.mBlock = aIsBlock;
}
@ -84,14 +89,88 @@ nsLineBox::nsLineBox(nsIFrame* aFrame, PRInt32 aCount, bool aIsBlock)
nsLineBox::~nsLineBox()
{
MOZ_COUNT_DTOR(nsLineBox);
if (NS_UNLIKELY(mFlags.mHasHashedFrames)) {
delete mFrames;
}
Cleanup();
}
nsLineBox*
NS_NewLineBox(nsIPresShell* aPresShell, nsIFrame* aFrame,
PRInt32 aCount, bool aIsBlock)
NS_NewLineBox(nsIPresShell* aPresShell, nsIFrame* aFrame, bool aIsBlock)
{
return new (aPresShell)nsLineBox(aFrame, aCount, aIsBlock);
return new (aPresShell) nsLineBox(aFrame, 1, aIsBlock);
}
nsLineBox*
NS_NewLineBox(nsIPresShell* aPresShell, nsLineBox* aFromLine,
nsIFrame* aFrame, PRInt32 aCount)
{
nsLineBox* newLine = new (aPresShell) nsLineBox(aFrame, aCount, false);
if (newLine) {
newLine->NoteFramesMovedFrom(aFromLine);
}
return newLine;
}
void
nsLineBox::StealHashTableFrom(nsLineBox* aFromLine, PRUint32 aFromLineNewCount)
{
MOZ_ASSERT(!mFlags.mHasHashedFrames);
MOZ_ASSERT(GetChildCount() >= PRInt32(aFromLineNewCount));
mFrames = aFromLine->mFrames;
mFlags.mHasHashedFrames = 1;
aFromLine->mFlags.mHasHashedFrames = 0;
aFromLine->mChildCount = aFromLineNewCount;
// remove aFromLine's frames that aren't on this line
nsIFrame* f = aFromLine->mFirstChild;
for (PRUint32 i = 0; i < aFromLineNewCount; f = f->GetNextSibling(), ++i) {
mFrames->RemoveEntry(f);
}
}
void
nsLineBox::NoteFramesMovedFrom(nsLineBox* aFromLine)
{
PRUint32 fromCount = aFromLine->GetChildCount();
PRUint32 toCount = GetChildCount();
MOZ_ASSERT(toCount <= fromCount, "moved more frames than aFromLine has");
PRUint32 fromNewCount = fromCount - toCount;
if (NS_LIKELY(!aFromLine->mFlags.mHasHashedFrames)) {
aFromLine->mChildCount = fromNewCount;
MOZ_ASSERT(toCount < kMinChildCountForHashtable);
} else if (fromNewCount < kMinChildCountForHashtable) {
// aFromLine has a hash table but will not have it after moving the frames
// so this line can steal the hash table if it needs it.
if (toCount >= kMinChildCountForHashtable) {
StealHashTableFrom(aFromLine, fromNewCount);
} else {
delete aFromLine->mFrames;
aFromLine->mFlags.mHasHashedFrames = 0;
aFromLine->mChildCount = fromNewCount;
}
} else {
// aFromLine still needs a hash table.
if (toCount < kMinChildCountForHashtable) {
// remove the moved frames from it
nsIFrame* f = mFirstChild;
for (PRUint32 i = 0; i < toCount; f = f->GetNextSibling(), ++i) {
aFromLine->mFrames->RemoveEntry(f);
}
} else if (toCount <= fromNewCount) {
// This line needs a hash table, allocate a hash table for it since that
// means fewer hash ops.
nsIFrame* f = mFirstChild;
for (PRUint32 i = 0; i < toCount; f = f->GetNextSibling(), ++i) {
aFromLine->mFrames->RemoveEntry(f); // toCount RemoveEntry
}
SwitchToHashtable(); // toCount PutEntry
} else {
// This line needs a hash table, but it's fewer hash ops to steal
// aFromLine's hash table and allocate a new hash table for that line.
StealHashTableFrom(aFromLine, fromNewCount); // fromNewCount RemoveEntry
aFromLine->SwitchToHashtable(); // fromNewCount PutEntry
}
}
}
// Overloaded new operator. Uses an arena (which comes from the presShell)
@ -102,21 +181,11 @@ nsLineBox::operator new(size_t sz, nsIPresShell* aPresShell) CPP_THROW_NEW
return aPresShell->AllocateMisc(sz);
}
// Overloaded delete operator. Doesn't actually free the memory, because we
// use an arena
void
nsLineBox::operator delete(void* aPtr, size_t sz)
{
}
void
nsLineBox::Destroy(nsIPresShell* aPresShell)
{
// Destroy the object. This won't actually free the memory, though
delete this;
// Have the pres shell recycle the memory
aPresShell->FreeMisc(sizeof(*this), (void*)this);
this->nsLineBox::~nsLineBox();
aPresShell->FreeMisc(sizeof(*this), this);
}
void
@ -365,10 +434,18 @@ nsLineBox::RFindLineContaining(nsIFrame* aFrame,
PRInt32* aFrameIndexInLine)
{
NS_PRECONDITION(aFrame, "null ptr");
nsIFrame* curFrame = aLastFrameBeforeEnd;
while (aBegin != aEnd) {
--aEnd;
NS_ASSERTION(aEnd->LastChild() == curFrame, "Unexpected curFrame");
if (NS_UNLIKELY(aEnd->mFlags.mHasHashedFrames) &&
!aEnd->Contains(aFrame)) {
if (aEnd->mFirstChild) {
curFrame = aEnd->mFirstChild->GetPrevSibling();
}
continue;
}
// i is the index of curFrame in aEnd
PRInt32 i = aEnd->GetChildCount() - 1;
while (i >= 0) {
@ -379,6 +456,7 @@ nsLineBox::RFindLineContaining(nsIFrame* aFrame,
--i;
curFrame = curFrame->GetPrevSibling();
}
MOZ_ASSERT(!aEnd->mFlags.mHasHashedFrames, "Contains lied to us!");
}
*aFrameIndexInLine = -1;
return false;

Просмотреть файл

@ -182,9 +182,21 @@ protected:
need to rearrange the mBits bitfield;
#endif
// Funtion to create a line box
/**
* Function to create a line box and initialize it with a single frame.
* If the frame was moved from another line then you're responsible
* for notifying that line using NoteFrameRemoved(). Alternatively,
* it's better to use the next function that does that for you in an
* optimal way.
*/
nsLineBox* NS_NewLineBox(nsIPresShell* aPresShell, nsIFrame* aFrame,
PRInt32 aCount, bool aIsBlock);
bool aIsBlock);
/**
* Function to create a line box and initialize it with aCount frames
* that are currently on aFromLine.
*/
nsLineBox* NS_NewLineBox(nsIPresShell* aPresShell, nsLineBox* aFromLine,
nsIFrame* aFrame, PRInt32 aCount);
class nsLineList;
@ -232,13 +244,14 @@ private:
// Overloaded new operator. Uses an arena (which comes from the presShell)
// to perform the allocation.
void* operator new(size_t sz, nsIPresShell* aPresShell) CPP_THROW_NEW;
void operator delete(void* aPtr, size_t sz);
void operator delete(void* aPtr, size_t sz) MOZ_DELETE;
public:
// Use these two functions to allocate and destroy line boxes
// Use these functions to allocate and destroy line boxes
friend nsLineBox* NS_NewLineBox(nsIPresShell* aPresShell, nsIFrame* aFrame,
PRInt32 aCount, bool aIsBlock);
bool aIsBlock);
friend nsLineBox* NS_NewLineBox(nsIPresShell* aPresShell, nsLineBox* aFromLine,
nsIFrame* aFrame, PRInt32 aCount);
void Destroy(nsIPresShell* aPresShell);
// mBlock bit
@ -284,7 +297,6 @@ public:
// mImpactedByFloat bit
void SetLineIsImpactedByFloat(bool aValue) {
NS_ASSERTION((false==aValue || true==aValue), "somebody is playing fast and loose with bools and bits!");
mFlags.mImpactedByFloat = aValue;
}
bool IsImpactedByFloat() const {
@ -293,7 +305,6 @@ public:
// mLineWrapped bit
void SetLineWrapped(bool aOn) {
NS_ASSERTION((false==aOn || true==aOn), "somebody is playing fast and loose with bools and bits!");
mFlags.mLineWrapped = aOn;
}
bool IsLineWrapped() const {
@ -302,7 +313,6 @@ public:
// mInvalidateTextRuns bit
void SetInvalidateTextRuns(bool aOn) {
NS_ASSERTION((false==aOn || true==aOn), "somebody is playing fast and loose with bools and bits!");
mFlags.mInvalidateTextRuns = aOn;
}
bool GetInvalidateTextRuns() const {
@ -344,20 +354,76 @@ public:
return mFlags.mHadFloatPushed;
}
private:
// Add a hash table for fast lookup when the line has more frames than this.
static const PRUint32 kMinChildCountForHashtable = 200;
// mChildCount value
/**
* Take ownership of aFromLine's hash table and remove the frames that
* stay on aFromLine from it, i.e. aFromLineNewCount frames starting with
* mFirstChild. This method is used to optimize moving a large number
* of frames from one line to the next.
*/
void StealHashTableFrom(nsLineBox* aFromLine, PRUint32 aFromLineNewCount);
/**
* Does the equivalent of this->NoteFrameAdded and aFromLine->NoteFrameRemoved
* for each frame on this line, but in a optimized way.
*/
void NoteFramesMovedFrom(nsLineBox* aFromLine);
void SwitchToHashtable()
{
MOZ_ASSERT(!mFlags.mHasHashedFrames);
PRUint32 count = GetChildCount();
mFrames = new nsTHashtable< nsPtrHashKey<nsIFrame> >();
mFlags.mHasHashedFrames = 1;
PRUint32 minSize =
NS_MAX(kMinChildCountForHashtable, PRUint32(PL_DHASH_MIN_SIZE));
mFrames->Init(NS_MAX(count, minSize));
for (nsIFrame* f = mFirstChild; count-- > 0; f = f->GetNextSibling()) {
mFrames->PutEntry(f);
}
}
void SwitchToCounter() {
MOZ_ASSERT(mFlags.mHasHashedFrames);
PRUint32 count = GetChildCount();
delete mFrames;
mFlags.mHasHashedFrames = 0;
mChildCount = count;
}
public:
PRInt32 GetChildCount() const {
return (PRInt32) mFlags.mChildCount;
return NS_UNLIKELY(mFlags.mHasHashedFrames) ? mFrames->Count() : mChildCount;
}
void SetChildCount(PRInt32 aNewCount) {
if (aNewCount < 0) {
NS_WARNING("negative child count");
aNewCount = 0;
/**
* Register that aFrame is now on this line.
*/
void NoteFrameAdded(nsIFrame* aFrame) {
if (NS_UNLIKELY(mFlags.mHasHashedFrames)) {
mFrames->PutEntry(aFrame);
} else {
if (++mChildCount >= kMinChildCountForHashtable) {
SwitchToHashtable();
}
if (aNewCount > LINE_MAX_CHILD_COUNT) {
aNewCount = LINE_MAX_CHILD_COUNT;
}
mFlags.mChildCount = aNewCount;
}
/**
* Register that aFrame is not on this line anymore.
*/
void NoteFrameRemoved(nsIFrame* aFrame) {
MOZ_ASSERT(GetChildCount() > 0);
if (NS_UNLIKELY(mFlags.mHasHashedFrames)) {
mFrames->RemoveEntry(aFrame);
if (mFrames->Count() < kMinChildCountForHashtable) {
SwitchToCounter();
}
} else {
--mChildCount;
}
}
// mBreakType value
@ -473,10 +539,13 @@ public:
nsIFrame* LastChild() const;
#endif
private:
PRInt32 IndexOf(nsIFrame* aFrame) const;
public:
bool Contains(nsIFrame* aFrame) const {
return IndexOf(aFrame) >= 0;
return NS_UNLIKELY(mFlags.mHasHashedFrames) ? mFrames->Contains(aFrame)
: IndexOf(aFrame) >= 0;
}
// whether the line box is "logically" empty (just like nsIFrame::IsEmpty)
@ -504,6 +573,12 @@ public:
nsRect mBounds;
// mFlags.mHasHashedFrames says which one to use
union {
nsTHashtable< nsPtrHashKey<nsIFrame> >* mFrames;
PRUint32 mChildCount;
};
struct FlagBits {
PRUint32 mDirty : 1;
PRUint32 mPreviousMarginDirty : 1;
@ -521,10 +596,8 @@ public:
// Indicates that this line *may* have a placeholder for a float
// that was pushed to a later column or page.
PRUint32 mHadFloatPushed : 1;
PRUint32 mHasHashedFrames: 1;
PRUint32 mBreakType : 4;
// FIXME: Move this out of FlagBits
PRUint32 mChildCount;
};
struct ExtraData {