Bug 1000382 - Don't simplify the invalidation region before calling nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects. r=roc
authorMarkus Stange <mstange@themasta.com>
Thu, 24 Apr 2014 10:25:17 +0200
changeset 198436 a26c8f53a22365985eabcf3b5120fbb26f11fd96
parent 198435 36dcdf8ec0855a19c89ec5729315025beb21ecb8
child 198437 3dcee82b43f4c9e95e893c9a0467e431836c29bb
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs1000382
milestone31.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 1000382 - Don't simplify the invalidation region before calling nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects. r=roc
layout/base/FrameLayerBuilder.cpp
layout/reftests/invalidation/reftest.list
layout/reftests/invalidation/scroll-inactive-layers-2.html
layout/svg/nsFilterInstance.cpp
layout/svg/nsFilterInstance.h
layout/svg/nsSVGIntegrationUtils.cpp
layout/svg/nsSVGIntegrationUtils.h
layout/svg/nsSVGUtils.cpp
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -2850,17 +2850,17 @@ FrameLayerBuilder::AddThebesDisplayItem(
       tempManager->AbortTransaction();
 
       nsIntPoint offset = GetLastPaintOffset(layer) - GetTranslationForThebesLayer(layer);
       props->MoveBy(-offset);
       nsIntRegion invalid = props->ComputeDifferences(tmpLayer, nullptr);
       if (aLayerState == LAYER_SVG_EFFECTS) {
         invalid = nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(aItem->Frame(),
                                                                         aItem->ToReferenceFrame(),
-                                                                        invalid.GetBounds());
+                                                                        invalid);
       }
       if (!invalid.IsEmpty()) {
 #ifdef MOZ_DUMP_PAINTING
         if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
           printf_stderr("Inactive LayerManager(%p) for display item %s(%p) has an invalid region - invalidating layer %p\n", tempManager.get(), aItem->Name(), aItem->Frame(), layer);
         }
 #endif
         if (hasClip) {
--- a/layout/reftests/invalidation/reftest.list
+++ b/layout/reftests/invalidation/reftest.list
@@ -34,8 +34,9 @@ pref(layout.animated-image-layers.enable
 == filter-userspace-offset.svg?offsetContainer=foreignObject&mask=boundingBox filter-userspace-offset.svg
 == filter-userspace-offset.svg?offsetContainer=rect&mask=userSpace-at100 filter-userspace-offset.svg
 == filter-userspace-offset.svg?offsetContainer=use&mask=userSpace-atZero filter-userspace-offset.svg
 == filter-userspace-offset.svg?offsetContainer=innerSVG&mask=userSpace-atZero filter-userspace-offset.svg
 == filter-userspace-offset.svg?offsetContainer=foreignObject&mask=userSpace-at100 filter-userspace-offset.svg
 == filter-userspace-offset.svg?offsetContainer=rect&filter=matrix-fillPaint-boundingBox filter-userspace-offset.svg
 == filter-userspace-offset.svg?offsetContainer=rect&filter=matrix-fillPaint-userSpace-at100 filter-userspace-offset.svg
 == scroll-inactive-layers.html scroll-inactive-layers.html
+== scroll-inactive-layers-2.html scroll-inactive-layers-2.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/scroll-inactive-layers-2.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Scrolling over inactive layers shouldn't repaint their contents even if both the top and the bottom edge of the inactive layers are offscreen</title>
+
+<style>
+
+html, body {
+  margin: 0;
+  padding: 0;
+}
+
+.outer {
+  border: 1px solid black;
+  width: 100px;
+  height: 2000px;
+  margin-right: 20px;
+  padding-top: 200px;
+  float: left;
+}
+
+.opacity {
+  opacity: 0.5;
+}
+
+.transform {
+  transform: translateX(1px);
+}
+
+.filter {
+  filter: url(#filter);
+}
+
+.mask {
+  mask: url(#mask);
+}
+
+.reftest-no-paint {
+  height: 50px;
+  border: 1px solid lime;
+}
+
+</style>
+
+<svg height="0">
+  <defs>
+    <filter id="filter" filterUnits="objectBoundingBox"
+            x="0%" y="0%" width="100%" height="100%"
+            color-interpolation-filters="sRGB">
+      <feMerge><feMergeNode/><feMerge>
+    </filter>
+    <mask id="mask" maskContentUnits="objectBoundingBox">
+      <rect x="0" y="0" width="1" height="1" fill="white"/>
+    </mask>
+  </defs>
+</svg>
+
+<div class="outer opacity">
+  <div class="reftest-no-paint"></div>
+</div>
+
+<div class="outer transform">
+  <div class="reftest-no-paint"></div>
+</div>
+
+<div class="outer filter">
+  <div class="reftest-no-paint"></div>
+</div>
+
+<div class="outer mask">
+  <div class="reftest-no-paint"></div>
+</div>
+
+<script>
+
+function doTest() {
+  document.documentElement.scrollTop = 100;
+  document.documentElement.removeAttribute("class");
+}
+document.documentElement.scrollTop = 50;
+document.addEventListener("MozReftestInvalidate", doTest, false);
+
+</script>
--- a/layout/svg/nsFilterInstance.cpp
+++ b/layout/svg/nsFilterInstance.cpp
@@ -21,97 +21,103 @@
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
 nsresult
 nsFilterInstance::PaintFilteredFrame(nsRenderingContext *aContext,
                                      nsIFrame *aFilteredFrame,
                                      nsSVGFilterPaintCallback *aPaintCallback,
-                                     const nsRect *aDirtyArea,
+                                     const nsRegion *aDirtyArea,
                                      nsIFrame* aTransformRoot)
 {
   nsFilterInstance instance(aFilteredFrame, aPaintCallback, aDirtyArea,
                             nullptr, nullptr, nullptr,
                             aTransformRoot);
   if (!instance.IsInitialized()) {
     return NS_OK;
   }
   return instance.Render(aContext->ThebesContext());
 }
 
-nsRect
+nsRegion
 nsFilterInstance::GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
-                                         const nsRect& aPreFilterDirtyRect)
+                                         const nsRegion& aPreFilterDirtyRegion)
 {
-  if (aPreFilterDirtyRect.IsEmpty()) {
-    return nsRect();
+  if (aPreFilterDirtyRegion.IsEmpty()) {
+    return nsRegion();
   }
 
   nsFilterInstance instance(aFilteredFrame, nullptr, nullptr,
-                            &aPreFilterDirtyRect);
+                            &aPreFilterDirtyRegion);
   if (!instance.IsInitialized()) {
-    return nsRect();
+    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.
-  nsRect dirtyRect;
-  nsresult rv = instance.ComputePostFilterDirtyRect(&dirtyRect);
+  nsRegion dirtyRegion;
+  nsresult rv = instance.ComputePostFilterDirtyRegion(&dirtyRegion);
   if (NS_SUCCEEDED(rv)) {
-    return dirtyRect;
+    return dirtyRegion;
   }
-  return nsRect();
+  return nsRegion();
 }
 
-nsRect
+nsRegion
 nsFilterInstance::GetPreFilterNeededArea(nsIFrame *aFilteredFrame,
-                                         const nsRect& aPostFilterDirtyRect)
+                                         const nsRegion& aPostFilterDirtyRegion)
 {
-  nsFilterInstance instance(aFilteredFrame, nullptr, &aPostFilterDirtyRect);
+  nsFilterInstance instance(aFilteredFrame, nullptr, &aPostFilterDirtyRegion);
   if (!instance.IsInitialized()) {
     return nsRect();
   }
   // Now we can ask the instance to compute the area of the source
   // that's needed.
   nsRect neededRect;
   nsresult rv = instance.ComputeSourceNeededRect(&neededRect);
   if (NS_SUCCEEDED(rv)) {
     return neededRect;
   }
-  return nsRect();
+  return nsRegion();
 }
 
 nsRect
 nsFilterInstance::GetPostFilterBounds(nsIFrame *aFilteredFrame,
                                       const gfxRect *aOverrideBBox,
                                       const nsRect *aPreFilterBounds)
 {
   MOZ_ASSERT(!(aFilteredFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
              !(aFilteredFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
              "Non-display SVG do not maintain visual overflow rects");
 
+  nsRegion preFilterRegion;
+  nsRegion* preFilterRegionPtr = nullptr;
+  if (aPreFilterBounds) {
+    preFilterRegion = *aPreFilterBounds;
+    preFilterRegionPtr = &preFilterRegion;
+  }
   nsFilterInstance instance(aFilteredFrame, nullptr, nullptr,
-                            aPreFilterBounds, aPreFilterBounds,
+                            preFilterRegionPtr, aPreFilterBounds,
                             aOverrideBBox);
   if (!instance.IsInitialized()) {
     return nsRect();
   }
   nsRect bbox;
   nsresult rv = instance.ComputePostFilterExtents(&bbox);
   if (NS_SUCCEEDED(rv)) {
     return bbox;
   }
   return nsRect();
 }
 
 nsFilterInstance::nsFilterInstance(nsIFrame *aTargetFrame,
                                    nsSVGFilterPaintCallback *aPaintCallback,
-                                   const nsRect *aPostFilterDirtyRect,
-                                   const nsRect *aPreFilterDirtyRect,
+                                   const nsRegion *aPostFilterDirtyRegion,
+                                   const nsRegion *aPreFilterDirtyRegion,
                                    const nsRect *aPreFilterVisualOverflowRectOverride,
                                    const gfxRect *aOverrideBBox,
                                    nsIFrame* aTransformRoot) :
   mTargetFrame(aTargetFrame),
   mPaintCallback(aPaintCallback),
   mTransformRoot(aTransformRoot),
   mInitialized(false) {
 
@@ -151,18 +157,18 @@ nsFilterInstance::nsFilterInstance(nsIFr
 
   mFilterSpaceToFrameSpaceInCSSPxTransform =
     filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
   // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
   mFrameSpaceInCSSPxToFilterSpaceTransform =
     mFilterSpaceToFrameSpaceInCSSPxTransform;
   mFrameSpaceInCSSPxToFilterSpaceTransform.Invert();
 
-  mPostFilterDirtyRect = FrameSpaceToFilterSpace(aPostFilterDirtyRect);
-  mPreFilterDirtyRect = FrameSpaceToFilterSpace(aPreFilterDirtyRect);
+  mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion);
+  mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion);
   if (aPreFilterVisualOverflowRectOverride) {
     mTargetBounds = 
       FrameSpaceToFilterSpace(aPreFilterVisualOverflowRectOverride);
   } else {
     nsRect preFilterVOR = mTargetFrame->GetPreEffectsVisualOverflowRect();
     mTargetBounds = FrameSpaceToFilterSpace(&preFilterVOR);
   }
 
@@ -267,17 +273,17 @@ nsFilterInstance::ComputeNeededBoxes()
     return;
 
   nsIntRegion sourceGraphicNeededRegion;
   nsIntRegion fillPaintNeededRegion;
   nsIntRegion strokePaintNeededRegion;
 
   FilterDescription filter(mPrimitiveDescriptions, ToIntRect(mFilterSpaceBounds));
   FilterSupport::ComputeSourceNeededRegions(
-    filter, mPostFilterDirtyRect,
+    filter, mPostFilterDirtyRegion,
     sourceGraphicNeededRegion, fillPaintNeededRegion, strokePaintNeededRegion);
 
   nsIntRect sourceBoundsInt;
   gfxRect sourceBounds = UserSpaceToFilterSpace(mTargetBBox);
   sourceBounds.RoundOut();
   // Detect possible float->int overflow
   if (!gfxUtils::GfxRectToIntRect(sourceBounds, &sourceBoundsInt))
     return;
@@ -438,17 +444,17 @@ nsFilterInstance::BuildSourceImage(gfxAS
   mSourceGraphic.mSurfaceRect = ToIntRect(neededRect);
    
   return NS_OK;
 }
 
 nsresult
 nsFilterInstance::Render(gfxContext* aContext)
 {
-  nsIntRect filterRect = mPostFilterDirtyRect.Intersect(mFilterSpaceBounds);
+  nsIntRect filterRect = mPostFilterDirtyRegion.GetBounds().Intersect(mFilterSpaceBounds);
   gfxMatrix ctm = GetFilterSpaceToDeviceSpaceTransform();
 
   if (filterRect.IsEmpty() || ctm.IsSingular()) {
     return NS_OK;
   }
 
   Matrix oldDTMatrix;
   nsRefPtr<gfxASurface> resultImage;
@@ -502,30 +508,30 @@ nsFilterInstance::Render(gfxContext* aCo
   } else {
     dt->SetTransform(oldDTMatrix);
   }
 
   return NS_OK;
 }
 
 nsresult
-nsFilterInstance::ComputePostFilterDirtyRect(nsRect* aPostFilterDirtyRect)
+nsFilterInstance::ComputePostFilterDirtyRegion(nsRegion* aPostFilterDirtyRegion)
 {
-  *aPostFilterDirtyRect = nsRect();
-  if (mPreFilterDirtyRect.IsEmpty()) {
+  *aPostFilterDirtyRegion = nsRegion();
+  if (mPreFilterDirtyRegion.IsEmpty()) {
     return NS_OK;
   }
 
   IntRect filterSpaceBounds = ToIntRect(mFilterSpaceBounds);
   FilterDescription filter(mPrimitiveDescriptions, filterSpaceBounds);
   nsIntRegion resultChangeRegion =
     FilterSupport::ComputeResultChangeRegion(filter,
-      mPreFilterDirtyRect, nsIntRegion(), nsIntRegion());
-  *aPostFilterDirtyRect =
-    FilterSpaceToFrameSpace(resultChangeRegion.GetBounds());
+      mPreFilterDirtyRegion, nsIntRegion(), nsIntRegion());
+  *aPostFilterDirtyRegion =
+    FilterSpaceToFrameSpace(resultChangeRegion);
   return NS_OK;
 }
 
 nsresult
 nsFilterInstance::ComputePostFilterExtents(nsRect* aPostFilterExtents)
 {
   *aPostFilterExtents = nsRect();
 
@@ -578,16 +584,44 @@ nsFilterInstance::FrameSpaceToFilterSpac
 nsRect
 nsFilterInstance::FilterSpaceToFrameSpace(const nsIntRect& aRect) const
 {
   if (aRect.IsEmpty()) {
     return nsRect();
   }
   gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
   r = mFilterSpaceToFrameSpaceInCSSPxTransform.TransformBounds(r);
+  // nsLayoutUtils::RoundGfxRectToAppRect rounds out.
   return nsLayoutUtils::RoundGfxRectToAppRect(r, mAppUnitsPerCSSPx);
 }
 
+nsIntRegion
+nsFilterInstance::FrameSpaceToFilterSpace(const nsRegion* aRegion) const
+{
+  if (!aRegion) {
+    return mFilterSpaceBounds;
+  }
+  nsIntRegion result;
+  nsRegionRectIterator it(*aRegion);
+  while (const nsRect* r = it.Next()) {
+    // FrameSpaceToFilterSpace rounds out, so this works.
+    result.Or(result, FrameSpaceToFilterSpace(r));
+  }
+  return result;
+}
+
+nsRegion
+nsFilterInstance::FilterSpaceToFrameSpace(const nsIntRegion& aRegion) const
+{
+  nsRegion result;
+  nsIntRegionRectIterator it(aRegion);
+  while (const nsIntRect* r = it.Next()) {
+    // FilterSpaceToFrameSpace rounds out, so this works.
+    result.Or(result, FilterSpaceToFrameSpace(*r));
+  }
+  return result;
+}
+
 gfxMatrix
 nsFilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const
 {
   return gfxMatrix().Translate(-nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame));
 }
--- a/layout/svg/nsFilterInstance.h
+++ b/layout/svg/nsFilterInstance.h
@@ -53,36 +53,36 @@ 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).
    */
   static nsresult PaintFilteredFrame(nsRenderingContext *aContext,
                                      nsIFrame *aFilteredFrame,
                                      nsSVGFilterPaintCallback *aPaintCallback,
-                                     const nsRect* aDirtyArea,
+                                     const nsRegion* aDirtyArea,
                                      nsIFrame* aTransformRoot = nullptr);
 
   /**
    * 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
+   * @param aPreFilterDirtyRegion The pre-filter area of aFilteredFrame that has
    *   changed, relative to aFilteredFrame, in app units.
    */
-  static nsRect GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
-                                       const nsRect& aPreFilterDirtyRect);
+  static nsRegion GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
+                                         const nsRegion& aPreFilterDirtyRegion);
 
   /**
    * Returns the pre-filter area that is needed from aFilteredFrame when the
    * given post-filter area needs to be repainted.
-   * @param aPostFilterDirtyRect The post-filter area that is dirty, relative
+   * @param aPostFilterDirtyRegion The post-filter area that is dirty, relative
    *   to aFilteredFrame, in app units.
    */
-  static nsRect GetPreFilterNeededArea(nsIFrame *aFilteredFrame,
-                                       const nsRect& aPostFilterDirtyRect);
+  static nsRegion GetPreFilterNeededArea(nsIFrame *aFilteredFrame,
+                                         const nsRegion& aPostFilterDirtyRegion);
 
   /**
    * Returns the post-filter visual overflow rect (paint bounds) of
    * aFilteredFrame.
    * @param aOverrideBBox A user space rect, in user units, that should be used
    *   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.
@@ -90,72 +90,72 @@ public:
   static nsRect GetPostFilterBounds(nsIFrame *aFilteredFrame,
                                     const gfxRect *aOverrideBBox = nullptr,
                                     const nsRect *aPreFilterBounds = nullptr);
 
   /**
    * @param aTargetFrame The frame of the filtered element under consideration.
    * @param aPaintCallback [optional] The callback that Render() should use to
    *   paint. Only required if you will call Render().
-   * @param aPostFilterDirtyRect [optional] The bounds of the post-filter area
-   *   that has to be repainted, in filter space. Only required if you will
+   * @param aPostFilterDirtyRegion [optional] The post-filter area
+   *   that has to be repainted, in app units. Only required if you will
    *   call ComputeSourceNeededRect() or Render().
-   * @param aPreFilterDirtyRect [optional] The bounds of the pre-filter area of
-   *   the filtered element that changed, in filter space. Only required if you
-   *   will call ComputePostFilterDirtyRect().
+   * @param aPreFilterDirtyRegion [optional] The pre-filter area of
+   *   the filtered element that changed, in app units. Only required if you
+   *   will call ComputePostFilterDirtyRegion().
    * @param aOverridePreFilterVisualOverflowRect [optional] Use a different
    *   visual overflow rect for the target element.
    * @param aOverrideBBox [optional] Use a different SVG bbox for the target
    *   element.
    * @param aTransformRoot [optional] The transform root frame for painting.
    */
   nsFilterInstance(nsIFrame *aTargetFrame,
                    nsSVGFilterPaintCallback *aPaintCallback,
-                   const nsRect *aPostFilterDirtyRect = nullptr,
-                   const nsRect *aPreFilterDirtyRect = nullptr,
+                   const nsRegion *aPostFilterDirtyRegion = nullptr,
+                   const nsRegion *aPreFilterDirtyRegion = nullptr,
                    const nsRect *aOverridePreFilterVisualOverflowRect = nullptr,
                    const gfxRect *aOverrideBBox = nullptr,
                    nsIFrame* aTransformRoot = nullptr);
 
   /**
    * Returns true if the filter instance was created successfully.
    */
   bool IsInitialized() const { return mInitialized; }
 
   /**
    * Draws the filter output into aContext. The area that
    * needs to be painted must have been specified before calling this method
-   * by passing it as the aPostFilterDirtyRect argument to the
+   * by passing it as the aPostFilterDirtyRegion argument to the
    * nsFilterInstance constructor.
    */
   nsresult Render(gfxContext* aContext);
 
   /**
-   * Sets the aPostFilterDirtyRect outparam to the post-filter bounds in frame
-   * space of the area that would be dirtied by mTargetFrame when a given
+   * Sets the aPostFilterDirtyRegion outparam to the post-filter area in frame
+   * space that would be dirtied by mTargetFrame when a given
    * pre-filter area of mTargetFrame is dirtied. The pre-filter area must have
    * been specified before calling this method by passing it as the
-   * aPreFilterDirtyRect argument to the nsFilterInstance constructor.
+   * aPreFilterDirtyRegion argument to the nsFilterInstance constructor.
    */
-  nsresult ComputePostFilterDirtyRect(nsRect* aPostFilterDirtyRect);
+  nsresult ComputePostFilterDirtyRegion(nsRegion* aPostFilterDirtyRegion);
 
   /**
    * Sets the aPostFilterExtents outparam to the post-filter bounds in frame
    * space for the whole filter output. This is not necessarily equivalent to
    * the area that would be dirtied in the result when the entire pre-filter
    * area is dirtied, because some filter primitives can generate output
    * without any input.
    */
   nsresult ComputePostFilterExtents(nsRect* aPostFilterExtents);
 
   /**
    * 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 aPostFilterDirtyRect
+   * specified before calling this method by passing it as the aPostFilterDirtyRegion
    * argument to the nsFilterInstance constructor.
    */
   nsresult ComputeSourceNeededRect(nsRect* aDirty);
 
 
   /**
    * Returns the transform from filter space to outer-<svg> device space.
    */
@@ -212,40 +212,49 @@ private:
    * Add to the list of FilterPrimitiveDescriptions for a particular SVG
    * reference filter or CSS filter. This populates mPrimitiveDescrs and
    * mInputImages.
    */
   nsresult BuildPrimitivesForFilter(const nsStyleFilter& aFilter);
 
   /**
    * Computes the filter space bounds of the areas that we actually *need* from
-   * the filter sources, based on the value of mPostFilterDirtyRect.
+   * the filter sources, based on the value of mPostFilterDirtyRegion.
    * This sets mNeededBounds on the corresponding SourceInfo structs.
    */
   void ComputeNeededBoxes();
 
   /**
    * Compute the scale factors between user space and filter space.
    */
   nsresult ComputeUserSpaceToFilterSpaceScale();
 
   /**
    * Transform a rect between user space and filter space.
    */
   gfxRect UserSpaceToFilterSpace(const gfxRect& aUserSpace) const;
   gfxRect FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const;
 
   /**
-   * Converts an nsRect that is relative to a filtered frame's origin (i.e. the
-   * top-left corner of its border box) into filter space.
-   * Returns the entire filter region if aRect is null, or if the result is too
-   * large to be stored in an nsIntRect.
+   * Converts an nsRect or an nsRegion that is relative to a filtered frame's
+   * origin (i.e. the top-left corner of its border box) into filter space,
+   * rounding out.
+   * Returns the entire filter region if aRect / aRegion is null, or if the
+   * result is too large to be stored in an nsIntRect.
    */
   nsIntRect FrameSpaceToFilterSpace(const nsRect* aRect) const;
+  nsIntRegion FrameSpaceToFilterSpace(const nsRegion* aRegion) const;
+
+  /**
+   * Converts an nsIntRect or an nsIntRegion from filter space into the space
+   * that is relative to a filtered frame's origin (i.e. the top-left corner
+   * of its border box) in app units, rounding out.
+   */
   nsRect FilterSpaceToFrameSpace(const nsIntRect& aRect) const;
+  nsRegion FilterSpaceToFrameSpace(const nsIntRegion& aRegion) const;
 
   /**
    * Returns the transform from frame space to the coordinate space that
    * GetCanvasTM transforms to. "Frame space" is the origin of a frame, aka the
    * top-left corner of its border box, aka the top left corner of its mRect.
    */
   gfxMatrix GetUserSpaceToFrameSpaceInCSSPxTransform() const;
 
@@ -286,29 +295,24 @@ private:
 
   /**
    * Pre-filter paint bounds of the element that is being filtered, in filter
    * space.
    */
   nsIntRect               mTargetBounds;
 
   /**
-   * If set, this is the filter space bounds of the outer-<svg> device space
-   * bounds of the dirty area that needs to be repainted. (As bounds-of-bounds,
-   * this may be a fair bit bigger than we actually need, unfortunately.)
+   * The dirty area that needs to be repainted, in filter space.
    */
-  nsIntRect               mPostFilterDirtyRect;
+  nsIntRegion             mPostFilterDirtyRegion;
 
   /**
-   * If set, this is the filter space bounds of the outer-<svg> device bounds
-   * of the pre-filter area of the filtered element that changed. (As
-   * bounds-of-bounds, this may be a fair bit bigger than we actually need,
-   * unfortunately.)
+   * The pre-filter area of the filtered element that changed, in filter space.
    */
-  nsIntRect               mPreFilterDirtyRect;
+  nsIntRegion             mPreFilterDirtyRegion;
 
   SourceInfo              mSourceGraphic;
   SourceInfo              mFillPaint;
   SourceInfo              mStrokePaint;
   nsIFrame*               mTransformRoot;
   nsTArray<mozilla::RefPtr<SourceSurface>> mInputImages;
   nsTArray<FilterPrimitiveDescription> mPrimitiveDescriptions;
   int32_t                 mAppUnitsPerCSSPx;
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -278,57 +278,57 @@ nsRect
 
   nsRect overflowRect =
     nsFilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox);
 
   // Return overflowRect relative to aFrame, rather than "user space":
   return overflowRect - (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox);
 }
 
-nsIntRect
+nsIntRegion
 nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(nsIFrame* aFrame,
                                                       const nsPoint& aToReferenceFrame,
-                                                      const nsIntRect& aInvalidRect)
+                                                      const nsIntRegion& aInvalidRegion)
 {
-  if (aInvalidRect.IsEmpty()) {
+  if (aInvalidRegion.IsEmpty()) {
     return nsIntRect();
   }
 
   // Don't bother calling GetEffectProperties; the filter property should
   // already have been set up during reflow/ComputeFrameEffectsRect
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
   nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame);
   if (!prop || !prop->IsInObserverLists()) {
-    return aInvalidRect;
+    return aInvalidRegion;
   }
 
   int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
 
   if (!prop || !prop->ReferencesValidResources()) {
     // The frame is either not there or not currently available,
     // perhaps because we're in the middle of tearing stuff down.
     // Be conservative, return our visual overflow rect relative
     // to the reference frame.
     nsRect overflow = aFrame->GetVisualOverflowRect() + aToReferenceFrame;
     return overflow.ToOutsidePixels(appUnitsPerDevPixel);
   }
 
-  // Convert aInvalidRect into bounding box frame space in app units:
+  // Convert aInvalidRegion into bounding box frame space in app units:
   nsPoint toBoundingBox =
     aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
   // The initial rect was relative to the reference frame, so we need to
   // remove that offset to get a rect relative to the current frame.
   toBoundingBox -= aToReferenceFrame;
-  nsRect preEffectsRect = aInvalidRect.ToAppUnits(appUnitsPerDevPixel) + toBoundingBox;
+  nsRegion preEffectsRegion = aInvalidRegion.ToAppUnits(appUnitsPerDevPixel).MovedBy(toBoundingBox);
 
   // Adjust the dirty area for effects, and shift it back to being relative to
   // the reference frame.
-  nsRect result = nsFilterInstance::GetPostFilterDirtyArea(firstFrame,
-    preEffectsRect) - toBoundingBox;
+  nsRegion result = nsFilterInstance::GetPostFilterDirtyArea(firstFrame,
+    preEffectsRegion).MovedBy(-toBoundingBox);
   // Return the result, in pixels relative to the reference frame.
   return result.ToOutsidePixels(appUnitsPerDevPixel);
 }
 
 nsRect
 nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(nsIFrame* aFrame,
                                                        const nsRect& aDirtyRect)
 {
@@ -342,17 +342,17 @@ nsSVGIntegrationUtils::GetRequiredSource
   }
   
   // Convert aDirtyRect into "user space" in app units:
   nsPoint toUserSpace =
     aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
   nsRect postEffectsRect = aDirtyRect + toUserSpace;
 
   // Return ther result, relative to aFrame, not in user space:
-  return nsFilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect)
+  return nsFilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect).GetBounds()
     - toUserSpace;
 }
 
 bool
 nsSVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt)
 {
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
@@ -516,18 +516,18 @@ nsSVGIntegrationUtils::PaintFramesWithEf
     clipPathFrame->ClipPaint(aCtx, aFrame, cssPxToDevPxMatrix);
   }
 
   /* Paint the child */
   if (effectProperties.HasValidFilter()) {
     RegularFramePaintCallback callback(aBuilder, aLayerManager,
                                        offsetToUserSpace);
 
-    nsRect dirtyRect = aDirtyRect - offsetToBoundingBox;
-    nsFilterInstance::PaintFilteredFrame(aCtx, aFrame, &callback, &dirtyRect);
+    nsRegion dirtyRegion = aDirtyRect - offsetToBoundingBox;
+    nsFilterInstance::PaintFilteredFrame(aCtx, aFrame, &callback, &dirtyRegion);
   } else {
     gfx->SetMatrix(matrixAutoSaveRestore.Matrix());
     aLayerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer, aBuilder);
     aCtx->Translate(offsetToUserSpace);
   }
 
   if (clipPathFrame && isTrivialClip) {
     gfx->Restore();
--- a/layout/svg/nsSVGIntegrationUtils.h
+++ b/layout/svg/nsSVGIntegrationUtils.h
@@ -11,16 +11,17 @@
 #include "gfxRect.h"
 #include "nsAutoPtr.h"
 
 class gfxDrawable;
 class nsDisplayList;
 class nsDisplayListBuilder;
 class nsIFrame;
 class nsRenderingContext;
+class nsIntRegion;
 
 struct nsRect;
 struct nsIntRect;
 
 namespace mozilla {
 namespace layers {
 class LayerManager;
 }
@@ -91,24 +92,24 @@ public:
 
   /**
    * Used to adjust the area of a frame that needs to be invalidated to take
    * account of SVG effects.
    *
    * @param aFrame The effects frame.
    * @param aToReferenceFrame The offset (in app units) from aFrame to its
    * reference display item.
-   * @param aInvalidRect The pre-effects invalid rect in pixels relative to
+   * @param aInvalidRegion The pre-effects invalid region in pixels relative to
    * the reference display item.
    * @return The post-effects invalid rect in pixels relative to the reference
    * display item.
    */
-  static nsIntRect
+  static nsIntRegion
   AdjustInvalidAreaForSVGEffects(nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
-                                 const nsIntRect& aInvalidRect);
+                                 const nsIntRegion& aInvalidRegion);
 
   /**
    * Figure out which area of the source is needed given an area to
    * repaint
    */
   static nsRect
   GetRequiredSourceForInvalidArea(nsIFrame* aFrame, const nsRect& aDamageRect);
 
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -592,40 +592,40 @@ nsSVGUtils::PaintFrameWithEffects(nsRend
    */
   if (clipPathFrame && isTrivialClip) {
     gfx->Save();
     clipPathFrame->ClipPaint(aContext, aFrame, matrix);
   }
 
   /* Paint the child */
   if (effectProperties.HasValidFilter()) {
-    nsRect* dirtyRect = nullptr;
-    nsRect tmpDirtyRect;
+    nsRegion* dirtyRegion = nullptr;
+    nsRegion tmpDirtyRegion;
     if (aDirtyRect) {
       // aDirtyRect is in outer-<svg> device pixels, but the filter code needs
       // it in frame space.
       gfxMatrix userToDeviceSpace =
         GetUserToCanvasTM(aFrame, nsISVGChildFrame::FOR_OUTERSVG_TM);
       if (userToDeviceSpace.IsSingular()) {
         return;
       }
       gfxMatrix deviceToUserSpace = userToDeviceSpace;
       deviceToUserSpace.Invert();
       gfxRect dirtyBounds = deviceToUserSpace.TransformBounds(
                               gfxRect(aDirtyRect->x, aDirtyRect->y,
                                       aDirtyRect->width, aDirtyRect->height));
-      tmpDirtyRect =
+      tmpDirtyRegion =
         nsLayoutUtils::RoundGfxRectToAppRect(
           dirtyBounds, aFrame->PresContext()->AppUnitsPerCSSPixel()) -
         aFrame->GetPosition();
-      dirtyRect = &tmpDirtyRect;
+      dirtyRegion = &tmpDirtyRegion;
     }
     SVGPaintCallback paintCallback;
     nsFilterInstance::PaintFilteredFrame(aContext, aFrame, &paintCallback,
-                                         dirtyRect, aTransformRoot);
+                                         dirtyRegion, aTransformRoot);
   } else {
     svgChildFrame->PaintSVG(aContext, aDirtyRect, aTransformRoot);
   }
 
   if (clipPathFrame && isTrivialClip) {
     gfx->Restore();
   }