Bug 1188075 - Speed up inner box-shadow drawing by using a border-image style approach. r=mstange
authorMason Chang <mchang@mozilla.com>
Fri, 18 Sep 2015 11:23:55 -0700
changeset 263320 3c837b3a38fa
parent 263319 f2cc5afecb1f
child 263321 7a41937bf6a2
push id29395
push userphilringnalda@gmail.com
push date2015-09-19 04:34 +0000
treeherdermozilla-central@9f7b7ab7dc1f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1188075
milestone43.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 1188075 - Speed up inner box-shadow drawing by using a border-image style approach. r=mstange
gfx/thebes/gfx2DGlue.h
gfx/thebes/gfxBlur.cpp
gfx/thebes/gfxBlur.h
layout/base/nsCSSRendering.cpp
layout/base/nsCSSRendering.h
layout/reftests/box-shadow/boxshadow-color-rounding-middle-ref.html
layout/reftests/box-shadow/boxshadow-color-rounding-middle.html
layout/reftests/box-shadow/reftest.list
--- a/gfx/thebes/gfx2DGlue.h
+++ b/gfx/thebes/gfx2DGlue.h
@@ -35,17 +35,17 @@ inline Rect ToRect(const IntRect &aRect)
 }
 
 inline Color ToColor(const gfxRGBA &aRGBA)
 {
   return Color(Float(aRGBA.r), Float(aRGBA.g),
                Float(aRGBA.b), Float(aRGBA.a));
 }
 
-inline gfxRGBA ThebesColor(Color &aColor)
+inline gfxRGBA ThebesColor(const Color &aColor)
 {
   return gfxRGBA(aColor.r, aColor.g, aColor.b, aColor.a);
 }
 
 inline Matrix ToMatrix(const gfxMatrix &aMatrix)
 {
   return Matrix(Float(aMatrix._11), Float(aMatrix._12), Float(aMatrix._21),
                 Float(aMatrix._22), Float(aMatrix._31), Float(aMatrix._32));
--- a/gfx/thebes/gfxBlur.cpp
+++ b/gfx/thebes/gfxBlur.cpp
@@ -165,68 +165,109 @@ struct BlurCacheKey : public PLDHashEntr
   typedef const BlurCacheKey* KeyTypePointer;
   enum { ALLOW_MEMMOVE = true };
 
   IntSize mMinSize;
   IntSize mBlurRadius;
   gfxRGBA mShadowColor;
   BackendType mBackend;
   RectCornerRadii mCornerRadii;
+  bool mIsInset;
 
-  BlurCacheKey(IntSize aMinimumSize, gfxIntSize aBlurRadius,
+  // Only used for inset blurs
+  bool mHasBorderRadius;
+  gfxIntSize mSpreadRadius;
+  IntSize mInnerMinSize;
+
+  BlurCacheKey(IntSize aMinSize, gfxIntSize aBlurRadius,
                RectCornerRadii* aCornerRadii, gfxRGBA aShadowColor,
-               BackendType aBackend)
-    : mMinSize(aMinimumSize)
-    , mBlurRadius(aBlurRadius)
-    , mShadowColor(aShadowColor)
-    , mBackend(aBackend)
-    , mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii())
-  { }
+               BackendType aBackendType)
+    : BlurCacheKey(aMinSize, IntSize(0, 0),
+                   aBlurRadius, IntSize(0, 0),
+                   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,
+                        gfxIntSize aBlurRadius, gfxIntSize aSpreadRadius,
+                        const RectCornerRadii* aCornerRadii, gfxRGBA 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);
     hash = AddToHash(hash, aKey->mBlurRadius.width, aKey->mBlurRadius.height);
 
     hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.r, sizeof(gfxFloat)));
     hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.g, sizeof(gfxFloat)));
     hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.b, sizeof(gfxFloat)));
     hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.a, sizeof(gfxFloat)));
 
     for (int i = 0; i < 4; i++) {
-    hash = AddToHash(hash, aKey->mCornerRadii[i].width, aKey->mCornerRadii[i].height);
+      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
+  bool
+  KeyEquals(KeyTypePointer aKey) const
   {
     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);
+      }
+
       return true;
      }
 
      return false;
   }
-  static KeyTypePointer KeyToPointer(KeyType aKey)
+
+  static KeyTypePointer
+  KeyToPointer(KeyType aKey)
   {
     return &aKey;
   }
 };
 
 /**
  * This class is what is cached. It need to be allocated in an object separated
  * to the cache entry to be able to be tracked by the nsExpirationTracker.
@@ -286,16 +327,37 @@ class BlurCache final : public nsExpirat
                                       aBackendType));
       if (blur) {
         MarkUsed(blur);
       }
 
       return blur;
     }
 
+    BlurCacheData* LookupInsetBoxShadow(const IntSize aOuterMinSize,
+                                        const IntSize aInnerMinSize,
+                                        const gfxIntSize& aBlurRadius,
+                                        const gfxIntSize& aSpreadRadius,
+                                        const RectCornerRadii* aCornerRadii,
+                                        const gfxRGBA& aShadowColor,
+                                        const bool& aHasBorderRadius,
+                                        BackendType aBackendType)
+    {
+      BlurCacheKey key(aOuterMinSize, aInnerMinSize,
+                       aBlurRadius, aSpreadRadius,
+                       aCornerRadii, aShadowColor,
+                       true, aHasBorderRadius, aBackendType);
+      BlurCacheData* blur = mHashEntries.Get(key);
+      if (blur) {
+        MarkUsed(blur);
+      }
+
+      return blur;
+    }
+
     // Returns true if we successfully register the blur in the cache, false
     // otherwise.
     bool RegisterEntry(BlurCacheData* aValue)
     {
       nsresult rv = AddObject(aValue);
       if (NS_FAILED(rv)) {
         // We are OOM, and we cannot track this object. We don't want stall
         // entries in the hash table (since the expiration tracker is responsible
@@ -426,33 +488,33 @@ CreateBlurMask(const IntSize& aRectSize,
 
   MOZ_ASSERT(aSliceBorder.LeftRight() <= expandedMinRect.width);
   MOZ_ASSERT(aSliceBorder.TopBottom() <= expandedMinRect.height);
 
   return result.forget();
 }
 
 static already_AddRefed<SourceSurface>
-CreateBoxShadow(DrawTarget& aDT, SourceSurface* aBlurMask, const gfxRGBA& aShadowColor)
+CreateBoxShadow(SourceSurface* aBlurMask, const gfxRGBA& aShadowColor)
 {
   IntSize blurredSize = aBlurMask->GetSize();
   gfxPlatform* platform = gfxPlatform::GetPlatform();
   RefPtr<DrawTarget> boxShadowDT =
     platform->CreateOffscreenContentDrawTarget(blurredSize, SurfaceFormat::B8G8R8A8);
 
   if (!boxShadowDT) {
     return nullptr;
   }
 
   ColorPattern shadowColor(ToDeviceColor(aShadowColor));
   boxShadowDT->MaskSurface(shadowColor, aBlurMask, Point(0, 0));
   return boxShadowDT->Snapshot();
 }
 
-SourceSurface*
+static SourceSurface*
 GetBlur(DrawTarget& aDT,
         const IntSize& aRectSize,
         const gfxIntSize& aBlurRadius,
         RectCornerRadii* aCornerRadii,
         const gfxRGBA& aShadowColor,
         IntMargin& aExtendDestBy,
         IntMargin& aSlice)
 {
@@ -475,17 +537,17 @@ GetBlur(DrawTarget& aDT,
 
   RefPtr<SourceSurface> blurMask =
     CreateBlurMask(aRectSize, aCornerRadii, aBlurRadius, aExtendDestBy, aSlice, aDT);
 
   if (!blurMask) {
     return nullptr;
   }
 
-  RefPtr<SourceSurface> boxShadow = CreateBoxShadow(aDT, blurMask, aShadowColor);
+  RefPtr<SourceSurface> boxShadow = CreateBoxShadow(blurMask, aShadowColor);
   if (!boxShadow) {
     return nullptr;
   }
 
   CacheBlur(aDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, aExtendDestBy, boxShadow);
   return boxShadow;
 }
 
@@ -536,16 +598,78 @@ DrawCorner(DrawTarget& aDT, SourceSurfac
 {
   if (aSkipRect.Contains(aDest)) {
     return;
   }
 
   aDT.DrawSurface(aSurface, aDest, aSrc);
 }
 
+static void
+DrawBoxShadows(DrawTarget& aDestDrawTarget, SourceSurface* aSourceBlur,
+               Rect aDstOuter, Rect aDstInner, Rect aSrcOuter, Rect aSrcInner,
+               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);
+
+  DrawCorner(aDestDrawTarget, aSourceBlur,
+             RectWithEdgesTRBL(aDstOuter.Y(), aDstOuter.XMost(),
+                               aDstInner.Y(), aDstInner.XMost()),
+             RectWithEdgesTRBL(aSrcOuter.Y(), aSrcOuter.XMost(),
+                               aSrcInner.Y(), aSrcInner.XMost()),
+             aSkipRect);
+
+  DrawCorner(aDestDrawTarget, aSourceBlur,
+             RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.X(),
+                               aDstOuter.YMost(), aDstOuter.X()),
+             RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.X(),
+                               aSrcOuter.YMost(), aSrcOuter.X()),
+             aSkipRect);
+
+  DrawCorner(aDestDrawTarget, aSourceBlur,
+             RectWithEdgesTRBL(aDstInner.YMost(), aDstOuter.XMost(),
+                               aDstOuter.YMost(), aDstInner.XMost()),
+             RectWithEdgesTRBL(aSrcInner.YMost(), aSrcOuter.XMost(),
+                               aSrcOuter.YMost(), aSrcInner.XMost()),
+             aSkipRect);
+
+  // Edges: top, left, right, bottom
+  RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
+                         RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(),
+                                           aDstInner.Y(), aDstInner.X()),
+                         RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
+                                           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(),
+                                           aDstOuter.YMost(), aDstInner.X()),
+                         RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.XMost(),
+                                           aSrcOuter.YMost(), aSrcInner.X()),
+                         aSkipRect);
+}
+
+
 /***
  * We draw a blurred a rectangle by only blurring a smaller rectangle and
  * splitting the rectangle into 9 parts.
  * First, a small minimum source rect is calculated and used to create a blur
  * mask since the actual blurring itself is expensive. Next, we use the mask
  * with the given shadow color to create a minimally-sized box shadow of the
  * right color. Finally, we cut out the 9 parts from the box-shadow source and
  * paint each part in the right place, stretching the non-corner parts to fill
@@ -593,70 +717,18 @@ gfxAlphaBoxBlur::BlurRectangle(gfxContex
 
   Rect skipRect = ToRect(aSkipRect);
 
   if (srcInner.IsEqualInterior(srcOuter)) {
     MOZ_ASSERT(dstInner.IsEqualInterior(dstOuter));
     // The target rect is smaller than the minimal size so just draw the surface
     destDrawTarget.DrawSurface(boxShadow, dstInner, srcInner);
   } else {
-    // Corners: top left, top right, bottom left, bottom right
-    DrawCorner(destDrawTarget, boxShadow,
-               RectWithEdgesTRBL(dstOuter.Y(), dstInner.X(),
-                                 dstInner.Y(), dstOuter.X()),
-               RectWithEdgesTRBL(srcOuter.Y(), srcInner.X(),
-                                 srcInner.Y(), srcOuter.X()),
-               skipRect);
-
-    DrawCorner(destDrawTarget, boxShadow,
-               RectWithEdgesTRBL(dstOuter.Y(), dstOuter.XMost(),
-                                 dstInner.Y(), dstInner.XMost()),
-               RectWithEdgesTRBL(srcOuter.Y(), srcOuter.XMost(),
-                                 srcInner.Y(), srcInner.XMost()),
-               skipRect);
-
-    DrawCorner(destDrawTarget, boxShadow,
-               RectWithEdgesTRBL(dstInner.YMost(), dstInner.X(),
-                                 dstOuter.YMost(), dstOuter.X()),
-               RectWithEdgesTRBL(srcInner.YMost(), srcInner.X(),
-                                 srcOuter.YMost(), srcOuter.X()),
-               skipRect);
-
-    DrawCorner(destDrawTarget, boxShadow,
-               RectWithEdgesTRBL(dstInner.YMost(), dstOuter.XMost(),
-                                 dstOuter.YMost(), dstInner.XMost()),
-               RectWithEdgesTRBL(srcInner.YMost(), srcOuter.XMost(),
-                                 srcOuter.YMost(), srcInner.XMost()),
-               skipRect);
-
-    // Edges: top, left, right, bottom
-    RepeatOrStretchSurface(destDrawTarget, boxShadow,
-                           RectWithEdgesTRBL(dstOuter.Y(), dstInner.XMost(),
-                                             dstInner.Y(), dstInner.X()),
-                           RectWithEdgesTRBL(srcOuter.Y(), srcInner.XMost(),
-                                             srcInner.Y(), srcInner.X()),
-                           skipRect);
-    RepeatOrStretchSurface(destDrawTarget, boxShadow,
-                           RectWithEdgesTRBL(dstInner.Y(), dstInner.X(),
-                                             dstInner.YMost(), dstOuter.X()),
-                           RectWithEdgesTRBL(srcInner.Y(), srcInner.X(),
-                                             srcInner.YMost(), srcOuter.X()),
-                           skipRect);
-    RepeatOrStretchSurface(destDrawTarget, boxShadow,
-                           RectWithEdgesTRBL(dstInner.Y(), dstOuter.XMost(),
-                                             dstInner.YMost(), dstInner.XMost()),
-                           RectWithEdgesTRBL(srcInner.Y(), srcOuter.XMost(),
-                                             srcInner.YMost(), srcInner.XMost()),
-                           skipRect);
-    RepeatOrStretchSurface(destDrawTarget, boxShadow,
-                           RectWithEdgesTRBL(dstInner.YMost(), dstInner.XMost(),
-                                             dstOuter.YMost(), dstInner.X()),
-                           RectWithEdgesTRBL(srcInner.YMost(), srcInner.XMost(),
-                                             srcOuter.YMost(), srcInner.X()),
-                           skipRect);
+    DrawBoxShadows(destDrawTarget, boxShadow, dstOuter, dstInner,
+                   srcOuter, srcInner, skipRect);
 
     // Middle part
     RepeatOrStretchSurface(destDrawTarget, boxShadow,
                            RectWithEdgesTRBL(dstInner.Y(), dstInner.XMost(),
                                              dstInner.YMost(), dstInner.X()),
                            RectWithEdgesTRBL(srcInner.Y(), srcInner.XMost(),
                                              srcInner.YMost(), srcInner.X()),
                            skipRect);
@@ -677,8 +749,263 @@ gfxAlphaBoxBlur::BlurRectangle(gfxContex
   // will not just fill the pixels that have their pixel center inside the
   // filled shape. Instead, it will fill all the pixels which are partially
   // covered by the shape. So for pixels on the edge between two adjacent parts,
   // all those pixels will be painted to by both parts, which looks very bad.
 
   destDrawTarget.PopClip();
 }
 
+static already_AddRefed<Path>
+GetBoxShadowInsetPath(DrawTarget* aDrawTarget,
+                      const Rect aOuterRect, const Rect aInnerRect,
+                      const bool aHasBorderRadius, const RectCornerRadii& aInnerClipRadii)
+{
+  /***
+   * We create an inset path by having two rects.
+   *
+   *  -----------------------
+   *  |  ________________   |
+   *  | |                |  |
+   *  | |                |  |
+   *  | ------------------  |
+   *  |_____________________|
+   *
+   * The outer rect and the inside rect. The path
+   * creates a frame around the content where we draw the inset shadow.
+   */
+  RefPtr<PathBuilder> builder =
+    aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
+  AppendRectToPath(builder, aOuterRect, true);
+
+  if (aHasBorderRadius) {
+    AppendRoundedRectToPath(builder, aInnerRect, aInnerClipRadii, false);
+  } else {
+    AppendRectToPath(builder, aInnerRect, false);
+  }
+  return builder->Finish();
+}
+
+static void
+ComputeRectsForInsetBoxShadow(gfxIntSize aBlurRadius,
+                              gfxIntSize aSpreadRadius,
+                              const Rect& aDestRect,
+                              const Rect& aShadowClipRect,
+                              Rect& aOutOuterRect,
+                              Rect& aOutInnerRect,
+                              Margin& aOutPathMargins)
+{
+  gfxIntSize marginSize = aBlurRadius + aSpreadRadius;
+  // The sizes we're given for aBlurRadius/aSpreadRadius are radius'.
+  // We actually want to paint the whole blur, so we need the diameter.
+  // We render both the outer / inner blur portions of a blur,
+  // Then we clip out the outer portion later.
+  aOutPathMargins.SizeTo(marginSize.height, marginSize.width, marginSize.height, marginSize.width);
+  aOutPathMargins += aOutPathMargins;
+
+  aOutOuterRect.x = 0;
+  aOutInnerRect.x = marginSize.width;
+
+  aOutOuterRect.y = 0;
+  aOutInnerRect.y = marginSize.height;
+
+  // + 1 for the middle edges so we can sample them
+  aOutInnerRect.width = aOutPathMargins.LeftRight() + 1;
+  aOutInnerRect.height = aOutPathMargins.TopBottom() + 1;
+
+  // The outer path rect needs to be 1 blur radius past the inner edges
+  aOutOuterRect.width = aOutInnerRect.XMost() + marginSize.width;
+  aOutOuterRect.height = aOutInnerRect.YMost() + marginSize.height;
+
+  if ((aOutOuterRect.width >= aDestRect.width) ||
+      (aOutOuterRect.height >= aDestRect.height) ||
+      (aOutInnerRect.width >= aShadowClipRect.width) ||
+      (aOutInnerRect.height >= aShadowClipRect.height))
+  {
+    aOutOuterRect.width = aDestRect.width;
+    aOutOuterRect.height = aDestRect.height;
+    aOutInnerRect.width = aShadowClipRect.width;
+    aOutInnerRect.height = aShadowClipRect.height;
+    aOutPathMargins.SizeTo(0, 0, 0, 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
+  // surface.
+  aDestinationCtx->SetColor(ThebesColor(aShadowColor));
+  DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
+  RefPtr<Path> shadowPath = GetBoxShadowInsetPath(destDrawTarget, aDestinationRect,
+                                                  aShadowClipRect, aHasBorderRadius,
+                                                  aInnerClipRadii);
+
+  aDestinationCtx->SetPath(shadowPath);
+  aDestinationCtx->Fill();
+}
+
+void
+CacheInsetBlur(const IntSize aMinOuterSize,
+               const IntSize aMinInnerSize,
+               const gfxIntSize& aBlurRadius,
+               const gfxIntSize& aSpreadRadius,
+               const RectCornerRadii* aCornerRadii,
+               const gfxRGBA& aShadowColor,
+               const bool& aHasBorderRadius,
+               BackendType aBackendType,
+               IntMargin aExtendBy,
+               SourceSurface* aBoxShadow)
+{
+  BlurCacheKey key(aMinOuterSize, aMinInnerSize,
+                   aBlurRadius, aSpreadRadius,
+                   aCornerRadii, aShadowColor,
+                   true, aHasBorderRadius, aBackendType);
+  BlurCacheData* data = new BlurCacheData(aBoxShadow, aExtendBy, key);
+  if (!gBlurCache->RegisterEntry(data)) {
+    delete data;
+  }
+}
+
+already_AddRefed<mozilla::gfx::SourceSurface>
+gfxAlphaBoxBlur::GetInsetBlur(Rect& aOuterRect,
+                              Rect& aInnerRect,
+                              const gfxIntSize& aBlurRadius,
+                              const gfxIntSize& aSpreadRadius,
+                              const RectCornerRadii& aInnerClipRadii,
+                              const Color& aShadowColor,
+                              const bool& aHasBorderRadius,
+                              IntPoint& aOutTopLeft,
+                              gfxContext* aDestinationCtx)
+
+{
+  if (!gBlurCache) {
+    gBlurCache = new BlurCache();
+  }
+
+  gfxIntSize outerRectSize = RoundedToInt(aOuterRect).Size();
+  gfxIntSize innerRectSize = RoundedToInt(aInnerRect).Size();
+  DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
+
+  BlurCacheData* cached =
+      gBlurCache->LookupInsetBoxShadow(outerRectSize, innerRectSize, aBlurRadius, aSpreadRadius,
+                                       &aInnerClipRadii, ThebesColor(aShadowColor),
+                                       aHasBorderRadius, destDrawTarget->GetBackendType());
+
+  if (cached) {
+    IntMargin extends = cached->mExtendDest;
+    aOutTopLeft.x = extends.left;
+    aOutTopLeft.y = extends.top;
+    // 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.
+  gfxIntSize zeroSpread(0, 0);
+  gfxContext* minGfxContext = Init(ThebesRect(aOuterRect), zeroSpread, aBlurRadius, nullptr, nullptr);
+  if (!minGfxContext) {
+    return nullptr;
+  }
+
+  DrawTarget* minDrawTarget = minGfxContext->GetDrawTarget();
+  RefPtr<Path> maskPath = GetBoxShadowInsetPath(minDrawTarget, aOuterRect,
+                                                aInnerRect, aHasBorderRadius,
+                                                aInnerClipRadii);
+
+  minGfxContext->SetColor(ThebesColor(aShadowColor));
+  minGfxContext->SetPath(maskPath);
+  minGfxContext->Fill();
+
+  RefPtr<SourceSurface> minMask = DoBlur(minDrawTarget, &aOutTopLeft);
+  if (!minMask) {
+    return nullptr;
+  }
+
+  RefPtr<SourceSurface> minInsetBlur = CreateBoxShadow(minMask, ThebesColor(aShadowColor));
+  if (!minInsetBlur) {
+    return nullptr;
+  }
+
+  IntMargin extendBy(aOutTopLeft.y, 0, 0, aOutTopLeft.x);
+  CacheInsetBlur(outerRectSize, innerRectSize,
+                 aBlurRadius, aSpreadRadius,
+                 &aInnerClipRadii, ThebesColor(aShadowColor),
+                 aHasBorderRadius, destDrawTarget->GetBackendType(),
+                 extendBy, 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.
+ *
+ * All parameters should already be in device pixels.
+ */
+void
+gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx,
+                              const Rect aDestinationRect,
+                              const Rect aShadowClipRect,
+                              const gfxIntSize aBlurRadius,
+                              const gfxIntSize aSpreadRadius,
+                              const Color& aShadowColor,
+                              bool aHasBorderRadius,
+                              const RectCornerRadii& aInnerClipRadii,
+                              const Rect aSkipRect)
+{
+  if ((aBlurRadius.width <= 0 && aBlurRadius.height <= 0)) {
+    // The outer path must be rounded out
+    // If not blurring, we're done now.
+    Rect pathRect(aDestinationRect);
+    pathRect.RoundOut();
+    FillDestinationPath(aDestinationCtx, pathRect, aShadowClipRect,
+        aShadowColor, aHasBorderRadius, aInnerClipRadii);
+    return;
+  }
+
+  DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
+  Rect outerRect;
+  Rect innerRect;
+  Margin pathMargins;
+  ComputeRectsForInsetBoxShadow(aBlurRadius, aSpreadRadius,
+                                aDestinationRect, aShadowClipRect,
+                                outerRect, innerRect,
+                                pathMargins);
+  IntPoint topLeft;
+  RefPtr<SourceSurface> minInsetBlur = GetInsetBlur(outerRect, innerRect,
+                                                    aBlurRadius, aSpreadRadius,
+                                                    aInnerClipRadii, aShadowColor,
+                                                    aHasBorderRadius,
+                                                    topLeft, aDestinationCtx);
+  if (!minInsetBlur) {
+    return;
+  }
+
+  Rect destRectOuter(aDestinationRect);
+  destRectOuter.RoundIn();
+  Rect destRectInner(destRectOuter);
+  destRectInner.Deflate(pathMargins);
+
+  Rect srcRectOuter(outerRect);
+  srcRectOuter.MoveBy(abs(topLeft.x), abs(topLeft.y));
+  Rect srcRectInner(srcRectOuter);
+  srcRectInner.Deflate(pathMargins);
+
+  if (srcRectOuter.IsEqualInterior(srcRectInner)) {
+    destDrawTarget->DrawSurface(minInsetBlur, destRectOuter, srcRectOuter);
+  } else {
+    DrawBoxShadows(*destDrawTarget, minInsetBlur,
+                   destRectOuter, destRectInner,
+                   srcRectOuter, srcRectInner,
+                   aSkipRect);
+ }
+}
--- a/gfx/thebes/gfxBlur.h
+++ b/gfx/thebes/gfxBlur.h
@@ -15,16 +15,17 @@
 
 class gfxContext;
 struct gfxRect;
 struct gfxRGBA;
 
 namespace mozilla {
   namespace gfx {
     class AlphaBoxBlur;
+    struct Color;
     struct RectCornerRadii;
     class SourceSurface;
     class DrawTarget;
   } // namespace gfx
 } // namespace mozilla
 
 /**
  * Implementation of a triple box blur approximation of a Gaussian blur.
@@ -130,19 +131,54 @@ public:
                               RectCornerRadii* aCornerRadii,
                               const gfxPoint& aBlurStdDev,
                               const gfxRGBA& aShadowColor,
                               const gfxRect& aDirtyRect,
                               const gfxRect& aSkipRect);
 
     static void ShutdownBlurCache();
 
-
+    /***
+     * Blurs an inset box shadow according to a given path.
+     * This is equivalent to calling Init(), drawing the inset path,
+     * and calling paint. Do not call Init() if using this method.
+     *
+     * @param aDestinationCtx     The destination to blur to.
+     * @param aDestinationRect    The destination rect in device pixels
+     * @param aShadowClipRect     The destiniation inner rect of the
+     *                            inset path in device pixels.
+     * @param aBlurRadius         The standard deviation of the blur.
+     * @param aSpreadRadius       The spread radius in device pixels.
+     * @param aShadowColor        The color of the blur.
+     * @param aHasBorderRadius    If this element also has a border radius
+     * @param aInnerClipRadii     Corner radii for the inside rect if it is a rounded rect.
+     * @param aSkipRect           An area in device pixels we don't have to paint in.
+     */
+    void BlurInsetBox(gfxContext* aDestinationCtx,
+                      const mozilla::gfx::Rect aDestinationRect,
+                      const mozilla::gfx::Rect aShadowClipRect,
+                      const gfxIntSize aBlurRadius,
+                      const gfxIntSize aSpreadRadius,
+                      const mozilla::gfx::Color& aShadowColor,
+                      const bool aHasBorderRadius,
+                      const RectCornerRadii& aInnerClipRadii,
+                      const mozilla::gfx::Rect aSkipRect);
 
 protected:
+    already_AddRefed<mozilla::gfx::SourceSurface>
+                   GetInsetBlur(mozilla::gfx::Rect& aOuterRect,
+                                mozilla::gfx::Rect& aInnerRect,
+                                const gfxIntSize& aBlurRadius,
+                                const gfxIntSize& aSpreadRadius,
+                                const RectCornerRadii& aInnerClipRadii,
+                                const mozilla::gfx::Color& aShadowColor,
+                                const bool& aHasBorderRadius,
+                                mozilla::gfx::IntPoint& aOutTopLeft,
+                                gfxContext* aDestinationCtx);
+
     /**
      * The context of the temporary alpha surface.
      */
     nsRefPtr<gfxContext> mContext;
 
     /**
      * The temporary alpha surface.
      */
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -1576,63 +1576,47 @@ nsCSSRendering::PaintBoxShadowInner(nsPr
 
     // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
     // unchanged. And by construction the gfxSkipRect is not touched by the
     // rendered shadow (even after blurring), so those pixels must be completely
     // transparent in the shadow, so drawing them changes nothing.
     gfxContext* renderContext = aRenderingContext.ThebesContext();
     DrawTarget* drawTarget = renderContext->GetDrawTarget();
     nsContextBoxBlur blurringArea;
-    gfxContext* shadowContext =
-      blurringArea.Init(shadowPaintRect, 0, blurRadius, twipsPerPixel,
-                        renderContext, aDirtyRect, &skipGfxRect);
-    if (!shadowContext)
-      continue;
-    DrawTarget* shadowDT = shadowContext->GetDrawTarget();
-
-    // shadowContext is owned by either blurringArea or aRenderingContext.
-    MOZ_ASSERT(shadowContext == renderContext ||
-               shadowContext == blurringArea.GetContext());
-
-    // Set the shadow color; if not specified, use the foreground color
-    Color shadowColor = Color::FromABGR(shadowItem->mHasColor ?
-                                          shadowItem->mColor :
-                                          aForFrame->StyleColor()->mColor);
-    renderContext->Save();
-    renderContext->SetColor(ThebesColor(shadowColor));
 
     // Clip the context to the area of the frame's padding rect, so no part of the
     // shadow is painted outside. Also cut out anything beyond where the inset shadow
     // will be.
     Rect shadowGfxRect = NSRectToRect(paddingRect, twipsPerPixel);
     shadowGfxRect.Round();
+
+    // Set the shadow color; if not specified, use the foreground color
+    Color shadowColor = Color::FromABGR(shadowItem->mHasColor ?
+                                          shadowItem->mColor :
+                                          aForFrame->StyleColor()->mColor);
+
+    renderContext->Save();
+
+    // This clips the outside border radius.
+    // clipRectRadii is the border radius inside the inset shadow.
     if (hasBorderRadius) {
       RefPtr<Path> roundedRect =
         MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
       renderContext->Clip(roundedRect);
     } else {
       renderContext->Clip(shadowGfxRect);
     }
 
-    // Fill the surface minus the area within the frame that we should
-    // not paint in, and blur and apply it.
-    RefPtr<PathBuilder> builder =
-      shadowDT->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
-    AppendRectToPath(builder, shadowPaintGfxRect, true);
-    if (hasBorderRadius) {
-      AppendRoundedRectToPath(builder, shadowClipGfxRect, clipRectRadii, false);
-    } else {
-      AppendRectToPath(builder, shadowClipGfxRect, false);
-    }
-    RefPtr<Path> path = builder->Finish();
-    shadowContext->SetPath(path);
-    shadowContext->Fill();
-    shadowContext->NewPath();
-
-    blurringArea.DoPaint();
+    nsContextBoxBlur insetBoxBlur;
+    gfxRect destRect = nsLayoutUtils::RectToGfxRect(shadowPaintRect, twipsPerPixel);
+    insetBoxBlur.InsetBoxBlur(renderContext, ToRect(destRect),
+                              shadowClipGfxRect, shadowColor,
+                              blurRadius, spreadDistanceAppUnits,
+                              twipsPerPixel, hasBorderRadius,
+                              clipRectRadii, ToRect(skipGfxRect));
     renderContext->Restore();
   }
 }
 
 DrawResult
 nsCSSRendering::PaintBackground(nsPresContext* aPresContext,
                                 nsRenderingContext& aRenderingContext,
                                 nsIFrame* aForFrame,
@@ -5318,37 +5302,22 @@ nsContextBoxBlur::Init(const nsRect& aRe
                        const gfxRect* aSkipRect,
                        uint32_t aFlags)
 {
   if (aRect.IsEmpty()) {
     mContext = nullptr;
     return nullptr;
   }
 
-  gfxFloat scaleX = 1;
-  gfxFloat scaleY = 1;
-
-  // Do blurs in device space when possible.
-  // Chrome/Skia always does the blurs in device space
-  // and will sometimes get incorrect results (e.g. rotated blurs)
-  gfxMatrix transform = aDestinationCtx->CurrentMatrix();
-  // XXX: we could probably handle negative scales but for now it's easier just to fallback
-  if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || transform._22 <= 0.0) {
-    transform = gfxMatrix();
-  } else {
-    scaleX = transform._11;
-    scaleY = transform._22;
-  }
-
-  // compute a large or smaller blur radius
-  gfxIntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
-  gfxIntSize spreadRadius = gfxIntSize(std::min(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
-                                              int32_t(MAX_SPREAD_RADIUS)),
-                                       std::min(int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel),
-                                              int32_t(MAX_SPREAD_RADIUS)));
+  gfxIntSize blurRadius;
+  gfxIntSize spreadRadius;
+  GetBlurAndSpreadRadius(aDestinationCtx, aAppUnitsPerDevPixel,
+                         aBlurRadius, aSpreadRadius,
+                         blurRadius, spreadRadius);
+
   mDestinationCtx = aDestinationCtx;
 
   // If not blurring, draw directly onto the destination device
   if (blurRadius.width <= 0 && blurRadius.height <= 0 &&
       spreadRadius.width <= 0 && spreadRadius.height <= 0 &&
       !(aFlags & FORCE_MASK)) {
     mContext = aDestinationCtx;
     return mContext;
@@ -5356,16 +5325,17 @@ nsContextBoxBlur::Init(const nsRect& aRe
 
   // Convert from app units to device pixels
   gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
 
   gfxRect dirtyRect =
     nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
   dirtyRect.RoundOut();
 
+  gfxMatrix transform = aDestinationCtx->CurrentMatrix();
   rect = transform.TransformBounds(rect);
 
   mPreTransformed = !transform.IsIdentity();
 
   // Create the temporary surface for blurring
   dirtyRect = transform.TransformBounds(dirtyRect);
   if (aSkipRect) {
     gfxRect skipRect = transform.TransformBounds(*aSkipRect);
@@ -5382,18 +5352,19 @@ nsContextBoxBlur::Init(const nsRect& aRe
     mContext->Multiply(transform);
   }
   return mContext;
 }
 
 void
 nsContextBoxBlur::DoPaint()
 {
-  if (mContext == mDestinationCtx)
+  if (mContext == mDestinationCtx) {
     return;
+  }
 
   gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
 
   if (mPreTransformed) {
     mDestinationCtx->SetMatrix(gfxMatrix());
   }
 
   mAlphaBoxBlur.Paint(mDestinationCtx);
@@ -5481,8 +5452,104 @@ nsContextBoxBlur::BlurRectangle(gfxConte
   gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx,
                                  shadowThebesRect,
                                  aCornerRadii,
                                  blurStdDev,
                                  aShadowColor,
                                  dirtyRect,
                                  skipRect);
 }
+
+/* static */ void
+nsContextBoxBlur::GetBlurAndSpreadRadius(gfxContext* aDestinationCtx,
+                                         int32_t aAppUnitsPerDevPixel,
+                                         nscoord aBlurRadius,
+                                         nscoord aSpreadRadius,
+                                         gfxIntSize& aOutBlurRadius,
+                                         gfxIntSize& aOutSpreadRadius,
+                                         bool aConstrainSpreadRadius)
+{
+  gfxFloat scaleX = 1;
+  gfxFloat scaleY = 1;
+
+  // Do blurs in device space when possible.
+  // Chrome/Skia always does the blurs in device space
+  // and will sometimes get incorrect results (e.g. rotated blurs)
+  gfxMatrix transform = aDestinationCtx->CurrentMatrix();
+  // XXX: we could probably handle negative scales but for now it's easier just to fallback
+  if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || transform._22 <= 0.0) {
+    transform = gfxMatrix();
+  } else {
+    scaleX = transform._11;
+    scaleY = transform._22;
+  }
+
+  // compute a large or smaller blur radius
+  aOutBlurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
+  aOutSpreadRadius =
+      gfxIntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
+                 int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel));
+
+
+  if (aConstrainSpreadRadius) {
+    aOutSpreadRadius.width = std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS));
+    aOutSpreadRadius.height = std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS));
+  }
+}
+
+/* static */ bool
+nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx,
+                               Rect aDestinationRect,
+                               Rect aShadowClipRect,
+                               Color& aShadowColor,
+                               nscoord aBlurRadiusAppUnits,
+                               nscoord aSpreadDistanceAppUnits,
+                               int32_t aAppUnitsPerDevPixel,
+                               bool aHasBorderRadius,
+                               RectCornerRadii& aInnerClipRectRadii,
+                               Rect aSkipRect)
+{
+  if (aDestinationRect.IsEmpty()) {
+    mContext = nullptr;
+    return false;
+  }
+
+  gfxIntSize blurRadius;
+  gfxIntSize spreadRadius;
+  // Convert the blur and spread radius to device pixels
+  bool constrainSpreadRadius = false;
+  GetBlurAndSpreadRadius(aDestinationCtx, aAppUnitsPerDevPixel,
+                         aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
+                         blurRadius, spreadRadius, constrainSpreadRadius);
+
+  // The blur and spread radius are scaled already, so scale all
+  // input data to the blur. This way, we don't have to scale the min
+  // inset blur to the invert of the dest context, then rescale it back
+  // when we draw to the destination surface.
+  gfxSize scale = aDestinationCtx->CurrentMatrix().ScaleFactors(true);
+  Matrix currentMatrix = ToMatrix(aDestinationCtx->CurrentMatrix());
+
+  Rect transformedDestRect = currentMatrix.TransformBounds(aDestinationRect);
+  Rect transformedShadowClipRect = currentMatrix.TransformBounds(aShadowClipRect);
+  Rect transformedSkipRect = currentMatrix.TransformBounds(aSkipRect);
+
+  transformedDestRect.RoundIn();
+  transformedShadowClipRect.Round();
+  transformedSkipRect.RoundIn();
+
+  for (size_t i = 0; i < 4; i++) {
+    aInnerClipRectRadii[i].width = std::floor(scale.width * aInnerClipRectRadii[i].width);
+    aInnerClipRectRadii[i].height = std::floor(scale.height * aInnerClipRectRadii[i].height);
+  }
+
+  {
+    gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
+    aDestinationCtx->SetMatrix(gfxMatrix());
+
+    mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
+                               transformedShadowClipRect,
+                               blurRadius, spreadRadius,
+                               aShadowColor,
+                               aHasBorderRadius,
+                               aInnerClipRectRadii, transformedSkipRect);
+  }
+  return true;
+}
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -20,16 +20,17 @@
 class gfxDrawable;
 class nsStyleContext;
 class nsPresContext;
 class nsRenderingContext;
 
 namespace mozilla {
 
 namespace gfx {
+struct Color;
 class DrawTarget;
 } // namespace gfx
 
 namespace layers {
 class ImageContainer;
 } // namespace layers
 
 // A CSSSizeOrRatio represents a (possibly partially specified) size for use
@@ -939,20 +940,60 @@ public:
                             const nsRect& aRect,
                             int32_t aAppUnitsPerDevPixel,
                             RectCornerRadii* aCornerRadii,
                             nscoord aBlurRadius,
                             const gfxRGBA& aShadowColor,
                             const nsRect& aDirtyRect,
                             const gfxRect& aSkipRect);
 
+  /**
+   * Draws a blurred inset box shadow shape onto the destination surface.
+   * Like BlurRectangle, this is equivalent to calling Init(),
+   * drawing a rectangle onto the returned surface
+   * and then calling DoPaint, but may let us optimize better in the
+   * backend.
+   *
+   * @param aDestinationCtx      The destination to blur to.
+   * @param aDestinationRect     The rectangle to blur in app units.
+   * @param aShadowClipRect      The inside clip rect that creates the path.
+   * @param aShadowColor         The color of the blur
+   * @param aBlurRadiusAppUnits  The blur radius in app units
+   * @param aSpreadRadiusAppUnits The spread radius in app units.
+   * @param aAppUnitsPerDevPixel The number of app units in a device pixel,
+   *                             for conversion.  Most of the time you'll
+   *                             pass this from the current PresContext if
+   *                             available.
+   * @param aHasBorderRadius     If this inset box blur has a border radius
+   * @param aInnerClipRectRadii  The clip rect radii used for the inside rect's path.
+   * @param aSkipRect            An area in device pixels (NOT app units!) to avoid
+   *                             blurring over, to prevent unnecessary work.
+   */
+  bool InsetBoxBlur(gfxContext* aDestinationCtx,
+                    mozilla::gfx::Rect aDestinationRect,
+                    mozilla::gfx::Rect aShadowClipRect,
+                    mozilla::gfx::Color& aShadowColor,
+                    nscoord aBlurRadiusAppUnits,
+                    nscoord aSpreadRadiusAppUnits,
+                    int32_t aAppUnitsPerDevPixel,
+                    bool aHasBorderRadius,
+                    RectCornerRadii& aInnerClipRectRadii,
+                    mozilla::gfx::Rect aSkipRect);
+
 protected:
+  static void GetBlurAndSpreadRadius(gfxContext* aContext,
+                                     int32_t aAppUnitsPerDevPixel,
+                                     nscoord aBlurRadius,
+                                     nscoord aSpreadRadius,
+                                     gfxIntSize& aOutBlurRadius,
+                                     gfxIntSize& aOutSpreadRadius,
+                                     bool aConstrainSpreadRadius = true);
+
   gfxAlphaBoxBlur mAlphaBoxBlur;
   nsRefPtr<gfxContext> mContext;
   gfxContext* mDestinationCtx;
 
   /* This is true if the blur already has it's content transformed
    * by mDestinationCtx's transform */
   bool mPreTransformed;
-
 };
 
 #endif /* nsCSSRendering_h___ */
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box-shadow/boxshadow-color-rounding-middle-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<style>
+#outerDiv {
+  width: 500px;
+  height: 500px;
+  background: lime;
+  position: absolute;
+}
+
+#middleBlur {
+  width: 300px;
+  height: 300px;
+  margin-left: 100px;
+  margin-top: 100px;
+  background: black;
+  box-shadow: inset 0 0 20px 100px lime;
+}
+</style>
+
+<div id="outerDiv">
+  <div id="middleBlur">
+  </div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box-shadow/boxshadow-color-rounding-middle.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<style>
+#thediv {
+  width: 500px;
+  height: 500px;
+  background: black;
+  box-shadow: inset 0 0 20px 200px lime;
+}
+</style>
+
+<div id="thediv"></div>
--- a/layout/reftests/box-shadow/reftest.list
+++ b/layout/reftests/box-shadow/reftest.list
@@ -16,16 +16,17 @@ random-if(layersGPUAccelerated) == boxsh
 random-if(d2d) fuzzy-if(B2G,12,18) == boxshadow-rounded-spread.html boxshadow-rounded-spread-ref.html
 skip-if((B2G&&browserIsRemote)||Mulet) HTTP(..) == boxshadow-dynamic.xul boxshadow-dynamic-ref.xul # Initial mulet triage: parity with B2G/B2G Desktop
 random-if(d2d) == boxshadow-onecorner.html boxshadow-onecorner-ref.html
 random-if(d2d) == boxshadow-twocorners.html boxshadow-twocorners-ref.html
 random-if(d2d) == boxshadow-threecorners.html boxshadow-threecorners-ref.html
 == boxshadow-skiprect.html boxshadow-skiprect-ref.html
 == 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
 
 == 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) == fieldset.html fieldset-ref.html # minor anti-aliasing problem on Windows
 fuzzy-if(winWidget,5,30) == fieldset-inset.html fieldset-inset-ref.html # minor anti-aliasing problem on Windows