Bug 1116733 (Part 1) - Allocate frames off-main-thread. r=tn
authorSeth Fowler <seth@mozilla.com>
Thu, 08 Jan 2015 00:04:31 -0800
changeset 222612 c96ef32cd8a5852e04e7e3924dd52cd48c709433
parent 222611 0933c1aef19729d4163d61f8962f11c473a26103
child 222613 b4cdc04f65550567f05b881bc4a89224a751b8c9
push id53671
push usermfowler@mozilla.com
push dateThu, 08 Jan 2015 08:04:43 +0000
treeherdermozilla-inbound@b4cdc04f6555 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1116733
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 1116733 (Part 1) - Allocate frames off-main-thread. r=tn
image/decoders/nsGIFDecoder2.cpp
image/decoders/nsPNGDecoder.cpp
image/src/Decoder.cpp
image/src/FrameAnimator.cpp
image/src/RasterImage.cpp
image/src/SurfaceCache.cpp
image/src/imgFrame.cpp
image/src/imgFrame.h
testing/web-platform/meta/2dcontext/drawing-images-to-the-canvas/2d.drawImage.broken.html.ini
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -194,22 +194,16 @@ nsGIFDecoder2::BeginImageFrame(uint16_t 
       // We need padding on the first frame, which means that we don't draw into
       // part of the image at all. Report that as transparency.
       PostHasTransparency();
 
       // Regardless of depth of input, image is decoded into 24bit RGB
       NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset,
                    mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height,
                    format);
-    } else {
-      // Our preallocated frame matches up, with the possible exception
-      // of alpha.
-      if (format == gfx::SurfaceFormat::B8G8R8X8) {
-        currentFrame->SetHasNoAlpha();
-      }
     }
   }
 
   mCurrentFrameIndex = mGIFStruct.images_decoded;
 }
 
 
 //******************************************************************************
@@ -228,18 +222,22 @@ nsGIFDecoder2::EndImageFrame()
     // This will clear the remaining bits of the placeholder. (Bug 37589)
     const uint32_t realFrameHeight = mGIFStruct.height + mGIFStruct.y_offset;
     if (realFrameHeight < mGIFStruct.screen_height) {
       nsIntRect r(0, realFrameHeight,
                   mGIFStruct.screen_width,
                   mGIFStruct.screen_height - realFrameHeight);
       PostInvalidation(r);
     }
-    // This transparency check is only valid for first frame
-    if (mGIFStruct.is_transparent && !mSawTransparency) {
+
+    // The first frame was preallocated with alpha; if it wasn't transparent, we
+    // should fix that. We can also mark it opaque unconditionally if we didn't
+    // actually see any transparent pixels - this test is only valid for the
+    // first frame.
+    if (!mGIFStruct.is_transparent || !mSawTransparency) {
       opacity = Opacity::OPAQUE;
     }
   }
   mCurrentRow = mLastFlushedRow = -1;
   mCurrentPass = mLastFlushedPass = 0;
 
   // Only add frame if we have any rows at all
   if (mGIFStruct.rows_remaining != mGIFStruct.height) {
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -160,21 +160,16 @@ void nsPNGDecoder::CreateFrame(png_uint_
       // We need padding on the first frame, which means that we don't draw into
       // part of the image at all. Report that as transparency.
       PostHasTransparency();
     }
 
     NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
   } else if (mNumFrames != 0) {
     NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
-  } else {
-    // Our preallocated frame matches up, with the possible exception of alpha.
-    if (format == gfx::SurfaceFormat::B8G8R8X8) {
-      currentFrame->SetHasNoAlpha();
-    }
   }
 
   mFrameRect = neededRect;
   mFrameHasNoAlpha = true;
 
   PR_LOG(GetPNGDecoderAccountingLog(), PR_LOG_DEBUG,
          ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
           "image frame with %dx%d pixels in container %p",
@@ -199,21 +194,19 @@ void
 nsPNGDecoder::EndImageFrame()
 {
   if (mFrameIsHidden) {
     return;
   }
 
   mNumFrames++;
 
-  Opacity opacity;
-  if (mFrameHasNoAlpha) {
+  Opacity opacity = Opacity::SOME_TRANSPARENCY;
+  if (format == gfx::SurfaceFormat::B8G8R8X8 || mFrameHasNoAlpha) {
     opacity = Opacity::OPAQUE;
-  } else {
-    opacity = Opacity::SOME_TRANSPARENCY;
   }
 
 #ifdef PNG_APNG_SUPPORTED
   uint32_t numFrames = GetFrameCount();
 
   // We can't use mPNG->num_frames_read as it may be one ahead.
   if (numFrames > 1) {
     PostInvalidation(mFrameRect);
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -129,29 +129,28 @@ Decoder::Write(const char* aBuffer, uint
   if (IsSizeDecode() && HasSize()) {
     // More data came in since we found the size. We have nothing to do here.
     return;
   }
 
   // Pass the data along to the implementation
   WriteInternal(aBuffer, aCount, aStrategy);
 
-  // If we're a synchronous decoder and we need a new frame to proceed, let's
-  // create one and call it again.
-  if (aStrategy == DecodeStrategy::SYNC) {
-    while (NeedsNewFrame() && !HasDataError()) {
-      nsresult rv = AllocateFrame();
+  // If we need a new frame to proceed, let's create one and call it again.
+  while (NeedsNewFrame() && !HasDataError()) {
+    MOZ_ASSERT(!IsSizeDecode(), "Shouldn't need new frame for size decode");
+
+    nsresult rv = AllocateFrame();
 
-      if (NS_SUCCEEDED(rv)) {
-        // Use the data we saved when we asked for a new frame.
-        WriteInternal(nullptr, 0, aStrategy);
-      }
+    if (NS_SUCCEEDED(rv)) {
+      // Use the data we saved when we asked for a new frame.
+      WriteInternal(nullptr, 0, aStrategy);
+    }
 
-      mNeedsToFlushData = false;
-    }
+    mNeedsToFlushData = false;
   }
 
   // Finish telemetry.
   mDecodeTime += (TimeStamp::Now() - start);
 }
 
 void
 Decoder::Finish(ShutdownReason aReason)
@@ -232,17 +231,16 @@ Decoder::FinishSharedDecoder()
     FinishInternal();
   }
 }
 
 nsresult
 Decoder::AllocateFrame()
 {
   MOZ_ASSERT(mNeedsNewFrame);
-  MOZ_ASSERT(NS_IsMainThread());
 
   mCurrentFrame = EnsureFrame(mNewFrameData.mFrameNum,
                               mNewFrameData.mFrameRect,
                               mDecodeFlags,
                               mNewFrameData.mFormat,
                               mNewFrameData.mPaletteDepth,
                               mCurrentFrame.get());
 
@@ -274,18 +272,16 @@ Decoder::AllocateFrame()
 RawAccessFrameRef
 Decoder::EnsureFrame(uint32_t aFrameNum,
                      const nsIntRect& aFrameRect,
                      uint32_t aDecodeFlags,
                      SurfaceFormat aFormat,
                      uint8_t aPaletteDepth,
                      imgFrame* aPreviousFrame)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
   if (mDataError || NS_FAILED(mFailCode)) {
     return RawAccessFrameRef();
   }
 
   MOZ_ASSERT(aFrameNum <= mFrameCount, "Invalid frame index!");
   if (aFrameNum > mFrameCount) {
     return RawAccessFrameRef();
   }
@@ -347,23 +343,23 @@ Decoder::InternalAddFrame(uint32_t aFram
   }
 
   if (!SurfaceCache::CanHold(imageSize.ToIntSize())) {
     NS_WARNING("Trying to add frame that's too large for the SurfaceCache");
     return RawAccessFrameRef();
   }
 
   nsRefPtr<imgFrame> frame = new imgFrame();
+  bool nonPremult =
+    aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
   if (NS_FAILED(frame->InitForDecoder(imageSize, aFrameRect, aFormat,
-                                      aPaletteDepth))) {
+                                      aPaletteDepth, nonPremult))) {
     NS_WARNING("imgFrame::Init should succeed");
     return RawAccessFrameRef();
   }
-  frame->SetAsNonPremult(aDecodeFlags &
-                         imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA);
 
   RawAccessFrameRef ref = frame->RawAccessRef();
   if (!ref) {
     return RawAccessFrameRef();
   }
 
   bool succeeded =
     SurfaceCache::Insert(frame, ImageKey(&mImage),
@@ -379,21 +375,21 @@ Decoder::InternalAddFrame(uint32_t aFram
 
   if (aFrameNum == 1) {
     MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
     aPreviousFrame->SetRawAccessOnly();
 
     // If we dispose of the first frame by clearing it, then the first frame's
     // refresh area is all of itself.
     // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
-    DisposalMethod disposalMethod = aPreviousFrame->GetDisposalMethod();
-    if (disposalMethod == DisposalMethod::CLEAR ||
-        disposalMethod == DisposalMethod::CLEAR_ALL ||
-        disposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
-      refreshArea = aPreviousFrame->GetRect();
+    AnimationData previousFrameData = aPreviousFrame->GetAnimationData();
+    if (previousFrameData.mDisposalMethod == DisposalMethod::CLEAR ||
+        previousFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL ||
+        previousFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
+      refreshArea = previousFrameData.mRect;
     }
   }
 
   if (aFrameNum > 0) {
     ref->SetRawAccessOnly();
 
     // Some GIFs are huge but only have a small area that they animate. We only
     // need to refresh that small area when frame 0 comes around again.
@@ -476,24 +472,17 @@ Decoder::PostFrameStop(Opacity aFrameOpa
   // We should be mid-frame
   MOZ_ASSERT(!IsSizeDecode(), "Stopping frame during a size decode");
   MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
   MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
 
   // Update our state
   mInFrame = false;
 
-  if (aFrameOpacity == Opacity::OPAQUE) {
-    mCurrentFrame->SetHasNoAlpha();
-  }
-
-  mCurrentFrame->SetDisposalMethod(aDisposalMethod);
-  mCurrentFrame->SetRawTimeout(aTimeout);
-  mCurrentFrame->SetBlendMethod(aBlendMethod);
-  mCurrentFrame->ImageUpdated(mCurrentFrame->GetRect());
+  mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod);
 
   mProgress |= FLAG_FRAME_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
 }
 
 void
 Decoder::PostInvalidation(nsIntRect& aRect)
 {
   // We should be mid-frame
--- a/image/src/FrameAnimator.cpp
+++ b/image/src/FrameAnimator.cpp
@@ -284,36 +284,36 @@ FrameAnimator::GetCompositedFrame(uint32
   MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "About to return a paletted frame");
   return ref;
 }
 
 int32_t
 FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
 {
   RawAccessFrameRef frame = GetRawFrame(aFrameNum);
-  const int32_t timeout = frame->GetRawTimeout();
+  AnimationData data = frame->GetAnimationData();
 
   // Ensure a minimal time between updates so we don't throttle the UI thread.
   // consider 0 == unspecified and make it fast but not too fast.  Unless we
   // have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug
   // 207059. The behavior of recent IE and Opera versions seems to be:
   // IE 6/Win:
   //   10 - 50ms go 100ms
   //   >50ms go correct speed
   // Opera 7 final/Win:
   //   10ms goes 100ms
   //   >10ms go correct speed
   // It seems that there are broken tools out there that set a 0ms or 10ms
   // timeout when they really want a "default" one.  So munge values in that
   // range.
-  if (timeout >= 0 && timeout <= 10 && mLoopCount != 0) {
+  if (data.mRawTimeout >= 0 && data.mRawTimeout <= 10 && mLoopCount != 0) {
     return 100;
   }
 
-  return timeout;
+  return data.mRawTimeout;
 }
 
 size_t
 FrameAnimator::SizeOfCompositingFrames(gfxMemoryLocation aLocation,
                                        MallocSizeOf aMallocSizeOf) const
 {
   size_t n = 0;
 
@@ -347,80 +347,80 @@ FrameAnimator::DoBlend(nsIntRect* aDirty
                        uint32_t aPrevFrameIndex,
                        uint32_t aNextFrameIndex)
 {
   RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex);
   RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex);
 
   MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");
 
-  DisposalMethod prevFrameDisposalMethod = prevFrame->GetDisposalMethod();
-  if (prevFrameDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
+  AnimationData prevFrameData = prevFrame->GetAnimationData();
+  if (prevFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
       !mCompositingPrevFrame) {
-    prevFrameDisposalMethod = DisposalMethod::CLEAR;
+    prevFrameData.mDisposalMethod = DisposalMethod::CLEAR;
   }
 
-  nsIntRect prevFrameRect = prevFrame->GetRect();
-  bool isFullPrevFrame = (prevFrameRect.x == 0 && prevFrameRect.y == 0 &&
-                          prevFrameRect.width == mSize.width &&
-                          prevFrameRect.height == mSize.height);
+  bool isFullPrevFrame = prevFrameData.mRect.x == 0 &&
+                         prevFrameData.mRect.y == 0 &&
+                         prevFrameData.mRect.width == mSize.width &&
+                         prevFrameData.mRect.height == mSize.height;
 
   // Optimization: DisposeClearAll if the previous frame is the same size as
   //               container and it's clearing itself
   if (isFullPrevFrame &&
-      (prevFrameDisposalMethod == DisposalMethod::CLEAR)) {
-    prevFrameDisposalMethod = DisposalMethod::CLEAR_ALL;
+      (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR)) {
+    prevFrameData.mDisposalMethod = DisposalMethod::CLEAR_ALL;
   }
 
-  DisposalMethod nextFrameDisposalMethod = nextFrame->GetDisposalMethod();
-  nsIntRect nextFrameRect = nextFrame->GetRect();
-  bool isFullNextFrame = (nextFrameRect.x == 0 && nextFrameRect.y == 0 &&
-                          nextFrameRect.width == mSize.width &&
-                          nextFrameRect.height == mSize.height);
+  AnimationData nextFrameData = nextFrame->GetAnimationData();
+  bool isFullNextFrame = nextFrameData.mRect.x == 0 &&
+                         nextFrameData.mRect.y == 0 &&
+                         nextFrameData.mRect.width == mSize.width &&
+                         nextFrameData.mRect.height == mSize.height;
 
   if (!nextFrame->GetIsPaletted()) {
     // Optimization: Skip compositing if the previous frame wants to clear the
     //               whole image
-    if (prevFrameDisposalMethod == DisposalMethod::CLEAR_ALL) {
+    if (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL) {
       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
       return true;
     }
 
     // Optimization: Skip compositing if this frame is the same size as the
     //               container and it's fully drawing over prev frame (no alpha)
     if (isFullNextFrame &&
-        (nextFrameDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
-        !nextFrame->GetHasAlpha()) {
+        (nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
+        !nextFrameData.mHasAlpha) {
       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
       return true;
     }
   }
 
   // Calculate area that needs updating
-  switch (prevFrameDisposalMethod) {
+  switch (prevFrameData.mDisposalMethod) {
     default:
     case DisposalMethod::NOT_SPECIFIED:
     case DisposalMethod::KEEP:
-      *aDirtyRect = nextFrameRect;
+      *aDirtyRect = nextFrameData.mRect;
       break;
 
     case DisposalMethod::CLEAR_ALL:
       // Whole image container is cleared
       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
       break;
 
     case DisposalMethod::CLEAR:
       // Calc area that needs to be redrawn (the combination of previous and
       // this frame)
       // XXX - This could be done with multiple framechanged calls
       //       Having prevFrame way at the top of the image, and nextFrame
       //       way at the bottom, and both frames being small, we'd be
       //       telling framechanged to refresh the whole image when only two
       //       small areas are needed.
-      aDirtyRect->UnionRect(nextFrameRect, prevFrameRect);
+      aDirtyRect->UnionRect(nextFrameData.mRect, prevFrameData.mRect);
       break;
 
     case DisposalMethod::RESTORE_PREVIOUS:
       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
       break;
   }
 
   // Optimization:
@@ -448,168 +448,171 @@ FrameAnimator::DoBlend(nsIntRect* aDirty
     needToBlankComposite = true;
   } else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex+1) {
 
     // If we are not drawing on top of last composited frame,
     // then we are building a new composite frame, so let's clear it first.
     needToBlankComposite = true;
   }
 
+  AnimationData compositingFrameData = mCompositingFrame->GetAnimationData();
+
   // More optimizations possible when next frame is not transparent
   // But if the next frame has DisposalMethod::RESTORE_PREVIOUS,
   // this "no disposal" optimization is not possible,
   // because the frame in "after disposal operation" state
   // needs to be stored in compositingFrame, so it can be
   // copied into compositingPrevFrame later.
   bool doDisposal = true;
-  if (!nextFrame->GetHasAlpha() &&
-      nextFrameDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
+  if (!nextFrameData.mHasAlpha &&
+      nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
     if (isFullNextFrame) {
       // Optimization: No need to dispose prev.frame when
       // next frame is full frame and not transparent.
       doDisposal = false;
       // No need to blank the composite frame
       needToBlankComposite = false;
     } else {
-      if ((prevFrameRect.x >= nextFrameRect.x) &&
-          (prevFrameRect.y >= nextFrameRect.y) &&
-          (prevFrameRect.x + prevFrameRect.width <=
-              nextFrameRect.x + nextFrameRect.width) &&
-          (prevFrameRect.y + prevFrameRect.height <=
-              nextFrameRect.y + nextFrameRect.height)) {
+      if ((prevFrameData.mRect.x >= nextFrameData.mRect.x) &&
+          (prevFrameData.mRect.y >= nextFrameData.mRect.y) &&
+          (prevFrameData.mRect.x + prevFrameData.mRect.width <=
+              nextFrameData.mRect.x + nextFrameData.mRect.width) &&
+          (prevFrameData.mRect.y + prevFrameData.mRect.height <=
+              nextFrameData.mRect.y + nextFrameData.mRect.height)) {
         // Optimization: No need to dispose prev.frame when
         // next frame fully overlaps previous frame.
         doDisposal = false;
       }
     }
   }
 
   if (doDisposal) {
     // Dispose of previous: clear, restore, or keep (copy)
-    switch (prevFrameDisposalMethod) {
+    switch (prevFrameData.mDisposalMethod) {
       case DisposalMethod::CLEAR:
         if (needToBlankComposite) {
           // If we just created the composite, it could have anything in its
           // buffer. Clear whole frame
-          ClearFrame(mCompositingFrame->GetRawData(),
-                     mCompositingFrame->GetRect());
+          ClearFrame(compositingFrameData.mRawData,
+                     compositingFrameData.mRect);
         } else {
           // Only blank out previous frame area (both color & Mask/Alpha)
-          ClearFrame(mCompositingFrame->GetRawData(),
-                     mCompositingFrame->GetRect(),
-                     prevFrameRect);
+          ClearFrame(compositingFrameData.mRawData,
+                     compositingFrameData.mRect,
+                     prevFrameData.mRect);
         }
         break;
 
       case DisposalMethod::CLEAR_ALL:
-        ClearFrame(mCompositingFrame->GetRawData(),
-                   mCompositingFrame->GetRect());
+        ClearFrame(compositingFrameData.mRawData,
+                   compositingFrameData.mRect);
         break;
 
       case DisposalMethod::RESTORE_PREVIOUS:
         // It would be better to copy only the area changed back to
         // compositingFrame.
         if (mCompositingPrevFrame) {
-          CopyFrameImage(mCompositingPrevFrame->GetRawData(),
-                         mCompositingPrevFrame->GetRect(),
-                         mCompositingFrame->GetRawData(),
-                         mCompositingFrame->GetRect());
+          AnimationData compositingPrevFrameData =
+            mCompositingPrevFrame->GetAnimationData();
+
+          CopyFrameImage(compositingPrevFrameData.mRawData,
+                         compositingPrevFrameData.mRect,
+                         compositingFrameData.mRawData,
+                         compositingFrameData.mRect);
 
           // destroy only if we don't need it for this frame's disposal
-          if (nextFrameDisposalMethod !=
+          if (nextFrameData.mDisposalMethod !=
               DisposalMethod::RESTORE_PREVIOUS) {
             mCompositingPrevFrame.reset();
           }
         } else {
-          ClearFrame(mCompositingFrame->GetRawData(),
-                     mCompositingFrame->GetRect());
+          ClearFrame(compositingFrameData.mRawData,
+                     compositingFrameData.mRect);
         }
         break;
 
       default:
         // Copy previous frame into compositingFrame before we put the new
         // frame on top
         // Assumes that the previous frame represents a full frame (it could be
         // smaller in size than the container, as long as the frame before it
         // erased itself)
         // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
         // will always be a valid frame number.
         if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
           if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
             // Just copy the bits
-            CopyFrameImage(prevFrame->GetRawData(),
-                           prevFrame->GetRect(),
-                           mCompositingFrame->GetRawData(),
-                           mCompositingFrame->GetRect());
+            CopyFrameImage(prevFrameData.mRawData,
+                           prevFrameData.mRect,
+                           compositingFrameData.mRawData,
+                           compositingFrameData.mRect);
           } else {
             if (needToBlankComposite) {
               // Only blank composite when prev is transparent or not full.
-              if (prevFrame->GetHasAlpha() || !isFullPrevFrame) {
-                ClearFrame(mCompositingFrame->GetRawData(),
-                           mCompositingFrame->GetRect());
+              if (prevFrameData.mHasAlpha || !isFullPrevFrame) {
+                ClearFrame(compositingFrameData.mRawData,
+                           compositingFrameData.mRect);
               }
             }
-            DrawFrameTo(prevFrame->GetRawData(), prevFrameRect,
-                        prevFrame->PaletteDataLength(),
-                        prevFrame->GetHasAlpha(),
-                        mCompositingFrame->GetRawData(),
-                        mCompositingFrame->GetRect(),
-                        prevFrame->GetBlendMethod());
+            DrawFrameTo(prevFrameData.mRawData, prevFrameData.mRect,
+                        prevFrameData.mPaletteDataLength,
+                        prevFrameData.mHasAlpha,
+                        compositingFrameData.mRawData,
+                        compositingFrameData.mRect,
+                        prevFrameData.mBlendMethod);
           }
         }
     }
   } else if (needToBlankComposite) {
     // If we just created the composite, it could have anything in its
     // buffers. Clear them
-    ClearFrame(mCompositingFrame->GetRawData(),
-               mCompositingFrame->GetRect());
+    ClearFrame(compositingFrameData.mRawData,
+               compositingFrameData.mRect);
   }
 
   // Check if the frame we are composing wants the previous image restored after
   // it is done. Don't store it (again) if last frame wanted its image restored
   // too
-  if ((nextFrameDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
-      (prevFrameDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
+  if ((nextFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
+      (prevFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
     // We are storing the whole image.
     // It would be better if we just stored the area that nextFrame is going to
     // overwrite.
     if (!mCompositingPrevFrame) {
       nsRefPtr<imgFrame> newFrame = new imgFrame;
       nsresult rv = newFrame->InitForDecoder(ThebesIntSize(mSize),
                                              SurfaceFormat::B8G8R8A8);
       if (NS_FAILED(rv)) {
         mCompositingPrevFrame.reset();
         return false;
       }
 
       mCompositingPrevFrame = newFrame->RawAccessRef();
     }
 
-    CopyFrameImage(mCompositingFrame->GetRawData(),
-                   mCompositingFrame->GetRect(),
-                   mCompositingPrevFrame->GetRawData(),
-                   mCompositingPrevFrame->GetRect());
+    AnimationData compositingPrevFrameData =
+      mCompositingPrevFrame->GetAnimationData();
+
+    CopyFrameImage(compositingFrameData.mRawData,
+                   compositingFrameData.mRect,
+                   compositingPrevFrameData.mRawData,
+                   compositingPrevFrameData.mRect);
   }
 
   // blit next frame into it's correct spot
-  DrawFrameTo(nextFrame->GetRawData(), nextFrameRect,
-              nextFrame->PaletteDataLength(),
-              nextFrame->GetHasAlpha(),
-              mCompositingFrame->GetRawData(),
-              mCompositingFrame->GetRect(),
-              nextFrame->GetBlendMethod());
-
-  // Set timeout of CompositeFrame to timeout of frame we just composed
-  // Bug 177948
-  int32_t timeout = nextFrame->GetRawTimeout();
-  mCompositingFrame->SetRawTimeout(timeout);
+  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(mCompositingFrame->GetRect());
+    mCompositingFrame->ImageUpdated(compositingFrameData.mRect);
   if (NS_FAILED(rv)) {
     return false;
   }
 
   mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
 
   return true;
 }
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -194,28 +194,24 @@ public:
 
     return true;
   }
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     if (mState == eReady) {
       // Collect information from the frames that we need to scale.
-      uint8_t* srcData = mSrcRef->GetImageData();
-      IntSize srcSize = mSrcRef->GetSize();
-      uint32_t srcStride = mSrcRef->GetImageBytesPerRow();
-      uint8_t* dstData = mDstRef->GetImageData();
-      uint32_t dstStride = mDstRef->GetImageBytesPerRow();
-      SurfaceFormat srcFormat = mSrcRef->GetFormat();
+      ScalingData srcData = mSrcRef->GetScalingData();
+      ScalingData dstData = mDstRef->GetScalingData();
 
       // Actually do the scaling.
       bool succeeded =
-        gfx::Scale(srcData, srcSize.width, srcSize.height, srcStride,
-                   dstData, mDstSize.width, mDstSize.height, dstStride,
-                   srcFormat);
+        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(),
                    "Incomplete, but just updated the entire frame");
       }
 
@@ -652,16 +648,23 @@ RasterImage::IsOpaque()
 
   // Other, we're opaque if FLAG_HAS_TRANSPARENCY is not set.
   return !(progress & FLAG_HAS_TRANSPARENCY);
 }
 
 void
 RasterImage::OnSurfaceDiscarded()
 {
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewRunnableMethod(this, &RasterImage::OnSurfaceDiscarded);
+    NS_DispatchToMainThread(runnable);
+    return;
+  }
+
   if (mProgressTracker) {
     mProgressTracker->OnDiscard();
   }
 }
 
 //******************************************************************************
 /* readonly attribute boolean animated; */
 NS_IMETHODIMP
@@ -925,21 +928,52 @@ RasterImage::SizeOfDecoded(gfxMemoryLoca
   size_t n = 0;
   n += SurfaceCache::SizeOfSurfaces(ImageKey(this), aLocation, aMallocSizeOf);
   if (mAnim) {
     n += mAnim->SizeOfCompositingFrames(aLocation, aMallocSizeOf);
   }
   return n;
 }
 
+class OnAddedFrameRunnable : public nsRunnable
+{
+public:
+  OnAddedFrameRunnable(RasterImage* aImage,
+                       uint32_t aNewFrameCount,
+                       const nsIntRect& aNewRefreshArea)
+    : mImage(aImage)
+    , mNewFrameCount(aNewFrameCount)
+    , mNewRefreshArea(aNewRefreshArea)
+  {
+    MOZ_ASSERT(aImage);
+  }
+
+  NS_IMETHOD Run()
+  {
+    mImage->OnAddedFrame(mNewFrameCount, mNewRefreshArea);
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<RasterImage> mImage;
+  uint32_t mNewFrameCount;
+  nsIntRect mNewRefreshArea;
+};
+
 void
 RasterImage::OnAddedFrame(uint32_t aNewFrameCount,
                           const nsIntRect& aNewRefreshArea)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> runnable =
+      new OnAddedFrameRunnable(this, aNewFrameCount, aNewRefreshArea);
+    NS_DispatchToMainThread(runnable);
+    return;
+  }
+
   MOZ_ASSERT((mFrameCount == 1 && aNewFrameCount == 1) ||
              mFrameCount < aNewFrameCount,
              "Frame count running backwards");
 
   mFrameCount = aNewFrameCount;
 
   if (aNewFrameCount == 2) {
     // We're becoming animated, so initialize animation stuff.
--- a/image/src/SurfaceCache.cpp
+++ b/image/src/SurfaceCache.cpp
@@ -10,16 +10,17 @@
 #include "SurfaceCache.h"
 
 #include <algorithm>
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"  // for MOZ_THIS_IN_INITIALIZER_LIST
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Move.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/StaticPtr.h"
 #include "nsIMemoryReporter.h"
 #include "gfx2DGlue.h"
 #include "gfxPattern.h"  // Workaround for flaw in bug 921753 part 2.
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "imgFrame.h"
@@ -114,17 +115,18 @@ private:
 /**
  * A CachedSurface associates a surface with a key that uniquely identifies that
  * surface.
  */
 class CachedSurface
 {
   ~CachedSurface() {}
 public:
-  NS_INLINE_DECL_REFCOUNTING(CachedSurface)
+  MOZ_DECLARE_REFCOUNTED_TYPENAME(CachedSurface)
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CachedSurface)
 
   CachedSurface(imgFrame*          aSurface,
                 const Cost         aCost,
                 const ImageKey     aImageKey,
                 const SurfaceKey&  aSurfaceKey,
                 const Lifetime     aLifetime)
     : mSurface(aSurface)
     , mCost(aCost)
@@ -210,17 +212,18 @@ private:
  * or unlocked.
  */
 class ImageSurfaceCache
 {
   ~ImageSurfaceCache() { }
 public:
   ImageSurfaceCache() : mLocked(false) { }
 
-  NS_INLINE_DECL_REFCOUNTING(ImageSurfaceCache)
+  MOZ_DECLARE_REFCOUNTED_TYPENAME(ImageSurfaceCache)
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageSurfaceCache)
 
   typedef nsRefPtrHashtable<nsGenericHashKey<SurfaceKey>, CachedSurface> SurfaceTable;
 
   bool IsEmpty() const { return mSurfaces.Count() == 0; }
   
   void Insert(const SurfaceKey& aKey, CachedSurface* aSurface)
   {
     MOZ_ASSERT(aSurface, "Should have a surface");
@@ -272,16 +275,17 @@ public:
   NS_DECL_ISUPPORTS
 
   SurfaceCacheImpl(uint32_t aSurfaceCacheExpirationTimeMS,
                    uint32_t aSurfaceCacheDiscardFactor,
                    uint32_t aSurfaceCacheSize)
     : mExpirationTracker(MOZ_THIS_IN_INITIALIZER_LIST(),
                          aSurfaceCacheExpirationTimeMS)
     , mMemoryPressureObserver(new MemoryPressureObserver)
+    , mMutex("SurfaceCache")
     , mDiscardFactor(aSurfaceCacheDiscardFactor)
     , mMaxCost(aSurfaceCacheSize)
     , mAvailableCost(aSurfaceCacheSize)
     , mLockedCost(0)
   {
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     if (os)
       os->AddObserver(mMemoryPressureObserver, "memory-pressure", false);
@@ -293,19 +297,19 @@ private:
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     if (os)
       os->RemoveObserver(mMemoryPressureObserver, "memory-pressure");
 
     UnregisterWeakMemoryReporter(this);
   }
 
 public:
-  void InitMemoryReporter() {
-    RegisterWeakMemoryReporter(this);
-  }
+  void InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
+
+  Mutex& GetMutex() { return mMutex; }
 
   bool Insert(imgFrame*         aSurface,
               const Cost        aCost,
               const ImageKey    aImageKey,
               const SurfaceKey& aSurfaceKey,
               Lifetime          aLifetime)
   {
     MOZ_ASSERT(!Lookup(aImageKey, aSurfaceKey),
@@ -700,16 +704,17 @@ private:
     virtual ~MemoryPressureObserver() { }
   };
 
 
   nsTArray<CostEntry>                                       mCosts;
   nsRefPtrHashtable<nsPtrHashKey<Image>, ImageSurfaceCache> mImageCaches;
   SurfaceTracker                                            mExpirationTracker;
   nsRefPtr<MemoryPressureObserver>                          mMemoryPressureObserver;
+  Mutex                                                     mMutex;
   const uint32_t                                            mDiscardFactor;
   const Cost                                                mMaxCost;
   Cost                                                      mAvailableCost;
   Cost                                                      mLockedCost;
 };
 
 NS_IMPL_ISUPPORTS(SurfaceCacheImpl, nsIMemoryReporter)
 NS_IMPL_ISUPPORTS(SurfaceCacheImpl::MemoryPressureObserver, nsIObserver)
@@ -717,16 +722,17 @@ NS_IMPL_ISUPPORTS(SurfaceCacheImpl::Memo
 ///////////////////////////////////////////////////////////////////////////////
 // Public API
 ///////////////////////////////////////////////////////////////////////////////
 
 /* static */ void
 SurfaceCache::Initialize()
 {
   // Initialize preferences.
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!sInstance, "Shouldn't initialize more than once");
 
   // See gfxPrefs for the default values of these preferences.
 
   // Length of time before an unused surface is removed from the cache, in
   // milliseconds.
   uint32_t surfaceCacheExpirationTimeMS =
     gfxPrefs::ImageMemSurfaceCacheMinExpirationMS();
@@ -770,112 +776,112 @@ SurfaceCache::Initialize()
                                    surfaceCacheDiscardFactor,
                                    finalSurfaceCacheSizeBytes);
   sInstance->InitMemoryReporter();
 }
 
 /* static */ void
 SurfaceCache::Shutdown()
 {
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sInstance, "No singleton - was Shutdown() called twice?");
   sInstance = nullptr;
 }
 
 /* static */ DrawableFrameRef
 SurfaceCache::Lookup(const ImageKey    aImageKey,
                      const SurfaceKey& aSurfaceKey)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (!sInstance) {
     return DrawableFrameRef();
   }
 
+  MutexAutoLock lock(sInstance->GetMutex());
   return sInstance->Lookup(aImageKey, aSurfaceKey);
 }
 
 /* static */ bool
 SurfaceCache::Insert(imgFrame*         aSurface,
                      const ImageKey    aImageKey,
                      const SurfaceKey& aSurfaceKey,
                      Lifetime          aLifetime)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (!sInstance) {
     return false;
   }
 
+  MutexAutoLock lock(sInstance->GetMutex());
   Cost cost = ComputeCost(aSurfaceKey.Size());
   return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey, aLifetime);
 }
 
 /* static */ bool
 SurfaceCache::CanHold(const IntSize& aSize)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (!sInstance) {
     return false;
   }
 
   Cost cost = ComputeCost(aSize);
   return sInstance->CanHold(cost);
 }
 
 /* static */ void
 SurfaceCache::LockImage(Image* aImageKey)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
+    MutexAutoLock lock(sInstance->GetMutex());
     return sInstance->LockImage(aImageKey);
   }
 }
 
 /* static */ void
 SurfaceCache::UnlockImage(Image* aImageKey)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
+    MutexAutoLock lock(sInstance->GetMutex());
     return sInstance->UnlockImage(aImageKey);
   }
 }
 
 /* static */ void
 SurfaceCache::RemoveSurface(const ImageKey    aImageKey,
                             const SurfaceKey& aSurfaceKey)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
+    MutexAutoLock lock(sInstance->GetMutex());
     sInstance->RemoveSurface(aImageKey, aSurfaceKey);
   }
 }
 
 /* static */ void
 SurfaceCache::RemoveImage(Image* aImageKey)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
+    MutexAutoLock lock(sInstance->GetMutex());
     sInstance->RemoveImage(aImageKey);
   }
 }
 
 /* static */ void
 SurfaceCache::DiscardAll()
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
+    MutexAutoLock lock(sInstance->GetMutex());
     sInstance->DiscardAll();
   }
 }
 
 /* static */ size_t
 SurfaceCache::SizeOfSurfaces(const ImageKey    aImageKey,
                              gfxMemoryLocation aLocation,
                              MallocSizeOf      aMallocSizeOf)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   if (!sInstance) {
     return 0;
   }
 
+  MutexAutoLock lock(sInstance->GetMutex());
   return sInstance->SizeOfSurfaces(aImageKey, aLocation, aMallocSizeOf);
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -17,16 +17,17 @@
 
 static bool gDisableOptimize = false;
 
 #include "GeckoProfiler.h"
 #include "mozilla/Likely.h"
 #include "MainThreadUtils.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsMargin.h"
+#include "nsThreadUtils.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/gfx/Tools.h"
 
 
 namespace mozilla {
 
 using namespace gfx;
 
@@ -123,29 +124,30 @@ static bool AllowedImageAndFrameDimensio
   nsIntRect imageRect(0, 0, aImageSize.width, aImageSize.height);
   if (!imageRect.Contains(aFrameRect)) {
     return false;
   }
   return true;
 }
 
 
-imgFrame::imgFrame() :
-  mDecoded(0, 0, 0, 0),
-  mDecodedMutex("imgFrame::mDecoded"),
-  mPalettedImageData(nullptr),
-  mTimeout(100),
-  mLockCount(0),
-  mDisposalMethod(DisposalMethod::NOT_SPECIFIED),
-  mBlendMethod(BlendMethod::OVER),
-  mSinglePixel(false),
-  mCompositingFailed(false),
-  mHasNoAlpha(false),
-  mNonPremult(false),
-  mOptimizable(false)
+imgFrame::imgFrame()
+  : mMutex("imgFrame")
+  , mDecoded(0, 0, 0, 0)
+  , mLockCount(0)
+  , mTimeout(100)
+  , mDisposalMethod(DisposalMethod::NOT_SPECIFIED)
+  , mBlendMethod(BlendMethod::OVER)
+  , mHasNoAlpha(false)
+  , mPalettedImageData(nullptr)
+  , mPaletteDepth(0)
+  , mNonPremult(false)
+  , mSinglePixel(false)
+  , mCompositingFailed(false)
+  , mOptimizable(false)
 {
   static bool hasCheckedOptimize = false;
   if (!hasCheckedOptimize) {
     if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) {
       gDisableOptimize = true;
     }
     hasCheckedOptimize = true;
   }
@@ -156,42 +158,47 @@ imgFrame::~imgFrame()
   moz_free(mPalettedImageData);
   mPalettedImageData = nullptr;
 }
 
 nsresult
 imgFrame::InitForDecoder(const nsIntSize& aImageSize,
                          const nsIntRect& aRect,
                          SurfaceFormat aFormat,
-                         uint8_t aPaletteDepth /* = 0 */)
+                         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");
     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");
       return NS_ERROR_FAILURE;
     }
 
-    // Use the fallible allocator here
-    mPalettedImageData = (uint8_t*)moz_malloc(PaletteDataLength() + GetImageDataLength());
+    // 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) {
@@ -295,16 +302,17 @@ imgFrame::InitWithDrawable(gfxDrawable* 
   }
 
   return NS_OK;
 }
 
 nsresult imgFrame::Optimize()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  mMutex.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)
@@ -354,17 +362,18 @@ nsresult imgFrame::Optimize()
 
     // if it's not RGB24/ARGB32, don't optimize, but we never hit this at the moment
   }
 
 #ifdef ANDROID
   SurfaceFormat optFormat =
     gfxPlatform::GetPlatform()->Optimal2DFormatForContent(gfxContentType::COLOR);
 
-  if (!GetHasAlpha() && optFormat == SurfaceFormat::R5G6B5) {
+  if (mFormat != SurfaceFormat::B8G8R8A8 &&
+      optFormat == SurfaceFormat::R5G6B5) {
     RefPtr<VolatileBuffer> buf =
       AllocateBufferForImage(mSize, optFormat);
     if (!buf)
       return NS_OK;
 
     RefPtr<DataSourceSurface> surf =
       CreateLockedSurface(buf, mSize, optFormat);
     if (!surf)
@@ -423,32 +432,36 @@ RawAccessFrameRef
 imgFrame::RawAccessRef()
 {
   return RawAccessFrameRef(this);
 }
 
 void
 imgFrame::SetRawAccessOnly()
 {
-  MOZ_ASSERT(mLockCount > 0, "Must hold a RawAccessFrameRef");
+  AssertImageDataLocked();
+
   // Lock our data and throw away the key.
   LockImageData();
 }
 
 
 imgFrame::SurfaceWithFormat
 imgFrame::SurfaceForDrawing(bool               aDoPadding,
                             bool               aDoPartialDecode,
                             bool               aDoTile,
                             gfxContext*        aContext,
                             const nsIntMargin& aPadding,
                             gfxRect&           aImageRect,
                             ImageRegion&       aRegion,
                             SourceSurface*     aSurface)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+  mMutex.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);
 
@@ -491,43 +504,46 @@ imgFrame::SurfaceForDrawing(bool        
 }
 
 bool imgFrame::Draw(gfxContext* aContext, const ImageRegion& aRegion,
                     GraphicsFilter aFilter, uint32_t aImageFlags)
 {
   PROFILER_LABEL("imgFrame", "Draw",
     js::ProfileEntry::Category::GRAPHICS);
 
+  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);
+
   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 = !ImageComplete();
+  bool doPartialDecode = !ImageCompleteInternal();
 
   if (mSinglePixel && !doPadding && !doPartialDecode) {
     if (mSinglePixelColor.a == 0.0) {
       return true;
     }
     RefPtr<DrawTarget> dt = aContext->GetDrawTarget();
     dt->FillRect(ToRect(aRegion.Rect()),
                  ColorPattern(mSinglePixelColor),
                  DrawOptions(1.0f,
                              CompositionOpForOp(aContext->CurrentOperator())));
     return true;
   }
 
-  RefPtr<SourceSurface> surf = GetSurface();
+  RefPtr<SourceSurface> surf = GetSurfaceInternal();
   if (!surf && !mSinglePixel) {
     return false;
   }
 
   gfxRect imageRect(0, 0, mImageSize.width, mImageSize.height);
   bool doTile = !imageRect.Contains(aRegion.Rect()) &&
                 !(aImageFlags & imgIContainer::FLAG_CLAMP);
   ImageRegion region(aRegion);
@@ -545,102 +561,139 @@ bool imgFrame::Draw(gfxContext* aContext
   if (surfaceResult.IsValid()) {
     gfxUtils::DrawPixelSnapped(aContext, surfaceResult.mDrawable,
                                imageRect.Size(), region, surfaceResult.mFormat,
                                aFilter, aImageFlags);
   }
   return true;
 }
 
-// This can be called from any thread, but not simultaneously.
-nsresult imgFrame::ImageUpdated(const nsIntRect &aUpdateRect)
+nsresult
+imgFrame::ImageUpdated(const nsIntRect& aUpdateRect)
 {
-  MutexAutoLock lock(mDecodedMutex);
+  MutexAutoLock lock(mMutex);
+  return ImageUpdatedInternal(aUpdateRect);
+}
+
+nsresult
+imgFrame::ImageUpdatedInternal(const nsIntRect& aUpdateRect)
+{
+  mMutex.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);
 
   return NS_OK;
 }
 
+void
+imgFrame::Finish(Opacity aFrameOpacity, DisposalMethod aDisposalMethod,
+                 int32_t aRawTimeout, BlendMethod aBlendMethod)
+{
+  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
+
+  if (aFrameOpacity == Opacity::OPAQUE) {
+    mHasNoAlpha = true;
+  }
+
+  mDisposalMethod = aDisposalMethod;
+  mTimeout = aRawTimeout;
+  mBlendMethod = aBlendMethod;
+  ImageUpdatedInternal(GetRect());
+}
+
 nsIntRect imgFrame::GetRect() const
 {
   return nsIntRect(mOffset, nsIntSize(mSize.width, mSize.height));
 }
 
 int32_t
 imgFrame::GetStride() const
 {
+  mMutex.AssertCurrentThreadOwns();
+
   if (mImageSurface) {
     return mImageSurface->Stride();
   }
 
   return VolatileSurfaceStride(mSize, mFormat);
 }
 
 SurfaceFormat imgFrame::GetFormat() const
 {
+  MutexAutoLock lock(mMutex);
   return mFormat;
 }
 
 uint32_t imgFrame::GetImageBytesPerRow() const
 {
+  mMutex.AssertCurrentThreadOwns();
+
   if (mVBuf)
     return mSize.width * BytesPerPixel(mFormat);
 
   if (mPaletteDepth)
     return mSize.width;
 
   return 0;
 }
 
 uint32_t imgFrame::GetImageDataLength() const
 {
   return GetImageBytesPerRow() * mSize.height;
 }
 
-void imgFrame::GetImageData(uint8_t **aData, uint32_t *length) const
+void
+imgFrame::GetImageData(uint8_t** aData, uint32_t* aLength) const
 {
-  NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetImageData unless frame is locked");
+  MutexAutoLock lock(mMutex);
+  GetImageDataInternal(aData, aLength);
+}
 
-  if (mImageSurface)
+void
+imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const
+{
+  mMutex.AssertCurrentThreadOwns();
+  MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
+
+  if (mImageSurface) {
     *aData = mVBufPtr;
-  else if (mPalettedImageData)
+    MOZ_ASSERT(*aData, "mImageSurface is non-null, but mVBufPtr is null in GetImageData");
+  } else if (mPalettedImageData) {
     *aData = mPalettedImageData + PaletteDataLength();
-  else
+    MOZ_ASSERT(*aData, "mPalettedImageData is non-null, but result is null in GetImageData");
+  } else {
+    MOZ_ASSERT(false, "Have neither mImageSurface nor mPalettedImageData in GetImageData");
     *aData = nullptr;
+  }
 
-  *length = GetImageDataLength();
+  *aLength = GetImageDataLength();
 }
 
 uint8_t* imgFrame::GetImageData() const
 {
   uint8_t *data;
   uint32_t length;
   GetImageData(&data, &length);
   return data;
 }
 
 bool imgFrame::GetIsPaletted() const
 {
   return mPalettedImageData != nullptr;
 }
 
-bool imgFrame::GetHasAlpha() const
-{
-  return mFormat == SurfaceFormat::B8G8R8A8;
-}
-
 void imgFrame::GetPaletteData(uint32_t **aPalette, uint32_t *length) const
 {
-  NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetPaletteData unless frame is locked");
+  AssertImageDataLocked();
 
   if (!mPalettedImageData) {
     *aPalette = nullptr;
     *length = 0;
   } else {
     *aPalette = (uint32_t *) mPalettedImageData;
     *length = PaletteDataLength();
   }
@@ -649,45 +702,53 @@ void imgFrame::GetPaletteData(uint32_t *
 uint32_t* imgFrame::GetPaletteData() const
 {
   uint32_t* data;
   uint32_t length;
   GetPaletteData(&data, &length);
   return data;
 }
 
-uint8_t*
-imgFrame::GetRawData() const
+nsresult
+imgFrame::LockImageData()
 {
-  MOZ_ASSERT(mLockCount, "Should be locked to call GetRawData()");
-  if (mPalettedImageData) {
-    return mPalettedImageData;
-  }
-  return GetImageData();
-}
+  MutexAutoLock lock(mMutex);
 
-nsresult imgFrame::LockImageData()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks");
+  MOZ_ASSERT(mLockCount >= 0, "Unbalanced locks and unlocks");
   if (mLockCount < 0) {
     return NS_ERROR_FAILURE;
   }
 
   mLockCount++;
 
   // If we are not the first lock, there's nothing to do.
   if (mLockCount != 1) {
     return NS_OK;
   }
 
+  // If we're the first lock, but have an image surface, we're OK.
+  if (mImageSurface) {
+    mVBufPtr = mVBuf;
+    return NS_OK;
+  }
+
   // Paletted images don't have surfaces, so there's nothing to do.
-  if (mPalettedImageData)
+  if (mPalettedImageData) {
     return NS_OK;
+  }
+
+  return Deoptimize();
+}
+
+nsresult
+imgFrame::Deoptimize()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mMutex.AssertCurrentThreadOwns();
+  MOZ_ASSERT(!mImageSurface);
 
   if (!mImageSurface) {
     if (mVBuf) {
       VolatileBufferPtr<uint8_t> ref(mVBuf);
       if (ref.WasBufferPurged())
         return NS_ERROR_FAILURE;
 
       mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat);
@@ -741,29 +802,61 @@ nsresult imgFrame::LockImageData()
       mOptSurface = nullptr;
     }
   }
 
   mVBufPtr = mVBuf;
   return NS_OK;
 }
 
-nsresult imgFrame::UnlockImageData()
+void
+imgFrame::AssertImageDataLocked() const
+{
+#ifdef DEBUG
+  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
+#endif
+}
+
+class UnlockImageDataRunnable : public nsRunnable
 {
-  MOZ_ASSERT(NS_IsMainThread());
+public:
+  UnlockImageDataRunnable(imgFrame* aTarget)
+    : mTarget(aTarget)
+  {
+    MOZ_ASSERT(mTarget);
+  }
+
+  NS_IMETHOD Run() { return mTarget->UnlockImageData(); }
+
+private:
+  nsRefPtr<imgFrame> mTarget;
+};
+
+nsresult
+imgFrame::UnlockImageData()
+{
+  MutexAutoLock lock(mMutex);
 
   MOZ_ASSERT(mLockCount > 0, "Unlocking an unlocked image!");
   if (mLockCount <= 0) {
     return NS_ERROR_FAILURE;
   }
 
   // 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);
+      return NS_OK;
+    }
+
     // If we're using a surface format with alpha but the image has no alpha,
     // change the format. This doesn't change the underlying data at all, but
     // allows DrawTargets to avoid blending when drawing known opaque images.
     if (mHasNoAlpha && mFormat == SurfaceFormat::B8G8R8A8 && mImageSurface) {
       mFormat = SurfaceFormat::B8G8R8X8;
       mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat);
     }
 
@@ -778,23 +871,47 @@ nsresult imgFrame::UnlockImageData()
   mLockCount--;
 
   return NS_OK;
 }
 
 void
 imgFrame::SetOptimizable()
 {
-  MOZ_ASSERT(mLockCount, "Expected to be locked when SetOptimizable is called");
+  MOZ_ASSERT(NS_IsMainThread());
+  AssertImageDataLocked();
   mOptimizable = true;
 }
 
+Color
+imgFrame::SinglePixelColor() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mSinglePixelColor;
+}
+
+bool
+imgFrame::IsSinglePixel() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mSinglePixel;
+}
+
 TemporaryRef<SourceSurface>
 imgFrame::GetSurface()
 {
+  MutexAutoLock lock(mMutex);
+  return GetSurfaceInternal();
+}
+
+TemporaryRef<SourceSurface>
+imgFrame::GetSurfaceInternal()
+{
+  mMutex.AssertCurrentThreadOwns();
+
   if (mOptSurface) {
     if (mOptSurface->IsValid())
       return mOptSurface;
     else
       mOptSurface = nullptr;
   }
 
   if (mImageSurface)
@@ -808,74 +925,97 @@ imgFrame::GetSurface()
     return nullptr;
 
   return CreateLockedSurface(mVBuf, mSize, mFormat);
 }
 
 TemporaryRef<DrawTarget>
 imgFrame::GetDrawTarget()
 {
-  MOZ_ASSERT(mLockCount >= 1, "Should lock before requesting a DrawTarget");
+  MutexAutoLock lock(mMutex);
 
-  uint8_t* data = GetImageData();
+  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);
 }
 
-int32_t imgFrame::GetRawTimeout() const
+AnimationData
+imgFrame::GetAnimationData() const
 {
-  return mTimeout;
+  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
+
+  uint8_t* data;
+  if (mPalettedImageData) {
+    data = mPalettedImageData;
+  } else {
+    uint32_t length;
+    GetImageDataInternal(&data, &length);
+  }
+
+  bool hasAlpha = mFormat == SurfaceFormat::B8G8R8A8;
+
+  return AnimationData(data, PaletteDataLength(), mTimeout, GetRect(),
+                       mBlendMethod, mDisposalMethod, hasAlpha);
 }
 
-void imgFrame::SetRawTimeout(int32_t aTimeout)
+ScalingData
+imgFrame::GetScalingData() const
 {
-  mTimeout = aTimeout;
+  MutexAutoLock lock(mMutex);
+  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);
 }
 
-// This can be called from any thread.
-bool imgFrame::ImageComplete() const
+bool
+imgFrame::ImageComplete() const
 {
-  MutexAutoLock lock(mDecodedMutex);
+  MutexAutoLock lock(mMutex);
+  return ImageCompleteInternal();
+}
 
+bool
+imgFrame::ImageCompleteInternal() const
+{
+  mMutex.AssertCurrentThreadOwns();
   return mDecoded.IsEqualInterior(nsIntRect(mOffset.x, mOffset.y,
                                             mSize.width, mSize.height));
 }
 
-// A hint from the image decoders that this image has no alpha, even
-// though we're decoding it as B8G8R8A8. 
-void imgFrame::SetHasNoAlpha()
-{
-  MOZ_ASSERT(mLockCount, "Expected to be locked when SetHasNoAlpha is called");
-  mHasNoAlpha = true;
-}
-
-void imgFrame::SetAsNonPremult(bool aIsNonPremult)
-{
-  mNonPremult = aIsNonPremult;
-}
-
 bool imgFrame::GetCompositingFailed() const
 {
+  MOZ_ASSERT(NS_IsMainThread());
   return mCompositingFailed;
 }
 
 void imgFrame::SetCompositingFailed(bool val)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   mCompositingFailed = val;
 }
 
 size_t
 imgFrame::SizeOfExcludingThis(gfxMemoryLocation aLocation,
                               MallocSizeOf aMallocSizeOf) const
 {
+  MutexAutoLock lock(mMutex);
+
   // 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");
 
   size_t n = 0;
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -9,16 +9,17 @@
 
 #include "mozilla/MemoryReporting.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 {
 
 class ImageRegion;
 class DrawableFrameRef;
 class RawAccessFrameRef;
 
@@ -40,16 +41,75 @@ MOZ_BEGIN_ENUM_CLASS(DisposalMethod, int
   RESTORE_PREVIOUS // Restore the previous (composited) frame.
 MOZ_END_ENUM_CLASS(DisposalMethod)
 
 MOZ_BEGIN_ENUM_CLASS(Opacity, uint8_t)
   OPAQUE,
   SOME_TRANSPARENCY
 MOZ_END_ENUM_CLASS(Opacity)
 
+
+/**
+ * AnimationData contains all of the information necessary for using an imgFrame
+ * as part of an animation.
+ *
+ * It includes pointers to the raw image data of the underlying imgFrame, but
+ * does not own that data. A RawAccessFrameRef for the underlying imgFrame must
+ * outlive the AnimationData for it to remain valid.
+ */
+struct AnimationData
+{
+  AnimationData(uint8_t* aRawData, uint32_t aPaletteDataLength,
+                int32_t aRawTimeout, const nsIntRect& aRect,
+                BlendMethod aBlendMethod, DisposalMethod aDisposalMethod,
+                bool aHasAlpha)
+    : mRawData(aRawData)
+    , mPaletteDataLength(aPaletteDataLength)
+    , mRawTimeout(aRawTimeout)
+    , mRect(aRect)
+    , mBlendMethod(aBlendMethod)
+    , mDisposalMethod(aDisposalMethod)
+    , mHasAlpha(aHasAlpha)
+  { }
+
+  uint8_t* mRawData;
+  uint32_t mPaletteDataLength;
+  int32_t mRawTimeout;
+  nsIntRect mRect;
+  BlendMethod mBlendMethod;
+  DisposalMethod mDisposalMethod;
+  bool mHasAlpha;
+};
+
+/**
+ * ScalingData contains all of the information necessary for performing
+ * high-quality (CPU-based) scaling an imgFrame.
+ *
+ * It includes pointers to the raw image data of the underlying imgFrame, but
+ * does not own that data. A RawAccessFrameRef for the underlying imgFrame must
+ * outlive the ScalingData for it to remain valid.
+ */
+struct ScalingData
+{
+  ScalingData(uint8_t* aRawData,
+              gfx::IntSize aSize,
+              uint32_t aBytesPerRow,
+              gfx::SurfaceFormat aFormat)
+    : mRawData(aRawData)
+    , mSize(aSize)
+    , mBytesPerRow(aBytesPerRow)
+    , mFormat(aFormat)
+  { }
+
+  uint8_t* mRawData;
+  gfx::IntSize mSize;
+  uint32_t mBytesPerRow;
+  gfx::SurfaceFormat mFormat;
+};
+
 class imgFrame
 {
   typedef gfx::Color Color;
   typedef gfx::DataSourceSurface DataSourceSurface;
   typedef gfx::DrawTarget DrawTarget;
   typedef gfx::IntSize IntSize;
   typedef gfx::SourceSurface SourceSurface;
   typedef gfx::SurfaceFormat SurfaceFormat;
@@ -66,17 +126,18 @@ public:
    *
    * This is appropriate for use with decoded images, but it should not be used
    * when drawing content into an imgFrame, as it may use a different graphics
    * backend than normal content drawing.
    */
   nsresult InitForDecoder(const nsIntSize& aImageSize,
                           const nsIntRect& aRect,
                           SurfaceFormat aFormat,
-                          uint8_t aPaletteDepth = 0);
+                          uint8_t aPaletteDepth = 0,
+                          bool aNonPremult = false);
 
   nsresult InitForDecoder(const nsIntSize& aSize,
                           SurfaceFormat aFormat,
                           uint8_t aPaletteDepth = 0)
   {
     return InitForDecoder(aSize, nsIntRect(0, 0, aSize.width, aSize.height),
                           aFormat, aPaletteDepth);
   }
@@ -113,88 +174,95 @@ public:
    */
   void SetRawAccessOnly();
 
   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.
+   *
+   * @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);
+
   IntSize GetImageSize() { return mImageSize; }
   nsIntRect GetRect() const;
   IntSize GetSize() const { return mSize; }
   bool NeedsPadding() const { return mOffset != nsIntPoint(0, 0); }
-  int32_t GetStride() const;
-  SurfaceFormat GetFormat() const;
-  uint32_t GetImageBytesPerRow() const;
-  uint32_t GetImageDataLength() const;
-  bool GetIsPaletted() const;
-  bool GetHasAlpha() const;
   void GetImageData(uint8_t **aData, uint32_t *length) const;
   uint8_t* GetImageData() const;
+
+  bool GetIsPaletted() const;
   void GetPaletteData(uint32_t **aPalette, uint32_t *length) const;
   uint32_t* GetPaletteData() const;
-  uint8_t* GetRawData() const;
-
-  int32_t GetRawTimeout() const;
-  void SetRawTimeout(int32_t aTimeout);
+  uint8_t GetPaletteDepth() const { return mPaletteDepth; }
 
-  DisposalMethod GetDisposalMethod() const { return mDisposalMethod; }
-  void SetDisposalMethod(DisposalMethod aDisposalMethod)
-  {
-    mDisposalMethod = aDisposalMethod;
-  }
+  /**
+   * Get the SurfaceFormat for this imgFrame.
+   *
+   * This should only be used for assertions.
+   */
+  SurfaceFormat GetFormat() const;
 
-  BlendMethod GetBlendMethod() const { return mBlendMethod; }
-  void SetBlendMethod(BlendMethod aBlendMethod) { mBlendMethod = aBlendMethod; }
+  AnimationData GetAnimationData() const;
+  ScalingData GetScalingData() const;
 
   bool ImageComplete() const;
 
-  void SetHasNoAlpha();
-  void SetAsNonPremult(bool aIsNonPremult);
-
   bool GetCompositingFailed() const;
   void SetCompositingFailed(bool val);
 
   void SetOptimizable();
 
+  Color SinglePixelColor() const;
+  bool IsSinglePixel() const;
+
   TemporaryRef<SourceSurface> GetSurface();
   TemporaryRef<DrawTarget> GetDrawTarget();
 
-  Color
-  SinglePixelColor()
-  {
-    return mSinglePixelColor;
-  }
-
-  bool IsSinglePixel()
-  {
-    return mSinglePixel;
-  }
-
-  TemporaryRef<SourceSurface> CachedSurface();
-
   size_t SizeOfExcludingThis(gfxMemoryLocation aLocation,
                              MallocSizeOf aMallocSizeOf) const;
 
-  uint8_t GetPaletteDepth() const { return mPaletteDepth; }
-  uint32_t PaletteDataLength() const {
-    if (!mPaletteDepth)
-      return 0;
-
-    return ((1 << mPaletteDepth) * sizeof(uint32_t));
-  }
-
 private: // methods
 
   ~imgFrame();
 
   nsresult LockImageData();
   nsresult UnlockImageData();
   nsresult Optimize();
+  nsresult Deoptimize();
+
+  void AssertImageDataLocked() const;
+
+  bool ImageCompleteInternal() 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
+  {
+    return mPaletteDepth ? (1 << mPaletteDepth) * sizeof(uint32_t)
+                         : 0;
+  }
 
   struct SurfaceWithFormat {
     nsRefPtr<gfxDrawable> mDrawable;
     SurfaceFormat mFormat;
     SurfaceWithFormat() {}
     SurfaceWithFormat(gfxDrawable* aDrawable, SurfaceFormat aFormat)
      : mDrawable(aDrawable), mFormat(aFormat) {}
     bool IsValid() { return !!mDrawable; }
@@ -205,56 +273,75 @@ private: // methods
                                       bool               aDoTile,
                                       gfxContext*        aContext,
                                       const nsIntMargin& aPadding,
                                       gfxRect&           aImageRect,
                                       ImageRegion&       aRegion,
                                       SourceSurface*     aSurface);
 
 private: // data
+  friend class DrawableFrameRef;
+  friend class RawAccessFrameRef;
+  friend class UnlockImageDataRunnable;
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Thread-safe mutable data, protected by mMutex.
+  //////////////////////////////////////////////////////////////////////////////
+
+  mutable Mutex mMutex;
+
   RefPtr<DataSourceSurface> mImageSurface;
   RefPtr<SourceSurface> mOptSurface;
 
+  RefPtr<VolatileBuffer> mVBuf;
+  VolatileBufferPtr<uint8_t> mVBufPtr;
+
+  nsIntRect mDecoded;
+
+  //! Number of RawAccessFrameRefs currently alive for this imgFrame.
+  int32_t mLockCount;
+
+  //! Raw timeout for this frame. (See FrameAnimator::GetTimeoutForFrame.)
+  int32_t mTimeout; // -1 means display forever.
+
+  DisposalMethod mDisposalMethod;
+  BlendMethod    mBlendMethod;
+  SurfaceFormat  mFormat;
+
+  bool mHasNoAlpha;
+
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Effectively const data, only mutated in the Init methods.
+  //////////////////////////////////////////////////////////////////////////////
+
   IntSize      mImageSize;
   IntSize      mSize;
   nsIntPoint   mOffset;
 
-  nsIntRect    mDecoded;
-
-  mutable Mutex mDecodedMutex;
-
   // The palette and image data for images that are paletted, since Cairo
   // doesn't support these images.
   // The paletted data comes first, then the image data itself.
   // Total length is PaletteDataLength() + GetImageDataLength().
   uint8_t*     mPalettedImageData;
+  uint8_t      mPaletteDepth;
+
+  bool mNonPremult;
+
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Main-thread-only mutable data.
+  //////////////////////////////////////////////////////////////////////////////
 
   // Note that the data stored in gfx::Color is *non-alpha-premultiplied*.
   Color        mSinglePixelColor;
 
-  int32_t      mTimeout; // -1 means display forever
-
-  /** Indicates how many readers currently have locked this frame */
-  int32_t mLockCount;
-
-  RefPtr<VolatileBuffer> mVBuf;
-  VolatileBufferPtr<uint8_t> mVBufPtr;
-
-  SurfaceFormat  mFormat;
-  uint8_t        mPaletteDepth;
-  DisposalMethod mDisposalMethod;
-  BlendMethod    mBlendMethod;
   bool mSinglePixel;
   bool mCompositingFailed;
-  bool mHasNoAlpha;
-  bool mNonPremult;
   bool mOptimizable;
-
-  friend class DrawableFrameRef;
-  friend class RawAccessFrameRef;
 };
 
 /**
  * A reference to an imgFrame that holds the imgFrame's surface in memory,
  * allowing drawing. If you have a DrawableFrameRef |ref| and |if (ref)| returns
  * true, then calls to Draw() and GetSurface() are guaranteed to succeed.
  */
 class DrawableFrameRef MOZ_FINAL
deleted file mode 100644
--- a/testing/web-platform/meta/2dcontext/drawing-images-to-the-canvas/2d.drawImage.broken.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[2d.drawImage.broken.html]
-  type: testharness
-  [Canvas test: 2d.drawImage.broken]
-    expected: FAIL
-