Bug 815471 - Don't assume that all imgIContainers are either a RasterImage or a VectorImage. r=joe
authorSeth Fowler <seth@mozilla.com>
Thu, 13 Dec 2012 16:06:31 -0800
changeset 116587 6066a23ba701214c9aba74868fbbf718ae8d242b
parent 116586 1d268001c1377034127120bc5e134eb548193be5
child 116588 d0f96f272360f779e942951aab347dcd498203bb
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersjoe
bugs815471
milestone20.0a1
Bug 815471 - Don't assume that all imgIContainers are either a RasterImage or a VectorImage. r=joe
image/src/Decoder.cpp
image/src/Decoder.h
image/src/Image.h
image/src/ImageFactory.cpp
image/src/ImageFactory.h
image/src/Makefile.in
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/VectorImage.cpp
image/src/VectorImage.h
image/src/imgRequest.cpp
image/src/imgStatusTracker.cpp
image/src/imgTools.cpp
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -19,16 +19,17 @@ Decoder::Decoder(RasterImage &aImage, im
   , mDecodeDone(false)
   , mDataError(false)
   , mFrameCount(0)
   , mFailCode(NS_OK)
   , mInitialized(false)
   , mSizeDecode(false)
   , mInFrame(false)
   , mIsAnimated(false)
+  , mFirstWrite(true)
 {
 }
 
 Decoder::~Decoder()
 {
   mInitialized = false;
 }
 
@@ -37,45 +38,52 @@ Decoder::~Decoder()
  */
 
 void
 Decoder::Init()
 {
   // No re-initializing
   NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
 
-  // Fire OnStartDecode at init time to support bug 512435
-  if (!IsSizeDecode() && mObserver)
-      mObserver->OnStartDecode();
-
   // Implementation-specific initialization
   InitInternal();
   mInitialized = true;
 }
 
 // Initializes a decoder whose aImage and aObserver is already being used by a
 // parent decoder
 void
 Decoder::InitSharedDecoder()
 {
   // No re-initializing
   NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
 
+  // Prevent duplicate notifications.
+  mFirstWrite = false;
+
   // Implementation-specific initialization
   InitInternal();
   mInitialized = true;
 }
 
 void
 Decoder::Write(const char* aBuffer, uint32_t aCount)
 {
   // We're strict about decoder errors
   NS_ABORT_IF_FALSE(!HasDecoderError(),
                     "Not allowed to make more decoder calls after error!");
 
+  // If this is our first write, fire OnStartDecode to support bug 512435.
+  if (mFirstWrite) {
+    if (!IsSizeDecode() && mObserver)
+      mObserver->OnStartDecode();
+
+    mFirstWrite = false;
+  }
+
   // If a data error occured, just ignore future data
   if (HasDataError())
     return;
 
   // Pass the data along to the implementation
   WriteInternal(aBuffer, aCount);
 }
 
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -187,14 +187,15 @@ private:
   nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame.
 
   nsresult mFailCode;
 
   bool mInitialized;
   bool mSizeDecode;
   bool mInFrame;
   bool mIsAnimated;
+  bool mFirstWrite;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // MOZILLA_IMAGELIB_DECODER_H_
--- a/image/src/Image.h
+++ b/image/src/Image.h
@@ -3,16 +3,18 @@
  * 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_IMAGE_H_
 #define MOZILLA_IMAGELIB_IMAGE_H_
 
 #include "imgIContainer.h"
 #include "imgStatusTracker.h"
+#include "nsIRequest.h"
+#include "nsIInputStream.h"
 
 namespace mozilla {
 namespace image {
 
 class Image : public imgIContainer
 {
 public:
   imgStatusTracker& GetStatusTracker() { return *mStatusTracker; }
@@ -83,22 +85,56 @@ public:
   static eDecoderType GetDecoderType(const char *aMimeType);
 
   void IncrementAnimationConsumers();
   void DecrementAnimationConsumers();
 #ifdef DEBUG
   uint32_t GetAnimationConsumers() { return mAnimationConsumers; }
 #endif
 
+  /**
+   * Called from OnDataAvailable when the stream associated with the image has
+   * received new image data. The arguments are the same as OnDataAvailable's,
+   * but by separating this functionality into a different method we don't
+   * interfere with subclasses which wish to implement nsIStreamListener.
+   *
+   * Images should not do anything that could send out notifications until they
+   * have received their first OnImageDataAvailable notification; in
+   * particular, this means that instantiating decoders should be deferred
+   * until OnImageDataAvailable is called.
+   */
+  virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
+                                        nsISupports* aContext,
+                                        nsIInputStream* aInStr,
+                                        uint64_t aSourceOffset,
+                                        uint32_t aCount) = 0;
+
+  /**
+   * Called from OnStopRequest when the image's underlying request completes.
+   * The arguments are the same as OnStopRequest's, but by separating this
+   * functionality into a different method we don't interfere with subclasses
+   * which wish to implement nsIStreamListener.
+   */
+  virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
+                                       nsISupports* aContext,
+                                       nsresult status) = 0;
+
+  /**
+   * Called for multipart images to allow for any necessary reinitialization
+   * when there's a new part to add.
+   */
+  virtual nsresult OnNewSourceData() = 0;
+
   void SetInnerWindowID(uint64_t aInnerWindowId) {
     mInnerWindowId = aInnerWindowId;
   }
   uint64_t InnerWindowID() const { return mInnerWindowId; }
 
-  bool HasError() { return mError; }
+  bool HasError()    { return mError; }
+  void SetHasError() { mError = true; }
 
 protected:
   Image(imgStatusTracker* aStatusTracker);
 
   // Shared functionality for implementors of imgIContainer. Every
   // implementation of attribute animationMode should forward here.
   nsresult GetAnimationModeInternal(uint16_t *aAnimationMode);
   nsresult SetAnimationModeInternal(uint16_t aAnimationMode);
new file mode 100644
--- /dev/null
+++ b/image/src/ImageFactory.cpp
@@ -0,0 +1,193 @@
+/* -*- 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 "mozilla/Preferences.h"
+#include "mozilla/Likely.h"
+
+#include "nsIHttpChannel.h"
+
+#include "RasterImage.h"
+#include "VectorImage.h"
+
+#include "ImageFactory.h"
+
+namespace mozilla {
+namespace image {
+
+const char* SVG_MIMETYPE = "image/svg+xml";
+
+// Global preferences related to image containers.
+static bool gInitializedPrefCaches = false;
+static bool gDecodeOnDraw = false;
+static bool gDiscardable = false;
+
+static void
+InitPrefCaches()
+{
+  Preferences::AddBoolVarCache(&gDiscardable, "image.mem.discardable");
+  Preferences::AddBoolVarCache(&gDecodeOnDraw, "image.mem.decodeondraw");
+  gInitializedPrefCaches = true;
+}
+
+static uint32_t
+ComputeImageFlags(nsIURI* uri, bool isMultiPart)
+{
+  nsresult rv;
+
+  // We default to the static globals
+  bool isDiscardable = gDiscardable;
+  bool doDecodeOnDraw = gDecodeOnDraw;
+
+  // We want UI to be as snappy as possible and not to flicker. Disable discarding
+  // and decode-on-draw for chrome URLS
+  bool isChrome = false;
+  rv = uri->SchemeIs("chrome", &isChrome);
+  if (NS_SUCCEEDED(rv) && isChrome)
+    isDiscardable = doDecodeOnDraw = false;
+
+  // We don't want resources like the "loading" icon to be discardable or
+  // decode-on-draw either.
+  bool isResource = false;
+  rv = uri->SchemeIs("resource", &isResource);
+  if (NS_SUCCEEDED(rv) && isResource)
+    isDiscardable = doDecodeOnDraw = false;
+
+  // For multipart/x-mixed-replace, we basically want a direct channel to the
+  // decoder. Disable both for this case as well.
+  if (isMultiPart)
+    isDiscardable = doDecodeOnDraw = false;
+
+  // We have all the information we need
+  uint32_t imageFlags = Image::INIT_FLAG_NONE;
+  if (isDiscardable)
+    imageFlags |= Image::INIT_FLAG_DISCARDABLE;
+  if (doDecodeOnDraw)
+    imageFlags |= Image::INIT_FLAG_DECODE_ON_DRAW;
+  if (isMultiPart)
+    imageFlags |= Image::INIT_FLAG_MULTIPART;
+
+  return imageFlags;
+}
+
+/* static */ already_AddRefed<Image>
+ImageFactory::CreateImage(nsIRequest* aRequest,
+                          imgStatusTracker* aStatusTracker,
+                          const nsCString& aMimeType,
+                          nsIURI* aURI,
+                          bool aIsMultiPart,
+                          uint32_t aInnerWindowId)
+{
+  nsresult rv;
+
+  // Register our pref observers if we haven't yet.
+  if (MOZ_UNLIKELY(!gInitializedPrefCaches))
+    InitPrefCaches();
+
+  // Get the image's URI string.
+  nsAutoCString uriString;
+  rv = aURI ? aURI->GetSpec(uriString) : NS_ERROR_FAILURE;
+  if (NS_FAILED(rv))
+    uriString.Assign("<unknown image URI>");
+
+  // Compute the image's initialization flags.
+  uint32_t imageFlags = ComputeImageFlags(aURI, aIsMultiPart);
+
+  // Select the type of image to create based on MIME type.
+  if (aMimeType.Equals(SVG_MIMETYPE)) {
+    return CreateVectorImage(aRequest, aStatusTracker, aMimeType,
+                             uriString, imageFlags, aInnerWindowId);
+  } else {
+    return CreateRasterImage(aRequest, aStatusTracker, aMimeType,
+                             uriString, imageFlags, aInnerWindowId);
+  }
+}
+
+// Marks an image as having an error before returning it. Used with macros like
+// NS_ENSURE_SUCCESS, since we guarantee to always return an image even if an
+// error occurs, but callers need to be able to tell that this happened.
+template <typename T>
+static already_AddRefed<Image>
+BadImage(nsRefPtr<T>& image)
+{
+  image->SetHasError();
+  return image.forget();
+}
+
+/* static */ already_AddRefed<Image>
+ImageFactory::CreateRasterImage(nsIRequest* aRequest,
+                                imgStatusTracker* aStatusTracker,
+                                const nsCString& aMimeType,
+                                const nsCString& aURIString,
+                                uint32_t aImageFlags,
+                                uint32_t aInnerWindowId)
+{
+  nsresult rv;
+
+  nsRefPtr<RasterImage> newImage = new RasterImage(aStatusTracker);
+
+  rv = newImage->Init(aStatusTracker->GetDecoderObserver(),
+                      aMimeType.get(), aURIString.get(), aImageFlags);
+  NS_ENSURE_SUCCESS(rv, BadImage(newImage));
+
+  newImage->SetInnerWindowID(aInnerWindowId);
+
+  // Use content-length as a size hint for http channels.
+  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
+  if (httpChannel) {
+    nsAutoCString contentLength;
+    rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-length"),
+                                        contentLength);
+    if (NS_SUCCEEDED(rv)) {
+      int32_t len = contentLength.ToInteger(&rv);
+
+      // Pass anything usable on so that the RasterImage can preallocate
+      // its source buffer
+      if (len > 0) {
+        uint32_t sizeHint = (uint32_t) len;
+        sizeHint = NS_MIN<uint32_t>(sizeHint, 20000000); // Bound by something reasonable
+        rv = newImage->SetSourceSizeHint(sizeHint);
+        if (NS_FAILED(rv)) {
+          // Flush memory, try to get some back, and try again
+          rv = nsMemory::HeapMinimize(true);
+          nsresult rv2 = newImage->SetSourceSizeHint(sizeHint);
+          // If we've still failed at this point, things are going downhill
+          if (NS_FAILED(rv) || NS_FAILED(rv2)) {
+            NS_WARNING("About to hit OOM in imagelib!");
+          }
+        }
+      }
+    }
+  }
+
+  return newImage.forget();
+}
+
+/* static */ already_AddRefed<Image>
+ImageFactory::CreateVectorImage(nsIRequest* aRequest,
+                                imgStatusTracker* aStatusTracker,
+                                const nsCString& aMimeType,
+                                const nsCString& aURIString,
+                                uint32_t aImageFlags,
+                                uint32_t aInnerWindowId)
+{
+  nsresult rv;
+
+  nsRefPtr<VectorImage> newImage = new VectorImage(aStatusTracker);
+
+  rv = newImage->Init(aStatusTracker->GetDecoderObserver(),
+                      aMimeType.get(), aURIString.get(), aImageFlags);
+  NS_ENSURE_SUCCESS(rv, BadImage(newImage));
+
+  newImage->SetInnerWindowID(aInnerWindowId);
+
+  rv = newImage->OnStartRequest(aRequest, nullptr);
+  NS_ENSURE_SUCCESS(rv, BadImage(newImage));
+
+  return newImage.forget();
+}
+
+} // namespace image
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/image/src/ImageFactory.h
@@ -0,0 +1,60 @@
+/* -*- 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 "nsIURI.h"
+#include "nsIRequest.h"
+
+#include "imgIContainer.h"
+#include "imgStatusTracker.h"
+
+#include "Image.h"
+
+namespace mozilla {
+namespace image {
+
+extern const char* SVG_MIMETYPE;
+
+struct ImageFactory
+{
+  /**
+   * Creates a new image with the given properties.
+   *
+   * @param aRequest       The associated request.
+   * @param aStatusTracker A status tracker for the image to use.
+   * @param aMimeType      The mimetype of the image.
+   * @param aURI           The URI of the image.
+   * @param aIsMultiPart   Whether the image is part of a multipart request.
+   * @param aInnerWindowId The window this image belongs to.
+   */
+  static already_AddRefed<Image> CreateImage(nsIRequest* aRequest,
+                                             imgStatusTracker* aStatusTracker,
+                                             const nsCString& aMimeType,
+                                             nsIURI* aURI,
+                                             bool aIsMultiPart,
+                                             uint32_t aInnerWindowId);
+
+private:
+  // Factory functions that create specific types of image containers.
+  static already_AddRefed<Image> CreateRasterImage(nsIRequest* aRequest,
+                                                   imgStatusTracker* aStatusTracker,
+                                                   const nsCString& aMimeType,
+                                                   const nsCString& aURIString,
+                                                   uint32_t aImageFlags,
+                                                   uint32_t aInnerWindowId);
+
+  static already_AddRefed<Image> CreateVectorImage(nsIRequest* aRequest,
+                                                   imgStatusTracker* aStatusTracker,
+                                                   const nsCString& aMimeType,
+                                                   const nsCString& aURIString,
+                                                   uint32_t aImageFlags,
+                                                   uint32_t aInnerWindowId);
+
+  // This is a static factory class, so disallow instantiation.
+  virtual ~ImageFactory() = 0;
+};
+
+} // namespace image
+} // namespace mozilla
--- a/image/src/Makefile.in
+++ b/image/src/Makefile.in
@@ -21,16 +21,17 @@ FAIL_ON_WARNINGS = 1
 
 EXPORTS		=  imgLoader.h \
 		   imgRequest.h \
 		   imgRequestProxy.h \
 		   $(NULL)
 
 CPPSRCS		= \
 			Image.cpp \
+			ImageFactory.cpp \
 			Decoder.cpp \
 			DiscardTracker.cpp \
 			RasterImage.cpp \
 			ScriptedNotificationObserver.cpp \
 			SVGDocumentWrapper.cpp \
 			VectorImage.cpp \
 			imgFrame.cpp \
 			imgLoader.cpp    \
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -1800,17 +1800,17 @@ get_header_str (char *buf, char *data, s
     buf[i * 2]     = hex[(data[i] >> 4) & 0x0f];
     buf[i * 2 + 1] = hex[data[i] & 0x0f];
   }
 
   buf[i * 2] = 0;
 }
 
 nsresult
-RasterImage::SourceDataComplete()
+RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult)
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
   // If we've been called before, ignore. Otherwise, flag that we have everything
   if (mHasSourceData)
     return NS_OK;
   mHasSourceData = true;
@@ -1862,17 +1862,37 @@ RasterImage::SourceDataComplete()
   if (CanDiscard()) {
     nsresult rv = DiscardTracker::Reset(&mDiscardTrackerNode);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
   return NS_OK;
 }
 
 nsresult
-RasterImage::NewSourceData()
+RasterImage::OnImageDataAvailable(nsIRequest*,
+                                  nsISupports*,
+                                  nsIInputStream* aInStr,
+                                  uint64_t,
+                                  uint32_t aCount)
+{
+  nsresult rv;
+ 
+  // WriteToRasterImage always consumes everything it gets
+  // if it doesn't run out of memory
+  uint32_t bytesRead;
+  rv = aInStr->ReadSegments(WriteToRasterImage, this, aCount, &bytesRead);
+
+  NS_ABORT_IF_FALSE(bytesRead == aCount || HasError(),
+    "WriteToRasterImage should consume everything or the image must be in error!");
+
+  return rv;
+}
+
+nsresult
+RasterImage::OnNewSourceData()
 {
   nsresult rv;
 
   if (mError)
     return NS_ERROR_FAILURE;
 
   // The source data should be complete before calling this
   NS_ABORT_IF_FALSE(mHasSourceData,
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -18,16 +18,17 @@
 #define mozilla_imagelib_RasterImage_h_
 
 #include "Image.h"
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "imgIContainer.h"
 #include "nsIProperties.h"
 #include "nsITimer.h"
+#include "nsIRequest.h"
 #include "nsWeakReference.h"
 #include "nsTArray.h"
 #include "imgFrame.h"
 #include "nsThreadUtils.h"
 #include "DiscardTracker.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/LinkedList.h"
@@ -147,28 +148,31 @@ class RasterImage : public Image
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPROPERTIES
   NS_DECL_IMGICONTAINER
 #ifdef DEBUG
   NS_DECL_IMGICONTAINERDEBUG
 #endif
 
+  // XXX(seth) Currently the constructor is required to be public because it's
+  // exposed as part of the image module and imgTools needs it. New code should
+  // create images using ImageFactory.
   RasterImage(imgStatusTracker* aStatusTracker = nullptr);
   virtual ~RasterImage();
 
   virtual nsresult StartAnimation();
   virtual nsresult StopAnimation();
 
   // Methods inherited from Image
   nsresult Init(imgIDecoderObserver* aObserver,
                 const char* aMimeType,
                 const char* aURIString,
                 uint32_t aFlags);
-  void     GetCurrentFrameRect(nsIntRect& aRect);
+  virtual void  GetCurrentFrameRect(nsIntRect& aRect) MOZ_OVERRIDE;
 
   // Raster-specific methods
   static NS_METHOD WriteToRasterImage(nsIInputStream* aIn, void* aClosure,
                                       const char* aFromRawSegment,
                                       uint32_t aToOffset, uint32_t aCount,
                                       uint32_t* aWriteCount);
 
   /* The index of the current frame that would be drawn if the image was to be
@@ -244,21 +248,25 @@ public:
    * The decoder will use this data, either immediately or at draw time, to
    * decode the image.
    *
    * XXX This method's only caller (WriteToContainer) ignores the return
    * value. Should this just return void?
    */
   nsresult AddSourceData(const char *aBuffer, uint32_t aCount);
 
-  /* Called after the all the source data has been added with addSourceData. */
-  nsresult SourceDataComplete();
-
-  /* Called for multipart images when there's a new source image to add. */
-  nsresult NewSourceData();
+  virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
+                                        nsISupports* aContext,
+                                        nsIInputStream* aInStr,
+                                        uint64_t aSourceOffset,
+                                        uint32_t aCount) MOZ_OVERRIDE;
+  virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
+                                       nsISupports* aContext,
+                                       nsresult aResult) MOZ_OVERRIDE;
+  virtual nsresult OnNewSourceData() MOZ_OVERRIDE;
 
   /**
    * A hint of the number of bytes of source data that the image contains. If
    * called early on, this can help reduce copying and reallocations by
    * appropriately preallocating the source data buffer.
    *
    * We take this approach rather than having the source data management code do
    * something more complicated (like chunklisting) because HTTP is by far the
@@ -724,16 +732,18 @@ private: // data
   void DoError();
   bool CanDiscard();
   bool CanForciblyDiscard();
   bool DiscardingActive();
   bool StoringSourceData() const;
 
 protected:
   bool ShouldAnimate();
+
+  friend class ImageFactory;
 };
 
 inline NS_IMETHODIMP RasterImage::GetAnimationMode(uint16_t *aAnimationMode) {
   return GetAnimationModeInternal(aAnimationMode);
 }
 
 inline NS_IMETHODIMP RasterImage::SetAnimationMode(uint16_t aAnimationMode) {
   return SetAnimationModeInternal(aAnimationMode);
--- a/image/src/VectorImage.cpp
+++ b/image/src/VectorImage.cpp
@@ -239,16 +239,40 @@ VectorImage::NonHeapSizeOfDecoded() cons
 
 size_t
 VectorImage::OutOfProcessSizeOfDecoded() const
 {
   return 0;
 }
 
 nsresult
+VectorImage::OnImageDataComplete(nsIRequest* aRequest,
+                                 nsISupports* aContext,
+                                 nsresult aStatus)
+{
+  return OnStopRequest(aRequest, aContext, aStatus);
+}
+
+nsresult
+VectorImage::OnImageDataAvailable(nsIRequest* aRequest,
+                                  nsISupports* aContext,
+                                  nsIInputStream* aInStr,
+                                  uint64_t aSourceOffset,
+                                  uint32_t aCount)
+{
+  return OnDataAvailable(aRequest, aContext, aInStr, aSourceOffset, aCount);
+}
+
+nsresult
+VectorImage::OnNewSourceData()
+{
+  return NS_OK;
+}
+
+nsresult
 VectorImage::StartAnimation()
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
   NS_ABORT_IF_FALSE(ShouldAnimate(), "Should not animate!");
 
   mSVGDocumentWrapper->StartAnimation();
--- a/image/src/VectorImage.h
+++ b/image/src/VectorImage.h
@@ -3,16 +3,17 @@
  * 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_VectorImage_h_
 #define mozilla_imagelib_VectorImage_h_
 
 #include "Image.h"
 #include "nsIStreamListener.h"
+#include "nsIRequest.h"
 #include "nsWeakReference.h"
 #include "mozilla/TimeStamp.h"
 
 class imgIDecoderObserver;
 
 namespace mozilla {
 namespace layers {
 class LayerManager;
@@ -27,35 +28,47 @@ class VectorImage : public Image,
                     public nsIStreamListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_IMGICONTAINER
 
-  VectorImage(imgStatusTracker* aStatusTracker = nullptr);
+  // (no public constructor - use ImageFactory)
   virtual ~VectorImage();
 
   // Methods inherited from Image
   nsresult Init(imgIDecoderObserver* aObserver,
                 const char* aMimeType,
                 const char* aURIString,
                 uint32_t aFlags);
-  void GetCurrentFrameRect(nsIntRect& aRect);
+  virtual void GetCurrentFrameRect(nsIntRect& aRect) MOZ_OVERRIDE;
 
   virtual size_t HeapSizeOfSourceWithComputedFallback(nsMallocSizeOfFun aMallocSizeOf) const;
   virtual size_t HeapSizeOfDecodedWithComputedFallback(nsMallocSizeOfFun aMallocSizeOf) const;
   virtual size_t NonHeapSizeOfDecoded() const;
   virtual size_t OutOfProcessSizeOfDecoded() const;
 
+  virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
+                                        nsISupports* aContext,
+                                        nsIInputStream* aInStr,
+                                        uint64_t aSourceOffset,
+                                        uint32_t aCount) MOZ_OVERRIDE;
+  virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
+                                       nsISupports* aContext,
+                                       nsresult status) MOZ_OVERRIDE;
+  virtual nsresult OnNewSourceData() MOZ_OVERRIDE;
+
   // Callback for SVGRootRenderingObserver
   void InvalidateObserver();
 
 protected:
+  VectorImage(imgStatusTracker* aStatusTracker = nullptr);
+
   virtual nsresult StartAnimation();
   virtual nsresult StopAnimation();
   virtual bool     ShouldAnimate();
 
 private:
   nsWeakPtr                          mObserver;   //! imgIDecoderObserver
   nsRefPtr<SVGDocumentWrapper>       mSVGDocumentWrapper;
   nsRefPtr<SVGRootRenderingObserver> mRenderingObserver;
@@ -67,16 +80,18 @@ private:
 
   bool           mIsInitialized:1;        // Have we been initalized?
   bool           mIsFullyLoaded:1;        // Has OnStopRequest been called?
   bool           mIsDrawing:1;            // Are we currently drawing?
   bool           mHaveAnimations:1;       // Is our SVG content SMIL-animated?
                                           // (Only set after mIsFullyLoaded.)
   bool           mHaveRestrictedRegion:1; // Are we a restricted-region clone
                                           // created via ExtractFrame?
+
+  friend class ImageFactory;
 };
 
 inline NS_IMETHODIMP VectorImage::GetAnimationMode(uint16_t *aAnimationMode) {
   return GetAnimationModeInternal(aAnimationMode);
 }
 
 inline NS_IMETHODIMP VectorImage::SetAnimationMode(uint16_t aAnimationMode) {
   return SetAnimationModeInternal(aAnimationMode);
--- a/image/src/imgRequest.cpp
+++ b/image/src/imgRequest.cpp
@@ -12,18 +12,17 @@
  * gets changed.
  * This #undef needs to be in multiple places because we don't always pull
  * headers in in the same order.
  */
 #undef LoadImage
 
 #include "imgLoader.h"
 #include "imgRequestProxy.h"
-#include "RasterImage.h"
-#include "VectorImage.h"
+#include "ImageFactory.h"
 
 #include "imgILoader.h"
 
 #include "netCore.h"
 
 #include "nsIChannel.h"
 #include "nsICachingChannel.h"
 #include "nsILoadGroup.h"
@@ -42,39 +41,22 @@
 #include "nsICacheVisitor.h"
 
 #include "nsString.h"
 #include "nsXPIDLString.h"
 #include "plstr.h" // PL_strcasestr(...)
 #include "nsNetUtil.h"
 #include "nsIProtocolHandler.h"
 
-#include "mozilla/Preferences.h"
-#include "mozilla/Likely.h"
-
 #include "DiscardTracker.h"
 #include "nsAsyncRedirectVerifyHelper.h"
 
-#define SVG_MIMETYPE "image/svg+xml"
-
 using namespace mozilla;
 using namespace mozilla::image;
 
-static bool gInitializedPrefCaches = false;
-static bool gDecodeOnDraw = false;
-static bool gDiscardable = false;
-
-static void
-InitPrefCaches()
-{
-  Preferences::AddBoolVarCache(&gDiscardable, "image.mem.discardable");
-  Preferences::AddBoolVarCache(&gDecodeOnDraw, "image.mem.decodeondraw");
-  gInitializedPrefCaches = true;
-}
-
 #if defined(PR_LOGGING)
 PRLogModuleInfo *
 GetImgLog()
 {
   static PRLogModuleInfo *sImgLog;
   if (!sImgLog)
     sImgLog = PR_NewLogModule("imgRequest");
   return sImgLog;
@@ -93,22 +75,17 @@ imgRequest::imgRequest(imgLoader* aLoade
  , mValidator(nullptr)
  , mInnerWindowId(0)
  , mCORSMode(imgIRequest::CORS_NONE)
  , mDecodeRequested(false)
  , mIsMultiPartChannel(false)
  , mGotData(false)
  , mIsInCache(false)
  , mResniffMimeType(false)
-{
-  // Register our pref observers if we haven't yet.
-  if (MOZ_UNLIKELY(!gInitializedPrefCaches)) {
-    InitPrefCaches();
-  }
-}
+{ }
 
 imgRequest::~imgRequest()
 {
   // The status tracker can outlive this request, and needs to know it's dying.
   GetStatusTracker().ClearRequest();
 
   if (mURI) {
     nsAutoCString spec;
@@ -559,24 +536,23 @@ NS_IMETHODIMP imgRequest::OnStartRequest
   // If we're not multipart, we shouldn't have an image yet
   NS_ABORT_IF_FALSE(mIsMultiPartChannel || !mImage,
                     "Already have an image for non-multipart request");
 
   // If we're multipart and about to load another image, signal so we can
   // detect the mime type in OnDataAvailable.
   if (mIsMultiPartChannel && mImage) {
     mResniffMimeType = true;
-    if (mImage->GetType() == imgIContainer::TYPE_RASTER) {
-        // Tell the RasterImage to reinitialize itself. We have to do this in
-        // OnStartRequest so that its state machine is always in a consistent
-        // state.
-        // Note that if our MIME type changes, mImage will be replaced with a
-        // new object.
-        static_cast<RasterImage*>(mImage.get())->NewSourceData();
-      }
+
+    // Tell the image to reinitialize itself. We have to do this in
+    // OnStartRequest so that its state machine is always in a consistent
+    // state.
+    // Note that if our MIME type changes, mImage will be replaced with a
+    // new object.
+    mImage->OnNewSourceData();
   }
 
   /*
    * If mRequest is null here, then we need to set it so that we'll be able to
    * cancel it if our Cancel() method is called.  Note that this can only
    * happen for multipart channels.  We could simply not null out mRequest for
    * non-last parts, if GetIsLastPart() were reliable, but it's not.  See
    * https://bugzilla.mozilla.org/show_bug.cgi?id=339610
@@ -645,31 +621,22 @@ NS_IMETHODIMP imgRequest::OnStopRequest(
     mPrevChannelSink = nullptr;
     mChannel = nullptr;
   }
 
   // Tell the image that it has all of the source data. Note that this can
   // trigger a failure, since the image might be waiting for more non-optional
   // data and this is the point where we break the news that it's not coming.
   if (mImage) {
-    nsresult rv;
-    if (mImage->GetType() == imgIContainer::TYPE_RASTER) {
-      // Notify the image
-      rv = static_cast<RasterImage*>(mImage.get())->SourceDataComplete();
-    } else { // imageType == imgIContainer::TYPE_VECTOR
-      nsCOMPtr<nsIStreamListener> imageAsStream = do_QueryInterface(mImage);
-      NS_ABORT_IF_FALSE(imageAsStream,
-                        "SVG-typed Image failed QI to nsIStreamListener");
-      rv = imageAsStream->OnStopRequest(aRequest, ctxt, status);
-    }
+    nsresult rv = mImage->OnImageDataComplete(aRequest, ctxt, status);
 
-    // If we got an error in the SourceDataComplete() / OnStopRequest() call,
-    // we don't want to proceed as if nothing bad happened. However, we also
-    // want to give precedence to failure status codes from necko, since
-    // presumably they're more meaningful.
+    // If we got an error in the OnImageDataComplete() call, we don't want to
+    // proceed as if nothing bad happened. However, we also want to give
+    // precedence to failure status codes from necko, since presumably they're
+    // more meaningful.
     if (NS_FAILED(rv) && NS_SUCCEEDED(status))
       status = rv;
   }
 
   imgStatusTracker& statusTracker = GetStatusTracker();
   statusTracker.RecordStopRequest(lastPart, status);
 
   // If the request went through, update the cache entry size. Otherwise,
@@ -755,41 +722,32 @@ imgRequest::OnDataAvailable(nsIRequest *
     }
 
     // If we're a regular image and this is the first call to OnDataAvailable,
     // this will always be true. If we've resniffed our MIME type (i.e. we're a
     // multipart/x-mixed-replace image), we have to be able to switch our image
     // type and decoder.
     // We always reinitialize for SVGs, because they have no way of
     // reinitializing themselves.
-    if (mContentType != newType || newType.EqualsLiteral(SVG_MIMETYPE)) {
+    if (mContentType != newType || newType.Equals(SVG_MIMETYPE)) {
       mContentType = newType;
 
       // If we've resniffed our MIME type and it changed, we need to create a
       // new status tracker to give to the image, because we don't have one of
       // our own any more.
       if (mResniffMimeType) {
         NS_ABORT_IF_FALSE(mIsMultiPartChannel, "Resniffing a non-multipart image");
+
         imgStatusTracker* freshTracker = new imgStatusTracker(nullptr, this);
         freshTracker->AdoptConsumers(&GetStatusTracker());
         mStatusTracker = freshTracker;
+
+        mResniffMimeType = false;
       }
 
-      mResniffMimeType = false;
-
-      /* now we have mimetype, so we can infer the image type that we want */
-      if (mContentType.EqualsLiteral(SVG_MIMETYPE)) {
-        mImage = new VectorImage(mStatusTracker.forget());
-      } else {
-        mImage = new RasterImage(mStatusTracker.forget());
-      }
-      mImage->SetInnerWindowID(mInnerWindowId);
-
-      GetStatusTracker().OnDataAvailable();
-
       /* set our mimetype as a property */
       nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1"));
       if (contentType) {
         contentType->SetData(mContentType);
         mProperties->Set("type", contentType);
       }
 
       /* set our content disposition as a property */
@@ -802,131 +760,44 @@ imgRequest::OnDataAvailable(nsIRequest *
         if (contentDisposition) {
           contentDisposition->SetData(disposition);
           mProperties->Set("content-disposition", contentDisposition);
         }
       }
 
       LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "content type", mContentType.get());
 
-      //
-      // Figure out our Image initialization flags
-      //
-
-      // We default to the static globals
-      bool isDiscardable = gDiscardable;
-      bool doDecodeOnDraw = gDecodeOnDraw;
-
-      // We want UI to be as snappy as possible and not to flicker. Disable discarding
-      // and decode-on-draw for chrome URLS
-      bool isChrome = false;
-      rv = mURI->SchemeIs("chrome", &isChrome);
-      if (NS_SUCCEEDED(rv) && isChrome)
-        isDiscardable = doDecodeOnDraw = false;
-
-      // We don't want resources like the "loading" icon to be discardable or
-      // decode-on-draw either.
-      bool isResource = false;
-      rv = mURI->SchemeIs("resource", &isResource);
-      if (NS_SUCCEEDED(rv) && isResource)
-        isDiscardable = doDecodeOnDraw = false;
-
-      // For multipart/x-mixed-replace, we basically want a direct channel to the
-      // decoder. Disable both for this case as well.
-      if (mIsMultiPartChannel)
-        isDiscardable = doDecodeOnDraw = false;
+      // Now we can create a new image to hold the data. If we don't have a decoder
+      // for this mimetype we'll find out about it here.
+      mImage = ImageFactory::CreateImage(aRequest, mStatusTracker.forget(), mContentType,
+                                         mURI, mIsMultiPartChannel, mInnerWindowId);
 
-      // We have all the information we need
-      uint32_t imageFlags = Image::INIT_FLAG_NONE;
-      if (isDiscardable)
-        imageFlags |= Image::INIT_FLAG_DISCARDABLE;
-      if (doDecodeOnDraw)
-        imageFlags |= Image::INIT_FLAG_DECODE_ON_DRAW;
-      if (mIsMultiPartChannel)
-        imageFlags |= Image::INIT_FLAG_MULTIPART;
+      // Notify listeners that we have an image.
+      // XXX(seth): The name of this notification method is pretty misleading.
+      GetStatusTracker().OnDataAvailable();
 
-      // Get our URI string
-      nsAutoCString uriString;
-      rv = mURI->GetSpec(uriString);
-      if (NS_FAILED(rv))
-        uriString.Assign("<unknown image URI>");
-
-      // Initialize the image that we created above. For RasterImages, this
-      // instantiates a decoder behind the scenes, so if we don't have a decoder
-      // for this mimetype we'll find out about it here.
-      rv = mImage->Init(GetStatusTracker().GetDecoderObserver(),
-                        mContentType.get(), uriString.get(), imageFlags);
-
-      // We allow multipart images to fail to initialize without cancelling the
-      // load because subsequent images might be fine.
-      if (NS_FAILED(rv) && !mIsMultiPartChannel) { // Probably bad mimetype
-
-        this->Cancel(rv);
+      if (mImage->HasError() && !mIsMultiPartChannel) { // Probably bad mimetype
+        // We allow multipart images to fail to initialize without cancelling the
+        // load because subsequent images might be fine; thus only single part
+        // images end up here.
+        this->Cancel(NS_ERROR_FAILURE);
         return NS_BINDING_ABORTED;
       }
 
-      if (mImage->GetType() == imgIContainer::TYPE_RASTER) {
-        /* Use content-length as a size hint for http channels. */
-        nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
-        if (httpChannel) {
-          nsAutoCString contentLength;
-          rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-length"),
-                                              contentLength);
-          if (NS_SUCCEEDED(rv)) {
-            int32_t len = contentLength.ToInteger(&rv);
+      NS_ABORT_IF_FALSE(!!GetStatusTracker().GetImage(), "Status tracker should have an image!");
+      NS_ABORT_IF_FALSE(mImage, "imgRequest should have an image!");
 
-            // Pass anything usable on so that the RasterImage can preallocate
-            // its source buffer
-            if (len > 0) {
-              uint32_t sizeHint = (uint32_t) len;
-              sizeHint = NS_MIN<uint32_t>(sizeHint, 20000000); /* Bound by something reasonable */
-              RasterImage* rasterImage = static_cast<RasterImage*>(mImage.get());
-              rv = rasterImage->SetSourceSizeHint(sizeHint);
-              if (NS_FAILED(rv)) {
-                // Flush memory, try to get some back, and try again
-                rv = nsMemory::HeapMinimize(true);
-                nsresult rv2 = rasterImage->SetSourceSizeHint(sizeHint);
-                // If we've still failed at this point, things are going downhill
-                if (NS_FAILED(rv) || NS_FAILED(rv2)) {
-                  NS_WARNING("About to hit OOM in imagelib!");
-                }
-              }
-            }
-          }
-        }
-      }
-
-      if (mImage->GetType() == imgIContainer::TYPE_RASTER) {
-        // If we were waiting on the image to do something, now's our chance.
-        if (mDecodeRequested) {
-          mImage->StartDecoding();
-        }
-      } else { // mImage->GetType() == imgIContainer::TYPE_VECTOR
-        nsCOMPtr<nsIStreamListener> imageAsStream = do_QueryInterface(mImage);
-        NS_ABORT_IF_FALSE(imageAsStream,
-                          "SVG-typed Image failed QI to nsIStreamListener");
-        imageAsStream->OnStartRequest(aRequest, nullptr);
-      }
+      if (mDecodeRequested)
+        mImage->StartDecoding();
     }
   }
 
-  if (mImage->GetType() == imgIContainer::TYPE_RASTER) {
-    // WriteToRasterImage always consumes everything it gets
-    // if it doesn't run out of memory
-    uint32_t bytesRead;
-    rv = inStr->ReadSegments(RasterImage::WriteToRasterImage,
-                             static_cast<void*>(mImage),
-                             count, &bytesRead);
-    NS_ABORT_IF_FALSE(bytesRead == count || mImage->HasError(),
-  "WriteToRasterImage should consume everything or the image must be in error!");
-  } else { // mImage->GetType() == imgIContainer::TYPE_VECTOR
-    nsCOMPtr<nsIStreamListener> imageAsStream = do_QueryInterface(mImage);
-    rv = imageAsStream->OnDataAvailable(aRequest, ctxt, inStr,
-                                        sourceOffset, count);
-  }
+  // Notify the image that it has new data.
+  rv = mImage->OnImageDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);
+
   if (NS_FAILED(rv)) {
     PR_LOG(GetImgLog(), PR_LOG_WARNING,
            ("[this=%p] imgRequest::OnDataAvailable -- "
             "copy to RasterImage failed\n", this));
     this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
     return NS_BINDING_ABORTED;
   }
 
--- a/image/src/imgStatusTracker.cpp
+++ b/image/src/imgStatusTracker.cpp
@@ -398,31 +398,30 @@ imgStatusTracker::SyncNotify(imgRequestP
   if (mState & stateHasSize)
     proxy->OnStartContainer();
 
   // BlockOnload
   if (mState & stateBlockingOnload)
     proxy->BlockOnload();
 
   if (mImage) {
-    int16_t imageType = mImage->GetType();
-    // Send frame messages (OnDataAvailable, OnStopFrame)
-    if (imageType == imgIContainer::TYPE_VECTOR ||
-        static_cast<RasterImage*>(mImage)->GetNumFrames() > 0) {
+    // OnDataAvailable
+    // XXX - Should only send partial rects here, but that needs to
+    // wait until we fix up the observer interface
+    nsIntRect r;
+    mImage->GetCurrentFrameRect(r);
 
-      // OnDataAvailable
-      // XXX - Should only send partial rects here, but that needs to
-      // wait until we fix up the observer interface
-      nsIntRect r;
-      mImage->GetCurrentFrameRect(r);
+    // If there's any content in this frame at all (always true for
+    // vector images, true for raster images that have decoded at
+    // least one frame) then send OnFrameUpdate.
+    if (!r.IsEmpty())
       proxy->OnFrameUpdate(&r);
 
-      if (mState & stateFrameStopped)
-        proxy->OnStopFrame();
-    }
+    if (mState & stateFrameStopped)
+      proxy->OnStopFrame();
 
     // OnImageIsAnimated
     bool isAnimated = false;
 
     nsresult rv = mImage->GetAnimated(&isAnimated);
     if (NS_SUCCEEDED(rv) && isAnimated) {
       proxy->OnImageIsAnimated();
     }
--- a/image/src/imgTools.cpp
+++ b/image/src/imgTools.cpp
@@ -52,16 +52,19 @@ NS_IMETHODIMP imgTools::DecodeImageData(
                                         const nsACString& aMimeType,
                                         imgIContainer **aContainer)
 {
   nsresult rv;
   RasterImage* image;  // convenience alias for *aContainer
 
   NS_ENSURE_ARG_POINTER(aInStr);
 
+  // XXX(seth) This needs to be switched over to use ImageFactory, but we need
+  // to be sure that no callers actually provide an existing imgIContainer first.
+
   // If the caller didn't provide an imgIContainer, create one.
   if (*aContainer) {
     NS_ABORT_IF_FALSE((*aContainer)->GetType() == imgIContainer::TYPE_RASTER,
                       "wrong type of imgIContainer for decoding into");
     image = static_cast<RasterImage*>(*aContainer);
   } else {
     *aContainer = image = new RasterImage();
     NS_ADDREF(image);
@@ -93,17 +96,17 @@ NS_IMETHODIMP imgTools::DecodeImageData(
   rv = inStream->ReadSegments(RasterImage::WriteToRasterImage,
                               static_cast<void*>(image),
                               (uint32_t)length, &bytesRead);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ABORT_IF_FALSE(bytesRead == length || image->HasError(),
   "WriteToRasterImage should consume everything or the image must be in error!");
 
   // Let the Image know we've sent all the data
-  rv = image->SourceDataComplete();
+  rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // All done
   return NS_OK;
 }
 
 
 NS_IMETHODIMP imgTools::EncodeImage(imgIContainer *aContainer,