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 180258 a26c8f53a22365985eabcf3b5120fbb26f11fd96
parent 180257 36dcdf8ec0855a19c89ec5729315025beb21ecb8
child 180259 3dcee82b43f4c9e95e893c9a0467e431836c29bb
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersroc
bugs1000382
milestone31.0a1
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();
   }