Bug 317278. In some cases we push float placeholders from one line to the next after we have already placed their floats on the line. In such situations, reset the space manager and force the line to reflow again so those floats don't get placed. r+sr=dbaron

This commit is contained in:
roc+%cs.cmu.edu 2006-06-19 00:02:49 +00:00
Родитель 972f9e353f
Коммит 5053e9f8ff
6 изменённых файлов: 210 добавлений и 76 удалений

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

@ -3756,12 +3756,6 @@ nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState,
return rv;
}
#define LINE_REFLOW_OK 0
#define LINE_REFLOW_STOP 1
#define LINE_REFLOW_REDO 2
// a frame was complete, but truncated and not at the top of a page
#define LINE_REFLOW_TRUNCATED 3
nsresult
nsBlockFrame::ReflowInlineFrames(nsBlockReflowState& aState,
line_iterator aLine,
@ -3775,45 +3769,64 @@ nsBlockFrame::ReflowInlineFrames(nsBlockReflowState& aState,
#ifdef DEBUG
PRInt32 spins = 0;
#endif
PRUint8 lineReflowStatus = LINE_REFLOW_REDO;
PRBool didRedo = PR_FALSE;
LineReflowStatus lineReflowStatus = LINE_REFLOW_REDO_NEXT_BAND;
PRBool movedPastFloat = PR_FALSE;
do {
// Once upon a time we allocated the first 30 nsLineLayout objects
// on the stack, and then we switched to the heap. At that time
// these objects were large (1100 bytes on a 32 bit system).
// Then the nsLineLayout object was shrunk to 156 bytes by
// removing some internal buffers. Given that it is so much
// smaller, the complexity of 2 different ways of allocating
// no longer makes sense. Now we always allocate on the stack
nsLineLayout lineLayout(aState.mPresContext,
aState.mReflowState.mSpaceManager,
&aState.mReflowState,
aState.GetFlag(BRS_COMPUTEMAXELEMENTWIDTH));
lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
rv = DoReflowInlineFrames(aState, lineLayout, aLine,
aKeepReflowGoing, &lineReflowStatus,
aUpdateMaximumWidth, aDamageDirtyArea);
lineLayout.EndLineReflow();
if (LINE_REFLOW_REDO == lineReflowStatus) {
didRedo = PR_TRUE;
}
PRBool allowPullUp = PR_TRUE;
do {
aState.mReflowState.mSpaceManager->PushState();
// Once upon a time we allocated the first 30 nsLineLayout objects
// on the stack, and then we switched to the heap. At that time
// these objects were large (1100 bytes on a 32 bit system).
// Then the nsLineLayout object was shrunk to 156 bytes by
// removing some internal buffers. Given that it is so much
// smaller, the complexity of 2 different ways of allocating
// no longer makes sense. Now we always allocate on the stack
nsLineLayout lineLayout(aState.mPresContext,
aState.mReflowState.mSpaceManager,
&aState.mReflowState,
aState.GetFlag(BRS_COMPUTEMAXELEMENTWIDTH));
lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
rv = DoReflowInlineFrames(aState, lineLayout, aLine,
aKeepReflowGoing, &lineReflowStatus,
aUpdateMaximumWidth, aDamageDirtyArea,
allowPullUp);
lineLayout.EndLineReflow();
if (LINE_REFLOW_REDO_NO_PULL == lineReflowStatus ||
LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus) {
// restore the space manager state
aState.mReflowState.mSpaceManager->PopState();
// Clear out below-current-line-floats
aState.mBelowCurrentLineFloats.DeleteAll();
} else {
// We succeeded, so pop the saved state off the space manager
// without restoring it
aState.mReflowState.mSpaceManager->DiscardState();
}
#ifdef DEBUG
spins++;
if (1000 == spins) {
ListTag(stdout);
printf(": yikes! spinning on a line over 1000 times!\n");
NS_ABORT();
}
spins++;
if (1000 == spins) {
ListTag(stdout);
printf(": yikes! spinning on a line over 1000 times!\n");
NS_ABORT();
}
#endif
} while (NS_SUCCEEDED(rv) && LINE_REFLOW_REDO == lineReflowStatus);
// Don't allow pullup on a subsequent LINE_REFLOW_REDO_NO_PULL pass
allowPullUp = PR_FALSE;
} while (NS_SUCCEEDED(rv) && LINE_REFLOW_REDO_NO_PULL == lineReflowStatus);
// If we did at least one REDO, then the line did not fit next to some float.
if (LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus) {
movedPastFloat = PR_TRUE;
}
} while (NS_SUCCEEDED(rv) && LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus);
// If we did at least one REDO_FOR_FLOAT, then the line did not fit next to some float.
// Mark it as impacted by a float, even if it no longer is next to a float.
if (didRedo) {
if (movedPastFloat) {
aLine->SetLineIsImpactedByFloat(PR_TRUE);
}
@ -3844,9 +3857,10 @@ nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
line_iterator aLine,
PRBool* aKeepReflowGoing,
PRUint8* aLineReflowStatus,
LineReflowStatus* aLineReflowStatus,
PRBool aUpdateMaximumWidth,
PRBool aDamageDirtyArea)
PRBool aDamageDirtyArea,
PRBool aAllowPullUp)
{
// Forget all of the floats on the line
aLine->FreeFloats(aState.mFloatCacheFreeList);
@ -3896,7 +3910,7 @@ nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState,
// Reflow the frames that are already on the line first
nsresult rv = NS_OK;
PRUint8 lineReflowStatus = LINE_REFLOW_OK;
LineReflowStatus lineReflowStatus = LINE_REFLOW_OK;
PRInt32 i;
nsIFrame* frame = aLine->mFirstChild;
aLine->SetHasPercentageChild(PR_FALSE); // To be set by ReflowInlineFrame below
@ -3943,7 +3957,7 @@ nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState,
}
// Don't pull up new frames into lines with continuation placeholders
if (!isContinuingPlaceholders) {
if (!isContinuingPlaceholders && aAllowPullUp) {
// Pull frames and reflow them until we can't
while (LINE_REFLOW_OK == lineReflowStatus) {
rv = PullFrame(aState, aLine, aDamageDirtyArea, frame);
@ -3975,7 +3989,7 @@ nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState,
}
}
if (LINE_REFLOW_REDO == lineReflowStatus) {
if (LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus) {
// This happens only when we have a line that is impacted by
// floats and the first element in the line doesn't fit with
// the floats.
@ -3987,7 +4001,6 @@ nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState,
NS_ASSERTION(NS_UNCONSTRAINEDSIZE != aState.mAvailSpaceRect.height,
"unconstrained height on totally empty line");
if (aState.mAvailSpaceRect.height > 0) {
aState.mY += aState.mAvailSpaceRect.height;
} else {
@ -4020,6 +4033,13 @@ nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState,
// push the line and return now instead of later on after we are
// past the float.
}
else if (LINE_REFLOW_REDO_NO_PULL == lineReflowStatus) {
// We don't want to advance by the bottom margin anymore (we did it
// once at the beginning of this function, which will just be called
// again), and we certainly don't want to go back if it's negative
// (infinite loop, bug 153429).
aState.mPrevBottomMargin.Zero();
}
else if (LINE_REFLOW_TRUNCATED != lineReflowStatus) {
// If we are propagating out a break-before status then there is
// no point in placing the line.
@ -4047,9 +4067,9 @@ nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
line_iterator aLine,
nsIFrame* aFrame,
PRUint8* aLineReflowStatus)
LineReflowStatus* aLineReflowStatus)
{
NS_ENSURE_ARG_POINTER(aFrame);
NS_ENSURE_ARG_POINTER(aFrame);
*aLineReflowStatus = LINE_REFLOW_OK;
@ -4142,12 +4162,12 @@ nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState,
// be trying to place content where there's no room (e.g. on a
// line with wide floats). Inform the caller to reflow the
// line after skipping past a float.
*aLineReflowStatus = LINE_REFLOW_REDO;
*aLineReflowStatus = LINE_REFLOW_REDO_NEXT_BAND;
}
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);
rv = SplitLine(aState, aLineLayout, aLine, aFrame, aLineReflowStatus);
if (NS_FAILED(rv)) {
return rv;
}
@ -4190,7 +4210,7 @@ nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState,
}
// Split line, but after the frame just reflowed
rv = SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling());
rv = SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), aLineReflowStatus);
if (NS_FAILED(rv)) {
return rv;
}
@ -4240,7 +4260,7 @@ nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState,
if (splitLine) {
// Split line after the current frame
*aLineReflowStatus = LINE_REFLOW_STOP;
rv = SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling());
rv = SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), aLineReflowStatus);
if (NS_FAILED(rv)) {
return rv;
}
@ -4322,11 +4342,35 @@ nsBlockFrame::SplitPlaceholder(nsBlockReflowState& aState,
return NS_OK;
}
static nsFloatCache*
GetLastFloat(nsLineBox* aLine)
{
nsFloatCache* fc = aLine->GetFirstFloat();
while (fc && fc->Next()) {
fc = fc->Next();
}
return fc;
}
static PRBool
CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine, nsFloatCache* aFC)
{
if (!aFC)
return PR_TRUE;
for (nsIFrame* f = aFC->mPlaceholder; f; f = f->GetParent()) {
if (f->GetParent() == aBlock)
return aLine->Contains(f);
}
NS_ASSERTION(PR_FALSE, "aBlock is not an ancestor of aFrame!");
return PR_TRUE;
}
nsresult
nsBlockFrame::SplitLine(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
line_iterator aLine,
nsIFrame* aFrame)
nsIFrame* aFrame,
LineReflowStatus* aLineReflowStatus)
{
NS_ABORT_IF_FALSE(aLine->IsInline(), "illegal SplitLine on block line");
@ -4373,6 +4417,17 @@ nsBlockFrame::SplitLine(nsBlockReflowState& aState,
// Let line layout know that some frames are no longer part of its
// state.
aLineLayout.SplitLineTo(aLine->GetChildCount());
// If floats have been placed whose placeholders have been pushed to the new
// line, we need to reflow the old line again. We don't want to look at the
// frames in the new line, because as a large paragraph is laid out the
// we'd get O(N^2) performance. So instead we just check that the last
// float and the last below-current-line float are still in aLine.
if (!CheckPlaceholderInLine(this, aLine, GetLastFloat(aLine)) ||
!CheckPlaceholderInLine(this, aLine, aState.mBelowCurrentLineFloats.Tail())) {
*aLineReflowStatus = LINE_REFLOW_REDO_NO_PULL;
}
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif

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

@ -52,6 +52,24 @@
#include "nsCSSPseudoElements.h"
#include "nsStyleSet.h"
enum LineReflowStatus {
// The line was completely reflowed and fit in available width, and we should
// try to pull up content from the next line if possible.
LINE_REFLOW_OK,
// The line was completely reflowed and fit in available width, but we should
// not try to pull up content from the next line.
LINE_REFLOW_STOP,
// We need to reflow the line again at its current vertical position. The
// new reflow should not try to pull up any frames from the next line.
LINE_REFLOW_REDO_NO_PULL,
// We need to reflow the line again at a lower vertical postion where there
// may be more horizontal space due to different float configuration.
LINE_REFLOW_REDO_NEXT_BAND,
// The line did not fit in the available vertical space. Try pushing it to
// the next page or column if it's not the first line on the current page/column.
LINE_REFLOW_TRUNCATED
};
class nsBlockReflowState;
class nsBulletFrame;
class nsLineBox;
@ -466,15 +484,16 @@ protected:
nsLineLayout& aLineLayout,
line_iterator aLine,
PRBool* aKeepReflowGoing,
PRUint8* aLineReflowStatus,
LineReflowStatus* aLineReflowStatus,
PRBool aUpdateMaximumWidth,
PRBool aDamageDirtyArea);
PRBool aDamageDirtyArea,
PRBool aAllowPullUp);
nsresult ReflowInlineFrame(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
line_iterator aLine,
nsIFrame* aFrame,
PRUint8* aLineReflowStatus);
LineReflowStatus* aLineReflowStatus);
// An incomplete aReflowStatus indicates the float should be split
// but only if the available height is constrained.
@ -501,7 +520,8 @@ protected:
nsresult SplitLine(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
line_iterator aLine,
nsIFrame* aFrame);
nsIFrame* aFrame,
LineReflowStatus* aLineReflowStatus);
nsresult PullFrame(nsBlockReflowState& aState,
line_iterator aLine,

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

@ -1102,7 +1102,7 @@ nsBlockReflowState::FlowAndPlaceFloat(nsFloatCache* aFloatCache,
* Place below-current-line floats.
*/
PRBool
nsBlockReflowState::PlaceBelowCurrentLineFloats(nsFloatCacheList& aList, PRBool aForceFit)
nsBlockReflowState::PlaceBelowCurrentLineFloats(nsFloatCacheFreeList& aList, PRBool aForceFit)
{
nsFloatCache* fc = aList.Head();
while (fc) {

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

@ -99,7 +99,7 @@ public:
PRBool* aIsLeftFloat,
nsReflowStatus& aReflowStatus,
PRBool aForceFit);
PRBool PlaceBelowCurrentLineFloats(nsFloatCacheList& aFloats, PRBool aForceFit);
PRBool PlaceBelowCurrentLineFloats(nsFloatCacheFreeList& aFloats, PRBool aForceFit);
// Returns the first coordinate >= aY that clears the
// indicated floats.

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

@ -875,14 +875,20 @@ nsFloatCacheList::nsFloatCacheList() :
nsFloatCacheList::~nsFloatCacheList()
{
nsFloatCache* fc = mHead;
while (fc) {
nsFloatCache* next = fc->mNext;
delete fc;
fc = next;
DeleteAll();
MOZ_COUNT_DTOR(nsFloatCacheList);
}
void
nsFloatCacheList::DeleteAll()
{
nsFloatCache* c = mHead;
while (c) {
nsFloatCache* next = c->Next();
delete c;
c = next;
}
mHead = nsnull;
MOZ_COUNT_DTOR(nsFloatCacheList);
}
nsFloatCache*
@ -929,17 +935,24 @@ nsFloatCacheList::Find(nsIFrame* aOutOfFlowFrame)
return fc;
}
void
nsFloatCacheList::Remove(nsFloatCache* aElement)
nsFloatCache*
nsFloatCacheList::RemoveAndReturnPrev(nsFloatCache* aElement)
{
nsFloatCache** fcp = &mHead;
nsFloatCache* fc;
while (nsnull != (fc = *fcp)) {
NS_ASSERTION(!aElement->mNext, "Can only remove a singleton element");
nsFloatCache* fc = mHead;
nsFloatCache* prev = nsnull;
while (fc) {
if (fc == aElement) {
*fcp = fc->mNext;
break;
if (prev) {
prev->mNext = fc->mNext;
} else {
mHead = fc->mNext;
}
return prev;
}
fcp = &fc->mNext;
prev = fc;
fc = fc->mNext;
}
}
@ -975,6 +988,22 @@ nsFloatCacheFreeList::Append(nsFloatCacheList& aList)
aList.mHead = nsnull;
}
void
nsFloatCacheFreeList::Remove(nsFloatCache* aElement)
{
nsFloatCache* prev = nsFloatCacheList::RemoveAndReturnPrev(aElement);
if (mTail == aElement) {
mTail = prev;
}
}
void
nsFloatCacheFreeList::DeleteAll()
{
nsFloatCacheList::DeleteAll();
mTail = nsnull;
}
nsFloatCache*
nsFloatCacheFreeList::Alloc()
{

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

@ -120,12 +120,14 @@ public:
nsFloatCache* Tail() const;
void DeleteAll();
nsFloatCache* Find(nsIFrame* aOutOfFlowFrame);
// Remove a nsFloatCache from this list. Deleting this nsFloatCache
// becomes the caller's responsibility.
void Remove(nsFloatCache* aElement);
void Remove(nsFloatCache* aElement) { RemoveAndReturnPrev(aElement); }
// Steal away aList's nsFloatCache objects and put them in this
// list. aList must not be empty.
void Append(nsFloatCacheFreeList& aList);
@ -133,12 +135,18 @@ public:
protected:
nsFloatCache* mHead;
// Remove a nsFloatCache from this list. Deleting this nsFloatCache
// becomes the caller's responsibility. Returns the nsFloatCache that was
// before aElement, or nsnull if aElement was the first.
nsFloatCache* RemoveAndReturnPrev(nsFloatCache* aElement);
friend class nsFloatCacheFreeList;
};
//---------------------------------------
// Like nsFloatCacheList, but with fast access to the tail
class nsFloatCacheFreeList : public nsFloatCacheList {
class nsFloatCacheFreeList : private nsFloatCacheList {
public:
#ifdef NS_BUILD_REFCNT_LOGGING
nsFloatCacheFreeList();
@ -148,15 +156,37 @@ public:
~nsFloatCacheFreeList() { }
#endif
// Reimplement trivial functions
PRBool IsEmpty() const {
return nsnull == mHead;
}
nsFloatCache* Head() const {
return mHead;
}
nsFloatCache* Tail() const {
return mTail;
}
PRBool NotEmpty() const {
return nsnull != mHead;
}
void DeleteAll();
// Steal away aList's nsFloatCache objects and put them on this
// free-list. aList must not be empty.
void Append(nsFloatCacheList& aList);
void Append(nsFloatCache* aFloatCache);
// Allocate a new nsFloatCache object
nsFloatCache* Alloc();
void Remove(nsFloatCache* aElement);
// Remove an nsFloatCache object from this list and return it, or create
// a new one if this one is empty;
nsFloatCache* Alloc();
protected:
nsFloatCache* mTail;