Bug 1112972 - Part 2: Add MultipartImage and use it for multipart/x-mixed-replace image loading. r=tn, a=sledru
authorSeth Fowler <seth@mozilla.com>
Wed, 07 Jan 2015 01:37:20 -0800
changeset 243672 438fed84c7c3
parent 243671 28f4806f60ee
child 243673 c858f34b8153
push id4432
push userryanvm@gmail.com
push date2015-02-04 15:12 +0000
treeherdermozilla-beta@7422906b1a32 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn, sledru
bugs1112972
milestone36.0
Bug 1112972 - Part 2: Add MultipartImage and use it for multipart/x-mixed-replace image loading. r=tn, a=sledru
image/src/MultipartImage.cpp
image/src/MultipartImage.h
image/src/ProgressTracker.cpp
image/src/imgRequest.cpp
image/src/imgRequest.h
image/src/moz.build
new file mode 100644
--- /dev/null
+++ b/image/src/MultipartImage.cpp
@@ -0,0 +1,299 @@
+/* -*- 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 "MultipartImage.h"
+
+#include "imgINotificationObserver.h"
+
+namespace mozilla {
+namespace image {
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers
+///////////////////////////////////////////////////////////////////////////////
+
+class NextPartObserver : public IProgressObserver
+{
+public:
+  MOZ_DECLARE_REFCOUNTED_TYPENAME(NextPartObserver)
+  NS_INLINE_DECL_REFCOUNTING(NextPartObserver)
+
+  explicit NextPartObserver(MultipartImage* aOwner)
+    : mOwner(aOwner)
+  {
+    MOZ_ASSERT(mOwner);
+  }
+
+  void BeginObserving(Image* aImage)
+  {
+    MOZ_ASSERT(aImage);
+    mImage = aImage;
+
+    nsRefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
+    tracker->AddObserver(this);
+  }
+
+  void FinishObservingWithoutNotifying()
+  {
+    FinishObserving(/* aNotify = */ false);
+  }
+
+  virtual void Notify(int32_t aType,
+                      const nsIntRect* aRect = nullptr) MOZ_OVERRIDE
+  {
+    if (!mImage) {
+      // We've already finished observing the last image we were given.
+      return;
+    }
+
+    if (aType == imgINotificationObserver::FRAME_COMPLETE) {
+      FinishObserving(/* aNotify = */ true);
+    }
+  }
+
+  virtual void OnLoadComplete(bool aLastPart) MOZ_OVERRIDE
+  {
+    if (!mImage) {
+      // We've already finished observing the last image we were given.
+      return;
+    }
+
+    // If there's already an error, we may never get a FRAME_COMPLETE
+    // notification, so go ahead and notify our owner right away.
+    nsRefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
+    if (tracker->GetProgress() & FLAG_HAS_ERROR) {
+      FinishObserving(/* aNotify = */ true);
+    }
+  }
+
+  // Other notifications are ignored.
+  virtual void BlockOnload() MOZ_OVERRIDE { }
+  virtual void UnblockOnload() MOZ_OVERRIDE { }
+  virtual void SetHasImage() MOZ_OVERRIDE { }
+  virtual void OnStartDecode() MOZ_OVERRIDE { }
+  virtual bool NotificationsDeferred() const MOZ_OVERRIDE { return false; }
+  virtual void SetNotificationsDeferred(bool) MOZ_OVERRIDE { }
+
+private:
+  virtual ~NextPartObserver() { }
+
+  void FinishObserving(bool aNotify)
+  {
+    MOZ_ASSERT(mImage);
+
+    nsRefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
+    tracker->RemoveObserver(this);
+    mImage = nullptr;
+
+    if (aNotify) {
+      mOwner->FinishTransition();
+    }
+  }
+
+  MultipartImage* mOwner;
+  nsRefPtr<Image> mImage;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Implementation
+///////////////////////////////////////////////////////////////////////////////
+
+MultipartImage::MultipartImage(Image* aImage, ProgressTracker* aTracker)
+  : ImageWrapper(aImage)
+  , mDeferNotifications(false)
+{
+  MOZ_ASSERT(aTracker);
+  mProgressTrackerInit = new ProgressTrackerInit(this, aTracker);
+  mNextPartObserver = new NextPartObserver(this);
+
+  // Start observing the first part.
+  nsRefPtr<ProgressTracker> firstPartTracker =
+    InnerImage()->GetProgressTracker();
+  firstPartTracker->AddObserver(this);
+  InnerImage()->RequestDecode();
+  InnerImage()->IncrementAnimationConsumers();
+}
+
+MultipartImage::~MultipartImage() { }
+
+NS_IMPL_QUERY_INTERFACE_INHERITED0(MultipartImage, ImageWrapper) 
+NS_IMPL_ADDREF(MultipartImage)
+NS_IMPL_RELEASE(MultipartImage)
+
+void
+MultipartImage::BeginTransitionToPart(Image* aNextPart)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aNextPart);
+
+  if (mNextPart) {
+    NS_WARNING("Decoder not keeping up with multipart image");
+    mNextPartObserver->FinishObservingWithoutNotifying();
+  }
+
+  mNextPart = aNextPart;
+
+  // Start observing the next part; we'll complete the transition when
+  // NextPartObserver calls FinishTransition.
+  mNextPartObserver->BeginObserving(mNextPart);
+  mNextPart->RequestDecode();
+  mNextPart->IncrementAnimationConsumers();
+}
+
+void MultipartImage::FinishTransition()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mNextPart, "Should have a next part here");
+
+  // Stop observing the current part.
+  {
+    nsRefPtr<ProgressTracker> currentPartTracker =
+      InnerImage()->GetProgressTracker();
+    currentPartTracker->RemoveObserver(this);
+  }
+
+  // Make the next part become the current part.
+  mTracker->ResetForNewRequest();
+  SetInnerImage(mNextPart);
+  mNextPart = nullptr;
+  nsRefPtr<ProgressTracker> newCurrentPartTracker =
+    InnerImage()->GetProgressTracker();
+  newCurrentPartTracker->AddObserver(this);
+
+  // Finally, send all the notifications for the new current part and send a
+  // FRAME_UPDATE notification so that observers know to redraw.
+  mTracker->SyncNotifyProgress(newCurrentPartTracker->GetProgress(),
+                               nsIntRect::GetMaxSizedIntRect());
+}
+
+already_AddRefed<imgIContainer>
+MultipartImage::Unwrap()
+{
+  // Although we wrap another image, we don't allow callers to unwrap as. As far
+  // as external code is concerned, MultipartImage is atomic.
+  nsCOMPtr<imgIContainer> image = this;
+  return image.forget();
+}
+
+already_AddRefed<ProgressTracker>
+MultipartImage::GetProgressTracker()
+{
+  MOZ_ASSERT(mTracker);
+  nsRefPtr<ProgressTracker> tracker = mTracker;
+  return tracker.forget();
+}
+
+void
+MultipartImage::SetProgressTracker(ProgressTracker* aTracker)
+{
+  MOZ_ASSERT(aTracker);
+  MOZ_ASSERT(!mTracker);
+  mTracker = aTracker;
+}
+
+nsresult
+MultipartImage::OnImageDataAvailable(nsIRequest* aRequest,
+                                     nsISupports* aContext,
+                                     nsIInputStream* aInStr,
+                                     uint64_t aSourceOffset,
+                                     uint32_t aCount)
+{
+  // Note that this method is special in that we forward it to the next part if
+  // one exists, and *not* the current part.
+
+  // We may trigger notifications that will free mNextPart, so keep it alive.
+  nsRefPtr<Image> nextPart = mNextPart;
+  if (nextPart) {
+    return nextPart->OnImageDataAvailable(aRequest, aContext, aInStr,
+                                          aSourceOffset, aCount);
+  }
+
+  return InnerImage()->OnImageDataAvailable(aRequest, aContext, aInStr,
+                                            aSourceOffset, aCount);
+}
+
+nsresult
+MultipartImage::OnImageDataComplete(nsIRequest* aRequest,
+                                    nsISupports* aContext,
+                                    nsresult aStatus,
+                                    bool aLastPart)
+{
+  // Note that this method is special in that we forward it to the next part if
+  // one exists, and *not* the current part.
+
+  // We may trigger notifications that will free mNextPart, so keep it alive.
+  nsRefPtr<Image> nextPart = mNextPart;
+  if (nextPart) {
+    return nextPart->OnImageDataComplete(aRequest, aContext, aStatus,
+                                         aLastPart);
+  }
+
+  return InnerImage()->OnImageDataComplete(aRequest, aContext, aStatus,
+                                           aLastPart);
+}
+
+void
+MultipartImage::Notify(int32_t aType, const nsIntRect* aRect /* = nullptr*/)
+{
+  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+    mTracker->SyncNotifyProgress(FLAG_SIZE_AVAILABLE);
+  } else if (aType == imgINotificationObserver::FRAME_UPDATE) {
+    mTracker->SyncNotifyProgress(NoProgress, *aRect);
+  } else if (aType == imgINotificationObserver::FRAME_COMPLETE) {
+    mTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE);
+  } else if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+    mTracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
+  } else if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+    mTracker->SyncNotifyProgress(FLAG_DECODE_COMPLETE);
+  } else if (aType == imgINotificationObserver::DISCARD) {
+    mTracker->OnDiscard();
+  } else if (aType == imgINotificationObserver::UNLOCKED_DRAW) {
+    mTracker->OnUnlockedDraw();
+  } else if (aType == imgINotificationObserver::IS_ANIMATED) {
+    mTracker->SyncNotifyProgress(FLAG_IS_ANIMATED);
+  } else if (aType == imgINotificationObserver::HAS_TRANSPARENCY) {
+    mTracker->SyncNotifyProgress(FLAG_HAS_TRANSPARENCY);
+  } else {
+    NS_NOTREACHED("Notification list should be exhaustive");
+  }
+}
+
+void
+MultipartImage::OnLoadComplete(bool aLastPart)
+{
+  Progress progress = FLAG_LOAD_COMPLETE;
+  if (aLastPart) {
+    progress |= FLAG_LAST_PART_COMPLETE;
+  }
+  mTracker->SyncNotifyProgress(progress);
+}
+
+void
+MultipartImage::SetHasImage()
+{
+  mTracker->OnImageAvailable();
+}
+
+void
+MultipartImage::OnStartDecode()
+{
+  mTracker->SyncNotifyProgress(FLAG_DECODE_STARTED);
+}
+
+bool
+MultipartImage::NotificationsDeferred() const
+{
+  return mDeferNotifications;
+}
+
+void
+MultipartImage::SetNotificationsDeferred(bool aDeferNotifications)
+{
+  mDeferNotifications = aDeferNotifications;
+}
+
+} // namespace image
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/image/src/MultipartImage.h
@@ -0,0 +1,90 @@
+/* -*- 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_MULTIPARTIMAGE_H_
+#define MOZILLA_IMAGELIB_MULTIPARTIMAGE_H_
+
+#include "ImageWrapper.h"
+#include "IProgressObserver.h"
+#include "ProgressTracker.h"
+
+namespace mozilla {
+namespace image {
+
+class NextPartObserver;
+
+/**
+ * An Image wrapper that implements support for multipart/x-mixed-replace
+ * images.
+ */
+class MultipartImage
+  : public ImageWrapper
+  , public IProgressObserver
+{
+public:
+  MOZ_DECLARE_REFCOUNTED_TYPENAME(MultipartImage)
+  NS_DECL_ISUPPORTS
+
+  MultipartImage(Image* aImage, ProgressTracker* aTracker);
+
+  void BeginTransitionToPart(Image* aNextPart);
+
+  // Overridden ImageWrapper methods:
+  virtual already_AddRefed<imgIContainer> Unwrap() MOZ_OVERRIDE;
+  virtual already_AddRefed<ProgressTracker> GetProgressTracker() MOZ_OVERRIDE;
+  virtual void SetProgressTracker(ProgressTracker* aTracker) MOZ_OVERRIDE;
+  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 aStatus,
+                                       bool aLastPart) MOZ_OVERRIDE;
+
+  // We don't support locking or track animation consumers for individual parts,
+  // so we override these methods to do nothing.
+  NS_IMETHOD LockImage() MOZ_OVERRIDE { return NS_OK; }
+  NS_IMETHOD UnlockImage() MOZ_OVERRIDE { return NS_OK; }
+  virtual void IncrementAnimationConsumers() MOZ_OVERRIDE { }
+  virtual void DecrementAnimationConsumers() MOZ_OVERRIDE { }
+#ifdef DEBUG
+  virtual uint32_t GetAnimationConsumers() MOZ_OVERRIDE { return 1; }
+#endif
+
+  // Overridden IProgressObserver methods:
+  virtual void Notify(int32_t aType,
+                      const nsIntRect* aRect = nullptr) MOZ_OVERRIDE;
+  virtual void OnLoadComplete(bool aLastPart) MOZ_OVERRIDE;
+  virtual void SetHasImage() MOZ_OVERRIDE;
+  virtual void OnStartDecode() MOZ_OVERRIDE;
+  virtual bool NotificationsDeferred() const MOZ_OVERRIDE;
+  virtual void SetNotificationsDeferred(bool aDeferNotifications) MOZ_OVERRIDE;
+
+  // We don't allow multipart images to block onload, so we override these
+  // methods to do nothing.
+  virtual void BlockOnload() MOZ_OVERRIDE { }
+  virtual void UnblockOnload() MOZ_OVERRIDE { }
+
+protected:
+  virtual ~MultipartImage();
+
+private:
+  friend class NextPartObserver;
+
+  void FinishTransition();
+
+  nsRefPtr<ProgressTracker> mTracker;
+  nsAutoPtr<ProgressTrackerInit> mProgressTrackerInit;
+  nsRefPtr<NextPartObserver> mNextPartObserver;
+  nsRefPtr<Image> mNextPart;
+  bool mDeferNotifications : 1;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // MOZILLA_IMAGELIB_MULTIPARTIMAGE_H_
--- a/image/src/ProgressTracker.cpp
+++ b/image/src/ProgressTracker.cpp
@@ -495,22 +495,17 @@ ProgressTracker::OnUnlockedDraw()
   NOTIFY_IMAGE_OBSERVERS(mObservers,
                          Notify(imgINotificationObserver::UNLOCKED_DRAW));
 }
 
 void
 ProgressTracker::ResetForNewRequest()
 {
   MOZ_ASSERT(NS_IsMainThread());
-
-  // We're starting a new load (and if this is called more than once, this is a
-  // multipart request) so keep only the bits that carry over between loads.
-  mProgress &= FLAG_IS_MULTIPART | FLAG_HAS_ERROR |
-               FLAG_ONLOAD_BLOCKED | FLAG_ONLOAD_UNBLOCKED;
-
+  mProgress = NoProgress;
   CheckProgressConsistency(mProgress);
 }
 
 void
 ProgressTracker::OnDiscard()
 {
   MOZ_ASSERT(NS_IsMainThread());
   NOTIFY_IMAGE_OBSERVERS(mObservers,
--- a/image/src/imgRequest.cpp
+++ b/image/src/imgRequest.cpp
@@ -7,16 +7,17 @@
 #include "imgRequest.h"
 #include "ImageLogging.h"
 
 #include "imgLoader.h"
 #include "imgRequestProxy.h"
 #include "ProgressTracker.h"
 #include "ImageFactory.h"
 #include "Image.h"
+#include "MultipartImage.h"
 #include "RasterImage.h"
 
 #include "nsIChannel.h"
 #include "nsICachingChannel.h"
 #include "nsIThreadRetargetableRequest.h"
 #include "nsIInputStream.h"
 #include "nsIMultiPartChannel.h"
 #include "nsIHttpChannel.h"
@@ -67,17 +68,17 @@ imgRequest::imgRequest(imgLoader* aLoade
  , mInnerWindowId(0)
  , mCORSMode(imgIRequest::CORS_NONE)
  , mReferrerPolicy(mozilla::net::RP_Default)
  , mImageErrorCode(NS_OK)
  , mDecodeRequested(false)
  , mIsMultiPartChannel(false)
  , mGotData(false)
  , mIsInCache(false)
- , mResniffMimeType(false)
+ , mNewPartPending(false)
 { }
 
 imgRequest::~imgRequest()
 {
   if (mLoader) {
     mLoader->RemoveFromUncachedImages(this);
   }
   if (mURI) {
@@ -137,17 +138,17 @@ nsresult imgRequest::Init(nsIURI *aURI,
 
 void imgRequest::ClearLoader() {
   mLoader = nullptr;
 }
 
 already_AddRefed<ProgressTracker>
 imgRequest::GetProgressTracker()
 {
-  if (mImage && mGotData) {
+  if (mImage) {
     NS_ABORT_IF_FALSE(!mProgressTracker,
                       "Should have given mProgressTracker to mImage");
     return mImage->GetProgressTracker();
   } else {
     NS_ABORT_IF_FALSE(mProgressTracker,
                       "Should have mProgressTracker until we create mImage");
     nsRefPtr<ProgressTracker> progressTracker = mProgressTracker;
     MOZ_ASSERT(progressTracker);
@@ -628,62 +629,46 @@ imgRequest::StartDecoding()
 
 /** nsIRequestObserver methods **/
 
 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
 NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
 {
   LOG_SCOPE(GetImgLog(), "imgRequest::OnStartRequest");
 
-  // Figure out if we're multipart
+  mNewPartPending = true;
+
+  // Figure out if we're multipart.
   nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
   nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
   if (mpchan) {
     mIsMultiPartChannel = true;
-    progressTracker->SetIsMultipart();
   } else {
     NS_ABORT_IF_FALSE(!mIsMultiPartChannel, "Something went wrong");
   }
 
   // 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;
-
-    // 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
    */
   if (!mRequest) {
     NS_ASSERTION(mpchan,
                  "We should have an mRequest here unless we're multipart");
     nsCOMPtr<nsIChannel> chan;
     mpchan->GetBaseChannel(getter_AddRefs(chan));
     mRequest = chan;
   }
 
-  // Note: refreshing progressTracker in case OnNewSourceData changed it.
-  progressTracker = GetProgressTracker();
-  progressTracker->ResetForNewRequest();
-
   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
   if (channel)
     channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
 
   /* Get our principal */
   nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
   if (chan) {
     nsCOMPtr<nsIScriptSecurityManager> secMan = nsContentUtils::GetSecurityManager();
@@ -833,26 +818,22 @@ imgRequest::OnDataAvailable(nsIRequest *
                             nsIInputStream *inStr, uint64_t sourceOffset,
                             uint32_t count)
 {
   LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "count", count);
 
   NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!");
 
   nsresult rv;
-
-  if (!mGotData || mResniffMimeType) {
-    LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |First time through... finding mimetype|");
+  mGotData = true;
 
-    mGotData = true;
+  if (mNewPartPending) {
+    LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |New part; finding MIME type|");
 
-    // Store and reset this for the invariant that it's always false after
-    // calls to OnDataAvailable (see bug 907575)
-    bool resniffMimeType = mResniffMimeType;
-    mResniffMimeType = false;
+    mNewPartPending = false;
 
     mimetype_closure closure;
     nsAutoCString newType;
     closure.newType = &newType;
 
     /* look at the first few bytes and see if we can tell what the data is from that
      * since servers tend to lie. :(
      */
@@ -876,75 +857,76 @@ imgRequest::OnDataAvailable(nsIRequest *
         this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
 
         return NS_BINDING_ABORTED;
       }
 
       LOG_MSG(GetImgLog(), "imgRequest::OnDataAvailable", "Got content type from the channel");
     }
 
-    // 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(IMAGE_SVG_XML)) {
-      mContentType = newType;
+    mContentType = newType;
+    SetProperties(chan);
+    bool firstPart = !mImage;
+
+    LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "content type", mContentType.get());
 
-      // 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 (resniffMimeType) {
-        MOZ_ASSERT(mIsMultiPartChannel, "Resniffing a non-multipart image");
+    // XXX If server lied about mimetype and it's SVG, we may need to copy
+    // the data and dispatch back to the main thread, AND tell the channel to
+    // dispatch there in the future.
 
-        // Initialize a new status tracker.
-        nsRefPtr<ProgressTracker> freshTracker = new ProgressTracker();
-        freshTracker->SetIsMultipart();
+    // Create the new image and give it ownership of our ProgressTracker.
+    if (mIsMultiPartChannel) {
+      // Create the ProgressTracker and image for this part.
+      nsRefPtr<ProgressTracker> progressTracker = new ProgressTracker();
+      nsRefPtr<Image> image =
+        ImageFactory::CreateImage(aRequest, progressTracker, mContentType,
+                                  mURI, /* aIsMultipart = */ true,
+                                  static_cast<uint32_t>(mInnerWindowId));
 
-        // Replace the old status tracker with it.
-        nsRefPtr<ProgressTracker> oldProgressTracker = GetProgressTracker();
-        freshTracker->AdoptObservers(oldProgressTracker);
-        mProgressTracker = freshTracker.forget();
+      if (!mImage) {
+        // First part for a multipart channel. Create the MultipartImage wrapper.
+        MOZ_ASSERT(mProgressTracker, "Shouldn't have given away tracker yet");
+        mImage = new MultipartImage(image, mProgressTracker);
+        mProgressTracker = nullptr;
+      } else {
+        // Transition to the new part.
+        static_cast<MultipartImage*>(mImage.get())->BeginTransitionToPart(image);
       }
-
-      SetProperties(chan);
-
-      LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "content type", mContentType.get());
+    } else {
+      MOZ_ASSERT(!mImage, "New part for non-multipart channel?");
+      MOZ_ASSERT(mProgressTracker, "Shouldn't have given away tracker yet");
 
-      // XXX If server lied about mimetype and it's SVG, we may need to copy
-      // the data and dispatch back to the main thread, AND tell the channel to
-      // dispatch there in the future.
+      // Create an image using our progress tracker.
+      mImage =
+        ImageFactory::CreateImage(aRequest, mProgressTracker, mContentType,
+                                  mURI, /* aIsMultipart = */ false,
+                                  static_cast<uint32_t>(mInnerWindowId));
+      mProgressTracker = nullptr;
+    }
 
-      // 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, mProgressTracker, mContentType,
-                                         mURI, mIsMultiPartChannel,
-                                         static_cast<uint32_t>(mInnerWindowId));
-
-      // Release our copy of the status tracker since the image owns it now.
-      mProgressTracker = nullptr;
-
+    if (firstPart) {
       // Notify listeners that we have an image.
       nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
       progressTracker->OnImageAvailable();
+      MOZ_ASSERT(progressTracker->HasImage());
+    }
 
-      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->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;
+    }
 
-      NS_ABORT_IF_FALSE(progressTracker->HasImage(), "Status tracker should have an image!");
-      NS_ABORT_IF_FALSE(mImage, "imgRequest should have an image!");
+    MOZ_ASSERT(!mProgressTracker, "Should've given tracker to image");
+    MOZ_ASSERT(mImage, "Should have image");
 
-      if (mDecodeRequested)
-        mImage->StartDecoding();
+    if (mDecodeRequested) {
+      mImage->StartDecoding();
     }
   }
 
   // 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,
--- a/image/src/imgRequest.h
+++ b/image/src/imgRequest.h
@@ -267,12 +267,12 @@ private:
   // Sometimes consumers want to do things before the image is ready. Let them,
   // and apply the action when the image becomes available.
   bool mDecodeRequested : 1;
 
   bool mIsMultiPartChannel : 1;
   bool mGotData : 1;
   bool mIsInCache : 1;
   bool mBlockingOnload : 1;
-  bool mResniffMimeType : 1;
+  bool mNewPartPending : 1;
 };
 
 #endif
--- a/image/src/moz.build
+++ b/image/src/moz.build
@@ -25,16 +25,17 @@ UNIFIED_SOURCES += [
     'FrozenImage.cpp',
     'Image.cpp',
     'ImageFactory.cpp',
     'ImageMetadata.cpp',
     'ImageOps.cpp',
     'ImageWrapper.cpp',
     'imgFrame.cpp',
     'imgTools.cpp',
+    'MultipartImage.cpp',
     'OrientedImage.cpp',
     'ScriptedNotificationObserver.cpp',
     'ShutdownTracker.cpp',
     'SurfaceCache.cpp',
     'SVGDocumentWrapper.cpp',
     'VectorImage.cpp',
 ]