Backed out 8 changesets (bug 1224207) for Win8 Reftest failures
authorIris Hsiao <ihsiao@mozilla.com>
Wed, 08 Feb 2017 17:53:07 +0800
changeset 341424 f9aae3308f056124451e99bfbfa3e3c8efd17215
parent 341423 eabcf85ca6408755829890abaf0a57d965686de9
child 341425 5b516731820c841dd85447c2155bd751ec9f7de6
push id86727
push userkwierso@gmail.com
push dateThu, 09 Feb 2017 00:21:26 +0000
treeherdermozilla-inbound@55a4f5189115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1224207
milestone54.0a1
backs out2fce8d53b10569170323e4021b648bca1c2203b1
c31e96bf56d03570f041c4e6dfd424f854deca98
587cde853b75c985b4818120378de19a10d8c3ce
7faefd871e00abc5907b6c0072fd305e4922715d
15f62c3ec366258db66a95ae7937fdcd032bf47d
9448e628569ed5f7bbef82363f3dfa867fafb5ee
958379e4f61cc97f3f81c4a4da31ffbff3c099d2
d23079bc188ee40cc4a8275bed3065e939537add
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
Backed out 8 changesets (bug 1224207) for Win8 Reftest failures Backed out changeset 2fce8d53b105 (bug 1224207) Backed out changeset c31e96bf56d0 (bug 1224207) Backed out changeset 587cde853b75 (bug 1224207) Backed out changeset 7faefd871e00 (bug 1224207) Backed out changeset 15f62c3ec366 (bug 1224207) Backed out changeset 9448e628569e (bug 1224207) Backed out changeset 958379e4f61c (bug 1224207) Backed out changeset d23079bc188e (bug 1224207)
layout/reftests/svg/filters/css-filters/reftest.list
layout/reftests/svg/filters/css-filters/scale-filtered-content-01-ref.html
layout/reftests/svg/filters/css-filters/scale-filtered-content-01.html
layout/svg/SVGGeometryFrame.cpp
layout/svg/SVGTextFrame.cpp
layout/svg/nsFilterInstance.cpp
layout/svg/nsFilterInstance.h
layout/svg/nsSVGIntegrationUtils.cpp
layout/svg/nsSVGIntegrationUtils.h
layout/svg/nsSVGOuterSVGFrame.cpp
layout/svg/nsSVGUtils.cpp
layout/svg/nsSVGUtils.h
--- a/layout/reftests/svg/filters/css-filters/reftest.list
+++ b/layout/reftests/svg/filters/css-filters/reftest.list
@@ -61,11 +61,8 @@ fuzzy-if(d2d,1,10000) == saturate-desatu
 == saturate-one.html saturate-one-ref.html
 == saturate-percent.html saturate-percent-ref.html
 fuzzy-if(d2d,1,10000) == saturate-zero.html saturate-zero-ref.html
 fuzzy-if(d2d,1,10000) == sepia.html sepia-ref.html
 fuzzy-if(d2d,1,10000) == sepia-one.html sepia-one-ref.html
 fuzzy-if(d2d,1,10000) == sepia-over-one.html sepia-over-one-ref.html
 fuzzy-if(d2d,1,10000) == sepia-percent.html sepia-percent-ref.html
 == sepia-zero.html sepia-zero-ref.html
-
-fuzzy(2,127572) == scale-filtered-content-01.html scale-filtered-content-01-ref.html
-
deleted file mode 100644
--- a/layout/reftests/svg/filters/css-filters/scale-filtered-content-01-ref.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
-     Any copyright is dedicated to the Public Domain.
-     http://creativecommons.org/publicdomain/zero/1.0/
--->
-<!DOCTYPE html>
-<html>
-<head>
-  <title>CSS Filters: Filtered content should be rendered in device space</title>
-  <style type="text/css">
-    #filtered {
-      width: 100px;
-      height: 50px;
-      background-color: lime;
-      opacity: 0.5;
-      transform: translate(10px) scale(5);
-      transform-origin: 0 0;
-    }
-  </style>
-</head>
-<body>
-  <p>You should see a clear tanslucent string.</p>
-  <div id="filtered">The text shoud not be blur.</div>
-</body>
deleted file mode 100644
--- a/layout/reftests/svg/filters/css-filters/scale-filtered-content-01.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
-     Any copyright is dedicated to the Public Domain.
-     http://creativecommons.org/publicdomain/zero/1.0/
--->
-<!DOCTYPE html>
-<html>
-<head>
-  <title>CSS Filters: Filtered content should be rendered in device space</title>
-  <style type="text/css">
-    #filtered {
-      width: 100px;
-      height: 50px;
-      background-color: lime;
-      filter: opacity(50%);
-      transform: translate(10px) scale(5);
-      transform-origin: 0 0;
-    }
-  </style>
-</head>
-<body>
-  <p>You should see a clear tanslucent string.</p>
-  <div id="filtered">The text shoud not be blur.</div>
-</body>
--- a/layout/svg/SVGGeometryFrame.cpp
+++ b/layout/svg/SVGGeometryFrame.cpp
@@ -114,17 +114,17 @@ nsDisplaySVGGeometry::Paint(nsDisplayLis
   // ToReferenceFrame includes our mRect offset, but painting takes
   // account of that too. To avoid double counting, we subtract that
   // here.
   nsPoint offset = ToReferenceFrame() - mFrame->GetPosition();
 
   gfxPoint devPixelOffset =
     nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
 
-  gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
+  gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) *
                    gfxMatrix::Translation(devPixelOffset);
   DrawResult result =
     static_cast<SVGGeometryFrame*>(mFrame)->PaintSVG(*aCtx->ThebesContext(), tm);
 
   nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
 }
 
 void
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -3110,17 +3110,17 @@ nsDisplaySVGText::Paint(nsDisplayListBui
   // ToReferenceFrame includes our mRect offset, but painting takes
   // account of that too. To avoid double counting, we subtract that
   // here.
   nsPoint offset = ToReferenceFrame() - mFrame->GetPosition();
 
   gfxPoint devPixelOffset =
     nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
 
-  gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
+  gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) *
                    gfxMatrix::Translation(devPixelOffset);
 
   gfxContext* ctx = aCtx->ThebesContext();
   ctx->Save();
   DrawResult result = static_cast<SVGTextFrame*>(mFrame)->PaintSVG(*ctx, tm);
   nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
   ctx->Restore();
 }
--- a/layout/svg/nsFilterInstance.cpp
+++ b/layout/svg/nsFilterInstance.cpp
@@ -34,20 +34,20 @@ using namespace mozilla::image;
 FilterDescription
 nsFilterInstance::GetFilterDescription(nsIContent* aFilteredElement,
                                        const nsTArray<nsStyleFilter>& aFilterChain,
                                        bool aFilterInputIsTainted,
                                        const UserSpaceMetrics& aMetrics,
                                        const gfxRect& aBBox,
                                        nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages)
 {
-  gfxMatrix identity;
+  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
   nsFilterInstance instance(nullptr, aFilteredElement, aMetrics,
                             aFilterChain, aFilterInputIsTainted, nullptr,
-                            identity, nullptr, nullptr, nullptr, &aBBox);
+                            unused, nullptr, nullptr, nullptr, &aBBox);
   if (!instance.IsInitialized()) {
     return FilterDescription();
   }
   return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages);
 }
 
 static UniquePtr<UserSpaceMetrics>
 UserSpaceMetricsForFrame(nsIFrame* aFrame)
@@ -65,65 +65,64 @@ nsFilterInstance::PaintFilteredFrame(nsI
                                      const gfxMatrix& aTransform,
                                      nsSVGFilterPaintCallback *aPaintCallback,
                                      const nsRegion *aDirtyArea)
 {
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
-                            *metrics, filterChain, /* InputIsTainted */ true,
-                            aPaintCallback, aTransform, aDirtyArea, nullptr,
-                            nullptr, nullptr);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
+                            filterChain, /* InputIsTainted */ true, aPaintCallback,
+                            aTransform, aDirtyArea, nullptr, nullptr, nullptr);
   if (!instance.IsInitialized()) {
     return DrawResult::BAD_IMAGE;
   }
 
   return instance.Render(aDrawTarget);
 }
 
 nsRegion
 nsFilterInstance::GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
                                          const nsRegion& aPreFilterDirtyRegion)
 {
   if (aPreFilterDirtyRegion.IsEmpty()) {
     return nsRegion();
   }
 
-  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
+  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
-                            *metrics, filterChain, /* InputIsTainted */ true,
-                            nullptr, tm, nullptr, &aPreFilterDirtyRegion);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
+                            filterChain, /* InputIsTainted */ true, nullptr, unused,
+                            nullptr, &aPreFilterDirtyRegion);
   if (!instance.IsInitialized()) {
     return nsRegion();
   }
 
   // We've passed in the source's dirty area so the instance knows about it.
   // Now we can ask the instance to compute the area of the filter output
   // that's dirty.
   return instance.ComputePostFilterDirtyRegion();
 }
 
 nsRegion
 nsFilterInstance::GetPreFilterNeededArea(nsIFrame *aFilteredFrame,
                                          const nsRegion& aPostFilterDirtyRegion)
 {
-  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
+  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
-                            *metrics, filterChain, /* InputIsTainted */ true,
-                            nullptr, tm, &aPostFilterDirtyRegion);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
+                            filterChain, /* InputIsTainted */ true, nullptr, unused,
+                            &aPostFilterDirtyRegion);
   if (!instance.IsInitialized()) {
     return nsRect();
   }
 
   // Now we can ask the instance to compute the area of the source
   // that's needed.
   return instance.ComputeSourceNeededRect();
 }
@@ -139,25 +138,25 @@ nsFilterInstance::GetPostFilterBounds(ns
 
   nsRegion preFilterRegion;
   nsRegion* preFilterRegionPtr = nullptr;
   if (aPreFilterBounds) {
     preFilterRegion = *aPreFilterBounds;
     preFilterRegionPtr = &preFilterRegion;
   }
 
-  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
+  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
-                            *metrics, filterChain, /* InputIsTainted */ true,
-                            nullptr, tm, nullptr, preFilterRegionPtr,
-                            aPreFilterBounds, aOverrideBBox);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
+                            filterChain, /* InputIsTainted */ true, nullptr, unused,
+                            nullptr, preFilterRegionPtr, aPreFilterBounds,
+                            aOverrideBBox);
   if (!instance.IsInitialized()) {
     return nsRect();
   }
 
   return instance.ComputePostFilterExtents();
 }
 
 nsFilterInstance::nsFilterInstance(nsIFrame *aTargetFrame,
@@ -199,16 +198,21 @@ nsFilterInstance::nsFilterInstance(nsIFr
   }
 
   // Get various transforms:
 
   gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.width, 0.0f,
                               0.0f, mFilterSpaceToUserSpaceScale.height,
                               0.0f, 0.0f);
 
+  // Only used (so only set) when we paint:
+  if (mPaintCallback) {
+    mFilterSpaceToDeviceSpaceTransform = filterToUserSpace * mPaintTransform;
+  }
+
   mFilterSpaceToFrameSpaceInCSSPxTransform =
     filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
   // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
   mFrameSpaceInCSSPxToFilterSpaceTransform =
     mFilterSpaceToFrameSpaceInCSSPxTransform;
   mFrameSpaceInCSSPxToFilterSpaceTransform.Invert();
 
   nsIntRect targetBounds;
@@ -234,29 +238,32 @@ nsFilterInstance::nsFilterInstance(nsIFr
   mInitialized = true;
 }
 
 nsresult
 nsFilterInstance::ComputeUserSpaceToFilterSpaceScale()
 {
   gfxMatrix canvasTransform;
   if (mTargetFrame) {
-    mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors(true);
-    if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
-        mUserSpaceToFilterSpaceScale.height <= 0.0f) {
+    canvasTransform = nsSVGUtils::GetCanvasTM(mTargetFrame);
+    if (canvasTransform.IsSingular()) {
       // Nothing should be rendered.
       return NS_ERROR_FAILURE;
     }
-  } else {
-    mUserSpaceToFilterSpaceScale = gfxSize(1.0, 1.0);
   }
 
-  mFilterSpaceToUserSpaceScale =
-    gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
-            1.0f / mUserSpaceToFilterSpaceScale.height);
+  mUserSpaceToFilterSpaceScale = canvasTransform.ScaleFactors(true);
+  if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
+      mUserSpaceToFilterSpaceScale.height <= 0.0f) {
+    // Nothing should be rendered.
+    return NS_ERROR_FAILURE;
+  }
+
+  mFilterSpaceToUserSpaceScale = gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
+                                         1.0f / mUserSpaceToFilterSpaceScale.height);
   return NS_OK;
 }
 
 gfxRect
 nsFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const
 {
   gfxRect filterSpaceRect = aUserSpaceRect;
   filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width,
@@ -350,74 +357,85 @@ nsFilterInstance::ComputeNeededBoxes()
   sourceGraphicNeededRegion.And(sourceGraphicNeededRegion, mTargetBounds);
 
   mSourceGraphic.mNeededBounds = sourceGraphicNeededRegion.GetBounds();
   mFillPaint.mNeededBounds = fillPaintNeededRegion.GetBounds();
   mStrokePaint.mNeededBounds = strokePaintNeededRegion.GetBounds();
 }
 
 DrawResult
-nsFilterInstance::BuildSourcePaint(SourceInfo *aSource)
+nsFilterInstance::BuildSourcePaint(SourceInfo *aSource,
+                                   DrawTarget* aTargetDT)
 {
   MOZ_ASSERT(mTargetFrame);
   nsIntRect neededRect = aSource->mNeededBounds;
 
   RefPtr<DrawTarget> offscreenDT =
     gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
       neededRect.Size(), SurfaceFormat::B8G8R8A8);
   if (!offscreenDT || !offscreenDT->IsValid()) {
     return DrawResult::TEMPORARY_ERROR;
   }
 
-  RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
-  MOZ_ASSERT(ctx); // already checked the draw target above
-  gfxContextAutoSaveRestore saver(ctx);
+  gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform();
+  DebugOnly<bool> invertible = deviceToFilterSpace.Invert();
+  MOZ_ASSERT(invertible,
+             "The returning matix of GetFilterSpaceToDeviceSpaceTransform must"
+             "be an invertible matrix(not a singular one), since we already"
+             "checked it and early return if it's not from the caller side"
+             "(nsFilterInstance::Render)");
 
-  ctx->SetMatrix(mPaintTransform *
-                 gfxMatrix::Translation(-neededRect.TopLeft()));
-  GeneralPattern pattern;
-  if (aSource == &mFillPaint) {
-    nsSVGUtils::MakeFillPatternFor(mTargetFrame, ctx, &pattern);
-  } else if (aSource == &mStrokePaint) {
-    nsSVGUtils::MakeStrokePatternFor(mTargetFrame, ctx, &pattern);
-  }
-
-  if (pattern.GetPattern()) {
-    offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))),
-                          pattern);
+  if (!mPaintTransform.IsSingular()) {
+    RefPtr<gfxContext> gfx = gfxContext::CreateOrNull(offscreenDT);
+    MOZ_ASSERT(gfx); // already checked the draw target above
+    gfx->Save();
+    gfx->Multiply(mPaintTransform *
+                  deviceToFilterSpace *
+                  gfxMatrix::Translation(-neededRect.TopLeft()));
+    GeneralPattern pattern;
+    if (aSource == &mFillPaint) {
+      nsSVGUtils::MakeFillPatternFor(mTargetFrame, gfx, &pattern);
+    } else if (aSource == &mStrokePaint) {
+      nsSVGUtils::MakeStrokePatternFor(mTargetFrame, gfx, &pattern);
+    }
+    if (pattern.GetPattern()) {
+      offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))),
+                            pattern);
+    }
+    gfx->Restore();
   }
 
   aSource->mSourceSurface = offscreenDT->Snapshot();
   aSource->mSurfaceRect = neededRect;
 
   return DrawResult::SUCCESS;
 }
 
 DrawResult
-nsFilterInstance::BuildSourcePaints()
+nsFilterInstance::BuildSourcePaints(DrawTarget* aTargetDT)
 {
   if (!mFillPaint.mNeededBounds.IsEmpty()) {
-    DrawResult result = BuildSourcePaint(&mFillPaint);
+    DrawResult result = BuildSourcePaint(&mFillPaint, aTargetDT);
     if (result != DrawResult::SUCCESS) {
       return result;
     }
   }
 
   if (!mStrokePaint.mNeededBounds.IsEmpty()) {
-    DrawResult result = BuildSourcePaint(&mStrokePaint);
+    DrawResult result = BuildSourcePaint(&mStrokePaint, aTargetDT);
     if (result != DrawResult::SUCCESS) {
       return result;
     }
   }
 
   return  DrawResult::SUCCESS;
 }
 
 DrawResult
-nsFilterInstance::BuildSourceImage()
+nsFilterInstance::BuildSourceImage(DrawTarget* aTargetDT)
 {
   MOZ_ASSERT(mTargetFrame);
 
   nsIntRect neededRect = mSourceGraphic.mNeededBounds;
   if (neededRect.IsEmpty()) {
     return DrawResult::SUCCESS;
   }
 
@@ -441,23 +459,29 @@ nsFilterInstance::BuildSourceImage()
   //
   // (In theory it would be better to minimize error by having filtered SVG
   // 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();
+  DebugOnly<bool> invertible = deviceToFilterSpace.Invert();
+  MOZ_ASSERT(invertible,
+             "The returning matix of GetFilterSpaceToDeviceSpaceTransform must"
+             "be an invertible matrix(not a singular one), since we already"
+             "checked it and early return if it's not from the caller side"
+             "(nsFilterInstance::Render)");
+
   RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
   MOZ_ASSERT(ctx); // already checked the draw target above
-  gfxMatrix devPxToCssPxTM = nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
-  DebugOnly<bool> invertible = devPxToCssPxTM.Invert();
-  MOZ_ASSERT(invertible);
-  ctx->SetMatrix(devPxToCssPxTM * mPaintTransform *
-                 gfxMatrix::Translation(-neededRect.TopLeft()));
+  ctx->SetMatrix(
+    ctx->CurrentMatrix().Translate(-neededRect.TopLeft()).
+                         PreMultiply(deviceToFilterSpace));
 
   DrawResult result =
     mPaintCallback->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty);
 
   mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
   mSourceGraphic.mSurfaceRect = neededRect;
 
   return result;
@@ -470,37 +494,33 @@ nsFilterInstance::Render(DrawTarget* aDr
 
   if (mPrimitiveDescriptions.IsEmpty()) {
     // An filter without any primitive. Treat it as success and paint nothing.
     return DrawResult::SUCCESS;
   }
 
   nsIntRect filterRect =
     mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
-  if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
+  gfxMatrix ctm = GetFilterSpaceToDeviceSpaceTransform();
+
+  if (filterRect.IsEmpty() || ctm.IsSingular()) {
     return DrawResult::SUCCESS;
   }
 
   AutoRestoreTransform autoRestoreTransform(aDrawTarget);
-  gfxMatrix filterSpaceToUserSpace = mPaintTransform;
-  DebugOnly<bool> invertible = filterSpaceToUserSpace.Invert();
-  MOZ_ASSERT(invertible);
-  filterSpaceToUserSpace *= nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
-
-  aDrawTarget->SetTransform(ToMatrix(filterSpaceToUserSpace) *
-                            aDrawTarget->GetTransform() *
-                            Matrix::Translation(filterRect.TopLeft()));
+  Matrix newTM = ToMatrix(ctm).PreTranslate(filterRect.x, filterRect.y) *
+                 aDrawTarget->GetTransform();
+  aDrawTarget->SetTransform(newTM);
 
   ComputeNeededBoxes();
-
-  DrawResult result = BuildSourceImage();
+  DrawResult result = BuildSourceImage(aDrawTarget);
   if (result != DrawResult::SUCCESS){
     return result;
   }
-  result = BuildSourcePaints();
+  result = BuildSourcePaints(aDrawTarget);
   if (result != DrawResult::SUCCESS){
     return result;
   }
 
   FilterSupport::RenderFilterDescription(
     aDrawTarget, mFilterDescription, IntRectToRect(filterRect),
     mSourceGraphic.mSourceSurface, mSourceGraphic.mSurfaceRect,
     mFillPaint.mSourceSurface, mFillPaint.mSurfaceRect,
--- a/layout/svg/nsFilterInstance.h
+++ b/layout/svg/nsFilterInstance.h
@@ -114,17 +114,16 @@ public:
    *   as aFilteredFrame's bbox ('bbox' is a specific SVG term), if non-null.
    * @param aPreFilterBounds The pre-filter visual overflow rect of
    *   aFilteredFrame, if non-null.
    */
   static nsRect GetPostFilterBounds(nsIFrame *aFilteredFrame,
                                     const gfxRect *aOverrideBBox = nullptr,
                                     const nsRect *aPreFilterBounds = nullptr);
 
-private:
   /**
    * @param aTargetFrame The frame of the filtered element under consideration,
    *   may be null.
    * @param aTargetContent The filtered element itself.
    * @param aMetrics The metrics to resolve SVG lengths against.
    * @param aFilterChain The list of filters to apply.
    * @param aFilterInputIsTainted Describes whether the SourceImage / SourceAlpha
    *   input is tainted. This affects whether feDisplacementMap will respect
@@ -197,16 +196,25 @@ private:
    * Sets the aDirty outparam to the pre-filter bounds in frame space of the
    * area of mTargetFrame that is needed in order to paint the filtered output
    * for a given post-filter dirtied area. The post-filter area must have been
    * specified before calling this method by passing it as the aPostFilterDirtyRegion
    * argument to the nsFilterInstance constructor.
    */
   nsRect ComputeSourceNeededRect();
 
+
+  /**
+   * Returns the transform from filter space to outer-<svg> device space.
+   */
+  gfxMatrix GetFilterSpaceToDeviceSpaceTransform() const {
+    return mFilterSpaceToDeviceSpaceTransform;
+  }
+
+private:
   struct SourceInfo {
     // Specifies which parts of the source need to be rendered.
     // Set by ComputeNeededBoxes().
     nsIntRect mNeededBounds;
 
     // The surface that contains the input rendering.
     // Set by BuildSourceImage / BuildSourcePaint.
     RefPtr<SourceSurface> mSourceSurface;
@@ -215,30 +223,31 @@ private:
     // Set by BuildSourceImage / BuildSourcePaint.
     IntRect mSurfaceRect;
   };
 
   /**
    * Creates a SourceSurface for either the FillPaint or StrokePaint graph
    * nodes
    */
-  DrawResult BuildSourcePaint(SourceInfo *aPrimitive);
+  DrawResult BuildSourcePaint(SourceInfo *aPrimitive,
+                            DrawTarget* aTargetDT);
 
   /**
    * Creates a SourceSurface for either the FillPaint and StrokePaint graph
    * nodes, fills its contents and assigns it to mFillPaint.mSourceSurface and
    * mStrokePaint.mSourceSurface respectively.
    */
-  DrawResult BuildSourcePaints();
+  DrawResult BuildSourcePaints(DrawTarget* aTargetDT);
 
   /**
    * Creates the SourceSurface for the SourceGraphic graph node, paints its
    * contents, and assigns it to mSourceGraphic.mSourceSurface.
    */
-  DrawResult BuildSourceImage();
+  DrawResult BuildSourceImage(DrawTarget* aTargetDT);
 
   /**
    * Build the list of FilterPrimitiveDescriptions that describes the filter's
    * filter primitives and their connections. This populates
    * mPrimitiveDescriptions and mInputImages. aFilterInputIsTainted describes
    * whether the SourceGraphic is tainted.
    */
   nsresult BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain,
@@ -326,16 +335,21 @@ private:
   gfxRect mTargetBBox;
 
   /**
    * The SVG bbox of the element that is being filtered, in filter space.
    */
   nsIntRect mTargetBBoxInFilterSpace;
 
   /**
+   * The transform from filter space to outer-<svg> device space.
+   */
+  gfxMatrix mFilterSpaceToDeviceSpaceTransform;
+
+  /**
    * Transform rects between filter space and frame space in CSS pixels.
    */
   gfxMatrix mFilterSpaceToFrameSpaceInCSSPxTransform;
   gfxMatrix mFrameSpaceInCSSPxToFilterSpaceTransform;
 
   /**
    * The scale factors between user space and filter space.
    */
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -398,40 +398,42 @@ nsSVGIntegrationUtils::HitTestFrameForEf
   return nsSVGUtils::HitTestClip(firstFrame, userSpacePt);
 }
 
 class RegularFramePaintCallback : public nsSVGFilterPaintCallback
 {
 public:
   RegularFramePaintCallback(nsDisplayListBuilder* aBuilder,
                             LayerManager* aManager,
-                            const gfxPoint& aUserSpaceToFrameSpaceOffset)
+                            const nsPoint& aOffset)
     : mBuilder(aBuilder), mLayerManager(aManager),
-      mUserSpaceToFrameSpaceOffset(aUserSpaceToFrameSpaceOffset) {}
+      mOffset(aOffset) {}
 
   virtual DrawResult Paint(gfxContext& aContext, nsIFrame *aTarget,
                            const gfxMatrix& aTransform,
                            const nsIntRect* aDirtyRect) override
   {
     BasicLayerManager* basic = mLayerManager->AsBasicLayerManager();
-    RefPtr<gfxContext> oldCtx = basic->GetTarget();
     basic->SetTarget(&aContext);
 
+    gfxPoint devPixelOffset =
+      nsLayoutUtils::PointToGfxPoint(-mOffset,
+                                     aTarget->PresContext()->AppUnitsPerDevPixel());
+
     gfxContextMatrixAutoSaveRestore autoSR(&aContext);
-    aContext.SetMatrix(aContext.CurrentMatrix().Translate(-mUserSpaceToFrameSpaceOffset));
+    aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffset));
 
     mLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, mBuilder);
-    basic->SetTarget(oldCtx);
     return DrawResult::SUCCESS;
   }
 
 private:
   nsDisplayListBuilder* mBuilder;
   LayerManager* mLayerManager;
-  gfxPoint mUserSpaceToFrameSpaceOffset;
+  nsPoint mOffset;
 };
 
 typedef nsSVGIntegrationUtils::PaintFramesParams PaintFramesParams;
 
 /**
  * Paint css-positioned-mask onto a given target(aMaskDT).
  */
 static DrawResult
@@ -442,17 +444,17 @@ PaintMaskSurface(const PaintFramesParams
                  const nsPoint& aOffsetToUserSpace)
 {
   MOZ_ASSERT(aMaskFrames.Length() > 0);
   MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8);
   MOZ_ASSERT(aOpacity == 1.0 || aMaskFrames.Length() == 1);
 
   const nsStyleSVGReset *svgReset = aSC->StyleSVGReset();
   gfxMatrix cssPxToDevPxMatrix =
-    nsSVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
+    nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
 
   nsPresContext* presContext = aParams.frame->PresContext();
   gfxPoint devPixelOffsetToUserSpace =
     nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace,
                                    presContext->AppUnitsPerDevPixel());
 
   RefPtr<gfxContext> maskContext = gfxContext::CreateOrNull(aMaskDT);
   MOZ_ASSERT(maskContext);
@@ -541,17 +543,17 @@ CreateAndPaintMaskSurface(const PaintFra
   MOZ_ASSERT(aMaskFrames.Length() > 0);
   MaskPaintResult paintResult;
 
   gfxContext& ctx = aParams.ctx;
 
   // Optimization for single SVG mask.
   if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
     gfxMatrix cssPxToDevPxMatrix =
-      nsSVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
+    nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
     paintResult.opacityApplied = true;
     nsSVGMaskFrame::MaskParams params(&ctx, aParams.frame, cssPxToDevPxMatrix,
                                       aOpacity, &paintResult.maskTransform,
                                       svgReset->mMask.mLayers[0].mMaskMode);
     Tie(paintResult.result, paintResult.maskSurface) =
       aMaskFrames[0]->GetMaskForMaskedFrame(params);
 
     if (!paintResult.maskSurface) {
@@ -646,96 +648,71 @@ ValidateSVGFrame(nsIFrame* aFrame)
       // The SVG spec says not to draw _anything_
       return false;
     }
   }
 
   return true;
 }
 
-struct EffectOffsets {
-  // The offset between the reference frame and the bounding box of the
-  // target frame in app unit.
-  nsPoint  offsetToBoundingBox;
-  // The offset between the reference frame and the bounding box of the
-  // target frame in device unit.
-  gfxPoint offsetToBoundingBoxInDevPx;
-  // The offset between the reference frame and the bounding box of the
-  // target frame in app unit.
-  nsPoint  offsetToUserSpace;
-  // The offset between the reference frame and the bounding box of the
-  // target frame in device unit.
-  gfxPoint offsetToUserSpaceInDevPx;
-};
-
-EffectOffsets
-ComputeEffectOffset(nsIFrame* aFrame, const PaintFramesParams& aParams)
+/**
+ * Setup transform matrix of a gfx context by a specific frame. Depend on
+ * aClipCtx, this function may clip that context by the visual overflow area
+ * of aFrame.
+ *
+ * @param aFrame is the target frame.
+ * @param aOffsetToBoundingBox returns the offset between the reference frame
+ *        and the bounding box of aFrame.
+ * @oaram aOffsetToUserSpace returns the offset between the reference frame and
+ *        the user space coordinate of aFrame.
+ */
+static void
+SetupContextMatrix(nsIFrame* aFrame, const PaintFramesParams& aParams,
+                   nsPoint& aOffsetToBoundingBox, nsPoint& aOffsetToUserSpace)
 {
-  EffectOffsets result;
-
-  result.offsetToBoundingBox =
-    aParams.builder->ToReferenceFrame(aFrame) -
-    nsSVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
+  aOffsetToBoundingBox = aParams.builder->ToReferenceFrame(aFrame) -
+                         nsSVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
   if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
     /* Snap the offset if the reference frame is not a SVG frame,
      * since other frames will be snapped to pixel when rendering. */
-    result.offsetToBoundingBox =
-      nsPoint(
-        aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(result.offsetToBoundingBox.x),
-        aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(result.offsetToBoundingBox.y));
+    aOffsetToBoundingBox = nsPoint(
+      aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.x),
+      aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.y));
   }
 
   // After applying only "aOffsetToBoundingBox", aParams.ctx would have its
   // origin at the top left corner of frame's bounding box (over all
   // continuations).
   // However, SVG painting needs the origin to be located at the origin of the
   // SVG frame's "user space", i.e. the space in which, for example, the
   // frame's BBox lives.
   // SVG geometry frames and foreignObject frames apply their own offsets, so
   // their position is relative to their user space. So for these frame types,
   // if we want aParams.ctx to be in user space, we first need to subtract the
   // frame's position so that SVG painting can later add it again and the
   // frame is painted in the right place.
+
   gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
   nsPoint toUserSpace =
     nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
             nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
 
-  result.offsetToUserSpace = result.offsetToBoundingBox - toUserSpace;
+  aOffsetToUserSpace = aOffsetToBoundingBox - toUserSpace;
 
 #ifdef DEBUG
   bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
-  NS_ASSERTION(hasSVGLayout ||
-               result.offsetToBoundingBox == result.offsetToUserSpace,
+  NS_ASSERTION(hasSVGLayout || aOffsetToBoundingBox == aOffsetToUserSpace,
                "For non-SVG frames there shouldn't be any additional offset");
 #endif
 
-  result.offsetToUserSpaceInDevPx =
-    nsLayoutUtils::PointToGfxPoint(result.offsetToUserSpace,
-                                   aFrame->PresContext()->AppUnitsPerDevPixel());
-  result.offsetToBoundingBoxInDevPx =
-    nsLayoutUtils::PointToGfxPoint(result.offsetToBoundingBox,
+  gfxPoint devPixelOffsetToUserSpace =
+    nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace,
                                    aFrame->PresContext()->AppUnitsPerDevPixel());
-
-  return result;
-}
-
-/**
- * Setup transform matrix of a gfx context by a specific frame. Move the
- * origin of aParams.ctx to the user space of aFrame.
- */
-static EffectOffsets
-MoveContextOriginToUserSpace(nsIFrame* aFrame, const PaintFramesParams& aParams)
-{
-  EffectOffsets offset = ComputeEffectOffset(aFrame, aParams);
-
-  aParams.ctx.SetMatrix(
-    aParams.ctx.CurrentMatrix().Translate(offset.offsetToUserSpaceInDevPx));
-
-  return offset;
+  gfxContext& context = aParams.ctx;
+  context.SetMatrix(context.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
 }
 
 bool
 nsSVGIntegrationUtils::IsMaskResourceReady(nsIFrame* aFrame)
 {
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
   nsSVGEffects::EffectProperties effectProperties =
@@ -816,23 +793,26 @@ nsSVGIntegrationUtils::PaintMask(const P
   bool shouldPushOpacity = (maskUsage.opacity != 1.0) &&
                            (maskFrames.Length() != 1);
   if (shouldPushOpacity) {
     ctx.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, maskUsage.opacity);
     autoPop.SetContext(&ctx);
   }
 
   gfxContextMatrixAutoSaveRestore matSR;
+  nsPoint offsetToBoundingBox;
+  nsPoint offsetToUserSpace;
 
   // Paint clip-path-basic-shape onto ctx
   gfxContextAutoSaveRestore basicShapeSR;
   if (maskUsage.shouldApplyBasicShape) {
     matSR.SetContext(&ctx);
 
-    MoveContextOriginToUserSpace(firstFrame, aParams);
+    SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+                       offsetToUserSpace);
 
     basicShapeSR.SetContext(&ctx);
     nsCSSClipPathInstance::ApplyBasicShapeClip(ctx, frame);
     if (!maskUsage.shouldGenerateMaskLayer) {
       // Only have basic-shape clip-path effect. Fill clipped region by
       // opaque white.
       ctx.SetColor(Color(1.0, 1.0, 1.0, 1.0));
       ctx.Fill();
@@ -841,35 +821,36 @@ nsSVGIntegrationUtils::PaintMask(const P
     }
   }
 
   // Paint mask onto ctx.
   if (maskUsage.shouldGenerateMaskLayer) {
     matSR.Restore();
     matSR.SetContext(&ctx);
 
-    EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
+    SetupContextMatrix(frame, aParams, offsetToBoundingBox,
+                       offsetToUserSpace);
     result = PaintMaskSurface(aParams, maskTarget,
                               shouldPushOpacity ?  1.0 : maskUsage.opacity,
                               firstFrame->StyleContext(), maskFrames,
-                              ctx.CurrentMatrix(),
-                              offsets.offsetToUserSpace);
+                              ctx.CurrentMatrix(), offsetToUserSpace);
     if (result != DrawResult::SUCCESS) {
       return result;
     }
   }
 
   // Paint clip-path onto ctx.
   if (maskUsage.shouldGenerateClipMaskLayer || maskUsage.shouldApplyClipPath) {
     matSR.Restore();
     matSR.SetContext(&ctx);
 
-    MoveContextOriginToUserSpace(firstFrame, aParams);
+    SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+                       offsetToUserSpace);
     Matrix clipMaskTransform;
-    gfxMatrix cssPxToDevPxMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
+    gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame);
 
     nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame();
     RefPtr<SourceSurface> maskSurface =
       maskUsage.shouldGenerateMaskLayer ? maskTarget->Snapshot() : nullptr;
     result =
       clipPathFrame->PaintClipMask(ctx, frame, cssPxToDevPxMatrix,
                                    &clipMaskTransform, maskSurface,
                                    ToMatrix(ctx.CurrentMatrix()));
@@ -919,19 +900,22 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
      so make sure all applicable ones are set again. */
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
   nsSVGEffects::EffectProperties effectProperties =
     nsSVGEffects::GetEffectProperties(firstFrame);
 
   nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame();
 
-  gfxMatrix cssPxToDevPxMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
+  gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame);
   nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
 
+  nsPoint offsetToBoundingBox;
+  nsPoint offsetToUserSpace;
+
   bool shouldGenerateMask = (maskUsage.opacity != 1.0f ||
                              maskUsage.shouldGenerateClipMaskLayer ||
                              maskUsage.shouldGenerateMaskLayer);
   bool shouldPushMask = false;
 
   /* Check if we need to do additional operations on this child's
    * rendering, which necessitates rendering into another surface. */
   if (shouldGenerateMask) {
@@ -942,21 +926,22 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
     bool opacityApplied = false;
 
     if (maskUsage.shouldGenerateMaskLayer) {
       matSR.SetContext(&context);
 
       // For css-mask, we want to generate a mask for each continuation frame,
       // so we setup context matrix by the position of the current frame,
       // instead of the first continuation frame.
-      EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
+      SetupContextMatrix(frame, aParams, offsetToBoundingBox,
+                         offsetToUserSpace);
       MaskPaintResult paintResult =
         CreateAndPaintMaskSurface(aParams, maskUsage.opacity,
                                   firstFrame->StyleContext(),
-                                  maskFrames, offsets.offsetToUserSpace);
+                                  maskFrames, offsetToUserSpace);
 
       if (paintResult.transparentBlackMask) {
         return paintResult.result;
       }
 
       result &= paintResult.result;
       maskSurface = paintResult.maskSurface;
       if (maskSurface) {
@@ -966,17 +951,18 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
         opacityApplied = paintResult.opacityApplied;
       }
     }
 
     if (maskUsage.shouldGenerateClipMaskLayer) {
       matSR.Restore();
       matSR.SetContext(&context);
 
-      MoveContextOriginToUserSpace(firstFrame, aParams);
+      SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+                         offsetToUserSpace);
       Matrix clipMaskTransform;
       DrawResult clipMaskResult;
       RefPtr<SourceSurface> clipMaskSurface;
       Tie(clipMaskResult, clipMaskSurface) =
         clipPathFrame->GetClipMask(context, frame, cssPxToDevPxMatrix,
                                    &clipMaskTransform, maskSurface,
                                    maskTransform);
 
@@ -993,17 +979,18 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
     }
 
     // opacity != 1.0f.
     if (!maskUsage.shouldGenerateClipMaskLayer &&
         !maskUsage.shouldGenerateMaskLayer) {
       MOZ_ASSERT(maskUsage.opacity != 1.0f);
 
       matSR.SetContext(&context);
-      MoveContextOriginToUserSpace(firstFrame, aParams);
+      SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+                         offsetToUserSpace);
       shouldPushMask = true;
     }
 
     if (shouldPushMask) {
       if (aParams.layerManager->GetRoot()->GetContentFlags() &
           Layer::CONTENT_COMPONENT_ALPHA) {
         context.PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA,
                                            opacityApplied
@@ -1019,17 +1006,18 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
   }
 
   /* If this frame has only a trivial clipPath, set up cairo's clipping now so
    * we can just do normal painting and get it clipped appropriately.
    */
   if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
     gfxContextMatrixAutoSaveRestore matSR(&context);
 
-    MoveContextOriginToUserSpace(firstFrame, aParams);
+    SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+                       offsetToUserSpace);
 
     MOZ_ASSERT(!maskUsage.shouldApplyClipPath ||
                !maskUsage.shouldApplyBasicShape);
     if (maskUsage.shouldApplyClipPath) {
       clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
     } else {
       nsCSSClipPathInstance::ApplyBasicShapeClip(context, frame);
     }
@@ -1105,46 +1093,56 @@ nsSVGIntegrationUtils::PaintFilter(const
   nsSVGEffects::EffectProperties effectProperties =
     nsSVGEffects::GetEffectProperties(firstFrame);
 
   if (effectProperties.HasInvalidFilter()) {
     return DrawResult::NOT_READY;
   }
 
   gfxContext& context = aParams.ctx;
+  nsPoint offsetToBoundingBox;
+  nsPoint offsetToUserSpace;
 
   gfxContextAutoSaveRestore autoSR(&context);
-  EffectOffsets offsets = MoveContextOriginToUserSpace(firstFrame, aParams);
+  SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+                     offsetToUserSpace);
 
   if (opacity != 1.0f) {
     context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity,
                                   nullptr, Matrix());
   }
 
   /* Paint the child and apply filters */
   RegularFramePaintCallback callback(aParams.builder, aParams.layerManager,
-                                     offsets.offsetToUserSpaceInDevPx);
-  nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
-  gfxSize scaleFactors = context.CurrentMatrix().ScaleFactors(true);
-  gfxMatrix scaleMatrix(scaleFactors.width, 0.0f,
-                        0.0f, scaleFactors.height,
-                        0.0f, 0.0f);
-  gfxMatrix tm =
-    scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
+                                     offsetToUserSpace);
+  nsRegion dirtyRegion = aParams.dirtyRect - offsetToBoundingBox;
+  gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(frame);
   DrawResult result =
     nsFilterInstance::PaintFilteredFrame(frame, context.GetDrawTarget(),
                                          tm, &callback, &dirtyRegion);
 
   if (opacity != 1.0f) {
     context.PopGroupAndBlend();
   }
 
   return result;
 }
 
+gfxMatrix
+nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame)
+{
+  int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel();
+  float devPxPerCSSPx =
+    1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel);
+
+  return gfxMatrix(devPxPerCSSPx, 0.0,
+                   0.0, devPxPerCSSPx,
+                   0.0, 0.0);
+}
+
 class PaintFrameCallback : public gfxDrawingCallback {
 public:
   PaintFrameCallback(nsIFrame* aFrame,
                      const nsSize aPaintServerSize,
                      const IntSize aRenderSize,
                      uint32_t aFlags)
    : mFrame(aFrame)
    , mPaintServerSize(aPaintServerSize)
--- a/layout/svg/nsSVGIntegrationUtils.h
+++ b/layout/svg/nsSVGIntegrationUtils.h
@@ -176,16 +176,25 @@ public:
 
   /**
    * Paint non-SVG frame with filter and opacity effect.
    */
   static DrawResult
   PaintFilter(const PaintFramesParams& aParams);
 
   /**
+   * SVG frames expect to paint in SVG user units, which are equal to CSS px
+   * units. This method provides a transform matrix to multiply onto a
+   * gfxContext's current transform to convert the context's current units from
+   * its usual dev pixels to SVG user units/CSS px to keep the SVG code happy.
+   */
+  static gfxMatrix
+  GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame);
+
+  /**
    * @param aRenderingContext the target rendering context in which the paint
    * server will be rendered
    * @param aTarget the target frame onto which the paint server will be
    * rendered
    * @param aPaintServer a first-continuation frame to use as the source
    * @param aFilter a filter to be applied when scaling
    * @param aDest the area the paint server image should be mapped to
    * @param aFill the area to be filled with copies of the paint server image
--- a/layout/svg/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/nsSVGOuterSVGFrame.cpp
@@ -621,17 +621,17 @@ nsDisplayOuterSVG::Paint(nsDisplayListBu
       ToOutsidePixels(appUnitsPerDevPixel);
 
   gfxPoint devPixelOffset =
     nsLayoutUtils::PointToGfxPoint(viewportRect.TopLeft(), appUnitsPerDevPixel);
 
   aContext->ThebesContext()->Save();
   // We include the offset of our frame and a scale from device pixels to user
   // units (i.e. CSS px) in the matrix that we pass to our children):
-  gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
+  gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) *
                    gfxMatrix::Translation(devPixelOffset);
   DrawResult result =
     nsSVGUtils::PaintFrameWithEffects(mFrame, *aContext->ThebesContext(), tm,
                                       &contentAreaDirtyRect);
   nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
   aContext->ThebesContext()->Restore();
 
 #if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING)
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -394,25 +394,25 @@ nsSVGUtils::GetOuterSVGFrameAndCoveredRe
 }
 
 gfxMatrix
 nsSVGUtils::GetCanvasTM(nsIFrame *aFrame)
 {
   // XXX yuck, we really need a common interface for GetCanvasTM
 
   if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
-    return GetCSSPxToDevPxMatrix(aFrame);
+    return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
   }
 
   nsIAtom* type = aFrame->GetType();
   if (type == nsGkAtoms::svgForeignObjectFrame) {
     return static_cast<nsSVGForeignObjectFrame*>(aFrame)->GetCanvasTM();
   }
   if (type == nsGkAtoms::svgOuterSVGFrame) {
-    return GetCSSPxToDevPxMatrix(aFrame);
+    return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
   }
 
   nsSVGContainerFrame *containerFrame = do_QueryFrame(aFrame);
   if (containerFrame) {
     return containerFrame->GetCanvasTM();
   }
 
   return static_cast<SVGGeometryFrame*>(aFrame)->GetCanvasTM();
@@ -1859,20 +1859,8 @@ nsRect
 nsSVGUtils::ToCanvasBounds(const gfxRect &aUserspaceRect,
                            const gfxMatrix &aToCanvas,
                            const nsPresContext *presContext)
 {
   return nsLayoutUtils::RoundGfxRectToAppRect(
                           aToCanvas.TransformBounds(aUserspaceRect),
                           presContext->AppUnitsPerDevPixel());
 }
-
-gfxMatrix
-nsSVGUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame)
-{
-  int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel();
-  float devPxPerCSSPx =
-    1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel);
-
-  return gfxMatrix(devPxPerCSSPx, 0.0,
-                   0.0, devPxPerCSSPx,
-                   0.0, 0.0);
-}
\ No newline at end of file
--- a/layout/svg/nsSVGUtils.h
+++ b/layout/svg/nsSVGUtils.h
@@ -592,20 +592,11 @@ public:
     { }
   };
 
   static void
   DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity, MaskUsage& aUsage);
 
   static float
   ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity);
-
-  /**
-   * SVG frames expect to paint in SVG user units, which are equal to CSS px
-   * units. This method provides a transform matrix to multiply onto a
-   * gfxContext's current transform to convert the context's current units from
-   * its usual dev pixels to SVG user units/CSS px to keep the SVG code happy.
-   */
-  static gfxMatrix
-  GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame);
 };
 
 #endif