Bug 1089046 (Part 1) - Remove imgDecoderObserver and related code. r=tn
authorSeth Fowler <seth@mozilla.com>
Fri, 14 Nov 2014 20:06:19 -0800
changeset 215885 b892bd18e0d923cafb676a2dc1dc33e992992a04
parent 215884 d1903414198f3255cfbaa579981384afb5496529
child 215886 02d3440cd34b569b21842a27350d45612297fe3d
push id27829
push usergszorc@mozilla.com
push dateSat, 15 Nov 2014 22:34:49 +0000
treeherderautoland@19f75e1211e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1089046
milestone36.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 1089046 (Part 1) - Remove imgDecoderObserver and related code. r=tn
image/decoders/nsICODecoder.cpp
image/src/Decoder.cpp
image/src/Decoder.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/VectorImage.cpp
image/src/imgDecoderObserver.h
image/src/imgRequest.cpp
image/src/imgRequestProxy.cpp
image/src/imgRequestProxy.h
image/src/imgStatusTracker.cpp
image/src/imgStatusTracker.h
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -78,16 +78,17 @@ nsICODecoder::FinishInternal()
 {
   // We shouldn't be called in error cases
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call FinishInternal after error!");
 
   // Finish the internally used decoder as well
   if (mContainedDecoder) {
     mContainedDecoder->FinishSharedDecoder();
     mDecodeDone = mContainedDecoder->GetDecodeDone();
+    mDiff = mContainedDecoder->GetDiff();
   }
 }
 
 // Returns a buffer filled with the bitmap file header in little endian:
 // Signature 2 bytes 'BM'
 // FileSize      4 bytes File size in bytes
 // reserved      4 bytes unused (=0)
 // DataOffset    4 bytes File offset to Raster Data
@@ -334,17 +335,16 @@ nsICODecoder::WriteInternal(const char* 
     mPos += toCopy;
     aCount -= toCopy;
     aBuffer += toCopy;
 
     mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
                      PNGSIGNATURESIZE);
     if (mIsPNG) {
       mContainedDecoder = new nsPNGDecoder(mImage);
-      mContainedDecoder->SetObserver(mObserver);
       mContainedDecoder->SetSizeDecode(IsSizeDecode());
       mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
                                            mColormap, mColormapSize,
                                            mCurrentFrame);
       if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE, aStrategy)) {
         return;
       }
     }
@@ -413,17 +413,16 @@ nsICODecoder::WriteInternal(const char* 
     mBPP = ExtractBPPFromBitmap(reinterpret_cast<int8_t*>(mBIHraw));
 
     // Init the bitmap decoder which will do most of the work for us
     // It will do everything except the AND mask which isn't present in bitmaps
     // bmpDecoder is for local scope ease, it will be freed by mContainedDecoder
     nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
     mContainedDecoder = bmpDecoder;
     bmpDecoder->SetUseAlphaData(true);
-    mContainedDecoder->SetObserver(mObserver);
     mContainedDecoder->SetSizeDecode(IsSizeDecode());
     mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
                                          mColormap, mColormapSize,
                                          mCurrentFrame);
 
     // The ICO format when containing a BMP does not include the 14 byte
     // bitmap file header. To use the code of the BMP decoder we need to
     // generate this header ourselves and feed it to the BMP decoder.
@@ -583,16 +582,17 @@ nsICODecoder::WriteInternal(const char* 
   }
 }
 
 bool
 nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount,
                                       DecodeStrategy aStrategy)
 {
   mContainedDecoder->Write(aBuffer, aCount, aStrategy);
+  mDiff = mContainedDecoder->GetDiff();
   if (mContainedDecoder->HasDataError()) {
     mDataError = mContainedDecoder->HasDataError();
   }
   if (mContainedDecoder->HasDecoderError()) {
     PostDecoderError(mContainedDecoder->GetDecoderError());
   }
   return !HasError();
 }
@@ -627,16 +627,17 @@ nsICODecoder::NeedsNewFrame() const
 }
 
 nsresult
 nsICODecoder::AllocateFrame()
 {
   if (mContainedDecoder) {
     nsresult rv = mContainedDecoder->AllocateFrame();
     mCurrentFrame = mContainedDecoder->GetCurrentFrame();
+    mDiff = mContainedDecoder->GetDiff();
     return rv;
   }
 
   return Decoder::AllocateFrame();
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -25,55 +25,53 @@ Decoder::Decoder(RasterImage &aImage)
   , mDataError(false)
   , mFrameCount(0)
   , mFailCode(NS_OK)
   , mNeedsNewFrame(false)
   , mInitialized(false)
   , mSizeDecode(false)
   , mInFrame(false)
   , mIsAnimated(false)
-{
-}
+{ }
 
 Decoder::~Decoder()
 {
   mInitialized = false;
 }
 
 /*
  * Common implementation of the decoder interface.
  */
 
 void
 Decoder::Init()
 {
   // No re-initializing
   NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
-  NS_ABORT_IF_FALSE(mObserver, "Need an observer!");
 
   // Fire OnStartDecode at init time to support bug 512435.
-  if (!IsSizeDecode())
-      mObserver->OnStartDecode();
+  if (!IsSizeDecode()) {
+      mDiff.diffState |= FLAG_DECODE_STARTED | FLAG_ONLOAD_BLOCKED;
+  }
 
   // Implementation-specific initialization
   InitInternal();
 
   mInitialized = true;
 }
 
 // Initializes a decoder whose image and observer is already being used by a
 // parent decoder
 void
 Decoder::InitSharedDecoder(uint8_t* imageData, uint32_t imageDataLength,
                            uint32_t* colormap, uint32_t colormapSize,
                            imgFrame* currentFrame)
 {
   // No re-initializing
   NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
-  NS_ABORT_IF_FALSE(mObserver, "Need an observer!");
 
   mImageData = imageData;
   mImageDataLength = imageDataLength;
   mColormap = colormap;
   mColormapSize = colormapSize;
   mCurrentFrame = currentFrame;
   // We have all the frame data, so we've started the frame.
   if (!IsSizeDecode()) {
@@ -172,19 +170,18 @@ Decoder::Finish(RasterImage::eShutdownIn
     // If we're usable, do exactly what we should have when the decoder
     // completed.
     if (usable) {
       if (mInFrame) {
         PostFrameStop();
       }
       PostDecodeDone();
     } else {
-      if (mObserver) {
-        mObserver->OnStopDecode(NS_ERROR_FAILURE);
-      }
+      mDiff.diffState |= FLAG_DECODE_STOPPED | FLAG_ONLOAD_UNBLOCKED |
+                         FLAG_HAS_ERROR;
     }
   }
 
   // Set image metadata before calling DecodingComplete, because DecodingComplete calls Optimize().
   mImageMetadata.SetOnImage(&mImage);
 
   if (mDecodeDone) {
     mImage.DecodingComplete();
@@ -275,31 +272,36 @@ Decoder::PostSize(int32_t aWidth,
 {
   // Validate
   NS_ABORT_IF_FALSE(aWidth >= 0, "Width can't be negative!");
   NS_ABORT_IF_FALSE(aHeight >= 0, "Height can't be negative!");
 
   // Tell the image
   mImageMetadata.SetSize(aWidth, aHeight, aOrientation);
 
-  // Notify the observer
-  if (mObserver)
-    mObserver->OnStartContainer();
+  // Record this notification.
+  mDiff.diffState |= FLAG_HAS_SIZE;
 }
 
 void
 Decoder::PostFrameStart()
 {
   // We shouldn't already be mid-frame
   NS_ABORT_IF_FALSE(!mInFrame, "Starting new frame but not done with old one!");
 
   // Update our state to reflect the new frame
   mFrameCount++;
   mInFrame = true;
 
+  // If we just became animated, record that fact.
+  if (mFrameCount > 1) {
+    mIsAnimated = true;
+    mDiff.diffState |= FLAG_IS_ANIMATED;
+  }
+
   // Decoder implementations should only call this method if they successfully
   // appended the frame to the image. So mFrameCount should always match that
   // reported by the Image.
   NS_ABORT_IF_FALSE(mFrameCount == mImage.GetNumFrames(),
                     "Decoder frame count doesn't match image's!");
 }
 
 void
@@ -319,24 +321,17 @@ Decoder::PostFrameStop(FrameBlender::Fra
     mCurrentFrame->SetHasNoAlpha();
   }
 
   mCurrentFrame->SetFrameDisposalMethod(aDisposalMethod);
   mCurrentFrame->SetRawTimeout(aTimeout);
   mCurrentFrame->SetBlendMethod(aBlendMethod);
   mCurrentFrame->ImageUpdated(mCurrentFrame->GetRect());
 
-  // Fire notifications
-  if (mObserver) {
-    mObserver->OnStopFrame();
-    if (mFrameCount > 1 && !mIsAnimated) {
-      mIsAnimated = true;
-      mObserver->OnImageIsAnimated();
-    }
-  }
+  mDiff.diffState |= FLAG_FRAME_STOPPED | FLAG_ONLOAD_UNBLOCKED;
 }
 
 void
 Decoder::PostInvalidation(nsIntRect& aRect)
 {
   // We should be mid-frame
   NS_ABORT_IF_FALSE(mInFrame, "Can't invalidate when not mid-frame!");
   NS_ABORT_IF_FALSE(mCurrentFrame, "Can't invalidate when not mid-frame!");
@@ -352,19 +347,17 @@ Decoder::PostDecodeDone(int32_t aLoopCou
   NS_ABORT_IF_FALSE(!IsSizeDecode(), "Can't be done with decoding with size decode!");
   NS_ABORT_IF_FALSE(!mInFrame, "Can't be done decoding if we're mid-frame!");
   NS_ABORT_IF_FALSE(!mDecodeDone, "Decode already done!");
   mDecodeDone = true;
 
   mImageMetadata.SetLoopCount(aLoopCount);
   mImageMetadata.SetIsNonPremultiplied(GetDecodeFlags() & DECODER_NO_PREMULTIPLY_ALPHA);
 
-  if (mObserver) {
-    mObserver->OnStopDecode(NS_OK);
-  }
+  mDiff.diffState |= FLAG_DECODE_STOPPED;
 }
 
 void
 Decoder::PostDataError()
 {
   mDataError = true;
 }
 
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -2,17 +2,16 @@
 /* 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_DECODER_H_
 #define MOZILLA_IMAGELIB_DECODER_H_
 
 #include "RasterImage.h"
-#include "imgDecoderObserver.h"
 #include "mozilla/RefPtr.h"
 #include "DecodeStrategy.h"
 #include "ImageMetadata.h"
 #include "Orientation.h"
 #include "mozilla/Telemetry.h"
 
 namespace mozilla {
 
@@ -93,23 +92,19 @@ public:
   // must be enabled by SetSizeDecode() _before_calling Init().
   bool IsSizeDecode() { return mSizeDecode; }
   void SetSizeDecode(bool aSizeDecode)
   {
     NS_ABORT_IF_FALSE(!mInitialized, "Can't set size decode after Init()!");
     mSizeDecode = aSizeDecode;
   }
 
-  void SetObserver(imgDecoderObserver* aObserver)
-  {
-    MOZ_ASSERT(aObserver);
-    mObserver = aObserver;
-  }
+  size_t BytesDecoded() const { return mBytesDecoded; }
 
-  size_t BytesDecoded() const { return mBytesDecoded; }
+  ImageStatusDiff GetDiff() const { return mDiff; }
 
   // The number of frames we have, including anything in-progress. Thus, this
   // is only 0 if we haven't begun any frames.
   uint32_t GetFrameCount() { return mFrameCount; }
 
   // The number of complete frames we have (ie, not including anything in-progress).
   uint32_t GetCompleteFrameCount() { return mInFrame ? mFrameCount - 1 : mFrameCount; }
 
@@ -226,18 +221,18 @@ protected:
   void PostDecoderError(nsresult aFailCode);
 
   /*
    * Member variables.
    *
    */
   RasterImage &mImage;
   nsRefPtr<imgFrame> mCurrentFrame;
-  RefPtr<imgDecoderObserver> mObserver;
   ImageMetadata mImageMetadata;
+  ImageStatusDiff mDiff;
 
   uint8_t* mImageData;       // Pointer to image data in either Cairo or 8bit format
   uint32_t mImageDataLength;
   uint32_t* mColormap;       // Current colormap to be used in Cairo format
   uint32_t mColormapSize;
 
   uint32_t mDecodeFlags;
   size_t mBytesDecoded;
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -6,17 +6,16 @@
 // Must #include ImageLogging.h before any IPDL-generated files or other files that #include prlog.h
 #include "ImageLogging.h"
 
 #include "RasterImage.h"
 
 #include "base/histogram.h"
 #include "gfxPlatform.h"
 #include "nsComponentManagerUtils.h"
-#include "imgDecoderObserver.h"
 #include "nsError.h"
 #include "Decoder.h"
 #include "nsAutoPtr.h"
 #include "prenv.h"
 #include "prsystem.h"
 #include "ImageContainer.h"
 #include "ImageRegion.h"
 #include "Layers.h"
@@ -1038,20 +1037,16 @@ RasterImage::EnsureAnimExists()
     //
     // Note that this is inefficient, since we could get rid of the source
     // data too. However, doing this is actually hard, because we're probably
     // calling ensureAnimExists mid-decode, and thus we're decoding out of
     // the source buffer. Since we're going to fix this anyway later, and
     // since we didn't kill the source data in the old world either, locking
     // is acceptable for the moment.
     LockImage();
-
-    // Notify our observers that we are starting animation.
-    nsRefPtr<imgStatusTracker> statusTracker = CurrentStatusTracker();
-    statusTracker->RecordImageIsAnimated();
   }
 }
 
 nsresult
 RasterImage::InternalAddFrameHelper(uint32_t framenum, imgFrame *aFrame,
                                     uint8_t **imageData, uint32_t *imageLength,
                                     uint32_t **paletteData, uint32_t *paletteLength,
                                     imgFrame** aRetFrame)
@@ -1691,24 +1686,23 @@ nsresult
 RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus, bool aLastPart)
 {
   nsresult finalStatus = DoImageDataComplete();
 
   // Give precedence to Necko failure codes.
   if (NS_FAILED(aStatus))
     finalStatus = aStatus;
 
+  ImageStatusDiff diff =
+    ImageStatusDiff::ForOnStopRequest(aLastPart, mError, finalStatus);
+
   // We just recorded OnStopRequest; we need to inform our listeners.
   {
     ReentrantMonitorAutoEnter lock(mDecodingMonitor);
-
-    nsRefPtr<imgStatusTracker> statusTracker = CurrentStatusTracker();
-    statusTracker->GetDecoderObserver()->OnStopRequest(aLastPart, finalStatus);
-
-    FinishedSomeDecoding();
+    FinishedSomeDecoding(eShutdownIntent_Done, nullptr, diff);
   }
 
   return finalStatus;
 }
 
 nsresult
 RasterImage::OnImageDataAvailable(nsIRequest*,
                                   nsISupports*,
@@ -1988,19 +1982,16 @@ RasterImage::InitDecoder(bool aDoSizeDec
     nsRefPtr<imgFrame> curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
     curframe->LockImageData();
   }
 
   // Initialize the decoder
   if (!mDecodeRequest) {
     mDecodeRequest = new DecodeRequest(this);
   }
-  MOZ_ASSERT(mDecodeRequest->mStatusTracker);
-  MOZ_ASSERT(mDecodeRequest->mStatusTracker->GetDecoderObserver());
-  mDecoder->SetObserver(mDecodeRequest->mStatusTracker->GetDecoderObserver());
   mDecoder->SetSizeDecode(aDoSizeDecode);
   mDecoder->SetDecodeFlags(mFrameDecodeFlags);
   if (!aDoSizeDecode) {
     // We already have the size; tell the decoder so it can preallocate a
     // frame.  By default, we create an ARGB frame with no offset. If decoders
     // need a different type, they need to ask for it themselves.
     mDecoder->NeedNewFrame(0, 0, 0, mSize.width, mSize.height,
                            SurfaceFormat::B8G8R8A8);
@@ -2848,31 +2839,27 @@ RasterImage::DoError()
     return;
 
   // We can't safely handle errors off-main-thread, so dispatch a worker to do it.
   if (!NS_IsMainThread()) {
     HandleErrorWorker::DispatchIfNeeded(this);
     return;
   }
 
-  // Calling FinishedSomeDecoding and CurrentStatusTracker requires us to be in
-  // the decoding monitor.
+  // Calling FinishedSomeDecoding requires us to be in the decoding monitor.
   ReentrantMonitorAutoEnter lock(mDecodingMonitor);
 
   // If we're mid-decode, shut down the decoder.
   if (mDecoder) {
     FinishedSomeDecoding(eShutdownIntent_Error);
   }
 
   // Put the container in an error state.
   mError = true;
 
-  nsRefPtr<imgStatusTracker> statusTracker = CurrentStatusTracker();
-  statusTracker->GetDecoderObserver()->OnError();
-
   // Log our error
   LOG_CONTAINER_ERROR;
 }
 
 /* static */ void
 RasterImage::HandleErrorWorker::DispatchIfNeeded(RasterImage* aImage)
 {
   if (!aImage->mPendingError) {
@@ -2970,17 +2957,18 @@ RasterImage::RequestDecodeIfNeeded(nsres
   }
 
   // We don't need a full decode right now, so just return the existing status.
   return aStatus;
 }
 
 nsresult
 RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_Done */,
-                                  DecodeRequest* aRequest /* = nullptr */)
+                                  DecodeRequest* aRequest /* = nullptr */,
+                                  const ImageStatusDiff& aDiff /* = ImageStatusDiff::NoChange() */)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mDecodingMonitor.AssertCurrentThreadIn();
 
   nsRefPtr<DecodeRequest> request;
   if (aRequest) {
     request = aRequest;
@@ -2991,19 +2979,21 @@ RasterImage::FinishedSomeDecoding(eShutd
   // Ensure that, if the decoder is the last reference to the image, we don't
   // destroy it by destroying the decoder.
   nsRefPtr<RasterImage> image(this);
 
   bool done = false;
   bool wasSize = false;
   nsIntRect invalidRect;
   nsresult rv = NS_OK;
+  ImageStatusDiff diff = aDiff;
 
   if (image->mDecoder) {
     invalidRect = image->mDecoder->TakeInvalidRect();
+    diff.Combine(image->mDecoder->GetDiff());
 
     if (request && request->mChunkCount && !image->mDecoder->IsSizeDecode()) {
       Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, request->mChunkCount);
     }
 
     if (!image->mHasSize && image->mDecoder->HasSize()) {
       image->mDecoder->SetSizeOnImage();
     }
@@ -3034,33 +3024,34 @@ RasterImage::FinishedSomeDecoding(eShutd
       }
 
       // We need to shut down the decoder first, in order to ensure all
       // decoding routines have been finished.
       rv = image->ShutdownDecoder(aIntent);
       if (NS_FAILED(rv)) {
         image->DoError();
       }
+
+      // If there were any final state changes, grab them.
+      diff.Combine(decoder->GetDiff());
     }
   }
 
   if (GetCurrentFrameIndex() > 0) {
     // Don't send invalidations for animated frames after the first; let
     // RequestRefresh take care of that.
     invalidRect = nsIntRect();
   }
   if (mHasBeenDecoded && !invalidRect.IsEmpty()) {
     // Don't send partial invalidations if we've been decoded before.
     invalidRect = mDecoded ? GetFirstFrameRect()
                            : nsIntRect();
   }
 
-  ImageStatusDiff diff =
-    request ? image->mStatusTracker->Difference(request->mStatusTracker)
-            : image->mStatusTracker->DecodeStateAsDifference();
+  diff = image->mStatusTracker->Difference(diff);
   image->mStatusTracker->ApplyDifference(diff);
 
   if (mNotifying) {
     // Accumulate the status changes. We don't permit recursive notifications
     // because they cause subtle concurrency bugs, so we'll delay sending out
     // the notifications until we pop back to the lowest invocation of
     // FinishedSomeDecoding on the stack.
     NS_WARNING("Recursively notifying in RasterImage::FinishedSomeDecoding!");
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -304,53 +304,34 @@ public:
     eShutdownIntent_NotNeeded   = 1,
     eShutdownIntent_Error       = 2,
     eShutdownIntent_AllCount    = 3
   };
 
   // Decode strategy
 
 private:
-  already_AddRefed<imgStatusTracker> CurrentStatusTracker()
-  {
-    mDecodingMonitor.AssertCurrentThreadIn();
-    nsRefPtr<imgStatusTracker> statusTracker;
-    statusTracker = mDecodeRequest ? mDecodeRequest->mStatusTracker
-                                   : mStatusTracker;
-    MOZ_ASSERT(statusTracker);
-    return statusTracker.forget();
-  }
-
   nsresult OnImageDataCompleteCore(nsIRequest* aRequest, nsISupports*, nsresult aStatus);
 
   /**
    * Each RasterImage has a pointer to one or zero heap-allocated
    * DecodeRequests.
    */
   struct DecodeRequest
   {
     explicit DecodeRequest(RasterImage* aImage)
       : mImage(aImage)
       , mBytesToDecode(0)
       , mRequestStatus(REQUEST_INACTIVE)
       , mChunkCount(0)
       , mAllocatedNewFrame(false)
-    {
-      MOZ_ASSERT(aImage, "aImage cannot be null");
-      MOZ_ASSERT(aImage->mStatusTracker,
-                 "aImage should have an imgStatusTracker");
-      mStatusTracker = aImage->mStatusTracker->CloneForRecording();
-    }
+    { }
 
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodeRequest)
 
-    // The status tracker that is associated with a given decode request, to
-    // ensure their lifetimes are linked.
-    nsRefPtr<imgStatusTracker> mStatusTracker;
-
     RasterImage* mImage;
 
     size_t mBytesToDecode;
 
     enum DecodeRequestStatus
     {
       REQUEST_INACTIVE,
       REQUEST_PENDING,
@@ -526,17 +507,18 @@ private:
     explicit FrameNeededWorker(RasterImage* image);
 
   private: /* members */
 
     nsRefPtr<RasterImage> mImage;
   };
 
   nsresult FinishedSomeDecoding(eShutdownIntent intent = eShutdownIntent_Done,
-                                DecodeRequest* request = nullptr);
+                                DecodeRequest* request = nullptr,
+                                const ImageStatusDiff& aDiff = ImageStatusDiff::NoChange());
 
   void DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
                                     gfxContext* aContext,
                                     const nsIntSize& aSize,
                                     const ImageRegion& aRegion,
                                     GraphicsFilter aFilter,
                                     uint32_t aFlags);
 
--- a/image/src/VectorImage.cpp
+++ b/image/src/VectorImage.cpp
@@ -5,17 +5,16 @@
 
 #include "VectorImage.h"
 
 #include "gfx2DGlue.h"
 #include "gfxContext.h"
 #include "gfxDrawable.h"
 #include "gfxPlatform.h"
 #include "gfxUtils.h"
-#include "imgDecoderObserver.h"
 #include "imgFrame.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/SVGSVGElement.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/RefPtr.h"
 #include "nsIDOMEvent.h"
 #include "nsIPresShell.h"
@@ -438,21 +437,18 @@ VectorImage::OnImageDataComplete(nsIRequ
   nsresult finalStatus = OnStopRequest(aRequest, aContext, aStatus);
 
   // Give precedence to Necko failure codes.
   if (NS_FAILED(aStatus))
     finalStatus = aStatus;
 
   // Actually fire OnStopRequest.
   if (mStatusTracker) {
-    // XXX(seth): Is this seriously the least insane way to do this?
-    nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording();
-    imgDecoderObserver* observer = clone->GetDecoderObserver();
-    observer->OnStopRequest(aLastPart, finalStatus);
-    ImageStatusDiff diff = mStatusTracker->Difference(clone);
+    ImageStatusDiff diff =
+      ImageStatusDiff::ForOnStopRequest(aLastPart, mError, finalStatus);
     mStatusTracker->ApplyDifference(diff);
     mStatusTracker->SyncNotifyDifference(diff);
   }
   return finalStatus;
 }
 
 nsresult
 VectorImage::OnImageDataAvailable(nsIRequest* aRequest,
@@ -1032,20 +1028,18 @@ VectorImage::OnStartRequest(nsIRequest* 
     mError = true;
     return rv;
   }
 
   // Sending StartDecode will block page load until the document's ready.  (We
   // unblock it by sending StopDecode in OnSVGDocumentLoaded or
   // OnSVGDocumentError.)
   if (mStatusTracker) {
-    nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording();
-    imgDecoderObserver* observer = clone->GetDecoderObserver();
-    observer->OnStartDecode();
-    ImageStatusDiff diff = mStatusTracker->Difference(clone);
+    ImageStatusDiff diff;
+    diff.diffState |= FLAG_DECODE_STARTED | FLAG_ONLOAD_BLOCKED;
     mStatusTracker->ApplyDifference(diff);
     mStatusTracker->SyncNotifyDifference(diff);
   }
 
   // Create a listener to wait until the SVG document is fully loaded, which
   // will signal that this image is ready to render. Certain error conditions
   // will prevent us from ever getting this notification, so we also create a
   // listener that waits for parsing to complete and cancels the
@@ -1137,22 +1131,20 @@ VectorImage::OnSVGDocumentError()
   CancelAllListeners();
 
   // XXXdholbert Need to do something more for the parsing failed case -- right
   // now, this just makes us draw the "object" icon, rather than the (jagged)
   // "broken image" icon.  See bug 594505.
   mError = true;
 
   if (mStatusTracker) {
-    nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording();
-    imgDecoderObserver* observer = clone->GetDecoderObserver();
-
     // Unblock page load.
-    observer->OnStopDecode(NS_ERROR_FAILURE);
-    ImageStatusDiff diff = mStatusTracker->Difference(clone);
+    ImageStatusDiff diff;
+    diff.diffState |= FLAG_DECODE_STOPPED | FLAG_ONLOAD_UNBLOCKED |
+                      FLAG_HAS_ERROR;
     mStatusTracker->ApplyDifference(diff);
     mStatusTracker->SyncNotifyDifference(diff);
   }
 }
 
 //------------------------------------------------------------------------------
 // nsIStreamListener method
 
deleted file mode 100644
--- a/image/src/imgDecoderObserver.h
+++ /dev/null
@@ -1,118 +0,0 @@
-/** -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef MOZILLA_IMAGELIB_IMGDECODEROBSERVER_H_
-#define MOZILLA_IMAGELIB_IMGDECODEROBSERVER_H_
-
-#include "nsRect.h"
-#include "mozilla/WeakPtr.h"
-
-struct nsIntRect;
-
-/**
- * imgDecoderObserver interface
- *
- * This interface is used to observe Decoder objects.
- *
- * We make the distinction here between "load" and "decode" notifications. Load
- * notifications are fired as the image is loaded from the network or
- * filesystem. Decode notifications are fired as the image is decoded. If an
- * image is decoded on load and not visibly discarded, decode notifications are
- * nested logically inside load notifications as one might expect. However, with
- * decode-on-draw, the set of decode notifications can come completely _after_
- * the load notifications, and can come multiple times if the image is
- * discardable. Moreover, they can be interleaved in various ways. In general,
- * any presumed ordering between load and decode notifications should not be
- * relied upon.
- *
- * Decode notifications may or may not be synchronous, depending on the
- * situation. If imgIDecoder::FLAG_SYNC_DECODE is passed to a function that
- * triggers a decode, all notifications that can be generated from the currently
- * loaded data fire before the call returns. If FLAG_SYNC_DECODE is not passed,
- * all, some, or none of the notifications may fire before the call returns.
- */
-class imgDecoderObserver
-{
-public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(imgDecoderObserver);
-
-  /**
-   * Load notification.
-   *
-   * called at the same time that nsIRequestObserver::onStartRequest would be
-   * (used only for observers of imgIRequest objects, which are nsIRequests,
-   * not imgIDecoder objects)
-   */
-  virtual void OnStartRequest() = 0;
-
-  /**
-   * Decode notification.
-   *
-   * Called as soon as the image begins getting decoded. This does not include
-   * "header-only" decodes used by decode-on-draw to parse the width/height
-   * out of the image. Thus, it is a decode notification only.
-   */
-  virtual void OnStartDecode() = 0;
-
-  /**
-   * Load notification.
-   *
-   * Called once enough data has been loaded from the network that we were able
-   * to parse the width/height from the image. By the time this callback is been
-   * called, the size has been set on the container and STATUS_SIZE_AVAILABLE
-   * has been set on the associated imgRequest.
-   */
-  virtual void OnStartContainer() = 0;
-
-  /**
-   * Decode notification.
-   *
-   * called when a frame is finished decoding.
-   */
-  virtual void OnStopFrame() = 0;
-
-  /**
-   * Notification for when an image is known to be animated. This should be
-   * fired at the earliest possible time.
-   */
-  virtual void OnImageIsAnimated() = 0;
-
-  /**
-   * Decode notification.
-   *
-   * Called when all decoding has terminated.
-   */
-  virtual void OnStopDecode(nsresult status) = 0;
-
-  /**
-   * Load notification.
-   *
-   * called at the same time that nsIRequestObserver::onStopRequest would be
-   * (used only for observers of imgIRequest objects, which are nsIRequests,
-   * not imgIDecoder objects)
-   */
-  virtual void OnStopRequest(bool aIsLastPart, nsresult aStatus) = 0;
-
-  /**
-   * Called when we are asked to Draw an image that is not locked.
-   */
-  virtual void OnUnlockedDraw() = 0;
-
-  /**
-   * Called when an image is realized to be in error state.
-   */
-  virtual void OnError() = 0;
-
-protected:
-  virtual ~imgDecoderObserver() = 0;
-};
-
-// We must define a destructor because derived classes call our destructor from
-// theirs.  Pure virtual destructors only requires that child classes implement
-// a virtual destructor, not that we can't have one too!
-inline imgDecoderObserver::~imgDecoderObserver()
-{}
-
-#endif // MOZILLA_IMAGELIB_IMGDECODEROBSERVER_H
--- a/image/src/imgRequest.cpp
+++ b/image/src/imgRequest.cpp
@@ -288,17 +288,17 @@ void imgRequest::Cancel(nsresult aStatus
   /* The Cancel() method here should only be called by this class. */
 
   LOG_SCOPE(GetImgLog(), "imgRequest::Cancel");
 
   nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker();
 
   statusTracker->MaybeUnblockOnload();
 
-  statusTracker->RecordCancel();
+  statusTracker->RecordError();
 
   if (NS_IsMainThread()) {
     ContinueCancel(aStatus);
   } else {
     NS_DispatchToMainThread(new imgRequestMainThreadCancel(this, aStatus));
   }
 }
 
@@ -727,16 +727,17 @@ NS_IMETHODIMP imgRequest::OnStartRequest
 
   return NS_OK;
 }
 
 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
 NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
 {
   LOG_FUNC(GetImgLog(), "imgRequest::OnStopRequest");
+  MOZ_ASSERT(NS_IsMainThread(), "Can't send notifications off-main-thread");
 
   // XXXldb What if this is a non-last part of a multipart request?
   // xxx before we release our reference to mRequest, lets
   // save the last status that we saw so that the
   // imgRequestProxy will have access to it.
   if (mRequest) {
     mRequest = nullptr;  // we no longer need the request
   }
@@ -790,18 +791,22 @@ NS_IMETHODIMP imgRequest::OnStopRequest(
     // if the error isn't "just" a partial transfer
     // stops animations, removes from cache
     this->Cancel(status);
   }
 
   if (!mImage) {
     // We have to fire imgStatusTracker::OnStopRequest ourselves because there's
     // no image capable of doing so.
+    ImageStatusDiff diff =
+      ImageStatusDiff::ForOnStopRequest(lastPart, /* aError = */ false, status);
+
     nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker();
-    statusTracker->OnStopRequest(lastPart, status);
+    statusTracker->ApplyDifference(diff);
+    statusTracker->SyncNotifyDifference(diff);
   }
 
   mTimedChannel = nullptr;
   return NS_OK;
 }
 
 struct mimetype_closure
 {
--- a/image/src/imgRequestProxy.cpp
+++ b/image/src/imgRequestProxy.cpp
@@ -716,18 +716,16 @@ NS_IMETHODIMP imgRequestProxy::GetHasTra
     *hasData = GetOwner()->HasTransferredData();
   } else {
     // The safe thing to do is to claim we have data
     *hasData = true;
   }
   return NS_OK;
 }
 
-/** imgDecoderObserver methods **/
-
 void imgRequestProxy::OnStartDecode()
 {
   // This notification is deliberately not propagated since there are no
   // listeners who care about it.
   if (GetOwner()) {
     // In the case of streaming jpegs, it is possible to get multiple
     // OnStartDecodes which indicates the beginning of a new decode.  The cache
     // entry's size therefore needs to be reset to 0 here.  If we do not do
--- a/image/src/imgRequestProxy.h
+++ b/image/src/imgRequestProxy.h
@@ -140,17 +140,16 @@ protected:
       nsRefPtr<imgRequestProxy> mOwner;
       nsresult mStatus;
   };
 
   // The following notification functions are protected to ensure that (friend
   // class) imgStatusTracker is the only class allowed to send us
   // notifications.
 
-  /* non-virtual imgDecoderObserver methods */
   void OnStartDecode     ();
   void OnStartContainer  ();
   void OnFrameUpdate     (const nsIntRect * aRect);
   void OnStopFrame       ();
   void OnStopDecode      ();
   void OnDiscard         ();
   void OnUnlockedDraw    ();
   void OnImageIsAnimated ();
--- a/image/src/imgStatusTracker.cpp
+++ b/image/src/imgStatusTracker.cpp
@@ -4,157 +4,26 @@
  * 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 "ImageLogging.h"
 #include "imgStatusTracker.h"
 
 #include "imgIContainer.h"
 #include "imgRequestProxy.h"
-#include "imgDecoderObserver.h"
 #include "Image.h"
 #include "nsNetUtil.h"
 #include "nsIObserverService.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Services.h"
 
 using namespace mozilla::image;
 using mozilla::WeakPtr;
 
-class imgStatusTrackerObserver : public imgDecoderObserver
-{
-public:
-  explicit imgStatusTrackerObserver(imgStatusTracker* aTracker)
-  : mTracker(aTracker)
-  {
-    MOZ_ASSERT(aTracker);
-  }
-
-  void SetTracker(imgStatusTracker* aTracker)
-  {
-    MOZ_ASSERT(aTracker);
-    mTracker = aTracker;
-  }
-
-  /** imgDecoderObserver methods **/
-
-  virtual void OnStartDecode() MOZ_OVERRIDE
-  {
-    LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStartDecode");
-    nsRefPtr<imgStatusTracker> tracker = mTracker.get();
-    if (!tracker) { return; }
-    tracker->RecordStartDecode();
-    if (!tracker->IsMultipart()) {
-      tracker->RecordBlockOnload();
-    }
-  }
-
-  virtual void OnStartRequest() MOZ_OVERRIDE
-  {
-    NS_NOTREACHED("imgStatusTrackerObserver(imgDecoderObserver)::OnStartRequest");
-  }
-
-  virtual void OnStartContainer() MOZ_OVERRIDE
-  {
-    LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStartContainer");
-    nsRefPtr<imgStatusTracker> tracker = mTracker.get();
-    if (!tracker) { return; }
-    nsRefPtr<Image> image = tracker->GetImage();;
-    tracker->RecordStartContainer(image);
-  }
-
-  virtual void OnStopFrame() MOZ_OVERRIDE
-  {
-    LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStopFrame");
-    nsRefPtr<imgStatusTracker> tracker = mTracker.get();
-    if (!tracker) { return; }
-    tracker->RecordStopFrame();
-    tracker->RecordUnblockOnload();
-  }
-
-  virtual void OnStopDecode(nsresult aStatus) MOZ_OVERRIDE
-  {
-    LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStopDecode");
-    nsRefPtr<imgStatusTracker> tracker = mTracker.get();
-    if (!tracker) { return; }
-    tracker->RecordStopDecode(aStatus);
-
-    // This is really hacky. We need to handle the case where we start decoding,
-    // block onload, but then hit an error before we get to our first frame.
-    tracker->RecordUnblockOnload();
-  }
-
-  virtual void OnStopRequest(bool aLastPart, nsresult aStatus) MOZ_OVERRIDE
-  {
-    LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStopRequest");
-    nsRefPtr<imgStatusTracker> tracker = mTracker.get();
-    if (!tracker) { return; }
-    tracker->RecordStopRequest(aLastPart, aStatus);
-  }
-
-  virtual void OnUnlockedDraw() MOZ_OVERRIDE
-  {
-    LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnUnlockedDraw");
-    nsRefPtr<imgStatusTracker> tracker = mTracker.get();
-    if (!tracker) { return; }
-    NS_ABORT_IF_FALSE(tracker->HasImage(),
-                      "OnUnlockedDraw callback before we've created our image");
-    tracker->RecordUnlockedDraw();
-  }
-
-  virtual void OnImageIsAnimated() MOZ_OVERRIDE
-  {
-    LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnImageIsAnimated");
-    nsRefPtr<imgStatusTracker> tracker = mTracker.get();
-    if (!tracker) { return; }
-    tracker->RecordImageIsAnimated();
-  }
-
-  virtual void OnError() MOZ_OVERRIDE
-  {
-    LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnError");
-    nsRefPtr<imgStatusTracker> tracker = mTracker.get();
-    if (!tracker) { return; }
-    tracker->RecordError();
-  }
-
-protected:
-  virtual ~imgStatusTrackerObserver() {}
-
-private:
-  WeakPtr<imgStatusTracker> mTracker;
-};
-
-// imgStatusTracker methods
-
-imgStatusTracker::imgStatusTracker(Image* aImage)
-  : mImage(aImage)
-  , mState(0)
-{
-  mTrackerObserver = new imgStatusTrackerObserver(this);
-}
-
-// Private, used only by CloneForRecording.
-imgStatusTracker::imgStatusTracker(const imgStatusTracker& aOther)
-  : mImage(aOther.mImage)
-  , mState(aOther.mState)
-    // Note: we explicitly don't copy several fields:
-    //  - mRequestRunnable, because it won't be nulled out when the
-    //    mRequestRunnable's Run function eventually gets called.
-    //  - mProperties, because we don't need it and it'd just point at the same
-    //    object
-    //  - mConsumers, because we don't need to talk to consumers
-{
-  mTrackerObserver = new imgStatusTrackerObserver(this);
-}
-
-imgStatusTracker::~imgStatusTracker()
-{}
-
 imgStatusTrackerInit::imgStatusTrackerInit(mozilla::image::Image* aImage,
                                            imgStatusTracker* aTracker)
 {
   MOZ_ASSERT(aImage);
 
   if (aTracker) {
     mTracker = aTracker;
     mTracker->SetImage(aImage);
@@ -183,16 +52,21 @@ imgStatusTracker::ResetImage()
 {
   NS_ABORT_IF_FALSE(mImage, "Resetting image when it's already null!");
   mImage = nullptr;
 }
 
 void imgStatusTracker::SetIsMultipart()
 {
   mState |= FLAG_IS_MULTIPART;
+
+  // If we haven't already blocked onload, make sure we never do.
+  if (!(mState & FLAG_ONLOAD_BLOCKED)) {
+    mState |= FLAG_ONLOAD_BLOCKED | FLAG_ONLOAD_UNBLOCKED;
+  }
 }
 
 bool
 imgStatusTracker::IsLoading() const
 {
   // Checking for whether OnStopRequest has fired allows us to say we're
   // loading before OnStartRequest gets called, letting the request properly
   // get removed from the cache in certain cases.
@@ -413,30 +287,26 @@ imgStatusTracker::SyncNotifyState(ProxyA
   }
 
   if (aState & FLAG_REQUEST_STOPPED) {
     NOTIFY_IMAGE_OBSERVERS(OnStopRequest(aState & FLAG_MULTIPART_STOPPED));
   }
 }
 
 ImageStatusDiff
-imgStatusTracker::Difference(imgStatusTracker* aOther) const
-{
-  MOZ_ASSERT(aOther, "aOther cannot be null");
-  ImageStatusDiff diff;
-  diff.diffState = ~mState & aOther->mState;
-  return diff;
-}
-
-ImageStatusDiff
-imgStatusTracker::DecodeStateAsDifference() const
+imgStatusTracker::Difference(const ImageStatusDiff& aOther) const
 {
   ImageStatusDiff diff;
-  // XXX(seth): Is FLAG_REQUEST_STARTED really the only non-"decode state" flag?
-  diff.diffState = mState & ~FLAG_REQUEST_STARTED;
+  diff.diffState = ~mState & aOther.diffState;
+
+  // Don't unblock onload if we're not blocked.
+  if (!((mState | diff.diffState) & FLAG_ONLOAD_BLOCKED)) {
+    diff.diffState &= ~FLAG_ONLOAD_UNBLOCKED;
+  }
+
   return diff;
 }
 
 void
 imgStatusTracker::ApplyDifference(const ImageStatusDiff& aDiff)
 {
   LOG_SCOPE(GetImgLog(), "imgStatusTracker::ApplyDifference");
   mState |= aDiff.diffState;
@@ -451,25 +321,16 @@ imgStatusTracker::SyncNotifyDifference(c
 
   SyncNotifyState(mConsumers, !!mImage, aDiff.diffState, aInvalidRect);
 
   if (aDiff.diffState & FLAG_HAS_ERROR) {
     FireFailureNotification();
   }
 }
 
-already_AddRefed<imgStatusTracker>
-imgStatusTracker::CloneForRecording()
-{
-  // Grab a ref to this to ensure it isn't deleted.
-  nsRefPtr<imgStatusTracker> thisStatusTracker = this;
-  nsRefPtr<imgStatusTracker> clone = new imgStatusTracker(*thisStatusTracker);
-  return clone.forget();
-}
-
 void
 imgStatusTracker::SyncNotify(imgRequestProxy* proxy)
 {
   MOZ_ASSERT(NS_IsMainThread(), "imgRequestProxy is not threadsafe");
 #ifdef PR_LOGGING
   nsRefPtr<ImageURL> uri;
   proxy->GetURI(getter_AddRefs(uri));
   nsAutoCString spec;
@@ -554,97 +415,46 @@ imgStatusTracker::FirstConsumerIs(imgReq
     if (proxy) {
       return proxy.get() == aConsumer;
     }
   }
   return false;
 }
 
 void
-imgStatusTracker::RecordCancel()
+imgStatusTracker::RecordError()
 {
   mState |= FLAG_HAS_ERROR;
 }
 
 void
-imgStatusTracker::RecordLoaded()
-{
-  NS_ABORT_IF_FALSE(mImage, "RecordLoaded called before we have an Image");
-  mState |= FLAG_REQUEST_STARTED | FLAG_HAS_SIZE |
-            FLAG_REQUEST_STOPPED | FLAG_MULTIPART_STOPPED;
-}
-
-void
-imgStatusTracker::RecordDecoded()
-{
-  NS_ABORT_IF_FALSE(mImage, "RecordDecoded called before we have an Image");
-  mState |= FLAG_DECODE_STARTED | FLAG_DECODE_STOPPED | FLAG_FRAME_STOPPED;
-}
-
-void
-imgStatusTracker::RecordStartDecode()
-{
-  NS_ABORT_IF_FALSE(mImage, "RecordStartDecode without an Image");
-  mState |= FLAG_DECODE_STARTED;
-}
-
-void
 imgStatusTracker::SendStartDecode(imgRequestProxy* aProxy)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!aProxy->NotificationsDeferred())
     aProxy->OnStartDecode();
 }
 
 void
-imgStatusTracker::RecordStartContainer(imgIContainer* aContainer)
-{
-  NS_ABORT_IF_FALSE(mImage,
-                    "RecordStartContainer called before we have an Image");
-  NS_ABORT_IF_FALSE(mImage == aContainer,
-                    "RecordStartContainer called with wrong Image");
-  mState |= FLAG_HAS_SIZE;
-}
-
-void
 imgStatusTracker::SendStartContainer(imgRequestProxy* aProxy)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!aProxy->NotificationsDeferred())
     aProxy->OnStartContainer();
 }
 
 void
-imgStatusTracker::RecordStopFrame()
-{
-  NS_ABORT_IF_FALSE(mImage, "RecordStopFrame called before we have an Image");
-  mState |= FLAG_FRAME_STOPPED;
-}
-
-void
 imgStatusTracker::SendStopFrame(imgRequestProxy* aProxy)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!aProxy->NotificationsDeferred())
     aProxy->OnStopFrame();
 }
 
 void
-imgStatusTracker::RecordStopDecode(nsresult aStatus)
-{
-  MOZ_ASSERT(mImage, "RecordStopDecode called before we have an Image");
-
-  mState |= FLAG_DECODE_STOPPED;
-
-  if (NS_FAILED(aStatus)) {
-    mState |= FLAG_HAS_ERROR;
-  }
-}
-
-void
 imgStatusTracker::SendStopDecode(imgRequestProxy* aProxy,
                                  nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!aProxy->NotificationsDeferred())
     aProxy->OnStopDecode();
 }
 
@@ -653,31 +463,16 @@ imgStatusTracker::SendDiscard(imgRequest
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!aProxy->NotificationsDeferred())
     aProxy->OnDiscard();
 }
 
 
 void
-imgStatusTracker::RecordUnlockedDraw()
-{
-  NS_ABORT_IF_FALSE(mImage,
-                    "RecordUnlockedDraw called before we have an Image");
-}
-
-void
-imgStatusTracker::RecordImageIsAnimated()
-{
-  NS_ABORT_IF_FALSE(mImage,
-                    "RecordImageIsAnimated called before we have an Image");
-  mState |= FLAG_IS_ANIMATED;
-}
-
-void
 imgStatusTracker::SendImageIsAnimated(imgRequestProxy* aProxy)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!aProxy->NotificationsDeferred())
     aProxy->OnImageIsAnimated();
 }
 
 void
@@ -687,175 +482,88 @@ imgStatusTracker::SendUnlockedDraw(imgRe
   if (!aProxy->NotificationsDeferred())
     aProxy->OnUnlockedDraw();
 }
 
 void
 imgStatusTracker::OnUnlockedDraw()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  RecordUnlockedDraw();
   ProxyArray::ForwardIterator iter(mConsumers);
   while (iter.HasMore()) {
     nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
     if (proxy) {
       SendUnlockedDraw(proxy);
     }
   }
 }
 
 /* non-virtual sort-of-nsIRequestObserver methods */
 void
-imgStatusTracker::RecordStartRequest()
+imgStatusTracker::SendStartRequest(imgRequestProxy* aProxy)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!aProxy->NotificationsDeferred())
+    aProxy->OnStartRequest();
+}
+
+void
+imgStatusTracker::OnStartRequest()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
   // We're starting a new load, so clear any status and state bits indicating
   // load/decode.
   // XXX(seth): Are these really the only flags we want to clear?
   mState &= ~FLAG_REQUEST_STARTED;
   mState &= ~FLAG_DECODE_STARTED;
   mState &= ~FLAG_DECODE_STOPPED;
   mState &= ~FLAG_REQUEST_STOPPED;
   mState &= ~FLAG_ONLOAD_BLOCKED;
   mState &= ~FLAG_ONLOAD_UNBLOCKED;
   mState &= ~FLAG_IS_ANIMATED;
 
   mState |= FLAG_REQUEST_STARTED;
-}
 
-void
-imgStatusTracker::SendStartRequest(imgRequestProxy* aProxy)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (!aProxy->NotificationsDeferred())
-    aProxy->OnStartRequest();
-}
-
-void
-imgStatusTracker::OnStartRequest()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  RecordStartRequest();
   ProxyArray::ForwardIterator iter(mConsumers);
   while (iter.HasMore()) {
     nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
     if (proxy) {
       SendStartRequest(proxy);
     }
   }
 }
 
 void
-imgStatusTracker::RecordStopRequest(bool aLastPart,
-                                    nsresult aStatus)
-{
-  mState |= FLAG_REQUEST_STOPPED;
-  if (aLastPart) {
-    mState |= FLAG_MULTIPART_STOPPED;
-  }
-  if (NS_FAILED(aStatus)) {
-    mState |= FLAG_HAS_ERROR;
-  }
-}
-
-void
 imgStatusTracker::SendStopRequest(imgRequestProxy* aProxy,
                                   bool aLastPart,
                                   nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!aProxy->NotificationsDeferred()) {
     aProxy->OnStopRequest(aLastPart);
   }
 }
 
-class OnStopRequestEvent : public nsRunnable
-{
-public:
-  OnStopRequestEvent(imgStatusTracker* aTracker,
-                     bool aLastPart,
-                     nsresult aStatus)
-    : mTracker(aTracker)
-    , mLastPart(aLastPart)
-    , mStatus(aStatus)
-  {
-    MOZ_ASSERT(!NS_IsMainThread(), "Should be created off the main thread");
-    MOZ_ASSERT(aTracker, "aTracker should not be null");
-  }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
-    MOZ_ASSERT(mTracker, "mTracker should not be null");
-    mTracker->OnStopRequest(mLastPart, mStatus);
-    return NS_OK;
-  }
-private:
-  nsRefPtr<imgStatusTracker> mTracker;
-  bool mLastPart;
-  nsresult mStatus;
-};
-
-void
-imgStatusTracker::OnStopRequest(bool aLastPart,
-                                nsresult aStatus)
-{
-  if (!NS_IsMainThread()) {
-    NS_DispatchToMainThread(
-      new OnStopRequestEvent(this, aLastPart, aStatus));
-    return;
-  }
-  bool preexistingError = mState & FLAG_HAS_ERROR;
-
-  RecordStopRequest(aLastPart, aStatus);
-  /* notify the kids */
-  ProxyArray::ForwardIterator srIter(mConsumers);
-  while (srIter.HasMore()) {
-    nsRefPtr<imgRequestProxy> proxy = srIter.GetNext().get();
-    if (proxy) {
-      SendStopRequest(proxy, aLastPart, aStatus);
-    }
-  }
-
-  if (NS_FAILED(aStatus) && !preexistingError) {
-    FireFailureNotification();
-  }
-}
-
 void
 imgStatusTracker::OnDiscard()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   /* notify the kids */
   ProxyArray::ForwardIterator iter(mConsumers);
   while (iter.HasMore()) {
     nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
     if (proxy) {
       SendDiscard(proxy);
     }
   }
 }
 
 void
-imgStatusTracker::OnStopFrame()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  RecordStopFrame();
-
-  /* notify the kids */
-  ProxyArray::ForwardIterator iter(mConsumers);
-  while (iter.HasMore()) {
-    nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
-    if (proxy) {
-      SendStopFrame(proxy);
-    }
-  }
-}
-
-void
 imgStatusTracker::OnDataAvailable()
 {
   if (!NS_IsMainThread()) {
     // Note: SetHasImage calls Image::Lock and Image::IncrementAnimationCounter
     // so subsequent calls or dispatches which Unlock or Decrement~ should
     // be issued after this to avoid race conditions.
     NS_DispatchToMainThread(
       NS_NewRunnableMethod(this, &imgStatusTracker::OnDataAvailable));
@@ -867,41 +575,25 @@ imgStatusTracker::OnDataAvailable()
     nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
     if (proxy) {
       proxy->SetHasImage();
     }
   }
 }
 
 void
-imgStatusTracker::RecordBlockOnload()
-{
-  mState |= FLAG_ONLOAD_BLOCKED;
-}
-
-void
 imgStatusTracker::SendBlockOnload(imgRequestProxy* aProxy)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!aProxy->NotificationsDeferred()) {
     aProxy->BlockOnload();
   }
 }
 
 void
-imgStatusTracker::RecordUnblockOnload()
-{
-  // We sometimes unblock speculatively, so only actually unblock if we've
-  // previously blocked.
-  if (mState & FLAG_ONLOAD_BLOCKED) {
-    mState |= FLAG_ONLOAD_UNBLOCKED;
-  }
-}
-
-void
 imgStatusTracker::SendUnblockOnload(imgRequestProxy* aProxy)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!aProxy->NotificationsDeferred()) {
     aProxy->UnblockOnload();
   }
 }
 
@@ -912,34 +604,28 @@ imgStatusTracker::MaybeUnblockOnload()
     NS_DispatchToMainThread(
       NS_NewRunnableMethod(this, &imgStatusTracker::MaybeUnblockOnload));
     return;
   }
   if (!(mState & FLAG_ONLOAD_BLOCKED) || (mState & FLAG_ONLOAD_UNBLOCKED)) {
     return;
   }
 
-  RecordUnblockOnload();
+  mState |= FLAG_ONLOAD_UNBLOCKED;
 
   ProxyArray::ForwardIterator iter(mConsumers);
   while (iter.HasMore()) {
     nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
     if (proxy) {
       SendUnblockOnload(proxy);
     }
   }
 }
 
 void
-imgStatusTracker::RecordError()
-{
-  mState |= FLAG_HAS_ERROR;
-}
-
-void
 imgStatusTracker::FireFailureNotification()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Some kind of problem has happened with image decoding.
   // Report the URI to net:failed-to-process-uri-conent observers.
   if (mImage) {
     // Should be on main thread, so ok to create a new nsIURI.
--- a/image/src/imgStatusTracker.h
+++ b/image/src/imgStatusTracker.h
@@ -2,17 +2,16 @@
  *
  * 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 imgStatusTracker_h__
 #define imgStatusTracker_h__
 
-class imgDecoderObserver;
 class imgIContainer;
 class imgStatusNotifyRunnable;
 class imgRequestNotifyRunnable;
 class imgStatusTrackerObserver;
 class nsIRunnable;
 
 #include "mozilla/RefPtr.h"
 #include "mozilla/WeakPtr.h"
@@ -47,16 +46,31 @@ struct ImageStatusDiff
 {
   ImageStatusDiff()
     : diffState(0)
   { }
 
   static ImageStatusDiff NoChange() { return ImageStatusDiff(); }
   bool IsNoChange() const { return *this == NoChange(); }
 
+  static ImageStatusDiff ForOnStopRequest(bool aLastPart,
+                                          bool aError,
+                                          nsresult aStatus)
+  {
+    ImageStatusDiff diff;
+    diff.diffState |= FLAG_REQUEST_STOPPED;
+    if (aLastPart) {
+      diff.diffState |= FLAG_MULTIPART_STOPPED;
+    }
+    if (NS_FAILED(aStatus) || aError) {
+      diff.diffState |= FLAG_HAS_ERROR;
+    }
+    return diff;
+  }
+
   bool operator!=(const ImageStatusDiff& aOther) const { return !(*this == aOther); }
   bool operator==(const ImageStatusDiff& aOther) const {
     return aOther.diffState == diffState;
   }
 
   void Combine(const ImageStatusDiff& aOther) {
     diffState |= aOther.diffState;
   }
@@ -76,26 +90,29 @@ struct ImageStatusDiff
  * When a new proxy needs to be notified of the current state of an image, call
  * the Notify() method on this class with the relevant proxy as its argument,
  * and the notifications will be replayed to the proxy asynchronously.
  */
 
 
 class imgStatusTracker : public mozilla::SupportsWeakPtr<imgStatusTracker>
 {
-  virtual ~imgStatusTracker();
+  virtual ~imgStatusTracker() { }
 
 public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(imgStatusTracker)
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(imgStatusTracker)
 
   // aImage is the image that this status tracker will pass to the
   // imgRequestProxys in SyncNotify() and EmulateRequestFinished(), and must be
   // alive as long as this instance is, because we hold a weak reference to it.
-  explicit imgStatusTracker(mozilla::image::Image* aImage);
+  explicit imgStatusTracker(mozilla::image::Image* aImage)
+    : mImage(aImage)
+    , mState(0)
+  { }
 
   // Image-setter, for imgStatusTrackers created by imgRequest::Init, which
   // are created before their Image is created.  This method should only
   // be called once, and only on an imgStatusTracker that was initialized
   // without an image.
   void SetImage(mozilla::image::Image* aImage);
 
   // Image resetter, for when mImage is about to go out of scope. mImage is a
@@ -158,119 +175,82 @@ public:
 
   // Returns whether we are in the process of loading; that is, whether we have
   // not received OnStopRequest.
   bool IsLoading() const;
 
   // Get the current image status (as in imgIRequest).
   uint32_t GetImageStatus() const;
 
-  // Following are all the notification methods. You must call the Record
-  // variant on this status tracker, then call the Send variant for each proxy
-  // you want to notify.
-
-  // Call when the request is being cancelled.
-  void RecordCancel();
-
-  // Shorthand for recording all the load notifications: StartRequest,
-  // StartContainer, StopRequest.
-  void RecordLoaded();
-
-  // Shorthand for recording all the decode notifications: StartDecode,
-  // DataAvailable, StopFrame, StopDecode.
-  void RecordDecoded();
-
-  /* non-virtual imgDecoderObserver methods */
   // Functions with prefix Send- are main thread only, since they contain calls
   // to imgRequestProxy functions, which are expected on the main thread.
-  void RecordStartDecode();
   void SendStartDecode(imgRequestProxy* aProxy);
-  void RecordStartContainer(imgIContainer* aContainer);
   void SendStartContainer(imgRequestProxy* aProxy);
-  void RecordStopFrame();
   void SendStopFrame(imgRequestProxy* aProxy);
-  void RecordStopDecode(nsresult statusg);
   void SendStopDecode(imgRequestProxy* aProxy, nsresult aStatus);
   void SendDiscard(imgRequestProxy* aProxy);
-  void RecordUnlockedDraw();
   void SendUnlockedDraw(imgRequestProxy* aProxy);
-  void RecordImageIsAnimated();
   void SendImageIsAnimated(imgRequestProxy *aProxy);
 
   /* non-virtual sort-of-nsIRequestObserver methods */
   // Functions with prefix Send- are main thread only, since they contain calls
   // to imgRequestProxy functions, which are expected on the main thread.
-  void RecordStartRequest();
   void SendStartRequest(imgRequestProxy* aProxy);
-  void RecordStopRequest(bool aLastPart, nsresult aStatus);
   void SendStopRequest(imgRequestProxy* aProxy, bool aLastPart, nsresult aStatus);
 
   // All main thread only because they call functions (like SendStartRequest)
   // which are expected to be called on the main thread.
   void OnStartRequest();
   // OnDataAvailable will dispatch a call to itself onto the main thread if not
   // called there.
   void OnDataAvailable();
-  void OnStopRequest(bool aLastPart, nsresult aStatus);
   void OnDiscard();
   void OnUnlockedDraw();
-  // This is called only by VectorImage, and only to ensure tests work
-  // properly. Do not use it.
-  void OnStopFrame();
 
   /* non-virtual imgIOnloadBlocker methods */
   // NB: If UnblockOnload is sent, and then we are asked to replay the
   // notifications, we will not send a BlockOnload/UnblockOnload pair.  This
   // is different from all the other notifications.
-  void RecordBlockOnload();
   void SendBlockOnload(imgRequestProxy* aProxy);
-  void RecordUnblockOnload();
   void SendUnblockOnload(imgRequestProxy* aProxy);
 
   // Main thread only because mConsumers is not threadsafe.
   void MaybeUnblockOnload();
 
   void RecordError();
 
   bool IsMultipart() const { return mState & mozilla::image::FLAG_IS_MULTIPART; }
 
   // Weak pointer getters - no AddRefs.
   inline already_AddRefed<mozilla::image::Image> GetImage() const {
     nsRefPtr<mozilla::image::Image> image = mImage;
     return image.forget();
   }
   inline bool HasImage() { return mImage; }
 
-  inline imgDecoderObserver* GetDecoderObserver() { return mTrackerObserver.get(); }
-
-  already_AddRefed<imgStatusTracker> CloneForRecording();
-
   // Compute the difference between this status tracker and aOther.
-  mozilla::image::ImageStatusDiff Difference(imgStatusTracker* aOther) const;
-
-  // Captures all of the decode notifications (i.e., not OnStartRequest /
-  // OnStopRequest) so far as an ImageStatusDiff.
-  mozilla::image::ImageStatusDiff DecodeStateAsDifference() const;
+  mozilla::image::ImageStatusDiff Difference(const mozilla::image::ImageStatusDiff& aOther) const;
 
   // Update our state to incorporate the changes in aDiff.
   void ApplyDifference(const mozilla::image::ImageStatusDiff& aDiff);
 
   // Notify for the changes captured in an ImageStatusDiff. Because this may
   // result in recursive notifications, no decoding locks may be held.
   // Called on the main thread only.
   void SyncNotifyDifference(const mozilla::image::ImageStatusDiff& aDiff,
                             const nsIntRect& aInvalidRect = nsIntRect());
 
 private:
   typedef nsTObserverArray<mozilla::WeakPtr<imgRequestProxy>> ProxyArray;
   friend class imgStatusNotifyRunnable;
   friend class imgRequestNotifyRunnable;
   friend class imgStatusTrackerObserver;
   friend class imgStatusTrackerInit;
-  imgStatusTracker(const imgStatusTracker& aOther);
+
+  imgStatusTracker(const imgStatusTracker& aOther) MOZ_DELETE;
 
   // Main thread only because it deals with the observer service.
   void FireFailureNotification();
 
   // Main thread only, since imgRequestProxy calls are expected on the main
   // thread, and mConsumers is not threadsafe.
   static void SyncNotifyState(ProxyArray& aProxies,
                               bool aHasImage, uint32_t aState,
@@ -281,18 +261,16 @@ private:
   // This weak ref should be set null when the image goes out of scope.
   mozilla::image::Image* mImage;
 
   // List of proxies attached to the image. Each proxy represents a consumer
   // using the image. Array and/or individual elements should only be accessed
   // on the main thread.
   ProxyArray mConsumers;
 
-  mozilla::RefPtr<imgDecoderObserver> mTrackerObserver;
-
   uint32_t mState;
 };
 
 class imgStatusTrackerInit
 {
 public:
   imgStatusTrackerInit(mozilla::image::Image* aImage,
                        imgStatusTracker* aTracker);