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 125327 6066a23ba701214c9aba74868fbbf718ae8d242b
parent 125326 1d268001c1377034127120bc5e134eb548193be5
child 125328 d0f96f272360f779e942951aab347dcd498203bb
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoe
bugs815471
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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,