Bug 1118087 - Correctly sync decode even if an imgFrame is partially decoded. r=tn
☠☠ backed out by ddc853131dec ☠ ☠
authorSeth Fowler <seth@mozilla.com>
Fri, 09 Jan 2015 05:12:37 -0800
changeset 248826 710d6d8b8f1c9d827596c15e1e97b4f7680db428
parent 248825 f61a4592da851ede0e4b6d1dd9dcb7dac432d310
child 248827 bb2775fb362c04a626aeae82a6538c939e0f6533
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1118087
milestone37.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 1118087 - Correctly sync decode even if an imgFrame is partially decoded. r=tn
image/src/Decoder.cpp
image/src/FrameAnimator.cpp
image/src/RasterImage.cpp
image/src/imgFrame.cpp
image/src/imgFrame.h
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -230,31 +230,34 @@ Decoder::AllocateFrameInternal(uint32_t 
   }
 
   nsRefPtr<imgFrame> frame = new imgFrame();
   bool nonPremult =
     aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
   if (NS_FAILED(frame->InitForDecoder(imageSize, aFrameRect, aFormat,
                                       aPaletteDepth, nonPremult))) {
     NS_WARNING("imgFrame::Init should succeed");
+    frame->Abort();
     return RawAccessFrameRef();
   }
 
   RawAccessFrameRef ref = frame->RawAccessRef();
   if (!ref) {
+    frame->Abort();
     return RawAccessFrameRef();
   }
 
   bool succeeded =
     SurfaceCache::Insert(frame, ImageKey(&mImage),
                          RasterSurfaceKey(imageSize.ToIntSize(),
                                           aDecodeFlags,
                                           aFrameNum),
                          Lifetime::Persistent);
   if (!succeeded) {
+    ref->Abort();
     return RawAccessFrameRef();
   }
 
   nsIntRect refreshArea;
 
   if (aFrameNum == 1) {
     MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
     aPreviousFrame->SetRawAccessOnly();
@@ -388,24 +391,32 @@ Decoder::PostDecodeDone(int32_t aLoopCou
 
   mProgress |= FLAG_DECODE_COMPLETE;
 }
 
 void
 Decoder::PostDataError()
 {
   mDataError = true;
+
+  if (mInFrame && mCurrentFrame) {
+    mCurrentFrame->Abort();
+  }
 }
 
 void
 Decoder::PostDecoderError(nsresult aFailureCode)
 {
   NS_ABORT_IF_FALSE(NS_FAILED(aFailureCode), "Not a failure code!");
 
   mFailCode = aFailureCode;
 
   // XXXbholley - we should report the image URI here, but imgContainer
   // needs to know its URI first
   NS_WARNING("Image decoding error - This is probably a bug!");
+
+  if (mInFrame && mCurrentFrame) {
+    mCurrentFrame->Abort();
+  }
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/FrameAnimator.cpp
+++ b/image/src/FrameAnimator.cpp
@@ -84,17 +84,18 @@ FrameAnimator::AdvanceFrame(TimeStamp aT
   int32_t timeout = 0;
 
   RefreshResult ret;
   RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);
 
   // If we're done decoding, we know we've got everything we're going to get.
   // If we aren't, we only display fully-downloaded frames; everything else
   // gets delayed.
-  bool canDisplay = mDoneDecoding || (nextFrame && nextFrame->ImageComplete());
+  bool canDisplay = mDoneDecoding ||
+                    (nextFrame && nextFrame->IsImageComplete());
 
   if (!canDisplay) {
     // Uh oh, the frame we want to show is currently being decoded (partial)
     // Wait until the next refresh driver tick and try again
     return ret;
   }
 
   // If we're done decoding the next frame, go ahead and display it now and
@@ -590,32 +591,30 @@ FrameAnimator::DoBlend(nsIntRect* aDirty
 
     AnimationData compositingPrevFrameData =
       mCompositingPrevFrame->GetAnimationData();
 
     CopyFrameImage(compositingFrameData.mRawData,
                    compositingFrameData.mRect,
                    compositingPrevFrameData.mRawData,
                    compositingPrevFrameData.mRect);
+
+    mCompositingPrevFrame->Finish();
   }
 
   // blit next frame into it's correct spot
   DrawFrameTo(nextFrameData.mRawData, nextFrameData.mRect,
               nextFrameData.mPaletteDataLength,
               nextFrameData.mHasAlpha,
               compositingFrameData.mRawData,
               compositingFrameData.mRect,
               nextFrameData.mBlendMethod);
 
   // Tell the image that it is fully 'downloaded'.
-  nsresult rv =
-    mCompositingFrame->ImageUpdated(compositingFrameData.mRect);
-  if (NS_FAILED(rv)) {
-    return false;
-  }
+  mCompositingFrame->Finish();
 
   mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
 
   return true;
 }
 
 //******************************************************************************
 // Fill aFrame with black. Does also clears the mask.
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -199,17 +199,17 @@ public:
       bool succeeded =
         gfx::Scale(srcData.mRawData, srcData.mSize.width, srcData.mSize.height,
                    srcData.mBytesPerRow, dstData.mRawData, mDstSize.width,
                    mDstSize.height, dstData.mBytesPerRow, srcData.mFormat);
 
       if (succeeded) {
         // Mark the frame as complete and discardable.
         mDstRef->ImageUpdated(mDstRef->GetRect());
-        MOZ_ASSERT(mDstRef->ImageComplete(),
+        MOZ_ASSERT(mDstRef->IsImageComplete(),
                    "Incomplete, but just updated the entire frame");
       }
 
       // We need to send notifications and release our references on the main
       // thread, so finish up there.
       mState = succeeded ? eFinish : eFinishWithError;
       NS_DispatchToMainThread(this);
     } else if (mState == eFinish) {
@@ -546,31 +546,40 @@ RasterImage::LookupFrame(uint32_t aFrame
     MOZ_ASSERT(!mAnim, "Animated frames should be locked");
 
     // Update our state so the decoder knows what to do.
     mFrameDecodeFlags = aFlags & DECODE_FLAGS_MASK;
     mDecoded = false;
     mFrameCount = 0;
     WantDecodedFrames(aFlags, aShouldSyncNotify);
 
-    // See if we managed to redecode enough to get the frame we want.
+    // If we were able to sync decode, we should already have the frame. If we
+    // had to decode asynchronously, maybe we've gotten lucky.
     ref = LookupFrameInternal(aFrameNum, aSize, aFlags);
 
     if (!ref) {
       // We didn't successfully redecode, so just fail.
       return DrawableFrameRef();
     }
   }
 
   if (ref->GetCompositingFailed()) {
     return DrawableFrameRef();
   }
 
   MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "Should not have paletted frame");
 
+  // Sync decoding guarantees that we got the frame, but if it's owned by an
+  // async decoder that's currently running, the contents of the frame may not
+  // be available yet. Make sure we get everything.
+  if (ref && mHasSourceData && aShouldSyncNotify &&
+      (aFlags & FLAG_SYNC_DECODE)) {
+    ref->WaitUntilComplete();
+  }
+
   return ref;
 }
 
 uint32_t
 RasterImage::GetCurrentFrameIndex() const
 {
   if (mAnim) {
     return mAnim->GetCurrentAnimationFrameIndex();
@@ -1897,17 +1906,17 @@ RasterImage::NotifyNewScaledFrame()
 }
 
 void
 RasterImage::RequestScale(imgFrame* aFrame,
                           uint32_t aFlags,
                           const nsIntSize& aSize)
 {
   // We don't scale frames which aren't fully decoded.
-  if (!aFrame->ImageComplete()) {
+  if (!aFrame->IsImageComplete()) {
     return;
   }
 
   // We can't scale frames that need padding or are single pixel.
   if (aFrame->NeedsPadding() || aFrame->IsSinglePixel()) {
     return;
   }
 
@@ -1946,17 +1955,17 @@ RasterImage::DrawWithPreDownscaleIfNeede
                                             DecodeFlags(aFlags),
                                             0));
     if (!frameRef) {
       // We either didn't have a matching scaled frame or the OS threw it away.
       // Request a new one so we'll be ready next time. For now, we'll fall back
       // to aFrameRef below.
       RequestScale(aFrameRef.get(), aFlags, aSize);
     }
-    if (frameRef && !frameRef->ImageComplete()) {
+    if (frameRef && !frameRef->IsImageComplete()) {
       frameRef.reset();  // We're still scaling, so we can't use this yet.
     }
   }
 
   gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
   ImageRegion region(aRegion);
   if (!frameRef) {
     frameRef = Move(aFrameRef);
@@ -2453,17 +2462,17 @@ RasterImage::OptimalImageSizeForDest(con
 
   if (CanScale(aFilter, destSize, aFlags)) {
     DrawableFrameRef frameRef =
       SurfaceCache::Lookup(ImageKey(this),
                            RasterSurfaceKey(destSize.ToIntSize(),
                                             DecodeFlags(aFlags),
                                             0));
 
-    if (frameRef && frameRef->ImageComplete()) {
+    if (frameRef && frameRef->IsImageComplete()) {
         return destSize;  // We have an existing HQ scale for this size.
     }
     if (!frameRef) {
       // We could HQ scale to this size, but we haven't. Request a scale now.
       frameRef = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
                              mSize, aFlags);
       if (frameRef) {
         RequestScale(frameRef.get(), aFlags, destSize);
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -125,23 +125,24 @@ static bool AllowedImageAndFrameDimensio
   if (!imageRect.Contains(aFrameRect)) {
     return false;
   }
   return true;
 }
 
 
 imgFrame::imgFrame()
-  : mMutex("imgFrame")
+  : mMonitor("imgFrame")
   , mDecoded(0, 0, 0, 0)
   , mLockCount(0)
   , mTimeout(100)
   , mDisposalMethod(DisposalMethod::NOT_SPECIFIED)
   , mBlendMethod(BlendMethod::OVER)
   , mHasNoAlpha(false)
+  , mAborted(false)
   , mPalettedImageData(nullptr)
   , mPaletteDepth(0)
   , mNonPremult(false)
   , mSinglePixel(false)
   , mCompositingFailed(false)
   , mOptimizable(false)
 {
   static bool hasCheckedOptimize = false;
@@ -150,16 +151,21 @@ imgFrame::imgFrame()
       gDisableOptimize = true;
     }
     hasCheckedOptimize = true;
   }
 }
 
 imgFrame::~imgFrame()
 {
+#ifdef DEBUG
+  MonitorAutoLock lock(mMonitor);
+  MOZ_ASSERT(mAborted || IsImageCompleteInternal());
+#endif
+
   moz_free(mPalettedImageData);
   mPalettedImageData = nullptr;
 }
 
 nsresult
 imgFrame::InitForDecoder(const nsIntSize& aImageSize,
                          const nsIntRect& aRect,
                          SurfaceFormat aFormat,
@@ -296,23 +302,27 @@ imgFrame::InitWithDrawable(gfxDrawable* 
   }
 
   if (!canUseDataSurface) {
     // We used an offscreen surface, which is an "optimized" surface from
     // imgFrame's perspective.
     mOptSurface = target->Snapshot();
   }
 
+  // If we reach this point, we should regard ourselves as complete.
+  mDecoded = GetRect();
+  MOZ_ASSERT(IsImageComplete());
+
   return NS_OK;
 }
 
 nsresult imgFrame::Optimize()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mMutex.AssertCurrentThreadOwns();
+  mMonitor.AssertCurrentThreadOwns();
   MOZ_ASSERT(mLockCount == 1,
              "Should only optimize when holding the lock exclusively");
 
   // Don't optimize during shutdown because gfxPlatform may not be available.
   if (ShutdownTracker::ShutdownHasStarted())
     return NS_OK;
 
   if (!mOptimizable || gDisableOptimize)
@@ -450,17 +460,17 @@ imgFrame::SurfaceForDrawing(bool        
                             bool               aDoTile,
                             gfxContext*        aContext,
                             const nsIntMargin& aPadding,
                             gfxRect&           aImageRect,
                             ImageRegion&       aRegion,
                             SourceSurface*     aSurface)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mMutex.AssertCurrentThreadOwns();
+  mMonitor.AssertCurrentThreadOwns();
 
   IntSize size(int32_t(aImageRect.Width()), int32_t(aImageRect.Height()));
   if (!aDoPadding && !aDoPartialDecode) {
     NS_ASSERTION(!mSinglePixel, "This should already have been handled");
     return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, ThebesIntSize(size)), mFormat);
   }
 
   gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height);
@@ -511,25 +521,25 @@ bool imgFrame::Draw(gfxContext* aContext
 
   MOZ_ASSERT(NS_IsMainThread());
   NS_ASSERTION(!aRegion.Rect().IsEmpty(), "Drawing empty region!");
   NS_ASSERTION(!aRegion.IsRestricted() ||
                !aRegion.Rect().Intersect(aRegion.Restriction()).IsEmpty(),
                "We must be allowed to sample *some* source pixels!");
   NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!");
 
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
 
   nsIntMargin padding(mOffset.y,
                       mImageSize.width - (mOffset.x + mSize.width),
                       mImageSize.height - (mOffset.y + mSize.height),
                       mOffset.x);
 
   bool doPadding = padding != nsIntMargin(0,0,0,0);
-  bool doPartialDecode = !ImageCompleteInternal();
+  bool doPartialDecode = !IsImageCompleteInternal();
 
   if (mSinglePixel && !doPadding && !doPartialDecode) {
     if (mSinglePixelColor.a == 0.0) {
       return true;
     }
     RefPtr<DrawTarget> dt = aContext->GetDrawTarget();
     dt->FillRect(ToRect(aRegion.Rect()),
                  ColorPattern(mSinglePixelColor),
@@ -564,40 +574,47 @@ bool imgFrame::Draw(gfxContext* aContext
                                aFilter, aImageFlags);
   }
   return true;
 }
 
 nsresult
 imgFrame::ImageUpdated(const nsIntRect& aUpdateRect)
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
   return ImageUpdatedInternal(aUpdateRect);
 }
 
 nsresult
 imgFrame::ImageUpdatedInternal(const nsIntRect& aUpdateRect)
 {
-  mMutex.AssertCurrentThreadOwns();
+  mMonitor.AssertCurrentThreadOwns();
 
   mDecoded.UnionRect(mDecoded, aUpdateRect);
 
   // clamp to bounds, in case someone sends a bogus updateRect (I'm looking at
   // you, gif decoder)
   nsIntRect boundsRect(mOffset, nsIntSize(mSize.width, mSize.height));
   mDecoded.IntersectRect(mDecoded, boundsRect);
 
+  // If the image is now complete, wake up anyone who's waiting.
+  if (IsImageCompleteInternal()) {
+    mMonitor.NotifyAll();
+  }
+
   return NS_OK;
 }
 
 void
-imgFrame::Finish(Opacity aFrameOpacity, DisposalMethod aDisposalMethod,
-                 int32_t aRawTimeout, BlendMethod aBlendMethod)
+imgFrame::Finish(Opacity aFrameOpacity /* = Opacity::SOME_TRANSPARENCY */,
+                 DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */,
+                 int32_t aRawTimeout /* = 0 */,
+                 BlendMethod aBlendMethod /* = BlendMethod::OVER */)
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 
   if (aFrameOpacity == Opacity::OPAQUE) {
     mHasNoAlpha = true;
   }
 
   mDisposalMethod = aDisposalMethod;
   mTimeout = aRawTimeout;
@@ -608,34 +625,34 @@ imgFrame::Finish(Opacity aFrameOpacity, 
 nsIntRect imgFrame::GetRect() const
 {
   return nsIntRect(mOffset, nsIntSize(mSize.width, mSize.height));
 }
 
 int32_t
 imgFrame::GetStride() const
 {
-  mMutex.AssertCurrentThreadOwns();
+  mMonitor.AssertCurrentThreadOwns();
 
   if (mImageSurface) {
     return mImageSurface->Stride();
   }
 
   return VolatileSurfaceStride(mSize, mFormat);
 }
 
 SurfaceFormat imgFrame::GetFormat() const
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
   return mFormat;
 }
 
 uint32_t imgFrame::GetImageBytesPerRow() const
 {
-  mMutex.AssertCurrentThreadOwns();
+  mMonitor.AssertCurrentThreadOwns();
 
   if (mVBuf)
     return mSize.width * BytesPerPixel(mFormat);
 
   if (mPaletteDepth)
     return mSize.width;
 
   return 0;
@@ -644,24 +661,24 @@ uint32_t imgFrame::GetImageBytesPerRow()
 uint32_t imgFrame::GetImageDataLength() const
 {
   return GetImageBytesPerRow() * mSize.height;
 }
 
 void
 imgFrame::GetImageData(uint8_t** aData, uint32_t* aLength) const
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
   GetImageDataInternal(aData, aLength);
 }
 
 void
 imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const
 {
-  mMutex.AssertCurrentThreadOwns();
+  mMonitor.AssertCurrentThreadOwns();
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 
   if (mImageSurface) {
     *aData = mVBufPtr;
     MOZ_ASSERT(*aData, "mImageSurface is non-null, but mVBufPtr is null in GetImageData");
   } else if (mPalettedImageData) {
     *aData = mPalettedImageData + PaletteDataLength();
     MOZ_ASSERT(*aData, "mPalettedImageData is non-null, but result is null in GetImageData");
@@ -705,17 +722,17 @@ uint32_t* imgFrame::GetPaletteData() con
   uint32_t length;
   GetPaletteData(&data, &length);
   return data;
 }
 
 nsresult
 imgFrame::LockImageData()
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
 
   MOZ_ASSERT(mLockCount >= 0, "Unbalanced locks and unlocks");
   if (mLockCount < 0) {
     return NS_ERROR_FAILURE;
   }
 
   mLockCount++;
 
@@ -737,17 +754,17 @@ imgFrame::LockImageData()
 
   return Deoptimize();
 }
 
 nsresult
 imgFrame::Deoptimize()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mMutex.AssertCurrentThreadOwns();
+  mMonitor.AssertCurrentThreadOwns();
   MOZ_ASSERT(!mImageSurface);
 
   if (!mImageSurface) {
     if (mVBuf) {
       VolatileBufferPtr<uint8_t> ref(mVBuf);
       if (ref.WasBufferPurged())
         return NS_ERROR_FAILURE;
 
@@ -806,17 +823,17 @@ imgFrame::Deoptimize()
   mVBufPtr = mVBuf;
   return NS_OK;
 }
 
 void
 imgFrame::AssertImageDataLocked() const
 {
 #ifdef DEBUG
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 #endif
 }
 
 class UnlockImageDataRunnable : public nsRunnable
 {
 public:
   explicit UnlockImageDataRunnable(imgFrame* aTarget)
@@ -829,23 +846,26 @@ public:
 
 private:
   nsRefPtr<imgFrame> mTarget;
 };
 
 nsresult
 imgFrame::UnlockImageData()
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
 
   MOZ_ASSERT(mLockCount > 0, "Unlocking an unlocked image!");
   if (mLockCount <= 0) {
     return NS_ERROR_FAILURE;
   }
 
+  MOZ_ASSERT(mLockCount > 1 || IsImageCompleteInternal() || mAborted,
+             "Should have marked complete or aborted before unlocking");
+
   // If we're about to become unlocked, we don't need to hold on to our data
   // surface anymore. (But we don't need to do anything for paletted images,
   // which don't have surfaces.)
   if (mLockCount == 1 && !mPalettedImageData) {
     // We can't safely optimize off-main-thread, so create a runnable to do it.
     if (!NS_IsMainThread()) {
       nsCOMPtr<nsIRunnable> runnable = new UnlockImageDataRunnable(this);
       NS_DispatchToMainThread(runnable);
@@ -893,24 +913,24 @@ imgFrame::IsSinglePixel() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mSinglePixel;
 }
 
 TemporaryRef<SourceSurface>
 imgFrame::GetSurface()
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
   return GetSurfaceInternal();
 }
 
 TemporaryRef<SourceSurface>
 imgFrame::GetSurfaceInternal()
 {
-  mMutex.AssertCurrentThreadOwns();
+  mMonitor.AssertCurrentThreadOwns();
 
   if (mOptSurface) {
     if (mOptSurface->IsValid())
       return mOptSurface;
     else
       mOptSurface = nullptr;
   }
 
@@ -925,34 +945,34 @@ imgFrame::GetSurfaceInternal()
     return nullptr;
 
   return CreateLockedSurface(mVBuf, mSize, mFormat);
 }
 
 TemporaryRef<DrawTarget>
 imgFrame::GetDrawTarget()
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
 
   uint8_t* data;
   uint32_t length;
   GetImageDataInternal(&data, &length);
   if (!data) {
     return nullptr;
   }
 
   int32_t stride = GetStride();
   return gfxPlatform::GetPlatform()->
     CreateDrawTargetForData(data, mSize, stride, mFormat);
 }
 
 AnimationData
 imgFrame::GetAnimationData() const
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 
   uint8_t* data;
   if (mPalettedImageData) {
     data = mPalettedImageData;
   } else {
     uint32_t length;
     GetImageDataInternal(&data, &length);
@@ -962,38 +982,67 @@ imgFrame::GetAnimationData() const
 
   return AnimationData(data, PaletteDataLength(), mTimeout, GetRect(),
                        mBlendMethod, mDisposalMethod, hasAlpha);
 }
 
 ScalingData
 imgFrame::GetScalingData() const
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
   MOZ_ASSERT(!GetIsPaletted(), "GetScalingData can't handle paletted images");
 
   uint8_t* data;
   uint32_t length;
   GetImageDataInternal(&data, &length);
 
   return ScalingData(data, mSize, GetImageBytesPerRow(), mFormat);
 }
 
-bool
-imgFrame::ImageComplete() const
+void
+imgFrame::Abort()
 {
-  MutexAutoLock lock(mMutex);
-  return ImageCompleteInternal();
+  MonitorAutoLock lock(mMonitor);
+
+  mAborted = true;
+
+  // Wake up anyone who's waiting.
+  if (IsImageCompleteInternal()) {
+    mMonitor.NotifyAll();
+  }
 }
 
 bool
-imgFrame::ImageCompleteInternal() const
+imgFrame::IsImageComplete() const
+{
+  MonitorAutoLock lock(mMonitor);
+  return IsImageCompleteInternal();
+}
+
+void
+imgFrame::WaitUntilComplete() const
 {
-  mMutex.AssertCurrentThreadOwns();
+  MonitorAutoLock lock(mMonitor);
+
+  while (true) {
+    // Return if we're aborted or complete.
+    if (mAborted || IsImageCompleteInternal()) {
+      return;
+    }
+
+    // Not complete yet, so we'll have to wait.
+    mMonitor.Wait();
+  }
+}
+
+bool
+imgFrame::IsImageCompleteInternal() const
+{
+  mMonitor.AssertCurrentThreadOwns();
   return mDecoded.IsEqualInterior(nsIntRect(mOffset.x, mOffset.y,
                                             mSize.width, mSize.height));
 }
 
 bool imgFrame::GetCompositingFailed() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mCompositingFailed;
@@ -1004,17 +1053,17 @@ void imgFrame::SetCompositingFailed(bool
   MOZ_ASSERT(NS_IsMainThread());
   mCompositingFailed = val;
 }
 
 size_t
 imgFrame::SizeOfExcludingThis(gfxMemoryLocation aLocation,
                               MallocSizeOf aMallocSizeOf) const
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
 
   // aMallocSizeOf is only used if aLocation==gfxMemoryLocation::IN_PROCESS_HEAP.  It
   // should be nullptr otherwise.
   NS_ABORT_IF_FALSE(
     (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP &&  aMallocSizeOf) ||
     (aLocation != gfxMemoryLocation::IN_PROCESS_HEAP && !aMallocSizeOf),
     "mismatch between aLocation and aMallocSizeOf");
 
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -3,18 +3,18 @@
  * 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 imgFrame_h
 #define imgFrame_h
 
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/Monitor.h"
 #include "mozilla/Move.h"
-#include "mozilla/Mutex.h"
 #include "mozilla/TypedEnum.h"
 #include "mozilla/VolatileBuffer.h"
 #include "gfxDrawable.h"
 #include "imgIContainer.h"
 #include "MainThreadUtils.h"
 
 namespace mozilla {
 namespace image {
@@ -177,29 +177,58 @@ public:
   bool Draw(gfxContext* aContext, const ImageRegion& aRegion,
             GraphicsFilter aFilter, uint32_t aImageFlags);
 
   nsresult ImageUpdated(const nsIntRect &aUpdateRect);
 
   /**
    * Mark this imgFrame as completely decoded, and set final options.
    *
+   * You must always call either Finish() or Abort() before releasing the last
+   * RawAccessFrameRef pointing to an imgFrame.
+   *
    * @param aFrameOpacity    Whether this imgFrame is opaque.
    * @param aDisposalMethod  For animation frames, how this imgFrame is cleared
    *                         from the compositing frame before the next frame is
    *                         displayed.
    * @param aRawTimeout      For animation frames, the timeout in milliseconds
    *                         before the next frame is displayed. This timeout is
    *                         not necessarily the timeout that will actually be
    *                         used; see FrameAnimator::GetTimeoutForFrame.
    * @param aBlendMethod     For animation frames, a blending method to be used
    *                         when compositing this frame.
    */
-  void Finish(Opacity aFrameOpacity, DisposalMethod aDisposalMethod,
-              int32_t aRawTimeout, BlendMethod aBlendMethod);
+  void Finish(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
+              DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
+              int32_t aRawTimeout = 0,
+              BlendMethod aBlendMethod = BlendMethod::OVER);
+
+  /**
+   * Mark this imgFrame as aborted. This informs the imgFrame that if it isn't
+   * completely decoded now, it never will be.
+   *
+   * You must always call either Finish() or Abort() before releasing the last
+   * RawAccessFrameRef pointing to an imgFrame.
+   */
+  void Abort();
+
+  /**
+   * Returns true if this imgFrame is completely decoded.
+   */
+  bool IsImageComplete() const;
+
+  /**
+   * Blocks until this imgFrame is either completely decoded, or is marked as
+   * aborted.
+   *
+   * Note that calling this on the main thread _blocks the main thread_. Be very
+   * careful in your use of this method to avoid excessive main thread jank or
+   * deadlock.
+   */
+  void WaitUntilComplete() const;
 
   IntSize GetImageSize() { return mImageSize; }
   nsIntRect GetRect() const;
   IntSize GetSize() const { return mSize; }
   bool NeedsPadding() const { return mOffset != nsIntPoint(0, 0); }
   void GetImageData(uint8_t **aData, uint32_t *length) const;
   uint8_t* GetImageData() const;
 
@@ -213,18 +242,16 @@ public:
    *
    * This should only be used for assertions.
    */
   SurfaceFormat GetFormat() const;
 
   AnimationData GetAnimationData() const;
   ScalingData GetScalingData() const;
 
-  bool ImageComplete() const;
-
   bool GetCompositingFailed() const;
   void SetCompositingFailed(bool val);
 
   void SetOptimizable();
 
   Color SinglePixelColor() const;
   bool IsSinglePixel() const;
 
@@ -240,17 +267,17 @@ private: // methods
 
   nsresult LockImageData();
   nsresult UnlockImageData();
   nsresult Optimize();
   nsresult Deoptimize();
 
   void AssertImageDataLocked() const;
 
-  bool ImageCompleteInternal() const;
+  bool IsImageCompleteInternal() const;
   nsresult ImageUpdatedInternal(const nsIntRect& aUpdateRect);
   void GetImageDataInternal(uint8_t **aData, uint32_t *length) const;
   uint32_t GetImageBytesPerRow() const;
   uint32_t GetImageDataLength() const;
   int32_t GetStride() const;
   TemporaryRef<SourceSurface> GetSurfaceInternal();
 
   uint32_t PaletteDataLength() const
@@ -278,20 +305,20 @@ private: // methods
                                       SourceSurface*     aSurface);
 
 private: // data
   friend class DrawableFrameRef;
   friend class RawAccessFrameRef;
   friend class UnlockImageDataRunnable;
 
   //////////////////////////////////////////////////////////////////////////////
-  // Thread-safe mutable data, protected by mMutex.
+  // Thread-safe mutable data, protected by mMonitor.
   //////////////////////////////////////////////////////////////////////////////
 
-  mutable Mutex mMutex;
+  mutable Monitor mMonitor;
 
   RefPtr<DataSourceSurface> mImageSurface;
   RefPtr<SourceSurface> mOptSurface;
 
   RefPtr<VolatileBuffer> mVBuf;
   VolatileBufferPtr<uint8_t> mVBufPtr;
 
   nsIntRect mDecoded;
@@ -302,16 +329,17 @@ private: // data
   //! Raw timeout for this frame. (See FrameAnimator::GetTimeoutForFrame.)
   int32_t mTimeout; // -1 means display forever.
 
   DisposalMethod mDisposalMethod;
   BlendMethod    mBlendMethod;
   SurfaceFormat  mFormat;
 
   bool mHasNoAlpha;
+  bool mAborted;
 
 
   //////////////////////////////////////////////////////////////////////////////
   // Effectively const data, only mutated in the Init methods.
   //////////////////////////////////////////////////////////////////////////////
 
   IntSize      mImageSize;
   IntSize      mSize;