Bug 1341156 - Move nsImageRenderer to a separate file. r=mattwoodrow draft
authorMorris Tseng <mtseng@mozilla.com>
Fri, 24 Feb 2017 16:24:48 +0800
changeset 490713 ecba8e80babfdcdd6f2e262c2d08c9ce2f886464
parent 490712 2f84c893866133e4d4214b04242ee5ae6dccb2fe
child 490714 6cfe78696c87f413d56c3bf75a5bdb4835ec5ba0
push id47204
push userbmo:mtseng@mozilla.com
push dateWed, 01 Mar 2017 07:32:07 +0000
reviewersmattwoodrow
bugs1341156
milestone54.0a1
Bug 1341156 - Move nsImageRenderer to a separate file. r=mattwoodrow MozReview-Commit-ID: EIEl3Jbtcsi
layout/painting/moz.build
layout/painting/nsCSSRendering.cpp
layout/painting/nsCSSRendering.h
layout/painting/nsCSSRenderingBorders.h
layout/painting/nsImageRenderer.cpp
layout/painting/nsImageRenderer.h
--- a/layout/painting/moz.build
+++ b/layout/painting/moz.build
@@ -15,16 +15,17 @@ EXPORTS += [
     'DisplayListClipState.h',
     'FrameLayerBuilder.h',
     'LayerState.h',
     'nsCSSRenderingBorders.h',
     'nsDisplayItemTypes.h',
     'nsDisplayItemTypesList.h',
     'nsDisplayList.h',
     'nsDisplayListInvalidation.h',
+    'nsImageRenderer.h',
 ]
 
 EXPORTS.mozilla += [
     'PaintTracker.h',
 ]
 
 UNIFIED_SOURCES += [
     'ActiveLayerTracker.cpp',
@@ -35,16 +36,17 @@ UNIFIED_SOURCES += [
     'DisplayListClipState.cpp',
     'DottedCornerFinder.cpp',
     'FrameLayerBuilder.cpp',
     'MaskLayerImageCache.cpp',
     'nsCSSRendering.cpp',
     'nsCSSRenderingBorders.cpp',
     'nsDisplayList.cpp',
     'nsDisplayListInvalidation.cpp',
+    'nsImageRenderer.cpp',
     'PaintTracker.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '/docshell/base',
     '/dom/base',
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -5340,877 +5340,16 @@ nsCSSRendering::GetTextDecorationRectInt
     Swap(r.width, r.height);
   } else {
     r.y = baseline - floor(offset + 0.5);
   }
 
   return r;
 }
 
-// ------------------
-// ImageRenderer
-// ------------------
-nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame,
-                                 const nsStyleImage* aImage,
-                                 uint32_t aFlags)
-  : mForFrame(aForFrame)
-  , mImage(aImage)
-  , mType(aImage->GetType())
-  , mImageContainer(nullptr)
-  , mGradientData(nullptr)
-  , mPaintServerFrame(nullptr)
-  , mPrepareResult(DrawResult::NOT_READY)
-  , mSize(0, 0)
-  , mFlags(aFlags)
-  , mExtendMode(ExtendMode::CLAMP)
-  , mMaskOp(NS_STYLE_MASK_MODE_MATCH_SOURCE)
-{
-}
-
-nsImageRenderer::~nsImageRenderer()
-{
-}
-
-static bool
-ShouldTreatAsCompleteDueToSyncDecode(const nsStyleImage* aImage,
-                                     uint32_t aFlags)
-{
-  if (!(aFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES)) {
-    return false;
-  }
-
-  if (aImage->GetType() != eStyleImageType_Image) {
-    return false;
-  }
-
-  imgRequestProxy* req = aImage->GetImageData();
-  if (!req) {
-    return false;
-  }
-
-  uint32_t status = 0;
-  if (NS_FAILED(req->GetImageStatus(&status))) {
-    return false;
-  }
-
-  if (status & imgIRequest::STATUS_ERROR) {
-    // The image is "complete" since it's a corrupt image. If we created an
-    // imgIContainer at all, return true.
-    nsCOMPtr<imgIContainer> image;
-    req->GetImage(getter_AddRefs(image));
-    return bool(image);
-  }
-
-  if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
-    // We must have loaded all of the image's data and the size must be
-    // available, or else sync decoding won't be able to decode the image.
-    return false;
-  }
-
-  return true;
-}
-
-bool
-nsImageRenderer::PrepareImage()
-{
-  if (mImage->IsEmpty()) {
-    mPrepareResult = DrawResult::BAD_IMAGE;
-    return false;
-  }
-
-  if (!mImage->IsComplete()) {
-    // Make sure the image is actually decoding.
-    bool frameComplete = mImage->StartDecoding();
-
-    // Check again to see if we finished.
-    // We cannot prepare the image for rendering if it is not fully loaded.
-    // Special case: If we requested a sync decode and the image has loaded, push
-    // on through because the Draw() will do a sync decode then.
-    if (!(frameComplete || mImage->IsComplete()) &&
-        !ShouldTreatAsCompleteDueToSyncDecode(mImage, mFlags)) {
-      mPrepareResult = DrawResult::NOT_READY;
-      return false;
-    }
-  }
-
-  switch (mType) {
-    case eStyleImageType_Image: {
-      MOZ_ASSERT(mImage->GetImageData(),
-                 "must have image data, since we checked IsEmpty above");
-      nsCOMPtr<imgIContainer> srcImage;
-      DebugOnly<nsresult> rv =
-        mImage->GetImageData()->GetImage(getter_AddRefs(srcImage));
-      MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage,
-                 "If GetImage() is failing, mImage->IsComplete() "
-                 "should have returned false");
-
-      if (!mImage->GetCropRect()) {
-        mImageContainer.swap(srcImage);
-      } else {
-        nsIntRect actualCropRect;
-        bool isEntireImage;
-        bool success =
-          mImage->ComputeActualCropRect(actualCropRect, &isEntireImage);
-        NS_ASSERTION(success, "ComputeActualCropRect() should not fail here");
-        if (!success || actualCropRect.IsEmpty()) {
-          // The cropped image has zero size
-          mPrepareResult = DrawResult::BAD_IMAGE;
-          return false;
-        }
-        if (isEntireImage) {
-          // The cropped image is identical to the source image
-          mImageContainer.swap(srcImage);
-        } else {
-          nsCOMPtr<imgIContainer> subImage = ImageOps::Clip(srcImage,
-                                                            actualCropRect,
-                                                            Nothing());
-          mImageContainer.swap(subImage);
-        }
-      }
-      mPrepareResult = DrawResult::SUCCESS;
-      break;
-    }
-    case eStyleImageType_Gradient:
-      mGradientData = mImage->GetGradientData();
-      mPrepareResult = DrawResult::SUCCESS;
-      break;
-    case eStyleImageType_Element:
-    {
-      nsAutoString elementId =
-        NS_LITERAL_STRING("#") + nsDependentString(mImage->GetElementId());
-      nsCOMPtr<nsIURI> targetURI;
-      nsCOMPtr<nsIURI> base = mForFrame->GetContent()->GetBaseURI();
-      nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), elementId,
-                                                mForFrame->GetContent()->GetUncomposedDoc(), base);
-      nsSVGPaintingProperty* property = nsSVGEffects::GetPaintingPropertyForURI(
-          targetURI, mForFrame->FirstContinuation(),
-          nsSVGEffects::BackgroundImageProperty());
-      if (!property) {
-        mPrepareResult = DrawResult::BAD_IMAGE;
-        return false;
-      }
-
-      // If the referenced element is an <img>, <canvas>, or <video> element,
-      // prefer SurfaceFromElement as it's more reliable.
-      mImageElementSurface =
-        nsLayoutUtils::SurfaceFromElement(property->GetReferencedElement());
-      if (!mImageElementSurface.GetSourceSurface()) {
-        mPaintServerFrame = property->GetReferencedFrame();
-        if (!mPaintServerFrame) {
-          mPrepareResult = DrawResult::BAD_IMAGE;
-          return false;
-        }
-      }
-
-      mPrepareResult = DrawResult::SUCCESS;
-      break;
-    }
-    case eStyleImageType_Null:
-    default:
-      break;
-  }
-
-  return IsReady();
-}
-
-nsSize
-CSSSizeOrRatio::ComputeConcreteSize() const
-{
-  NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
-  if (mHasWidth && mHasHeight) {
-    return nsSize(mWidth, mHeight);
-  }
-  if (mHasWidth) {
-    nscoord height = NSCoordSaturatingNonnegativeMultiply(
-      mWidth,
-      double(mRatio.height) / mRatio.width);
-    return nsSize(mWidth, height);
-  }
-
-  MOZ_ASSERT(mHasHeight);
-  nscoord width = NSCoordSaturatingNonnegativeMultiply(
-    mHeight,
-    double(mRatio.width) / mRatio.height);
-  return nsSize(width, mHeight);
-}
-
-CSSSizeOrRatio
-nsImageRenderer::ComputeIntrinsicSize()
-{
-  NS_ASSERTION(IsReady(), "Ensure PrepareImage() has returned true "
-                          "before calling me");
-
-  CSSSizeOrRatio result;
-  switch (mType) {
-    case eStyleImageType_Image:
-    {
-      bool haveWidth, haveHeight;
-      CSSIntSize imageIntSize;
-      nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, imageIntSize,
-                                           result.mRatio, haveWidth, haveHeight);
-      if (haveWidth) {
-        result.SetWidth(nsPresContext::CSSPixelsToAppUnits(imageIntSize.width));
-      }
-      if (haveHeight) {
-        result.SetHeight(nsPresContext::CSSPixelsToAppUnits(imageIntSize.height));
-      }
-
-      // If we know the aspect ratio and one of the dimensions,
-      // we can compute the other missing width or height.
-      if (!haveHeight && haveWidth && result.mRatio.width != 0) {
-        nscoord intrinsicHeight =
-          NSCoordSaturatingNonnegativeMultiply(imageIntSize.width,
-                                               float(result.mRatio.height) /
-                                               float(result.mRatio.width));
-        result.SetHeight(nsPresContext::CSSPixelsToAppUnits(intrinsicHeight));
-      } else if (haveHeight && !haveWidth && result.mRatio.height != 0) {
-        nscoord intrinsicWidth =
-          NSCoordSaturatingNonnegativeMultiply(imageIntSize.height,
-                                               float(result.mRatio.width) /
-                                               float(result.mRatio.height));
-        result.SetWidth(nsPresContext::CSSPixelsToAppUnits(intrinsicWidth));
-      }
-
-      break;
-    }
-    case eStyleImageType_Element:
-    {
-      // XXX element() should have the width/height of the referenced element,
-      //     and that element's ratio, if it matches.  If it doesn't match, it
-      //     should have no width/height or ratio.  See element() in CSS images:
-      //     <http://dev.w3.org/csswg/css-images-4/#element-notation>.
-      //     Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize
-      //     when fixing this!
-      if (mPaintServerFrame) {
-        // SVG images have no intrinsic size
-        if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) {
-          // The intrinsic image size for a generic nsIFrame paint server is
-          // the union of the border-box rects of all of its continuations,
-          // rounded to device pixels.
-          int32_t appUnitsPerDevPixel =
-            mForFrame->PresContext()->AppUnitsPerDevPixel();
-          result.SetSize(
-            IntSizeToAppUnits(
-              nsSVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame).
-                ToNearestPixels(appUnitsPerDevPixel),
-              appUnitsPerDevPixel));
-        }
-      } else {
-        NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
-                     "Surface should be ready.");
-        IntSize surfaceSize = mImageElementSurface.mSize;
-        result.SetSize(
-          nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
-                 nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
-      }
-      break;
-    }
-    case eStyleImageType_Gradient:
-      // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
-      // intrinsic dimensions.
-    case eStyleImageType_Null:
-    default:
-      break;
-  }
-
-  return result;
-}
-
-/* static */ nsSize
-nsImageRenderer::ComputeConcreteSize(const CSSSizeOrRatio& aSpecifiedSize,
-                                     const CSSSizeOrRatio& aIntrinsicSize,
-                                     const nsSize& aDefaultSize)
-{
-  // The specified size is fully specified, just use that
-  if (aSpecifiedSize.IsConcrete()) {
-    return aSpecifiedSize.ComputeConcreteSize();
-  }
-
-  MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight);
-
-  if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) {
-    // no specified size, try using the intrinsic size
-    if (aIntrinsicSize.CanComputeConcreteSize()) {
-      return aIntrinsicSize.ComputeConcreteSize();
-    }
-
-    if (aIntrinsicSize.mHasWidth) {
-      return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height);
-    }
-    if (aIntrinsicSize.mHasHeight) {
-      return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight);
-    }
-
-    // couldn't use the intrinsic size either, revert to using the default size
-    return ComputeConstrainedSize(aDefaultSize,
-                                  aIntrinsicSize.mRatio,
-                                  CONTAIN);
-  }
-
-  MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
-
-  // The specified height is partial, try to compute the missing part.
-  if (aSpecifiedSize.mHasWidth) {
-    nscoord height;
-    if (aIntrinsicSize.HasRatio()) {
-      height = NSCoordSaturatingNonnegativeMultiply(
-        aSpecifiedSize.mWidth,
-        double(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width);
-    } else if (aIntrinsicSize.mHasHeight) {
-      height = aIntrinsicSize.mHeight;
-    } else {
-      height = aDefaultSize.height;
-    }
-    return nsSize(aSpecifiedSize.mWidth, height);
-  }
-
-  MOZ_ASSERT(aSpecifiedSize.mHasHeight);
-  nscoord width;
-  if (aIntrinsicSize.HasRatio()) {
-    width = NSCoordSaturatingNonnegativeMultiply(
-      aSpecifiedSize.mHeight,
-      double(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height);
-  } else if (aIntrinsicSize.mHasWidth) {
-    width = aIntrinsicSize.mWidth;
-  } else {
-    width = aDefaultSize.width;
-  }
-  return nsSize(width, aSpecifiedSize.mHeight);
-}
-
-/* static */ nsSize
-nsImageRenderer::ComputeConstrainedSize(const nsSize& aConstrainingSize,
-                                        const nsSize& aIntrinsicRatio,
-                                        FitType aFitType)
-{
-  if (aIntrinsicRatio.width <= 0 && aIntrinsicRatio.height <= 0) {
-    return aConstrainingSize;
-  }
-
-  float scaleX = double(aConstrainingSize.width) / aIntrinsicRatio.width;
-  float scaleY = double(aConstrainingSize.height) / aIntrinsicRatio.height;
-  nsSize size;
-  if ((aFitType == CONTAIN) == (scaleX < scaleY)) {
-    size.width = aConstrainingSize.width;
-    size.height = NSCoordSaturatingNonnegativeMultiply(
-                    aIntrinsicRatio.height, scaleX);
-    // If we're reducing the size by less than one css pixel, then just use the
-    // constraining size.
-    if (aFitType == CONTAIN && aConstrainingSize.height - size.height < nsPresContext::AppUnitsPerCSSPixel()) {
-      size.height = aConstrainingSize.height;
-    }
-  } else {
-    size.width = NSCoordSaturatingNonnegativeMultiply(
-                   aIntrinsicRatio.width, scaleY);
-    if (aFitType == CONTAIN && aConstrainingSize.width - size.width < nsPresContext::AppUnitsPerCSSPixel()) {
-      size.width = aConstrainingSize.width;
-    }
-    size.height = aConstrainingSize.height;
-  }
-  return size;
-}
-
-/**
- * mSize is the image's "preferred" size for this particular rendering, while
- * the drawn (aka concrete) size is the actual rendered size after accounting
- * for background-size etc..  The preferred size is most often the image's
- * intrinsic dimensions.  But for images with incomplete intrinsic dimensions,
- * the preferred size varies, depending on the specified and default sizes, see
- * nsImageRenderer::Compute*Size.
- *
- * This distinction is necessary because the components of a vector image are
- * specified with respect to its preferred size for a rendering situation, not
- * to its actual rendered size.  For example, consider a 4px wide background
- * vector image with no height which contains a left-aligned
- * 2px wide black rectangle with height 100%.  If the background-size width is
- * auto (or 4px), the vector image will render 4px wide, and the black rectangle
- * will be 2px wide.  If the background-size width is 8px, the vector image will
- * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
- * In both cases mSize.width will be 4px; but in the first case the returned
- * width will be 4px, while in the second case the returned width will be 8px.
- */
-void
-nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize,
-                                  const nsSize& aDefaultSize)
-{
-  mSize.width = aIntrinsicSize.mHasWidth
-                  ? aIntrinsicSize.mWidth
-                  : aDefaultSize.width;
-  mSize.height = aIntrinsicSize.mHasHeight
-                  ? aIntrinsicSize.mHeight
-                  : aDefaultSize.height;
-}
-
-// Convert from nsImageRenderer flags to the flags we want to use for drawing in
-// the imgIContainer namespace.
-static uint32_t
-ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)
-{
-  uint32_t drawFlags = imgIContainer::FLAG_NONE;
-  if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
-    drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
-  }
-  if (aImageRendererFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) {
-    drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
-  }
-  return drawFlags;
-}
-
-/*
- *  SVG11: A luminanceToAlpha operation is equivalent to the following matrix operation:                                                   |
- *  | R' |     |      0        0        0  0  0 |   | R |
- *  | G' |     |      0        0        0  0  0 |   | G |
- *  | B' |  =  |      0        0        0  0  0 | * | B |
- *  | A' |     | 0.2125   0.7154   0.0721  0  0 |   | A |
- *  | 1  |     |      0        0        0  0  1 |   | 1 |
- */
-static void
-RGBALuminanceOperation(uint8_t *aData,
-                       int32_t aStride,
-                       const IntSize &aSize)
-{
-  int32_t redFactor = 55;    // 256 * 0.2125
-  int32_t greenFactor = 183; // 256 * 0.7154
-  int32_t blueFactor = 18;   // 256 * 0.0721
-
-  for (int32_t y = 0; y < aSize.height; y++) {
-    uint32_t *pixel = (uint32_t*)(aData + aStride * y);
-    for (int32_t x = 0; x < aSize.width; x++) {
-      *pixel = (((((*pixel & 0x00FF0000) >> 16) * redFactor) +
-                 (((*pixel & 0x0000FF00) >>  8) * greenFactor) +
-                  ((*pixel & 0x000000FF)        * blueFactor)) >> 8) << 24;
-      pixel++;
-    }
-  }
-}
-
-
-DrawResult
-nsImageRenderer::Draw(nsPresContext*       aPresContext,
-                      nsRenderingContext&  aRenderingContext,
-                      const nsRect&        aDirtyRect,
-                      const nsRect&        aDest,
-                      const nsRect&        aFill,
-                      const nsPoint&       aAnchor,
-                      const nsSize&        aRepeatSize,
-                      const CSSIntRect&    aSrc,
-                      float                aOpacity)
-{
-  if (!IsReady()) {
-    NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
-    return DrawResult::TEMPORARY_ERROR;
-  }
-  if (aDest.IsEmpty() || aFill.IsEmpty() ||
-      mSize.width <= 0 || mSize.height <= 0) {
-    return DrawResult::SUCCESS;
-  }
-
-  SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
-  DrawResult result = DrawResult::SUCCESS;
-  RefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();
-  IntRect tmpDTRect;
-
-  if (ctx->CurrentOp() != CompositionOp::OP_OVER || mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
-    gfxRect clipRect = ctx->GetClipExtents();
-    tmpDTRect = RoundedOut(ToRect(clipRect));
-    if (tmpDTRect.IsEmpty()) {
-      return DrawResult::SUCCESS;
-    }
-    RefPtr<DrawTarget> tempDT =
-      gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(ctx->GetDrawTarget(),
-                                                                  tmpDTRect.Size(),
-                                                                  SurfaceFormat::B8G8R8A8);
-    if (!tempDT || !tempDT->IsValid()) {
-      gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
-      return DrawResult::TEMPORARY_ERROR;
-    }
-    tempDT->SetTransform(Matrix::Translation(-tmpDTRect.TopLeft()));
-    ctx = gfxContext::CreatePreservingTransformOrNull(tempDT);
-    if (!ctx) {
-      gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
-      return DrawResult::TEMPORARY_ERROR;
-    }
-  }
-
-  switch (mType) {
-    case eStyleImageType_Image:
-    {
-      CSSIntSize imageSize(nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
-                           nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
-      result =
-        nsLayoutUtils::DrawBackgroundImage(*ctx,
-                                           aPresContext,
-                                           mImageContainer, imageSize,
-                                           samplingFilter,
-                                           aDest, aFill, aRepeatSize,
-                                           aAnchor, aDirtyRect,
-                                           ConvertImageRendererToDrawFlags(mFlags),
-                                           mExtendMode, aOpacity);
-      break;
-    }
-    case eStyleImageType_Gradient:
-    {
-      nsCSSRendering::PaintGradient(aPresContext, aRenderingContext,
-                                    mGradientData, aDirtyRect,
-                                    aDest, aFill, aRepeatSize, aSrc, mSize,
-                                    aOpacity);
-      break;
-    }
-    case eStyleImageType_Element:
-    {
-      RefPtr<gfxDrawable> drawable = DrawableForElement(aDest,
-                                                          aRenderingContext);
-      if (!drawable) {
-        NS_WARNING("Could not create drawable for element");
-        return DrawResult::TEMPORARY_ERROR;
-      }
-
-      nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
-      result =
-        nsLayoutUtils::DrawImage(*ctx,
-                                 aPresContext, image,
-                                 samplingFilter, aDest, aFill, aAnchor, aDirtyRect,
-                                 ConvertImageRendererToDrawFlags(mFlags),
-                                 aOpacity);
-      break;
-    }
-    case eStyleImageType_Null:
-    default:
-      break;
-  }
-
-  if (!tmpDTRect.IsEmpty()) {
-    RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
-    if (mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
-      RefPtr<DataSourceSurface> maskData = surf->GetDataSurface();
-      DataSourceSurface::MappedSurface map;
-      if (!maskData->Map(DataSourceSurface::MapType::WRITE, &map)) {
-        return result;
-      }
-
-      RGBALuminanceOperation(map.mData, map.mStride, maskData->GetSize());
-      maskData->Unmap();
-      surf = maskData;
-    }
-
-    DrawTarget* dt = aRenderingContext.ThebesContext()->GetDrawTarget();
-    dt->DrawSurface(surf, Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height),
-                    Rect(0, 0, tmpDTRect.width, tmpDTRect.height),
-                    DrawSurfaceOptions(SamplingFilter::POINT),
-                    DrawOptions(1.0f, aRenderingContext.ThebesContext()->CurrentOp()));
-  }
-
-  return result;
-}
-
-already_AddRefed<gfxDrawable>
-nsImageRenderer::DrawableForElement(const nsRect& aImageRect,
-                                    nsRenderingContext&  aRenderingContext)
-{
-  NS_ASSERTION(mType == eStyleImageType_Element,
-               "DrawableForElement only makes sense if backed by an element");
-  if (mPaintServerFrame) {
-    // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here,
-    // DrawableFromPaintServer would have to return a DrawResult indicating
-    // whether any images could not be painted because they weren't fully
-    // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all
-    // problems, as it won't help if there are image which haven't finished
-    // loading, but it's better than nothing.
-    int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
-    nsRect destRect = aImageRect - aImageRect.TopLeft();
-    nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
-    IntSize imageSize(roundedOut.width, roundedOut.height);
-    RefPtr<gfxDrawable> drawable =
-      nsSVGIntegrationUtils::DrawableFromPaintServer(
-        mPaintServerFrame, mForFrame, mSize, imageSize,
-        aRenderingContext.GetDrawTarget(),
-        aRenderingContext.ThebesContext()->CurrentMatrix(),
-        nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES);
-
-    return drawable.forget();
-  }
-  NS_ASSERTION(mImageElementSurface.GetSourceSurface(), "Surface should be ready.");
-  RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
-                                mImageElementSurface.GetSourceSurface().get(),
-                                mImageElementSurface.mSize);
-  return drawable.forget();
-}
-
-DrawResult
-nsImageRenderer::DrawLayer(nsPresContext*       aPresContext,
-                           nsRenderingContext&  aRenderingContext,
-                           const nsRect&        aDest,
-                           const nsRect&        aFill,
-                           const nsPoint&       aAnchor,
-                           const nsRect&        aDirty,
-                           const nsSize&        aRepeatSize,
-                           float                aOpacity)
-{
-  if (!IsReady()) {
-    NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
-    return DrawResult::TEMPORARY_ERROR;
-  }
-  if (aDest.IsEmpty() || aFill.IsEmpty() ||
-      mSize.width <= 0 || mSize.height <= 0) {
-    return DrawResult::SUCCESS;
-  }
-
-  return Draw(aPresContext, aRenderingContext,
-              aDirty, aDest, aFill, aAnchor, aRepeatSize,
-              CSSIntRect(0, 0,
-                         nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
-                         nsPresContext::AppUnitsToIntCSSPixels(mSize.height)),
-              aOpacity);
-}
-
-/**
- * Compute the size and position of the master copy of the image. I.e., a single
- * tile used to fill the dest rect.
- * aFill The destination rect to be filled
- * aHFill and aVFill are the repeat patterns for the component -
- * NS_STYLE_BORDER_IMAGE_REPEAT_* - i.e., how a tiling unit is used to fill aFill
- * aUnitSize The size of the source rect in dest coords.
- */
-static nsRect
-ComputeTile(nsRect&              aFill,
-            uint8_t              aHFill,
-            uint8_t              aVFill,
-            const nsSize&        aUnitSize,
-            nsSize&              aRepeatSize)
-{
-  nsRect tile;
-  switch (aHFill) {
-  case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
-    tile.x = aFill.x;
-    tile.width = aFill.width;
-    aRepeatSize.width = tile.width;
-    break;
-  case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
-    tile.x = aFill.x + aFill.width/2 - aUnitSize.width/2;
-    tile.width = aUnitSize.width;
-    aRepeatSize.width = tile.width;
-    break;
-  case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
-    tile.x = aFill.x;
-    tile.width = ComputeRoundedSize(aUnitSize.width, aFill.width);
-    aRepeatSize.width = tile.width;
-    break;
-  case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
-    {
-      nscoord space;
-      aRepeatSize.width =
-        ComputeBorderSpacedRepeatSize(aUnitSize.width, aFill.width, space);
-      tile.x = aFill.x + space;
-      tile.width = aUnitSize.width;
-      aFill.x = tile.x;
-      aFill.width = aFill.width - space * 2;
-    }
-    break;
-  default:
-    NS_NOTREACHED("unrecognized border-image fill style");
-  }
-
-  switch (aVFill) {
-  case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
-    tile.y = aFill.y;
-    tile.height = aFill.height;
-    aRepeatSize.height = tile.height;
-    break;
-  case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
-    tile.y = aFill.y + aFill.height/2 - aUnitSize.height/2;
-    tile.height = aUnitSize.height;
-    aRepeatSize.height = tile.height;
-    break;
-  case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
-    tile.y = aFill.y;
-    tile.height = ComputeRoundedSize(aUnitSize.height, aFill.height);
-    aRepeatSize.height = tile.height;
-    break;
-  case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
-    {
-      nscoord space;
-      aRepeatSize.height =
-        ComputeBorderSpacedRepeatSize(aUnitSize.height, aFill.height, space);
-      tile.y = aFill.y + space;
-      tile.height = aUnitSize.height;
-      aFill.y = tile.y;
-      aFill.height = aFill.height - space * 2;
-    }
-    break;
-  default:
-    NS_NOTREACHED("unrecognized border-image fill style");
-  }
-
-  return tile;
-}
-
-/**
- * Returns true if the given set of arguments will require the tiles which fill
- * the dest rect to be scaled from the source tile. See comment on ComputeTile
- * for argument descriptions.
- */
-static bool
-RequiresScaling(const nsRect&        aFill,
-                uint8_t              aHFill,
-                uint8_t              aVFill,
-                const nsSize&        aUnitSize)
-{
-  // If we have no tiling in either direction, we can skip the intermediate
-  // scaling step.
-  return (aHFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH ||
-          aVFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH) &&
-         (aUnitSize.width != aFill.width ||
-          aUnitSize.height != aFill.height);
-}
-
-DrawResult
-nsImageRenderer::DrawBorderImageComponent(nsPresContext*       aPresContext,
-                                          nsRenderingContext&  aRenderingContext,
-                                          const nsRect&        aDirtyRect,
-                                          const nsRect&        aFill,
-                                          const CSSIntRect&    aSrc,
-                                          uint8_t              aHFill,
-                                          uint8_t              aVFill,
-                                          const nsSize&        aUnitSize,
-                                          uint8_t              aIndex,
-                                          const Maybe<nsSize>& aSVGViewportSize,
-                                          const bool           aHasIntrinsicRatio)
-{
-  if (!IsReady()) {
-    NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
-    return DrawResult::BAD_ARGS;
-  }
-  if (aFill.IsEmpty() || aSrc.IsEmpty()) {
-    return DrawResult::SUCCESS;
-  }
-
-  if (mType == eStyleImageType_Image || mType == eStyleImageType_Element) {
-    nsCOMPtr<imgIContainer> subImage;
-
-    // To draw one portion of an image into a border component, we stretch that
-    // portion to match the size of that border component and then draw onto.
-    // However, preserveAspectRatio attribute of a SVG image may break this rule.
-    // To get correct rendering result, we add
-    // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore
-    // preserveAspectRatio attribute, and always do non-uniform stretch.
-    uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) |
-                           imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE;
-    // For those SVG image sources which don't have fixed aspect ratio (i.e.
-    // without viewport size and viewBox), we should scale the source uniformly
-    // after the viewport size is decided by "Default Sizing Algorithm".
-    if (!aHasIntrinsicRatio) {
-      drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING;
-    }
-    // Retrieve or create the subimage we'll draw.
-    nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height);
-    if (mType == eStyleImageType_Image) {
-      if ((subImage = mImage->GetSubImage(aIndex)) == nullptr) {
-        subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize);
-        mImage->SetSubImage(aIndex, subImage);
-      }
-    } else {
-      // This path, for eStyleImageType_Element, is currently slower than it
-      // needs to be because we don't cache anything. (In particular, if we have
-      // to draw to a temporary surface inside ClippedImage, we don't cache that
-      // temporary surface since we immediately throw the ClippedImage we create
-      // here away.) However, if we did cache, we'd need to know when to
-      // invalidate that cache, and it's not clear that it's worth the trouble
-      // since using border-image with -moz-element is rare.
-
-      RefPtr<gfxDrawable> drawable = DrawableForElement(nsRect(nsPoint(), mSize),
-                                                          aRenderingContext);
-      if (!drawable) {
-        NS_WARNING("Could not create drawable for element");
-        return DrawResult::TEMPORARY_ERROR;
-      }
-
-      nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
-      subImage = ImageOps::Clip(image, srcRect, aSVGViewportSize);
-    }
-
-    MOZ_ASSERT_IF(aSVGViewportSize,
-                  subImage->GetType() == imgIContainer::TYPE_VECTOR);
-
-    SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
-
-    if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
-      return nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(),
-                                            aPresContext,
-                                            subImage,
-                                            samplingFilter,
-                                            aFill, aDirtyRect,
-                                            /* no SVGImageContext */ Nothing(),
-                                            drawFlags);
-    }
-
-    nsSize repeatSize;
-    nsRect fillRect(aFill);
-    nsRect tile = ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize);
-    CSSIntSize imageSize(srcRect.width, srcRect.height);
-    return nsLayoutUtils::DrawBackgroundImage(*aRenderingContext.ThebesContext(),
-                                              aPresContext,
-                                              subImage, imageSize, samplingFilter,
-                                              tile, fillRect, repeatSize,
-                                              tile.TopLeft(), aDirtyRect,
-                                              drawFlags,
-                                              ExtendMode::CLAMP, 1.0);
-  }
-
-  nsSize repeatSize(aFill.Size());
-  nsRect fillRect(aFill);
-  nsRect destTile = RequiresScaling(fillRect, aHFill, aVFill, aUnitSize)
-                  ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize)
-                  : fillRect;
-  return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile,
-              fillRect, destTile.TopLeft(), repeatSize, aSrc);
-}
-
-bool
-nsImageRenderer::IsRasterImage()
-{
-  if (mType != eStyleImageType_Image || !mImageContainer)
-    return false;
-  return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
-}
-
-bool
-nsImageRenderer::IsAnimatedImage()
-{
-  if (mType != eStyleImageType_Image || !mImageContainer)
-    return false;
-  bool animated = false;
-  if (NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated)
-    return true;
-
-  return false;
-}
-
-already_AddRefed<imgIContainer>
-nsImageRenderer::GetImage()
-{
-  if (mType != eStyleImageType_Image || !mImageContainer) {
-    return nullptr;
-  }
-
-  nsCOMPtr<imgIContainer> image = mImageContainer;
-  return image.forget();
-}
-
-void
-nsImageRenderer::PurgeCacheForViewportChange(
-  const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio)
-{
-  // Check if we should flush the cached data - only vector images need to do
-  // the check since they might not have fixed ratio.
-  if (mImageContainer &&
-      mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
-    mImage->PurgeCacheForViewportChange(aSVGViewportSize, aHasIntrinsicRatio);
-  }
-}
-
 #define MAX_BLUR_RADIUS 300
 #define MAX_SPREAD_RADIUS 50
 
 static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
                                          int32_t aAppUnitsPerDevPixel,
                                          gfxFloat aScaleX,
                                          gfxFloat aScaleY)
 {
--- a/layout/painting/nsCSSRendering.h
+++ b/layout/painting/nsCSSRendering.h
@@ -12,314 +12,50 @@
 #include "gfxContext.h"
 #include "imgIContainer.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/TypedEnumBits.h"
 #include "nsLayoutUtils.h"
 #include "nsStyleStruct.h"
 #include "nsIFrame.h"
+#include "nsImageRenderer.h"
 #include "nsCSSRenderingBorders.h"
 
-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
-// in computing image sizes. Either or both of the width and height might be
-// given. A ratio of width to height may also be given. If we at least two
-// of these then we can compute a concrete size, that is a width and height.
-struct CSSSizeOrRatio
-{
-  CSSSizeOrRatio()
-    : mRatio(0, 0)
-    , mHasWidth(false)
-    , mHasHeight(false) {}
-
-  bool CanComputeConcreteSize() const
-  {
-    return mHasWidth + mHasHeight + HasRatio() >= 2;
-  }
-  bool IsConcrete() const { return mHasWidth && mHasHeight; }
-  bool HasRatio() const { return mRatio.width > 0 && mRatio.height > 0; }
-  bool IsEmpty() const
-  {
-    return (mHasWidth && mWidth <= 0) ||
-           (mHasHeight && mHeight <= 0) ||
-           mRatio.width <= 0 || mRatio.height <= 0; 
-  }
-
-  // CanComputeConcreteSize must return true when ComputeConcreteSize is
-  // called.
-  nsSize ComputeConcreteSize() const;
-
-  void SetWidth(nscoord aWidth)
-  {
-    mWidth = aWidth;
-    mHasWidth = true;
-    if (mHasHeight) {
-      mRatio = nsSize(mWidth, mHeight);
-    }
-  }
-  void SetHeight(nscoord aHeight)
-  {
-    mHeight = aHeight;
-    mHasHeight = true;
-    if (mHasWidth) {
-      mRatio = nsSize(mWidth, mHeight);
-    }
-  }
-  void SetSize(const nsSize& aSize)
-  {
-    mWidth = aSize.width;
-    mHeight = aSize.height;
-    mHasWidth = true;
-    mHasHeight = true;
-    mRatio = aSize;    
-  }
-  void SetRatio(const nsSize& aRatio)
-  {
-    MOZ_ASSERT(!mHasWidth || !mHasHeight,
-               "Probably shouldn't be setting a ratio if we have a concrete size");
-    mRatio = aRatio;
-  }
-
-  nsSize mRatio;
-  nscoord mWidth;
-  nscoord mHeight;
-  bool mHasWidth;
-  bool mHasHeight;
-};
-
 enum class PaintBorderFlags : uint8_t
 {
   SYNC_DECODE_IMAGES = 1 << 0
 };
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PaintBorderFlags)
 
 } // namespace mozilla
 
 /**
- * This is a small wrapper class to encapsulate image drawing that can draw an
- * nsStyleImage image, which may internally be a real image, a sub image, or a
- * CSS gradient.
- *
- * @note Always call the member functions in the order of PrepareImage(),
- * SetSize(), and Draw*().
- */
-class nsImageRenderer {
-public:
-  typedef mozilla::image::DrawResult DrawResult;
-  typedef mozilla::layers::LayerManager LayerManager;
-  typedef mozilla::layers::ImageContainer ImageContainer;
-
-  enum {
-    FLAG_SYNC_DECODE_IMAGES = 0x01,
-    FLAG_PAINTING_TO_WINDOW = 0x02
-  };
-  enum FitType
-  {
-    CONTAIN,
-    COVER
-  };
-
-  nsImageRenderer(nsIFrame* aForFrame, const nsStyleImage* aImage, uint32_t aFlags);
-  ~nsImageRenderer();
-  /**
-   * Populates member variables to get ready for rendering.
-   * @return true iff the image is ready, and there is at least a pixel to
-   * draw.
-   */
-  bool PrepareImage();
-
-  /**
-   * The three Compute*Size functions correspond to the sizing algorthms and
-   * definitions from the CSS Image Values and Replaced Content spec. See
-   * http://dev.w3.org/csswg/css-images-3/#sizing .
-   */
-
-  /**
-   * Compute the intrinsic size of the image as defined in the CSS Image Values
-   * spec. The intrinsic size is the unscaled size which the image would ideally
-   * like to be in app units.
-   */
-  mozilla::CSSSizeOrRatio ComputeIntrinsicSize();
-
-  /**
-   * Computes the placement for a background image, or for the image data
-   * inside of a replaced element.
-   *
-   * @param aPos The CSS <position> value that specifies the image's position.
-   * @param aOriginBounds The box to which the tiling position should be
-   *          relative. For background images, this should correspond to
-   *          'background-origin' for the frame, except when painting on the
-   *          canvas, in which case the origin bounds should be the bounds
-   *          of the root element's frame. For a replaced element, this should
-   *          be the element's content-box.
-   * @param aTopLeft [out] The top-left corner where an image tile should be
-   *          drawn.
-   * @param aAnchorPoint [out] A point which should be pixel-aligned by
-   *          nsLayoutUtils::DrawImage. This is the same as aTopLeft, unless
-   *          CSS specifies a percentage (including 'right' or 'bottom'), in
-   *          which case it's that percentage within of aOriginBounds. So
-   *          'right' would set aAnchorPoint.x to aOriginBounds.XMost().
-   *
-   * Points are returned relative to aOriginBounds.
-   */
-  static void ComputeObjectAnchorPoint(const mozilla::Position& aPos,
-                                       const nsSize& aOriginBounds,
-                                       const nsSize& aImageSize,
-                                       nsPoint* aTopLeft,
-                                       nsPoint* aAnchorPoint);
-
-  /**
-   * Compute the size of the rendered image using either the 'cover' or
-   * 'contain' constraints (aFitType).
-   * aIntrinsicRatio may be an invalid ratio, that is one or both of its
-   * dimensions can be less than or equal to zero.
-   */
-  static nsSize ComputeConstrainedSize(const nsSize& aConstrainingSize,
-                                       const nsSize& aIntrinsicRatio,
-                                       FitType aFitType);
-  /**
-   * Compute the size of the rendered image (the concrete size) where no cover/
-   * contain constraints are given. The 'default algorithm' from the CSS Image
-   * Values spec.
-   */
-  static nsSize ComputeConcreteSize(const mozilla::CSSSizeOrRatio& aSpecifiedSize,
-                                    const mozilla::CSSSizeOrRatio& aIntrinsicSize,
-                                    const nsSize& aDefaultSize);
-
-  /**
-   * Set this image's preferred size. This will be its intrinsic size where
-   * specified and the default size where it is not. Used as the unscaled size
-   * when rendering the image.
-   */
-  void SetPreferredSize(const mozilla::CSSSizeOrRatio& aIntrinsicSize,
-                        const nsSize& aDefaultSize);
-
-  /**
-   * Draws the image to the target rendering context using
-   * {background|mask}-specific arguments.
-   * @see nsLayoutUtils::DrawImage() for parameters.
-   */
-  DrawResult DrawLayer(nsPresContext*       aPresContext,
-                       nsRenderingContext&  aRenderingContext,
-                       const nsRect&        aDest,
-                       const nsRect&        aFill,
-                       const nsPoint&       aAnchor,
-                       const nsRect&        aDirty,
-                       const nsSize&        aRepeatSize,
-                       float                aOpacity);
-
-  /**
-   * Draw the image to a single component of a border-image style rendering.
-   * aFill The destination rect to be drawn into
-   * aSrc is the part of the image to be rendered into a tile (aUnitSize in
-   * aFill), if aSrc and the dest tile are different sizes, the image will be
-   * scaled to map aSrc onto the dest tile.
-   * aHFill and aVFill are the repeat patterns for the component -
-   * NS_STYLE_BORDER_IMAGE_REPEAT_*
-   * aUnitSize The scaled size of a single source rect (in destination coords)
-   * aIndex identifies the component: 0 1 2
-   *                                  3 4 5
-   *                                  6 7 8
-   * aSVGViewportSize The image size evaluated by default sizing algorithm.
-   * Pass Nothing() if we can read a valid viewport size or aspect-ratio from
-   * the drawing image directly, otherwise, pass Some() with viewport size
-   * evaluated from default sizing algorithm.
-   * aHasIntrinsicRatio is used to record if the source image has fixed
-   * intrinsic ratio.
-   */
-  DrawResult
-  DrawBorderImageComponent(nsPresContext*       aPresContext,
-                           nsRenderingContext&  aRenderingContext,
-                           const nsRect&        aDirtyRect,
-                           const nsRect&        aFill,
-                           const mozilla::CSSIntRect& aSrc,
-                           uint8_t              aHFill,
-                           uint8_t              aVFill,
-                           const nsSize&        aUnitSize,
-                           uint8_t              aIndex,
-                           const mozilla::Maybe<nsSize>& aSVGViewportSize,
-                           const bool           aHasIntrinsicRatio);
-
-  bool IsRasterImage();
-  bool IsAnimatedImage();
-
-  /// Retrieves the image associated with this nsImageRenderer, if there is one.
-  already_AddRefed<imgIContainer> GetImage();
-
-  bool IsReady() const { return mPrepareResult == DrawResult::SUCCESS; }
-  DrawResult PrepareResult() const { return mPrepareResult; }
-  void SetExtendMode(mozilla::gfx::ExtendMode aMode) { mExtendMode = aMode; }
-  void SetMaskOp(uint8_t aMaskOp) { mMaskOp = aMaskOp; }
-  void PurgeCacheForViewportChange(const mozilla::Maybe<nsSize>& aSVGViewportSize,
-                                   const bool aHasRatio);
-
-private:
-  /**
-   * Draws the image to the target rendering context.
-   * aSrc is a rect on the source image which will be mapped to aDest; it's
-   * currently only used for gradients.
-   *
-   * @see nsLayoutUtils::DrawImage() for other parameters.
-   */
-  DrawResult Draw(nsPresContext*       aPresContext,
-                  nsRenderingContext&  aRenderingContext,
-                  const nsRect&        aDirtyRect,
-                  const nsRect&        aDest,
-                  const nsRect&        aFill,
-                  const nsPoint&       aAnchor,
-                  const nsSize&        aRepeatSize,
-                  const mozilla::CSSIntRect& aSrc,
-                  float                aOpacity = 1.0);
-
-  /**
-   * Helper method for creating a gfxDrawable from mPaintServerFrame or
-   * mImageElementSurface.
-   * Requires mType is eStyleImageType_Element.
-   * Returns null if we cannot create the drawable.
-   */
-  already_AddRefed<gfxDrawable> DrawableForElement(const nsRect& aImageRect,
-                                                   nsRenderingContext&  aRenderingContext);
-
-  nsIFrame*                 mForFrame;
-  const nsStyleImage*       mImage;
-  nsStyleImageType          mType;
-  nsCOMPtr<imgIContainer>   mImageContainer;
-  RefPtr<nsStyleGradient> mGradientData;
-  nsIFrame*                 mPaintServerFrame;
-  nsLayoutUtils::SurfaceFromElementResult mImageElementSurface;
-  DrawResult                mPrepareResult;
-  nsSize                    mSize; // unscaled size of the image, in app units
-  uint32_t                  mFlags;
-  mozilla::gfx::ExtendMode  mExtendMode;
-  uint8_t                   mMaskOp;
-};
-
-/**
  * A struct representing all the information needed to paint a background
  * image to some target, taking into account all CSS background-* properties.
  * See PrepareImageLayer.
  */
 struct nsBackgroundLayerState {
   typedef mozilla::gfx::CompositionOp CompositionOp;
+  typedef mozilla::nsImageRenderer nsImageRenderer;
 
   /**
    * @param aFlags some combination of nsCSSRendering::PAINTBG_* flags
    */
   nsBackgroundLayerState(nsIFrame* aForFrame, const nsStyleImage* aImage,
                          uint32_t aFlags)
     : mImageRenderer(aForFrame, aImage, aFlags)
   {}
--- a/layout/painting/nsCSSRenderingBorders.h
+++ b/layout/painting/nsCSSRenderingBorders.h
@@ -10,16 +10,17 @@
 #include "gfxRect.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/BezierUtils.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/RefPtr.h"
 #include "nsColor.h"
 #include "nsCOMPtr.h"
+#include "nsImageRenderer.h"
 #include "nsStyleConsts.h"
 #include "nsStyleStruct.h"
 #include "nsPresContext.h"
 
 struct nsBorderColors;
 class nsDisplayBorder;
 
 namespace mozilla {
new file mode 100644
--- /dev/null
+++ b/layout/painting/nsImageRenderer.cpp
@@ -0,0 +1,868 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=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/. */
+
+/* utility functions for drawing borders and backgrounds */
+
+#include "nsImageRenderer.h"
+
+nsSize
+CSSSizeOrRatio::ComputeConcreteSize() const
+{
+  NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
+  if (mHasWidth && mHasHeight) {
+    return nsSize(mWidth, mHeight);
+  }
+  if (mHasWidth) {
+    nscoord height = NSCoordSaturatingNonnegativeMultiply(
+      mWidth,
+      double(mRatio.height) / mRatio.width);
+    return nsSize(mWidth, height);
+  }
+
+  MOZ_ASSERT(mHasHeight);
+  nscoord width = NSCoordSaturatingNonnegativeMultiply(
+    mHeight,
+    double(mRatio.width) / mRatio.height);
+  return nsSize(width, mHeight);
+}
+
+nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame,
+                                 const nsStyleImage* aImage,
+                                 uint32_t aFlags)
+  : mForFrame(aForFrame)
+  , mImage(aImage)
+  , mType(aImage->GetType())
+  , mImageContainer(nullptr)
+  , mGradientData(nullptr)
+  , mPaintServerFrame(nullptr)
+  , mPrepareResult(DrawResult::NOT_READY)
+  , mSize(0, 0)
+  , mFlags(aFlags)
+  , mExtendMode(ExtendMode::CLAMP)
+  , mMaskOp(NS_STYLE_MASK_MODE_MATCH_SOURCE)
+{
+}
+
+nsImageRenderer::~nsImageRenderer()
+{
+}
+
+static bool
+ShouldTreatAsCompleteDueToSyncDecode(const nsStyleImage* aImage,
+                                     uint32_t aFlags)
+{
+  if (!(aFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES)) {
+    return false;
+  }
+
+  if (aImage->GetType() != eStyleImageType_Image) {
+    return false;
+  }
+
+  imgRequestProxy* req = aImage->GetImageData();
+  if (!req) {
+    return false;
+  }
+
+  uint32_t status = 0;
+  if (NS_FAILED(req->GetImageStatus(&status))) {
+    return false;
+  }
+
+  if (status & imgIRequest::STATUS_ERROR) {
+    // The image is "complete" since it's a corrupt image. If we created an
+    // imgIContainer at all, return true.
+    nsCOMPtr<imgIContainer> image;
+    req->GetImage(getter_AddRefs(image));
+    return bool(image);
+  }
+
+  if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
+    // We must have loaded all of the image's data and the size must be
+    // available, or else sync decoding won't be able to decode the image.
+    return false;
+  }
+
+  return true;
+}
+
+bool
+nsImageRenderer::PrepareImage()
+{
+  if (mImage->IsEmpty()) {
+    mPrepareResult = DrawResult::BAD_IMAGE;
+    return false;
+  }
+
+  if (!mImage->IsComplete()) {
+    // Make sure the image is actually decoding.
+    bool frameComplete = mImage->StartDecoding();
+
+    // Check again to see if we finished.
+    // We cannot prepare the image for rendering if it is not fully loaded.
+    // Special case: If we requested a sync decode and the image has loaded, push
+    // on through because the Draw() will do a sync decode then.
+    if (!(frameComplete || mImage->IsComplete()) &&
+        !ShouldTreatAsCompleteDueToSyncDecode(mImage, mFlags)) {
+      mPrepareResult = DrawResult::NOT_READY;
+      return false;
+    }
+  }
+
+  switch (mType) {
+    case eStyleImageType_Image: {
+      MOZ_ASSERT(mImage->GetImageData(),
+                 "must have image data, since we checked IsEmpty above");
+      nsCOMPtr<imgIContainer> srcImage;
+      DebugOnly<nsresult> rv =
+        mImage->GetImageData()->GetImage(getter_AddRefs(srcImage));
+      MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage,
+                 "If GetImage() is failing, mImage->IsComplete() "
+                 "should have returned false");
+
+      if (!mImage->GetCropRect()) {
+        mImageContainer.swap(srcImage);
+      } else {
+        nsIntRect actualCropRect;
+        bool isEntireImage;
+        bool success =
+          mImage->ComputeActualCropRect(actualCropRect, &isEntireImage);
+        NS_ASSERTION(success, "ComputeActualCropRect() should not fail here");
+        if (!success || actualCropRect.IsEmpty()) {
+          // The cropped image has zero size
+          mPrepareResult = DrawResult::BAD_IMAGE;
+          return false;
+        }
+        if (isEntireImage) {
+          // The cropped image is identical to the source image
+          mImageContainer.swap(srcImage);
+        } else {
+          nsCOMPtr<imgIContainer> subImage = ImageOps::Clip(srcImage,
+                                                            actualCropRect,
+                                                            Nothing());
+          mImageContainer.swap(subImage);
+        }
+      }
+      mPrepareResult = DrawResult::SUCCESS;
+      break;
+    }
+    case eStyleImageType_Gradient:
+      mGradientData = mImage->GetGradientData();
+      mPrepareResult = DrawResult::SUCCESS;
+      break;
+    case eStyleImageType_Element:
+    {
+      nsAutoString elementId =
+        NS_LITERAL_STRING("#") + nsDependentString(mImage->GetElementId());
+      nsCOMPtr<nsIURI> targetURI;
+      nsCOMPtr<nsIURI> base = mForFrame->GetContent()->GetBaseURI();
+      nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), elementId,
+                                                mForFrame->GetContent()->GetUncomposedDoc(), base);
+      nsSVGPaintingProperty* property = nsSVGEffects::GetPaintingPropertyForURI(
+          targetURI, mForFrame->FirstContinuation(),
+          nsSVGEffects::BackgroundImageProperty());
+      if (!property) {
+        mPrepareResult = DrawResult::BAD_IMAGE;
+        return false;
+      }
+
+      // If the referenced element is an <img>, <canvas>, or <video> element,
+      // prefer SurfaceFromElement as it's more reliable.
+      mImageElementSurface =
+        nsLayoutUtils::SurfaceFromElement(property->GetReferencedElement());
+      if (!mImageElementSurface.GetSourceSurface()) {
+        mPaintServerFrame = property->GetReferencedFrame();
+        if (!mPaintServerFrame) {
+          mPrepareResult = DrawResult::BAD_IMAGE;
+          return false;
+        }
+      }
+
+      mPrepareResult = DrawResult::SUCCESS;
+      break;
+    }
+    case eStyleImageType_Null:
+    default:
+      break;
+  }
+
+  return IsReady();
+}
+
+CSSSizeOrRatio
+nsImageRenderer::ComputeIntrinsicSize()
+{
+  NS_ASSERTION(IsReady(), "Ensure PrepareImage() has returned true "
+                          "before calling me");
+
+  CSSSizeOrRatio result;
+  switch (mType) {
+    case eStyleImageType_Image:
+    {
+      bool haveWidth, haveHeight;
+      CSSIntSize imageIntSize;
+      nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, imageIntSize,
+                                           result.mRatio, haveWidth, haveHeight);
+      if (haveWidth) {
+        result.SetWidth(nsPresContext::CSSPixelsToAppUnits(imageIntSize.width));
+      }
+      if (haveHeight) {
+        result.SetHeight(nsPresContext::CSSPixelsToAppUnits(imageIntSize.height));
+      }
+
+      // If we know the aspect ratio and one of the dimensions,
+      // we can compute the other missing width or height.
+      if (!haveHeight && haveWidth && result.mRatio.width != 0) {
+        nscoord intrinsicHeight =
+          NSCoordSaturatingNonnegativeMultiply(imageIntSize.width,
+                                               float(result.mRatio.height) /
+                                               float(result.mRatio.width));
+        result.SetHeight(nsPresContext::CSSPixelsToAppUnits(intrinsicHeight));
+      } else if (haveHeight && !haveWidth && result.mRatio.height != 0) {
+        nscoord intrinsicWidth =
+          NSCoordSaturatingNonnegativeMultiply(imageIntSize.height,
+                                               float(result.mRatio.width) /
+                                               float(result.mRatio.height));
+        result.SetWidth(nsPresContext::CSSPixelsToAppUnits(intrinsicWidth));
+      }
+
+      break;
+    }
+    case eStyleImageType_Element:
+    {
+      // XXX element() should have the width/height of the referenced element,
+      //     and that element's ratio, if it matches.  If it doesn't match, it
+      //     should have no width/height or ratio.  See element() in CSS images:
+      //     <http://dev.w3.org/csswg/css-images-4/#element-notation>.
+      //     Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize
+      //     when fixing this!
+      if (mPaintServerFrame) {
+        // SVG images have no intrinsic size
+        if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) {
+          // The intrinsic image size for a generic nsIFrame paint server is
+          // the union of the border-box rects of all of its continuations,
+          // rounded to device pixels.
+          int32_t appUnitsPerDevPixel =
+            mForFrame->PresContext()->AppUnitsPerDevPixel();
+          result.SetSize(
+            IntSizeToAppUnits(
+              nsSVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame).
+                ToNearestPixels(appUnitsPerDevPixel),
+              appUnitsPerDevPixel));
+        }
+      } else {
+        NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
+                     "Surface should be ready.");
+        IntSize surfaceSize = mImageElementSurface.mSize;
+        result.SetSize(
+          nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
+                 nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
+      }
+      break;
+    }
+    case eStyleImageType_Gradient:
+      // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
+      // intrinsic dimensions.
+    case eStyleImageType_Null:
+    default:
+      break;
+  }
+
+  return result;
+}
+
+/* static */ nsSize
+nsImageRenderer::ComputeConcreteSize(const CSSSizeOrRatio& aSpecifiedSize,
+                                     const CSSSizeOrRatio& aIntrinsicSize,
+                                     const nsSize& aDefaultSize)
+{
+  // The specified size is fully specified, just use that
+  if (aSpecifiedSize.IsConcrete()) {
+    return aSpecifiedSize.ComputeConcreteSize();
+  }
+
+  MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight);
+
+  if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) {
+    // no specified size, try using the intrinsic size
+    if (aIntrinsicSize.CanComputeConcreteSize()) {
+      return aIntrinsicSize.ComputeConcreteSize();
+    }
+
+    if (aIntrinsicSize.mHasWidth) {
+      return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height);
+    }
+    if (aIntrinsicSize.mHasHeight) {
+      return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight);
+    }
+
+    // couldn't use the intrinsic size either, revert to using the default size
+    return ComputeConstrainedSize(aDefaultSize,
+                                  aIntrinsicSize.mRatio,
+                                  CONTAIN);
+  }
+
+  MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
+
+  // The specified height is partial, try to compute the missing part.
+  if (aSpecifiedSize.mHasWidth) {
+    nscoord height;
+    if (aIntrinsicSize.HasRatio()) {
+      height = NSCoordSaturatingNonnegativeMultiply(
+        aSpecifiedSize.mWidth,
+        double(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width);
+    } else if (aIntrinsicSize.mHasHeight) {
+      height = aIntrinsicSize.mHeight;
+    } else {
+      height = aDefaultSize.height;
+    }
+    return nsSize(aSpecifiedSize.mWidth, height);
+  }
+
+  MOZ_ASSERT(aSpecifiedSize.mHasHeight);
+  nscoord width;
+  if (aIntrinsicSize.HasRatio()) {
+    width = NSCoordSaturatingNonnegativeMultiply(
+      aSpecifiedSize.mHeight,
+      double(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height);
+  } else if (aIntrinsicSize.mHasWidth) {
+    width = aIntrinsicSize.mWidth;
+  } else {
+    width = aDefaultSize.width;
+  }
+  return nsSize(width, aSpecifiedSize.mHeight);
+}
+
+/* static */ nsSize
+nsImageRenderer::ComputeConstrainedSize(const nsSize& aConstrainingSize,
+                                        const nsSize& aIntrinsicRatio,
+                                        FitType aFitType)
+{
+  if (aIntrinsicRatio.width <= 0 && aIntrinsicRatio.height <= 0) {
+    return aConstrainingSize;
+  }
+
+  float scaleX = double(aConstrainingSize.width) / aIntrinsicRatio.width;
+  float scaleY = double(aConstrainingSize.height) / aIntrinsicRatio.height;
+  nsSize size;
+  if ((aFitType == CONTAIN) == (scaleX < scaleY)) {
+    size.width = aConstrainingSize.width;
+    size.height = NSCoordSaturatingNonnegativeMultiply(
+                    aIntrinsicRatio.height, scaleX);
+    // If we're reducing the size by less than one css pixel, then just use the
+    // constraining size.
+    if (aFitType == CONTAIN && aConstrainingSize.height - size.height < nsPresContext::AppUnitsPerCSSPixel()) {
+      size.height = aConstrainingSize.height;
+    }
+  } else {
+    size.width = NSCoordSaturatingNonnegativeMultiply(
+                   aIntrinsicRatio.width, scaleY);
+    if (aFitType == CONTAIN && aConstrainingSize.width - size.width < nsPresContext::AppUnitsPerCSSPixel()) {
+      size.width = aConstrainingSize.width;
+    }
+    size.height = aConstrainingSize.height;
+  }
+  return size;
+}
+
+/**
+ * mSize is the image's "preferred" size for this particular rendering, while
+ * the drawn (aka concrete) size is the actual rendered size after accounting
+ * for background-size etc..  The preferred size is most often the image's
+ * intrinsic dimensions.  But for images with incomplete intrinsic dimensions,
+ * the preferred size varies, depending on the specified and default sizes, see
+ * nsImageRenderer::Compute*Size.
+ *
+ * This distinction is necessary because the components of a vector image are
+ * specified with respect to its preferred size for a rendering situation, not
+ * to its actual rendered size.  For example, consider a 4px wide background
+ * vector image with no height which contains a left-aligned
+ * 2px wide black rectangle with height 100%.  If the background-size width is
+ * auto (or 4px), the vector image will render 4px wide, and the black rectangle
+ * will be 2px wide.  If the background-size width is 8px, the vector image will
+ * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
+ * In both cases mSize.width will be 4px; but in the first case the returned
+ * width will be 4px, while in the second case the returned width will be 8px.
+ */
+void
+nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize,
+                                  const nsSize& aDefaultSize)
+{
+  mSize.width = aIntrinsicSize.mHasWidth
+                  ? aIntrinsicSize.mWidth
+                  : aDefaultSize.width;
+  mSize.height = aIntrinsicSize.mHasHeight
+                  ? aIntrinsicSize.mHeight
+                  : aDefaultSize.height;
+}
+
+// Convert from nsImageRenderer flags to the flags we want to use for drawing in
+// the imgIContainer namespace.
+static uint32_t
+ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)
+{
+  uint32_t drawFlags = imgIContainer::FLAG_NONE;
+  if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
+    drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
+  }
+  if (aImageRendererFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) {
+    drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
+  }
+  return drawFlags;
+}
+
+/*
+ *  SVG11: A luminanceToAlpha operation is equivalent to the following matrix operation:                                                   |
+ *  | R' |     |      0        0        0  0  0 |   | R |
+ *  | G' |     |      0        0        0  0  0 |   | G |
+ *  | B' |  =  |      0        0        0  0  0 | * | B |
+ *  | A' |     | 0.2125   0.7154   0.0721  0  0 |   | A |
+ *  | 1  |     |      0        0        0  0  1 |   | 1 |
+ */
+static void
+RGBALuminanceOperation(uint8_t *aData,
+                       int32_t aStride,
+                       const IntSize &aSize)
+{
+  int32_t redFactor = 55;    // 256 * 0.2125
+  int32_t greenFactor = 183; // 256 * 0.7154
+  int32_t blueFactor = 18;   // 256 * 0.0721
+
+  for (int32_t y = 0; y < aSize.height; y++) {
+    uint32_t *pixel = (uint32_t*)(aData + aStride * y);
+    for (int32_t x = 0; x < aSize.width; x++) {
+      *pixel = (((((*pixel & 0x00FF0000) >> 16) * redFactor) +
+                 (((*pixel & 0x0000FF00) >>  8) * greenFactor) +
+                  ((*pixel & 0x000000FF)        * blueFactor)) >> 8) << 24;
+      pixel++;
+    }
+  }
+}
+
+
+DrawResult
+nsImageRenderer::Draw(nsPresContext*       aPresContext,
+                      nsRenderingContext&  aRenderingContext,
+                      const nsRect&        aDirtyRect,
+                      const nsRect&        aDest,
+                      const nsRect&        aFill,
+                      const nsPoint&       aAnchor,
+                      const nsSize&        aRepeatSize,
+                      const CSSIntRect&    aSrc,
+                      float                aOpacity)
+{
+  if (!IsReady()) {
+    NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
+    return DrawResult::TEMPORARY_ERROR;
+  }
+  if (aDest.IsEmpty() || aFill.IsEmpty() ||
+      mSize.width <= 0 || mSize.height <= 0) {
+    return DrawResult::SUCCESS;
+  }
+
+  SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
+  DrawResult result = DrawResult::SUCCESS;
+  RefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();
+  IntRect tmpDTRect;
+
+  if (ctx->CurrentOp() != CompositionOp::OP_OVER || mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
+    gfxRect clipRect = ctx->GetClipExtents();
+    tmpDTRect = RoundedOut(ToRect(clipRect));
+    if (tmpDTRect.IsEmpty()) {
+      return DrawResult::SUCCESS;
+    }
+    RefPtr<DrawTarget> tempDT =
+      gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(ctx->GetDrawTarget(),
+                                                                  tmpDTRect.Size(),
+                                                                  SurfaceFormat::B8G8R8A8);
+    if (!tempDT || !tempDT->IsValid()) {
+      gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
+      return DrawResult::TEMPORARY_ERROR;
+    }
+    tempDT->SetTransform(Matrix::Translation(-tmpDTRect.TopLeft()));
+    ctx = gfxContext::CreatePreservingTransformOrNull(tempDT);
+    if (!ctx) {
+      gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
+      return DrawResult::TEMPORARY_ERROR;
+    }
+  }
+
+  switch (mType) {
+    case eStyleImageType_Image:
+    {
+      CSSIntSize imageSize(nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
+                           nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
+      result =
+        nsLayoutUtils::DrawBackgroundImage(*ctx,
+                                           aPresContext,
+                                           mImageContainer, imageSize,
+                                           samplingFilter,
+                                           aDest, aFill, aRepeatSize,
+                                           aAnchor, aDirtyRect,
+                                           ConvertImageRendererToDrawFlags(mFlags),
+                                           mExtendMode, aOpacity);
+      break;
+    }
+    case eStyleImageType_Gradient:
+    {
+      nsCSSRendering::PaintGradient(aPresContext, aRenderingContext,
+                                    mGradientData, aDirtyRect,
+                                    aDest, aFill, aRepeatSize, aSrc, mSize,
+                                    aOpacity);
+      break;
+    }
+    case eStyleImageType_Element:
+    {
+      RefPtr<gfxDrawable> drawable = DrawableForElement(aDest,
+                                                          aRenderingContext);
+      if (!drawable) {
+        NS_WARNING("Could not create drawable for element");
+        return DrawResult::TEMPORARY_ERROR;
+      }
+
+      nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
+      result =
+        nsLayoutUtils::DrawImage(*ctx,
+                                 aPresContext, image,
+                                 samplingFilter, aDest, aFill, aAnchor, aDirtyRect,
+                                 ConvertImageRendererToDrawFlags(mFlags),
+                                 aOpacity);
+      break;
+    }
+    case eStyleImageType_Null:
+    default:
+      break;
+  }
+
+  if (!tmpDTRect.IsEmpty()) {
+    RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
+    if (mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
+      RefPtr<DataSourceSurface> maskData = surf->GetDataSurface();
+      DataSourceSurface::MappedSurface map;
+      if (!maskData->Map(DataSourceSurface::MapType::WRITE, &map)) {
+        return result;
+      }
+
+      RGBALuminanceOperation(map.mData, map.mStride, maskData->GetSize());
+      maskData->Unmap();
+      surf = maskData;
+    }
+
+    DrawTarget* dt = aRenderingContext.ThebesContext()->GetDrawTarget();
+    dt->DrawSurface(surf, Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height),
+                    Rect(0, 0, tmpDTRect.width, tmpDTRect.height),
+                    DrawSurfaceOptions(SamplingFilter::POINT),
+                    DrawOptions(1.0f, aRenderingContext.ThebesContext()->CurrentOp()));
+  }
+
+  return result;
+}
+
+already_AddRefed<gfxDrawable>
+nsImageRenderer::DrawableForElement(const nsRect& aImageRect,
+                                    nsRenderingContext&  aRenderingContext)
+{
+  NS_ASSERTION(mType == eStyleImageType_Element,
+               "DrawableForElement only makes sense if backed by an element");
+  if (mPaintServerFrame) {
+    // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here,
+    // DrawableFromPaintServer would have to return a DrawResult indicating
+    // whether any images could not be painted because they weren't fully
+    // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all
+    // problems, as it won't help if there are image which haven't finished
+    // loading, but it's better than nothing.
+    int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
+    nsRect destRect = aImageRect - aImageRect.TopLeft();
+    nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
+    IntSize imageSize(roundedOut.width, roundedOut.height);
+    RefPtr<gfxDrawable> drawable =
+      nsSVGIntegrationUtils::DrawableFromPaintServer(
+        mPaintServerFrame, mForFrame, mSize, imageSize,
+        aRenderingContext.GetDrawTarget(),
+        aRenderingContext.ThebesContext()->CurrentMatrix(),
+        nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES);
+
+    return drawable.forget();
+  }
+  NS_ASSERTION(mImageElementSurface.GetSourceSurface(), "Surface should be ready.");
+  RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
+                                mImageElementSurface.GetSourceSurface().get(),
+                                mImageElementSurface.mSize);
+  return drawable.forget();
+}
+
+DrawResult
+nsImageRenderer::DrawLayer(nsPresContext*       aPresContext,
+                           nsRenderingContext&  aRenderingContext,
+                           const nsRect&        aDest,
+                           const nsRect&        aFill,
+                           const nsPoint&       aAnchor,
+                           const nsRect&        aDirty,
+                           const nsSize&        aRepeatSize,
+                           float                aOpacity)
+{
+  if (!IsReady()) {
+    NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
+    return DrawResult::TEMPORARY_ERROR;
+  }
+  if (aDest.IsEmpty() || aFill.IsEmpty() ||
+      mSize.width <= 0 || mSize.height <= 0) {
+    return DrawResult::SUCCESS;
+  }
+
+  return Draw(aPresContext, aRenderingContext,
+              aDirty, aDest, aFill, aAnchor, aRepeatSize,
+              CSSIntRect(0, 0,
+                         nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
+                         nsPresContext::AppUnitsToIntCSSPixels(mSize.height)),
+              aOpacity);
+}
+
+/**
+ * Compute the size and position of the master copy of the image. I.e., a single
+ * tile used to fill the dest rect.
+ * aFill The destination rect to be filled
+ * aHFill and aVFill are the repeat patterns for the component -
+ * NS_STYLE_BORDER_IMAGE_REPEAT_* - i.e., how a tiling unit is used to fill aFill
+ * aUnitSize The size of the source rect in dest coords.
+ */
+static nsRect
+ComputeTile(nsRect&              aFill,
+            uint8_t              aHFill,
+            uint8_t              aVFill,
+            const nsSize&        aUnitSize,
+            nsSize&              aRepeatSize)
+{
+  nsRect tile;
+  switch (aHFill) {
+  case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
+    tile.x = aFill.x;
+    tile.width = aFill.width;
+    aRepeatSize.width = tile.width;
+    break;
+  case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
+    tile.x = aFill.x + aFill.width/2 - aUnitSize.width/2;
+    tile.width = aUnitSize.width;
+    aRepeatSize.width = tile.width;
+    break;
+  case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
+    tile.x = aFill.x;
+    tile.width = ComputeRoundedSize(aUnitSize.width, aFill.width);
+    aRepeatSize.width = tile.width;
+    break;
+  case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
+    {
+      nscoord space;
+      aRepeatSize.width =
+        ComputeBorderSpacedRepeatSize(aUnitSize.width, aFill.width, space);
+      tile.x = aFill.x + space;
+      tile.width = aUnitSize.width;
+      aFill.x = tile.x;
+      aFill.width = aFill.width - space * 2;
+    }
+    break;
+  default:
+    NS_NOTREACHED("unrecognized border-image fill style");
+  }
+
+  switch (aVFill) {
+  case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
+    tile.y = aFill.y;
+    tile.height = aFill.height;
+    aRepeatSize.height = tile.height;
+    break;
+  case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
+    tile.y = aFill.y + aFill.height/2 - aUnitSize.height/2;
+    tile.height = aUnitSize.height;
+    aRepeatSize.height = tile.height;
+    break;
+  case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
+    tile.y = aFill.y;
+    tile.height = ComputeRoundedSize(aUnitSize.height, aFill.height);
+    aRepeatSize.height = tile.height;
+    break;
+  case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
+    {
+      nscoord space;
+      aRepeatSize.height =
+        ComputeBorderSpacedRepeatSize(aUnitSize.height, aFill.height, space);
+      tile.y = aFill.y + space;
+      tile.height = aUnitSize.height;
+      aFill.y = tile.y;
+      aFill.height = aFill.height - space * 2;
+    }
+    break;
+  default:
+    NS_NOTREACHED("unrecognized border-image fill style");
+  }
+
+  return tile;
+}
+
+/**
+ * Returns true if the given set of arguments will require the tiles which fill
+ * the dest rect to be scaled from the source tile. See comment on ComputeTile
+ * for argument descriptions.
+ */
+static bool
+RequiresScaling(const nsRect&        aFill,
+                uint8_t              aHFill,
+                uint8_t              aVFill,
+                const nsSize&        aUnitSize)
+{
+  // If we have no tiling in either direction, we can skip the intermediate
+  // scaling step.
+  return (aHFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH ||
+          aVFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH) &&
+         (aUnitSize.width != aFill.width ||
+          aUnitSize.height != aFill.height);
+}
+
+DrawResult
+nsImageRenderer::DrawBorderImageComponent(nsPresContext*       aPresContext,
+                                          nsRenderingContext&  aRenderingContext,
+                                          const nsRect&        aDirtyRect,
+                                          const nsRect&        aFill,
+                                          const CSSIntRect&    aSrc,
+                                          uint8_t              aHFill,
+                                          uint8_t              aVFill,
+                                          const nsSize&        aUnitSize,
+                                          uint8_t              aIndex,
+                                          const Maybe<nsSize>& aSVGViewportSize,
+                                          const bool           aHasIntrinsicRatio)
+{
+  if (!IsReady()) {
+    NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
+    return DrawResult::BAD_ARGS;
+  }
+  if (aFill.IsEmpty() || aSrc.IsEmpty()) {
+    return DrawResult::SUCCESS;
+  }
+
+  if (mType == eStyleImageType_Image || mType == eStyleImageType_Element) {
+    nsCOMPtr<imgIContainer> subImage;
+
+    // To draw one portion of an image into a border component, we stretch that
+    // portion to match the size of that border component and then draw onto.
+    // However, preserveAspectRatio attribute of a SVG image may break this rule.
+    // To get correct rendering result, we add
+    // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore
+    // preserveAspectRatio attribute, and always do non-uniform stretch.
+    uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) |
+                           imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE;
+    // For those SVG image sources which don't have fixed aspect ratio (i.e.
+    // without viewport size and viewBox), we should scale the source uniformly
+    // after the viewport size is decided by "Default Sizing Algorithm".
+    if (!aHasIntrinsicRatio) {
+      drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING;
+    }
+    // Retrieve or create the subimage we'll draw.
+    nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height);
+    if (mType == eStyleImageType_Image) {
+      if ((subImage = mImage->GetSubImage(aIndex)) == nullptr) {
+        subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize);
+        mImage->SetSubImage(aIndex, subImage);
+      }
+    } else {
+      // This path, for eStyleImageType_Element, is currently slower than it
+      // needs to be because we don't cache anything. (In particular, if we have
+      // to draw to a temporary surface inside ClippedImage, we don't cache that
+      // temporary surface since we immediately throw the ClippedImage we create
+      // here away.) However, if we did cache, we'd need to know when to
+      // invalidate that cache, and it's not clear that it's worth the trouble
+      // since using border-image with -moz-element is rare.
+
+      RefPtr<gfxDrawable> drawable = DrawableForElement(nsRect(nsPoint(), mSize),
+                                                          aRenderingContext);
+      if (!drawable) {
+        NS_WARNING("Could not create drawable for element");
+        return DrawResult::TEMPORARY_ERROR;
+      }
+
+      nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
+      subImage = ImageOps::Clip(image, srcRect, aSVGViewportSize);
+    }
+
+    MOZ_ASSERT_IF(aSVGViewportSize,
+                  subImage->GetType() == imgIContainer::TYPE_VECTOR);
+
+    SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
+
+    if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
+      return nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(),
+                                            aPresContext,
+                                            subImage,
+                                            samplingFilter,
+                                            aFill, aDirtyRect,
+                                            /* no SVGImageContext */ Nothing(),
+                                            drawFlags);
+    }
+
+    nsSize repeatSize;
+    nsRect fillRect(aFill);
+    nsRect tile = ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize);
+    CSSIntSize imageSize(srcRect.width, srcRect.height);
+    return nsLayoutUtils::DrawBackgroundImage(*aRenderingContext.ThebesContext(),
+                                              aPresContext,
+                                              subImage, imageSize, samplingFilter,
+                                              tile, fillRect, repeatSize,
+                                              tile.TopLeft(), aDirtyRect,
+                                              drawFlags,
+                                              ExtendMode::CLAMP, 1.0);
+  }
+
+  nsSize repeatSize(aFill.Size());
+  nsRect fillRect(aFill);
+  nsRect destTile = RequiresScaling(fillRect, aHFill, aVFill, aUnitSize)
+                  ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize)
+                  : fillRect;
+  return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile,
+              fillRect, destTile.TopLeft(), repeatSize, aSrc);
+}
+
+bool
+nsImageRenderer::IsRasterImage()
+{
+  if (mType != eStyleImageType_Image || !mImageContainer)
+    return false;
+  return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
+}
+
+bool
+nsImageRenderer::IsAnimatedImage()
+{
+  if (mType != eStyleImageType_Image || !mImageContainer)
+    return false;
+  bool animated = false;
+  if (NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated)
+    return true;
+
+  return false;
+}
+
+already_AddRefed<imgIContainer>
+nsImageRenderer::GetImage()
+{
+  if (mType != eStyleImageType_Image || !mImageContainer) {
+    return nullptr;
+  }
+
+  nsCOMPtr<imgIContainer> image = mImageContainer;
+  return image.forget();
+}
+
+void
+nsImageRenderer::PurgeCacheForViewportChange(
+  const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio)
+{
+  // Check if we should flush the cached data - only vector images need to do
+  // the check since they might not have fixed ratio.
+  if (mImageContainer &&
+      mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+    mImage->PurgeCacheForViewportChange(aSVGViewportSize, aHasIntrinsicRatio);
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/layout/painting/nsImageRenderer.h
@@ -0,0 +1,280 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsImageRenderer_h__
+#define nsImageRenderer_h__
+
+class gfxDrawable;
+
+namespace mozilla {
+
+// A CSSSizeOrRatio represents a (possibly partially specified) size for use
+// in computing image sizes. Either or both of the width and height might be
+// given. A ratio of width to height may also be given. If we at least two
+// of these then we can compute a concrete size, that is a width and height.
+struct CSSSizeOrRatio
+{
+  CSSSizeOrRatio()
+    : mRatio(0, 0)
+    , mHasWidth(false)
+    , mHasHeight(false) {}
+
+  bool CanComputeConcreteSize() const
+  {
+    return mHasWidth + mHasHeight + HasRatio() >= 2;
+  }
+  bool IsConcrete() const { return mHasWidth && mHasHeight; }
+  bool HasRatio() const { return mRatio.width > 0 && mRatio.height > 0; }
+  bool IsEmpty() const
+  {
+    return (mHasWidth && mWidth <= 0) ||
+           (mHasHeight && mHeight <= 0) ||
+           mRatio.width <= 0 || mRatio.height <= 0;
+  }
+
+  // CanComputeConcreteSize must return true when ComputeConcreteSize is
+  // called.
+  nsSize ComputeConcreteSize() const;
+
+  void SetWidth(nscoord aWidth)
+  {
+    mWidth = aWidth;
+    mHasWidth = true;
+    if (mHasHeight) {
+      mRatio = nsSize(mWidth, mHeight);
+    }
+  }
+  void SetHeight(nscoord aHeight)
+  {
+    mHeight = aHeight;
+    mHasHeight = true;
+    if (mHasWidth) {
+      mRatio = nsSize(mWidth, mHeight);
+    }
+  }
+  void SetSize(const nsSize& aSize)
+  {
+    mWidth = aSize.width;
+    mHeight = aSize.height;
+    mHasWidth = true;
+    mHasHeight = true;
+    mRatio = aSize;
+  }
+  void SetRatio(const nsSize& aRatio)
+  {
+    MOZ_ASSERT(!mHasWidth || !mHasHeight,
+               "Probably shouldn't be setting a ratio if we have a concrete size");
+    mRatio = aRatio;
+  }
+
+  nsSize mRatio;
+  nscoord mWidth;
+  nscoord mHeight;
+  bool mHasWidth;
+  bool mHasHeight;
+};
+
+/**
+ * This is a small wrapper class to encapsulate image drawing that can draw an
+ * nsStyleImage image, which may internally be a real image, a sub image, or a
+ * CSS gradient.
+ *
+ * @note Always call the member functions in the order of PrepareImage(),
+ * SetSize(), and Draw*().
+ */
+class nsImageRenderer {
+public:
+  typedef mozilla::image::DrawResult DrawResult;
+  typedef mozilla::layers::LayerManager LayerManager;
+  typedef mozilla::layers::ImageContainer ImageContainer;
+
+  enum {
+    FLAG_SYNC_DECODE_IMAGES = 0x01,
+    FLAG_PAINTING_TO_WINDOW = 0x02
+  };
+  enum FitType
+  {
+    CONTAIN,
+    COVER
+  };
+
+  nsImageRenderer(nsIFrame* aForFrame, const nsStyleImage* aImage, uint32_t aFlags);
+  ~nsImageRenderer();
+  /**
+   * Populates member variables to get ready for rendering.
+   * @return true iff the image is ready, and there is at least a pixel to
+   * draw.
+   */
+  bool PrepareImage();
+
+  /**
+   * The three Compute*Size functions correspond to the sizing algorthms and
+   * definitions from the CSS Image Values and Replaced Content spec. See
+   * http://dev.w3.org/csswg/css-images-3/#sizing .
+   */
+
+  /**
+   * Compute the intrinsic size of the image as defined in the CSS Image Values
+   * spec. The intrinsic size is the unscaled size which the image would ideally
+   * like to be in app units.
+   */
+  mozilla::CSSSizeOrRatio ComputeIntrinsicSize();
+
+  /**
+   * Computes the placement for a background image, or for the image data
+   * inside of a replaced element.
+   *
+   * @param aPos The CSS <position> value that specifies the image's position.
+   * @param aOriginBounds The box to which the tiling position should be
+   *          relative. For background images, this should correspond to
+   *          'background-origin' for the frame, except when painting on the
+   *          canvas, in which case the origin bounds should be the bounds
+   *          of the root element's frame. For a replaced element, this should
+   *          be the element's content-box.
+   * @param aTopLeft [out] The top-left corner where an image tile should be
+   *          drawn.
+   * @param aAnchorPoint [out] A point which should be pixel-aligned by
+   *          nsLayoutUtils::DrawImage. This is the same as aTopLeft, unless
+   *          CSS specifies a percentage (including 'right' or 'bottom'), in
+   *          which case it's that percentage within of aOriginBounds. So
+   *          'right' would set aAnchorPoint.x to aOriginBounds.XMost().
+   *
+   * Points are returned relative to aOriginBounds.
+   */
+  static void ComputeObjectAnchorPoint(const mozilla::Position& aPos,
+                                       const nsSize& aOriginBounds,
+                                       const nsSize& aImageSize,
+                                       nsPoint* aTopLeft,
+                                       nsPoint* aAnchorPoint);
+
+  /**
+   * Compute the size of the rendered image using either the 'cover' or
+   * 'contain' constraints (aFitType).
+   * aIntrinsicRatio may be an invalid ratio, that is one or both of its
+   * dimensions can be less than or equal to zero.
+   */
+  static nsSize ComputeConstrainedSize(const nsSize& aConstrainingSize,
+                                       const nsSize& aIntrinsicRatio,
+                                       FitType aFitType);
+  /**
+   * Compute the size of the rendered image (the concrete size) where no cover/
+   * contain constraints are given. The 'default algorithm' from the CSS Image
+   * Values spec.
+   */
+  static nsSize ComputeConcreteSize(const mozilla::CSSSizeOrRatio& aSpecifiedSize,
+                                    const mozilla::CSSSizeOrRatio& aIntrinsicSize,
+                                    const nsSize& aDefaultSize);
+
+  /**
+   * Set this image's preferred size. This will be its intrinsic size where
+   * specified and the default size where it is not. Used as the unscaled size
+   * when rendering the image.
+   */
+  void SetPreferredSize(const mozilla::CSSSizeOrRatio& aIntrinsicSize,
+                        const nsSize& aDefaultSize);
+
+  /**
+   * Draws the image to the target rendering context using
+   * {background|mask}-specific arguments.
+   * @see nsLayoutUtils::DrawImage() for parameters.
+   */
+  DrawResult DrawLayer(nsPresContext*       aPresContext,
+                       nsRenderingContext&  aRenderingContext,
+                       const nsRect&        aDest,
+                       const nsRect&        aFill,
+                       const nsPoint&       aAnchor,
+                       const nsRect&        aDirty,
+                       const nsSize&        aRepeatSize,
+                       float                aOpacity);
+
+  /**
+   * Draw the image to a single component of a border-image style rendering.
+   * aFill The destination rect to be drawn into
+   * aSrc is the part of the image to be rendered into a tile (aUnitSize in
+   * aFill), if aSrc and the dest tile are different sizes, the image will be
+   * scaled to map aSrc onto the dest tile.
+   * aHFill and aVFill are the repeat patterns for the component -
+   * NS_STYLE_BORDER_IMAGE_REPEAT_*
+   * aUnitSize The scaled size of a single source rect (in destination coords)
+   * aIndex identifies the component: 0 1 2
+   *                                  3 4 5
+   *                                  6 7 8
+   * aSVGViewportSize The image size evaluated by default sizing algorithm.
+   * Pass Nothing() if we can read a valid viewport size or aspect-ratio from
+   * the drawing image directly, otherwise, pass Some() with viewport size
+   * evaluated from default sizing algorithm.
+   * aHasIntrinsicRatio is used to record if the source image has fixed
+   * intrinsic ratio.
+   */
+  DrawResult
+  DrawBorderImageComponent(nsPresContext*       aPresContext,
+                           nsRenderingContext&  aRenderingContext,
+                           const nsRect&        aDirtyRect,
+                           const nsRect&        aFill,
+                           const mozilla::CSSIntRect& aSrc,
+                           uint8_t              aHFill,
+                           uint8_t              aVFill,
+                           const nsSize&        aUnitSize,
+                           uint8_t              aIndex,
+                           const mozilla::Maybe<nsSize>& aSVGViewportSize,
+                           const bool           aHasIntrinsicRatio);
+
+  bool IsRasterImage();
+  bool IsAnimatedImage();
+
+  /// Retrieves the image associated with this nsImageRenderer, if there is one.
+  already_AddRefed<imgIContainer> GetImage();
+
+  bool IsReady() const { return mPrepareResult == DrawResult::SUCCESS; }
+  DrawResult PrepareResult() const { return mPrepareResult; }
+  void SetExtendMode(mozilla::gfx::ExtendMode aMode) { mExtendMode = aMode; }
+  void SetMaskOp(uint8_t aMaskOp) { mMaskOp = aMaskOp; }
+  void PurgeCacheForViewportChange(const mozilla::Maybe<nsSize>& aSVGViewportSize,
+                                   const bool aHasRatio);
+
+private:
+  /**
+   * Draws the image to the target rendering context.
+   * aSrc is a rect on the source image which will be mapped to aDest; it's
+   * currently only used for gradients.
+   *
+   * @see nsLayoutUtils::DrawImage() for other parameters.
+   */
+  DrawResult Draw(nsPresContext*       aPresContext,
+                  nsRenderingContext&  aRenderingContext,
+                  const nsRect&        aDirtyRect,
+                  const nsRect&        aDest,
+                  const nsRect&        aFill,
+                  const nsPoint&       aAnchor,
+                  const nsSize&        aRepeatSize,
+                  const mozilla::CSSIntRect& aSrc,
+                  float                aOpacity = 1.0);
+
+  /**
+   * Helper method for creating a gfxDrawable from mPaintServerFrame or
+   * mImageElementSurface.
+   * Requires mType is eStyleImageType_Element.
+   * Returns null if we cannot create the drawable.
+   */
+  already_AddRefed<gfxDrawable> DrawableForElement(const nsRect& aImageRect,
+                                                   nsRenderingContext&  aRenderingContext);
+
+  nsIFrame*                 mForFrame;
+  const nsStyleImage*       mImage;
+  nsStyleImageType          mType;
+  nsCOMPtr<imgIContainer>   mImageContainer;
+  RefPtr<nsStyleGradient> mGradientData;
+  nsIFrame*                 mPaintServerFrame;
+  nsLayoutUtils::SurfaceFromElementResult mImageElementSurface;
+  DrawResult                mPrepareResult;
+  nsSize                    mSize; // unscaled size of the image, in app units
+  uint32_t                  mFlags;
+  mozilla::gfx::ExtendMode  mExtendMode;
+  uint8_t                   mMaskOp;
+};
+
+} // namespace mozilla
+
+#endif /* nsImageRenderer_h__ */