Bug 1250947. Fill area outside blur with solid color. r=mstange
authorMason Chang <mchang@mozilla.com>
Tue, 29 Mar 2016 08:24:21 -0700
changeset 290943 98818a65c221dc220432d1c3da491f3aa97c1a81
parent 290942 4de1094b41b27db10a5b0e5683ba4272c505ee9c
child 290944 0ff7a5a4b24f76859762f1e93ad9c617b8f3c643
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1250947
milestone48.0a1
Bug 1250947. Fill area outside blur with solid color. r=mstange
gfx/thebes/gfxBlur.cpp
gfx/thebes/gfxBlur.h
layout/reftests/box-shadow/boxshadow-inset-large-border-radius-ref.html
layout/reftests/box-shadow/boxshadow-inset-large-border-radius.html
layout/reftests/box-shadow/boxshadow-inset-large-offset-ref.html
layout/reftests/box-shadow/boxshadow-inset-large-offset.html
layout/reftests/box-shadow/reftest.list
--- a/gfx/thebes/gfxBlur.cpp
+++ b/gfx/thebes/gfxBlur.cpp
@@ -170,53 +170,50 @@ struct BlurCacheKey : public PLDHashEntr
   IntSize mBlurRadius;
   Color mShadowColor;
   BackendType mBackend;
   RectCornerRadii mCornerRadii;
   bool mIsInset;
 
   // Only used for inset blurs
   bool mHasBorderRadius;
-  IntSize mSpreadRadius;
   IntSize mInnerMinSize;
 
   BlurCacheKey(IntSize aMinSize, IntSize aBlurRadius,
                RectCornerRadii* aCornerRadii, const Color& aShadowColor,
                BackendType aBackendType)
     : BlurCacheKey(aMinSize, IntSize(0, 0),
-                   aBlurRadius, IntSize(0, 0),
-                   aCornerRadii, aShadowColor,
-                   false, false, aBackendType)
+                   aBlurRadius, aCornerRadii,
+                   aShadowColor, false,
+                   false, aBackendType)
   {}
 
   explicit BlurCacheKey(const BlurCacheKey* aOther)
     : mMinSize(aOther->mMinSize)
     , mBlurRadius(aOther->mBlurRadius)
     , mShadowColor(aOther->mShadowColor)
     , mBackend(aOther->mBackend)
     , mCornerRadii(aOther->mCornerRadii)
     , mIsInset(aOther->mIsInset)
     , mHasBorderRadius(aOther->mHasBorderRadius)
-    , mSpreadRadius(aOther->mSpreadRadius)
     , mInnerMinSize(aOther->mInnerMinSize)
   { }
 
   explicit BlurCacheKey(IntSize aOuterMinSize, IntSize aInnerMinSize,
-                        IntSize aBlurRadius, IntSize aSpreadRadius,
+                        IntSize aBlurRadius,
                         const RectCornerRadii* aCornerRadii,
                         const Color& aShadowColor, bool aIsInset,
                         bool aHasBorderRadius, BackendType aBackendType)
     : mMinSize(aOuterMinSize)
     , mBlurRadius(aBlurRadius)
     , mShadowColor(aShadowColor)
     , mBackend(aBackendType)
     , mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii())
     , mIsInset(aIsInset)
     , mHasBorderRadius(aHasBorderRadius)
-    , mSpreadRadius(aSpreadRadius)
     , mInnerMinSize(aInnerMinSize)
   { }
 
   static PLDHashNumber
   HashKey(const KeyTypePointer aKey)
   {
     PLDHashNumber hash = 0;
     hash = AddToHash(hash, aKey->mMinSize.width, aKey->mMinSize.height);
@@ -233,17 +230,16 @@ struct BlurCacheKey : public PLDHashEntr
 
     for (int i = 0; i < 4; i++) {
       hash = AddToHash(hash, aKey->mCornerRadii[i].width, aKey->mCornerRadii[i].height);
     }
 
     hash = AddToHash(hash, (uint32_t)aKey->mBackend);
 
     if (aKey->mIsInset) {
-      hash = AddToHash(hash, aKey->mSpreadRadius.width, aKey->mSpreadRadius.height);
       hash = AddToHash(hash, aKey->mInnerMinSize.width, aKey->mInnerMinSize.height);
       hash = AddToHash(hash, HashBytes(&aKey->mHasBorderRadius, sizeof(bool)));
     }
     return hash;
   }
 
   bool
   KeyEquals(KeyTypePointer aKey) const
@@ -251,18 +247,17 @@ struct BlurCacheKey : public PLDHashEntr
     if (aKey->mMinSize == mMinSize &&
         aKey->mBlurRadius == mBlurRadius &&
         aKey->mCornerRadii == mCornerRadii &&
         aKey->mShadowColor == mShadowColor &&
         aKey->mBackend == mBackend) {
 
       if (mIsInset) {
         return (mHasBorderRadius == aKey->mHasBorderRadius) &&
-                (mInnerMinSize == aKey->mInnerMinSize) &&
-                (mSpreadRadius == aKey->mSpreadRadius);
+                (mInnerMinSize == aKey->mInnerMinSize);
       }
 
       return true;
      }
 
      return false;
   }
 
@@ -335,26 +330,26 @@ class BlurCache final : public nsExpirat
       }
 
       return blur;
     }
 
     BlurCacheData* LookupInsetBoxShadow(const IntSize aOuterMinSize,
                                         const IntSize aInnerMinSize,
                                         const IntSize& aBlurRadius,
-                                        const IntSize& aSpreadRadius,
                                         const RectCornerRadii* aCornerRadii,
                                         const Color& aShadowColor,
                                         const bool& aHasBorderRadius,
                                         BackendType aBackendType)
     {
+      bool insetBoxShadow = true;
       BlurCacheKey key(aOuterMinSize, aInnerMinSize,
-                       aBlurRadius, aSpreadRadius,
-                       aCornerRadii, aShadowColor,
-                       true, aHasBorderRadius, aBackendType);
+                       aBlurRadius, aCornerRadii,
+                       aShadowColor, insetBoxShadow,
+                       aHasBorderRadius, aBackendType);
       BlurCacheData* blur = mHashEntries.Get(key);
       if (blur) {
         MarkUsed(blur);
       }
 
       return blur;
     }
 
@@ -630,17 +625,17 @@ DrawBoxShadows(DrawTarget& aDestDrawTarg
                Rect aSkipRect)
 {
   // Corners: top left, top right, bottom left, bottom right
   DrawCorner(aDestDrawTarget, aSourceBlur,
              RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.X(),
                                aDstInner.Y(), aDstOuter.X()),
              RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.X(),
                                aSrcInner.Y(), aSrcOuter.X()),
-             aSkipRect);
+                               aSkipRect);
 
   DrawCorner(aDestDrawTarget, aSourceBlur,
              RectWithEdgesTRBL(aDstOuter.Y(), aDstOuter.XMost(),
                                aDstInner.Y(), aDstInner.XMost()),
              RectWithEdgesTRBL(aSrcOuter.Y(), aSrcOuter.XMost(),
                                aSrcInner.Y(), aSrcInner.XMost()),
              aSkipRect);
 
@@ -666,16 +661,17 @@ DrawBoxShadows(DrawTarget& aDestDrawTarg
                                            aSrcInner.Y(), aSrcInner.X()),
                          aSkipRect);
   RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
                          RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(),
                                            aDstInner.YMost(), aDstOuter.X()),
                          RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
                                            aSrcInner.YMost(), aSrcOuter.X()),
                          aSkipRect);
+
   RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
                          RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(),
                                            aDstInner.YMost(), aDstInner.XMost()),
                          RectWithEdgesTRBL(aSrcInner.Y(), aSrcOuter.XMost(),
                                            aSrcInner.YMost(), aSrcInner.XMost()),
                          aSkipRect);
   RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
                          RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(),
@@ -799,83 +795,16 @@ GetBoxShadowInsetPath(DrawTarget* aDrawT
     AppendRoundedRectToPath(builder, aInnerRect, aInnerClipRadii, false);
   } else {
     AppendRectToPath(builder, aInnerRect, false);
   }
   return builder->Finish();
 }
 
 static void
-ComputeRectsForInsetBoxShadow(IntSize aBlurRadius,
-                              IntSize aSpreadRadius,
-                              IntRect& aOutOuterRect,
-                              IntRect& aOutInnerRect,
-                              IntMargin& aOutPathMargins,
-                              const Rect& aDestRect,
-                              const Rect& aShadowClipRect,
-                              bool aHasBorderRadius,
-                              const RectCornerRadii& aInnerClipRectRadii)
-{
-  IntSize rectBufferSize = aBlurRadius + aSpreadRadius;
-  float cornerWidth = 0;
-  float cornerHeight = 0;
-  if (aHasBorderRadius) {
-    for (size_t i = 0; i < 4; i++) {
-      cornerWidth = std::max(cornerWidth, aInnerClipRectRadii[i].width);
-      cornerHeight = std::max(cornerHeight, aInnerClipRectRadii[i].height);
-    }
-  }
-
-  // Create the inner rect to be the smallest possible size based on
-  // blur / spread / corner radii
-  IntMargin innerMargin = IntMargin(ceil(cornerHeight) + rectBufferSize.height,
-                                    ceil(cornerWidth) + rectBufferSize.width,
-                                    ceil(cornerHeight) + rectBufferSize.height,
-                                    ceil(cornerWidth) + rectBufferSize.width);
-  aOutPathMargins = innerMargin;
-
-  // If we have a negative spread radius, we would not have enough
-  // size to actually do the blur. So the min size must be the abs() of the blur
-  // and spread radius.
-  IntSize minBlurSize(std::abs(aSpreadRadius.width) + std::abs(aBlurRadius.width),
-                      std::abs(aSpreadRadius.height) + std::abs(aBlurRadius.height));
-
-  IntMargin minInnerMargins = IntMargin(ceil(cornerHeight) + minBlurSize.height,
-                                        ceil(cornerWidth) + minBlurSize.width,
-                                        ceil(cornerHeight) + minBlurSize.height,
-                                        ceil(cornerWidth) + minBlurSize.width);
-
-  IntSize minInnerSize(minInnerMargins.LeftRight() + 1,
-                       minInnerMargins.TopBottom() + 1);
-
-  if (aShadowClipRect.height < minInnerSize.height) {
-    minInnerSize.height = aShadowClipRect.height;
-  }
-
-  if (aShadowClipRect.width < minInnerSize.width) {
-    minInnerSize.width = aShadowClipRect.width;
-  }
-
-  // Then expand the outer rect based on the size between the inner/outer rects
-  IntSize minOuterSize(minInnerSize);
-  IntMargin outerRectMargin(rectBufferSize.height, rectBufferSize.width,
-                            rectBufferSize.height, rectBufferSize.width);
-  minOuterSize.width += outerRectMargin.LeftRight();
-  minOuterSize.height += outerRectMargin.TopBottom();
-
-  aOutOuterRect = IntRect(IntPoint(), minOuterSize);
-  aOutInnerRect = IntRect(IntPoint(rectBufferSize.width, rectBufferSize.height), minInnerSize);
-
-  if (aShadowClipRect.IsEmpty()) {
-    aOutInnerRect.width = 0;
-    aOutInnerRect.height = 0;
-  }
-}
-
-static void
 FillDestinationPath(gfxContext* aDestinationCtx,
                     const Rect aDestinationRect,
                     const Rect aShadowClipRect,
                     const Color& aShadowColor,
                     const bool aHasBorderRadius,
                     const RectCornerRadii& aInnerClipRadii)
 {
   // When there is no blur radius, fill the path onto the destination
@@ -885,204 +814,275 @@ FillDestinationPath(gfxContext* aDestina
   RefPtr<Path> shadowPath = GetBoxShadowInsetPath(destDrawTarget, aDestinationRect,
                                                   aShadowClipRect, aHasBorderRadius,
                                                   aInnerClipRadii);
 
   aDestinationCtx->SetPath(shadowPath);
   aDestinationCtx->Fill();
 }
 
-void
+static void
 CacheInsetBlur(const IntSize aMinOuterSize,
                const IntSize aMinInnerSize,
                const IntSize& aBlurRadius,
-               const IntSize& aSpreadRadius,
                const RectCornerRadii* aCornerRadii,
                const Color& aShadowColor,
                const bool& aHasBorderRadius,
                BackendType aBackendType,
-               IntMargin aExtendBy,
                SourceSurface* aBoxShadow)
 {
+  bool isInsetBlur = true;
   BlurCacheKey key(aMinOuterSize, aMinInnerSize,
-                   aBlurRadius, aSpreadRadius,
-                   aCornerRadii, aShadowColor,
-                   true, aHasBorderRadius, aBackendType);
-  BlurCacheData* data = new BlurCacheData(aBoxShadow, aExtendBy, key);
+                   aBlurRadius, aCornerRadii,
+                   aShadowColor, isInsetBlur,
+                   aHasBorderRadius, aBackendType);
+  IntMargin extendDestBy(0, 0, 0, 0);
+  BlurCacheData* data = new BlurCacheData(aBoxShadow, extendDestBy, key);
   if (!gBlurCache->RegisterEntry(data)) {
     delete data;
   }
 }
 
 already_AddRefed<mozilla::gfx::SourceSurface>
-gfxAlphaBoxBlur::GetInsetBlur(IntMargin& aExtendDestBy,
-                              IntMargin& aSlice,
-                              const Rect aDestinationRect,
-                              const Rect aShadowClipRect,
-                              const IntSize& aBlurRadius,
-                              const IntSize& aSpreadRadius,
+gfxAlphaBoxBlur::GetInsetBlur(const mozilla::gfx::Rect aOuterRect,
+                              const mozilla::gfx::Rect aWhitespaceRect,
+                              const bool aIsDestRect,
+                              const mozilla::gfx::Color& aShadowColor,
+                              const mozilla::gfx::IntSize& aBlurRadius,
+                              const bool aHasBorderRadius,
                               const RectCornerRadii& aInnerClipRadii,
-                              const Color& aShadowColor,
-                              const bool& aHasBorderRadius,
-                              const Point aShadowOffset,
-                              bool& aMovedOffset,
                               DrawTarget* aDestDrawTarget)
 {
   if (!gBlurCache) {
     gBlurCache = new BlurCache();
   }
 
-  IntRect outerRect;
-  IntRect innerRect;
-  ComputeRectsForInsetBoxShadow(aBlurRadius, aSpreadRadius,
-                                outerRect, innerRect,
-                                aSlice, aDestinationRect,
-                                aShadowClipRect, aHasBorderRadius,
-                                aInnerClipRadii);
+  IntSize outerSize((int)aOuterRect.width, (int)aOuterRect.height);
+  IntSize whitespaceSize((int)aWhitespaceRect.width, (int)aWhitespaceRect.height);
+  BlurCacheData* cached =
+      gBlurCache->LookupInsetBoxShadow(outerSize, whitespaceSize,
+                                       aBlurRadius, &aInnerClipRadii,
+                                       aShadowColor, aHasBorderRadius,
+                                       aDestDrawTarget->GetBackendType());
 
-  // If we have a shadow offset larger than the min rect,
-  // there's no clean way we can properly create a min rect with the offset
-  // in the correct place and still render correctly.  In those cases,
-  // fallback to just rendering the dest rect as is.
-  bool useDestRect = (std::abs(aShadowOffset.x) > aSlice.left) ||
-                     (std::abs(aShadowOffset.y) > aSlice.top);
-  aMovedOffset = false;
-  if (useDestRect) {
-    aDestinationRect.ToIntRect(&outerRect);
-    aShadowClipRect.ToIntRect(&innerRect);
-    aMovedOffset = true;
-  }
-
-  BlurCacheData* cached =
-      gBlurCache->LookupInsetBoxShadow(outerRect.Size(), innerRect.Size(),
-                                       aBlurRadius, aSpreadRadius,
-                                       &aInnerClipRadii, aShadowColor,
-                                       aHasBorderRadius,
-                                       aDestDrawTarget->GetBackendType());
-  if (cached && !useDestRect) {
-    aExtendDestBy = cached->mExtendDest;
-    // Need to extend it twice: once for the outer rect and once for the inner rect.
-    aSlice += aExtendDestBy;
-    aSlice += aExtendDestBy;
-
+  if (cached && !aIsDestRect) {
     // So we don't forget the actual cached blur
     RefPtr<SourceSurface> cachedBlur = cached->mBlur;
     return cachedBlur.forget();
   }
 
-  // Dirty rect and skip rect are null for the min inset shadow.
-  // When rendering inset box shadows, we respect the spread radius by changing
-  //  the shape of the unblurred shadow, and can pass a spread radius of zero here.
+  // If we can do a min rect, the whitespace rect will be expanded in Init to
+  // aOuterRect.
+  Rect blurRect = aIsDestRect ? aOuterRect : aWhitespaceRect;
   IntSize zeroSpread(0, 0);
-  gfxContext* minGfxContext = Init(ThebesRect(outerRect),
-                                   zeroSpread, aBlurRadius, nullptr, nullptr);
+  gfxContext* minGfxContext = Init(ThebesRect(blurRect),
+                                   zeroSpread, aBlurRadius,
+                                   nullptr, nullptr);
   if (!minGfxContext) {
     return nullptr;
   }
 
+  // This is really annoying. When we create the AlphaBoxBlur, the gfxContext
+  // has a translation applied to it that is the topLeft point. This is actually
+  // the rect we gave it plus the blur radius. The rects we give this for the outer
+  // and whitespace rects are based at (0, 0). We could either translate those rects
+  // when we don't have a destination rect or ignore the translation when using
+  // the dest rect. The dest rects layout gives us expect this translation.
+  if (!aIsDestRect) {
+    minGfxContext->SetMatrix(gfxMatrix());
+  }
+
   DrawTarget* minDrawTarget = minGfxContext->GetDrawTarget();
+
+  // Fill in the path between the inside white space / outer rects
+  // NOT the inner frame
   RefPtr<Path> maskPath =
-    GetBoxShadowInsetPath(minDrawTarget, IntRectToRect(outerRect),
-                          IntRectToRect(innerRect), aHasBorderRadius,
+    GetBoxShadowInsetPath(minDrawTarget, aOuterRect,
+                          aWhitespaceRect, aHasBorderRadius,
                           aInnerClipRadii);
 
   Color black(0.f, 0.f, 0.f, 1.f);
   minGfxContext->SetColor(black);
   minGfxContext->SetPath(maskPath);
   minGfxContext->Fill();
 
+  // Create the A8 mask
   IntPoint topLeft;
   RefPtr<SourceSurface> minMask = DoBlur(minDrawTarget, &topLeft);
   if (!minMask) {
     return nullptr;
   }
 
+  // Fill in with the color we actually wanted
   RefPtr<SourceSurface> minInsetBlur = CreateBoxShadow(minMask, aShadowColor);
   if (!minInsetBlur) {
     return nullptr;
   }
 
-  IntRect blurRect(topLeft, minInsetBlur->GetSize());
-  aExtendDestBy = blurRect - outerRect;
-
-  if (useDestRect) {
-    // Since we're just going to paint the actual rect to the destination
-    aSlice.SizeTo(0, 0, 0, 0);
-  } else {
-    aSlice += aExtendDestBy;
-    aSlice += aExtendDestBy;
-
-    CacheInsetBlur(outerRect.Size(), innerRect.Size(),
-                 aBlurRadius, aSpreadRadius,
-                 &aInnerClipRadii, aShadowColor,
-                 aHasBorderRadius, aDestDrawTarget->GetBackendType(),
-                 aExtendDestBy, minInsetBlur);
-
+  if (!aIsDestRect) {
+    CacheInsetBlur(outerSize, whitespaceSize,
+                   aBlurRadius, &aInnerClipRadii,
+                   aShadowColor, aHasBorderRadius,
+                   aDestDrawTarget->GetBackendType(),
+                   minInsetBlur);
   }
 
   return minInsetBlur.forget();
 }
 
 /***
- * Blur an inset box shadow by doing:
- * 1) Create a minimal box shadow path that creates a frame.
- * 2) Draw the box shadow portion over the destination surface.
- * 3) The "inset" part is created by a clip rect that properly clips
- *    the alpha mask so that it has clean edges. We still create the full
- *    proper alpha mask, but let the clip deal with the clean edges.
+ * We create our minimal rect with 2 rects.
+ * The first is the inside whitespace rect, that is "cut out"
+ * from the box. This is (1). This must be the size
+ * of the blur radius + corner radius so we can have a big enough
+ * inside cut.
+ *
+ * The second (2) is one blur radius surrounding the inner
+ * frame of (1). This is the amount of blur space required
+ * to get a proper blend.
  *
- * All parameters should already be in device pixels.
+ * B = one blur size
+ * W = one blur + corner radii - known as inner margin
+ * ___________________________________
+ * |                                |
+ * |          |             |       |
+ * |      (2) |    (1)      |  (2)  |
+ * |       B  |     W       |   B   |
+ * |          |             |       |
+ * |          |             |       |
+ * |          |                     |
+ * |________________________________|
  */
+static void GetBlurMargins(const bool aHasBorderRadius,
+                           const RectCornerRadii& aInnerClipRadii,
+                           const IntSize aBlurRadius,
+                           Margin& aOutBlurMargin,
+                           Margin& aOutInnerMargin)
+{
+  float cornerWidth = 0;
+  float cornerHeight = 0;
+  if (aHasBorderRadius) {
+    for (size_t i = 0; i < 4; i++) {
+      cornerWidth = std::max(cornerWidth, aInnerClipRadii[i].width);
+      cornerHeight = std::max(cornerHeight, aInnerClipRadii[i].height);
+    }
+  }
+
+  // Only the inside whitespace size cares about the border radius size.
+  // Outer sizes only care about blur.
+  int width = cornerWidth + aBlurRadius.width;
+  int height = cornerHeight + aBlurRadius.height;
+
+  aOutInnerMargin.SizeTo(height, width, height, width);
+  aOutBlurMargin.SizeTo(aBlurRadius.height, aBlurRadius.width,
+                        aBlurRadius.height, aBlurRadius.width);
+}
+
+static bool
+GetInsetBoxShadowRects(const Margin aBlurMargin,
+                       const Margin aInnerMargin,
+                       const Rect aShadowClipRect,
+                       const Rect aDestinationRect,
+                       Rect& aOutWhitespaceRect,
+                       Rect& aOutOuterRect)
+{
+  // We always copy (2 * blur radius) + corner radius worth of data to the destination rect
+  // This covers the blend of the path + the actual blur
+  // Need +1 so that we copy the edges correctly as we'll copy
+  // over the min box shadow corners then the +1 for the edges between
+  // Note, the (x,y) coordinates are from the blur margin
+  // since the frame outside the whitespace rect is 1 blur radius extra space.
+  Rect insideWhiteSpace(aBlurMargin.left,
+                        aBlurMargin.top,
+                        aInnerMargin.LeftRight() + 1,
+                        aInnerMargin.TopBottom() + 1);
+
+  // If the inner white space rect is larger than the shadow clip rect
+  // our approach does not work as we'll just copy one corner
+  // and cover the destination. In those cases, fallback to the destination rect
+  bool useDestRect = (aShadowClipRect.width <= aInnerMargin.LeftRight()) ||
+                     (aShadowClipRect.height <= aInnerMargin.TopBottom());
+
+  if (useDestRect) {
+    aOutWhitespaceRect = aShadowClipRect;
+    aOutOuterRect = aDestinationRect;
+  } else {
+    aOutWhitespaceRect = insideWhiteSpace;
+    aOutOuterRect = aOutWhitespaceRect;
+    aOutOuterRect.Inflate(aBlurMargin);
+  }
+
+  return useDestRect;
+}
+
 void
 gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx,
                               const Rect aDestinationRect,
                               const Rect aShadowClipRect,
                               const IntSize aBlurRadius,
                               const IntSize aSpreadRadius,
                               const Color& aShadowColor,
                               bool aHasBorderRadius,
                               const RectCornerRadii& aInnerClipRadii,
                               const Rect aSkipRect,
                               const Point aShadowOffset)
 {
-  DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
-
-  // Blur inset shadows ALWAYS have a 0 spread radius.
-  if ((aBlurRadius.width <= 0 && aBlurRadius.height <= 0)) {
+  if ((aBlurRadius.width == 0 && aBlurRadius.height == 0)
+      || aShadowClipRect.IsEmpty()) {
     FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect,
         aShadowColor, aHasBorderRadius, aInnerClipRadii);
     return;
   }
 
-  IntMargin extendDest;
-  IntMargin slice;
-  bool didMoveOffset;
-  RefPtr<SourceSurface> minInsetBlur = GetInsetBlur(extendDest, slice,
-                                                    aDestinationRect, aShadowClipRect,
-                                                    aBlurRadius, aSpreadRadius,
-                                                    aInnerClipRadii, aShadowColor,
-                                                    aHasBorderRadius, aShadowOffset,
-                                                    didMoveOffset, destDrawTarget);
-  if (!minInsetBlur) {
+  DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
+
+  Margin innerMargin;
+  Margin blurMargin;
+  GetBlurMargins(aHasBorderRadius, aInnerClipRadii, aBlurRadius,
+                 blurMargin, innerMargin);
+
+  Rect whitespaceRect;
+  Rect outerRect;
+  bool useDestRect = GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect,
+                                            aDestinationRect, whitespaceRect, outerRect);
+
+  RefPtr<SourceSurface> minBlur = GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor,
+                                               aBlurRadius, aHasBorderRadius, aInnerClipRadii,
+                                               destDrawTarget);
+  if (!minBlur) {
     return;
   }
 
-  Rect srcOuter(Point(), Size(minInsetBlur->GetSize()));
-  Rect srcInner = srcOuter;
-  srcInner.Deflate(Margin(slice));
+  if (useDestRect) {
+    IntSize blurSize = minBlur->GetSize();
+    Rect srcBlur(0, 0, blurSize.width, blurSize.height);
+    Rect destBlur = aDestinationRect;
+
+    // The blur itself expands the rect by the blur margin, so we
+    // have to mimic that here.
+    destBlur.Inflate(blurMargin);
+    MOZ_ASSERT(srcBlur.Size() == destBlur.Size());
+    destDrawTarget->DrawSurface(minBlur, destBlur, srcBlur);
+  } else {
+    Rect srcOuter(outerRect);
+    Rect srcInner(srcOuter);
+    srcInner.Deflate(blurMargin);   // The outer color fill
+    srcInner.Deflate(innerMargin);  // The inner whitespace
 
-  Rect dstOuter(aDestinationRect);
-  if (!didMoveOffset) {
-    dstOuter.MoveBy(aShadowOffset);
-  }
-  dstOuter.Inflate(Margin(extendDest));
-  Rect dstInner = dstOuter;
-  dstInner.Deflate(Margin(slice));
+    // The shadow clip rect already takes into account the spread radius
+    Rect outerFillRect(aShadowClipRect);
+    outerFillRect.Inflate(blurMargin);
+    FillDestinationPath(aDestinationCtx, aDestinationRect, outerFillRect, aShadowColor, false, RectCornerRadii());
 
-  if (dstOuter.Size() == srcOuter.Size()) {
-    destDrawTarget->DrawSurface(minInsetBlur, dstOuter, srcOuter);
-  } else {
-    DrawBoxShadows(*destDrawTarget, minInsetBlur,
-                   dstOuter, dstInner,
+    // Inflate once for the frame around the whitespace
+    Rect destRect(aShadowClipRect);
+    destRect.Inflate(blurMargin);
+
+    // Deflate for the blurred in white space
+    Rect destInnerRect(aShadowClipRect);
+    destInnerRect.Deflate(innerMargin);
+
+    DrawBoxShadows(*destDrawTarget, minBlur,
+                   destRect, destInnerRect,
                    srcOuter, srcInner,
                    aSkipRect);
   }
 }
--- a/gfx/thebes/gfxBlur.h
+++ b/gfx/thebes/gfxBlur.h
@@ -162,27 +162,23 @@ public:
                       const mozilla::gfx::Color& aShadowColor,
                       const bool aHasBorderRadius,
                       const RectCornerRadii& aInnerClipRadii,
                       const mozilla::gfx::Rect aSkipRect,
                       const mozilla::gfx::Point aShadowOffset);
 
 protected:
     already_AddRefed<mozilla::gfx::SourceSurface>
-    GetInsetBlur(mozilla::gfx::IntMargin& aExtendDestBy,
-                 mozilla::gfx::IntMargin& aSlice,
-                 const mozilla::gfx::Rect aDestinationRect,
-                 const mozilla::gfx::Rect aShadowClipRect,
+    GetInsetBlur(const mozilla::gfx::Rect aOuterRect,
+                 const mozilla::gfx::Rect aWhitespaceRect,
+                 const bool aIsDestRect,
+                 const mozilla::gfx::Color& aShadowColor,
                  const mozilla::gfx::IntSize& aBlurRadius,
-                 const mozilla::gfx::IntSize& aSpreadRadius,
+                 const bool aHasBorderRadius,
                  const RectCornerRadii& aInnerClipRadii,
-                 const mozilla::gfx::Color& aShadowColor,
-                 const bool& aHasBorderRadius,
-                 const mozilla::gfx::Point aShadowOffset,
-                 bool& aMovedOffset,
                  DrawTarget* aDestDrawTarget);
 
     /**
      * The context of the temporary alpha surface.
      */
     RefPtr<gfxContext> mContext;
 
     /**
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box-shadow/boxshadow-inset-large-border-radius-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+#ref {
+  background: black;
+  width: 575px;
+  height: 5px;
+  margin-left: 125px;
+}
+
+</style>
+</head>
+<body>
+<div id="ref">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box-shadow/boxshadow-inset-large-border-radius.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+#boxShadow {
+  background: gray;
+  width: 700px;
+  height: 20px;
+  box-shadow: 15px 15px 3px black inset;
+  border-radius: 100px 0px 0px 0px;
+}
+
+#cutLeft {
+  background: white;
+  width: 125px;
+  height: 20px;
+  position: absolute;
+}
+
+#cutAcross {
+  background: white;
+  width: 700px;
+  height: 15px;
+  position: absolute;
+  margin-top: 5px;
+}
+
+</style>
+</head>
+<body>
+<div id="cutLeft"></div>
+<div id="cutAcross"></div>
+<div id="boxShadow"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box-shadow/boxshadow-inset-large-offset-ref.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<style>
+div.test {
+  width: 530px;
+  height: 500px;
+  background-color: black;
+}
+
+div.cover {
+  width: 500px;
+  height: 500px;
+  background-color: black;
+  position: absolute;
+  margin-top: 0px;
+  margin-left: 30px;
+}
+</style>
+<body>
+<div class="test">
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box-shadow/boxshadow-inset-large-offset.html
@@ -0,0 +1,26 @@
+<html>
+<head>
+<style>
+div.test {
+  width: 500px;
+  height: 500px;
+  box-shadow: inset 70px 70px 50px 0px black;
+  position: absolute;
+}
+
+div.cover {
+  width: 500px;
+  height: 500px;
+  background-color: black;
+  position: absolute;
+  margin-top: 0px;
+  margin-left: 30px;
+}
+</style>
+<body>
+<div class="test">
+</div>
+<div class="cover">
+</div>
+</body>
+</html>
--- a/layout/reftests/box-shadow/reftest.list
+++ b/layout/reftests/box-shadow/reftest.list
@@ -22,16 +22,20 @@ random-if(d2d) == boxshadow-threecorners
 == boxshadow-opacity.html boxshadow-opacity-ref.html
 == boxshadow-color-rounding.html boxshadow-color-rounding-ref.html
 == boxshadow-color-rounding-middle.html boxshadow-color-rounding-middle-ref.html
 fuzzy-if(OSX==1010,1,24) fuzzy-if(d2d,16,568) == boxshadow-large-border-radius.html boxshadow-large-border-radius-ref.html # Bug 1209649
 fuzzy-if(d2d,2,1080) == boxshadow-border-radius-int.html boxshadow-border-radius-int-ref.html
 == boxshadow-inset-neg-spread.html about:blank
 == boxshadow-inset-neg-spread2.html boxshadow-inset-neg-spread2-ref.html
 fuzzy(26,3610) == boxshadow-rotated.html boxshadow-rotated-ref.html # Bug 1211264
+== boxshadow-inset-large-border-radius.html boxshadow-inset-large-border-radius-ref.html
+
+# fuzzy due to blur going inside, but as long as it's essentially black instead of a light gray its ok.
+fuzzy(12,9445) == boxshadow-inset-large-offset.html boxshadow-inset-large-offset-ref.html
 
 == overflow-not-scrollable-1.html overflow-not-scrollable-1-ref.html
 == overflow-not-scrollable-1.html overflow-not-scrollable-1-ref2.html
 == overflow-not-scrollable-2.html overflow-not-scrollable-2-ref.html
 fails-if(B2G||Mulet) == 611574-1.html 611574-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 fails-if(B2G||Mulet) == 611574-2.html 611574-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(winWidget,5,30) fuzzy-if(skiaContent,16,10) == fieldset.html fieldset-ref.html # minor anti-aliasing problem on Windows
 fuzzy-if(winWidget,5,30) fuzzy-if(skiaContent,16,10) == fieldset-inset.html fieldset-inset-ref.html # minor anti-aliasing problem on Windows