Make first-letter frames use the content parent of the textnode as their

content.  Add some first-letter tests, and a few assertions.  Bug 367650, r+sr=roc
This commit is contained in:
bzbarsky%mit.edu 2007-02-28 22:32:00 +00:00
parent 63723ba408
commit 5bc00870a9
27 changed files with 485 additions and 29 deletions

View File

@ -9795,39 +9795,40 @@ nsCSSFrameConstructor::CharacterDataChanged(nsIContent* aContent,
aSubContent, frame));
#endif
// Special check for text content that is a child of a letter
// frame. There are two interesting cases that we have to handle
// carefully: text content that is going empty (which means we
// should select a new text node as the first-letter text) or text
// content that empty but is no longer empty (it might be the
// first-letter text but isn't currently).
//
// To deal with both of these we make a simple change: map a
// CharacterDataChanged into a ReinsertContent when we are changing text
// that is part of a first-letter situation.
PRBool doCharacterDataChanged = PR_TRUE;
// Ok, it's text content. Now do some real work...
// Special check for text content that is a child of a letter frame. If
// this happens, we should remove the letter frame, do whatever we're
// planning to do with this notification, then put the letter frame back.
// Note that this is basically what ReinsertContent ends up doing; the
// reason we dont' want to call that here is that our text content could be
// native anonymous, in which case ReinsertContent would completely barf on
// it. And reinserting the non-anonymous ancestor would just lead us to
// come back into this notification (e.g. if quotes or counters are
// involved), leading to a loop.
nsIFrame* block = GetFloatContainingBlock(frame);
PRBool haveFirstLetterStyle = PR_FALSE;
if (block) {
// See if the block has first-letter style applied to it.
nsIContent* blockContent = block->GetContent();
nsStyleContext* blockSC = block->GetStyleContext();
PRBool haveFirstLetterStyle =
HaveFirstLetterStyle(blockContent, blockSC);
haveFirstLetterStyle = HaveFirstLetterStyle(blockContent, blockSC);
if (haveFirstLetterStyle) {
// The block has first-letter style. Use content-replaced to
// repair the blocks frame structure properly.
nsCOMPtr<nsIContent> container = aContent->GetParent();
if (container) {
doCharacterDataChanged = PR_FALSE;
rv = ReinsertContent(container, aContent);
}
RemoveLetterFrames(mPresShell->GetPresContext(), mPresShell,
mPresShell->FrameManager(), block);
// Reget |frame|, since we might have killed it.
// Do we really need to call CharacterDataChanged in this case, though?
frame = mPresShell->GetPrimaryFrameFor(aContent);
NS_ASSERTION(frame, "Should have frame here!");
}
}
if (doCharacterDataChanged) {
frame->CharacterDataChanged(mPresShell->GetPresContext(), aContent,
aAppend);
frame->CharacterDataChanged(mPresShell->GetPresContext(), aContent,
aAppend);
if (haveFirstLetterStyle) {
nsFrameConstructorState state(mPresShell, mFixedContainingBlock,
GetAbsoluteContainingBlock(frame),
block, nsnull);
RecoverLetterFrames(state, block);
}
}
@ -11641,7 +11642,14 @@ nsCSSFrameConstructor::CreateFloatingLetterFrame(
nsStyleSet *styleSet = mPresShell->StyleSet();
letterFrame = NS_NewFirstLetterFrame(mPresShell, aStyleContext);
InitAndRestoreFrame(aState, aTextContent,
// We don't want to use a text content for a non-text frame (because we want
// its primary frame to be a text frame). So use its parent for the
// first-letter.
nsIContent* letterContent = aTextContent->GetParent();
NS_ASSERTION(letterContent->GetBindingParent() != letterContent,
"Reframes of this letter frame will mess with the root of a "
"native anonymous content subtree!");
InitAndRestoreFrame(aState, letterContent,
aState.GetGeometricParent(aStyleContext->GetStyleDisplay(),
aParentFrame),
nsnull, letterFrame);
@ -11694,7 +11702,7 @@ nsCSSFrameConstructor::CreateFloatingLetterFrame(
}
rv = aState.AddChild(letterFrame, aResult, letterFrame->GetStyleDisplay(),
aTextContent, aStyleContext, aParentFrame, PR_FALSE,
letterContent, aStyleContext, aParentFrame, PR_FALSE,
PR_TRUE, PR_FALSE, PR_TRUE, insertAfter);
if (nextTextFrame) {
@ -11727,6 +11735,9 @@ nsCSSFrameConstructor::CreateLetterFrame(nsFrameConstructorState& aState,
// find a matching style rule.
nsIContent* blockContent = aState.mFloatedItems.containingBlock->GetContent();
NS_ASSERTION(blockContent == aBlockFrame->GetContent(),
"Unexpected block content");
// Create first-letter style rule
nsRefPtr<nsStyleContext> sc = GetFirstLetterStyle(blockContent,
parentStyleContext);
@ -11751,10 +11762,17 @@ nsCSSFrameConstructor::CreateLetterFrame(nsFrameConstructorState& aState,
nsIFrame* letterFrame = NS_NewFirstLetterFrame(mPresShell, sc);
if (letterFrame) {
// Initialize the first-letter-frame.
letterFrame->Init(aTextContent, aParentFrame, nsnull);
// Initialize the first-letter-frame. We don't want to use a text
// content for a non-text frame (because we want its primary frame to
// be a text frame). So use its parent for the first-letter.
nsIContent* letterContent = aTextContent->GetParent();
NS_ASSERTION(letterContent->GetBindingParent() != letterContent,
"Reframes of this letter frame will mess with the root "
"of a native anonymous content subtree!");
letterFrame->Init(letterContent, aParentFrame, nsnull);
InitAndRestoreFrame(aState, aTextContent, letterFrame, nsnull, textFrame);
InitAndRestoreFrame(aState, aTextContent, letterFrame, nsnull,
textFrame);
letterFrame->SetInitialChildList(nsnull, textFrame);
aResult.childList = aResult.lastChild = letterFrame;
@ -12875,6 +12893,9 @@ nsCSSFrameConstructor::PostRestyleEvent(nsIContent* aContent,
return;
}
NS_ASSERTION(aContent->IsNodeOfType(nsINode::eELEMENT),
"Shouldn't be trying to restyle non-elements directly");
RestyleData existingData;
existingData.mRestyleHint = nsReStyleHint(0);
existingData.mChangeHint = NS_STYLE_HINT_NONE;

View File

@ -92,6 +92,8 @@ nsStyleChangeList::AppendChange(nsIFrame* aFrame, nsIContent* aContent, nsChange
"must have frame");
NS_ASSERTION(aContent || !(aHint & nsChangeHint_ReconstructFrame),
"must have content");
NS_ASSERTION(!aContent || aContent->IsNodeOfType(nsINode::eELEMENT),
"Shouldn't be trying to restyle non-elements directly");
if ((0 < mCount) && (aHint & nsChangeHint_ReconstructFrame)) { // filter out all other changes for same content
if (aContent) {

View File

@ -1360,6 +1360,8 @@ nsTextFrame::Init(nsIContent* aContent,
nsIFrame* aParent,
nsIFrame* aPrevInFlow)
{
NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT),
"Bogus content!");
nsresult rv = nsFrame::Init(aContent, aParent, aPrevInFlow);
if (NS_SUCCEEDED(rv) && !aPrevInFlow &&
GetStyleText()->WhiteSpaceIsSignificant()) {
@ -6421,6 +6423,7 @@ nsTextFrame::List(FILE* out, PRInt32 aIndent) const
fprintf(out, " [state=%08x]", mState);
}
}
fprintf(out, " [content=%p]", NS_STATIC_CAST(void*, mContent));
fprintf(out, " sc=%p", NS_STATIC_CAST(void*, mStyleContext));
nsIAtom* pseudoTag = mStyleContext->GetPseudoType();
if (pseudoTag) {

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
div:first-letter { color: green; }
</style>
</head>
<body>
<div>This is text</div>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
div::first-letter { color: green; }
</style>
</head>
<body>
<div>This is text</div>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
span { color: green; }
</style>
</head>
<body>
<div><span>T</span>his is text</div>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<style>
span:before { content: '"'; }
span:after { content: '"'; }
:after { border: 3px solid green; }
:first-letter { color: green; }
</style>
</head>
<body>
<p><span>Foo</span></p>
<p id="p2"><span id="q2"></span></p>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<script>function setTextContent(n, t) { n.textContent = t; }
var stylesheets = [];
function initFuzzerSpecific()
{
var myStylesheetHolder = document.getElementsByTagName("head")[0];
for (var i = 0; i < 25; ++i) {
var s = document.createElementNS("http://www.w3.org/1999/xhtml", 'style');
s.style.display = "none";
myStylesheetHolder.appendChild(s);
stylesheets.push(s);
}
}
</script>
<style>
span:before { content: open-quote; }
span:after { content: close-quote; }
span { quotes: '"' '"'; }
</style>
</head>
<body>
<p><span>Foo</span></p>
<p id="p2"><span id="q2"></span></p>
<script>
document.body.offsetWidth;
initFuzzerSpecific();
setTextContent(stylesheets[1], "*:after { border: 3px solid green; } :first-letter { color: green; }");
setTextContent(stylesheets[2], "*:before { counter-reset: chicken; }");
</script>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<p><q>Foo</q></p>
<p id="p2">&lt;1&gt;0&lt;/1&gt;</p>
</body>
</html>

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<script>
function boom1()
{
initFuzzerSpecific();
setTextContent(stylesheets[1], "*:first-letter { }");
setTextContent(stylesheets[2], "*:before { counter-reset: chicken; }");
document.body.offsetWidth;
boom2();
}
function boom2()
{
setTextContent(stylesheets[3], "#q2:first-letter { content: 'generated'; }");
setTextContent(stylesheets[1], "");
setTextContent(stylesheets[4], "#q2 { quotes: '<1>' '</1>'; }");
document.body.offsetWidth;
boom3();
}
function boom3()
{
document.getElementById("p2").style.counterReset = "egg";
setTextContent(stylesheets[1], "*:first-letter { }");
}
function setTextContent(n, t) { n.textContent = t; }
var stylesheets = [];
function initFuzzerSpecific()
{
var myStylesheetHolder = document.getElementsByTagName("head")[0];
for (var i = 0; i < 25; ++i) {
var s = document.createElementNS("http://www.w3.org/1999/xhtml", 'style');
s.style.display = "none";
myStylesheetHolder.appendChild(s);
stylesheets.push(s);
}
}
</script>
</head>
<body>
<p><q>Foo</q></p>
<p id="p2"><q id="q2">0</q></p>
<script>
document.body.offsetWidth;
boom1();
</script>
</body>
</html>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div>Text</div>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<style>
div#x::first-letter { color: blue; float: left; }
</style>
</head>
<body onload="setTimeout(test, 300)">
<div id="x">Need at least two letters here</div>
<script>
document.body.offsetWidth;
var div = document.getElementById("x");
div.id = "y";
div.firstChild.data = "Text";
</script>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<style>
div#x::first-letter { color: blue; float: none }
</style>
</head>
<body onload="setTimeout(test, 300)">
<div id="x">x</div>
<script>
document.body.offsetWidth;
var div = document.getElementById("x");
div.id = "y";
div.firstChild.data = "Text";
</script>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
span#letter { color: green; }
</style>
</head>
<body>
<div>
<span>
<span id="letter">T</span>his is text
</span>
</div>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
div::first-letter { color: green; }
</style>
</head>
<body>
<div>
<span>
This is text
</span>
</div>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
div::first-letter { color: green; }
</style>
</head>
<body>
<div>This is more text
<span>
This is text
</span>
</div>
<script>
document.body.offsetWidth;
document.getElementsByTagName("div")[0].firstChild.data = "";
</script>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
div::first-letter { color: green; }
</style>
</head>
<body>
<div>
<span>
</span>
</div>
<script>
document.body.offsetWidth;
document.getElementsByTagName("span")[0].firstChild.data = "This is text";
</script>
</body>
</html>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
div::first-letter { color: green; }
</style>
</head>
<body>
<div>
<span>
</span>
</div>
<script>
document.body.offsetWidth;
document.getElementsByTagName("span")[0].
appendChild(document.createTextNode("This is text"));
</script>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
div::first-letter { color: green; }
</style>
</head>
<body>
<div>
<span></span>
</div>
<script>
document.body.offsetWidth;
document.getElementsByTagName("span")[0].
appendChild(document.createTextNode("This is text"));
</script>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
div::first-letter { color: green; }
</style>
</head>
<body>
<div>
<span> </span>
<span>
This is text
</span>
</div>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
span#letter { color: green; }
</style>
</head>
<body>
<div><span id="letter">"T</span>his is text"</span></div>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
div::first-letter { color: green; }
</style>
</head>
<body>
<div>"This is text"</div>
</body>
</html>

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
div::first-letter { color: green; }
span:before { content: open-quote; }
span:after { content: close-quote; }
span { quotes: '"' '"'; }
</style>
</head>
<body>
<div><span>This is text</span></div>
</body>
</html>

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
div::first-letter { color: green; }
span:before { content: open-quote "This "; }
span:after { content: close-quote; }
span { quotes: '"' '"'; }
</style>
</head>
<body>
<div><span>is text</span></div>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<style>
div { color: black; }
div::first-letter { color: green; }
</style>
</head>
<body>
<div><span></span></div>
<script>
document.body.offsetWidth;
document.getElementsByTagName("span")[0].
appendChild(document.createTextNode('"'));
document.getElementsByTagName("span")[0].
appendChild(document.createTextNode('This is text"'));
</script>
</body>
</html>

View File

@ -0,0 +1,18 @@
== basic-1.html basic-ref.html
== basic-2.html basic-ref.html
== nested-1a.html nested-1-ref.html
== nested-1b.html nested-1-ref.html
== nested-1c.html nested-1-ref.html
== nested-1d.html nested-1-ref.html
== nested-1e.html nested-1-ref.html
== nested-1f.html nested-1-ref.html
== quote-1a.html quote-1-ref.html
fails == quote-1b.html quote-1-ref.html
fails == quote-1c.html quote-1-ref.html
== quote-1c.html quote-1b.html
fails == quote-1d.html quote-1-ref.html
== quote-1d.html quote-1b.html
== dynamic-1.html dynamic-1-ref.html
== dynamic-2.html dynamic-2-ref.html
== dynamic-3a.html dynamic-3-ref.html
== dynamic-3b.html dynamic-3-ref.html

View File

@ -112,3 +112,6 @@ include xul-document-load/reftest.list
# box-properties/
include box-properties/reftest.list
# first-letter/
include first-letter/reftest.list