Bug 1118087 - Correctly sync decode even if an imgFrame is partially decoded. r=tn
authorSeth Fowler <seth@mozilla.com>
Sun, 11 Jan 2015 19:28:02 -0800
changeset 223242 de7cd37e48e8a718eff35ff63dfe1d7dd207ff3c
parent 223241 225fd7ea8fc6cb4e1f5e5f0fc21c43facfa4c419
child 223243 e82f640cb53fa9765f4fefcacdb6d351fe6cf887
push id10769
push usercbook@mozilla.com
push dateMon, 12 Jan 2015 14:15:52 +0000
treeherderfx-team@0e9765732906 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1118087
milestone37.0a1
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
@@ -307,22 +307,24 @@ Decoder::EnsureFrame(uint32_t aFrameNum,
     return RawAccessFrameRef();
   }
 
   MOZ_ASSERT(!aPreviousFrame->GetRect().IsEqualEdges(aFrameRect) ||
              aPreviousFrame->GetFormat() != aFormat ||
              aPreviousFrame->GetPaletteDepth() != aPaletteDepth,
              "Replacing first frame with the same kind of frame?");
 
-  // Remove the old frame from the SurfaceCache.
+  // Remove the old frame from the SurfaceCache and release our reference to it.
   IntSize prevFrameSize = aPreviousFrame->GetImageSize();
   SurfaceCache::RemoveSurface(ImageKey(&mImage),
                               RasterSurfaceKey(prevFrameSize, aDecodeFlags, 0));
   mFrameCount = 0;
   mInFrame = false;
+  mCurrentFrame->Abort();
+  mCurrentFrame = RawAccessFrameRef();
 
   // Add the new frame as usual.
   return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
                           aPaletteDepth, nullptr);
 }
 
 RawAccessFrameRef
 Decoder::InternalAddFrame(uint32_t aFrameNum,
@@ -356,26 +358,28 @@ Decoder::InternalAddFrame(uint32_t aFram
   if (NS_FAILED(frame->InitForDecoder(imageSize, aFrameRect, aFormat,
                                       aPaletteDepth, nonPremult))) {
     NS_WARNING("imgFrame::Init should succeed");
     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();
@@ -509,28 +513,36 @@ 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();
+  }
 }
 
 void
 Decoder::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 */)
 {
--- 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();
@@ -1906,17 +1915,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;
   }
 
@@ -1955,17 +1964,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);
@@ -2462,17 +2471,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,74 +151,83 @@ 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,
                          uint8_t aPaletteDepth /* = 0 */,
                          bool aNonPremult /* = false */)
 {
   // Assert for properties that should be verified by decoders,
   // warn for properties related to bad content.
   if (!AllowedImageAndFrameDimensions(aImageSize, aRect)) {
     NS_WARNING("Should have legal image size");
+    mAborted = true;
     return NS_ERROR_FAILURE;
   }
 
   mImageSize = aImageSize.ToIntSize();
   mOffset.MoveTo(aRect.x, aRect.y);
   mSize.SizeTo(aRect.width, aRect.height);
 
   mFormat = aFormat;
   mPaletteDepth = aPaletteDepth;
   mNonPremult = aNonPremult;
 
   if (aPaletteDepth != 0) {
     // We're creating for a paletted image.
     if (aPaletteDepth > 8) {
       NS_WARNING("Should have legal palette depth");
       NS_ERROR("This Depth is not supported");
+      mAborted = true;
       return NS_ERROR_FAILURE;
     }
 
     // Use the fallible allocator here. Paletted images always use 1 byte per
     // pixel, so calculating the amount of memory we need is straightforward.
     mPalettedImageData =
       static_cast<uint8_t*>(moz_malloc(PaletteDataLength() +
                                        (mSize.width * mSize.height)));
     if (!mPalettedImageData)
       NS_WARNING("moz_malloc for paletted image data should succeed");
     NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY);
   } else {
     MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitForDecoder() twice?");
 
     mVBuf = AllocateBufferForImage(mSize, mFormat);
     if (!mVBuf) {
+      mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
     if (mVBuf->OnHeap()) {
       int32_t stride = VolatileSurfaceStride(mSize, mFormat);
       VolatileBufferPtr<uint8_t> ptr(mVBuf);
       memset(ptr, 0, stride * mSize.height);
     }
     mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat);
 
     if (!mImageSurface) {
       NS_WARNING("Failed to create VolatileDataSourceSurface");
+      mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
   return NS_OK;
 }
 
 nsresult
@@ -226,16 +236,17 @@ imgFrame::InitWithDrawable(gfxDrawable* 
                            const SurfaceFormat aFormat,
                            GraphicsFilter aFilter,
                            uint32_t aImageFlags)
 {
   // Assert for properties that should be verified by decoders,
   // warn for properties related to bad content.
   if (!AllowedImageSize(aSize.width, aSize.height)) {
     NS_WARNING("Should have legal image size");
+    mAborted = true;
     return NS_ERROR_FAILURE;
   }
 
   mImageSize = aSize.ToIntSize();
   mOffset.MoveTo(0, 0);
   mSize.SizeTo(aSize.width, aSize.height);
 
   mFormat = aFormat;
@@ -248,22 +259,24 @@ imgFrame::InitWithDrawable(gfxDrawable* 
 
   if (canUseDataSurface) {
     // It's safe to use data surfaces for content on this platform, so we can
     // get away with using volatile buffers.
     MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitWithDrawable() twice?");
 
     mVBuf = AllocateBufferForImage(mSize, mFormat);
     if (!mVBuf) {
+      mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     int32_t stride = VolatileSurfaceStride(mSize, mFormat);
     VolatileBufferPtr<uint8_t> ptr(mVBuf);
     if (!ptr) {
+      mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
     if (mVBuf->OnHeap()) {
       memset(ptr, 0, stride * mSize.height);
     }
     mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat);
 
     target = gfxPlatform::GetPlatform()->
@@ -275,44 +288,50 @@ imgFrame::InitWithDrawable(gfxDrawable* 
     // the documentation for this method.
     MOZ_ASSERT(!mOptSurface, "Called imgFrame::InitWithDrawable() twice?");
 
     target = gfxPlatform::GetPlatform()->
         CreateOffscreenContentDrawTarget(mSize, mFormat);
   }
 
   if (!target) {
+    mAborted = true;
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   // Draw using the drawable the caller provided.
   nsIntRect imageRect(0, 0, mSize.width, mSize.height);
   nsRefPtr<gfxContext> ctx = new gfxContext(target);
   gfxUtils::DrawPixelSnapped(ctx, aDrawable, ThebesIntSize(mSize),
                              ImageRegion::Create(imageRect),
                              mFormat, aFilter, aImageFlags);
 
   if (canUseDataSurface && !mImageSurface) {
     NS_WARNING("Failed to create VolatileDataSourceSurface");
+    mAborted = true;
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   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 +469,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 +530,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 +583,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 +634,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 +670,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 +731,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 +763,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 +832,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 +855,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 +922,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 +954,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 +991,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 +1062,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;