Backed out changesets 957f2b35ce83:1799cffbba63 (bug 826093) for Android reftest-2 failures (again) on a CLOSED TREE.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 26 Mar 2013 21:56:03 -0400
changeset 126679 446b90989fdd8cfdbbf64c86d52b3bcd8de0b7c8
parent 126678 123d7b2c7307b054120999cdb6241aa6d9f4d0c0
child 126680 8136a81da0378b3580379780aae07e4b20a2e672
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
bugs826093
milestone22.0a1
backs out957f2b35ce830f0f40ec3f8ecf9a4b311c412488
Backed out changesets 957f2b35ce83:1799cffbba63 (bug 826093) for Android reftest-2 failures (again) on a CLOSED TREE.
image/public/imgIContainer.idl
image/src/ClippedImage.cpp
image/src/ClippedImage.h
image/src/FrozenImage.cpp
image/src/FrozenImage.h
image/src/ImageFactory.cpp
image/src/ImageFactory.h
image/src/ImageOps.cpp
image/src/ImageOps.h
image/src/ImageWrapper.cpp
image/src/Makefile.in
image/src/RasterImage.cpp
image/src/VectorImage.cpp
image/src/VectorImage.h
image/src/imgRequestProxy.cpp
layout/base/nsCSSRendering.cpp
--- a/image/public/imgIContainer.idl
+++ b/image/public/imgIContainer.idl
@@ -47,22 +47,23 @@ native nsSize(nsSize);
 [ptr] native ImageContainer(mozilla::layers::ImageContainer);
 [ptr] native LayerManager(mozilla::layers::LayerManager);
 [ref] native TimeStamp(mozilla::TimeStamp);
 [ptr] native SVGImageContext(mozilla::SVGImageContext);
 
 
 /**
  * imgIContainer is the interface that represents an image. It allows
- * access to frames as Thebes surfaces. It also allows drawing of images
- * onto Thebes contexts.
+ * access to frames as Thebes surfaces, and permits users to extract subregions
+ * as other imgIContainers. It also allows drawing of images on to Thebes
+ * contexts.
  *
  * Internally, imgIContainer also manages animation of images.
  */
-[scriptable, builtinclass, uuid(0c1caf24-bce7-4db5-971d-8e1b6ed07540)]
+[scriptable, builtinclass, uuid(01c4f92f-f883-4837-a127-d8f30920e374)]
 interface imgIContainer : nsISupports
 {
   /**
    * The width of the container rectangle.  In the case of any error,
    * zero is returned, and an exception will be thrown.
    */
   readonly attribute int32_t width;
 
@@ -173,16 +174,29 @@ interface imgIContainer : nsISupports
 
   /**
    * Attempts to create an ImageContainer (and Image) containing the current
    * frame. Only valid for RASTER type images.
    */
   [noscript] ImageContainer getImageContainer(in LayerManager aManager);
 
   /**
+   * Create a new imgContainer that contains only a single frame, which itself
+   * contains a subregion of the given frame.
+   *
+   * @param aWhichFrame Frame specifier of the FRAME_* variety.
+   * @param aRect the area of the current frame to be duplicated in the
+   *              returned imgContainer's frame.
+   * @param aFlags Flags of the FLAG_* variety
+   */
+  [noscript] imgIContainer extractFrame(in uint32_t aWhichFrame,
+                                        [const] in nsIntRect aRect,
+                                        in uint32_t aFlags);
+
+  /**
    * Draw a frame onto the context specified.
    *
    * @param aContext The Thebes context to draw the image to.
    * @param aFilter The filter to be used if we're scaling the image.
    * @param aUserSpaceToImageSpace The transformation from user space (e.g.,
    *                               appunits) to image space.
    * @param aFill The area in the context to draw pixels to. When aFlags includes
    *              FLAG_CLAMP, the image will be extended to this area by clampling
@@ -210,20 +224,21 @@ interface imgIContainer : nsISupports
                        [const] in nsIntRect aSubimage,
                        [const] in nsIntSize aViewportSize,
                        [const] in SVGImageContext aSVGContext,
                        in uint32_t aWhichFrame,
                        in uint32_t aFlags);
 
   /*
    * Ensures that an image is decoding. Calling this function guarantees that
-   * the image will at some point fire off decode notifications. Calling draw()
-   * or getFrame() triggers the same mechanism internally. Thus, if you want to
-   * be sure that the image will be decoded but don't want to access it until
-   * then, you must call requestDecode().
+   * the image will at some point fire off decode notifications. Calling draw(),
+   * getFrame(), copyFrame(), or extractCurrentFrame() triggers the same
+   * mechanism internally. Thus, if you want to be sure that the image will be
+   * decoded but don't want to access it until then, you must call
+   * requestDecode().
    */
   void requestDecode();
 
   /*
    * This is equivalent to requestDecode() but it also decodes some of the image.
    */
   [noscript] void startDecoding();
 
deleted file mode 100644
--- a/image/src/ClippedImage.cpp
+++ /dev/null
@@ -1,324 +0,0 @@
-/* -*- 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/. */
-
-#include "gfxDrawable.h"
-#include "gfxPlatform.h"
-#include "gfxUtils.h"
-
-#include "ClippedImage.h"
-
-using mozilla::layers::LayerManager;
-using mozilla::layers::ImageContainer;
-
-namespace mozilla {
-namespace image {
-
-class DrawSingleTileCallback : public gfxDrawingCallback
-{
-public:
-  DrawSingleTileCallback(ClippedImage* aImage,
-                         const nsIntRect& aClip,
-                         const nsIntSize& aViewportSize,
-                         const SVGImageContext* aSVGContext,
-                         uint32_t aWhichFrame,
-                         uint32_t aFlags)
-    : mImage(aImage)
-    , mClip(aClip)
-    , mViewportSize(aViewportSize)
-    , mSVGContext(aSVGContext)
-    , mWhichFrame(aWhichFrame)
-    , mFlags(aFlags)
-  {
-    MOZ_ASSERT(mImage, "Must have an image to clip");
-  }
-
-  virtual bool operator()(gfxContext* aContext,
-                          const gfxRect& aFillRect,
-                          const gfxPattern::GraphicsFilter& aFilter,
-                          const gfxMatrix& aTransform)
-  {
-    // Draw the image. |gfxCallbackDrawable| always calls this function with
-    // arguments that guarantee we never tile.
-    mImage->DrawSingleTile(aContext, aFilter, aTransform, aFillRect, mClip,
-                           mViewportSize, mSVGContext, mWhichFrame, mFlags);
-
-    return true;
-  }
-
-private:
-  nsRefPtr<ClippedImage> mImage;
-  const nsIntRect        mClip;
-  const nsIntSize        mViewportSize;
-  const SVGImageContext* mSVGContext;
-  const uint32_t         mWhichFrame;
-  const uint32_t         mFlags;
-};
-
-ClippedImage::ClippedImage(Image* aImage,
-                           nsIntRect aClip)
-  : ImageWrapper(aImage)
-  , mClip(aClip)
-{
-  MOZ_ASSERT(aImage != nullptr, "ClippedImage requires an existing Image");
-}
-
-bool
-ClippedImage::ShouldClip()
-{
-  // We need to evaluate the clipping region against the image's width and height
-  // once they're available to determine if it's valid and whether we actually
-  // need to do any work. We may fail if the image's width and height aren't
-  // available yet, in which case we'll try again later.
-  if (mShouldClip.empty()) {
-    int32_t width, height;
-    if (InnerImage()->HasError()) {
-      // If there's a problem with the inner image we'll let it handle everything.
-      mShouldClip.construct(false);
-    } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&width)) && width > 0 &&
-               NS_SUCCEEDED(InnerImage()->GetHeight(&height)) && height > 0) {
-      // Clamp the clipping region to the size of the underlying image.
-      mClip = mClip.Intersect(nsIntRect(0, 0, width, height));
-
-      // If the clipping region is the same size as the underlying image we
-      // don't have to do anything.
-      mShouldClip.construct(!mClip.IsEqualInterior(nsIntRect(0, 0, width, height)));
-    } else if (InnerImage()->GetStatusTracker().IsLoading()) {
-      // The image just hasn't finished loading yet. We don't yet know whether
-      // clipping with be needed or not for now. Just return without memoizing
-      // anything.
-      return false;
-    } else {
-      // We have a fully loaded image without a clearly defined width and
-      // height. This can happen with SVG images.
-      mShouldClip.construct(false);
-    }
-  }
-
-  MOZ_ASSERT(!mShouldClip.empty(), "Should have computed a result");
-  return mShouldClip.ref();
-}
-
-NS_IMPL_ISUPPORTS1(ClippedImage, imgIContainer)
-
-nsIntRect
-ClippedImage::FrameRect(uint32_t aWhichFrame)
-{
-  if (!ShouldClip()) {
-    return InnerImage()->FrameRect(aWhichFrame);
-  }
-
-  return nsIntRect(0, 0, mClip.width, mClip.height);
-}
-
-NS_IMETHODIMP
-ClippedImage::GetWidth(int32_t* aWidth)
-{
-  if (!ShouldClip()) {
-    return InnerImage()->GetWidth(aWidth);
-  }
-
-  *aWidth = mClip.width;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-ClippedImage::GetHeight(int32_t* aHeight)
-{
-  if (!ShouldClip()) {
-    return InnerImage()->GetHeight(aHeight);
-  }
-
-  *aHeight = mClip.height;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-ClippedImage::GetIntrinsicSize(nsSize* aSize)
-{
-  if (!ShouldClip()) {
-    return InnerImage()->GetIntrinsicSize(aSize);
-  }
-
-  *aSize = nsSize(mClip.width, mClip.height);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-ClippedImage::GetIntrinsicRatio(nsSize* aRatio)
-{
-  if (!ShouldClip()) {
-    return InnerImage()->GetIntrinsicRatio(aRatio);
-  }
-
-  *aRatio = nsSize(mClip.width, mClip.height);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-ClippedImage::GetFrame(uint32_t aWhichFrame,
-                       uint32_t aFlags,
-                       gfxASurface** _retval)
-{
-  if (!ShouldClip()) {
-    return InnerImage()->GetFrame(aWhichFrame, aFlags, _retval);
-  }
-
-  // Create a surface to draw into.
-  gfxImageSurface::gfxImageFormat format = gfxASurface::ImageFormatARGB32;
-  nsRefPtr<gfxASurface> surface = gfxPlatform::GetPlatform()
-    ->CreateOffscreenSurface(gfxIntSize(mClip.width, mClip.height),
-                             gfxImageSurface::ContentFromFormat(format));
-  // Create our callback.
-  nsRefPtr<gfxDrawingCallback> drawTileCallback =
-    new DrawSingleTileCallback(this, mClip, mClip.Size(), nullptr, aWhichFrame, aFlags);
-  nsRefPtr<gfxDrawable> drawable =
-    new gfxCallbackDrawable(drawTileCallback, mClip.Size());
-
-  // Actually draw. The callback will end up invoking DrawSingleTile.
-  nsRefPtr<gfxContext> ctx = new gfxContext(surface);
-  gfxRect imageRect(0, 0, mClip.width, mClip.height);
-  gfxUtils::DrawPixelSnapped(ctx, drawable, gfxMatrix(),
-                             imageRect, imageRect, imageRect, imageRect,
-                             gfxASurface::ImageFormatARGB32, gfxPattern::FILTER_FAST);
-
-  *_retval = surface.forget().get();
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-ClippedImage::GetImageContainer(LayerManager* aManager, ImageContainer** _retval)
-{
-  // XXX(seth): We currently don't have a way of clipping the result of
-  // GetImageContainer. We work around this by always returning null, but if it
-  // ever turns out that ClippedImage is widely used on codepaths that can
-  // actually benefit from GetImageContainer, it would be a good idea to fix
-  // that method for performance reasons.
-
-  *_retval = nullptr;
-  return NS_OK;
-}
-
-bool
-ClippedImage::MustCreateSurface(gfxContext* aContext,
-                                const gfxMatrix& aTransform,
-                                const gfxRect& aSourceRect,
-                                const nsIntRect& aSubimage,
-                                const uint32_t aFlags) const
-{
-  gfxRect gfxImageRect(0, 0, mClip.width, mClip.height);
-  nsIntRect intImageRect(0, 0, mClip.width, mClip.height);
-  bool willTile = !gfxImageRect.Contains(aSourceRect) &&
-                  !(aFlags & imgIContainer::FLAG_CLAMP);
-  bool willResample = (aContext->CurrentMatrix().HasNonIntegerTranslation() ||
-                       aTransform.HasNonIntegerTranslation()) &&
-                      (willTile || !aSubimage.Contains(intImageRect));
-  return willTile || willResample;
-}
-
-NS_IMETHODIMP
-ClippedImage::Draw(gfxContext* aContext,
-                   gfxPattern::GraphicsFilter aFilter,
-                   const gfxMatrix& aUserSpaceToImageSpace,
-                   const gfxRect& aFill,
-                   const nsIntRect& aSubimage,
-                   const nsIntSize& aViewportSize,
-                   const SVGImageContext* aSVGContext,
-                   uint32_t aWhichFrame,
-                   uint32_t aFlags)
-{
-  if (!ShouldClip()) {
-    return InnerImage()->Draw(aContext, aFilter, aUserSpaceToImageSpace,
-                              aFill, aSubimage, aViewportSize, aSVGContext,
-                              aWhichFrame, aFlags);
-  }
-
-  // Check for tiling. If we need to tile then we need to create a
-  // gfxCallbackDrawable to handle drawing for us.
-  gfxRect sourceRect = aUserSpaceToImageSpace.Transform(aFill);
-  if (MustCreateSurface(aContext, aUserSpaceToImageSpace, sourceRect, aSubimage, aFlags)) {
-    // Create a temporary surface containing a single tile of this image.
-    // GetFrame will call DrawSingleTile internally.
-    nsRefPtr<gfxASurface> surface;
-    GetFrame(aWhichFrame, aFlags, getter_AddRefs(surface));
-    NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
-
-    // Create a drawable from that surface.
-    nsRefPtr<gfxSurfaceDrawable> drawable =
-      new gfxSurfaceDrawable(surface, gfxIntSize(mClip.width, mClip.height));
-
-    // Draw.
-    gfxRect imageRect(0, 0, mClip.width, mClip.height);
-    gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height);
-    gfxUtils::DrawPixelSnapped(aContext, drawable, aUserSpaceToImageSpace,
-                               subimage, sourceRect, imageRect, aFill,
-                               gfxASurface::ImageFormatARGB32, aFilter);
-
-    return NS_OK;
-  }
-
-  // Determine the appropriate subimage for the inner image.
-  nsIntRect innerSubimage(aSubimage);
-  innerSubimage.MoveBy(mClip.x, mClip.y);
-  innerSubimage.Intersect(mClip);
-
-  return DrawSingleTile(aContext, aFilter, aUserSpaceToImageSpace, aFill, innerSubimage,
-                        aViewportSize, aSVGContext, aWhichFrame, aFlags);
-}
-
-gfxFloat
-ClippedImage::ClampFactor(const gfxFloat aToClamp, const int aReference) const
-{
-  return aToClamp > aReference ? aReference / aToClamp
-                               : 1.0;
-}
-
-nsresult
-ClippedImage::DrawSingleTile(gfxContext* aContext,
-                             gfxPattern::GraphicsFilter aFilter,
-                             const gfxMatrix& aUserSpaceToImageSpace,
-                             const gfxRect& aFill,
-                             const nsIntRect& aSubimage,
-                             const nsIntSize& aViewportSize,
-                             const SVGImageContext* aSVGContext,
-                             uint32_t aWhichFrame,
-                             uint32_t aFlags)
-{
-  MOZ_ASSERT(!MustCreateSurface(aContext, aUserSpaceToImageSpace,
-                                aUserSpaceToImageSpace.Transform(aFill),
-                                aSubimage - nsIntPoint(mClip.x, mClip.y), aFlags),
-             "DrawSingleTile shouldn't need to create a surface");
-
-  // Make the viewport reflect the original image's size.
-  nsIntSize viewportSize(aViewportSize);
-  int32_t imgWidth, imgHeight;
-  if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) &&
-      NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) {
-    viewportSize = nsIntSize(imgWidth, imgHeight);
-  } else {
-    MOZ_ASSERT(false, "If ShouldClip() led us to draw then we should never get here");
-  }
-
-  // Add a translation to the transform to reflect the clipping region.
-  gfxMatrix transform(aUserSpaceToImageSpace);
-  transform.Multiply(gfxMatrix().Translate(gfxPoint(mClip.x, mClip.y)));
-
-  // "Clamp the source rectangle" to the clipping region's width and height.
-  // Really, this means modifying the transform to get the results we want.
-  gfxRect sourceRect = transform.Transform(aFill);
-  if (sourceRect.width > mClip.width || sourceRect.height > mClip.height) {
-    gfxMatrix clampSource;
-    clampSource.Translate(gfxPoint(sourceRect.x, sourceRect.y));
-    clampSource.Scale(ClampFactor(sourceRect.width, mClip.width),
-                      ClampFactor(sourceRect.height, mClip.height));
-    clampSource.Translate(gfxPoint(-sourceRect.x, -sourceRect.y));
-    transform.Multiply(clampSource);
-  }
-
-  return InnerImage()->Draw(aContext, aFilter, transform, aFill, aSubimage,
-                            viewportSize, aSVGContext, aWhichFrame, aFlags);
-}
-
-} // namespace image
-} // namespace mozilla
deleted file mode 100644
--- a/image/src/ClippedImage.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/* -*- 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 MOZILLA_IMAGELIB_CLIPPEDIMAGE_H_
-#define MOZILLA_IMAGELIB_CLIPPEDIMAGE_H_
-
-#include "ImageWrapper.h"
-
-namespace mozilla {
-namespace image {
-
-class DrawSingleTileCallback;
-
-/**
- * An Image wrapper that clips an image against a rectangle. Right now only
- * absolute coordinates in pixels are supported.
- *
- * XXX(seth): There a known (performance, not correctness) issue with
- * GetImageContainer. See the comments for that method for more information.
- */
-class ClippedImage : public ImageWrapper
-{
-public:
-  NS_DECL_ISUPPORTS
-
-  virtual ~ClippedImage() { }
-
-  virtual nsIntRect FrameRect(uint32_t aWhichFrame) MOZ_OVERRIDE;
-
-  NS_IMETHOD GetWidth(int32_t* aWidth) MOZ_OVERRIDE;
-  NS_IMETHOD GetHeight(int32_t* aHeight) MOZ_OVERRIDE;
-  NS_IMETHOD GetIntrinsicSize(nsSize* aSize) MOZ_OVERRIDE;
-  NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) MOZ_OVERRIDE;
-  NS_IMETHOD GetFrame(uint32_t aWhichFrame,
-                      uint32_t aFlags,
-                      gfxASurface** _retval) MOZ_OVERRIDE;
-  NS_IMETHOD GetImageContainer(mozilla::layers::LayerManager* aManager,
-                               mozilla::layers::ImageContainer** _retval) MOZ_OVERRIDE;
-  NS_IMETHOD Draw(gfxContext* aContext,
-                  gfxPattern::GraphicsFilter aFilter,
-                  const gfxMatrix& aUserSpaceToImageSpace,
-                  const gfxRect& aFill,
-                  const nsIntRect& aSubimage,
-                  const nsIntSize& aViewportSize,
-                  const SVGImageContext* aSVGContext,
-                  uint32_t aWhichFrame,
-                  uint32_t aFlags) MOZ_OVERRIDE;
-
-protected:
-  ClippedImage(Image* aImage, nsIntRect aClip);
-
-private:
-  bool ShouldClip();
-  bool MustCreateSurface(gfxContext* aContext,
-                         const gfxMatrix& aTransform,
-                         const gfxRect& aSourceRect,
-                         const nsIntRect& aSubimage,
-                         const uint32_t aFlags) const;
-  gfxFloat ClampFactor(const gfxFloat aToClamp, const int aReference) const;
-  nsresult DrawSingleTile(gfxContext* aContext,
-                          gfxPattern::GraphicsFilter aFilter,
-                          const gfxMatrix& aUserSpaceToImageSpace,
-                          const gfxRect& aFill,
-                          const nsIntRect& aSubimage,
-                          const nsIntSize& aViewportSize,
-                          const SVGImageContext* aSVGContext,
-                          uint32_t aWhichFrame,
-                          uint32_t aFlags);
-
-  nsIntRect   mClip;              // The region to clip to.
-  Maybe<bool> mShouldClip;        // Memoized ShouldClip() if present.
-
-  friend class DrawSingleTileCallback;
-  friend class ImageOps;
-};
-
-} // namespace image
-} // namespace mozilla
-
-#endif // MOZILLA_IMAGELIB_CLIPPEDIMAGE_H_
--- a/image/src/FrozenImage.cpp
+++ b/image/src/FrozenImage.cpp
@@ -64,16 +64,25 @@ FrozenImage::GetImageContainer(layers::L
   // benefit from GetImageContainer, it would be a good idea to fix that method
   // for performance reasons.
 
   *_retval = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+FrozenImage::ExtractFrame(uint32_t aWhichFrame,
+                          const nsIntRect& aRegion,
+                          uint32_t aFlags,
+                          imgIContainer** _retval)
+{
+  return InnerImage()->ExtractFrame(FRAME_FIRST, aRegion, aFlags, _retval);
+}
+
+NS_IMETHODIMP
 FrozenImage::Draw(gfxContext* aContext,
                   gfxPattern::GraphicsFilter aFilter,
                   const gfxMatrix& aUserSpaceToImageSpace,
                   const gfxRect& aFill,
                   const nsIntRect& aSubimage,
                   const nsIntSize& aViewportSize,
                   const SVGImageContext* aSVGContext,
                   uint32_t /* aWhichFrame - ignored */,
--- a/image/src/FrozenImage.h
+++ b/image/src/FrozenImage.h
@@ -35,16 +35,20 @@ public:
 
   NS_IMETHOD GetAnimated(bool* aAnimated) MOZ_OVERRIDE;
   NS_IMETHOD GetFrame(uint32_t aWhichFrame,
                       uint32_t aFlags,
                       gfxASurface** _retval) MOZ_OVERRIDE;
   NS_IMETHOD_(bool) FrameIsOpaque(uint32_t aWhichFrame) MOZ_OVERRIDE;
   NS_IMETHOD GetImageContainer(layers::LayerManager* aManager,
                                layers::ImageContainer** _retval) MOZ_OVERRIDE;
+  NS_IMETHOD ExtractFrame(uint32_t aWhichFrame,
+                          const nsIntRect& aRegion,
+                          uint32_t aFlags,
+                          imgIContainer** _retval) MOZ_OVERRIDE;
   NS_IMETHOD Draw(gfxContext* aContext,
                   gfxPattern::GraphicsFilter aFilter,
                   const gfxMatrix& aUserSpaceToImageSpace,
                   const gfxRect& aFill,
                   const nsIntRect& aSubimage,
                   const nsIntSize& aViewportSize,
                   const SVGImageContext* aSVGContext,
                   uint32_t aWhichFrame,
@@ -53,15 +57,15 @@ public:
   NS_IMETHOD GetAnimationMode(uint16_t* aAnimationMode) MOZ_OVERRIDE;
   NS_IMETHOD SetAnimationMode(uint16_t aAnimationMode) MOZ_OVERRIDE;
   NS_IMETHOD ResetAnimation() MOZ_OVERRIDE;
 
 protected:
   FrozenImage(Image* aImage) : ImageWrapper(aImage) { }
 
 private:
-  friend class ImageOps;
+  friend class ImageFactory;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // MOZILLA_IMAGELIB_FROZENIMAGE_H_
--- a/image/src/ImageFactory.cpp
+++ b/image/src/ImageFactory.cpp
@@ -16,16 +16,17 @@
 #include "nsMimeTypes.h"
 #include "nsIURI.h"
 #include "nsIRequest.h"
 
 #include "imgIContainer.h"
 #include "imgStatusTracker.h"
 #include "RasterImage.h"
 #include "VectorImage.h"
+#include "FrozenImage.h"
 #include "Image.h"
 #include "nsMediaFragmentURIParser.h"
 
 #include "ImageFactory.h"
 
 namespace mozilla {
 namespace image {
 
@@ -170,16 +171,23 @@ GetContentSize(nsIRequest* aRequest)
     }
   }
 
   // Fallback - neither http nor file. We'll use dynamic allocation.
   return 0;
 }
 
 /* static */ already_AddRefed<Image>
+ImageFactory::Freeze(Image* aImage)
+{
+  nsRefPtr<Image> frozenImage = new FrozenImage(aImage);
+  return frozenImage.forget();
+}
+
+/* static */ already_AddRefed<Image>
 ImageFactory::CreateRasterImage(nsIRequest* aRequest,
                                 imgStatusTracker* aStatusTracker,
                                 const nsCString& aMimeType,
                                 nsIURI* aURI,
                                 uint32_t aImageFlags,
                                 uint32_t aInnerWindowId)
 {
   nsresult rv;
--- a/image/src/ImageFactory.h
+++ b/image/src/ImageFactory.h
@@ -41,16 +41,24 @@ public:
   /**
    * Creates a new image which isn't associated with a URI or loaded through
    * the usual image loading mechanism.
    *
    * @param aMimeType      The mimetype of the image.
    */
   static already_AddRefed<Image> CreateAnonymousImage(const nsCString& aMimeType);
 
+  /**
+   * Creates a version of an existing image which does not animate and is frozen
+   * at the first frame.
+   *
+   * @param aImage         The existing image.
+   */
+  static already_AddRefed<Image> Freeze(Image* aImage);
+
 private:
   // Factory functions that create specific types of image containers.
   static already_AddRefed<Image> CreateRasterImage(nsIRequest* aRequest,
                                                    imgStatusTracker* aStatusTracker,
                                                    const nsCString& aMimeType,
                                                    nsIURI* aURI,
                                                    uint32_t aImageFlags,
                                                    uint32_t aInnerWindowId);
deleted file mode 100644
--- a/image/src/ImageOps.cpp
+++ /dev/null
@@ -1,48 +0,0 @@
-/* -*- 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/. */
-
-#include "imgIContainer.h"
-#include "ClippedImage.h"
-#include "FrozenImage.h"
-#include "Image.h"
-
-#include "ImageOps.h"
-
-namespace mozilla {
-namespace image {
-
-/* static */ already_AddRefed<Image>
-ImageOps::Freeze(Image* aImage)
-{
-  nsRefPtr<Image> frozenImage = new FrozenImage(aImage);
-  return frozenImage.forget();
-}
-
-/* static */ already_AddRefed<imgIContainer>
-ImageOps::Freeze(imgIContainer* aImage)
-{
-  nsCOMPtr<imgIContainer> frozenImage =
-    new FrozenImage(static_cast<Image*>(aImage));
-  return frozenImage.forget();
-}
-
-/* static */ already_AddRefed<Image>
-ImageOps::Clip(Image* aImage, nsIntRect aClip)
-{
-  nsRefPtr<Image> clippedImage = new ClippedImage(aImage, aClip);
-  return clippedImage.forget();
-}
-
-/* static */ already_AddRefed<imgIContainer>
-ImageOps::Clip(imgIContainer* aImage, nsIntRect aClip)
-{
-  nsCOMPtr<imgIContainer> clippedImage =
-    new ClippedImage(static_cast<Image*>(aImage), aClip);
-  return clippedImage.forget();
-}
-
-} // namespace image
-} // namespace mozilla
deleted file mode 100644
--- a/image/src/ImageOps.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- 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 MOZILLA_IMAGELIB_IMAGEOPS_H_
-#define MOZILLA_IMAGELIB_IMAGEOPS_H_
-
-#include "nsCOMPtr.h"
-#include "nsRect.h"
-
-class imgIContainer;
-
-namespace mozilla {
-namespace image {
-
-class Image;
-
-class ImageOps
-{
-public:
-  /**
-   * Creates a version of an existing image which does not animate and is frozen
-   * at the first frame.
-   *
-   * @param aImage         The existing image.
-   */
-  static already_AddRefed<Image> Freeze(Image* aImage);
-  static already_AddRefed<imgIContainer> Freeze(imgIContainer* aImage);
-
-  /**
-   * Creates a clipped version of an existing image. Animation is unaffected.
-   *
-   * @param aImage         The existing image.
-   * @param aClip          The rectangle to clip the image against.
-   */
-  static already_AddRefed<Image> Clip(Image* aImage, nsIntRect aClip);
-  static already_AddRefed<imgIContainer> Clip(imgIContainer* aImage, nsIntRect aClip);
-
-private:
-  // This is a static utility class, so disallow instantiation.
-  virtual ~ImageOps() = 0;
-};
-
-} // namespace image
-} // namespace mozilla
-
-#endif // MOZILLA_IMAGELIB_IMAGEOPS_H_
--- a/image/src/ImageWrapper.cpp
+++ b/image/src/ImageWrapper.cpp
@@ -199,16 +199,25 @@ ImageWrapper::FrameIsOpaque(uint32_t aWh
 
 NS_IMETHODIMP
 ImageWrapper::GetImageContainer(LayerManager* aManager, ImageContainer** _retval)
 {
   return mInnerImage->GetImageContainer(aManager, _retval);
 }
 
 NS_IMETHODIMP
+ImageWrapper::ExtractFrame(uint32_t aWhichFrame,
+                           const nsIntRect& aRegion,
+                           uint32_t aFlags,
+                           imgIContainer** _retval)
+{
+  return mInnerImage->ExtractFrame(aWhichFrame, aRegion, aFlags, _retval);
+}
+
+NS_IMETHODIMP
 ImageWrapper::Draw(gfxContext* aContext,
                    gfxPattern::GraphicsFilter aFilter,
                    const gfxMatrix& aUserSpaceToImageSpace,
                    const gfxRect& aFill,
                    const nsIntRect& aSubimage,
                    const nsIntSize& aViewportSize,
                    const SVGImageContext* aSVGContext,
                    uint32_t aWhichFrame,
--- a/image/src/Makefile.in
+++ b/image/src/Makefile.in
@@ -15,26 +15,23 @@ FORCE_STATIC_LIB = 1
 MODULE_NAME	= nsImageLib2Module
 LIBXUL_LIBRARY  = 1
 FAIL_ON_WARNINGS = 1
 
 
 EXPORTS		=  imgLoader.h \
 		   imgRequest.h \
 		   imgRequestProxy.h \
-		   ImageOps.h \
 		   $(NULL)
 
 CPPSRCS		= \
 			Image.cpp \
 			ImageFactory.cpp \
                         ImageMetadata.cpp \
-			ImageOps.cpp \
 			ImageWrapper.cpp \
-			ClippedImage.cpp \
 			Decoder.cpp \
 			DiscardTracker.cpp \
 			FrozenImage.cpp \
 			RasterImage.cpp \
 			ScriptedNotificationObserver.cpp \
 			SVGDocumentWrapper.cpp \
 			VectorImage.cpp \
 			imgFrame.cpp \
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -502,16 +502,24 @@ RasterImage::Init(const char* aMimeType,
   mMultipart = !!(aFlags & INIT_FLAG_MULTIPART);
 
   // Statistics
   if (mDiscardable) {
     num_discardable_containers++;
     discardable_source_bytes += mSourceData.Length();
   }
 
+  // If we're being called from ExtractFrame (used by borderimage),
+  // we don't actually do any decoding. Bail early.
+  // XXX - This should be removed when we fix borderimage
+  if (mSourceDataMimeType.Length() == 0) {
+    mInitialized = true;
+    return NS_OK;
+  }
+
   // Instantiate the decoder
   nsresult rv = InitDecoder(/* aDoSizeDecode = */ true);
   CONTAINER_ENSURE_SUCCESS(rv);
 
   // If we aren't storing source data, we want to switch from a size decode to
   // a full decode as soon as possible.
   if (!StoringSourceData()) {
     mWantFullDecode = true;
@@ -676,16 +684,95 @@ RasterImage::RequestRefresh(const mozill
     // Explicitly call this on mStatusTracker so we're sure to not interfere
     // with the decoding process
     if (mStatusTracker)
       mStatusTracker->FrameChanged(&dirtyRect);
   }
 }
 
 //******************************************************************************
+/* [noscript] imgIContainer extractFrame(uint32_t aWhichFrame,
+ *                                       [const] in nsIntRect aRegion,
+ *                                       in uint32_t aFlags); */
+NS_IMETHODIMP
+RasterImage::ExtractFrame(uint32_t aWhichFrame,
+                          const nsIntRect &aRegion,
+                          uint32_t aFlags,
+                          imgIContainer **_retval)
+{
+  NS_ENSURE_ARG_POINTER(_retval);
+
+  nsresult rv;
+
+  if (aWhichFrame > FRAME_MAX_VALUE)
+    return NS_ERROR_INVALID_ARG;
+
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  // Disallowed in the API
+  if (mInDecoder && (aFlags & imgIContainer::FLAG_SYNC_DECODE))
+    return NS_ERROR_FAILURE;
+
+  // Make a new container. This should switch to another class with bug 505959.
+  nsRefPtr<RasterImage> img(new RasterImage());
+
+  // We don't actually have a mimetype in this case. The empty string tells the
+  // init routine not to try to instantiate a decoder. This should be fixed in
+  // bug 505959.
+  img->Init("", INIT_FLAG_NONE);
+  img->SetSize(aRegion.width, aRegion.height);
+  img->mDecoded = true; // Also, we need to mark the image as decoded
+  img->mHasBeenDecoded = true;
+  img->mFrameDecodeFlags = aFlags & DECODE_FLAGS_MASK;
+
+  if (!ApplyDecodeFlags(aFlags))
+    return NS_ERROR_NOT_AVAILABLE;
+  
+  // If a synchronous decode was requested, do it
+  if (aFlags & FLAG_SYNC_DECODE) {
+    rv = SyncDecode();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // Get the frame. If it's not there, it's probably the caller's fault for
+  // not waiting for the data to be loaded from the network or not passing
+  // FLAG_SYNC_DECODE
+  uint32_t frameIndex = (aWhichFrame == FRAME_FIRST) ?
+                        0 : GetCurrentImgFrameIndex();
+  imgFrame *frame = GetDrawableImgFrame(frameIndex);
+  if (!frame) {
+    *_retval = nullptr;
+    return NS_ERROR_FAILURE;
+  }
+
+  // The frame can be smaller than the image. We want to extract only the part
+  // of the frame that actually exists.
+  nsIntRect framerect = frame->GetRect();
+  framerect.IntersectRect(framerect, aRegion);
+
+  if (framerect.IsEmpty())
+    return NS_ERROR_NOT_AVAILABLE;
+
+  nsAutoPtr<imgFrame> subframe;
+  rv = frame->Extract(framerect, getter_Transfers(subframe));
+  if (NS_FAILED(rv))
+    return rv;
+
+  img->mFrames.AppendElement(subframe.forget());
+
+  img->mStatusTracker->RecordLoaded();
+  img->mStatusTracker->RecordDecoded();
+
+  *_retval = img.forget().get();
+
+  return NS_OK;
+}
+
+//******************************************************************************
 /* readonly attribute int32_t width; */
 NS_IMETHODIMP
 RasterImage::GetWidth(int32_t *aWidth)
 {
   NS_ENSURE_ARG_POINTER(aWidth);
 
   if (mError) {
     *aWidth = 0;
@@ -2656,20 +2743,21 @@ RasterImage::WriteToDecoder(const char *
   // Keep track of the total number of bytes written over the lifetime of the
   // decoder
   mBytesDecoded += aCount;
 
   return NS_OK;
 }
 
 // This function is called in situations where it's clear that we want the
-// frames in decoded form (Draw, GetFrame, etc).  If we're completely decoded,
-// this method resets the discard timer (if we're discardable), since wanting
-// the frames now is a good indicator of wanting them again soon. If we're not
-// decoded, this method kicks off asynchronous decoding to generate the frames.
+// frames in decoded form (Draw, GetFrame, CopyFrame, ExtractFrame, etc).
+// If we're completely decoded, this method resets the discard timer (if
+// we're discardable), since wanting the frames now is a good indicator of
+// wanting them again soon. If we're not decoded, this method kicks off
+// asynchronous decoding to generate the frames.
 nsresult
 RasterImage::WantDecodedFrames()
 {
   nsresult rv;
 
   // If we can discard, the clock should be running. Reset it.
   if (CanDiscard()) {
     NS_ABORT_IF_FALSE(DiscardingActive(),
--- a/image/src/VectorImage.cpp
+++ b/image/src/VectorImage.cpp
@@ -296,20 +296,22 @@ NS_IMPL_ISUPPORTS3(VectorImage,
                    nsIRequestObserver)
 
 //------------------------------------------------------------------------------
 // Constructor / Destructor
 
 VectorImage::VectorImage(imgStatusTracker* aStatusTracker,
                          nsIURI* aURI /* = nullptr */) :
   ImageResource(aStatusTracker, aURI), // invoke superclass's constructor
+  mRestrictedRegion(0, 0, 0, 0),
   mIsInitialized(false),
   mIsFullyLoaded(false),
   mIsDrawing(false),
-  mHaveAnimations(false)
+  mHaveAnimations(false),
+  mHaveRestrictedRegion(false)
 {
 }
 
 VectorImage::~VectorImage()
 {
   CancelAllListeners();
 }
 
@@ -319,17 +321,18 @@ VectorImage::~VectorImage()
 nsresult
 VectorImage::Init(const char* aMimeType,
                   uint32_t aFlags)
 {
   // We don't support re-initialization
   if (mIsInitialized)
     return NS_ERROR_ILLEGAL_VALUE;
 
-  MOZ_ASSERT(!mIsFullyLoaded && !mHaveAnimations && !mError,
+  MOZ_ASSERT(!mIsFullyLoaded && !mHaveAnimations &&
+             !mHaveRestrictedRegion && !mError,
              "Flags unexpectedly set before initialization");
   MOZ_ASSERT(!strcmp(aMimeType, IMAGE_SVG_XML), "Unexpected mimetype");
 
   mIsInitialized = true;
   return NS_OK;
 }
 
 nsIntRect
@@ -587,17 +590,24 @@ VectorImage::GetFrame(uint32_t aWhichFra
     // We'll get here if our SVG doc has a percent-valued width or height.
     return NS_ERROR_FAILURE;
   }
 
   // Create a surface that we'll ultimately return
   // ---------------------------------------------
   // Make our surface the size of what will ultimately be drawn to it.
   // (either the full image size, or the restricted region)
-  gfxIntSize surfaceSize(imageIntSize.width, imageIntSize.height);
+  gfxIntSize surfaceSize;
+  if (mHaveRestrictedRegion) {
+    surfaceSize.width = mRestrictedRegion.width;
+    surfaceSize.height = mRestrictedRegion.height;
+  } else {
+    surfaceSize.width = imageIntSize.width;
+    surfaceSize.height = imageIntSize.height;
+  }
 
   nsRefPtr<gfxImageSurface> surface =
     new gfxImageSurface(surfaceSize, gfxASurface::ImageFormatARGB32);
   nsRefPtr<gfxContext> context = new gfxContext(surface);
 
   // Draw to our surface!
   // --------------------
   nsresult rv = Draw(context, gfxPattern::FILTER_NEAREST, gfxMatrix(),
@@ -617,16 +627,64 @@ NS_IMETHODIMP
 VectorImage::GetImageContainer(LayerManager* aManager,
                                mozilla::layers::ImageContainer** _retval)
 {
   *_retval = nullptr;
   return NS_OK;
 }
 
 //******************************************************************************
+/* [noscript] imgIContainer extractFrame(uint32_t aWhichFrame,
+ *                                       [const] in nsIntRect aRegion,
+ *                                       in uint32_t aFlags); */
+NS_IMETHODIMP
+VectorImage::ExtractFrame(uint32_t aWhichFrame,
+                          const nsIntRect& aRegion,
+                          uint32_t aFlags,
+                          imgIContainer** _retval)
+{
+  NS_ENSURE_ARG_POINTER(_retval);
+  if (mError || !mIsFullyLoaded)
+    return NS_ERROR_FAILURE;
+
+  // XXXdholbert NOTE: This method assumes FRAME_CURRENT (not FRAME_FIRST)
+  // right now, because mozilla doesn't actually contain any clients of this
+  // method that use FRAME_FIRST.  If it's needed, we *could* handle
+  // FRAME_FIRST by saving the helper-doc's current SMIL time, seeking it to
+  // time 0, rendering to a RasterImage, and then restoring our saved time.
+  if (aWhichFrame != FRAME_CURRENT) {
+    NS_WARNING("VectorImage::ExtractFrame with something other than "
+               "FRAME_CURRENT isn't supported yet. Assuming FRAME_CURRENT.");
+  }
+
+  // XXXdholbert This method also doesn't actually freeze animation in the
+  // returned imgIContainer, because it shares our helper-document. To
+  // get a true snapshot, we need to clone the document - see bug 590792.
+
+  // Make a new container with same SVG document.
+  nsRefPtr<VectorImage> extractedImg = new VectorImage();
+  extractedImg->mSVGDocumentWrapper = mSVGDocumentWrapper;
+  extractedImg->mAnimationMode = kDontAnimMode;
+
+  extractedImg->mRestrictedRegion.x = aRegion.x;
+  extractedImg->mRestrictedRegion.y = aRegion.y;
+
+  // (disallow negative width/height on our restricted region)
+  extractedImg->mRestrictedRegion.width  = std::max(aRegion.width,  0);
+  extractedImg->mRestrictedRegion.height = std::max(aRegion.height, 0);
+
+  extractedImg->mIsInitialized = true;
+  extractedImg->mIsFullyLoaded = true;
+  extractedImg->mHaveRestrictedRegion = true;
+
+  *_retval = extractedImg.forget().get();
+  return NS_OK;
+}
+
+//******************************************************************************
 /* [noscript] void draw(in gfxContext aContext,
  *                      in gfxGraphicsFilter aFilter,
  *                      [const] in gfxMatrix aUserSpaceToImageSpace,
  *                      [const] in gfxRect aFill,
  *                      [const] in nsIntRect aSubimage,
  *                      [const] in nsIntSize aViewportSize,
  *                      [const] in SVGImageContext aSVGContext,
  *                      in uint32_t aWhichFrame,
@@ -659,42 +717,50 @@ VectorImage::Draw(gfxContext* aContext,
   float time = aWhichFrame == FRAME_FIRST ? 0.0f
                                           : mSVGDocumentWrapper->GetCurrentTime();
   AutoSVGRenderingState autoSVGState(aSVGContext,
                                      time,
                                      mSVGDocumentWrapper->GetRootSVGElem());
   mSVGDocumentWrapper->UpdateViewportBounds(aViewportSize);
   mSVGDocumentWrapper->FlushImageTransformInvalidation();
 
+  nsIntSize imageSize = mHaveRestrictedRegion ?
+    mRestrictedRegion.Size() : aViewportSize;
+
   // XXXdholbert Do we need to convert image size from
   // CSS pixels to dev pixels here? (is gfxCallbackDrawable's 2nd arg in dev
   // pixels?)
-  gfxIntSize imageSizeGfx(aViewportSize.width, aViewportSize.height);
+  gfxIntSize imageSizeGfx(imageSize.width, imageSize.height);
 
   // Based on imgFrame::Draw
   gfxRect sourceRect = aUserSpaceToImageSpace.Transform(aFill);
-  gfxRect imageRect(0, 0, aViewportSize.width, aViewportSize.height);
+  gfxRect imageRect(0, 0, imageSize.width, imageSize.height);
   gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height);
 
 
   nsRefPtr<gfxDrawingCallback> cb =
     new SVGDrawingCallback(mSVGDocumentWrapper,
+                           mHaveRestrictedRegion ?
+                           mRestrictedRegion :
                            nsIntRect(nsIntPoint(0, 0), aViewportSize),
                            aFlags);
 
   nsRefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, imageSizeGfx);
 
   gfxUtils::DrawPixelSnapped(aContext, drawable,
                              aUserSpaceToImageSpace,
                              subimage, sourceRect, imageRect, aFill,
                              gfxASurface::ImageFormatARGB32, aFilter);
 
-  // Allow ourselves to fire FrameChanged and OnStopFrame again.
-  MOZ_ASSERT(mRenderingObserver, "Should have a rendering observer by now");
-  mRenderingObserver->ResumeListening();
+  MOZ_ASSERT(mRenderingObserver || mHaveRestrictedRegion, 
+      "Should have a rendering observer by now unless ExtractFrame created us");
+  if (mRenderingObserver) {
+    // Allow ourselves to fire FrameChanged and OnStopFrame again.
+    mRenderingObserver->ResumeListening();
+  }
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* void requestDecode() */
 NS_IMETHODIMP
 VectorImage::RequestDecode()
--- a/image/src/VectorImage.h
+++ b/image/src/VectorImage.h
@@ -79,21 +79,28 @@ protected:
 private:
   void CancelAllListeners();
 
   nsRefPtr<SVGDocumentWrapper>       mSVGDocumentWrapper;
   nsRefPtr<SVGRootRenderingObserver> mRenderingObserver;
   nsRefPtr<SVGLoadEventListener>     mLoadEventListener;
   nsRefPtr<SVGParseCompleteListener> mParseCompleteListener;
 
+  nsIntRect      mRestrictedRegion;       // If we were created by
+                                          // ExtractFrame, this is the region
+                                          // that we're restricted to using.
+                                          // Otherwise, this is ignored.
+
   bool           mIsInitialized;          // Have we been initalized?
   bool           mIsFullyLoaded;          // Has the SVG document finished loading?
   bool           mIsDrawing;              // Are we currently drawing?
   bool           mHaveAnimations;         // Is our SVG content SMIL-animated?
                                           // (Only set after mIsFullyLoaded.)
+  bool           mHaveRestrictedRegion;   // Are we a restricted-region clone
+                                          // created via ExtractFrame?
 
   friend class ImageFactory;
 };
 
 inline NS_IMETHODIMP VectorImage::GetAnimationMode(uint16_t *aAnimationMode) {
   return GetAnimationModeInternal(aAnimationMode);
 }
 
--- a/image/src/imgRequestProxy.cpp
+++ b/image/src/imgRequestProxy.cpp
@@ -13,17 +13,17 @@
 #include "nsIMultiPartChannel.h"
 
 #include "nsString.h"
 #include "nsXPIDLString.h"
 #include "nsReadableUtils.h"
 #include "nsCRT.h"
 
 #include "Image.h"
-#include "ImageOps.h"
+#include "ImageFactory.h"
 #include "nsError.h"
 #include "ImageLogging.h"
 
 #include "nspr.h"
 
 using namespace mozilla::image;
 
 // The split of imgRequestProxy and imgRequestProxyStatic means that
@@ -910,17 +910,17 @@ imgRequestProxy::GetStaticRequest(imgReq
   // Check for errors in the image. Callers code rely on GetStaticRequest
   // failing in this case, though with FrozenImage there's no technical reason
   // for it anymore.
   if (image->HasError()) {
     return NS_ERROR_FAILURE;
   }
 
   // We are animated. We need to create a frozen version of this image.
-  nsRefPtr<Image> frozenImage = ImageOps::Freeze(image);
+  nsRefPtr<Image> frozenImage = ImageFactory::Freeze(image);
 
   // Create a static imgRequestProxy with our new extracted frame.
   nsCOMPtr<nsIPrincipal> currentPrincipal;
   GetImagePrincipal(getter_AddRefs(currentPrincipal));
   nsRefPtr<imgRequestProxy> req = new imgRequestProxyStatic(frozenImage,
                                                             currentPrincipal);
   req->Init(nullptr, nullptr, mURI, nullptr);
 
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -25,17 +25,16 @@
 #include "nsGkAtoms.h"
 #include "nsCSSAnonBoxes.h"
 #include "nsTransform2D.h"
 #include "nsIContent.h"
 #include "nsIDocumentInlines.h"
 #include "nsIScrollableFrame.h"
 #include "imgIRequest.h"
 #include "imgIContainer.h"
-#include "ImageOps.h"
 #include "nsCSSRendering.h"
 #include "nsCSSColorUtils.h"
 #include "nsITheme.h"
 #include "nsThemeConstants.h"
 #include "nsIServiceManager.h"
 #include "nsLayoutUtils.h"
 #include "nsINameSpaceManager.h"
 #include "nsBlockFrame.h"
@@ -56,17 +55,16 @@
 #include "mozilla/css/ImageLoader.h"
 #include "ImageContainer.h"
 #include "mozilla/Telemetry.h"
 #include "gfxUtils.h"
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::css;
-using mozilla::image::ImageOps;
 
 static int gFrameTreeLockCount = 0;
 
 // To avoid storing this data on nsInlineFrame (bloat) and to avoid
 // recalculating this for each frame in a continuation (perf), hold
 // a cache of various coordinate information that we need in order
 // to paint inline backgrounds.
 struct InlineBackgroundData
@@ -3311,20 +3309,30 @@ DrawBorderImageComponent(nsRenderingCont
                          uint8_t              aVFill,
                          const nsSize&        aUnitSize,
                          const nsStyleBorder& aStyleBorder,
                          uint8_t              aIndex)
 {
   if (aFill.IsEmpty() || aSrc.IsEmpty())
     return;
 
+  // Don't bother trying to cache sub images if the border image is animated
+  // We can only sucessfully call GetAnimated() if we are fully decoded, so default to true
+  bool animated = true;
+  aImage->GetAnimated(&animated);
+
   nsCOMPtr<imgIContainer> subImage;
-  if ((subImage = aStyleBorder.GetSubImage(aIndex)) == nullptr) {
-    subImage = ImageOps::Clip(aImage, aSrc);
-    aStyleBorder.SetSubImage(aIndex, subImage);
+  if (animated || (subImage = aStyleBorder.GetSubImage(aIndex)) == 0) {
+    if (NS_FAILED(aImage->ExtractFrame(imgIContainer::FRAME_CURRENT, aSrc,
+                                       imgIContainer::FLAG_SYNC_DECODE,
+                                       getter_AddRefs(subImage))))
+      return;
+
+    if (!animated)
+      aStyleBorder.SetSubImage(aIndex, subImage);
   }
 
   gfxPattern::GraphicsFilter graphicsFilter =
     nsLayoutUtils::GetGraphicsFilterForFrame(aForFrame);
 
   // If we have no tiling in either direction, we can skip the intermediate
   // scaling step.
   if ((aHFill == NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH &&
@@ -4280,17 +4288,28 @@ nsImageRenderer::PrepareImage()
         if (!success || actualCropRect.IsEmpty()) {
           // The cropped image has zero size
           return false;
         }
         if (isEntireImage) {
           // The cropped image is identical to the source image
           mImageContainer.swap(srcImage);
         } else {
-          nsCOMPtr<imgIContainer> subImage = ImageOps::Clip(srcImage, actualCropRect);
+          nsCOMPtr<imgIContainer> subImage;
+          uint32_t aExtractFlags = (mFlags & FLAG_SYNC_DECODE_IMAGES)
+                                     ? (uint32_t) imgIContainer::FLAG_SYNC_DECODE
+                                     : (uint32_t) imgIContainer::FLAG_NONE;
+          nsresult rv = srcImage->ExtractFrame(imgIContainer::FRAME_CURRENT,
+                                               actualCropRect, aExtractFlags,
+                                               getter_AddRefs(subImage));
+          if (NS_FAILED(rv)) {
+            NS_WARNING("The cropped image contains no pixels to draw; "
+                       "maybe the crop rect is outside the image frame rect");
+            return false;
+          }
           mImageContainer.swap(subImage);
         }
       }
       mIsReady = true;
       break;
     }
     case eStyleImageType_Gradient:
       mGradientData = mImage->GetGradientData();