Bug 403426. We should clear whitespace status when we reset the linebreaker. If a final break opportunity exists we should save it and forward it to the line layout. Relanding with a fix so hopefully we won't crash Tp this time. r=smontagu

This commit is contained in:
roc+@cs.cmu.edu 2007-12-03 00:22:07 -08:00
Родитель d68e211e1e
Коммит b59ab6a038
7 изменённых файлов: 93 добавлений и 23 удалений

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

@ -184,8 +184,11 @@ public:
* After this call, this linebreaker can be reused. * After this call, this linebreaker can be reused.
* This must be called at least once between any call to AppendText() and * This must be called at least once between any call to AppendText() and
* destroying the object. * destroying the object.
* @param aTrailingBreak this is set to true when there is a break opportunity
* at the end of the text. This will normally only be declared true when there
* is breakable whitespace at the end.
*/ */
nsresult Reset() { return FlushCurrentWord(); } nsresult Reset(PRBool* aTrailingBreak);
private: private:
// This is a list of text sources that make up the "current word" (i.e., // This is a list of text sources that make up the "current word" (i.e.,

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

@ -391,7 +391,8 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUint8* aText, PRUint32 aL
} }
nsresult nsresult
nsLineBreaker::AppendInvisibleWhitespace(PRUint32 aFlags) { nsLineBreaker::AppendInvisibleWhitespace(PRUint32 aFlags)
{
nsresult rv = FlushCurrentWord(); nsresult rv = FlushCurrentWord();
if (NS_FAILED(rv)) if (NS_FAILED(rv))
return rv; return rv;
@ -401,5 +402,18 @@ nsLineBreaker::AppendInvisibleWhitespace(PRUint32 aFlags) {
mBreakHere = PR_TRUE; mBreakHere = PR_TRUE;
} }
mAfterBreakableSpace = isBreakableSpace; mAfterBreakableSpace = isBreakableSpace;
return NS_OK; return NS_OK;
}
nsresult
nsLineBreaker::Reset(PRBool* aTrailingBreak)
{
nsresult rv = FlushCurrentWord();
if (NS_FAILED(rv))
return rv;
*aTrailingBreak = mBreakHere || mAfterBreakableSpace;
mBreakHere = PR_FALSE;
mAfterBreakableSpace = PR_FALSE;
return NS_OK;
} }

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

@ -603,10 +603,16 @@ public:
mDoubleByteText = PR_FALSE; mDoubleByteText = PR_FALSE;
} }
void ResetLineBreaker() { void ResetLineBreaker() {
mLineBreaker.Reset(); PRBool trailingBreak;
mLineBreaker.Reset(&trailingBreak);
} }
void AccumulateRunInfo(nsTextFrame* aFrame); void AccumulateRunInfo(nsTextFrame* aFrame);
void BuildTextRunForFrames(void* aTextBuffer); /**
* @return null to indicate either textrun construction failed or
* we constructed just a partial textrun to set up linebreaker and other
* state for following textruns.
*/
gfxTextRun* BuildTextRunForFrames(void* aTextBuffer);
void AssignTextRun(gfxTextRun* aTextRun); void AssignTextRun(gfxTextRun* aTextRun);
nsTextFrame* GetNextBreakBeforeFrame(PRUint32* aIndex); nsTextFrame* GetNextBreakBeforeFrame(PRUint32* aIndex);
void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, PRBool aIsExistingTextRun, void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, PRBool aIsExistingTextRun,
@ -691,9 +697,8 @@ private:
gfxContext* mContext; gfxContext* mContext;
nsIFrame* mLineContainer; nsIFrame* mLineContainer;
nsTextFrame* mLastFrame; nsTextFrame* mLastFrame;
// The common ancestor of the current frame and the previous text frame // The common ancestor of the current frame and the previous leaf frame
// on the line, if there's no non-text frame boundaries in between. Otherwise // on the line, or null if there was no previous leaf frame.
// null.
nsIFrame* mCommonAncestorWithLastFrame; nsIFrame* mCommonAncestorWithLastFrame;
// mMaxTextLength is an upper bound on the size of the text in all mapped frames // mMaxTextLength is an upper bound on the size of the text in all mapped frames
PRUint32 mMaxTextLength; PRUint32 mMaxTextLength;
@ -1028,26 +1033,35 @@ void BuildTextRunsScanner::FlushFrames(PRBool aFlushLineBreaks)
if (mMappedFlows.Length() == 0) if (mMappedFlows.Length() == 0)
return; return;
gfxTextRun* textRun;
if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun && if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
((mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0) == ((mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0) ==
mCurrentRunTrimLeadingWhitespace && mCurrentRunTrimLeadingWhitespace &&
IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) { IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
// Optimization: We do not need to (re)build the textrun. // Optimization: We do not need to (re)build the textrun.
textRun = mCurrentFramesAllSameTextRun;
// Feed this run's text into the linebreaker to provide context. This also // Feed this run's text into the linebreaker to provide context. This also
// updates mTrimNextRunLeadingWhitespace appropriately. // updates mTrimNextRunLeadingWhitespace appropriately.
SetupBreakSinksForTextRun(mCurrentFramesAllSameTextRun, PR_TRUE, PR_FALSE); SetupBreakSinksForTextRun(textRun, PR_TRUE, PR_FALSE);
mTrimNextRunLeadingWhitespace = mTrimNextRunLeadingWhitespace =
(mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE) != 0; (textRun->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE) != 0;
} else { } else {
nsAutoTArray<PRUint8,BIG_TEXT_NODE_SIZE> buffer; nsAutoTArray<PRUint8,BIG_TEXT_NODE_SIZE> buffer;
if (!buffer.AppendElements(mMaxTextLength*(mDoubleByteText ? 2 : 1))) if (!buffer.AppendElements(mMaxTextLength*(mDoubleByteText ? 2 : 1)))
return; return;
BuildTextRunForFrames(buffer.Elements()); textRun = BuildTextRunForFrames(buffer.Elements());
} }
if (aFlushLineBreaks) { if (aFlushLineBreaks) {
mLineBreaker.Reset(); PRBool trailingLineBreak;
nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
// textRun may be null for various reasons, including because we constructed
// a partial textrun just to get the linebreaker and other state set up
// to build the next textrun.
if (NS_SUCCEEDED(rv) && trailingLineBreak && textRun) {
textRun->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK);
}
PRUint32 i; PRUint32 i;
for (i = 0; i < mBreakSinks.Length(); ++i) { for (i = 0; i < mBreakSinks.Length(); ++i) {
if (!mBreakSinks[i]->mExistingTextRun || mBreakSinks[i]->mChangedBreaks) { if (!mBreakSinks[i]->mExistingTextRun || mBreakSinks[i]->mChangedBreaks) {
@ -1067,7 +1081,7 @@ void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame)
mMaxTextLength += aFrame->GetContentLength(); mMaxTextLength += aFrame->GetContentLength();
mDoubleByteText |= aFrame->GetContent()->GetText()->Is2b(); mDoubleByteText |= aFrame->GetContent()->GetText()->Is2b();
mLastFrame = aFrame; mLastFrame = aFrame;
mCommonAncestorWithLastFrame = aFrame; mCommonAncestorWithLastFrame = aFrame->GetParent();
MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
NS_ASSERTION(mappedFlow->mStartFrame == aFrame || NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
@ -1198,12 +1212,12 @@ void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame)
PRBool descendInto = PR_TRUE; PRBool descendInto = PR_TRUE;
if (!continueTextRun) { if (!continueTextRun) {
FlushFrames(PR_TRUE); FlushFrames(PR_TRUE);
mCommonAncestorWithLastFrame = nsnull; mCommonAncestorWithLastFrame = aFrame;
mTrimNextRunLeadingWhitespace = PR_FALSE;
// XXX do we need this? are there frames we need to descend into that aren't // XXX do we need this? are there frames we need to descend into that aren't
// float-containing-blocks? // float-containing-blocks?
descendInto = !aFrame->IsFloatContainingBlock(); descendInto = !aFrame->IsFloatContainingBlock();
mStartOfLine = PR_FALSE; mStartOfLine = PR_FALSE;
mTrimNextRunLeadingWhitespace = PR_FALSE;
} }
if (descendInto) { if (descendInto) {
@ -1215,7 +1229,7 @@ void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame)
if (!continueTextRun) { if (!continueTextRun) {
FlushFrames(PR_TRUE); FlushFrames(PR_TRUE);
mCommonAncestorWithLastFrame = nsnull; mCommonAncestorWithLastFrame = aFrame;
mTrimNextRunLeadingWhitespace = PR_FALSE; mTrimNextRunLeadingWhitespace = PR_FALSE;
} }
@ -1322,7 +1336,7 @@ GetFontMetrics(gfxFontGroup* aFontGroup)
return font->GetMetrics(); return font->GetMetrics();
} }
void gfxTextRun*
BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer) BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
{ {
gfxSkipCharsBuilder builder; gfxSkipCharsBuilder builder;
@ -1421,7 +1435,7 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
nsAutoTArray<PRUint8,BIG_TEXT_NODE_SIZE> tempBuf; nsAutoTArray<PRUint8,BIG_TEXT_NODE_SIZE> tempBuf;
if (!tempBuf.AppendElements(contentLength)) { if (!tempBuf.AppendElements(contentLength)) {
DestroyUserData(userData); DestroyUserData(userData);
return; return nsnull;
} }
PRUint8* bufStart = tempBuf.Elements(); PRUint8* bufStart = tempBuf.Elements();
PRUint8* end = nsTextFrameUtils::TransformText( PRUint8* end = nsTextFrameUtils::TransformText(
@ -1456,7 +1470,7 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
// Check for out-of-memory in gfxSkipCharsBuilder // Check for out-of-memory in gfxSkipCharsBuilder
if (!builder.IsOK()) { if (!builder.IsOK()) {
DestroyUserData(userData); DestroyUserData(userData);
return; return nsnull;
} }
void* finalUserData; void* finalUserData;
@ -1491,7 +1505,7 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame); gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame);
if (!fontGroup) { if (!fontGroup) {
DestroyUserData(userData); DestroyUserData(userData);
return; return nsnull;
} }
if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) { if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) {
@ -1598,7 +1612,7 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
} }
if (!textRun) { if (!textRun) {
DestroyUserData(userData); DestroyUserData(userData);
return; return nsnull;
} }
// We have to set these up after we've created the textrun, because // We have to set these up after we've created the textrun, because
@ -1615,12 +1629,13 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
gTextRuns->RemoveFromCache(textRun); gTextRuns->RemoveFromCache(textRun);
delete textRun; delete textRun;
DestroyUserData(userData); DestroyUserData(userData);
return; return nsnull;
} }
// Actually wipe out the textruns associated with the mapped frames and associate // Actually wipe out the textruns associated with the mapped frames and associate
// those frames with this text run. // those frames with this text run.
AssignTextRun(textRun); AssignTextRun(textRun);
return textRun;
} }
static PRBool static PRBool
@ -5508,6 +5523,19 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
lineLayout.NotifyOptionalBreakPosition(mContent, offset + length, lineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth); textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth);
} }
PRBool breakAfter = PR_FALSE;
if ((charsFit == length && transformedOffset + transformedLength == mTextRun->GetLength() &&
(mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK))) {
// Note that because we didn't break, we can be sure that (thanks to the
// code up above) textMetrics.mAdvanceWidth includes the width of any
// trailing whitespace. So we need to subtract trimmableWidth here
// because if we did break at this point, that much width would be trimmed.
if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) {
breakAfter = PR_TRUE;
} else {
lineLayout.NotifyOptionalBreakPosition(mContent, offset + length, PR_TRUE);
}
}
if (completedFirstLetter) { if (completedFirstLetter) {
lineLayout.SetFirstLetterStyleOK(PR_FALSE); lineLayout.SetFirstLetterStyleOK(PR_FALSE);
} }
@ -5523,6 +5551,8 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
// Ends in \n // Ends in \n
aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus); aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
lineLayout.SetLineEndsInBR(PR_TRUE); lineLayout.SetLineEndsInBR(PR_TRUE);
} else if (breakAfter) {
aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
} }
// Compute space and letter counts for justification, if required // Compute space and letter counts for justification, if required

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

@ -73,7 +73,12 @@ public:
TEXT_TRAILING_WHITESPACE = 0x400000, TEXT_TRAILING_WHITESPACE = 0x400000,
TEXT_COMPRESSED_LEADING_WHITESPACE = 0x800000, TEXT_COMPRESSED_LEADING_WHITESPACE = 0x800000,
TEXT_NO_BREAKS = 0x1000000, TEXT_NO_BREAKS = 0x1000000,
TEXT_IS_TRANSFORMED = 0x2000000 TEXT_IS_TRANSFORMED = 0x2000000,
// This gets set if there's a break opportunity at the end of the textrun.
// We normally don't use this break opportunity because the following text
// will have a break opportunity at the start, but it's useful for line
// layout to know about it in case the following content is not text
TEXT_HAS_TRAILING_BREAK = 0x4000000
}; };
/** /**

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

@ -0,0 +1,8 @@
<!DOCTYPE HTML>
<html>
<body>
<p><span style="white-space:nowrap;"><input type="checkbox">hello1</span>
<p>Hello2<br><span style="white-space:nowrap;"><input type="checkbox"></span>
<p>Hello3<br><input type="checkbox">
</body>
</html>

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

@ -0,0 +1,9 @@
<!DOCTYPE HTML>
<html>
<body style="width:0;">
<p><span style="white-space:nowrap;"><input type="checkbox">hello1</span>
<p>Hello2 <span style="white-space:nowrap;"><input type="checkbox"></span>
<p style="white-space:nowrap"><span style="white-space:normal">Hello3
</span><span style="white-space:nowrap;"><input type="checkbox"></span>
</body>
</html>

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

@ -473,6 +473,7 @@ random == 403134-1.html 403134-1-ref.html # bug 405377
== 403249-1b.html 403249-1-ref.html == 403249-1b.html 403249-1-ref.html
== 403249-2a.html 403249-2-ref.html == 403249-2a.html 403249-2-ref.html
== 403249-2b.html 403249-2-ref.html == 403249-2b.html 403249-2-ref.html
== 403426-1.html 403426-1-ref.html
== 403455-1.html 403455-1-ref.html == 403455-1.html 403455-1-ref.html
== 403505-1.xml 403505-1-ref.xul == 403505-1.xml 403505-1-ref.xul
== 403519-1.html 403519-1-ref.html == 403519-1.html 403519-1-ref.html