bug 875329 - don't ignore transforms when rendering SVG-in-OT glyphs. r=heycam
authorJonathan Kew <jkew@mozilla.com>
Wed, 11 Sep 2013 08:27:45 +0100
changeset 160365 fd077e283f6a22e024ab2076963b1e6967ec3996
parent 160364 3fe9649ebd84b0a4bdefc1321172ada924cb4372
child 160366 4cf68e5c75ca54cabc3a2b182b2c7bda0c1baafe
push id407
push userlsblakk@mozilla.com
push dateTue, 03 Dec 2013 03:32:50 +0000
treeherdermozilla-release@babf8c9ebc52 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs875329
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 875329 - don't ignore transforms when rendering SVG-in-OT glyphs. r=heycam
layout/svg/nsISVGChildFrame.h
layout/svg/nsSVGAFrame.cpp
layout/svg/nsSVGClipPathFrame.cpp
layout/svg/nsSVGClipPathFrame.h
layout/svg/nsSVGContainerFrame.cpp
layout/svg/nsSVGContainerFrame.h
layout/svg/nsSVGFilterFrame.cpp
layout/svg/nsSVGFilterFrame.h
layout/svg/nsSVGFilterInstance.cpp
layout/svg/nsSVGFilterInstance.h
layout/svg/nsSVGFilterPaintCallback.h
layout/svg/nsSVGForeignObjectFrame.cpp
layout/svg/nsSVGForeignObjectFrame.h
layout/svg/nsSVGGFrame.cpp
layout/svg/nsSVGGFrame.h
layout/svg/nsSVGGenericContainerFrame.cpp
layout/svg/nsSVGGenericContainerFrame.h
layout/svg/nsSVGGeometryFrame.h
layout/svg/nsSVGGlyphFrame.cpp
layout/svg/nsSVGGlyphFrame.h
layout/svg/nsSVGImageFrame.cpp
layout/svg/nsSVGInnerSVGFrame.cpp
layout/svg/nsSVGInnerSVGFrame.h
layout/svg/nsSVGIntegrationUtils.cpp
layout/svg/nsSVGMarkerFrame.cpp
layout/svg/nsSVGMarkerFrame.h
layout/svg/nsSVGMaskFrame.cpp
layout/svg/nsSVGMaskFrame.h
layout/svg/nsSVGOuterSVGFrame.cpp
layout/svg/nsSVGOuterSVGFrame.h
layout/svg/nsSVGPathGeometryFrame.cpp
layout/svg/nsSVGPathGeometryFrame.h
layout/svg/nsSVGPatternFrame.cpp
layout/svg/nsSVGPatternFrame.h
layout/svg/nsSVGSwitchFrame.cpp
layout/svg/nsSVGTSpanFrame.cpp
layout/svg/nsSVGTSpanFrame.h
layout/svg/nsSVGTextFrame.cpp
layout/svg/nsSVGTextFrame.h
layout/svg/nsSVGTextFrame2.cpp
layout/svg/nsSVGTextFrame2.h
layout/svg/nsSVGUtils.cpp
layout/svg/nsSVGUtils.h
--- a/layout/svg/nsISVGChildFrame.h
+++ b/layout/svg/nsISVGChildFrame.h
@@ -39,20 +39,30 @@ public:
   typedef mozilla::SVGAnimatedNumberList SVGAnimatedNumberList;
   typedef mozilla::SVGNumberList SVGNumberList;
   typedef mozilla::SVGAnimatedLengthList SVGAnimatedLengthList;
   typedef mozilla::SVGLengthList SVGLengthList;
   typedef mozilla::SVGUserUnitList SVGUserUnitList;
 
   NS_DECL_QUERYFRAME_TARGET(nsISVGChildFrame)
 
-  // Paint this frame - aDirtyRect is the area being redrawn, in frame
-  // offset pixel coordinates
+  // Paint this frame.
+  // aDirtyRect is the area being redrawn, in frame offset pixel coordinates.
+  // aTransformRoot (if non-null) is the frame at which we stop looking up
+  // transforms, when painting content that is part of an SVG glyph. (See
+  // bug 875329.)
+  // For normal SVG graphics using display-list rendering, any transforms on
+  // the element or its parents will have already been set up in the context
+  // before PaintSVG is called. When painting SVG glyphs, this is not the case,
+  // so the element's full transform needs to be applied; but we don't want to
+  // apply transforms from outside the actual glyph element, so we need to know
+  // how far up the ancestor chain to go.
   NS_IMETHOD PaintSVG(nsRenderingContext* aContext,
-                      const nsIntRect *aDirtyRect)=0;
+                      const nsIntRect *aDirtyRect,
+                      nsIFrame* aTransformRoot = nullptr) = 0;
 
   // Check if this frame or children contain the given point,
   // specified in app units relative to the origin of the outer
   // svg frame (origin ill-defined in the case of borders - bug
   // 290770).  See bug 290852 for foreignObject complications.
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint)=0;
 
   // Get bounds in our gfxContext's coordinates space (in app units)
--- a/layout/svg/nsSVGAFrame.cpp
+++ b/layout/svg/nsSVGAFrame.cpp
@@ -54,17 +54,18 @@ public:
   {
     return MakeFrameName(NS_LITERAL_STRING("SVGA"), aResult);
   }
 #endif
   // nsISVGChildFrame interface:
   virtual void NotifySVGChanged(uint32_t aFlags);
   
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor);
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
 
   // nsSVGTextContainerFrame methods:
   virtual void GetXY(mozilla::SVGUserUnitList *aX, mozilla::SVGUserUnitList *aY);
   virtual void GetDxDy(mozilla::SVGUserUnitList *aDx, mozilla::SVGUserUnitList *aDy);
   virtual const SVGNumberList* GetRotate() {
     return nullptr;
   }
 
@@ -138,31 +139,33 @@ nsSVGAFrame::NotifySVGChanged(uint32_t a
 
   nsSVGAFrameBase::NotifySVGChanged(aFlags);
 }
 
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods:
 
 gfxMatrix
-nsSVGAFrame::GetCanvasTM(uint32_t aFor)
+nsSVGAFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
-  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) {
     if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
         (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
       return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
     }
   }
   if (!mCanvasTM) {
     NS_ASSERTION(mParent, "null parent");
 
     nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
     dom::SVGAElement *content = static_cast<dom::SVGAElement*>(mContent);
 
-    gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM(aFor));
+    gfxMatrix tm = content->PrependLocalTransformsTo(
+        this == aTransformRoot ? gfxMatrix() :
+                                 parent->GetCanvasTM(aFor, aTransformRoot));
 
     mCanvasTM = new gfxMatrix(tm);
   }
 
   return *mCanvasTM;
 }
 
 //----------------------------------------------------------------------
--- a/layout/svg/nsSVGClipPathFrame.cpp
+++ b/layout/svg/nsSVGClipPathFrame.cpp
@@ -310,17 +310,17 @@ nsSVGClipPathFrame::Init(nsIContent* aCo
 
 nsIAtom *
 nsSVGClipPathFrame::GetType() const
 {
   return nsGkAtoms::svgClipPathFrame;
 }
 
 gfxMatrix
-nsSVGClipPathFrame::GetCanvasTM(uint32_t aFor)
+nsSVGClipPathFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
   SVGClipPathElement *content = static_cast<SVGClipPathElement*>(mContent);
 
   gfxMatrix tm =
     content->PrependLocalTransformsTo(mClipParentMatrix ?
                                       *mClipParentMatrix : gfxMatrix());
 
   return nsSVGUtils::AdjustMatrixForUnits(tm,
--- a/layout/svg/nsSVGClipPathFrame.h
+++ b/layout/svg/nsSVGClipPathFrame.h
@@ -96,12 +96,13 @@ public:
   };
 
   nsIFrame *mClipParent;
   nsAutoPtr<gfxMatrix> mClipParentMatrix;
   // recursion prevention flag
   bool mInUse;
 
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
 };
 
 #endif
--- a/layout/svg/nsSVGContainerFrame.cpp
+++ b/layout/svg/nsSVGContainerFrame.cpp
@@ -249,31 +249,32 @@ nsSVGDisplayContainerFrame::IsSVGTransfo
   return foundTransform;
 }
 
 //----------------------------------------------------------------------
 // nsISVGChildFrame methods
 
 NS_IMETHODIMP
 nsSVGDisplayContainerFrame::PaintSVG(nsRenderingContext* aContext,
-                                     const nsIntRect *aDirtyRect)
+                                     const nsIntRect *aDirtyRect,
+                                     nsIFrame* aTransformRoot)
 {
   NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
                (mState & NS_FRAME_IS_NONDISPLAY) ||
                PresContext()->IsGlyph(),
                "If display lists are enabled, only painting of non-display "
                "SVG should take this code path");
 
   const nsStyleDisplay *display = StyleDisplay();
   if (display->mOpacity == 0.0)
     return NS_OK;
 
   for (nsIFrame* kid = mFrames.FirstChild(); kid;
        kid = kid->GetNextSibling()) {
-    nsSVGUtils::PaintFrameWithEffects(aContext, aDirtyRect, kid);
+    nsSVGUtils::PaintFrameWithEffects(aContext, aDirtyRect, kid, aTransformRoot);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP_(nsIFrame*)
 nsSVGDisplayContainerFrame::GetFrameForPoint(const nsPoint &aPoint)
 {
--- a/layout/svg/nsSVGContainerFrame.h
+++ b/layout/svg/nsSVGContainerFrame.h
@@ -52,17 +52,18 @@ protected:
   }
 
 public:
   NS_DECL_QUERYFRAME_TARGET(nsSVGContainerFrame)
   NS_DECL_QUERYFRAME
   NS_DECL_FRAMEARENA_HELPERS
 
   // Returns the transform to our gfxContext (to device pixels, not CSS px)
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) {
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) {
     return gfxMatrix();
   }
 
   /**
    * Returns true if the frame's content has a transform that applies only to
    * its children, and not to the frame itself. For example, an implicit
    * transform introduced by a 'viewBox' attribute, or an explicit transform
    * due to a root-<svg> having its currentScale/currentTransform properties
@@ -141,17 +142,18 @@ public:
                                 const nsRect&           aDirtyRect,
                                 const nsDisplayListSet& aLists) MOZ_OVERRIDE;
 
   virtual bool IsSVGTransformed(gfxMatrix *aOwnTransform = nullptr,
                                 gfxMatrix *aFromParentTransform = nullptr) const MOZ_OVERRIDE;
 
   // nsISVGChildFrame interface:
   NS_IMETHOD PaintSVG(nsRenderingContext* aContext,
-                      const nsIntRect *aDirtyRect) MOZ_OVERRIDE;
+                      const nsIntRect *aDirtyRect,
+                      nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint) MOZ_OVERRIDE;
   NS_IMETHOD_(nsRect) GetCoveredRegion() MOZ_OVERRIDE;
   virtual void ReflowSVG() MOZ_OVERRIDE;
   virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE;
   virtual SVGBBox GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
                                       uint32_t aFlags) MOZ_OVERRIDE;
   NS_IMETHOD_(bool) IsDisplayContainer() MOZ_OVERRIDE { return true; }
 };
--- a/layout/svg/nsSVGFilterFrame.cpp
+++ b/layout/svg/nsSVGFilterFrame.cpp
@@ -119,34 +119,36 @@ private:
 class MOZ_STACK_CLASS nsAutoFilterInstance {
 public:
   nsAutoFilterInstance(nsIFrame *aTarget,
                        nsSVGFilterFrame *aFilterFrame,
                        nsSVGFilterPaintCallback *aPaint,
                        const nsRect *aPostFilterDirtyRect,
                        const nsRect *aPreFilterDirtyRect,
                        const nsRect *aOverridePreFilterVisualOverflowRect,
-                       const gfxRect *aOverrideBBox = nullptr);
+                       const gfxRect *aOverrideBBox = nullptr,
+                       nsIFrame* aTransformRoot = nullptr);
   ~nsAutoFilterInstance() {}
 
   // If this returns null, then draw nothing. Either the filter draws
   // nothing or it is "in error".
   nsSVGFilterInstance* get() { return mInstance; }
 
 private:
   nsAutoPtr<nsSVGFilterInstance> mInstance;
 };
 
 nsAutoFilterInstance::nsAutoFilterInstance(nsIFrame *aTarget,
                                            nsSVGFilterFrame *aFilterFrame,
                                            nsSVGFilterPaintCallback *aPaint,
                                            const nsRect *aPostFilterDirtyRect,
                                            const nsRect *aPreFilterDirtyRect,
                                            const nsRect *aPreFilterVisualOverflowRectOverride,
-                                           const gfxRect *aOverrideBBox)
+                                           const gfxRect *aOverrideBBox,
+                                           nsIFrame* aTransformRoot)
 {
   const SVGFilterElement *filter = aFilterFrame->GetFilterContent();
 
   uint16_t filterUnits =
     aFilterFrame->GetEnumValue(SVGFilterElement::FILTERUNITS);
   uint16_t primitiveUnits =
     aFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS);
 
@@ -274,17 +276,18 @@ nsAutoFilterInstance::nsAutoFilterInstan
   }
 
   // Setup instance data
   mInstance =
     new nsSVGFilterInstance(aTarget, aPaint, filter, bbox, filterRegion,
                             nsIntSize(filterRes.width, filterRes.height),
                             filterToDeviceSpace, filterToFrameSpaceInCSSPx,
                             preFilterVisualOverflowRect, postFilterDirtyRect,
-                            preFilterDirtyRect, primitiveUnits);
+                            preFilterDirtyRect, primitiveUnits,
+                            aTransformRoot);
 }
 
 uint16_t
 nsSVGFilterFrame::GetEnumValue(uint32_t aIndex, nsIContent *aDefault)
 {
   nsSVGEnum& thisEnum =
     static_cast<SVGFilterElement *>(mContent)->mEnumAttributes[aIndex];
 
@@ -434,20 +437,22 @@ nsSVGFilterFrame::AttributeChanged(int32
   return nsSVGFilterFrameBase::AttributeChanged(aNameSpaceID,
                                                 aAttribute, aModType);
 }
 
 nsresult
 nsSVGFilterFrame::PaintFilteredFrame(nsRenderingContext *aContext,
                                      nsIFrame *aFilteredFrame,
                                      nsSVGFilterPaintCallback *aPaintCallback,
-                                     const nsRect *aDirtyArea)
+                                     const nsRect *aDirtyArea,
+                                     nsIFrame* aTransformRoot)
 {
   nsAutoFilterInstance instance(aFilteredFrame, this, aPaintCallback,
-                                aDirtyArea, nullptr, nullptr);
+                                aDirtyArea, nullptr, nullptr, nullptr,
+                                aTransformRoot);
   if (!instance.get()) {
     return NS_OK;
   }
   nsRefPtr<gfxASurface> result;
   nsresult rv = instance.get()->Render(getter_AddRefs(result));
   if (NS_SUCCEEDED(rv) && result) {
     nsSVGUtils::CompositeSurfaceMatrix(aContext->ThebesContext(),
       result, instance.get()->GetFilterSpaceToDeviceSpaceTransform(), 1.0);
--- a/layout/svg/nsSVGFilterFrame.h
+++ b/layout/svg/nsSVGFilterFrame.h
@@ -61,17 +61,18 @@ public:
    * Paint the given filtered frame.
    * @param aDirtyArea The area than needs to be painted, in aFilteredFrame's
    *   frame space (i.e. relative to its origin, the top-left corner of its
    *   border box).
    */
   nsresult PaintFilteredFrame(nsRenderingContext *aContext,
                               nsIFrame *aFilteredFrame,
                               nsSVGFilterPaintCallback *aPaintCallback,
-                              const nsRect* aDirtyArea);
+                              const nsRect* aDirtyArea,
+                              nsIFrame* aTransformRoot);
 
   /**
    * Returns the post-filter area that could be dirtied when the given
    * pre-filter area of aFilteredFrame changes.
    * @param aPreFilterDirtyRect The pre-filter area of aFilteredFrame that has
    *   changed, relative to aFilteredFrame, in app units.
    */
   nsRect GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
--- a/layout/svg/nsSVGFilterInstance.cpp
+++ b/layout/svg/nsSVGFilterInstance.cpp
@@ -344,17 +344,18 @@ nsSVGFilterInstance::BuildSourcePaint(Pr
 
   gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform().Invert();
   gfxContext *gfx = tmpCtx.ThebesContext();
   gfx->Multiply(deviceToFilterSpace);
 
   gfx->Save();
 
   gfxMatrix matrix =
-    nsSVGUtils::GetCanvasTM(mTargetFrame, nsISVGChildFrame::FOR_PAINTING);
+    nsSVGUtils::GetCanvasTM(mTargetFrame, nsISVGChildFrame::FOR_PAINTING,
+                            mTransformRoot);
   if (!matrix.IsSingular()) {
     gfx->Multiply(matrix);
     gfx->Rectangle(r);
     if ((aPrimitive == &mFillPaint && 
          nsSVGUtils::SetupCairoFillPaint(mTargetFrame, gfx)) ||
         (aPrimitive == &mStrokePaint &&
          nsSVGUtils::SetupCairoStrokePaint(mTargetFrame, gfx))) {
       gfx->Fill();
@@ -434,17 +435,17 @@ nsSVGFilterInstance::BuildSourceImages()
     // graphics temporarily paint to user space when painting the sources and
     // only set a user space to filter space transform on the gfxContext
     // (since that would eliminate the transform multiplications from user
     // space to device space and back again). However, that would make the
     // code more complex while being hard to get right without introducing
     // subtle bugs, and in practice it probably makes no real difference.)
     gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform().Invert();
     tmpCtx.ThebesContext()->Multiply(deviceToFilterSpace);
-    mPaintCallback->Paint(&tmpCtx, mTargetFrame, &dirty);
+    mPaintCallback->Paint(&tmpCtx, mTargetFrame, &dirty, mTransformRoot);
 
     gfxContext copyContext(sourceColorAlpha);
     copyContext.SetSource(offscreen);
     copyContext.Paint();
   }
 
   if (!mSourceColorAlpha.mResultNeededBox.IsEmpty()) {
     NS_ASSERTION(mSourceColorAlpha.mImageUsers > 0, "Some user must have needed this");
--- a/layout/svg/nsSVGFilterInstance.h
+++ b/layout/svg/nsSVGFilterInstance.h
@@ -82,30 +82,32 @@ public:
                       const gfxRect &aTargetBBox,
                       const gfxRect& aFilterRegion,
                       const nsIntSize& aFilterSpaceSize,
                       const gfxMatrix &aFilterSpaceToDeviceSpaceTransform,
                       const gfxMatrix &aFilterSpaceToFrameSpaceInCSSPxTransform,
                       const nsIntRect& aTargetBounds,
                       const nsIntRect& aPostFilterDirtyRect,
                       const nsIntRect& aPreFilterDirtyRect,
-                      uint16_t aPrimitiveUnits) :
+                      uint16_t aPrimitiveUnits,
+                      nsIFrame* aTransformRoot) :
     mTargetFrame(aTargetFrame),
     mPaintCallback(aPaintCallback),
     mFilterElement(aFilterElement),
     mTargetBBox(aTargetBBox),
     mFilterSpaceToDeviceSpaceTransform(aFilterSpaceToDeviceSpaceTransform),
     mFilterSpaceToFrameSpaceInCSSPxTransform(aFilterSpaceToFrameSpaceInCSSPxTransform),
     mFilterRegion(aFilterRegion),
     mFilterSpaceSize(aFilterSpaceSize),
     mSurfaceRect(nsIntPoint(0, 0), aFilterSpaceSize),
     mTargetBounds(aTargetBounds),
     mPostFilterDirtyRect(aPostFilterDirtyRect),
     mPreFilterDirtyRect(aPreFilterDirtyRect),
-    mPrimitiveUnits(aPrimitiveUnits) {
+    mPrimitiveUnits(aPrimitiveUnits),
+    mTransformRoot(aTransformRoot) {
   }
 
   /**
    * Returns the user specified "filter region", in the filtered element's user
    * space, after it has been adjusted out (if necessary) so that its edges
    * coincide with pixel boundaries of the offscreen surface into which the
    * filtered output would/will be painted.
    */
@@ -430,11 +432,12 @@ private:
    */
   uint16_t                mPrimitiveUnits;
 
   PrimitiveInfo           mSourceColorAlpha;
   PrimitiveInfo           mSourceAlpha;
   PrimitiveInfo           mFillPaint;
   PrimitiveInfo           mStrokePaint;
   nsTArray<PrimitiveInfo> mPrimitives;
+  nsIFrame*               mTransformRoot;
 };
 
 #endif
--- a/layout/svg/nsSVGFilterPaintCallback.h
+++ b/layout/svg/nsSVGFilterPaintCallback.h
@@ -17,14 +17,17 @@ public:
    * Paint the frame contents.
    * SVG frames will have had matrix propagation set to false already.
    * Non-SVG frames have to do their own thing.
    * The caller will do a Save()/Restore() as necessary so feel free
    * to mess with context state.
    * The context will be configured to use the "user space" coordinate
    * system.
    * @param aDirtyRect the dirty rect *in user space pixels*
+   * @param aTransformRoot the outermost frame whose transform should be taken
+   *                       into account when painting an SVG glyph
    */
   virtual void Paint(nsRenderingContext *aContext, nsIFrame *aTarget,
-                     const nsIntRect *aDirtyRect) = 0;
+                     const nsIntRect *aDirtyRect,
+                     nsIFrame* aTransformRoot) = 0;
 };
 
 #endif
--- a/layout/svg/nsSVGForeignObjectFrame.cpp
+++ b/layout/svg/nsSVGForeignObjectFrame.cpp
@@ -189,31 +189,32 @@ nsSVGForeignObjectFrame::IsSVGTransforme
     }
     foundTransform = true;
   }
   return foundTransform;
 }
 
 NS_IMETHODIMP
 nsSVGForeignObjectFrame::PaintSVG(nsRenderingContext *aContext,
-                                  const nsIntRect *aDirtyRect)
+                                  const nsIntRect *aDirtyRect,
+                                  nsIFrame* aTransformRoot)
 {
   NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
                (mState & NS_FRAME_IS_NONDISPLAY),
                "If display lists are enabled, only painting of non-display "
                "SVG should take this code path");
 
   if (IsDisabled())
     return NS_OK;
 
   nsIFrame* kid = GetFirstPrincipalChild();
   if (!kid)
     return NS_OK;
 
-  gfxMatrix canvasTM = GetCanvasTM(FOR_PAINTING);
+  gfxMatrix canvasTM = GetCanvasTM(FOR_PAINTING, aTransformRoot);
 
   if (canvasTM.IsSingular()) {
     NS_WARNING("Can't render foreignObject element!");
     return NS_ERROR_FAILURE;
   }
 
   nsRect kidDirtyRect = kid->GetVisualOverflowRect();
 
@@ -481,32 +482,34 @@ nsSVGForeignObjectFrame::GetBBoxContribu
     return SVGBBox();
   }
   return aToBBoxUserspace.TransformBounds(gfxRect(0.0, 0.0, w, h));
 }
 
 //----------------------------------------------------------------------
 
 gfxMatrix
-nsSVGForeignObjectFrame::GetCanvasTM(uint32_t aFor)
+nsSVGForeignObjectFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
-  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) {
     if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
         (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
       return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
     }
   }
   if (!mCanvasTM) {
     NS_ASSERTION(mParent, "null parent");
 
     nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
     SVGForeignObjectElement *content =
       static_cast<SVGForeignObjectElement*>(mContent);
 
-    gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM(aFor));
+    gfxMatrix tm = content->PrependLocalTransformsTo(
+        this == aTransformRoot ? gfxMatrix() :
+                                 parent->GetCanvasTM(aFor, aTransformRoot));
 
     mCanvasTM = new gfxMatrix(tm);
   }
   return *mCanvasTM;
 }
 
 //----------------------------------------------------------------------
 // Implementation helpers
--- a/layout/svg/nsSVGForeignObjectFrame.h
+++ b/layout/svg/nsSVGForeignObjectFrame.h
@@ -72,26 +72,27 @@ public:
   NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE
   {
     return MakeFrameName(NS_LITERAL_STRING("SVGForeignObject"), aResult);
   }
 #endif
 
   // nsISVGChildFrame interface:
   NS_IMETHOD PaintSVG(nsRenderingContext *aContext,
-                      const nsIntRect *aDirtyRect) MOZ_OVERRIDE;
+                      const nsIntRect *aDirtyRect,
+                      nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint) MOZ_OVERRIDE;
   NS_IMETHOD_(nsRect) GetCoveredRegion() MOZ_OVERRIDE;
   virtual void ReflowSVG() MOZ_OVERRIDE;
   virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE;
   virtual SVGBBox GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
                                       uint32_t aFlags) MOZ_OVERRIDE;
   NS_IMETHOD_(bool) IsDisplayContainer() MOZ_OVERRIDE { return true; }
 
-  gfxMatrix GetCanvasTM(uint32_t aFor);
+  gfxMatrix GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot = nullptr);
 
   nsRect GetInvalidRegion();
 
 protected:
   // implementation helpers:
   void DoReflow();
   void RequestReflow(nsIPresShell::IntrinsicDirty aType);
 
--- a/layout/svg/nsSVGGFrame.cpp
+++ b/layout/svg/nsSVGGFrame.cpp
@@ -60,31 +60,32 @@ nsSVGGFrame::NotifySVGChanged(uint32_t a
     // make sure our cached transform matrix gets (lazily) updated
     mCanvasTM = nullptr;
   }
 
   nsSVGGFrameBase::NotifySVGChanged(aFlags);
 }
 
 gfxMatrix
-nsSVGGFrame::GetCanvasTM(uint32_t aFor)
+nsSVGGFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
-  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) {
     if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
         (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
       return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
     }
   }
   if (!mCanvasTM) {
     NS_ASSERTION(mParent, "null parent");
 
     nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
     SVGGraphicsElement *content = static_cast<SVGGraphicsElement*>(mContent);
-
-    gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM(aFor));
+    gfxMatrix tm = content->PrependLocalTransformsTo(
+        this == aTransformRoot ? gfxMatrix() :
+                                 parent->GetCanvasTM(aFor, aTransformRoot));
 
     mCanvasTM = new gfxMatrix(tm);
   }
   return *mCanvasTM;
 }
 
 NS_IMETHODIMP
 nsSVGGFrame::AttributeChanged(int32_t         aNameSpaceID,
--- a/layout/svg/nsSVGGFrame.h
+++ b/layout/svg/nsSVGGFrame.h
@@ -47,14 +47,15 @@ public:
   NS_IMETHOD AttributeChanged(int32_t         aNameSpaceID,
                               nsIAtom*        aAttribute,
                               int32_t         aModType) MOZ_OVERRIDE;
 
   // nsISVGChildFrame interface:
   virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE;
 
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
 
   nsAutoPtr<gfxMatrix> mCanvasTM;
 };
 
 #endif
--- a/layout/svg/nsSVGGenericContainerFrame.cpp
+++ b/layout/svg/nsSVGGenericContainerFrame.cpp
@@ -41,21 +41,23 @@ nsSVGGenericContainerFrame::GetType() co
 {
   return nsGkAtoms::svgGenericContainerFrame;
 }
 
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods:
 
 gfxMatrix
-nsSVGGenericContainerFrame::GetCanvasTM(uint32_t aFor)
+nsSVGGenericContainerFrame::GetCanvasTM(uint32_t aFor,
+                                        nsIFrame* aTransformRoot)
 {
-  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) {
     if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
         (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
       return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
     }
   }
 
   NS_ASSERTION(mParent, "null parent");
   
-  return static_cast<nsSVGContainerFrame*>(mParent)->GetCanvasTM(aFor);
+  return static_cast<nsSVGContainerFrame*>(mParent)->
+      GetCanvasTM(aFor, aTransformRoot);
 }
--- a/layout/svg/nsSVGGenericContainerFrame.h
+++ b/layout/svg/nsSVGGenericContainerFrame.h
@@ -44,12 +44,13 @@ public:
 #ifdef DEBUG
   NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE
   {
     return MakeFrameName(NS_LITERAL_STRING("SVGGenericContainer"), aResult);
   }
 #endif
 
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
 };
 
 #endif // __NS_SVGGENERICCONTAINERFRAME_H__
--- a/layout/svg/nsSVGGeometryFrame.h
+++ b/layout/svg/nsSVGGeometryFrame.h
@@ -47,17 +47,18 @@ public:
 		    nsIFrame* aPrevInFlow) MOZ_OVERRIDE;
 
   virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE
   {
     return nsSVGGeometryFrameBase::IsFrameOfType(aFlags & ~(nsIFrame::eSVG | nsIFrame::eSVGGeometry));
   }
 
   // nsSVGGeometryFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) = 0;
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) = 0;
   uint16_t GetClipRule();
 
 protected:
   /**
    * This function returns a set of bit flags indicating which parts of the
    * element (fill, stroke, bounds) should intercept pointer events. It takes
    * into account the type of element and the value of the 'pointer-events'
    * property on the element.
--- a/layout/svg/nsSVGGlyphFrame.cpp
+++ b/layout/svg/nsSVGGlyphFrame.cpp
@@ -353,17 +353,18 @@ nsSVGGlyphFrame::BuildDisplayList(nsDisp
     new (aBuilder) nsDisplaySVGGlyphs(aBuilder, this));
 }
 
 //----------------------------------------------------------------------
 // nsISVGChildFrame methods
 
 NS_IMETHODIMP
 nsSVGGlyphFrame::PaintSVG(nsRenderingContext *aContext,
-                          const nsIntRect *aDirtyRect)
+                          const nsIntRect *aDirtyRect,
+                          nsIFrame* aTransformRoot)
 {
   if (!StyleVisibility()->IsVisible())
     return NS_OK;
 
   if (StyleFont()->mFont.size <= 0) {
     // Don't even try to paint, or cairo will go into an error state.
     return NS_OK;
   }
@@ -382,17 +383,17 @@ nsSVGGlyphFrame::PaintSVG(nsRenderingCon
     break;
   }
 
   if (renderMode != SVGAutoRenderState::NORMAL) {
     NS_ABORT_IF_FALSE(renderMode == SVGAutoRenderState::CLIP ||
                       renderMode == SVGAutoRenderState::CLIP_MASK,
                       "Unknown render mode");
     gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(gfx);
-    SetupGlobalTransform(gfx, FOR_PAINTING);
+    SetupGlobalTransform(gfx, FOR_PAINTING, aTransformRoot);
 
     CharacterIterator iter(this, true);
     if (!iter.SetInitialMatrix(gfx)) {
       return NS_OK;
     }
 
     if (renderMode == SVGAutoRenderState::CLIP_MASK) {
       gfx->SetColor(gfxRGBA(1.0f, 1.0f, 1.0f, 1.0f));
@@ -402,17 +403,17 @@ nsSVGGlyphFrame::PaintSVG(nsRenderingCon
     }
 
     return NS_OK;
   }
 
   // We are adding patterns or gradients to the context. Save
   // it so we don't leak them into the next object we draw
   gfx->Save();
-  SetupGlobalTransform(gfx, FOR_PAINTING);
+  SetupGlobalTransform(gfx, FOR_PAINTING, aTransformRoot);
 
   CharacterIterator iter(this, true);
   if (!iter.SetInitialMatrix(gfx)) {
     gfx->Restore();
     return NS_OK;
   }
 
   gfxTextObjectPaint *outerObjectPaint =
@@ -667,29 +668,30 @@ nsSVGGlyphFrame::GetBBoxContribution(con
 
   return bbox;
 }
 
 //----------------------------------------------------------------------
 // nsSVGGeometryFrame methods:
 
 gfxMatrix
-nsSVGGlyphFrame::GetCanvasTM(uint32_t aFor)
+nsSVGGlyphFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
   if (mOverrideCanvasTM) {
     return *mOverrideCanvasTM;
   }
-  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) {
     if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
         (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
       return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
     }
   }
   NS_ASSERTION(mParent, "null parent");
-  return static_cast<nsSVGContainerFrame*>(mParent)->GetCanvasTM(aFor);
+  return static_cast<nsSVGContainerFrame*>(mParent)->
+      GetCanvasTM(aFor, aTransformRoot);
 }
 
 //----------------------------------------------------------------------
 // nsSVGGlyphFrame methods:
 
 bool
 nsSVGGlyphFrame::GetCharacterData(nsAString & aCharacterData)
 {
@@ -1596,19 +1598,20 @@ nsSVGGlyphFrame::NotifyGlyphMetricsChang
 {
   nsSVGTextContainerFrame *containerFrame =
     static_cast<nsSVGTextContainerFrame *>(mParent);
   if (containerFrame)
     containerFrame->NotifyGlyphMetricsChange();
 }
 
 void
-nsSVGGlyphFrame::SetupGlobalTransform(gfxContext *aContext, uint32_t aFor)
+nsSVGGlyphFrame::SetupGlobalTransform(gfxContext *aContext, uint32_t aFor,
+                                      nsIFrame* aTransformRoot)
 {
-  gfxMatrix matrix = GetCanvasTM(aFor);
+  gfxMatrix matrix = GetCanvasTM(aFor, aTransformRoot);
   if (!matrix.IsSingular()) {
     aContext->Multiply(matrix);
   }
 }
 
 void
 nsSVGGlyphFrame::ClearTextRun()
 {
--- a/layout/svg/nsSVGGlyphFrame.h
+++ b/layout/svg/nsSVGGlyphFrame.h
@@ -222,28 +222,30 @@ public:
 
   virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                 const nsRect&           aDirtyRect,
                                 const nsDisplayListSet& aLists) MOZ_OVERRIDE;
 
   // nsISVGChildFrame interface:
   // These four always use the global transform, even if NS_STATE_NONDISPLAY_CHILD
   NS_IMETHOD PaintSVG(nsRenderingContext *aContext,
-                      const nsIntRect *aDirtyRect) MOZ_OVERRIDE;
+                      const nsIntRect *aDirtyRect,
+                      nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint) MOZ_OVERRIDE;
   virtual SVGBBox GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
                                       uint32_t aFlags) MOZ_OVERRIDE;
 
   NS_IMETHOD_(nsRect) GetCoveredRegion() MOZ_OVERRIDE;
   virtual void ReflowSVG() MOZ_OVERRIDE;
   virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE;
   NS_IMETHOD_(bool) IsDisplayContainer() MOZ_OVERRIDE { return false; }
 
   // nsSVGGeometryFrame methods
-  gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  gfxMatrix GetCanvasTM(uint32_t aFor,
+                        nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
 
   // nsISVGGlyphFragmentNode interface:
   // These do not use the global transform if NS_STATE_NONDISPLAY_CHILD
   virtual uint32_t GetNumberOfChars() MOZ_OVERRIDE;
   virtual float GetComputedTextLength() MOZ_OVERRIDE;
   virtual float GetSubStringLength(uint32_t charnum, uint32_t fragmentChars) MOZ_OVERRIDE;
   virtual int32_t GetCharNumAtPosition(mozilla::nsISVGPoint *point) MOZ_OVERRIDE;
   NS_IMETHOD_(nsSVGGlyphFrame *) GetFirstGlyphFrame() MOZ_OVERRIDE;
@@ -308,17 +310,18 @@ private:
   void AddBoundingBoxesToPath(CharacterIterator *aIter,
                               gfxContext *aContext);
   void DrawCharacters(CharacterIterator *aIter,
                       gfxContext *aContext,
                       DrawMode aDrawMode,
                       gfxTextObjectPaint *aObjectPaint = nullptr);
 
   void NotifyGlyphMetricsChange();
-  void SetupGlobalTransform(gfxContext *aContext, uint32_t aFor);
+  void SetupGlobalTransform(gfxContext *aContext, uint32_t aFor,
+                            nsIFrame* aTransformRoot = nullptr);
   float GetSubStringAdvance(uint32_t charnum, uint32_t fragmentChars,
                             float aMetricsScale);
   gfxFloat GetBaselineOffset(float aMetricsScale);
 
   virtual void GetDxDy(SVGUserUnitList *aDx, SVGUserUnitList *aDy);
   virtual const SVGNumberList *GetRotate();
 
   // Used to support GetBBoxContribution by making GetConvasTM use this as the
--- a/layout/svg/nsSVGImageFrame.cpp
+++ b/layout/svg/nsSVGImageFrame.cpp
@@ -50,17 +50,18 @@ class nsSVGImageFrame : public nsSVGImag
 protected:
   nsSVGImageFrame(nsStyleContext* aContext) : nsSVGImageFrameBase(aContext) {}
   virtual ~nsSVGImageFrame();
 
 public:
   NS_DECL_FRAMEARENA_HELPERS
 
   // nsISVGChildFrame interface:
-  NS_IMETHOD PaintSVG(nsRenderingContext *aContext, const nsIntRect *aDirtyRect);
+  NS_IMETHOD PaintSVG(nsRenderingContext *aContext, const nsIntRect *aDirtyRect,
+                      nsIFrame* aTransformRoot) MOZ_OVERRIDE;
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint);
   virtual void ReflowSVG();
 
   // nsSVGPathGeometryFrame methods:
   virtual uint16_t GetHitTestFlags();
 
   // nsIFrame interface:
   NS_IMETHOD  AttributeChanged(int32_t         aNameSpaceID,
@@ -83,19 +84,22 @@ public:
   {
     return MakeFrameName(NS_LITERAL_STRING("SVGImage"), aResult);
   }
 #endif
 
 private:
   gfxMatrix GetRasterImageTransform(int32_t aNativeWidth,
                                     int32_t aNativeHeight,
-                                    uint32_t aFor);
-  gfxMatrix GetVectorImageTransform(uint32_t aFor);
-  bool      TransformContextForPainting(gfxContext* aGfxContext);
+                                    uint32_t aFor,
+                                    nsIFrame* aTransformRoot = nullptr);
+  gfxMatrix GetVectorImageTransform(uint32_t aFor,
+                                    nsIFrame* aTransformRoot = nullptr);
+  bool TransformContextForPainting(gfxContext* aGfxContext,
+                                   nsIFrame* aTransformRoot);
 
   nsCOMPtr<imgINotificationObserver> mListener;
 
   nsCOMPtr<imgIContainer> mImageContainer;
 
   friend class nsSVGImageListener;
 };
 
@@ -200,59 +204,66 @@ nsSVGImageFrame::AttributeChanged(int32_
 
   return nsSVGImageFrameBase::AttributeChanged(aNameSpaceID,
                                                aAttribute, aModType);
 }
 
 gfxMatrix
 nsSVGImageFrame::GetRasterImageTransform(int32_t aNativeWidth,
                                          int32_t aNativeHeight,
-                                         uint32_t aFor)
+                                         uint32_t aFor,
+                                         nsIFrame* aTransformRoot)
 {
   float x, y, width, height;
   SVGImageElement *element = static_cast<SVGImageElement*>(mContent);
   element->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
 
   gfxMatrix viewBoxTM =
     SVGContentUtils::GetViewBoxTransform(width, height,
                                          0, 0, aNativeWidth, aNativeHeight,
                                          element->mPreserveAspectRatio);
 
-  return viewBoxTM * gfxMatrix().Translate(gfxPoint(x, y)) * GetCanvasTM(aFor);
+  return viewBoxTM *
+         gfxMatrix().Translate(gfxPoint(x, y)) *
+         GetCanvasTM(aFor, aTransformRoot);
 }
 
 gfxMatrix
-nsSVGImageFrame::GetVectorImageTransform(uint32_t aFor)
+nsSVGImageFrame::GetVectorImageTransform(uint32_t aFor,
+                                         nsIFrame* aTransformRoot)
 {
   float x, y, width, height;
   SVGImageElement *element = static_cast<SVGImageElement*>(mContent);
   element->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
 
   // No viewBoxTM needed here -- our height/width overrides any concept of
   // "native size" that the SVG image has, and it will handle viewBox and
   // preserveAspectRatio on its own once we give it a region to draw into.
 
-  return gfxMatrix().Translate(gfxPoint(x, y)) * GetCanvasTM(aFor);
+  return gfxMatrix().Translate(gfxPoint(x, y)) *
+         GetCanvasTM(aFor, aTransformRoot);
 }
 
 bool
-nsSVGImageFrame::TransformContextForPainting(gfxContext* aGfxContext)
+nsSVGImageFrame::TransformContextForPainting(gfxContext* aGfxContext,
+                                             nsIFrame* aTransformRoot)
 {
   gfxMatrix imageTransform;
   if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
-    imageTransform = GetVectorImageTransform(FOR_PAINTING);
+    imageTransform = GetVectorImageTransform(FOR_PAINTING, aTransformRoot);
   } else {
     int32_t nativeWidth, nativeHeight;
     if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
         NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
         nativeWidth == 0 || nativeHeight == 0) {
       return false;
     }
     imageTransform =
-      GetRasterImageTransform(nativeWidth, nativeHeight, FOR_PAINTING);
+      GetRasterImageTransform(nativeWidth, nativeHeight, FOR_PAINTING,
+                              aTransformRoot);
 
     // NOTE: We need to cancel out the effects of Full-Page-Zoom, or else
     // it'll get applied an extra time by DrawSingleUnscaledImage.
     nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
     gfxFloat pageZoomFactor =
       nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPx);
     imageTransform.Scale(pageZoomFactor, pageZoomFactor);
   }
@@ -264,17 +275,18 @@ nsSVGImageFrame::TransformContextForPain
   aGfxContext->Multiply(imageTransform);
   return true;
 }
 
 //----------------------------------------------------------------------
 // nsISVGChildFrame methods:
 NS_IMETHODIMP
 nsSVGImageFrame::PaintSVG(nsRenderingContext *aContext,
-                          const nsIntRect *aDirtyRect)
+                          const nsIntRect *aDirtyRect,
+                          nsIFrame* aTransformRoot)
 {
   nsresult rv = NS_OK;
 
   if (!StyleVisibility()->IsVisible())
     return NS_OK;
 
   float x, y, width, height;
   SVGImageElement *imgElem = static_cast<SVGImageElement*>(mContent);
@@ -295,20 +307,21 @@ nsSVGImageFrame::PaintSVG(nsRenderingCon
 
   if (mImageContainer) {
     gfxContext* ctx = aContext->ThebesContext();
     gfxContextAutoSaveRestore autoRestorer(ctx);
 
     if (StyleDisplay()->IsScrollableOverflow()) {
       gfxRect clipRect = nsSVGUtils::GetClipRectForFrame(this, x, y,
                                                          width, height);
-      nsSVGUtils::SetClipRect(ctx, GetCanvasTM(FOR_PAINTING), clipRect);
+      nsSVGUtils::SetClipRect(ctx, GetCanvasTM(FOR_PAINTING, aTransformRoot),
+                              clipRect);
     }
 
-    if (!TransformContextForPainting(ctx)) {
+    if (!TransformContextForPainting(ctx, aTransformRoot)) {
       return NS_ERROR_FAILURE;
     }
 
     // fill-opacity doesn't affect <image>, so if we're allowed to
     // optimize group opacity, the opacity used for compositing the
     // image into the current canvas is just the group opacity.
     float opacity = 1.0f;
     if (nsSVGUtils::CanOptimizeOpacity(this)) {
--- a/layout/svg/nsSVGInnerSVGFrame.cpp
+++ b/layout/svg/nsSVGInnerSVGFrame.cpp
@@ -54,17 +54,18 @@ nsSVGInnerSVGFrame::GetType() const
   return nsGkAtoms::svgInnerSVGFrame;
 }
 
 //----------------------------------------------------------------------
 // nsISVGChildFrame methods
 
 NS_IMETHODIMP
 nsSVGInnerSVGFrame::PaintSVG(nsRenderingContext *aContext,
-                             const nsIntRect *aDirtyRect)
+                             const nsIntRect *aDirtyRect,
+                             nsIFrame* aTransformRoot)
 {
   NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
                (mState & NS_FRAME_IS_NONDISPLAY),
                "If display lists are enabled, only painting of non-display "
                "SVG should take this code path");
 
   gfxContextAutoSaveRestore autoSR;
 
@@ -73,17 +74,17 @@ nsSVGInnerSVGFrame::PaintSVG(nsRendering
     static_cast<SVGSVGElement*>(mContent)->
       GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
 
     if (width <= 0 || height <= 0) {
       return NS_OK;
     }
 
     nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
-    gfxMatrix clipTransform = parent->GetCanvasTM(FOR_PAINTING);
+    gfxMatrix clipTransform = parent->GetCanvasTM(FOR_PAINTING, aTransformRoot);
 
     gfxContext *gfx = aContext->ThebesContext();
     autoSR.SetContext(gfx);
     gfxRect clipRect =
       nsSVGUtils::GetClipRectForFrame(this, x, y, width, height);
     nsSVGUtils::SetClipRect(gfx, clipTransform, clipRect);
   }
 
@@ -263,31 +264,33 @@ nsSVGInnerSVGFrame::NotifyViewportOrTran
   // AttributeChanged(), so we should never be called.
   NS_ERROR("Not called for nsSVGInnerSVGFrame");
 }
 
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods:
 
 gfxMatrix
-nsSVGInnerSVGFrame::GetCanvasTM(uint32_t aFor)
+nsSVGInnerSVGFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
-  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) {
     if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
         (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
       return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
     }
   }
   if (!mCanvasTM) {
     NS_ASSERTION(mParent, "null parent");
 
     nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
     SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
 
-    gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM(aFor));
+    gfxMatrix tm = content->PrependLocalTransformsTo(
+        this == aTransformRoot ? gfxMatrix() :
+                                 parent->GetCanvasTM(aFor, aTransformRoot));
 
     mCanvasTM = new gfxMatrix(tm);
   }
   return *mCanvasTM;
 }
 
 bool
 nsSVGInnerSVGFrame::HasChildrenOnlyTransform(gfxMatrix *aTransform) const
--- a/layout/svg/nsSVGInnerSVGFrame.h
+++ b/layout/svg/nsSVGInnerSVGFrame.h
@@ -46,23 +46,26 @@ public:
   }
 #endif
 
   NS_IMETHOD  AttributeChanged(int32_t         aNameSpaceID,
                                nsIAtom*        aAttribute,
                                int32_t         aModType) MOZ_OVERRIDE;
 
   // nsISVGChildFrame interface:
-  NS_IMETHOD PaintSVG(nsRenderingContext *aContext, const nsIntRect *aDirtyRect) MOZ_OVERRIDE;
+  NS_IMETHOD PaintSVG(nsRenderingContext *aContext,
+                      const nsIntRect *aDirtyRect,
+                      nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
   virtual void ReflowSVG() MOZ_OVERRIDE;
   virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE;
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint) MOZ_OVERRIDE;
 
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
 
   virtual bool HasChildrenOnlyTransform(gfxMatrix *aTransform) const MOZ_OVERRIDE;
 
   // nsISVGSVGFrame interface:
   virtual void NotifyViewportOrTransformChanged(uint32_t aFlags) MOZ_OVERRIDE;
 
 protected:
 
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -377,17 +377,17 @@ class RegularFramePaintCallback : public
 public:
   RegularFramePaintCallback(nsDisplayListBuilder* aBuilder,
                             LayerManager* aManager,
                             const nsPoint& aOffset)
     : mBuilder(aBuilder), mLayerManager(aManager),
       mOffset(aOffset) {}
 
   virtual void Paint(nsRenderingContext *aContext, nsIFrame *aTarget,
-                     const nsIntRect* aDirtyRect)
+                     const nsIntRect* aDirtyRect, nsIFrame* aTransformRoot)
   {
     BasicLayerManager* basic = static_cast<BasicLayerManager*>(mLayerManager);
     basic->SetTarget(aContext->ThebesContext());
     nsRenderingContext::AutoPushTranslation push(aContext, -mOffset);
     mLayerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer, mBuilder);
   }
 
 private:
@@ -510,17 +510,17 @@ nsSVGIntegrationUtils::PaintFramesWithEf
     clipPathFrame->ClipPaint(aCtx, aFrame, cssPxToDevPxMatrix);
   }
 
   /* Paint the child */
   if (filterFrame) {
     RegularFramePaintCallback callback(aBuilder, aLayerManager,
                                        offsetWithoutSVGGeomFramePos);
     nsRect dirtyRect = aDirtyRect - offset;
-    filterFrame->PaintFilteredFrame(aCtx, aFrame, &callback, &dirtyRect);
+    filterFrame->PaintFilteredFrame(aCtx, aFrame, &callback, &dirtyRect, nullptr);
   } else {
     gfx->SetMatrix(matrixAutoSaveRestore.Matrix());
     aLayerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer, aBuilder);
     aCtx->Translate(offsetWithoutSVGGeomFramePos);
   }
 
   if (clipPathFrame && isTrivialClip) {
     gfx->Restore();
--- a/layout/svg/nsSVGMarkerFrame.cpp
+++ b/layout/svg/nsSVGMarkerFrame.cpp
@@ -65,29 +65,29 @@ nsSVGMarkerFrame::GetType() const
 {
   return nsGkAtoms::svgMarkerFrame;
 }
 
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods:
 
 gfxMatrix
-nsSVGMarkerFrame::GetCanvasTM(uint32_t aFor)
+nsSVGMarkerFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
   NS_ASSERTION(mMarkedFrame, "null nsSVGPathGeometry frame");
 
   if (mInUse2) {
     // We're going to be bailing drawing the marker, so return an identity.
     return gfxMatrix();
   }
 
   SVGMarkerElement *content = static_cast<SVGMarkerElement*>(mContent);
   
   mInUse2 = true;
-  gfxMatrix markedTM = mMarkedFrame->GetCanvasTM(aFor);
+  gfxMatrix markedTM = mMarkedFrame->GetCanvasTM(aFor, aTransformRoot);
   mInUse2 = false;
 
   gfxMatrix markerTM = content->GetMarkerTransform(mStrokeWidth, mX, mY,
                                                    mAutoAngle, mIsStart);
   gfxMatrix viewBoxTM = content->GetViewBoxTransform();
 
   return viewBoxTM * markerTM * markedTM;
 }
--- a/layout/svg/nsSVGMarkerFrame.h
+++ b/layout/svg/nsSVGMarkerFrame.h
@@ -92,17 +92,18 @@ public:
 
 private:
   // stuff needed for callback
   nsSVGPathGeometryFrame *mMarkedFrame;
   float mStrokeWidth, mX, mY, mAutoAngle;
   bool mIsStart;  // whether the callback is for a marker-start marker
 
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
 
   // A helper class to allow us to paint markers safely. The helper
   // automatically sets and clears the mInUse flag on the marker frame (to
   // prevent nasty reference loops) as well as the reference to the marked
   // frame and its coordinate context. It's easy to mess this up
   // and break things, so this helper makes the code far more robust.
   class AutoMarkerReferencer
   {
--- a/layout/svg/nsSVGMaskFrame.cpp
+++ b/layout/svg/nsSVGMaskFrame.cpp
@@ -165,17 +165,17 @@ nsSVGMaskFrame::Init(nsIContent* aConten
 
 nsIAtom *
 nsSVGMaskFrame::GetType() const
 {
   return nsGkAtoms::svgMaskFrame;
 }
 
 gfxMatrix
-nsSVGMaskFrame::GetCanvasTM(uint32_t aFor)
+nsSVGMaskFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
   NS_ASSERTION(mMaskParentMatrix, "null parent matrix");
 
   SVGMaskElement *mask = static_cast<SVGMaskElement*>(mContent);
 
   return nsSVGUtils::AdjustMatrixForUnits(
     mMaskParentMatrix ? *mMaskParentMatrix : gfxMatrix(),
     &mask->mEnumAttributes[SVGMaskElement::MASKCONTENTUNITS],
--- a/layout/svg/nsSVGMaskFrame.h
+++ b/layout/svg/nsSVGMaskFrame.h
@@ -87,12 +87,13 @@ private:
   };
 
   nsIFrame *mMaskParent;
   nsAutoPtr<gfxMatrix> mMaskParentMatrix;
   // recursion prevention flag
   bool mInUse;
 
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
 };
 
 #endif
--- a/layout/svg/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/nsSVGOuterSVGFrame.cpp
@@ -860,25 +860,26 @@ nsSVGOuterSVGFrame::NotifyViewportOrTran
   nsSVGUtils::NotifyChildrenOfSVGChange(GetFirstPrincipalChild(), aFlags);
 }
 
 //----------------------------------------------------------------------
 // nsISVGChildFrame methods:
 
 NS_IMETHODIMP
 nsSVGOuterSVGFrame::PaintSVG(nsRenderingContext* aContext,
-                             const nsIntRect *aDirtyRect)
+                             const nsIntRect *aDirtyRect,
+                             nsIFrame* aTransformRoot)
 {
   NS_ASSERTION(GetFirstPrincipalChild()->GetType() ==
                  nsGkAtoms::svgOuterSVGAnonChildFrame &&
                !GetFirstPrincipalChild()->GetNextSibling(),
                "We should have a single, anonymous, child");
   nsSVGOuterSVGAnonChildFrame *anonKid =
     static_cast<nsSVGOuterSVGAnonChildFrame*>(GetFirstPrincipalChild());
-  return anonKid->PaintSVG(aContext, aDirtyRect);
+  return anonKid->PaintSVG(aContext, aDirtyRect, aTransformRoot);
 }
 
 SVGBBox
 nsSVGOuterSVGFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
                                         uint32_t aFlags)
 {
   NS_ASSERTION(GetFirstPrincipalChild()->GetType() ==
                  nsGkAtoms::svgOuterSVGAnonChildFrame &&
@@ -890,25 +891,26 @@ nsSVGOuterSVGFrame::GetBBoxContribution(
     static_cast<nsSVGOuterSVGAnonChildFrame*>(GetFirstPrincipalChild());
   return anonKid->GetBBoxContribution(aToBBoxUserspace, aFlags);
 }
 
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods:
 
 gfxMatrix
-nsSVGOuterSVGFrame::GetCanvasTM(uint32_t aFor)
+nsSVGOuterSVGFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
-  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) {
     if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
         (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
       return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
     }
   }
   if (!mCanvasTM) {
+    NS_ASSERTION(!aTransformRoot, "transform root will be ignored here");
     SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
 
     float devPxPerCSSPx =
       1.0f / PresContext()->AppUnitsToFloatCSSPixels(
                                 PresContext()->AppUnitsPerDevPixel());
 
     gfxMatrix tm = content->PrependLocalTransformsTo(
                      gfxMatrix().Scale(devPxPerCSSPx, devPxPerCSSPx));
--- a/layout/svg/nsSVGOuterSVGFrame.h
+++ b/layout/svg/nsSVGOuterSVGFrame.h
@@ -106,23 +106,25 @@ public:
     return GetFirstPrincipalChild()->IsSVGTransformed();
   }
 
   // nsISVGSVGFrame interface:
   virtual void NotifyViewportOrTransformChanged(uint32_t aFlags) MOZ_OVERRIDE;
 
   // nsISVGChildFrame methods:
   NS_IMETHOD PaintSVG(nsRenderingContext* aContext,
-                      const nsIntRect *aDirtyRect) MOZ_OVERRIDE;
+                      const nsIntRect *aDirtyRect,
+                      nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
 
   virtual SVGBBox GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
                                       uint32_t aFlags) MOZ_OVERRIDE;
 
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
 
   /* Methods to allow descendant nsSVGForeignObjectFrame frames to register and
    * unregister themselves with their nearest nsSVGOuterSVGFrame ancestor. This
    * is temporary until display list based invalidation is impleented for SVG.
    * Maintaining a list of our foreignObject descendants allows us to search
    * them for areas that need to be invalidated, without having to also search
    * the SVG frame tree for foreignObjects. This is important so that bug 539356
    * does not slow down SVG in general (only foreignObjects, until bug 614732 is
@@ -256,19 +258,20 @@ public:
   /**
    * Get the "type" of the frame
    *
    * @see nsGkAtoms::svgOuterSVGAnonChildFrame
    */
   virtual nsIAtom* GetType() const MOZ_OVERRIDE;
 
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE {
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot) MOZ_OVERRIDE {
     // GetCanvasTM returns the transform from an SVG frame to the frame's
     // nsSVGOuterSVGFrame's content box, so we do not include any x/y offset
     // set on us for any CSS border or padding on our nsSVGOuterSVGFrame.
-    return static_cast<nsSVGOuterSVGFrame*>(mParent)->GetCanvasTM(aFor);
+    return static_cast<nsSVGOuterSVGFrame*>(mParent)->GetCanvasTM(aFor, aTransformRoot);
   }
 
   virtual bool HasChildrenOnlyTransform(gfxMatrix *aTransform) const MOZ_OVERRIDE;
 };
 
 #endif
--- a/layout/svg/nsSVGPathGeometryFrame.cpp
+++ b/layout/svg/nsSVGPathGeometryFrame.cpp
@@ -183,35 +183,36 @@ nsSVGPathGeometryFrame::BuildDisplayList
     new (aBuilder) nsDisplaySVGPathGeometry(aBuilder, this));
 }
 
 //----------------------------------------------------------------------
 // nsISVGChildFrame methods
 
 NS_IMETHODIMP
 nsSVGPathGeometryFrame::PaintSVG(nsRenderingContext *aContext,
-                                 const nsIntRect *aDirtyRect)
+                                 const nsIntRect *aDirtyRect,
+                                 nsIFrame* aTransformRoot)
 {
   if (!StyleVisibility()->IsVisible())
     return NS_OK;
 
   uint32_t paintOrder = StyleSVG()->mPaintOrder;
   if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) {
-    Render(aContext, eRenderFill | eRenderStroke);
+    Render(aContext, eRenderFill | eRenderStroke, aTransformRoot);
     PaintMarkers(aContext);
   } else {
     while (paintOrder) {
       uint32_t component =
         paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1);
       switch (component) {
         case NS_STYLE_PAINT_ORDER_FILL:
-          Render(aContext, eRenderFill);
+          Render(aContext, eRenderFill, aTransformRoot);
           break;
         case NS_STYLE_PAINT_ORDER_STROKE:
-          Render(aContext, eRenderStroke);
+          Render(aContext, eRenderStroke, aTransformRoot);
           break;
         case NS_STYLE_PAINT_ORDER_MARKERS:
           PaintMarkers(aContext);
           break;
       }
       paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
     }
   }
@@ -492,31 +493,33 @@ nsSVGPathGeometryFrame::GetBBoxContribut
 
   return bbox;
 }
 
 //----------------------------------------------------------------------
 // nsSVGGeometryFrame methods:
 
 gfxMatrix
-nsSVGPathGeometryFrame::GetCanvasTM(uint32_t aFor)
+nsSVGPathGeometryFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
-  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) {
     if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
         (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
       return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
     }
   }
 
   NS_ASSERTION(mParent, "null parent");
 
   nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
   dom::SVGGraphicsElement *content = static_cast<dom::SVGGraphicsElement*>(mContent);
 
-  return content->PrependLocalTransformsTo(parent->GetCanvasTM(aFor));
+  return content->PrependLocalTransformsTo(
+      this == aTransformRoot ? gfxMatrix() :
+                               parent->GetCanvasTM(aFor, aTransformRoot));
 }
 
 //----------------------------------------------------------------------
 // nsSVGPathGeometryFrame methods:
 
 nsSVGPathGeometryFrame::MarkerProperties
 nsSVGPathGeometryFrame::GetMarkerProperties(nsSVGPathGeometryFrame *aFrame)
 {
@@ -560,17 +563,18 @@ nsSVGPathGeometryFrame::MarkerProperties
   if (!mMarkerEnd)
     return nullptr;
   return static_cast<nsSVGMarkerFrame *>
     (mMarkerEnd->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr));
 }
 
 void
 nsSVGPathGeometryFrame::Render(nsRenderingContext *aContext,
-                               uint32_t aRenderComponents)
+                               uint32_t aRenderComponents,
+                               nsIFrame* aTransformRoot)
 {
   gfxContext *gfx = aContext->ThebesContext();
 
   uint16_t renderMode = SVGAutoRenderState::GetRenderMode(aContext);
 
   switch (StyleSVG()->mShapeRendering) {
   case NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED:
   case NS_STYLE_SHAPE_RENDERING_CRISPEDGES:
@@ -579,17 +583,17 @@ nsSVGPathGeometryFrame::Render(nsRenderi
   default:
     gfx->SetAntialiasMode(gfxContext::MODE_COVERAGE);
     break;
   }
 
   /* save/restore the state so we don't screw up the xform */
   gfx->Save();
 
-  GeneratePath(gfx, GetCanvasTM(FOR_PAINTING));
+  GeneratePath(gfx, GetCanvasTM(FOR_PAINTING, aTransformRoot));
 
   if (renderMode != SVGAutoRenderState::NORMAL) {
     NS_ABORT_IF_FALSE(renderMode == SVGAutoRenderState::CLIP ||
                       renderMode == SVGAutoRenderState::CLIP_MASK,
                       "Unknown render mode");
     gfx->Restore();
 
     if (GetClipRule() == NS_STYLE_FILL_RULE_EVENODD)
--- a/layout/svg/nsSVGPathGeometryFrame.h
+++ b/layout/svg/nsSVGPathGeometryFrame.h
@@ -76,36 +76,39 @@ public:
   }
 #endif
 
   virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                 const nsRect&           aDirtyRect,
                                 const nsDisplayListSet& aLists) MOZ_OVERRIDE;
 
   // nsSVGGeometryFrame methods
-  gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  gfxMatrix GetCanvasTM(uint32_t aFor,
+                        nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
 
 protected:
   // nsISVGChildFrame interface:
   NS_IMETHOD PaintSVG(nsRenderingContext *aContext,
-                      const nsIntRect *aDirtyRect) MOZ_OVERRIDE;
+                      const nsIntRect *aDirtyRect,
+                      nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint) MOZ_OVERRIDE;
   NS_IMETHOD_(nsRect) GetCoveredRegion() MOZ_OVERRIDE;
   virtual void ReflowSVG() MOZ_OVERRIDE;
   virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE;
   virtual SVGBBox GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
                                       uint32_t aFlags) MOZ_OVERRIDE;
   NS_IMETHOD_(bool) IsDisplayContainer() MOZ_OVERRIDE { return false; }
 
 protected:
   void GeneratePath(gfxContext *aContext, const gfxMatrix &aTransform);
 
 private:
   enum { eRenderFill = 1, eRenderStroke = 2 };
-  void Render(nsRenderingContext *aContext, uint32_t aRenderComponents);
+  void Render(nsRenderingContext *aContext, uint32_t aRenderComponents,
+              nsIFrame* aTransformRoot);
   void PaintMarkers(nsRenderingContext *aContext);
 
   struct MarkerProperties {
     nsSVGMarkerProperty* mMarkerStart;
     nsSVGMarkerProperty* mMarkerMid;
     nsSVGMarkerProperty* mMarkerEnd;
 
     bool MarkersExist() const {
--- a/layout/svg/nsSVGPatternFrame.cpp
+++ b/layout/svg/nsSVGPatternFrame.cpp
@@ -114,26 +114,26 @@ nsSVGPatternFrame::GetType() const
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods:
 
 // If our GetCanvasTM is getting called, we
 // need to return *our current* transformation
 // matrix, which depends on our units parameters
 // and X, Y, Width, and Height
 gfxMatrix
-nsSVGPatternFrame::GetCanvasTM(uint32_t aFor)
+nsSVGPatternFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
   if (mCTM) {
     return *mCTM;
   }
 
   // Do we know our rendering parent?
   if (mSource) {
     // Yes, use it!
-    return mSource->GetCanvasTM(aFor);
+    return mSource->GetCanvasTM(aFor, aTransformRoot);
   }
 
   // We get here when geometry in the <pattern> container is updated
   return gfxMatrix();
 }
 
 // -------------------------------------------------------------------------
 // Helper functions
--- a/layout/svg/nsSVGPatternFrame.h
+++ b/layout/svg/nsSVGPatternFrame.h
@@ -45,17 +45,18 @@ public:
                           nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
                           float aOpacity,
                           const gfxRect *aOverrideBounds) MOZ_OVERRIDE;
 
 public:
   typedef mozilla::SVGAnimatedPreserveAspectRatio SVGAnimatedPreserveAspectRatio;
 
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
 
   // nsIFrame interface:
   NS_IMETHOD AttributeChanged(int32_t         aNameSpaceID,
                               nsIAtom*        aAttribute,
                               int32_t         aModType) MOZ_OVERRIDE;
 
 #ifdef DEBUG
   virtual void Init(nsIContent*      aContent,
--- a/layout/svg/nsSVGSwitchFrame.cpp
+++ b/layout/svg/nsSVGSwitchFrame.cpp
@@ -46,17 +46,19 @@ public:
   }
 #endif
 
   virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                 const nsRect&           aDirtyRect,
                                 const nsDisplayListSet& aLists) MOZ_OVERRIDE;
 
   // nsISVGChildFrame interface:
-  NS_IMETHOD PaintSVG(nsRenderingContext* aContext, const nsIntRect *aDirtyRect);
+  NS_IMETHOD PaintSVG(nsRenderingContext* aContext,
+                      const nsIntRect *aDirtyRect,
+                      nsIFrame* aTransformRoot) MOZ_OVERRIDE;
   NS_IMETHODIMP_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint);
   NS_IMETHODIMP_(nsRect) GetCoveredRegion();
   virtual void ReflowSVG();
   virtual SVGBBox GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
                                       uint32_t aFlags);
 
 private:
   nsIFrame *GetActiveChildFrame();
@@ -100,29 +102,30 @@ nsSVGSwitchFrame::BuildDisplayList(nsDis
   nsIFrame* kid = GetActiveChildFrame();
   if (kid) {
     BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
   }
 }
 
 NS_IMETHODIMP
 nsSVGSwitchFrame::PaintSVG(nsRenderingContext* aContext,
-                           const nsIntRect *aDirtyRect)
+                           const nsIntRect *aDirtyRect,
+                           nsIFrame* aTransformRoot)
 {
   NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
                (mState & NS_FRAME_IS_NONDISPLAY),
                "If display lists are enabled, only painting of non-display "
                "SVG should take this code path");
 
   if (StyleDisplay()->mOpacity == 0.0)
     return NS_OK;
 
   nsIFrame *kid = GetActiveChildFrame();
   if (kid) {
-    nsSVGUtils::PaintFrameWithEffects(aContext, aDirtyRect, kid);
+    nsSVGUtils::PaintFrameWithEffects(aContext, aDirtyRect, kid, aTransformRoot);
   }
   return NS_OK;
 }
 
 
 NS_IMETHODIMP_(nsIFrame*)
 nsSVGSwitchFrame::GetFrameForPoint(const nsPoint &aPoint)
 {
--- a/layout/svg/nsSVGTSpanFrame.cpp
+++ b/layout/svg/nsSVGTSpanFrame.cpp
@@ -86,26 +86,27 @@ nsSVGTSpanFrame::AttributeChanged(int32_
 
   return NS_OK;
 }
 
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods:
 
 gfxMatrix
-nsSVGTSpanFrame::GetCanvasTM(uint32_t aFor)
+nsSVGTSpanFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
-  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) {
     if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
         (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
       return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
     }
   }
   NS_ASSERTION(mParent, "null parent");
-  return static_cast<nsSVGContainerFrame*>(mParent)->GetCanvasTM(aFor);
+  return static_cast<nsSVGContainerFrame*>(mParent)->
+      GetCanvasTM(aFor, aTransformRoot);
 }
 
 //----------------------------------------------------------------------
 // nsISVGGlyphFragmentNode methods:
 
 uint32_t
 nsSVGTSpanFrame::GetNumberOfChars()
 {
--- a/layout/svg/nsSVGTSpanFrame.h
+++ b/layout/svg/nsSVGTSpanFrame.h
@@ -60,17 +60,18 @@ public:
 
 #ifdef DEBUG
   NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE
   {
     return MakeFrameName(NS_LITERAL_STRING("SVGTSpan"), aResult);
   }
 #endif
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
   
   // nsISVGGlyphFragmentNode interface:
   virtual uint32_t GetNumberOfChars() MOZ_OVERRIDE;
   virtual float GetComputedTextLength() MOZ_OVERRIDE;
   virtual float GetSubStringLength(uint32_t charnum, uint32_t fragmentChars) MOZ_OVERRIDE;
   virtual int32_t GetCharNumAtPosition(mozilla::nsISVGPoint *point) MOZ_OVERRIDE;
   NS_IMETHOD_(nsSVGGlyphFrame *) GetFirstGlyphFrame() MOZ_OVERRIDE;
   NS_IMETHOD_(nsSVGGlyphFrame *) GetNextGlyphFrame() MOZ_OVERRIDE;
--- a/layout/svg/nsSVGTextFrame.cpp
+++ b/layout/svg/nsSVGTextFrame.cpp
@@ -198,26 +198,27 @@ nsSVGTextFrame::NotifySVGChanged(uint32_
     // 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();
   }
 }
 
 NS_IMETHODIMP
 nsSVGTextFrame::PaintSVG(nsRenderingContext* aContext,
-                         const nsIntRect *aDirtyRect)
+                         const nsIntRect *aDirtyRect,
+                         nsIFrame* aTransformRoot)
 {
   NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
                (mState & NS_FRAME_IS_NONDISPLAY),
                "If display lists are enabled, only painting of non-display "
                "SVG should take this code path");
 
   UpdateGlyphPositioning(true);
   
-  return nsSVGTextFrameBase::PaintSVG(aContext, aDirtyRect);
+  return nsSVGTextFrameBase::PaintSVG(aContext, aDirtyRect, aTransformRoot);
 }
 
 NS_IMETHODIMP_(nsIFrame*)
 nsSVGTextFrame::GetFrameForPoint(const nsPoint &aPoint)
 {
   NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
                (mState & NS_FRAME_IS_NONDISPLAY),
                "If display lists are enabled, only hit-testing of non-display "
@@ -263,33 +264,33 @@ nsSVGTextFrame::GetBBoxContribution(cons
 
   return nsSVGTextFrameBase::GetBBoxContribution(aToBBoxUserspace, aFlags);
 }
 
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods:
 
 gfxMatrix
-nsSVGTextFrame::GetCanvasTM(uint32_t aFor)
+nsSVGTextFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
-  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) {
     if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
         (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
       return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
     }
   }
-
   if (!mCanvasTM) {
     NS_ASSERTION(mParent, "null parent");
 
     nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
     dom::SVGGraphicsElement *content = static_cast<dom::SVGGraphicsElement*>(mContent);
 
-    gfxMatrix tm =
-      content->PrependLocalTransformsTo(parent->GetCanvasTM(aFor));
+    gfxMatrix tm = content->PrependLocalTransformsTo(
+        this == aTransformRoot ? gfxMatrix() :
+                                 parent->GetCanvasTM(aFor, aTransformRoot));
 
     mCanvasTM = new gfxMatrix(tm);
   }
 
   return *mCanvasTM;
 }
 
 //----------------------------------------------------------------------
--- a/layout/svg/nsSVGTextFrame.h
+++ b/layout/svg/nsSVGTextFrame.h
@@ -63,24 +63,26 @@ public:
                                 const nsRect&           aDirtyRect,
                                 const nsDisplayListSet& aLists) MOZ_OVERRIDE;
 
   // nsISVGChildFrame interface:
   virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE;
   // Override these four to ensure that UpdateGlyphPositioning is called
   // to bring glyph positions up to date
   NS_IMETHOD PaintSVG(nsRenderingContext* aContext,
-                      const nsIntRect *aDirtyRect) MOZ_OVERRIDE;
+                      const nsIntRect *aDirtyRect,
+                      nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint & aPoint) MOZ_OVERRIDE;
   virtual void ReflowSVG() MOZ_OVERRIDE;
   virtual SVGBBox GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
                                       uint32_t aFlags) MOZ_OVERRIDE;
   
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
   
   // nsSVGTextContainerFrame methods:
   virtual uint32_t GetNumberOfChars() MOZ_OVERRIDE;
   virtual float GetComputedTextLength() MOZ_OVERRIDE;
   virtual float GetSubStringLength(uint32_t charnum, uint32_t nchars) MOZ_OVERRIDE;
   virtual int32_t GetCharNumAtPosition(mozilla::nsISVGPoint *point) MOZ_OVERRIDE;
 
   NS_IMETHOD GetStartPositionOfChar(uint32_t charnum, nsISupports **_retval) MOZ_OVERRIDE;
--- a/layout/svg/nsSVGTextFrame2.cpp
+++ b/layout/svg/nsSVGTextFrame2.cpp
@@ -3432,17 +3432,18 @@ ShouldPaintCaret(const TextRenderedRun& 
     return true;
   }
 
   return false;
 }
 
 NS_IMETHODIMP
 nsSVGTextFrame2::PaintSVG(nsRenderingContext* aContext,
-                          const nsIntRect *aDirtyRect)
+                          const nsIntRect *aDirtyRect,
+                          nsIFrame* aTransformRoot)
 {
   nsIFrame* kid = GetFirstPrincipalChild();
   if (!kid)
     return NS_OK;
 
   nsPresContext* presContext = PresContext();
 
   gfxContext *gfx = aContext->ThebesContext();
@@ -3462,17 +3463,17 @@ nsSVGTextFrame2::PaintSVG(nsRenderingCon
     UpdateGlyphPositioning();
   } else if (NS_SUBTREE_DIRTY(this)) {
     // If we are asked to paint before reflow has recomputed mPositions etc.
     // directly via PaintSVG, rather than via a display list, then we need
     // to bail out here too.
     return NS_OK;
   }
 
-  gfxMatrix canvasTM = GetCanvasTM(FOR_PAINTING);
+  gfxMatrix canvasTM = GetCanvasTM(FOR_PAINTING, aTransformRoot);
   if (canvasTM.IsSingular()) {
     NS_WARNING("Can't render text element!");
     return NS_ERROR_FAILURE;
   }
 
   gfxMatrix matrixForPaintServers(canvasTM);
   matrixForPaintServers.Multiply(initialMatrix);
 
@@ -3726,35 +3727,38 @@ nsSVGTextFrame2::GetBBoxContribution(con
 
   return bbox;
 }
 
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods
 
 gfxMatrix
-nsSVGTextFrame2::GetCanvasTM(uint32_t aFor)
+nsSVGTextFrame2::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
 {
-  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+  if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) &&
+      !aTransformRoot) {
     if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
         (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
       return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
     }
   }
   if (!mCanvasTM) {
     NS_ASSERTION(mParent, "null parent");
     NS_ASSERTION(!(aFor == FOR_OUTERSVG_TM &&
                    (GetStateBits() & NS_FRAME_IS_NONDISPLAY)),
                  "should not call GetCanvasTM(FOR_OUTERSVG_TM) when we are "
                  "non-display");
 
     nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
     dom::SVGTextContentElement *content = static_cast<dom::SVGTextContentElement*>(mContent);
 
-    gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM(aFor));
+    gfxMatrix tm = content->PrependLocalTransformsTo(
+        this == aTransformRoot ? gfxMatrix() :
+                                 parent->GetCanvasTM(aFor, aTransformRoot));
 
     mCanvasTM = new gfxMatrix(tm);
   }
   return *mCanvasTM;
 }
 
 //----------------------------------------------------------------------
 // nsSVGTextFrame2 SVG DOM methods
--- a/layout/svg/nsSVGTextFrame2.h
+++ b/layout/svg/nsSVGTextFrame2.h
@@ -240,25 +240,27 @@ public:
   virtual void FindCloserFrameForSelection(nsPoint aPoint,
                                           FrameWithDistance* aCurrentBestFrame) MOZ_OVERRIDE;
 
 
 
   // nsISVGChildFrame interface:
   virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE;
   NS_IMETHOD PaintSVG(nsRenderingContext* aContext,
-                      const nsIntRect* aDirtyRect) MOZ_OVERRIDE;
+                      const nsIntRect* aDirtyRect,
+                      nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint& aPoint) MOZ_OVERRIDE;
   virtual void ReflowSVG() MOZ_OVERRIDE;
   NS_IMETHOD_(nsRect) GetCoveredRegion() MOZ_OVERRIDE;
   virtual SVGBBox GetBBoxContribution(const gfxMatrix& aToBBoxUserspace,
                                       uint32_t aFlags) MOZ_OVERRIDE;
 
   // nsSVGContainerFrame methods:
-  virtual gfxMatrix GetCanvasTM(uint32_t aFor) MOZ_OVERRIDE;
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor,
+                                nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE;
   
   // SVG DOM text methods:
   uint32_t GetNumberOfChars(nsIContent* aContent);
   float GetComputedTextLength(nsIContent* aContent);
   nsresult SelectSubString(nsIContent* aContent, uint32_t charnum, uint32_t nchars);
   nsresult GetSubStringLength(nsIContent* aContent, uint32_t charnum,
                               uint32_t nchars, float* aResult);
   int32_t GetCharNumAtPosition(nsIContent* aContent, mozilla::nsISVGPoint* point);
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -625,47 +625,51 @@ nsSVGUtils::GetOuterSVGFrameAndCoveredRe
   if (!svg)
     return nullptr;
   *aRect = (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ?
              nsRect(0, 0, 0, 0) : svg->GetCoveredRegion();
   return GetOuterSVGFrame(aFrame);
 }
 
 gfxMatrix
-nsSVGUtils::GetCanvasTM(nsIFrame *aFrame, uint32_t aFor)
+nsSVGUtils::GetCanvasTM(nsIFrame *aFrame, uint32_t aFor,
+                        nsIFrame* aTransformRoot)
 {
   // XXX yuck, we really need a common interface for GetCanvasTM
 
   if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
     return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
   }
 
-  if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+  if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) &&
+      !aTransformRoot) {
     if ((aFor == nsISVGChildFrame::FOR_PAINTING &&
          NS_SVGDisplayListPaintingEnabled()) ||
         (aFor == nsISVGChildFrame::FOR_HIT_TESTING &&
          NS_SVGDisplayListHitTestingEnabled())) {
       return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
     }
   }
 
   nsIAtom* type = aFrame->GetType();
   if (type == nsGkAtoms::svgForeignObjectFrame) {
-    return static_cast<nsSVGForeignObjectFrame*>(aFrame)->GetCanvasTM(aFor);
+    return static_cast<nsSVGForeignObjectFrame*>(aFrame)->
+        GetCanvasTM(aFor, aTransformRoot);
   }
   if (type == nsGkAtoms::svgOuterSVGFrame) {
     return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
   }
 
   nsSVGContainerFrame *containerFrame = do_QueryFrame(aFrame);
   if (containerFrame) {
-    return containerFrame->GetCanvasTM(aFor);
+    return containerFrame->GetCanvasTM(aFor, aTransformRoot);
   }
 
-  return static_cast<nsSVGGeometryFrame*>(aFrame)->GetCanvasTM(aFor);
+  return static_cast<nsSVGGeometryFrame*>(aFrame)->
+      GetCanvasTM(aFor, aTransformRoot);
 }
 
 gfxMatrix
 nsSVGUtils::GetUserToCanvasTM(nsIFrame *aFrame, uint32_t aFor)
 {
   NS_ASSERTION(aFor == nsISVGChildFrame::FOR_OUTERSVG_TM,
                "Unexpected aFor?");
 
@@ -705,48 +709,49 @@ nsSVGUtils::NotifyChildrenOfSVGChange(ns
 }
 
 // ************************************************************
 
 class SVGPaintCallback : public nsSVGFilterPaintCallback
 {
 public:
   virtual void Paint(nsRenderingContext *aContext, nsIFrame *aTarget,
-                     const nsIntRect* aDirtyRect)
+                     const nsIntRect* aDirtyRect, nsIFrame* aTransformRoot)
   {
     nsISVGChildFrame *svgChildFrame = do_QueryFrame(aTarget);
     NS_ASSERTION(svgChildFrame, "Expected SVG frame here");
 
     nsIntRect* dirtyRect = nullptr;
     nsIntRect tmpDirtyRect;
 
     // aDirtyRect is in user-space pixels, we need to convert to
     // outer-SVG-frame-relative device pixels.
     if (aDirtyRect) {
       gfxMatrix userToDeviceSpace =
-        nsSVGUtils::GetCanvasTM(aTarget, nsISVGChildFrame::FOR_PAINTING);
+        nsSVGUtils::GetCanvasTM(aTarget, nsISVGChildFrame::FOR_PAINTING, aTransformRoot);
       if (userToDeviceSpace.IsSingular()) {
         return;
       }
       gfxRect dirtyBounds = userToDeviceSpace.TransformBounds(
         gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height));
       dirtyBounds.RoundOut();
       if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) {
         dirtyRect = &tmpDirtyRect;
       }
     }
 
-    svgChildFrame->PaintSVG(aContext, dirtyRect);
+    svgChildFrame->PaintSVG(aContext, dirtyRect, aTransformRoot);
   }
 };
 
 void
 nsSVGUtils::PaintFrameWithEffects(nsRenderingContext *aContext,
                                   const nsIntRect *aDirtyRect,
-                                  nsIFrame *aFrame)
+                                  nsIFrame *aFrame,
+                                  nsIFrame *aTransformRoot)
 {
   NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
                (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ||
                aFrame->PresContext()->IsGlyph(),
                "If display lists are enabled, only painting of non-display "
                "SVG should take this code path");
 
   nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame);
@@ -781,17 +786,17 @@ nsSVGUtils::PaintFrameWithEffects(nsRend
     nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf();
     if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) ||
         aFrame->IsSVGText()) {
       // Unlike containers, leaf frames do not include GetPosition() in
       // GetCanvasTM().
       overflowRect = overflowRect + aFrame->GetPosition();
     }
     int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel();
-    gfxMatrix tm = GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING);
+    gfxMatrix tm = GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING, aTransformRoot);
     if (aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
       gfxMatrix childrenOnlyTM;
       if (static_cast<nsSVGContainerFrame*>(aFrame)->
             HasChildrenOnlyTransform(&childrenOnlyTM)) {
         // Undo the children-only transform:
         tm = childrenOnlyTM.Invert() * tm;
       }
     }
@@ -832,28 +837,28 @@ nsSVGUtils::PaintFrameWithEffects(nsRend
 
   if (!isOK) {
     // Some resource is invalid. We shouldn't paint anything.
     return;
   }
   
   gfxMatrix matrix;
   if (clipPathFrame || maskFrame)
-    matrix = GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING);
+    matrix = GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING, aTransformRoot);
 
   /* Check if we need to do additional operations on this child's
    * rendering, which necessitates rendering into another surface. */
   if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)) {
     complexEffects = true;
     gfx->Save();
     if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
       // aFrame has a valid visual overflow rect, so clip to it before calling
       // PushGroup() to minimize the size of the surfaces we'll composite:
       gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(gfx);
-      gfx->Multiply(GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING));
+      gfx->Multiply(GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING, aTransformRoot));
       nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf();
       if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) ||
           aFrame->IsSVGText()) {
         // Unlike containers, leaf frames do not include GetPosition() in
         // GetCanvasTM().
         overflowRect = overflowRect + aFrame->GetPosition();
       }
       aContext->IntersectClip(overflowRect);
@@ -888,19 +893,19 @@ nsSVGUtils::PaintFrameWithEffects(nsRend
                                       aDirtyRect->width, aDirtyRect->height));
       tmpDirtyRect =
         nsLayoutUtils::RoundGfxRectToAppRect(
           dirtyBounds, aFrame->PresContext()->AppUnitsPerCSSPixel()) -
         aFrame->GetPosition();
       dirtyRect = &tmpDirtyRect;
     }
     SVGPaintCallback paintCallback;
-    filterFrame->PaintFilteredFrame(aContext, aFrame, &paintCallback, dirtyRect);
+    filterFrame->PaintFilteredFrame(aContext, aFrame, &paintCallback, dirtyRect, aTransformRoot);
   } else {
-    svgChildFrame->PaintSVG(aContext, aDirtyRect);
+    svgChildFrame->PaintSVG(aContext, aDirtyRect, aTransformRoot);
   }
 
   if (clipPathFrame && isTrivialClip) {
     gfx->Restore();
   }
 
   /* No more effects, we're done. */
   if (!complexEffects)
@@ -1832,17 +1837,18 @@ nsSVGUtils::PaintSVGGlyph(Element* aElem
   nsIFrame* frame = aElement->GetPrimaryFrame();
   nsISVGChildFrame* svgFrame = do_QueryFrame(frame);
   if (!svgFrame) {
     return false;
   }
   nsRenderingContext context;
   context.Init(frame->PresContext()->DeviceContext(), aContext);
   context.AddUserData(&gfxTextObjectPaint::sUserDataKey, aObjectPaint, nullptr);
-  nsresult rv = svgFrame->PaintSVG(&context, nullptr);
+  svgFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED);
+  nsresult rv = svgFrame->PaintSVG(&context, nullptr, frame);
   return NS_SUCCEEDED(rv);
 }
 
 bool
 nsSVGUtils::GetSVGGlyphExtents(Element* aElement,
                                const gfxMatrix& aSVGToAppSpace,
                                gfxRect* aResult)
 {
--- a/layout/svg/nsSVGUtils.h
+++ b/layout/svg/nsSVGUtils.h
@@ -386,34 +386,36 @@ public:
   static nsIFrame*
   GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, nsRect* aRect);
 
   /* Paint SVG frame with SVG effects - aDirtyRect is the area being
    * redrawn, in device pixel coordinates relative to the outer svg */
   static void
   PaintFrameWithEffects(nsRenderingContext *aContext,
                         const nsIntRect *aDirtyRect,
-                        nsIFrame *aFrame);
+                        nsIFrame *aFrame,
+                        nsIFrame* aTransformRoot = nullptr);
 
   /* Hit testing - check if point hits the clipPath of indicated
    * frame.  Returns true if no clipPath set. */
   static bool
   HitTestClip(nsIFrame *aFrame, const nsPoint &aPoint);
   
   /* Hit testing - check if point hits any children of frame. */
 
   static nsIFrame *
   HitTestChildren(nsIFrame *aFrame, const nsPoint &aPoint);
 
   /*
    * Returns the CanvasTM of the indicated frame, whether it's a
    * child SVG frame, container SVG frame, or a regular frame.
    * For regular frames, we just return an identity matrix.
    */
-  static gfxMatrix GetCanvasTM(nsIFrame* aFrame, uint32_t aFor);
+  static gfxMatrix GetCanvasTM(nsIFrame* aFrame, uint32_t aFor,
+                               nsIFrame* aTransformRoot = nullptr);
 
   /**
    * Returns the transform from aFrame's user space to canvas space. Only call
    * with SVG frames. This is like GetCanvasTM, except that it only includes
    * the transforms from aFrame's user space (i.e. the coordinate context
    * established by its 'transform' attribute, or else the coordinate context
    * that its _parent_ establishes for its children) to outer-<svg> device
    * space. Specifically, it does not include any other transforms introduced