/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "gfxBlur.h"

#include "gfx2DGlue.h"
#include "gfxContext.h"
#include "gfxPlatform.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Blur.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/Maybe.h"
#include "nsExpirationTracker.h"
#include "nsClassHashtable.h"
#include "gfxUtils.h"
#include <cmath>

using namespace mozilla;
using namespace mozilla::gfx;

gfxGaussianBlur::~gfxGaussianBlur() = default;

UniquePtr<gfxContext> gfxGaussianBlur::Init(
    gfxContext* aDestinationCtx, const gfxRect& aRect,
    const IntSize& aSpreadRadius, const Point& aBlurSigma,
    const gfxRect* aDirtyRect, const gfxRect* aSkipRect, bool aClamp) {
  DrawTarget* refDT = aDestinationCtx->GetDrawTarget();
  Maybe<Rect> dirtyRect = aDirtyRect ? Some(ToRect(*aDirtyRect)) : Nothing();
  Maybe<Rect> skipRect = aSkipRect ? Some(ToRect(*aSkipRect)) : Nothing();
  RefPtr<DrawTarget> dt =
      InitDrawTarget(refDT, ToRect(aRect), aSpreadRadius, aBlurSigma,
                     dirtyRect.ptrOr(nullptr), skipRect.ptrOr(nullptr), aClamp);
  if (!dt || !dt->IsValid()) {
    return nullptr;
  }

  auto context = MakeUnique<gfxContext>(dt);
  context->SetMatrix(Matrix::Translation(-mBlur.GetRect().TopLeft()));
  return context;
}

UniquePtr<gfxContext> gfxGaussianBlur::Init(
    gfxContext* aDestinationCtx, const gfxRect& aRect,
    const IntSize& aSpreadRadius, const IntSize& aBlurRadius,
    const gfxRect* aDirtyRect, const gfxRect* aSkipRect, bool aClamp) {
  return Init(aDestinationCtx, aRect, aSpreadRadius,
              CalculateBlurSigma(aBlurRadius), aDirtyRect, aSkipRect, aClamp);
}

already_AddRefed<DrawTarget> gfxGaussianBlur::InitDrawTarget(
    const DrawTarget* aReferenceDT, const Rect& aRect,
    const IntSize& aSpreadRadius, const Point& aBlurSigma,
    const Rect* aDirtyRect, const Rect* aSkipRect, bool aClamp) {
  mBlur.Init(aRect, aSpreadRadius, aBlurSigma, aDirtyRect, aSkipRect,
             SurfaceFormat::A8, aClamp);
  size_t blurDataSize = mBlur.GetSurfaceAllocationSize();
  if (blurDataSize == 0) {
    return nullptr;
  }

  BackendType backend = aReferenceDT->GetBackendType();

  // Make an alpha-only surface to draw on. We will play with the data after
  // everything is drawn to create a blur effect.
  // This will be freed when the DrawTarget dies
  mData = static_cast<uint8_t*>(calloc(1, blurDataSize));
  if (!mData) {
    return nullptr;
  }
  mDrawTarget =
      Factory::DoesBackendSupportDataDrawtarget(backend)
          ? Factory::CreateDrawTargetForData(backend, mData, mBlur.GetSize(),
                                             mBlur.GetStride(),
                                             mBlur.GetFormat())
          : gfxPlatform::CreateDrawTargetForData(
                mData, mBlur.GetSize(), mBlur.GetStride(), mBlur.GetFormat());

  if (!mDrawTarget || !mDrawTarget->IsValid()) {
    if (mData) {
      free(mData);
    }

    return nullptr;
  }

  if (mData) {
    mDrawTarget->AddUserData(reinterpret_cast<UserDataKey*>(mDrawTarget.get()),
                             mData, free);
  }

  mDrawTarget->SetTransform(Matrix::Translation(-mBlur.GetRect().TopLeft()));
  return do_AddRef(mDrawTarget);
}

already_AddRefed<SourceSurface> gfxGaussianBlur::DoBlur(
    const sRGBColor* aShadowColor, IntPoint* aOutTopLeft) {
  if (aOutTopLeft) {
    *aOutTopLeft = mBlur.GetRect().TopLeft();
  }

  RefPtr<SourceSurface> blurMask;
  if (mData) {
    mDrawTarget->Blur(mBlur);
    blurMask = mDrawTarget->Snapshot();
  }

  if (!aShadowColor) {
    return blurMask.forget();
  }

  RefPtr<DrawTarget> shadowDT = mDrawTarget->CreateSimilarDrawTarget(
      blurMask->GetSize(), SurfaceFormat::B8G8R8A8);
  if (!shadowDT) {
    return nullptr;
  }
  ColorPattern shadowColor(ToDeviceColor(*aShadowColor));
  shadowDT->MaskSurface(shadowColor, blurMask, Point(0, 0));

  return shadowDT->Snapshot();
}

void gfxGaussianBlur::Paint(gfxContext* aDestinationCtx) {
  if (mDrawTarget && !mData) {
    return;
  }

  DrawTarget* dest = aDestinationCtx->GetDrawTarget();
  if (!dest) {
    NS_WARNING("Blurring not supported for Thebes contexts!");
    return;
  }

  RefPtr<gfxPattern> thebesPat = aDestinationCtx->GetPattern();
  Pattern* pat = thebesPat->GetPattern(dest, nullptr);
  if (!pat) {
    NS_WARNING("Failed to get pattern for blur!");
    return;
  }

  IntPoint topLeft;
  RefPtr<SourceSurface> mask = DoBlur(nullptr, &topLeft);
  if (!mask) {
    NS_ERROR("Failed to create mask!");
    return;
  }

  // Avoid a semi-expensive clip operation if we can, otherwise
  // clip to the dirty rect
  Rect* dirtyRect = mBlur.GetDirtyRect();
  if (dirtyRect) {
    dest->PushClipRect(*dirtyRect);
  }

  Matrix oldTransform = dest->GetTransform();
  Matrix newTransform = oldTransform;
  newTransform.PreTranslate(topLeft);
  dest->SetTransform(newTransform);

  dest->MaskSurface(*pat, mask, Point(0, 0));

  dest->SetTransform(oldTransform);

  if (dirtyRect) {
    dest->PopClip();
  }
}

IntSize gfxGaussianBlur::CalculateBlurRadius(const gfxPoint& aStd) {
  Point std(Float(aStd.x), Float(aStd.y));
  IntSize size = GaussianBlur::CalculateBlurRadius(std);
  return IntSize(size.width, size.height);
}

Point gfxGaussianBlur::CalculateBlurSigma(const IntSize& aBlurRadius) {
  return Point(GaussianBlur::CalculateBlurSigma(aBlurRadius.width),
               GaussianBlur::CalculateBlurSigma(aBlurRadius.height));
}

struct BlurCacheKey : public PLDHashEntryHdr {
  typedef const BlurCacheKey& KeyType;
  typedef const BlurCacheKey* KeyTypePointer;
  enum { ALLOW_MEMMOVE = true };

  IntSize mMinSize;
  IntSize mBlurRadius;
  sRGBColor mShadowColor;
  BackendType mBackend;
  RectCornerRadii mCornerRadii;
  bool mIsInset;

  // Only used for inset blurs
  IntSize mInnerMinSize;

  BlurCacheKey(const IntSize& aMinSize, const IntSize& aBlurRadius,
               const RectCornerRadii* aCornerRadii,
               const sRGBColor& aShadowColor, BackendType aBackendType)
      : BlurCacheKey(aMinSize, IntSize(0, 0), aBlurRadius, aCornerRadii,
                     aShadowColor, 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),
        mInnerMinSize(aOther->mInnerMinSize) {}

  explicit BlurCacheKey(const IntSize& aOuterMinSize,
                        const IntSize& aInnerMinSize,
                        const IntSize& aBlurRadius,
                        const RectCornerRadii* aCornerRadii,
                        const sRGBColor& aShadowColor, bool aIsInset,
                        BackendType aBackendType)
      : mMinSize(aOuterMinSize),
        mBlurRadius(aBlurRadius),
        mShadowColor(aShadowColor),
        mBackend(aBackendType),
        mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii()),
        mIsInset(aIsInset),
        mInnerMinSize(aInnerMinSize) {}

  BlurCacheKey(BlurCacheKey&&) = default;

  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(aKey->mShadowColor.r)));
    hash = AddToHash(
        hash, HashBytes(&aKey->mShadowColor.g, sizeof(aKey->mShadowColor.g)));
    hash = AddToHash(
        hash, HashBytes(&aKey->mShadowColor.b, sizeof(aKey->mShadowColor.b)));
    hash = AddToHash(
        hash, HashBytes(&aKey->mShadowColor.a, sizeof(aKey->mShadowColor.a)));

    for (auto corner : mozilla::AllPhysicalCorners()) {
      hash = AddToHash(hash, aKey->mCornerRadii[corner].width,
                       aKey->mCornerRadii[corner].height);
    }

    hash = AddToHash(hash, (uint32_t)aKey->mBackend);

    if (aKey->mIsInset) {
      hash = AddToHash(hash, aKey->mInnerMinSize.width,
                       aKey->mInnerMinSize.height);
    }
    return hash;
  }

  bool KeyEquals(KeyTypePointer aKey) const {
    if (aKey->mMinSize == mMinSize && aKey->mBlurRadius == mBlurRadius &&
        aKey->mCornerRadii == mCornerRadii &&
        aKey->mShadowColor == mShadowColor && aKey->mBackend == mBackend) {
      if (mIsInset) {
        return (mInnerMinSize == aKey->mInnerMinSize);
      }

      return true;
    }

    return false;
  }

  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.
 * */
struct BlurCacheData {
  BlurCacheData(SourceSurface* aBlur, const IntMargin& aBlurMargin,
                BlurCacheKey&& aKey)
      : mBlur(aBlur), mBlurMargin(aBlurMargin), mKey(std::move(aKey)) {}

  BlurCacheData(BlurCacheData&& aOther) = default;

  nsExpirationState* GetExpirationState() { return &mExpirationState; }

  nsExpirationState mExpirationState;
  RefPtr<SourceSurface> mBlur;
  IntMargin mBlurMargin;
  BlurCacheKey mKey;
};

/**
 * This class implements a cache with no maximum size, that retains the
 * SourceSurfaces used to draw the blurs.
 *
 * An entry stays in the cache as long as it is used often.
 */
class BlurCache final : public nsExpirationTracker<BlurCacheData, 4> {
 public:
  BlurCache()
      : nsExpirationTracker<BlurCacheData, 4>(GENERATION_MS, "BlurCache"_ns) {}

  virtual void NotifyExpired(BlurCacheData* aObject) override {
    RemoveObject(aObject);
    mHashEntries.Remove(aObject->mKey);
  }

  BlurCacheData* Lookup(const IntSize& aMinSize, const IntSize& aBlurRadius,
                        const RectCornerRadii* aCornerRadii,
                        const sRGBColor& aShadowColor,
                        BackendType aBackendType) {
    BlurCacheData* blur = mHashEntries.Get(BlurCacheKey(
        aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aBackendType));
    if (blur) {
      MarkUsed(blur);
    }

    return blur;
  }

  BlurCacheData* LookupInsetBoxShadow(const IntSize& aOuterMinSize,
                                      const IntSize& aInnerMinSize,
                                      const IntSize& aBlurRadius,
                                      const RectCornerRadii* aCornerRadii,
                                      const sRGBColor& aShadowColor,
                                      BackendType aBackendType) {
    bool insetBoxShadow = true;
    BlurCacheKey key(aOuterMinSize, aInnerMinSize, aBlurRadius, aCornerRadii,
                     aShadowColor, insetBoxShadow, aBackendType);
    BlurCacheData* blur = mHashEntries.Get(key);
    if (blur) {
      MarkUsed(blur);
    }

    return blur;
  }

  void RegisterEntry(UniquePtr<BlurCacheData> aValue) {
    nsresult rv = AddObject(aValue.get());
    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
      // for removing the cache entries), so we avoid putting that entry in the
      // table, which is a good thing considering we are short on memory
      // anyway, we probably don't want to retain things.
      return;
    }
    mHashEntries.InsertOrUpdate(aValue->mKey, std::move(aValue));
  }

 protected:
  static const uint32_t GENERATION_MS = 1000;
  /**
   * FIXME use nsTHashtable to avoid duplicating the BlurCacheKey.
   * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
   */
  nsClassHashtable<BlurCacheKey, BlurCacheData> mHashEntries;
};

static BlurCache* gBlurCache = nullptr;

static IntSize ComputeMinSizeForShadowShape(const RectCornerRadii* aCornerRadii,
                                            const IntSize& aBlurRadius,
                                            IntMargin& aOutSlice,
                                            const IntSize& aRectSize) {
  Size cornerSize(0, 0);
  if (aCornerRadii) {
    const RectCornerRadii& corners = *aCornerRadii;
    for (const auto i : mozilla::AllPhysicalCorners()) {
      cornerSize.width = std::max(cornerSize.width, corners[i].width);
      cornerSize.height = std::max(cornerSize.height, corners[i].height);
    }
  }

  IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius;
  aOutSlice =
      IntMargin(margin.height, margin.width, margin.height, margin.width);

  IntSize minSize(aOutSlice.LeftRight() + 1, aOutSlice.TopBottom() + 1);

  // If aRectSize is smaller than minSize, the border-image approach won't
  // work; there's no way to squeeze parts of the min box-shadow source
  // image such that the result looks correct. So we need to adjust minSize
  // in such a way that we can later draw it without stretching in the affected
  // dimension. We also need to adjust "slice" to ensure that we're not trying
  // to slice away more than we have.
  if (aRectSize.width < minSize.width) {
    minSize.width = aRectSize.width;
    aOutSlice.left = 0;
    aOutSlice.right = 0;
  }
  if (aRectSize.height < minSize.height) {
    minSize.height = aRectSize.height;
    aOutSlice.top = 0;
    aOutSlice.bottom = 0;
  }

  MOZ_ASSERT(aOutSlice.LeftRight() <= minSize.width);
  MOZ_ASSERT(aOutSlice.TopBottom() <= minSize.height);
  return minSize;
}

static void CacheBlur(DrawTarget* aDT, const IntSize& aMinSize,
                      const IntSize& aBlurRadius,
                      const RectCornerRadii* aCornerRadii,
                      const sRGBColor& aShadowColor,
                      const IntMargin& aBlurMargin, SourceSurface* aBoxShadow) {
  gBlurCache->RegisterEntry(MakeUnique<BlurCacheData>(
      aBoxShadow, aBlurMargin,
      BlurCacheKey(aMinSize, aBlurRadius, aCornerRadii, aShadowColor,
                   aDT->GetBackendType())));
}

// Blurs a small surface and creates the colored box shadow.
static already_AddRefed<SourceSurface> CreateBoxShadow(
    DrawTarget* aDestDrawTarget, const IntSize& aMinSize,
    const RectCornerRadii* aCornerRadii, const IntSize& aBlurRadius,
    const sRGBColor& aShadowColor, bool aMirrorCorners,
    IntMargin& aOutBlurMargin) {
  gfxGaussianBlur blur;
  Rect minRect(Point(0, 0), Size(aMinSize));
  Rect blurRect(minRect);
  // If mirroring corners, we only need to draw the top-left quadrant.
  // Use ceil to preserve the remaining 1x1 middle area for minimized box
  // shadows.
  if (aMirrorCorners) {
    blurRect.SizeTo(ceil(blurRect.Width() * 0.5f),
                    ceil(blurRect.Height() * 0.5f));
  }
  IntSize zeroSpread(0, 0);
  RefPtr<DrawTarget> blurDT =
      blur.InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread,
                          gfxGaussianBlur::CalculateBlurSigma(aBlurRadius));
  if (!blurDT) {
    return nullptr;
  }

  ColorPattern black(DeviceColor::MaskOpaqueBlack());

  if (aCornerRadii) {
    RefPtr<Path> roundedRect =
        MakePathForRoundedRect(*blurDT, minRect, *aCornerRadii);
    blurDT->Fill(roundedRect, black);
  } else {
    blurDT->FillRect(minRect, black);
  }

  IntPoint topLeft;
  RefPtr<SourceSurface> result = blur.DoBlur(&aShadowColor, &topLeft);
  if (!result) {
    return nullptr;
  }

  // Since blurRect is at (0, 0), we can find the inflated margin by
  // negating the new rect origin, which would have been negative if
  // the rect was inflated.
  aOutBlurMargin = IntMargin(-topLeft.y, -topLeft.x, -topLeft.y, -topLeft.x);

  return result.forget();
}

static already_AddRefed<SourceSurface> GetBlur(
    gfxContext* aDestinationCtx, const IntSize& aRectSize,
    const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii,
    const sRGBColor& aShadowColor, bool aMirrorCorners,
    IntMargin& aOutBlurMargin, IntMargin& aOutSlice, IntSize& aOutMinSize) {
  if (!gBlurCache) {
    gBlurCache = new BlurCache();
  }

  IntSize minSize = ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius,
                                                 aOutSlice, aRectSize);

  // We can get seams using the min size rect when drawing to the destination
  // rect if we have a non-pixel aligned destination transformation. In those
  // cases, fallback to just rendering the destination rect. During printing, we
  // record all the Moz 2d commands and replay them on the parent side with
  // Cairo. Cairo printing uses StretchDIBits to stretch the surface. However,
  // since our source image is only 1px for some parts, we make thousands of
  // calls. Instead just render the blur ourself here as one image and send it
  // over for printing.
  // TODO: May need to change this with the blob renderer in WR since it also
  // records.
  Matrix destMatrix = aDestinationCtx->CurrentMatrix();
  bool useDestRect = !destMatrix.IsRectilinear() ||
                     destMatrix.HasNonIntegerTranslation() ||
                     aDestinationCtx->GetDrawTarget()->IsRecording();
  if (useDestRect) {
    minSize = aRectSize;
  }

  int32_t maxTextureSize = gfxPlatform::MaxTextureSize();
  if (minSize.width > maxTextureSize || minSize.height > maxTextureSize) {
    return nullptr;
  }

  aOutMinSize = minSize;

  DrawTarget* destDT = aDestinationCtx->GetDrawTarget();

  if (!useDestRect) {
    BlurCacheData* cached =
        gBlurCache->Lookup(minSize, aBlurRadius, aCornerRadii, aShadowColor,
                           destDT->GetBackendType());
    if (cached) {
      // See CreateBoxShadow() for these values
      aOutBlurMargin = cached->mBlurMargin;
      RefPtr<SourceSurface> blur = cached->mBlur;
      return blur.forget();
    }
  }

  RefPtr<SourceSurface> boxShadow =
      CreateBoxShadow(destDT, minSize, aCornerRadii, aBlurRadius, aShadowColor,
                      aMirrorCorners, aOutBlurMargin);
  if (!boxShadow) {
    return nullptr;
  }

  if (RefPtr<SourceSurface> opt = destDT->OptimizeSourceSurface(boxShadow)) {
    boxShadow = opt;
  }

  if (!useDestRect) {
    CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor,
              aOutBlurMargin, boxShadow);
  }
  return boxShadow.forget();
}

void gfxGaussianBlur::ShutdownBlurCache() {
  delete gBlurCache;
  gBlurCache = nullptr;
}

static Rect RectWithEdgesTRBL(Float aTop, Float aRight, Float aBottom,
                              Float aLeft) {
  return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop);
}

static bool ShouldStretchSurface(DrawTarget* aDT, SourceSurface* aSurface) {
  // Use stretching if possible, since it leads to less seams when the
  // destination is transformed. However, don't do this if we're using cairo,
  // because if cairo is using pixman it won't render anything for large
  // stretch factors because pixman's internal fixed point precision is not
  // high enough to handle those scale factors.
  return aDT->GetBackendType() != BackendType::CAIRO;
}

static void RepeatOrStretchSurface(DrawTarget* aDT, SourceSurface* aSurface,
                                   const Rect& aDest, const Rect& aSrc,
                                   const Rect& aSkipRect) {
  if (aSkipRect.Contains(aDest)) {
    return;
  }

  if (ShouldStretchSurface(aDT, aSurface)) {
    aDT->DrawSurface(aSurface, aDest, aSrc);
    return;
  }

  SurfacePattern pattern(aSurface, ExtendMode::REPEAT,
                         Matrix::Translation(aDest.TopLeft() - aSrc.TopLeft()),
                         SamplingFilter::GOOD, RoundedToInt(aSrc));
  aDT->FillRect(aDest, pattern);
}

static void DrawCorner(DrawTarget* aDT, SourceSurface* aSurface,
                       const Rect& aDest, const Rect& aSrc,
                       const Rect& aSkipRect) {
  if (aSkipRect.Contains(aDest)) {
    return;
  }

  aDT->DrawSurface(aSurface, aDest, aSrc);
}

static void DrawMinBoxShadow(DrawTarget* aDestDrawTarget,
                             SourceSurface* aSourceBlur, const Rect& aDstOuter,
                             const Rect& aDstInner, const Rect& aSrcOuter,
                             const Rect& aSrcInner, const Rect& aSkipRect,
                             bool aMiddle = false) {
  // 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);

  // Middle part
  if (aMiddle) {
    RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
                           RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(),
                                             aDstInner.YMost(), aDstInner.X()),
                           RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(),
                                             aSrcInner.YMost(), aSrcInner.X()),
                           aSkipRect);
  }
}

static void DrawMirroredRect(DrawTarget* aDT, SourceSurface* aSurface,
                             const Rect& aDest, const Point& aSrc,
                             Float aScaleX, Float aScaleY) {
  SurfacePattern pattern(
      aSurface, ExtendMode::CLAMP,
      Matrix::Scaling(aScaleX, aScaleY)
          .PreTranslate(-aSrc)
          .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(),
                         aScaleY < 0 ? aDest.YMost() : aDest.Y()));
  aDT->FillRect(aDest, pattern);
}

static void DrawMirroredBoxShadow(DrawTarget* aDT, SourceSurface* aSurface,
                                  const Rect& aDestRect) {
  Point center(ceil(aDestRect.X() + aDestRect.Width() / 2),
               ceil(aDestRect.Y() + aDestRect.Height() / 2));
  Rect topLeft(aDestRect.X(), aDestRect.Y(), center.x - aDestRect.X(),
               center.y - aDestRect.Y());
  Rect bottomRight(topLeft.BottomRight(), aDestRect.Size() - topLeft.Size());
  Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(),
                topLeft.Height());
  Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(),
                  bottomRight.Height());
  DrawMirroredRect(aDT, aSurface, topLeft, Point(), 1, 1);
  DrawMirroredRect(aDT, aSurface, topRight, Point(), -1, 1);
  DrawMirroredRect(aDT, aSurface, bottomLeft, Point(), 1, -1);
  DrawMirroredRect(aDT, aSurface, bottomRight, Point(), -1, -1);
}

static void DrawMirroredCorner(DrawTarget* aDT, SourceSurface* aSurface,
                               const Rect& aDest, const Point& aSrc,
                               const Rect& aSkipRect, Float aScaleX,
                               Float aScaleY) {
  if (aSkipRect.Contains(aDest)) {
    return;
  }

  DrawMirroredRect(aDT, aSurface, aDest, aSrc, aScaleX, aScaleY);
}

static void RepeatOrStretchMirroredSurface(DrawTarget* aDT,
                                           SourceSurface* aSurface,
                                           const Rect& aDest, const Rect& aSrc,
                                           const Rect& aSkipRect, Float aScaleX,
                                           Float aScaleY) {
  if (aSkipRect.Contains(aDest)) {
    return;
  }

  if (ShouldStretchSurface(aDT, aSurface)) {
    aScaleX *= aDest.Width() / aSrc.Width();
    aScaleY *= aDest.Height() / aSrc.Height();
    DrawMirroredRect(aDT, aSurface, aDest, aSrc.TopLeft(), aScaleX, aScaleY);
    return;
  }

  SurfacePattern pattern(
      aSurface, ExtendMode::REPEAT,
      Matrix::Scaling(aScaleX, aScaleY)
          .PreTranslate(-aSrc.TopLeft())
          .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(),
                         aScaleY < 0 ? aDest.YMost() : aDest.Y()),
      SamplingFilter::GOOD, RoundedToInt(aSrc));
  aDT->FillRect(aDest, pattern);
}

static void DrawMirroredMinBoxShadow(
    DrawTarget* aDestDrawTarget, SourceSurface* aSourceBlur,
    const Rect& aDstOuter, const Rect& aDstInner, const Rect& aSrcOuter,
    const Rect& aSrcInner, const Rect& aSkipRect, bool aMiddle = false) {
  // Corners: top left, top right, bottom left, bottom right
  // Compute quadrant bounds and then clip them to corners along
  // dimensions where we need to stretch from min size.
  Point center(ceil(aDstOuter.X() + aDstOuter.Width() / 2),
               ceil(aDstOuter.Y() + aDstOuter.Height() / 2));
  Rect topLeft(aDstOuter.X(), aDstOuter.Y(), center.x - aDstOuter.X(),
               center.y - aDstOuter.Y());
  Rect bottomRight(topLeft.BottomRight(), aDstOuter.Size() - topLeft.Size());
  Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(),
                topLeft.Height());
  Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(),
                  bottomRight.Height());

  // Check if the middle part has been minimized along each dimension.
  // If so, those will be strecthed/drawn separately and need to be clipped out.
  if (aSrcInner.Width() == 1) {
    topLeft.SetRightEdge(aDstInner.X());
    topRight.SetLeftEdge(aDstInner.XMost());
    bottomLeft.SetRightEdge(aDstInner.X());
    bottomRight.SetLeftEdge(aDstInner.XMost());
  }
  if (aSrcInner.Height() == 1) {
    topLeft.SetBottomEdge(aDstInner.Y());
    topRight.SetBottomEdge(aDstInner.Y());
    bottomLeft.SetTopEdge(aDstInner.YMost());
    bottomRight.SetTopEdge(aDstInner.YMost());
  }

  DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topLeft, aSrcOuter.TopLeft(),
                     aSkipRect, 1, 1);
  DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topRight,
                     aSrcOuter.TopLeft(), aSkipRect, -1, 1);
  DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomLeft,
                     aSrcOuter.TopLeft(), aSkipRect, 1, -1);
  DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomRight,
                     aSrcOuter.TopLeft(), aSkipRect, -1, -1);

  // Edges: top, bottom, left, right
  // Draw middle edges where they need to be stretched. The top and left
  // sections that are part of the top-left quadrant will be mirrored to
  // the bottom and right sections, respectively.
  if (aSrcInner.Width() == 1) {
    Rect dstTop = RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(),
                                    aDstInner.Y(), aDstInner.X());
    Rect srcTop = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
                                    aSrcInner.Y(), aSrcInner.X());
    Rect dstBottom = RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(),
                                       aDstOuter.YMost(), aDstInner.X());
    Rect srcBottom = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
                                       aSrcInner.Y(), aSrcInner.X());
    // If we only need to stretch along the X axis and we're drawing
    // the middle section, just sample all the way to the center of the
    // source on the Y axis to avoid extra draw calls.
    if (aMiddle && aSrcInner.Height() != 1) {
      dstTop.SetBottomEdge(center.y);
      srcTop.SetHeight(dstTop.Height());
      dstBottom.SetTopEdge(dstTop.YMost());
      srcBottom.SetHeight(dstBottom.Height());
    }
    RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstTop, srcTop,
                                   aSkipRect, 1, 1);
    RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstBottom,
                                   srcBottom, aSkipRect, 1, -1);
  }

  if (aSrcInner.Height() == 1) {
    Rect dstLeft = RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(),
                                     aDstInner.YMost(), aDstOuter.X());
    Rect srcLeft = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
                                     aSrcInner.YMost(), aSrcOuter.X());
    Rect dstRight = RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(),
                                      aDstInner.YMost(), aDstInner.XMost());
    Rect srcRight = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
                                      aSrcInner.YMost(), aSrcOuter.X());
    // Only stretching on Y axis, so sample source to the center of the X axis.
    if (aMiddle && aSrcInner.Width() != 1) {
      dstLeft.SetRightEdge(center.x);
      srcLeft.SetWidth(dstLeft.Width());
      dstRight.SetLeftEdge(dstLeft.XMost());
      srcRight.SetWidth(dstRight.Width());
    }
    RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstLeft,
                                   srcLeft, aSkipRect, 1, 1);
    RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstRight,
                                   srcRight, aSkipRect, -1, 1);
  }

  // If we need to stretch along both dimensions, then the middle part
  // must be drawn separately.
  if (aMiddle && aSrcInner.Width() == 1 && aSrcInner.Height() == 1) {
    RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
                           RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(),
                                             aDstInner.YMost(), aDstInner.X()),
                           RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(),
                                             aSrcInner.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
 * the space between the corners.
 */

/* static */
void gfxGaussianBlur::BlurRectangle(gfxContext* aDestinationCtx,
                                    const gfxRect& aRect,
                                    const RectCornerRadii* aCornerRadii,
                                    const gfxPoint& aBlurStdDev,
                                    const sRGBColor& aShadowColor,
                                    const gfxRect& aDirtyRect,
                                    const gfxRect& aSkipRect) {
  if (!RectIsInt32Safe(ToRect(aRect))) {
    return;
  }

  IntSize blurRadius = CalculateBlurRadius(aBlurStdDev);
  bool mirrorCorners = !aCornerRadii || aCornerRadii->AreRadiiSame();

  IntRect rect = RoundedToInt(ToRect(aRect));
  IntMargin blurMargin;
  IntMargin slice;
  IntSize minSize;
  RefPtr<SourceSurface> boxShadow =
      GetBlur(aDestinationCtx, rect.Size(), blurRadius, aCornerRadii,
              aShadowColor, mirrorCorners, blurMargin, slice, minSize);
  if (!boxShadow) {
    return;
  }

  DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
  destDrawTarget->PushClipRect(ToRect(aDirtyRect));

  // Copy the right parts from boxShadow into destDrawTarget. The middle parts
  // will be stretched, border-image style.

  Rect srcOuter(Point(blurMargin.left, blurMargin.top), Size(minSize));
  Rect srcInner(srcOuter);
  srcOuter.Inflate(Margin(blurMargin));
  srcInner.Deflate(Margin(slice));

  Rect dstOuter(rect);
  Rect dstInner(rect);
  dstOuter.Inflate(Margin(blurMargin));
  dstInner.Deflate(Margin(slice));

  Rect skipRect = ToRect(aSkipRect);

  if (minSize == rect.Size()) {
    // The target rect is smaller than the minimal size so just draw the surface
    if (mirrorCorners) {
      DrawMirroredBoxShadow(destDrawTarget, boxShadow, dstOuter);
    } else {
      destDrawTarget->DrawSurface(boxShadow, dstOuter, srcOuter);
    }
  } else {
    if (mirrorCorners) {
      DrawMirroredMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner,
                               srcOuter, srcInner, skipRect, true);
    } else {
      DrawMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, srcOuter,
                       srcInner, skipRect, true);
    }
  }

  // A note about anti-aliasing and seems between adjacent parts:
  // We don't explicitly disable anti-aliasing in the DrawSurface calls above,
  // so if there's a transform on destDrawTarget that is not pixel-aligned,
  // there will be seams between adjacent parts of the box-shadow. It's hard to
  // avoid those without the use of an intermediate surface.
  // You might think that we could avoid those by just turning off AA, but there
  // is a problem with that: Box-shadow rendering needs to clip out the
  // element's border box, and we'd like that clip to have anti-aliasing -
  // especially if the element has rounded corners! So we can't do that unless
  // we have a way to say "Please anti-alias the clip, but don't antialias the
  // destination rect of the DrawSurface call".

  destDrawTarget->PopClip();
}

static already_AddRefed<Path> GetBoxShadowInsetPath(
    DrawTarget* aDrawTarget, const Rect aOuterRect, const Rect aInnerRect,
    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 (aInnerClipRadii) {
    AppendRoundedRectToPath(builder, aInnerRect, *aInnerClipRadii, false);
  } else {
    AppendRectToPath(builder, aInnerRect, false);
  }
  return builder->Finish();
}

static void FillDestinationPath(
    gfxContext* aDestinationCtx, const Rect& aDestinationRect,
    const Rect& aShadowClipRect, const sRGBColor& aShadowColor,
    const RectCornerRadii* aInnerClipRadii = nullptr) {
  // When there is no blur radius, fill the path onto the destination
  // surface.
  aDestinationCtx->SetColor(aShadowColor);
  DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
  RefPtr<Path> shadowPath = GetBoxShadowInsetPath(
      destDrawTarget, aDestinationRect, aShadowClipRect, aInnerClipRadii);

  aDestinationCtx->SetPath(shadowPath);
  aDestinationCtx->Fill();
}

static void CacheInsetBlur(const IntSize& aMinOuterSize,
                           const IntSize& aMinInnerSize,
                           const IntSize& aBlurRadius,
                           const RectCornerRadii* aCornerRadii,
                           const sRGBColor& aShadowColor,
                           BackendType aBackendType,
                           SourceSurface* aBoxShadow) {
  bool isInsetBlur = true;
  BlurCacheKey key(aMinOuterSize, aMinInnerSize, aBlurRadius, aCornerRadii,
                   aShadowColor, isInsetBlur, aBackendType);
  IntMargin blurMargin(0, 0, 0, 0);

  gBlurCache->RegisterEntry(
      MakeUnique<BlurCacheData>(aBoxShadow, blurMargin, std::move(key)));
}

already_AddRefed<SourceSurface> gfxGaussianBlur::GetInsetBlur(
    const Rect& aOuterRect, const Rect& aWhitespaceRect, bool aIsDestRect,
    const sRGBColor& aShadowColor, const IntSize& aBlurRadius,
    const RectCornerRadii* aInnerClipRadii, DrawTarget* aDestDrawTarget,
    bool aMirrorCorners) {
  if (!gBlurCache) {
    gBlurCache = new BlurCache();
  }

  IntSize outerSize = IntSize::Truncate(aOuterRect.Size());
  IntSize whitespaceSize = IntSize::Truncate(aWhitespaceRect.Size());
  if (!aIsDestRect) {
    BlurCacheData* cached = gBlurCache->LookupInsetBoxShadow(
        outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii, aShadowColor,
        aDestDrawTarget->GetBackendType());
    if (cached) {
      // So we don't forget the actual cached blur
      RefPtr<SourceSurface> cachedBlur = cached->mBlur;
      return cachedBlur.forget();
    }
  }

  // If we can do a min rect, the whitespace rect will be expanded in Init to
  // aOuterRect.
  Rect blurRect = aIsDestRect ? aOuterRect : aWhitespaceRect;
  // If mirroring corners, we only need to draw the top-left quadrant.
  // Use ceil to preserve the remaining 1x1 middle area for minimized box
  // shadows.
  if (aMirrorCorners) {
    blurRect.SizeTo(ceil(blurRect.Width() * 0.5f),
                    ceil(blurRect.Height() * 0.5f));
  }
  IntSize zeroSpread(0, 0);
  RefPtr<DrawTarget> minDrawTarget =
      InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread,
                     CalculateBlurSigma(aBlurRadius), nullptr, nullptr, true);
  if (!minDrawTarget) {
    return nullptr;
  }

  // This is really annoying. When we create the GaussianBlur, the DrawTarget
  // 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) {
    minDrawTarget->SetTransform(Matrix());
  }

  // Fill in the path between the inside white space / outer rects
  // NOT the inner frame
  RefPtr<Path> maskPath = GetBoxShadowInsetPath(
      minDrawTarget, aOuterRect, aWhitespaceRect, aInnerClipRadii);

  ColorPattern black(DeviceColor::MaskOpaqueBlack());
  minDrawTarget->Fill(maskPath, black);

  // Blur and fill in with the color we actually wanted
  RefPtr<SourceSurface> minInsetBlur = DoBlur(&aShadowColor);
  if (!minInsetBlur) {
    return nullptr;
  }

  if (RefPtr<SourceSurface> opt =
          aDestDrawTarget->OptimizeSourceSurface(minInsetBlur)) {
    minInsetBlur = opt;
  }

  if (!aIsDestRect) {
    CacheInsetBlur(outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii,
                   aShadowColor, aDestDrawTarget->GetBackendType(),
                   minInsetBlur);
  }

  return minInsetBlur.forget();
}

/***
 * 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.
 *
 * B = one blur size
 * W = one blur + corner radii - known as inner margin
 * ___________________________________
 * |                                |
 * |          |             |       |
 * |      (2) |    (1)      |  (2)  |
 * |       B  |     W       |   B   |
 * |          |             |       |
 * |          |             |       |
 * |          |                     |
 * |________________________________|
 */
static void GetBlurMargins(const RectCornerRadii* aInnerClipRadii,
                           const IntSize& aBlurRadius, Margin& aOutBlurMargin,
                           Margin& aOutInnerMargin) {
  Size cornerSize(0, 0);
  if (aInnerClipRadii) {
    const RectCornerRadii& corners = *aInnerClipRadii;
    for (const auto i : mozilla::AllPhysicalCorners()) {
      cornerSize.width = std::max(cornerSize.width, corners[i].width);
      cornerSize.height = std::max(cornerSize.height, corners[i].height);
    }
  }

  // Only the inside whitespace size cares about the border radius size.
  // Outer sizes only care about blur.
  IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius;

  aOutInnerMargin.SizeTo(margin.height, margin.width, margin.height,
                         margin.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 gfxGaussianBlur::BlurInsetBox(
    gfxContext* aDestinationCtx, const Rect& aDestinationRect,
    const Rect& aShadowClipRect, const IntSize& aBlurRadius,
    const sRGBColor& aShadowColor, const RectCornerRadii* aInnerClipRadii,
    const Rect& aSkipRect, const Point& aShadowOffset) {
  if ((aBlurRadius.width == 0 && aBlurRadius.height == 0) ||
      aShadowClipRect.IsEmpty()) {
    FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect,
                        aShadowColor, aInnerClipRadii);
    return;
  }

  DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();

  Margin innerMargin;
  Margin blurMargin;
  GetBlurMargins(aInnerClipRadii, aBlurRadius, blurMargin, innerMargin);

  Rect whitespaceRect;
  Rect outerRect;
  bool useDestRect =
      GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect,
                             aDestinationRect, whitespaceRect, outerRect);

  // Check that the inset margin between the outer and whitespace rects is
  // symmetric, and that all corner radii are the same, in which case the blur
  // can be mirrored.
  Margin checkMargin = outerRect - whitespaceRect;
  bool mirrorCorners = checkMargin.left == checkMargin.right &&
                       checkMargin.top == checkMargin.bottom &&
                       (!aInnerClipRadii || aInnerClipRadii->AreRadiiSame());
  RefPtr<SourceSurface> minBlur =
      GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor,
                   aBlurRadius, aInnerClipRadii, destDrawTarget, mirrorCorners);
  if (!minBlur) {
    return;
  }

  if (useDestRect) {
    Rect destBlur = aDestinationRect;
    destBlur.Inflate(blurMargin);
    if (mirrorCorners) {
      DrawMirroredBoxShadow(destDrawTarget, minBlur.get(), destBlur);
    } else {
      Rect srcBlur(Point(0, 0), Size(minBlur->GetSize()));
      MOZ_ASSERT(RoundedOut(srcBlur).Size() == RoundedOut(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

    // The shadow clip rect already takes into account the spread radius
    Rect outerFillRect(aShadowClipRect);
    outerFillRect.Inflate(blurMargin);
    FillDestinationPath(aDestinationCtx, aDestinationRect, outerFillRect,
                        aShadowColor);

    // 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);

    if (mirrorCorners) {
      DrawMirroredMinBoxShadow(destDrawTarget, minBlur, destRect, destInnerRect,
                               srcOuter, srcInner, aSkipRect);
    } else {
      DrawMinBoxShadow(destDrawTarget, minBlur, destRect, destInnerRect,
                       srcOuter, srcInner, aSkipRect);
    }
  }
}
