Bug 876831 - Only reflow nsSVGTextFrame2's anonymous block frame under ReflowSVG. r=bzbarsky,longsonr f=jwatt

This commit is contained in:
Cameron McCormack 2013-06-04 00:15:29 +10:00
parent 7135d9040a
commit b8ecd886bc
6 changed files with 148 additions and 20 deletions

View File

@ -82,6 +82,7 @@ MOCHITEST_FILES = \
test_text_2.html \
test_text_scaled.html \
test_text_selection.html \
test_text_update.html \
text-helper.svg \
text-helper-scaled.svg \
text-helper-selection.svg \

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<title>Test for Bug 876831</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=876831">Mozilla Bug 876831</a>
<p id="display"</p>
<!--
Test that the frame tree will be reflowed after a DOM mutation
and just before an SVG DOM method does its work.
-->
<svg>
<text>ab</text>
</svg>
<pre id="test">
<script class="testbody" type="application/javascript">
var text = document.querySelector("text");
var length = text.getComputedTextLength();
ok(length > 0, "text.getComputedTextLength() > 0");
text.firstChild.nodeValue += "cd";
ok(text.getComputedTextLength() > length, "text.getComputedTextLength() changes directly after DOM mutation");
text.removeChild(text.firstChild);
is(text.getComputedTextLength(), 0, "text.getComputedTextLength() == 0 after removing child");
</script>
</pre>

View File

@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg">
<script>
function boom()
{
var x = document.getElementById("x").firstChild;
x.data = x.data.slice(1);
document.caretPositionFromPoint(0, 0);
}
window.addEventListener("load", boom, false);
</script>
<text><tspan id="x">@&#x062A;</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 310 B

View File

@ -164,4 +164,5 @@ load 849688-2.svg
load 860378-1.svg
load 868904-1.svg
load 873806-1.svg
load 876831-1.svg
load 877029-1.svg

View File

@ -2222,6 +2222,7 @@ private:
mGlyphStartTextElementCharIndex = mTextElementCharIndex;
}
}
/**
* The filter to use.
*/
@ -3067,7 +3068,7 @@ nsSVGTextFrame2::DidSetStyleContext(nsStyleContext* aOldStyleContext)
// child to be reflowed when it is next painted, and (b) not cause the
// <text> to be repainted anyway since the user of the <mask> would not
// know it needs to be repainted.
ReflowSVGNonDisplayText();
ScheduleReflowSVGNonDisplayText();
}
}
@ -3090,6 +3091,51 @@ nsSVGTextFrame2::ReflowSVGNonDisplayText()
// be updated, which will then cause this nsSVGTextFrame2 to be painted and
// in doing so cause the anonymous block frame to be reflowed.
nsSVGEffects::InvalidateRenderingObservers(this);
// Finally, we need to actually reflow the anonymous block frame and update
// mPositions, in case we are being reflowed immediately after a DOM
// mutation that needs frame reconstruction.
MaybeReflowAnonymousBlockChild();
UpdateGlyphPositioning();
}
void
nsSVGTextFrame2::ScheduleReflowSVGNonDisplayText()
{
MOZ_ASSERT(!nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
"do not call ScheduleReflowSVGNonDisplayText when the outer SVG "
"frame is under ReflowSVG");
nsSVGOuterSVGFrame* outerSVGFrame = nullptr;
nsIFrame* f = GetParent();
while (f) {
if (f->GetStateBits() & NS_STATE_IS_OUTER_SVG) {
outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(f);
break;
}
if (!(f->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) {
// We don't need to set NS_FRAME_HAS_DIRTY_CHILDREN on non-display
// children in the path from the dirty nsSVGTextFrame2 up to the
// first display container frame, since ReflowSVGNonDisplayText
// does not check for it.
if (NS_SUBTREE_DIRTY(f)) {
return;
}
f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
f = f->GetParent();
}
NS_ABORT_IF_FALSE(outerSVGFrame &&
outerSVGFrame->GetType() == nsGkAtoms::svgOuterSVGFrame,
"Did not find nsSVGOuterSVGFrame!");
NS_ABORT_IF_FALSE(!(outerSVGFrame->GetStateBits() & NS_FRAME_IN_REFLOW),
"should not call ScheduleReflowSVGNonDisplayText under "
"nsSVGOuterSVGFrame::Reflow");
PresContext()->PresShell()->FrameNeedsReflow(
outerSVGFrame, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
}
NS_IMPL_ISUPPORTS1(nsSVGTextFrame2::MutationObserver, nsIMutationObserver)
@ -3208,6 +3254,7 @@ nsSVGTextFrame2::FindCloserFrameForSelection(
if (GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD) {
return;
}
UpdateGlyphPositioning();
nsPresContext* presContext = PresContext();
@ -3515,7 +3562,7 @@ nsSVGTextFrame2::ReflowSVG()
return;
}
// UpdateGlyphPositioning will call DoReflow if necessary.
MaybeReflowAnonymousBlockChild();
UpdateGlyphPositioning();
nsPresContext* presContext = PresContext();
@ -4819,6 +4866,20 @@ nsSVGTextFrame2::NotifyGlyphMetricsChange(uint32_t aFlags)
void
nsSVGTextFrame2::UpdateGlyphPositioning()
{
nsIFrame* kid = GetFirstPrincipalChild();
if (!kid) {
return;
}
if (mPositioningDirty) {
MOZ_ASSERT(!NS_SUBTREE_DIRTY(kid), "should have already reflowed the kid");
DoGlyphPositioning();
}
}
void
nsSVGTextFrame2::MaybeReflowAnonymousBlockChild()
{
nsIFrame* kid = GetFirstPrincipalChild();
if (!kid)
@ -4827,7 +4888,7 @@ nsSVGTextFrame2::UpdateGlyphPositioning()
NS_ASSERTION(!(kid->GetStateBits() & NS_FRAME_IN_REFLOW),
"should not be in reflow when about to reflow again");
if (mState & (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) {
if (NS_SUBTREE_DIRTY(this)) {
if (mState & NS_FRAME_IS_DIRTY) {
// If we require a full reflow, ensure our kid is marked fully dirty.
// (Note that our anonymous nsBlockFrame is not an nsISVGChildFrame, so
@ -4840,10 +4901,6 @@ nsSVGTextFrame2::UpdateGlyphPositioning()
nsPresContext::InterruptPreventer noInterrupts(PresContext());
DoReflow();
}
if (mPositioningDirty) {
DoGlyphPositioning();
}
}
void

View File

@ -294,14 +294,33 @@ public:
enum { ePositioningDirtyDueToMutation = 1 };
/**
* Performs any operations required when this frame is non-display and
* has had a style change. See the two callers for more information:
* ReflowSVGNonDisplayText in nsSVGContainer.cpp (called under
* nsSVGDisplayContainerFrame::ReflowSVG) and
* nsSVGTextFrame2::DidSetStyleContext.
* Reflows the anonymous block frame of this non-display nsSVGTextFrame2.
*
* When we are under nsSVGDisplayContainerFrame::ReflowSVG, we need to
* reflow any nsSVGTextFrame2 frames in the subtree in case they are
* being observed (by being for example in a <mask>) and the change
* that caused the reflow would not already have caused a reflow.
*
* Note that displayed nsSVGTextFrame2s are reflowed as needed, when PaintSVG
* is called or some SVG DOM method is called on the element.
*/
void ReflowSVGNonDisplayText();
/**
* This is a function that behaves similarly to nsSVGUtils::ScheduleReflowSVG,
* but which will skip over any ancestor non-display container frames on the
* way to the nsSVGOuterSVGFrame. It exists for the situation where a
* non-display <text> element has changed and needs to ensure ReflowSVG will
* be called on its closest display container frame, so that
* nsSVGDisplayContainerFrame::ReflowSVG will call ReflowSVGNonDisplayText on
* it.
*
* The only case where we have to do this is in response to a style change on
* a non-display <text>; the only caller of ScheduleReflowSVGNonDisplayText
* currently is nsSVGTextFrame2::DidSetStyleContext.
*/
void ScheduleReflowSVGNonDisplayText();
/**
* Updates the mFontSizeScaleFactor value by looking at the range of
* font-sizes used within the <text>.
@ -352,7 +371,6 @@ private:
}
~AutoCanvasTMForMarker()
{
// Default
mFrame->mGetCanvasTMForFlag = mOldFor;
}
private:
@ -399,17 +417,19 @@ private:
};
/**
* Reflows the anonymous block child.
* Reflows the anonymous block child if it is dirty or has dirty
* children, or if the nsSVGTextFrame2 itself is dirty.
*/
void MaybeReflowAnonymousBlockChild();
/**
* Performs the actual work of reflowing the anonymous block child.
*/
void DoReflow();
/**
* Calls FrameNeedsReflow on the anonymous block child.
*/
void RequestReflow(nsIPresShell::IntrinsicDirty aType, uint32_t aBit);
/**
* Reflows the anonymous block child and recomputes mPositions if needed.
* Recomputes mPositions by calling DoGlyphPositioning if this information
* is out of date.
*/
void UpdateGlyphPositioning();