Bug 411555 - Text inside filter causing invalidation loop. r=jwat,sr=tor,a1.9=blocking1.9+
authorlongsonr@gmail.com
Fri, 25 Jan 2008 01:27:03 -0800
changeset 10661 a69a47d62ab3d1e0b28b312685f886388ae23310
parent 10660 d81db93d73decc6aeb3cd3b58a53c94205fa8259
child 10662 1895a4dcb92a16a0eb344405b4fb5975c9c36164
push idunknown
push userunknown
push dateunknown
reviewersjwat, tor
bugs411555
milestone1.9b3pre
Bug 411555 - Text inside filter causing invalidation loop. r=jwat,sr=tor,a1.9=blocking1.9+
content/svg/content/src/nsSVGGraphicElement.cpp
content/svg/content/src/nsSVGLength2.h
content/svg/content/src/nsSVGSVGElement.cpp
layout/svg/base/src/nsISVGChildFrame.h
layout/svg/base/src/nsSVGAFrame.cpp
layout/svg/base/src/nsSVGClipPathFrame.cpp
layout/svg/base/src/nsSVGContainerFrame.cpp
layout/svg/base/src/nsSVGContainerFrame.h
layout/svg/base/src/nsSVGFilterFrame.cpp
layout/svg/base/src/nsSVGForeignObjectFrame.cpp
layout/svg/base/src/nsSVGForeignObjectFrame.h
layout/svg/base/src/nsSVGGFrame.cpp
layout/svg/base/src/nsSVGGFrame.h
layout/svg/base/src/nsSVGGlyphFrame.cpp
layout/svg/base/src/nsSVGGlyphFrame.h
layout/svg/base/src/nsSVGGradientFrame.cpp
layout/svg/base/src/nsSVGInnerSVGFrame.cpp
layout/svg/base/src/nsSVGMarkerFrame.cpp
layout/svg/base/src/nsSVGMaskFrame.cpp
layout/svg/base/src/nsSVGOuterSVGFrame.cpp
layout/svg/base/src/nsSVGPathGeometryFrame.cpp
layout/svg/base/src/nsSVGPathGeometryFrame.h
layout/svg/base/src/nsSVGPatternFrame.cpp
layout/svg/base/src/nsSVGTextFrame.cpp
layout/svg/base/src/nsSVGTextFrame.h
layout/svg/base/src/nsSVGTextPathFrame.cpp
layout/svg/base/src/nsSVGUseFrame.cpp
layout/svg/base/src/nsSVGUtils.cpp
layout/svg/base/src/nsSVGUtils.h
--- a/content/svg/content/src/nsSVGGraphicElement.cpp
+++ b/content/svg/content/src/nsSVGGraphicElement.cpp
@@ -94,20 +94,22 @@ NS_IMETHODIMP nsSVGGraphicElement::GetBB
   if (!frame || (frame->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD))
     return NS_ERROR_FAILURE;
 
   nsISVGChildFrame* svgframe;
   CallQueryInterface(frame, &svgframe);
   NS_ASSERTION(svgframe, "wrong frame type");
   if (svgframe) {
     svgframe->SetMatrixPropagation(PR_FALSE);
-    svgframe->NotifyCanvasTMChanged(PR_TRUE);
+    svgframe->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                               nsISVGChildFrame::TRANSFORM_CHANGED);
     nsresult rv = svgframe->GetBBox(_retval);
     svgframe->SetMatrixPropagation(PR_TRUE);
-    svgframe->NotifyCanvasTMChanged(PR_TRUE);
+    svgframe->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                               nsISVGChildFrame::TRANSFORM_CHANGED);
     return rv;
   }
   return NS_ERROR_FAILURE;
 }
 
 /* Helper for GetCTM and GetScreenCTM */
 nsresult
 nsSVGGraphicElement::AppendLocalTransform(nsIDOMSVGMatrix *aCTM,
--- a/content/svg/content/src/nsSVGLength2.h
+++ b/content/svg/content/src/nsSVGLength2.h
@@ -66,16 +66,18 @@ public:
 
   float GetBaseValue(nsSVGElement* aSVGElement)
     { return mBaseVal / GetUnitScaleFactor(aSVGElement); }
   float GetAnimValue(nsSVGElement* aSVGElement)
     { return mAnimVal / GetUnitScaleFactor(aSVGElement); }
 
   PRUint8 GetCtxType() const { return mCtxType; }
   PRUint8 GetSpecifiedUnitType() const { return mSpecifiedUnitType; }
+  PRBool IsPercentage() const
+    { return mSpecifiedUnitType == nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE; }
   float GetAnimValInSpecifiedUnits() const { return mAnimVal; }
   float GetBaseValInSpecifiedUnits() const { return mBaseVal; }
 
   float GetBaseValue(nsSVGSVGElement* aCtx)
     { return mBaseVal / GetUnitScaleFactor(aCtx); }
   float GetAnimValue(nsSVGSVGElement* aCtx)
     { return mAnimVal / GetUnitScaleFactor(aCtx); }
   
--- a/content/svg/content/src/nsSVGSVGElement.cpp
+++ b/content/svg/content/src/nsSVGSVGElement.cpp
@@ -676,20 +676,22 @@ nsSVGSVGElement::GetBBox(nsIDOMSVGRect *
 
   if (!frame || (frame->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD))
     return NS_ERROR_FAILURE;
 
   nsISVGChildFrame* svgframe;
   CallQueryInterface(frame, &svgframe);
   if (svgframe) {
     svgframe->SetMatrixPropagation(PR_FALSE);
-    svgframe->NotifyCanvasTMChanged(PR_TRUE);
+    svgframe->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                               nsISVGChildFrame::TRANSFORM_CHANGED);
     nsresult rv = svgframe->GetBBox(_retval);
     svgframe->SetMatrixPropagation(PR_TRUE);
-    svgframe->NotifyCanvasTMChanged(PR_TRUE);
+    svgframe->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                               nsISVGChildFrame::TRANSFORM_CHANGED);
     return rv;
   } else {
     // XXX: outer svg
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 }
 
 /* nsIDOMSVGMatrix getCTM (); */
--- a/layout/svg/base/src/nsISVGChildFrame.h
+++ b/layout/svg/base/src/nsISVGChildFrame.h
@@ -46,18 +46,18 @@
 class gfxContext;
 class nsPresContext;
 class nsIDOMSVGRect;
 class nsIDOMSVGMatrix;
 class nsSVGRenderState;
 struct nsRect;
 
 #define NS_ISVGCHILDFRAME_IID \
-{ 0x93560e72, 0x6818, 0x4218, \
- { 0xa1, 0xe9, 0xf3, 0xb9, 0x63, 0x6a, 0xff, 0xc2 } }
+{ 0x667e8781, 0x72bd, 0x4344, \
+ { 0x95, 0x8c, 0x69, 0xa5, 0x70, 0xc4, 0xcc, 0xb3 } }
 
 class nsISVGChildFrame : public nsISupports {
 public:
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISVGCHILDFRAME_IID)
 
   // Paint this frame - aDirtyRect is the area being redrawn, in frame
   // offset pixel coordinates
@@ -79,17 +79,29 @@ public:
   NS_IMETHOD UpdateCoveredRegion()=0;
 
   // Called once on SVG child frames except descendants of <defs>, either
   // when their nsSVGOuterSVGFrame receives its initial reflow (i.e. once
   // the SVG viewport dimensions are known), or else when they're inserted
   // into the frame tree (if they're inserted after the initial reflow).
   NS_IMETHOD InitialUpdate()=0;
 
-  NS_IMETHOD NotifyCanvasTMChanged(PRBool suppressInvalidation)=0;
+  // Flags to pass to NotifySVGChange:
+  //
+  // SUPPRESS_INVALIDATION - do not invalidate rendered areas (only to be
+  //                           used in conjunction with TRANSFORM_CHANGED)
+  // TRANSFORM_CHANGED     - the current transform matrix for this frame has changed
+  // COORD_CONTEXT_CHANGED - the dimensions of this frame's coordinate context has
+  //                           changed (percentage lengths must be reevaluated)
+  enum SVGChangedFlags {
+    SUPPRESS_INVALIDATION = 0x01,
+    TRANSFORM_CHANGED     = 0x02,
+    COORD_CONTEXT_CHANGED = 0x04
+  };
+  virtual void NotifySVGChanged(PRUint32 aFlags)=0;
   NS_IMETHOD NotifyRedrawSuspended()=0;
   NS_IMETHOD NotifyRedrawUnsuspended()=0;
 
   // Set whether we should stop multiplying matrices when building up
   // the current transformation matrix at this frame.
   NS_IMETHOD SetMatrixPropagation(PRBool aPropagate)=0;
 
   // Set the current transformation matrix to a particular matrix.
--- a/layout/svg/base/src/nsSVGAFrame.cpp
+++ b/layout/svg/base/src/nsSVGAFrame.cpp
@@ -76,17 +76,17 @@ public:
 
 #ifdef DEBUG
   NS_IMETHOD GetFrameName(nsAString& aResult) const
   {
     return MakeFrameName(NS_LITERAL_STRING("SVGA"), aResult);
   }
 #endif
   // nsISVGChildFrame interface:
-  NS_IMETHOD NotifyCanvasTMChanged(PRBool suppressInvalidation);
+  virtual void NotifySVGChanged(PRUint32 aFlags);
   
   // nsSVGContainerFrame methods:
   virtual already_AddRefed<nsIDOMSVGMatrix> GetCanvasTM();
   
 private:
   nsCOMPtr<nsIDOMSVGMatrix> mCanvasTM;
 };
 
@@ -118,24 +118,17 @@ nsSVGAFrame::AttributeChanged(PRInt32   
     return NS_OK;
 
   if (aAttribute == nsGkAtoms::transform) {
     // transform has changed
 
     // make sure our cached transform matrix gets (lazily) updated
     mCanvasTM = nsnull;
     
-    nsIFrame* kid = mFrames.FirstChild();
-    while (kid) {
-      nsISVGChildFrame* SVGFrame = nsnull;
-      CallQueryInterface(kid, &SVGFrame);
-      if (SVGFrame)
-        SVGFrame->NotifyCanvasTMChanged(PR_FALSE);
-      kid = kid->GetNextSibling();
-    }
+    nsSVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED);
   }
 
  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSVGAFrame::DidSetStyleContext()
 {
@@ -148,23 +141,25 @@ nsIAtom *
 nsSVGAFrame::GetType() const
 {
   return nsGkAtoms::svgAFrame;
 }
 
 //----------------------------------------------------------------------
 // nsISVGChildFrame methods
 
-NS_IMETHODIMP
-nsSVGAFrame::NotifyCanvasTMChanged(PRBool suppressInvalidation)
+void
+nsSVGAFrame::NotifySVGChanged(PRUint32 aFlags)
 {
-  // make sure our cached transform matrix gets (lazily) updated
-  mCanvasTM = nsnull;
+  if (aFlags & TRANSFORM_CHANGED) {
+    // make sure our cached transform matrix gets (lazily) updated
+    mCanvasTM = nsnull;
+  }
 
-  return nsSVGAFrameBase::NotifyCanvasTMChanged(suppressInvalidation);
+  nsSVGAFrameBase::NotifySVGChanged(aFlags);
 }
 
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods:
 
 already_AddRefed<nsIDOMSVGMatrix>
 nsSVGAFrame::GetCanvasTM()
 {
--- a/layout/svg/base/src/nsSVGClipPathFrame.cpp
+++ b/layout/svg/base/src/nsSVGClipPathFrame.cpp
@@ -95,17 +95,19 @@ nsSVGClipPathFrame::ClipPaint(nsSVGRende
                            isTrivial ? nsSVGRenderState::CLIP
                                      : nsSVGRenderState::CLIP_MASK);
 
   for (nsIFrame* kid = mFrames.FirstChild(); kid;
        kid = kid->GetNextSibling()) {
     nsISVGChildFrame* SVGFrame = nsnull;
     CallQueryInterface(kid, &SVGFrame);
     if (SVGFrame) {
-      SVGFrame->NotifyCanvasTMChanged(PR_TRUE);
+      // The CTM of each frame referencing us can be different.
+      SVGFrame->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION | 
+                                 nsISVGChildFrame::TRANSFORM_CHANGED);
       SVGFrame->PaintSVG(aContext, nsnull);
     }
   }
 
   if (isTrivial) {
     aContext->GetGfxContext()->Clip();
     aContext->GetGfxContext()->NewPath();
   }
@@ -134,17 +136,17 @@ nsSVGClipPathFrame::ClipHitTest(nsISVGCh
   for (nsIFrame* kid = mFrames.FirstChild(); kid;
        kid = kid->GetNextSibling()) {
     nsISVGChildFrame* SVGFrame = nsnull;
     CallQueryInterface(kid, &SVGFrame);
     if (SVGFrame) {
       // Notify the child frame that we may be working with a
       // different transform, so it can update its covered region
       // (used to shortcut hit testing).
-      SVGFrame->NotifyCanvasTMChanged(PR_FALSE);
+      SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED);
 
       nsIFrame *temp = nsnull;
       nsresult rv = SVGFrame->GetFrameForPointSVG(aX, aY, &temp);
       if (NS_SUCCEEDED(rv) && temp)
         return PR_TRUE;
     }
   }
   return PR_FALSE;
--- a/layout/svg/base/src/nsSVGContainerFrame.cpp
+++ b/layout/svg/base/src/nsSVGContainerFrame.cpp
@@ -236,25 +236,27 @@ nsSVGDisplayContainerFrame::InitialUpdat
   
   // Do unset the various reflow bits, though.
   mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
               NS_FRAME_HAS_DIRTY_CHILDREN);
   
   return NS_OK;
 }  
 
-NS_IMETHODIMP
-nsSVGDisplayContainerFrame::NotifyCanvasTMChanged(PRBool suppressInvalidation)
+void
+nsSVGDisplayContainerFrame::NotifySVGChanged(PRUint32 aFlags)
 {
-  if (!suppressInvalidation &&
+  NS_ASSERTION(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+               "Invalidation logic may need adjusting");
+
+  if (!(aFlags & SUPPRESS_INVALIDATION) &&
       !(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD))
     nsSVGUtils::UpdateFilterRegion(this);
 
-  nsSVGUtils::NotifyChildrenCanvasTMChanged(this, suppressInvalidation);
-  return NS_OK;
+  nsSVGUtils::NotifyChildrenOfSVGChange(this, aFlags);
 }
 
 NS_IMETHODIMP
 nsSVGDisplayContainerFrame::NotifyRedrawSuspended()
 {
   for (nsIFrame* kid = mFrames.FirstChild(); kid;
        kid = kid->GetNextSibling()) {
     nsISVGChildFrame* SVGFrame=nsnull;
--- a/layout/svg/base/src/nsSVGContainerFrame.h
+++ b/layout/svg/base/src/nsSVGContainerFrame.h
@@ -102,17 +102,17 @@ public:
                   nsIFrame*        aPrevInFlow);
 
   // nsISVGChildFrame interface:
   NS_IMETHOD PaintSVG(nsSVGRenderState* aContext, nsRect *aDirtyRect);
   NS_IMETHOD GetFrameForPointSVG(float x, float y, nsIFrame** hit);  
   NS_IMETHOD_(nsRect) GetCoveredRegion();
   NS_IMETHOD UpdateCoveredRegion();
   NS_IMETHOD InitialUpdate();
-  NS_IMETHOD NotifyCanvasTMChanged(PRBool suppressInvalidation);
+  virtual void NotifySVGChanged(PRUint32 aFlags);
   NS_IMETHOD NotifyRedrawSuspended();
   NS_IMETHOD NotifyRedrawUnsuspended();
   NS_IMETHOD SetMatrixPropagation(PRBool aPropagate) { return NS_ERROR_FAILURE; }
   NS_IMETHOD SetOverrideCTM(nsIDOMSVGMatrix *aCTM) { return NS_ERROR_FAILURE; }
   virtual already_AddRefed<nsIDOMSVGMatrix> GetOverrideCTM() { return nsnull; }
   NS_IMETHOD GetBBox(nsIDOMSVGRect **_retval);
   NS_IMETHOD_(PRBool) IsDisplayContainer() { return PR_TRUE; }
   NS_IMETHOD_(PRBool) HasValidCoveredRect() { return PR_FALSE; }
--- a/layout/svg/base/src/nsSVGFilterFrame.cpp
+++ b/layout/svg/base/src/nsSVGFilterFrame.cpp
@@ -74,17 +74,18 @@ NS_GetSVGFilterElement(nsIURI *aURI, nsI
 }
 
 void
 nsSVGFilterFrame::FilterFailCleanup(nsSVGRenderState *aContext,
                                     nsISVGChildFrame *aTarget)
 {
   aTarget->SetOverrideCTM(nsnull);
   aTarget->SetMatrixPropagation(PR_TRUE);
-  aTarget->NotifyCanvasTMChanged(PR_TRUE);
+  aTarget->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                            nsISVGChildFrame::TRANSFORM_CHANGED);
   aTarget->PaintSVG(aContext, nsnull);
 }
 
 nsresult
 nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
                               nsISVGChildFrame *aTarget)
 {
   nsCOMPtr<nsIDOMSVGFilterElement> aFilter = do_QueryInterface(mContent);
@@ -116,17 +117,18 @@ nsSVGFilterFrame::FilterPaint(nsSVGRende
   nsIFrame *frame;
   CallQueryInterface(aTarget, &frame);
 
   nsCOMPtr<nsIDOMSVGMatrix> ctm = nsSVGUtils::GetCanvasTM(frame);
 
   nsSVGElement *target = static_cast<nsSVGElement*>(frame->GetContent());
 
   aTarget->SetMatrixPropagation(PR_FALSE);
-  aTarget->NotifyCanvasTMChanged(PR_TRUE);
+  aTarget->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                            nsISVGChildFrame::TRANSFORM_CHANGED);
 
   nsSVGFilterElement *filter = static_cast<nsSVGFilterElement*>(mContent);
 
   float x, y, width, height;
   nsCOMPtr<nsIDOMSVGRect> bbox;
   aTarget->GetBBox(getter_AddRefs(bbox));
 
   nsSVGLength2 *tmpX, *tmpY, *tmpWidth, *tmpHeight;
@@ -187,17 +189,18 @@ nsSVGFilterFrame::FilterPaint(nsSVGRende
 #endif
 
   nsCOMPtr<nsIDOMSVGMatrix> filterTransform;
   NS_NewSVGMatrix(getter_AddRefs(filterTransform),
                   filterRes.width / width,      0.0f,
                   0.0f,                         filterRes.height / height,
                   -x * filterRes.width / width, -y * filterRes.height / height);
   aTarget->SetOverrideCTM(filterTransform);
-  aTarget->NotifyCanvasTMChanged(PR_TRUE);
+  aTarget->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                            nsISVGChildFrame::TRANSFORM_CHANGED);
 
   // paint the target geometry
   nsRefPtr<gfxImageSurface> tmpSurface =
     new gfxImageSurface(filterRes, gfxASurface::ImageFormatARGB32);
   if (!tmpSurface || tmpSurface->CairoStatus()) {
     FilterFailCleanup(aContext, aTarget);
     return NS_OK;
   }
@@ -284,17 +287,18 @@ nsSVGFilterFrame::FilterPaint(nsSVGRende
 
   ctm->Multiply(scale, getter_AddRefs(fini));
 
   nsSVGUtils::CompositeSurfaceMatrix(aContext->GetGfxContext(),
                                      filterSurface, fini, 1.0);
 
   aTarget->SetOverrideCTM(nsnull);
   aTarget->SetMatrixPropagation(PR_TRUE);
-  aTarget->NotifyCanvasTMChanged(PR_TRUE);
+  aTarget->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                            nsISVGChildFrame::TRANSFORM_CHANGED);
 
   return NS_OK;
 }
 
 nsRect
 nsSVGFilterFrame::GetInvalidationRegion(nsIFrame *aTarget)
 {
   nsSVGElement *targetContent =
@@ -309,22 +313,24 @@ nsSVGFilterFrame::GetInvalidationRegion(
 
   PRUint16 type =
     filter->mEnumAttributes[nsSVGFilterElement::FILTERUNITS].GetAnimValue();
 
   float x, y, width, height;
   nsCOMPtr<nsIDOMSVGRect> bbox;
 
   svg->SetMatrixPropagation(PR_FALSE);
-  svg->NotifyCanvasTMChanged(PR_TRUE);
+  svg->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                        nsISVGChildFrame::TRANSFORM_CHANGED);
 
   svg->GetBBox(getter_AddRefs(bbox));
 
   svg->SetMatrixPropagation(PR_TRUE);
-  svg->NotifyCanvasTMChanged(PR_TRUE);
+  svg->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                        nsISVGChildFrame::TRANSFORM_CHANGED);
 
   nsSVGLength2 *tmpX, *tmpY, *tmpWidth, *tmpHeight;
   tmpX = &filter->mLengthAttributes[nsSVGFilterElement::X];
   tmpY = &filter->mLengthAttributes[nsSVGFilterElement::Y];
   tmpWidth = &filter->mLengthAttributes[nsSVGFilterElement::WIDTH];
   tmpHeight = &filter->mLengthAttributes[nsSVGFilterElement::HEIGHT];
 
   if (type == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
--- a/layout/svg/base/src/nsSVGForeignObjectFrame.cpp
+++ b/layout/svg/base/src/nsSVGForeignObjectFrame.cpp
@@ -375,50 +375,56 @@ nsSVGForeignObjectFrame::InitialUpdate()
   
   // Do unset the various reflow bits, though.
   mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
               NS_FRAME_HAS_DIRTY_CHILDREN);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsSVGForeignObjectFrame::NotifyCanvasTMChanged(PRBool suppressInvalidation)
+void
+nsSVGForeignObjectFrame::NotifySVGChanged(PRUint32 aFlags)
 {
-  mCanvasTM = nsnull;
+  PRBool reflow = PR_FALSE;
 
-  // XXX we should really have a separate notification for viewport changes and
-  // not overload NotifyCanvasTMChanged, e.g. we wouldn't need to check
-  // IsReflowLocked below. Note both notifications would be required for
-  // viewport changes when there's a viewBox though!
-  //
-  // If our width/height have a percentage value then we need to reflow if the
-  // width/height of our parent coordinate context changes. XXX Perhaps
-  // unexpectedly we also reflow if our CTM changes. This is because glyph
-  // metrics do not necessarily scale uniformly with change in scale and, as a
-  // result, CTM changes may require text to break at different points. roc
-  // says we shouldn't do this. See bug 381285 comment 20.
+  if (aFlags & TRANSFORM_CHANGED) {
+    // Perhaps unexpectedly, we reflow if our CTM changes. This is because
+    // glyph metrics do not necessarily scale uniformly with change in scale
+    // and, as a result, CTM changes may require text to break at different
+    // points.
+    // XXX roc says we shouldn't do this. See bug 381285 comment 20.
+    reflow = PR_TRUE;
+    mCanvasTM = nsnull;
 
-  UpdateGraphic(); // update mRect before requesting reflow
-
-  // If we're called while the PresShell is handling reflow events then we
-  // must have been called as a result of the NotifyViewportChange() call in
-  // our nsSVGOuterSVGFrame's Reflow() method. We must not call RequestReflow
-  // at this point (i.e. during reflow) because it could confuse the PresShell
-  // and prevent it from reflowing us properly in future. Besides that,
-  // nsSVGOuterSVGFrame::DidReflow will take care of reflowing us
-  // synchronously, so there's no need.
-
-  PRBool reflowing;
-  PresContext()->PresShell()->IsReflowLocked(&reflowing);
-  if (!reflowing) {
-    RequestReflow(nsIPresShell::eResize); // XXX use mState & NS_FRAME_IN_REFLOW?
+  } else if (aFlags & COORD_CONTEXT_CHANGED) {
+    // Our coordinate context's width/height has changed. If we have a
+    // percentage width/height our dimensions will change so we must reflow.
+    nsSVGForeignObjectElement *fO =
+      static_cast<nsSVGForeignObjectElement*>(mContent);
+    if (fO->mLengthAttributes[nsSVGForeignObjectElement::WIDTH].IsPercentage() ||
+        fO->mLengthAttributes[nsSVGForeignObjectElement::HEIGHT].IsPercentage()) {
+      reflow = PR_TRUE;
+    }
   }
 
-  return NS_OK;
+  if (reflow) {
+    // If we're called while the PresShell is handling reflow events then we
+    // must have been called as a result of the NotifyViewportChange() call in
+    // our nsSVGOuterSVGFrame's Reflow() method. We must not call RequestReflow
+    // at this point (i.e. during reflow) because it could confuse the
+    // PresShell and prevent it from reflowing us properly in future. Besides
+    // that, nsSVGOuterSVGFrame::DidReflow will take care of reflowing us
+    // synchronously, so there's no need.
+    PRBool reflowing;
+    PresContext()->PresShell()->IsReflowLocked(&reflowing);
+    if (!reflowing) {
+      UpdateGraphic(); // update mRect before requesting reflow
+      RequestReflow(nsIPresShell::eResize);
+    }
+  }
 }
 
 NS_IMETHODIMP
 nsSVGForeignObjectFrame::NotifyRedrawSuspended()
 {
   return NS_OK;
 }
 
--- a/layout/svg/base/src/nsSVGForeignObjectFrame.h
+++ b/layout/svg/base/src/nsSVGForeignObjectFrame.h
@@ -107,17 +107,17 @@ public:
 #endif
 
   // nsISVGChildFrame interface:
   NS_IMETHOD PaintSVG(nsSVGRenderState *aContext, nsRect *aDirtyRect);
   NS_IMETHOD GetFrameForPointSVG(float x, float y, nsIFrame** hit);  
   NS_IMETHOD_(nsRect) GetCoveredRegion();
   NS_IMETHOD UpdateCoveredRegion();
   NS_IMETHOD InitialUpdate();
-  NS_IMETHOD NotifyCanvasTMChanged(PRBool suppressInvalidation);
+  virtual void NotifySVGChanged(PRUint32 aFlags);
   NS_IMETHOD NotifyRedrawSuspended();
   NS_IMETHOD NotifyRedrawUnsuspended();
   NS_IMETHOD SetMatrixPropagation(PRBool aPropagate);
   NS_IMETHOD SetOverrideCTM(nsIDOMSVGMatrix *aCTM);
   virtual already_AddRefed<nsIDOMSVGMatrix> GetOverrideCTM();
   NS_IMETHOD GetBBox(nsIDOMSVGRect **_retval);
   NS_IMETHOD_(PRBool) IsDisplayContainer() { return PR_TRUE; }
   NS_IMETHOD_(PRBool) HasValidCoveredRect() { return PR_FALSE; }
--- a/layout/svg/base/src/nsSVGGFrame.cpp
+++ b/layout/svg/base/src/nsSVGGFrame.cpp
@@ -64,23 +64,25 @@ nsIAtom *
 nsSVGGFrame::GetType() const
 {
   return nsGkAtoms::svgGFrame;
 }
 
 //----------------------------------------------------------------------
 // nsISVGChildFrame methods
 
-NS_IMETHODIMP
-nsSVGGFrame::NotifyCanvasTMChanged(PRBool suppressInvalidation)
+void
+nsSVGGFrame::NotifySVGChanged(PRUint32 aFlags)
 {
-  // make sure our cached transform matrix gets (lazily) updated
-  mCanvasTM = nsnull;
+  if (aFlags & TRANSFORM_CHANGED) {
+    // make sure our cached transform matrix gets (lazily) updated
+    mCanvasTM = nsnull;
+  }
 
-  return nsSVGGFrameBase::NotifyCanvasTMChanged(suppressInvalidation);
+  nsSVGGFrameBase::NotifySVGChanged(aFlags);
 }
 
 NS_IMETHODIMP
 nsSVGGFrame::SetMatrixPropagation(PRBool aPropagate)
 {
   mPropagateTransform = aPropagate;
   return NS_OK;
 }
@@ -150,19 +152,13 @@ nsSVGGFrame::AttributeChanged(PRInt32   
                               nsIAtom*        aAttribute,
                               PRInt32         aModType)
 {
   if (aNameSpaceID == kNameSpaceID_None &&
       aAttribute == nsGkAtoms::transform) {
     // make sure our cached transform matrix gets (lazily) updated
     mCanvasTM = nsnull;
 
-    for (nsIFrame* kid = mFrames.FirstChild(); kid;
-         kid = kid->GetNextSibling()) {
-      nsISVGChildFrame* SVGFrame = nsnull;
-      CallQueryInterface(kid, &SVGFrame);
-      if (SVGFrame)
-        SVGFrame->NotifyCanvasTMChanged(PR_FALSE);
-    }  
+    nsSVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED);
   }
   
   return NS_OK;
 }
--- a/layout/svg/base/src/nsSVGGFrame.h
+++ b/layout/svg/base/src/nsSVGGFrame.h
@@ -68,17 +68,17 @@ public:
 
   // nsIFrame interface:
   NS_IMETHOD DidSetStyleContext();
   NS_IMETHOD AttributeChanged(PRInt32         aNameSpaceID,
                               nsIAtom*        aAttribute,
                               PRInt32         aModType);
 
   // nsISVGChildFrame interface:
-  NS_IMETHOD NotifyCanvasTMChanged(PRBool suppressInvalidation);
+  virtual void NotifySVGChanged(PRUint32 aFlags);
   NS_IMETHOD SetMatrixPropagation(PRBool aPropagate);
   NS_IMETHOD SetOverrideCTM(nsIDOMSVGMatrix *aCTM);
   virtual already_AddRefed<nsIDOMSVGMatrix> GetOverrideCTM();
 
   // nsSVGContainerFrame methods:
   virtual already_AddRefed<nsIDOMSVGMatrix> GetCanvasTM();
 
   nsCOMPtr<nsIDOMSVGMatrix> mCanvasTM;
--- a/layout/svg/base/src/nsSVGGlyphFrame.cpp
+++ b/layout/svg/base/src/nsSVGGlyphFrame.cpp
@@ -470,22 +470,20 @@ nsSVGGlyphFrame::InitialUpdate()
   
   // Do unset the various reflow bits, though.
   mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
               NS_FRAME_HAS_DIRTY_CHILDREN);
   
   return NS_OK;
 }  
 
-NS_IMETHODIMP
-nsSVGGlyphFrame::NotifyCanvasTMChanged(PRBool suppressInvalidation)
+void
+nsSVGGlyphFrame::NotifySVGChanged(PRUint32 aFlags)
 {
-  UpdateGeometry(PR_TRUE, suppressInvalidation);
-  
-  return NS_OK;
+  UpdateGeometry(PR_TRUE, (aFlags & SUPPRESS_INVALIDATION) != 0);
 }
 
 NS_IMETHODIMP
 nsSVGGlyphFrame::NotifyRedrawSuspended()
 {
   // XXX should we cache the fact that redraw is suspended?
   return NS_OK;
 }
--- a/layout/svg/base/src/nsSVGGlyphFrame.h
+++ b/layout/svg/base/src/nsSVGGlyphFrame.h
@@ -107,17 +107,17 @@ public:
 #endif
 
   // nsISVGChildFrame interface:
   NS_IMETHOD PaintSVG(nsSVGRenderState *aContext, nsRect *aDirtyRect);
   NS_IMETHOD GetFrameForPointSVG(float x, float y, nsIFrame** hit);
   NS_IMETHOD_(nsRect) GetCoveredRegion();
   NS_IMETHOD UpdateCoveredRegion();
   NS_IMETHOD InitialUpdate();
-  NS_IMETHOD NotifyCanvasTMChanged(PRBool suppressInvalidation);
+  virtual void NotifySVGChanged(PRUint32 aFlags);
   NS_IMETHOD NotifyRedrawSuspended();
   NS_IMETHOD NotifyRedrawUnsuspended();
   NS_IMETHOD SetMatrixPropagation(PRBool aPropagate) { return NS_OK; }
   NS_IMETHOD SetOverrideCTM(nsIDOMSVGMatrix *aCTM) { return NS_ERROR_FAILURE; }
   virtual already_AddRefed<nsIDOMSVGMatrix> GetOverrideCTM() { return nsnull; }
   NS_IMETHOD GetBBox(nsIDOMSVGRect **_retval);
   NS_IMETHOD_(PRBool) IsDisplayContainer() { return PR_FALSE; }
   NS_IMETHOD_(PRBool) HasValidCoveredRect() { return PR_TRUE; }
--- a/layout/svg/base/src/nsSVGGradientFrame.cpp
+++ b/layout/svg/base/src/nsSVGGradientFrame.cpp
@@ -267,21 +267,23 @@ nsSVGGradientFrame::GetGradientTransform
       else
         CallQueryInterface(aSource, &frame);
     }
     nsCOMPtr<nsIDOMSVGRect> rect;
     if (frame) {
       nsCOMPtr<nsIDOMSVGMatrix> matrix = frame->GetOverrideCTM();
       frame->SetMatrixPropagation(PR_FALSE);
       frame->SetOverrideCTM(nsnull);
-      frame->NotifyCanvasTMChanged(PR_TRUE);
+      frame->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                              nsISVGChildFrame::TRANSFORM_CHANGED);
       frame->GetBBox(getter_AddRefs(rect));
       frame->SetMatrixPropagation(PR_TRUE);
       frame->SetOverrideCTM(matrix);
-      frame->NotifyCanvasTMChanged(PR_TRUE);
+      frame->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                              nsISVGChildFrame::TRANSFORM_CHANGED);
     }
     if (rect) {
       float x, y, width, height;
       rect->GetX(&x);
       rect->GetY(&y);
       rect->GetWidth(&width);
       rect->GetHeight(&height);
       bboxMatrix = gfxMatrix(width, 0, 0, height, x, y);
--- a/layout/svg/base/src/nsSVGInnerSVGFrame.cpp
+++ b/layout/svg/base/src/nsSVGInnerSVGFrame.cpp
@@ -82,17 +82,17 @@ public:
   NS_IMETHOD GetFrameName(nsAString& aResult) const
   {
     return MakeFrameName(NS_LITERAL_STRING("SVGInnerSVG"), aResult);
   }
 #endif
 
   // nsISVGChildFrame interface:
   NS_IMETHOD PaintSVG(nsSVGRenderState *aContext, nsRect *aDirtyRect);
-  NS_IMETHOD NotifyCanvasTMChanged(PRBool suppressInvalidation);
+  virtual void NotifySVGChanged(PRUint32 aFlags);
   NS_IMETHOD SetMatrixPropagation(PRBool aPropagate);
   NS_IMETHOD SetOverrideCTM(nsIDOMSVGMatrix *aCTM);
   virtual already_AddRefed<nsIDOMSVGMatrix> GetOverrideCTM();
   NS_IMETHOD GetFrameForPointSVG(float x, float y, nsIFrame** hit);
 
   // nsSVGContainerFrame methods:
   virtual already_AddRefed<nsIDOMSVGMatrix> GetCanvasTM();
 
@@ -186,23 +186,59 @@ nsSVGInnerSVGFrame::PaintSVG(nsSVGRender
 
   rv = nsSVGInnerSVGFrameBase::PaintSVG(aContext, aDirtyRect);
 
   gfx->Restore();
 
   return rv;
 }
 
-NS_IMETHODIMP
-nsSVGInnerSVGFrame::NotifyCanvasTMChanged(PRBool suppressInvalidation)
+void
+nsSVGInnerSVGFrame::NotifySVGChanged(PRUint32 aFlags)
 {
-  // make sure our cached transform matrix gets (lazily) updated
-  mCanvasTM = nsnull;
+  if (aFlags & COORD_CONTEXT_CHANGED) {
+
+    nsSVGSVGElement *svg = static_cast<nsSVGSVGElement*>(mContent);
+
+    // Coordinate context changes affect mCanvasTM if we have a
+    // percentage 'x' or 'y', or if we have a percentage 'width' or 'height' AND
+    // a 'viewBox'.
+
+    if (!(aFlags & TRANSFORM_CHANGED) &&
+        svg->mLengthAttributes[nsSVGSVGElement::X].IsPercentage() ||
+        svg->mLengthAttributes[nsSVGSVGElement::Y].IsPercentage() ||
+        (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::viewBox) &&
+         (svg->mLengthAttributes[nsSVGSVGElement::WIDTH].IsPercentage() ||
+          svg->mLengthAttributes[nsSVGSVGElement::HEIGHT].IsPercentage()))) {
+    
+      aFlags |= TRANSFORM_CHANGED;
+    }
 
-  return nsSVGInnerSVGFrameBase::NotifyCanvasTMChanged(suppressInvalidation);
+    // XXX We could clear the COORD_CONTEXT_CHANGED flag in some circumstances
+    // if we have a non-percentage 'width' AND 'height, or if we have a 'viewBox'
+    // rect. This is because, when we have a viewBox rect, the viewBox rect
+    // is the coordinate context for our children, and it isn't changing.
+    // Percentage lengths on our children will continue to resolve to the
+    // same number of user units because they're relative to our viewBox rect. The
+    // same is true if we have a non-percentage width and height and don't have a
+    // viewBox. We (the <svg>) establish the coordinate context for our children. Our
+    // children don't care about changes to our parent coordinate context unless that
+    // change results in a change to the coordinate context that _we_ establish. Hence
+    // we can (should, really) stop propagating COORD_CONTEXT_CHANGED in these cases.
+    // We'd actually need to check that we have a viewBox rect and not just
+    // that viewBox is set, since it could be set to none.
+    // Take care not to break the testcase for bug 394463 when implementing this
+  }
+
+  if (aFlags & TRANSFORM_CHANGED) {
+    // make sure our cached transform matrix gets (lazily) updated
+    mCanvasTM = nsnull;
+  }
+
+  nsSVGInnerSVGFrameBase::NotifySVGChanged(aFlags);
 }
 
 NS_IMETHODIMP
 nsSVGInnerSVGFrame::SetMatrixPropagation(PRBool aPropagate)
 {
   mPropagateTransform = aPropagate;
   return NS_OK;
 }
@@ -270,29 +306,40 @@ nsSVGInnerSVGFrame::UnsuspendRedraw()
     return NS_ERROR_FAILURE;
   }
   return outerSVGFrame->UnsuspendRedraw();
 }
 
 NS_IMETHODIMP
 nsSVGInnerSVGFrame::NotifyViewportChange()
 {
+  PRUint32 flags = COORD_CONTEXT_CHANGED;
+
+#if 1
+  // XXX nsSVGSVGElement::InvalidateTransformNotifyFrame calls us for changes
+  // to 'x' and 'y'. Until this is fixed, add TRANSFORM_CHANGED to flags
+  // unconditionally.
+
+  flags |= TRANSFORM_CHANGED;
+
   // make sure canvas transform matrix gets (lazily) recalculated:
   mCanvasTM = nsnull;
+#else
+  // viewport changes only affect our transform if we have a viewBox attribute
+  if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::viewBox)) {
+    // make sure canvas transform matrix gets (lazily) recalculated:
+    mCanvasTM = nsnull;
+
+    flags |= TRANSFORM_CHANGED;
+  }
+#endif
   
   // inform children
   SuspendRedraw();
-  nsIFrame* kid = mFrames.FirstChild();
-  while (kid) {
-    nsISVGChildFrame* SVGFrame = nsnull;
-    CallQueryInterface(kid, &SVGFrame);
-    if (SVGFrame)
-      SVGFrame->NotifyCanvasTMChanged(PR_FALSE); 
-    kid = kid->GetNextSibling();
-  }
+  nsSVGUtils::NotifyChildrenOfSVGChange(this, flags);
   UnsuspendRedraw();
   return NS_OK;
 }
 
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods:
 
 already_AddRefed<nsIDOMSVGMatrix>
--- a/layout/svg/base/src/nsSVGMarkerFrame.cpp
+++ b/layout/svg/base/src/nsSVGMarkerFrame.cpp
@@ -166,17 +166,19 @@ nsSVGMarkerFrame::PaintMark(nsSVGRenderS
     nsSVGUtils::SetClipRect(gfx, matrix, x, y, width, height);
   }
 
   for (nsIFrame* kid = mFrames.FirstChild(); kid;
        kid = kid->GetNextSibling()) {
     nsISVGChildFrame* SVGFrame = nsnull;
     CallQueryInterface(kid, &SVGFrame);
     if (SVGFrame) {
-      SVGFrame->NotifyCanvasTMChanged(PR_TRUE);
+      // The CTM of each frame referencing us may be different.
+      SVGFrame->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                                 nsISVGChildFrame::TRANSFORM_CHANGED);
       nsSVGUtils::PaintChildWithEffects(aContext, nsnull, kid);
     }
   }
 
   if (GetStyleDisplay()->IsScrollableOverflow())
     gfx->Restore();
 
   return NS_OK;
--- a/layout/svg/base/src/nsSVGMaskFrame.cpp
+++ b/layout/svg/base/src/nsSVGMaskFrame.cpp
@@ -107,23 +107,25 @@ nsSVGMaskFrame::ComputeMaskAlpha(nsSVGRe
     tmpHeight = &mask->mLengthAttributes[nsSVGMaskElement::HEIGHT];
 
     PRUint16 units =
       mask->mEnumAttributes[nsSVGMaskElement::MASKUNITS].GetAnimValue();
 
     if (units == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
 
       aParent->SetMatrixPropagation(PR_FALSE);
-      aParent->NotifyCanvasTMChanged(PR_TRUE);
+      aParent->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                                nsISVGChildFrame::TRANSFORM_CHANGED);
 
       nsCOMPtr<nsIDOMSVGRect> bbox;
       aParent->GetBBox(getter_AddRefs(bbox));
 
       aParent->SetMatrixPropagation(PR_TRUE);
-      aParent->NotifyCanvasTMChanged(PR_TRUE);
+      aParent->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                                nsISVGChildFrame::TRANSFORM_CHANGED);
 
       if (!bbox)
         return nsnull;
 
 #ifdef DEBUG_tor
       bbox->GetX(&x);
       bbox->GetY(&y);
       bbox->GetWidth(&width);
--- a/layout/svg/base/src/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/base/src/nsSVGOuterSVGFrame.cpp
@@ -222,17 +222,17 @@ nsSVGOuterSVGFrame::GetMinWidth(nsIRende
 nsSVGOuterSVGFrame::GetPrefWidth(nsIRenderingContext *aRenderingContext)
 {
   nscoord result;
   DISPLAY_PREF_WIDTH(this, result);
 
   nsSVGSVGElement *svg = static_cast<nsSVGSVGElement*>(mContent);
   nsSVGLength2 &width = svg->mLengthAttributes[nsSVGSVGElement::WIDTH];
 
-  if (width.GetSpecifiedUnitType() == nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE) {
+  if (width.IsPercentage()) {
     result = nscoord(0);
   } else {
     result = nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(svg));
     if (result < 0) {
       result = nscoord(0);
     }
   }
 
@@ -246,27 +246,27 @@ nsSVGOuterSVGFrame::GetIntrinsicSize()
   // specified and we're embedded inside an nsIObjectLoadingContent.
 
   IntrinsicSize intrinsicSize;
 
   nsSVGSVGElement *content = static_cast<nsSVGSVGElement*>(mContent);
   nsSVGLength2 &width  = content->mLengthAttributes[nsSVGSVGElement::WIDTH];
   nsSVGLength2 &height = content->mLengthAttributes[nsSVGSVGElement::HEIGHT];
 
-  if (width.GetSpecifiedUnitType() == nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE) {
+  if (width.IsPercentage()) {
     float val = width.GetAnimValInSpecifiedUnits() / 100.0f;
     if (val < 0.0f) val = 0.0f;
     intrinsicSize.width.SetPercentValue(val);
   } else {
     nscoord val = nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(content));
     if (val < 0) val = 0;
     intrinsicSize.width.SetCoordValue(val);
   }
 
-  if (height.GetSpecifiedUnitType() == nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE) {
+  if (height.IsPercentage()) {
     float val = height.GetAnimValInSpecifiedUnits() / 100.0f;
     if (val < 0.0f) val = 0.0f;
     intrinsicSize.height.SetPercentValue(val);
   } else {
     nscoord val = nsPresContext::CSSPixelsToAppUnits(height.GetAnimValue(content));
     if (val < 0) val = 0;
     intrinsicSize.height.SetCoordValue(val);
   }
@@ -280,18 +280,17 @@ nsSVGOuterSVGFrame::GetIntrinsicRatio()
   // We only have an intrinsic size/ratio if our width and height attributes
   // are both specified and set to non-percentage values, or we have a viewBox
   // rect: http://www.w3.org/TR/SVGMobile12/coords.html#IntrinsicSizing
 
   nsSVGSVGElement *content = static_cast<nsSVGSVGElement*>(mContent);
   nsSVGLength2 &width  = content->mLengthAttributes[nsSVGSVGElement::WIDTH];
   nsSVGLength2 &height = content->mLengthAttributes[nsSVGSVGElement::HEIGHT];
 
-  if (width.GetSpecifiedUnitType()  != nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE &&
-      height.GetSpecifiedUnitType() != nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE) {
+  if (!width.IsPercentage() && !height.IsPercentage()) {
     nsSize ratio(width.GetAnimValue(content), height.GetAnimValue(content));
     if (ratio.width < 0) {
       ratio.width = 0;
     }
     if (ratio.height < 0) {
       ratio.height = 0;
     }
     return ratio;
@@ -713,32 +712,38 @@ nsSVGOuterSVGFrame::UnsuspendRedraw()
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSVGOuterSVGFrame::NotifyViewportChange()
 {
   // no point in doing anything when were not init'ed yet:
-  if (!mViewportInitialized) return NS_OK;
-
-/* XXX this caused reftest failures
-  // viewport changes only affect our transform if we have a viewBox attribute
-  nsSVGSVGElement *svgElem = static_cast<nsSVGSVGElement*>(mContent);
-  if (!svgElem->HasAttr(kNameSpaceID_None, nsGkAtoms::viewBox)) {
+  if (!mViewportInitialized) {
     return NS_OK;
   }
-*/
+
+  PRUint32 flags = COORD_CONTEXT_CHANGED;
 
-  // make sure canvas transform matrix gets (lazily) recalculated:
-  mCanvasTM = nsnull;
-  
+  // viewport changes only affect our transform if we have a viewBox attribute
+#if 1
+  {
+#else
+  // XXX this caused reftest failures (bug 413960)
+  if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::viewBox)) {
+#endif
+    // make sure canvas transform matrix gets (lazily) recalculated:
+    mCanvasTM = nsnull;
+
+    flags |= TRANSFORM_CHANGED;
+  }
+
   // inform children
   SuspendRedraw();
-  nsSVGUtils::NotifyChildrenCanvasTMChanged(this, PR_FALSE);
+  nsSVGUtils::NotifyChildrenOfSVGChange(this, flags);
   UnsuspendRedraw();
   return NS_OK;
 }
 
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods:
 
 already_AddRefed<nsIDOMSVGMatrix>
--- a/layout/svg/base/src/nsSVGPathGeometryFrame.cpp
+++ b/layout/svg/base/src/nsSVGPathGeometryFrame.cpp
@@ -465,22 +465,20 @@ nsSVGPathGeometryFrame::InitialUpdate()
                "We don't actually participate in reflow");
   
   // Do unset the various reflow bits, though.
   mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
               NS_FRAME_HAS_DIRTY_CHILDREN);
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsSVGPathGeometryFrame::NotifyCanvasTMChanged(PRBool suppressInvalidation)
+void
+nsSVGPathGeometryFrame::NotifySVGChanged(PRUint32 aFlags)
 {
-  UpdateGraphic(suppressInvalidation);
-
-  return NS_OK;
+  UpdateGraphic((aFlags & SUPPRESS_INVALIDATION) != 0);
 }
 
 NS_IMETHODIMP
 nsSVGPathGeometryFrame::NotifyRedrawSuspended()
 {
   // XXX should we cache the fact that redraw is suspended?
   return NS_OK;
 }
--- a/layout/svg/base/src/nsSVGPathGeometryFrame.h
+++ b/layout/svg/base/src/nsSVGPathGeometryFrame.h
@@ -104,17 +104,17 @@ public:
 
 protected:
   // nsISVGChildFrame interface:
   NS_IMETHOD PaintSVG(nsSVGRenderState *aContext, nsRect *aDirtyRect);
   NS_IMETHOD GetFrameForPointSVG(float x, float y, nsIFrame** hit);
   NS_IMETHOD_(nsRect) GetCoveredRegion();
   NS_IMETHOD UpdateCoveredRegion();
   NS_IMETHOD InitialUpdate();
-  NS_IMETHOD NotifyCanvasTMChanged(PRBool suppressInvalidation);
+  virtual void NotifySVGChanged(PRUint32 aFlags);
   NS_IMETHOD NotifyRedrawSuspended();
   NS_IMETHOD NotifyRedrawUnsuspended();
   NS_IMETHOD SetMatrixPropagation(PRBool aPropagate);
   NS_IMETHOD SetOverrideCTM(nsIDOMSVGMatrix *aCTM);
   virtual already_AddRefed<nsIDOMSVGMatrix> GetOverrideCTM();
   NS_IMETHOD GetBBox(nsIDOMSVGRect **_retval);
   NS_IMETHOD_(PRBool) IsDisplayContainer() { return PR_FALSE; }
   NS_IMETHOD_(PRBool) HasValidCoveredRect() { return PR_TRUE; }
--- a/layout/svg/base/src/nsSVGPatternFrame.cpp
+++ b/layout/svg/base/src/nsSVGPatternFrame.cpp
@@ -776,20 +776,22 @@ nsSVGPatternFrame::GetCallerGeometry(nsI
   // will be in *device coordinates*
   nsISVGChildFrame *callerSVGFrame;
   if (callerType == nsGkAtoms::svgGlyphFrame)
     CallQueryInterface(aSource->GetParent(), &callerSVGFrame);
   else
     CallQueryInterface(aSource, &callerSVGFrame);
 
   callerSVGFrame->SetMatrixPropagation(PR_FALSE);
-  callerSVGFrame->NotifyCanvasTMChanged(PR_TRUE);
+  callerSVGFrame->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION | 
+                                   nsISVGChildFrame::TRANSFORM_CHANGED );
   callerSVGFrame->GetBBox(aBBox);
   callerSVGFrame->SetMatrixPropagation(PR_TRUE);
-  callerSVGFrame->NotifyCanvasTMChanged(PR_TRUE);
+  callerSVGFrame->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
+                                   nsISVGChildFrame::TRANSFORM_CHANGED);
 
   // Sanity check
   PRUint16 type = GetPatternUnits();
   if (type == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
     float width, height;
     (*aBBox)->GetWidth(&width);
     (*aBBox)->GetHeight(&height);
     if (width <= 0 || height <= 0) {
--- a/layout/svg/base/src/nsSVGTextFrame.cpp
+++ b/layout/svg/base/src/nsSVGTextFrame.cpp
@@ -90,25 +90,19 @@ nsSVGTextFrame::AttributeChanged(PRInt32
   if (aNameSpaceID != kNameSpaceID_None)
     return NS_OK;
 
   if (aAttribute == nsGkAtoms::transform) {
     // transform has changed
 
     // make sure our cached transform matrix gets (lazily) updated
     mCanvasTM = nsnull;
-    
-    nsIFrame* kid = mFrames.FirstChild();
-    while (kid) {
-      nsISVGChildFrame* SVGFrame = nsnull;
-      CallQueryInterface(kid, &SVGFrame);
-      if (SVGFrame)
-        SVGFrame->NotifyCanvasTMChanged(PR_FALSE);
-      kid = kid->GetNextSibling();
-    }
+
+    nsSVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED);
+   
   } else if (aAttribute == nsGkAtoms::x ||
              aAttribute == nsGkAtoms::y ||
              aAttribute == nsGkAtoms::dx ||
              aAttribute == nsGkAtoms::dy) {
     NotifyGlyphMetricsChange();
   }
 
  return NS_OK;
@@ -193,29 +187,35 @@ nsSVGTextFrame::GetCharNumAtPosition(nsI
 
   return nsSVGTextFrameBase::GetCharNumAtPosition(point,  _retval);
 }
 
 
 //----------------------------------------------------------------------
 // nsISVGChildFrame methods
 
-NS_IMETHODIMP
-nsSVGTextFrame::NotifyCanvasTMChanged(PRBool suppressInvalidation)
+void
+nsSVGTextFrame::NotifySVGChanged(PRUint32 aFlags)
 {
-  // make sure our cached transform matrix gets (lazily) updated
-  mCanvasTM = nsnull;
+  if (aFlags & TRANSFORM_CHANGED) {
+    // make sure our cached transform matrix gets (lazily) updated
+    mCanvasTM = nsnull;
+  }
 
-  // If we are positioned using percentage values we need to update our
-  // position whenever our viewport's dimensions change.
-  // XXX we should really have a separate notification for viewport changes and
-  // not overload NotifyCanvasTMChanged.
-  NotifyGlyphMetricsChange();
+  if (aFlags & COORD_CONTEXT_CHANGED) {
+    // If we are positioned using percentage values we need to update our
+    // position whenever our viewport's dimensions change.
 
-  return nsSVGTextFrameBase::NotifyCanvasTMChanged(suppressInvalidation);
+    // XXX We could check here whether the text frame or any of its children
+    // have any percentage co-ordinates and only update if they don't. This
+    // may not be worth it as we might need to check each glyph
+    NotifyGlyphMetricsChange();
+  }
+
+  nsSVGTextFrameBase::NotifySVGChanged(aFlags);
 }
 
 NS_IMETHODIMP
 nsSVGTextFrame::NotifyRedrawSuspended()
 {
   mMetricsState = suspended;
 
   return nsSVGTextFrameBase::NotifyRedrawSuspended();
--- a/layout/svg/base/src/nsSVGTextFrame.h
+++ b/layout/svg/base/src/nsSVGTextFrame.h
@@ -76,17 +76,17 @@ public:
     return MakeFrameName(NS_LITERAL_STRING("SVGText"), aResult);
   }
 #endif
 
   // nsISVGChildFrame interface:
   NS_IMETHOD SetMatrixPropagation(PRBool aPropagate);
   NS_IMETHOD SetOverrideCTM(nsIDOMSVGMatrix *aCTM);
   virtual already_AddRefed<nsIDOMSVGMatrix> GetOverrideCTM();
-  NS_IMETHOD NotifyCanvasTMChanged(PRBool suppressInvalidation);
+  virtual void NotifySVGChanged(PRUint32 aFlags);
   NS_IMETHOD NotifyRedrawSuspended();
   NS_IMETHOD NotifyRedrawUnsuspended();
   NS_IMETHOD GetBBox(nsIDOMSVGRect **_retval);
   
   // nsSVGContainerFrame methods:
   virtual already_AddRefed<nsIDOMSVGMatrix> GetCanvasTM();
   
   // nsISVGTextContentMetrics
--- a/layout/svg/base/src/nsSVGTextPathFrame.cpp
+++ b/layout/svg/base/src/nsSVGTextPathFrame.cpp
@@ -197,18 +197,17 @@ nsSVGTextPathFrame::GetStartOffset()
 {
   nsSVGTextPathElement *tp = static_cast<nsSVGTextPathElement*>(mContent);
   nsSVGLength2 *length = &tp->mLengthAttributes[nsSVGTextPathElement::STARTOFFSET];
   float val = length->GetAnimValInSpecifiedUnits();
 
   if (val == 0.0f)
     return 0.0;
 
-  if (length->GetSpecifiedUnitType() ==
-      nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE) {
+  if (length->IsPercentage()) {
     nsRefPtr<gfxFlattenedPath> data = GetFlattenedPath();
     return data ? (val * data->GetLength() / 100.0) : 0.0;
   } else {
     return val * GetPathScale();
   }
 }
 
 gfxFloat
--- a/layout/svg/base/src/nsSVGUseFrame.cpp
+++ b/layout/svg/base/src/nsSVGUseFrame.cpp
@@ -125,23 +125,17 @@ nsSVGUseFrame::AttributeChanged(PRInt32 
                                 PRInt32         aModType)
 {
   if (aNameSpaceID == kNameSpaceID_None &&
       (aAttribute == nsGkAtoms::x ||
        aAttribute == nsGkAtoms::y)) {
     // make sure our cached transform matrix gets (lazily) updated
     mCanvasTM = nsnull;
     
-    for (nsIFrame* kid = mFrames.FirstChild(); kid;
-         kid = kid->GetNextSibling()) {
-      nsISVGChildFrame* SVGFrame = nsnull;
-      CallQueryInterface(kid, &SVGFrame);
-      if (SVGFrame)
-        SVGFrame->NotifyCanvasTMChanged(PR_FALSE);
-    }
+    nsSVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED);
     return NS_OK;
   }
 
   return nsSVGUseFrameBase::AttributeChanged(aNameSpaceID,
                                              aAttribute, aModType);
 }
 
 void
--- a/layout/svg/base/src/nsSVGUtils.cpp
+++ b/layout/svg/base/src/nsSVGUtils.cpp
@@ -913,18 +913,17 @@ nsSVGUtils::ObjectSpace(nsIDOMSVGRect *a
   {
     float width, height;
     aRect->GetWidth(&width);
     aRect->GetHeight(&height);
     axis = sqrt(width * width + height * height)/sqrt(2.0f);
   }
   }
 
-  if (aLength->GetSpecifiedUnitType() ==
-      nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE) {
+  if (aLength->IsPercentage()) {
     fraction = aLength->GetAnimValInSpecifiedUnits() / 100;
   } else
     fraction = aLength->GetAnimValue(static_cast<nsSVGSVGElement*>
                                                 (nsnull));
 
   return fraction * axis;
 }
 
@@ -1110,30 +1109,30 @@ nsSVGUtils::GetCanvasTM(nsIFrame *aFrame
   nsIDOMSVGMatrix *retval;
   geometryFrame->GetCanvasTM(getter_AddRefs(matrix));
   retval = matrix.get();
   NS_IF_ADDREF(retval);
   return retval;
 }
 
 void 
-nsSVGUtils::NotifyChildrenCanvasTMChanged(nsIFrame *aFrame, PRBool suppressInvalidation)
+nsSVGUtils::NotifyChildrenOfSVGChange(nsIFrame *aFrame, PRUint32 aFlags)
 {
   nsIFrame *aKid = aFrame->GetFirstChild(nsnull);
 
   while (aKid) {
     nsISVGChildFrame* SVGFrame = nsnull;
     CallQueryInterface(aKid, &SVGFrame);
     if (SVGFrame) {
-      SVGFrame->NotifyCanvasTMChanged(suppressInvalidation); 
+      SVGFrame->NotifySVGChanged(aFlags); 
     } else {
       NS_ASSERTION(aKid->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
       // recurse into the children of container frames e.g. <clipPath>, <mask>
       // in case they have child frames with transformation matrices
-      nsSVGUtils::NotifyChildrenCanvasTMChanged(aKid, suppressInvalidation);
+      nsSVGUtils::NotifyChildrenOfSVGChange(aKid, aFlags);
     }
     aKid = aKid->GetNextSibling();
   }
 }
 
 void
 nsSVGUtils::AddObserver(nsISupports *aObserver, nsISupports *aTarget)
 {
--- a/layout/svg/base/src/nsSVGUtils.h
+++ b/layout/svg/base/src/nsSVGUtils.h
@@ -349,20 +349,20 @@ public:
 
   /*
    * Returns the CanvasTM of the indicated frame, whether it's a
    * child or container SVG frame.
    */
   static already_AddRefed<nsIDOMSVGMatrix> GetCanvasTM(nsIFrame *aFrame);
 
   /*
-   * Tells child frames that the canvasTM has changed
+   * Tells child frames that something that might affect them has changed
    */
   static void
-  NotifyChildrenCanvasTMChanged(nsIFrame *aFrame, PRBool suppressInvalidation);
+  NotifyChildrenOfSVGChange(nsIFrame *aFrame, PRUint32 aFlags);
 
   /*
    * Get frame's covered region by walking the children and doing union.
    */
   static nsRect
   GetCoveredRegion(const nsFrameList &aFrames);
 
   /*