Backed out 5 changesets (bug 1079653) for mochitest-dt asserts on a CLOSED TREE.
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 15 Oct 2014 19:00:12 -0400
changeset 210642 a13927f7835342093b3dca3a4e6b0c2a89dd4a29
parent 210641 21caa5dfb494bacb31882115053a8d824c44f0ba
child 210643 5ce2fc3939da728fdfcd4508a031cbcbf780b163
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
bugs1079653
milestone36.0a1
backs outaaac8c67129941d2a969ed0df7c76eb30dacd789
59d1754eb01fd84ec139d686fc7b2f79079f17c1
dfb0890b02be6dc35a3b76ddb1d5c31a3a9a888a
546f90c14465afd29b88fe16ff27bf5678b285d5
a8e760faf784c78986c0cbebede0177fd361ad4c
Backed out 5 changesets (bug 1079653) for mochitest-dt asserts on a CLOSED TREE. Backed out changeset aaac8c671299 (bug 1079653) Backed out changeset 59d1754eb01f (bug 1079653) Backed out changeset dfb0890b02be (bug 1079653) Backed out changeset 546f90c14465 (bug 1079653) Backed out changeset a8e760faf784 (bug 1079653)
image/src/Decoder.cpp
image/src/Decoder.h
image/src/RasterImage.cpp
image/src/RasterImage.h
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -14,25 +14,23 @@
 namespace mozilla {
 namespace image {
 
 Decoder::Decoder(RasterImage &aImage)
   : mImage(aImage)
   , mCurrentFrame(nullptr)
   , mImageData(nullptr)
   , mColormap(nullptr)
-  , mChunkCount(0)
   , mDecodeFlags(0)
   , mBytesDecoded(0)
   , mDecodeDone(false)
   , mDataError(false)
   , mFrameCount(0)
   , mFailCode(NS_OK)
   , mNeedsNewFrame(false)
-  , mNeedsToFlushData(false)
   , mInitialized(false)
   , mSizeDecode(false)
   , mInFrame(false)
   , mIsAnimated(false)
 {
 }
 
 Decoder::~Decoder()
@@ -94,30 +92,20 @@ Decoder::Write(const char* aBuffer, uint
     js::ProfileEntry::Category::GRAPHICS);
 
   MOZ_ASSERT(NS_IsMainThread() || aStrategy == DECODE_ASYNC);
 
   // We're strict about decoder errors
   MOZ_ASSERT(!HasDecoderError(),
              "Not allowed to make more decoder calls after error!");
 
-  // Begin recording telemetry data.
-  TimeStamp start = TimeStamp::Now();
-  mChunkCount++;
-
   // Keep track of the total number of bytes written.
   mBytesDecoded += aCount;
 
-  // If we're flushing data, clear the flag.
-  if (aBuffer == nullptr && aCount == 0) {
-    MOZ_ASSERT(mNeedsToFlushData, "Flushing when we don't need to");
-    mNeedsToFlushData = false;
-  }
-
-  // If a data error occured, just ignore future data.
+  // If a data error occured, just ignore future data
   if (HasDataError())
     return;
 
   if (IsSizeDecode() && HasSize()) {
     // More data came in since we found the size. We have nothing to do here.
     return;
   }
 
@@ -129,19 +117,16 @@ Decoder::Write(const char* aBuffer, uint
   while (aStrategy == DECODE_SYNC && NeedsNewFrame() && !HasDataError()) {
     nsresult rv = AllocateFrame();
 
     if (NS_SUCCEEDED(rv)) {
       // Tell the decoder to use the data it saved when it asked for a new frame.
       WriteInternal(nullptr, 0, aStrategy);
     }
   }
-
-  // Finish telemetry.
-  mDecodeTime += (TimeStamp::Now() - start);
 }
 
 void
 Decoder::Finish(RasterImage::eShutdownIntent aShutdownIntent)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Implementation-specific finalization
@@ -252,22 +237,16 @@ Decoder::AllocateFrame()
   } else if (NS_FAILED(rv)) {
     PostDataError();
   }
 
   // Mark ourselves as not needing another frame before talking to anyone else
   // so they can tell us if they need yet another.
   mNeedsNewFrame = false;
 
-  // If we've received any data at all, we may have pending data that needs to
-  // be flushed now that we have a frame to decode into.
-  if (mBytesDecoded > 0) {
-    mNeedsToFlushData = true;
-  }
-
   return rv;
 }
 
 void
 Decoder::FlushInvalidations()
 {
   NS_ABORT_IF_FALSE(!HasDecoderError(),
                     "Not allowed to make more decoder calls after error!");
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -39,21 +39,16 @@ public:
    */
   void InitSharedDecoder(uint8_t* imageData, uint32_t imageDataLength,
                          uint32_t* colormap, uint32_t colormapSize,
                          imgFrame* currentFrame);
 
   /**
    * Writes data to the decoder.
    *
-   * If aBuffer is null and aCount is 0, Write() flushes any buffered data to
-   * the decoder. Data is buffered if the decoder wasn't able to completely
-   * decode it because it needed a new frame.  If it's necessary to flush data,
-   * NeedsToFlushData() will return true.
-   *
    * @param aBuffer buffer containing the data to be written
    * @param aCount the number of bytes to write
    *
    * Any errors are reported by setting the appropriate state on the decoder.
    *
    * Notifications Sent: TODO
    */
   void Write(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
@@ -103,22 +98,16 @@ public:
   void SetObserver(imgDecoderObserver* aObserver)
   {
     MOZ_ASSERT(aObserver);
     mObserver = aObserver;
   }
 
   size_t BytesDecoded() const { return mBytesDecoded; }
 
-  // The amount of time we've spent inside Write() so far for this decoder.
-  TimeDuration DecodeTime() const { return mDecodeTime; }
-
-  // The number of times Write() has been called so far for this decoder.
-  uint32_t ChunkCount() const { return mChunkCount; }
-
   // 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; }
 
   // Error tracking
@@ -164,21 +153,16 @@ public:
   // will be called again with nullptr and 0 as arguments.
   void NeedNewFrame(uint32_t frameNum, uint32_t x_offset, uint32_t y_offset,
                     uint32_t width, uint32_t height,
                     gfx::SurfaceFormat format,
                     uint8_t palette_depth = 0);
 
   virtual bool NeedsNewFrame() const { return mNeedsNewFrame; }
 
-  // Returns true if we may have stored data that we need to flush now that we
-  // have a new frame to decode into. Callers can use Write() to actually
-  // flush the data; see the documentation for that method.
-  bool NeedsToFlushData() const { return mNeedsToFlushData; }
-
   // Try to allocate a frame as described in mNewFrameData and return the
   // status code from that attempt. Clears mNewFrameData.
   virtual nsresult AllocateFrame();
 
   already_AddRefed<imgFrame> GetCurrentFrame() const
   {
     nsRefPtr<imgFrame> frame = mCurrentFrame;
     return frame.forget();
@@ -246,20 +230,16 @@ protected:
   RefPtr<imgDecoderObserver> mObserver;
   ImageMetadata mImageMetadata;
 
   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;
 
-  // Telemetry data for this decoder.
-  TimeDuration mDecodeTime;
-  uint32_t mChunkCount;
-
   uint32_t mDecodeFlags;
   size_t mBytesDecoded;
   bool mDecodeDone;
   bool mDataError;
 
 private:
   uint32_t mFrameCount; // Number of frames, including anything in-progress
 
@@ -288,17 +268,16 @@ private:
     uint32_t mOffsetY;
     uint32_t mWidth;
     uint32_t mHeight;
     gfx::SurfaceFormat mFormat;
     uint8_t mPaletteDepth;
   };
   NewFrameData mNewFrameData;
   bool mNeedsNewFrame;
-  bool mNeedsToFlushData;
   bool mInitialized;
   bool mSizeDecode;
   bool mInFrame;
   bool mIsAnimated;
 };
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -316,17 +316,16 @@ RasterImage::RasterImage(imgStatusTracke
   mLockCount(0),
   mDecodeCount(0),
   mRequestedSampleSize(0),
 #ifdef DEBUG
   mFramesNotified(0),
 #endif
   mDecodingMonitor("RasterImage Decoding Monitor"),
   mDecoder(nullptr),
-  mDecodeStatus(DecodeStatus::INACTIVE),
   mInDecoder(false),
   mStatusDiff(ImageStatusDiff::NoChange()),
   mNotifying(false),
   mHasSize(false),
   mDecodeOnDraw(false),
   mMultipart(false),
   mDiscardable(false),
   mHasSourceData(false),
@@ -1762,18 +1761,17 @@ RasterImage::OnNewSourceData()
   // The decoder was shut down and we didn't flag an error, so we should be decoded
   NS_ABORT_IF_FALSE(mDecoded, "Should be decoded in NewSourceData");
 
   // Reset some flags
   mDecoded = false;
   mHasSourceData = false;
   mHasSize = false;
   mWantFullDecode = true;
-  mDecodeStatus = DecodeStatus::INACTIVE;
-  mDecodeStatusTracker = nullptr;
+  mDecodeRequest = nullptr;
 
   if (mAnim) {
     mAnim->SetDoneDecoding(false);
   }
 
   // We always need the size first.
   rv = InitDecoder(/* aDoSizeDecode = */ true);
   CONTAINER_ENSURE_SUCCESS(rv);
@@ -1864,18 +1862,17 @@ RasterImage::Discard(bool force)
 
   // Flag that we no longer have decoded frames for this image
   mDecoded = false;
 
   // Notify that we discarded
   if (mStatusTracker)
     mStatusTracker->OnDiscard();
 
-  mDecodeStatus = DecodeStatus::INACTIVE;
-  mDecodeStatusTracker = nullptr;
+  mDecodeRequest = nullptr;
 
   if (force)
     DiscardTracker::Remove(&mDiscardTrackerNode);
 
   // Log
   PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
          ("CompressedImageAccounting: discarded uncompressed image "
           "data from RasterImage %p (%s) - %d frames (cached count: %d); "
@@ -1985,22 +1982,22 @@ RasterImage::InitDecoder(bool aDoSizeDec
   // case. Regardless, we need to lock the last frame. Our invariant is that,
   // while we have a decoder open, the last frame is always locked.
   if (GetNumFrames() > 0) {
     nsRefPtr<imgFrame> curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
     curframe->LockImageData();
   }
 
   // Initialize the decoder
-  if (!mDecodeStatusTracker) {
-    MOZ_ASSERT(mStatusTracker, "Should have an imgStatusTracker");
-    mDecodeStatusTracker = mStatusTracker->CloneForRecording();
+  if (!mDecodeRequest) {
+    mDecodeRequest = new DecodeRequest(this);
   }
-  MOZ_ASSERT(mDecodeStatusTracker->GetDecoderObserver());
-  mDecoder->SetObserver(mDecodeStatusTracker->GetDecoderObserver());
+  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);
@@ -2226,17 +2223,18 @@ RasterImage::RequestDecodeCore(RequestDe
     // data, so signal that we want a full decode and give up for now.
     if (!mHasSize) {
       mWantFullDecode = true;
       return NS_OK;
     }
   }
 
   // If the image is waiting for decode work to be notified, go ahead and do that.
-  if (mDecodeStatus == DecodeStatus::WORK_DONE &&
+  if (mDecodeRequest &&
+      mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE &&
       aDecodeType == SYNCHRONOUS_NOTIFY) {
     ReentrantMonitorAutoEnter lock(mDecodingMonitor);
     nsresult rv = FinishedSomeDecoding();
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
   // If we're fully decoded, we have nothing to do. We need this check after
   // DecodeUntilSizeAvailable and FinishedSomeDecoding because they can result
@@ -2262,17 +2260,19 @@ RasterImage::RequestDecodeCore(RequestDe
   if (mDecoder && mDecoder->BytesDecoded() > mSourceData.Length()) {
     return NS_OK;
   }
 
   // After acquiring the lock we may have finished some more decoding, so
   // we need to repeat the following three checks after getting the lock.
 
   // If the image is waiting for decode work to be notified, go ahead and do that.
-  if (mDecodeStatus == DecodeStatus::WORK_DONE && aDecodeType != ASYNCHRONOUS) {
+  if (mDecodeRequest &&
+      mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE &&
+      aDecodeType != ASYNCHRONOUS) {
     nsresult rv = FinishedSomeDecoding();
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
   // If we're fully decoded, we have nothing to do. We need this check after
   // DecodeUntilSizeAvailable and FinishedSomeDecoding because they can result
   // in us finishing an in-progress decode (or kicking off and finishing a
   // synchronous decode if we're already waiting on a full decode).
@@ -2353,20 +2353,22 @@ RasterImage::SyncDecode()
   ReentrantMonitorAutoEnter lock(mDecodingMonitor);
 
   // We really have no good way of forcing a synchronous decode if we're being
   // called in a re-entrant manner (ie, from an event listener fired by a
   // decoder), because the decoding machinery is already tied up. We thus explicitly
   // disallow this type of call in the API, and check for it in API methods.
   NS_ABORT_IF_FALSE(!mInDecoder, "Yikes, forcing sync in reentrant call!");
 
-  // If the image is waiting for decode work to be notified, go ahead and do that.
-  if (mDecodeStatus == DecodeStatus::WORK_DONE) {
-    nsresult rv = FinishedSomeDecoding();
-    CONTAINER_ENSURE_SUCCESS(rv);
+  if (mDecodeRequest) {
+    // If the image is waiting for decode work to be notified, go ahead and do that.
+    if (mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
+      nsresult rv = FinishedSomeDecoding();
+      CONTAINER_ENSURE_SUCCESS(rv);
+    }
   }
 
   nsresult rv;
 
   // If we're decoded already, or decoding until the size was available
   // finished us as a side-effect, no worries
   if (mDecoded)
     return NS_OK;
@@ -2389,19 +2391,21 @@ RasterImage::SyncDecode()
       // with the new flags. If we can't discard then there isn't
       // anything we can do.
       if (!CanForciblyDiscardAndRedecode())
         return NS_ERROR_NOT_AVAILABLE;
       ForceDiscard();
     }
   }
 
-  // If we're currently waiting on a new frame for this image, create it now.
+  // If we're currently waiting on a new frame for this image, we have to create
+  // it now.
   if (mDecoder && mDecoder->NeedsNewFrame()) {
     mDecoder->AllocateFrame();
+    mDecodeRequest->mAllocatedNewFrame = true;
   }
 
   // If we don't have a decoder, create one
   if (!mDecoder) {
     rv = InitDecoder(/* aDoSizeDecode = */ false);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
@@ -2771,17 +2775,18 @@ RasterImage::DecodeSomeData(size_t aMaxB
 {
   MOZ_ASSERT(mDecoder, "Should have a decoder");
 
   mDecodingMonitor.AssertCurrentThreadIn();
 
   // First, if we've just been called because we allocated a frame on the main
   // thread, let the decoder deal with the data it set aside at that time by
   // passing it a null buffer.
-  if (mDecoder->NeedsToFlushData()) {
+  if (mDecodeRequest->mAllocatedNewFrame) {
+    mDecodeRequest->mAllocatedNewFrame = false;
     nsresult rv = WriteToDecoder(nullptr, 0, aStrategy);
     if (NS_FAILED(rv) || mDecoder->NeedsNewFrame()) {
       return rv;
     }
   }
 
   // If we have nothing else to decode, return.
   if (mDecoder->BytesDecoded() == mSourceData.Length()) {
@@ -2817,17 +2822,18 @@ RasterImage::IsDecodeFinished()
     }
   } else if (mDecoder->GetDecodeDone()) {
     return true;
   }
 
   // If the decoder returned because it needed a new frame and we haven't
   // written to it since then, the decoder may be storing data that it hasn't
   // decoded yet.
-  if (mDecoder->NeedsNewFrame() || mDecoder->NeedsToFlushData()) {
+  if (mDecoder->NeedsNewFrame() ||
+      (mDecodeRequest && mDecodeRequest->mAllocatedNewFrame)) {
     return false;
   }
 
   // Otherwise, if we have all the source data and wrote all the source data,
   // we're done.
   //
   // (NB - This can be the case even for non-erroneous images because
   // Decoder::GetDecodeDone() might not return true until after we call
@@ -2971,38 +2977,40 @@ 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 */,
-                                  imgStatusTracker* aDecodeTracker /* = nullptr */)
+                                  DecodeRequest* aRequest /* = nullptr */)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mDecodingMonitor.AssertCurrentThreadIn();
 
-  nsRefPtr<imgStatusTracker> statusTracker = aDecodeTracker
-                                           ? aDecodeTracker
-                                           : mDecodeStatusTracker.get();
+  nsRefPtr<DecodeRequest> request;
+  if (aRequest) {
+    request = aRequest;
+  } else {
+    request = mDecodeRequest;
+  }
 
   // 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;
   nsresult rv = NS_OK;
 
   if (image->mDecoder) {
-    if (!image->mDecoder->IsSizeDecode() && image->mDecoder->ChunkCount()) {
-      Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS,
-                            image->mDecoder->ChunkCount());
+    if (request && request->mChunkCount && !image->mDecoder->IsSizeDecode()) {
+      Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, request->mChunkCount);
     }
 
     if (!image->mHasSize && image->mDecoder->HasSize()) {
       image->mDecoder->SetSizeOnImage();
     }
 
     // If the decode finished, or we're specifically being told to shut down,
     // tell the image and shut down the decoder.
@@ -3010,42 +3018,42 @@ RasterImage::FinishedSomeDecoding(eShutd
       done = true;
 
       // Hold on to a reference to the decoder until we're done with it
       nsRefPtr<Decoder> decoder = image->mDecoder;
 
       wasSize = decoder->IsSizeDecode();
 
       // Do some telemetry if this isn't a size decode.
-      if (!wasSize) {
+      if (request && !wasSize) {
         Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME,
-                              int32_t(decoder->DecodeTime().ToMicroseconds()));
+                              int32_t(request->mDecodeTime.ToMicroseconds()));
 
         // We record the speed for only some decoders. The rest have
         // SpeedHistogram return HistogramCount.
         Telemetry::ID id = decoder->SpeedHistogram();
         if (id < Telemetry::HistogramCount) {
           int32_t KBps = int32_t(decoder->BytesDecoded() /
-                                 (1024 * decoder->DecodeTime().ToSeconds()));
+                                 (1024 * request->mDecodeTime.ToSeconds()));
           Telemetry::Accumulate(id, KBps);
         }
       }
 
       // 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();
       }
     }
   }
 
-  ImageStatusDiff diff = statusTracker
-                       ? image->mStatusTracker->Difference(statusTracker)
-                       : image->mStatusTracker->DecodeStateAsDifference();
+  ImageStatusDiff diff =
+    request ? image->mStatusTracker->Difference(request->mStatusTracker)
+            : image->mStatusTracker->DecodeStateAsDifference();
   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!");
@@ -3192,44 +3200,51 @@ void
 RasterImage::DecodePool::RequestDecode(RasterImage* aImg)
 {
   MOZ_ASSERT(aImg->mDecoder);
   aImg->mDecodingMonitor.AssertCurrentThreadIn();
 
   // If we're currently waiting on a new frame for this image, we can't do any
   // decoding.
   if (!aImg->mDecoder->NeedsNewFrame()) {
-    if (aImg->mDecodeStatus == DecodeStatus::PENDING ||
-        aImg->mDecodeStatus == DecodeStatus::ACTIVE) {
+    // No matter whether this is currently being decoded, we need to update the
+    // number of bytes we want it to decode.
+    aImg->mDecodeRequest->mBytesToDecode =
+      aImg->mSourceData.Length() - aImg->mDecoder->BytesDecoded();
+
+    if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_PENDING ||
+        aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_ACTIVE) {
       // The image is already in our list of images to decode, or currently being
       // decoded, so we don't have to do anything else.
       return;
     }
 
-    aImg->mDecodeStatus = DecodeStatus::PENDING;
-    nsRefPtr<DecodeJob> job = new DecodeJob(aImg);
+    aImg->mDecodeRequest->mRequestStatus = DecodeRequest::REQUEST_PENDING;
+    nsRefPtr<DecodeJob> job = new DecodeJob(aImg->mDecodeRequest, aImg);
 
     MutexAutoLock threadPoolLock(mThreadPoolMutex);
     if (!gfxPrefs::ImageMTDecodingEnabled() || !mThreadPool) {
       NS_DispatchToMainThread(job);
     } else {
       mThreadPool->Dispatch(job, nsIEventTarget::DISPATCH_NORMAL);
     }
   }
 }
 
 void
 RasterImage::DecodePool::DecodeABitOf(RasterImage* aImg, DecodeStrategy aStrategy)
 {
   MOZ_ASSERT(NS_IsMainThread());
   aImg->mDecodingMonitor.AssertCurrentThreadIn();
 
-  // If the image is waiting for decode work to be notified, go ahead and do that.
-  if (aImg->mDecodeStatus == DecodeStatus::WORK_DONE) {
-    aImg->FinishedSomeDecoding();
+  if (aImg->mDecodeRequest) {
+    // If the image is waiting for decode work to be notified, go ahead and do that.
+    if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
+      aImg->FinishedSomeDecoding();
+    }
   }
 
   DecodeSomeOfImage(aImg, aStrategy);
 
   aImg->FinishedSomeDecoding();
 
   // If the decoder needs a new frame, enqueue an event to get it; that event
   // will enqueue another decode request when it's done.
@@ -3249,84 +3264,80 @@ RasterImage::DecodePool::DecodeABitOf(Ra
 
 /* static */ void
 RasterImage::DecodePool::StopDecoding(RasterImage* aImg)
 {
   aImg->mDecodingMonitor.AssertCurrentThreadIn();
 
   // If we haven't got a decode request, we're not currently decoding. (Having
   // a decode request doesn't imply we *are* decoding, though.)
-  aImg->mDecodeStatus = DecodeStatus::STOPPED;
+  if (aImg->mDecodeRequest) {
+    aImg->mDecodeRequest->mRequestStatus = DecodeRequest::REQUEST_STOPPED;
+  }
 }
 
 NS_IMETHODIMP
 RasterImage::DecodePool::DecodeJob::Run()
 {
   ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
 
   // If we were interrupted, we shouldn't do any work.
-  if (mImage->mDecodeStatus == DecodeStatus::STOPPED) {
-    DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage,
-                                                 mImage->mDecodeStatusTracker);
+  if (mRequest->mRequestStatus == DecodeRequest::REQUEST_STOPPED) {
+    DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage, mRequest);
     return NS_OK;
   }
 
   // If someone came along and synchronously decoded us, there's nothing for us to do.
   if (!mImage->mDecoder || mImage->IsDecodeFinished()) {
-    DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage,
-                                                 mImage->mDecodeStatusTracker);
+    DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage, mRequest);
     return NS_OK;
   }
 
   // If we're a decode job that's been enqueued since a previous decode that
   // still needs a new frame, we can't do anything. Wait until the
   // FrameNeededWorker enqueues another frame.
   if (mImage->mDecoder->NeedsNewFrame()) {
     return NS_OK;
   }
 
-  mImage->mDecodeStatus = DecodeStatus::ACTIVE;
+  mRequest->mRequestStatus = DecodeRequest::REQUEST_ACTIVE;
 
   size_t oldByteCount = mImage->mDecoder->BytesDecoded();
 
   DecodeType type = DECODE_TYPE_UNTIL_DONE_BYTES;
 
   // Multithreaded decoding can be disabled. If we've done so, we don't want to
   // monopolize the main thread, and will allow a timeout in DecodeSomeOfImage.
   if (NS_IsMainThread()) {
     type = DECODE_TYPE_UNTIL_TIME;
   }
 
-  size_t maxBytes = mImage->mSourceData.Length() -
-                    mImage->mDecoder->BytesDecoded();
-  DecodePool::Singleton()->DecodeSomeOfImage(mImage, DECODE_ASYNC,
-                                             type, maxBytes);
+  DecodePool::Singleton()->DecodeSomeOfImage(mImage, DECODE_ASYNC, type, mRequest->mBytesToDecode);
 
   size_t bytesDecoded = mImage->mDecoder->BytesDecoded() - oldByteCount;
 
-  mImage->mDecodeStatus = DecodeStatus::WORK_DONE;
+  mRequest->mRequestStatus = DecodeRequest::REQUEST_WORK_DONE;
 
   // If the decoder needs a new frame, enqueue an event to get it; that event
   // will enqueue another decode request when it's done.
   if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) {
     FrameNeededWorker::GetNewFrame(mImage);
   }
   // If we aren't yet finished decoding and we have more data in hand, add
   // this request to the back of the list.
   else if (mImage->mDecoder &&
            !mImage->mError &&
            !mImage->mPendingError &&
            !mImage->IsDecodeFinished() &&
-           bytesDecoded < maxBytes &&
+           bytesDecoded < mRequest->mBytesToDecode &&
            bytesDecoded > 0) {
     DecodePool::Singleton()->RequestDecode(mImage);
   } else {
     // Nothing more for us to do - let everyone know what happened.
-    DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage,
-                                                 mImage->mDecodeStatusTracker);
+    DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage, mRequest);
   }
 
   return NS_OK;
 }
 
 RasterImage::DecodePool::DecodeJob::~DecodeJob()
 {
   if (gfxPrefs::ImageMTDecodingEnabled()) {
@@ -3344,22 +3355,24 @@ RasterImage::DecodePool::DecodeJob::~Dec
 }
 
 nsresult
 RasterImage::DecodePool::DecodeUntilSizeAvailable(RasterImage* aImg)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter lock(aImg->mDecodingMonitor);
 
-  // If the image is waiting for decode work to be notified, go ahead and do that.
-  if (aImg->mDecodeStatus == DecodeStatus::WORK_DONE) {
-    nsresult rv = aImg->FinishedSomeDecoding();
-    if (NS_FAILED(rv)) {
-      aImg->DoError();
-      return rv;
+  if (aImg->mDecodeRequest) {
+    // If the image is waiting for decode work to be notified, go ahead and do that.
+    if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
+      nsresult rv = aImg->FinishedSomeDecoding();
+      if (NS_FAILED(rv)) {
+        aImg->DoError();
+        return rv;
+      }
     }
   }
 
   // We use DECODE_ASYNC here because we just want to get the size information
   // here and defer the rest of the work.
   nsresult rv = DecodeSomeOfImage(aImg, DECODE_ASYNC, DECODE_TYPE_UNTIL_SIZE);
   if (NS_FAILED(rv)) {
     return rv;
@@ -3400,17 +3413,19 @@ RasterImage::DecodePool::DecodeSomeOfIma
   // example, a synchronous decode request came while the worker was pending).
   if (!aImg->mDecoder || aImg->mDecoded)
     return NS_OK;
 
   // If we're doing synchronous decodes, and we're waiting on a new frame for
   // this image, get it now.
   if (aStrategy == DECODE_SYNC && aImg->mDecoder->NeedsNewFrame()) {
     MOZ_ASSERT(NS_IsMainThread());
+
     aImg->mDecoder->AllocateFrame();
+    aImg->mDecodeRequest->mAllocatedNewFrame = true;
   }
 
   // If we're not synchronous, we can't allocate a frame right now.
   else if (aImg->mDecoder->NeedsNewFrame()) {
     return NS_OK;
   }
 
   nsRefPtr<Decoder> decoderKungFuDeathGrip = aImg->mDecoder;
@@ -3426,47 +3441,54 @@ RasterImage::DecodePool::DecodeSomeOfIma
     // to read the size from most images.
     maxBytes = gfxPrefs::ImageMemDecodeBytesAtATime();
   }
 
   if (bytesToDecode == 0) {
     bytesToDecode = aImg->mSourceData.Length() - aImg->mDecoder->BytesDecoded();
   }
 
-  TimeStamp deadline = TimeStamp::Now() +
-                       TimeDuration::FromMilliseconds(gfxPrefs::ImageMemMaxMSBeforeYield());
+  int32_t chunkCount = 0;
+  TimeStamp start = TimeStamp::Now();
+  TimeStamp deadline = start + TimeDuration::FromMilliseconds(gfxPrefs::ImageMemMaxMSBeforeYield());
 
   // We keep decoding chunks until:
   //  * we don't have any data left to decode,
   //  * the decode completes,
   //  * we're an UNTIL_SIZE decode and we get the size, or
   //  * we run out of time.
   // We also try to decode at least one "chunk" if we've allocated a new frame,
   // even if we have no more data to send to the decoder.
   while ((aImg->mSourceData.Length() > aImg->mDecoder->BytesDecoded() &&
           bytesToDecode > 0 &&
           !aImg->IsDecodeFinished() &&
           !(aDecodeType == DECODE_TYPE_UNTIL_SIZE && aImg->mHasSize) &&
           !aImg->mDecoder->NeedsNewFrame()) ||
-         aImg->mDecoder->NeedsToFlushData()) {
+         (aImg->mDecodeRequest && aImg->mDecodeRequest->mAllocatedNewFrame)) {
+    chunkCount++;
     uint32_t chunkSize = std::min(bytesToDecode, maxBytes);
     nsresult rv = aImg->DecodeSomeData(chunkSize, aStrategy);
     if (NS_FAILED(rv)) {
       aImg->DoError();
       return rv;
     }
 
     bytesToDecode -= chunkSize;
 
     // Yield if we've been decoding for too long. We check this _after_ decoding
     // a chunk to ensure that we don't yield without doing any decoding.
     if (aDecodeType == DECODE_TYPE_UNTIL_TIME && TimeStamp::Now() >= deadline)
       break;
   }
 
+  if (aImg->mDecodeRequest) {
+    aImg->mDecodeRequest->mDecodeTime += (TimeStamp::Now() - start);
+    aImg->mDecodeRequest->mChunkCount += chunkCount;
+  }
+
   // Flush invalidations (and therefore paint) now that we've decoded all the
   // chunks we're going to.
   //
   // However, don't paint if:
   //
   //  * This was an until-size decode.  Until-size decodes are always followed
   //    by normal decodes, so don't bother painting.
   //
@@ -3485,33 +3507,37 @@ RasterImage::DecodePool::DecodeSomeOfIma
     aImg->mInDecoder = true;
     aImg->mDecoder->FlushInvalidations();
     aImg->mInDecoder = false;
   }
 
   return NS_OK;
 }
 
+RasterImage::DecodeDoneWorker::DecodeDoneWorker(RasterImage* image, DecodeRequest* request)
+ : mImage(image)
+ , mRequest(request)
+{}
+
 void
-RasterImage::DecodeDoneWorker::NotifyFinishedSomeDecoding(RasterImage* aImage,
-                                                          imgStatusTracker* aTracker)
+RasterImage::DecodeDoneWorker::NotifyFinishedSomeDecoding(RasterImage* image, DecodeRequest* request)
 {
-  aImage->mDecodingMonitor.AssertCurrentThreadIn();
-
-  nsCOMPtr<nsIRunnable> worker = new DecodeDoneWorker(aImage, aTracker);
+  image->mDecodingMonitor.AssertCurrentThreadIn();
+
+  nsCOMPtr<nsIRunnable> worker = new DecodeDoneWorker(image, request);
   NS_DispatchToMainThread(worker);
 }
 
 NS_IMETHODIMP
 RasterImage::DecodeDoneWorker::Run()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
 
-  mImage->FinishedSomeDecoding(eShutdownIntent_Done, mTracker);
+  mImage->FinishedSomeDecoding(eShutdownIntent_Done, mRequest);
 
   return NS_OK;
 }
 
 RasterImage::FrameNeededWorker::FrameNeededWorker(RasterImage* image)
  : mImage(image)
 {}
 
@@ -3528,16 +3554,17 @@ RasterImage::FrameNeededWorker::Run()
 {
   ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
   nsresult rv = NS_OK;
 
   // If we got a synchronous decode in the mean time, we don't need to do
   // anything.
   if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) {
     rv = mImage->mDecoder->AllocateFrame();
+    mImage->mDecodeRequest->mAllocatedNewFrame = true;
   }
 
   if (NS_SUCCEEDED(rv) && mImage->mDecoder) {
     // By definition, we're not done decoding, so enqueue us for more decoding.
     DecodePool::Singleton()->RequestDecode(mImage);
   }
 
   return NS_OK;
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -28,17 +28,16 @@
 #include "DecodeStrategy.h"
 #include "DiscardTracker.h"
 #include "Orientation.h"
 #include "nsIObserver.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/TimeStamp.h"
-#include "mozilla/TypedEnum.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/UniquePtr.h"
 #ifdef DEBUG
   #include "imgIContainerDebug.h"
 #endif
 
 class nsIInputStream;
@@ -132,24 +131,16 @@ class Image;
 }
 
 namespace image {
 
 class Decoder;
 class FrameAnimator;
 class ScaleRunner;
 
-MOZ_BEGIN_ENUM_CLASS(DecodeStatus, uint8_t)
-  INACTIVE,
-  PENDING,
-  ACTIVE,
-  WORK_DONE,
-  STOPPED
-MOZ_END_ENUM_CLASS(DecodeStatus)
-
 class RasterImage MOZ_FINAL : public ImageResource
                             , public nsIProperties
                             , public SupportsWeakPtr<RasterImage>
 #ifdef DEBUG
                             , public imgIContainerDebug
 #endif
 {
   // (no public constructor - use ImageFactory)
@@ -317,22 +308,77 @@ public:
 
   // Decode strategy
 
 private:
   already_AddRefed<imgStatusTracker> CurrentStatusTracker()
   {
     mDecodingMonitor.AssertCurrentThreadIn();
     nsRefPtr<imgStatusTracker> statusTracker;
-    statusTracker = mDecodeStatusTracker ? mDecodeStatusTracker
-                                         : mStatusTracker;
+    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,
+      REQUEST_ACTIVE,
+      REQUEST_WORK_DONE,
+      REQUEST_STOPPED
+    } mRequestStatus;
+
+    /* Keeps track of how much time we've burned decoding this particular decode
+     * request. */
+    TimeDuration mDecodeTime;
+
+    /* The number of chunks it took to decode this image. */
+    int32_t mChunkCount;
+
+    /* True if a new frame has been allocated, but DecodeSomeData hasn't yet
+     * been called to flush data to it */
+    bool mAllocatedNewFrame;
+
+  private:
+    ~DecodeRequest() {}
+  };
+
   /*
    * DecodePool is a singleton class we use when decoding large images.
    *
    * When we wish to decode an image larger than
    * image.mem.max_bytes_for_sync_decode, we call DecodePool::RequestDecode()
    * for the image.  This adds the image to a queue of pending requests and posts
    * the DecodePool singleton to the event queue, if it's not already pending
    * there.
@@ -410,24 +456,28 @@ private:
                                DecodeType aDecodeType = DECODE_TYPE_UNTIL_TIME,
                                uint32_t bytesToDecode = 0);
 
     /* A decode job dispatched to a thread pool by DecodePool.
      */
     class DecodeJob : public nsRunnable
     {
     public:
-      DecodeJob(RasterImage* aImage) : mImage(aImage) { }
+      DecodeJob(DecodeRequest* aRequest, RasterImage* aImg)
+        : mRequest(aRequest)
+        , mImage(aImg)
+      {}
 
-      NS_IMETHOD Run() MOZ_OVERRIDE;
+      NS_IMETHOD Run();
 
     protected:
       virtual ~DecodeJob();
 
     private:
+      nsRefPtr<DecodeRequest> mRequest;
       nsRefPtr<RasterImage> mImage;
     };
 
   private: /* members */
 
     // mThreadPoolMutex protects mThreadPool. For all RasterImages R,
     // R::mDecodingMonitor must be acquired before mThreadPoolMutex
     // if both are acquired; the other order may cause deadlock.
@@ -440,31 +490,27 @@ private:
   public:
     /**
      * Called by the DecodePool with an image when it's done some significant
      * portion of decoding that needs to be notified about.
      *
      * Ensures the decode state accumulated by the decoding process gets
      * applied to the image.
      */
-    static void NotifyFinishedSomeDecoding(RasterImage* aImage,
-                                           imgStatusTracker* aTracker);
+    static void NotifyFinishedSomeDecoding(RasterImage* image, DecodeRequest* request);
 
     NS_IMETHOD Run();
 
   private: /* methods */
-    DecodeDoneWorker(RasterImage* aImage, imgStatusTracker* aTracker)
-      : mImage(aImage)
-      , mTracker(aTracker)
-    { }
+    DecodeDoneWorker(RasterImage* image, DecodeRequest* request);
 
   private: /* members */
 
     nsRefPtr<RasterImage> mImage;
-    nsRefPtr<imgStatusTracker> mTracker;
+    nsRefPtr<DecodeRequest> mRequest;
   };
 
   class FrameNeededWorker : public nsRunnable
   {
   public:
     /**
      * Called by the DecodeJob with an image when it's been told by the
      * decoder that it needs a new frame to be allocated on the main thread.
@@ -479,18 +525,18 @@ private:
   private: /* methods */
     explicit FrameNeededWorker(RasterImage* image);
 
   private: /* members */
 
     nsRefPtr<RasterImage> mImage;
   };
 
-  nsresult FinishedSomeDecoding(eShutdownIntent aIntent = eShutdownIntent_Done,
-                                imgStatusTracker* aDecodeTracker = nullptr);
+  nsresult FinishedSomeDecoding(eShutdownIntent intent = eShutdownIntent_Done,
+                                DecodeRequest* request = nullptr);
 
   void DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
                                     gfxContext* aContext,
                                     const nsIntSize& aSize,
                                     const ImageRegion& aRegion,
                                     GraphicsFilter aFilter,
                                     uint32_t aFlags);
 
@@ -604,18 +650,17 @@ private: // data
 
   // BEGIN LOCKED MEMBER VARIABLES
   ReentrantMonitor           mDecodingMonitor;
 
   FallibleTArray<char>       mSourceData;
 
   // Decoder and friends
   nsRefPtr<Decoder>          mDecoder;
-  nsRefPtr<imgStatusTracker> mDecodeStatusTracker;
-  DecodeStatus               mDecodeStatus;
+  nsRefPtr<DecodeRequest>    mDecodeRequest;
 
   bool                       mInDecoder;
   // END LOCKED MEMBER VARIABLES
 
   // Notification state. Used to avoid recursive notifications.
   ImageStatusDiff            mStatusDiff;
   bool                       mNotifying:1;