Bug 795940 - Part 0.1 - Allocate ScaleRequests dynamically and only hold on to a ScaleResult in RasterImage. Let ScaleRequests only hold a weak pointer to RasterImage so their lifetimes aren't bound. Finally, make ScaleRequests hold on to references to their surfaces instead of imgFrames. r=jlebar,jrmuizel
authorJoe Drew <joe@drew.ca>
Wed, 10 Oct 2012 11:35:23 -0400
changeset 118492 b0a701fd2322d04c1c6c338b8c0c8035e672d86b
parent 118491 0e3e4275119519096133d0967bf703a769b93952
child 118493 05a2876b0d0264e02f424751558bae6d5024cf0d
push id1997
push userakeybl@mozilla.com
push dateMon, 07 Jan 2013 21:25:26 +0000
treeherdermozilla-beta@4baf45cdcf21 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar, jrmuizel
bugs795940
milestone19.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 795940 - Part 0.1 - Allocate ScaleRequests dynamically and only hold on to a ScaleResult in RasterImage. Let ScaleRequests only hold a weak pointer to RasterImage so their lifetimes aren't bound. Finally, make ScaleRequests hold on to references to their surfaces instead of imgFrames. r=jlebar,jrmuizel
image/src/RasterImage.cpp
image/src/RasterImage.h
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -22,78 +22,24 @@
 #include "nsPNGDecoder.h"
 #include "nsGIFDecoder2.h"
 #include "nsJPEGDecoder.h"
 #include "nsBMPDecoder.h"
 #include "nsICODecoder.h"
 #include "nsIconDecoder.h"
 
 #include "gfxContext.h"
-#include "gfx2DGlue.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/StandardInteger.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/gfx/Scale.h"
 
-// The high-quality scaler requires Skia.
-#ifdef MOZ_ENABLE_SKIA
-
-static bool
-ScaleFrameImage(imgFrame *aSrcFrame, imgFrame *aDstFrame,
-                const gfxSize &aScaleFactors)
-{
-  if (aScaleFactors.width <= 0 || aScaleFactors.height <= 0)
-    return false;
-
-  imgFrame *srcFrame = aSrcFrame;
-  nsIntRect srcRect = srcFrame->GetRect();
-  uint32_t dstWidth = NSToIntRoundUp(srcRect.width * aScaleFactors.width);
-  uint32_t dstHeight = NSToIntRoundUp(srcRect.height * aScaleFactors.height);
-
-  // Destination is unconditionally ARGB32 because that's what the scaler
-  // outputs.
-  nsresult rv = aDstFrame->Init(0, 0, dstWidth, dstHeight,
-                                gfxASurface::ImageFormatARGB32);
-  if (!NS_FAILED(rv)) {
-    uint8_t* srcData;
-    uint32_t srcDataLength;
-    // Source frame data is locked/unlocked on the main thread.
-    srcFrame->GetImageData(&srcData, &srcDataLength);
-    NS_ASSERTION(srcData != nullptr, "Source data is unavailable! Is it locked?");
-
-    uint8_t* dstData;
-    uint32_t dstDataLength;
-    aDstFrame->LockImageData();
-    aDstFrame->GetImageData(&dstData, &dstDataLength);
-
-    // This returns an SkBitmap backed by dstData; since it wrote to dstData,
-    // we don't need to look at that SkBitmap.
-    mozilla::gfx::Scale(srcData, srcRect.width, srcRect.height, aSrcFrame->GetImageBytesPerRow(),
-                        dstData, dstWidth, dstHeight, aDstFrame->GetImageBytesPerRow(),
-                        mozilla::gfx::ImageFormatToSurfaceFormat(aSrcFrame->GetFormat()));
-
-    aDstFrame->UnlockImageData();
-    return true;
-  }
-
-  return false;
-}
-#else // MOZ_ENABLE_SKIA
-static bool
-ScaleFrameImage(imgFrame *aSrcFrame, imgFrame *aDstFrame,
-                const gfxSize &aScaleFactors)
-{
-  return false;
-}
-#endif // MOZ_ENABLE_SKIA
-
-
 #include "sampler.h"
 
 using namespace mozilla;
 using namespace mozilla::image;
 using namespace mozilla::layers;
 
 // a mask for flags that will affect the decoding
 #define DECODE_FLAGS_MASK (imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA | imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION)
@@ -230,33 +176,29 @@ RasterImage::RasterImage(imgStatusTracke
   mMultipart(false),
   mDiscardable(false),
   mHasSourceData(false),
   mDecoded(false),
   mHasBeenDecoded(false),
   mInDecoder(false),
   mAnimationFinished(false),
   mFinishing(false),
-  mInUpdateImageContainer(false),
-  mScaleRequest(this)
+  mInUpdateImageContainer(false)
 {
   // Set up the discard tracker node.
   mDiscardTrackerNode.img = this;
   Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
 
   // Statistics
   num_containers++;
-
 }
 
 //******************************************************************************
 RasterImage::~RasterImage()
 {
-  ScaleRequest::Stop(mScaleRequest.image);
-
   // Discardable statistics
   if (mDiscardable) {
     num_discardable_containers--;
     discardable_source_bytes -= mSourceData.Length();
 
     PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
             ("CompressedImageAccounting: destroying RasterImage %p.  "
              "Total Containers: %d, Discardable containers: %d, "
@@ -2694,64 +2636,63 @@ nsresult
 RasterImage::ScaleWorker::Run()
 {
   if (!mInitialized) {
     PR_SetCurrentThreadName("Image Scaler");
     mInitialized = true;
   }
 
   ScaleRequest* request;
-  gfxSize scale;
-  imgFrame* frame;
   {
     MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
     request = mScaleRequests.popFirst();
-    if (!request)
-      return NS_OK;
-
-    scale = request->scale;
-    frame = request->srcFrame;
   }
 
-  nsAutoPtr<imgFrame> scaledFrame(new imgFrame());
-  bool scaled = ScaleFrameImage(frame, scaledFrame, scale);
+  request->done = mozilla::gfx::Scale(request->srcData, request->srcRect.width, request->srcRect.height, request->srcStride,
+                                      request->dstData, request->dstSize.width, request->dstSize.height, request->dstStride,
+                                      request->srcFormat);
 
   // OK, we've got a new scaled image. Let's get the main thread to unlock and
   // redraw it.
-  {
-    MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
-    if (scaled && scale == request->scale && !request->isInList()) {
-      request->dstFrame = scaledFrame;
-      request->done = true;
-    }
-
-    DrawWorker::Singleton()->RequestDraw(request->image);
-  }
+  DrawWorker::Singleton()->RequestDraw(request);
+
   return NS_OK;
 }
 
 // Note: you MUST call RequestScale with the ScaleWorker mutex held.
-void
-RasterImage::ScaleWorker::RequestScale(RasterImage* aImg)
+bool
+RasterImage::ScaleWorker::RequestScale(ScaleRequest* request,
+                                       RasterImage* image,
+                                       imgFrame* aSrcFrame)
 {
   mRequestsMutex.AssertCurrentThreadOwns();
 
-  ScaleRequest* request = &aImg->mScaleRequest;
-  if (request->isInList())
-    return;
+  // Destination is unconditionally ARGB32 because that's what the scaler
+  // outputs.
+  request->dstFrame = new imgFrame();
+  nsresult rv = request->dstFrame->Init(0, 0, request->dstSize.width, request->dstSize.height,
+                                        gfxASurface::ImageFormatARGB32);
+
+  if (NS_FAILED(rv) || !request->GetSurfaces(aSrcFrame)) {
+    return false;
+  }
 
   mScaleRequests.insertBack(request);
 
   if (!sScaleWorkerThread) {
     NS_NewThread(getter_AddRefs(sScaleWorkerThread), this, NS_DISPATCH_NORMAL);
     ClearOnShutdown(&sScaleWorkerThread);
   }
   else {
     sScaleWorkerThread->Dispatch(this, NS_DISPATCH_NORMAL);
   }
+
+  image->SetResultPending(request);
+
+  return true;
 }
 
 /* static */ RasterImage::DrawWorker*
 RasterImage::DrawWorker::Singleton()
 {
   if (!sSingleton) {
     sSingleton = new DrawWorker();
     ClearOnShutdown(&sSingleton);
@@ -2763,65 +2704,57 @@ RasterImage::DrawWorker::Singleton()
 nsresult
 RasterImage::DrawWorker::Run()
 {
   ScaleRequest* request;
   {
     MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
     request = mDrawRequests.popFirst();
   }
-  if (request) {
-    // ScaleWorker is finished with this request, so we can unlock the data now.
-    request->UnlockSourceData();
-    // We have to reset dstFrame if request was stopped while ScaleWorker was scaling.
-    if (request->stopped) {
-      ScaleRequest::Stop(request->image);
-    }
-    nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(request->image->mObserver));
-    if (request->done && observer) {
-      imgFrame *scaledFrame = request->dstFrame.get();
-      scaledFrame->ImageUpdated(scaledFrame->GetRect());
-      nsIntRect frameRect = request->srcFrame->GetRect();
-      observer->FrameChanged(&frameRect);
+
+  // ScaleWorker is finished with this request, so we can unlock the data now.
+  request->ReleaseSurfaces();
+
+  // Only set the scale result if the request finished successfully.
+  if (request->done) {
+    RasterImage* image = request->weakImage;
+    if (image) {
+      nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(image->mObserver));
+      if (observer) {
+        imgFrame *scaledFrame = request->dstFrame.get();
+        scaledFrame->ImageUpdated(scaledFrame->GetRect());
+        observer->FrameChanged(&request->srcRect);
+      }
+
+      image->SetScaleResult(request);
     }
   }
 
+  // Scaling failed. Reset the scale result on our image.
+  else {
+    RasterImage* image = request->weakImage;
+    if (image) {
+      image->SetScaleResult(nullptr);
+    }
+  }
+
+  // We're all done with this ScaleRequest; now it dies.
+  delete request;
+
   return NS_OK;
 }
 
 void
-RasterImage::DrawWorker::RequestDraw(RasterImage* aImg)
+RasterImage::DrawWorker::RequestDraw(ScaleRequest* request)
 {
-  ScaleRequest* request = &aImg->mScaleRequest;
+  MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
   mDrawRequests.insertBack(request);
   NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
 }
 
-void
-RasterImage::ScaleRequest::Stop(RasterImage* aImg)
-{
-  ScaleRequest* request = &aImg->mScaleRequest;
-  // It's safe to unlock source image data only if request is in the list.
-  // Otherwise we may be reading from the source while performing scaling
-  // and can't interrupt immediately.
-  if (request->isInList()) {
-    request->remove();
-    request->UnlockSourceData();
-  }
-  // We have to check if request is finished before dropping the destination
-  // frame. Otherwise we may be writing to the dest while performing scaling.
-  if (request->done) {
-    request->done = false;
-    request->dstFrame = nullptr;
-    request->scale.width = 0;
-    request->scale.height = 0;
-  }
-  request->stopped = true;
-}
-
 static inline bool
 IsDownscale(const gfxSize& scale)
 {
   if (scale.width > 1.0)
     return false;
   if (scale.height > 1.0)
     return false;
   if (scale.width == 1.0 && scale.height == 1.0)
@@ -2842,16 +2775,38 @@ RasterImage::CanScale(gfxPattern::Graphi
     return (aScale.width < factor || aScale.height < factor);
   }
 #endif
 
   return false;
 }
 
 void
+RasterImage::SetScaleResult(ScaleRequest* request)
+{
+  if (request) {
+    MOZ_ASSERT(request->done);
+    mScaleResult.status = SCALE_DONE;
+    mScaleResult.frame = request->dstFrame;
+    mScaleResult.scale = request->scale;
+  } else {
+    mScaleResult.status = SCALE_INVALID;
+    mScaleResult.frame = nullptr;
+  }
+}
+
+void
+RasterImage::SetResultPending(ScaleRequest* request)
+{
+  MOZ_ASSERT(request);
+  mScaleResult.scale = request->scale;
+  mScaleResult.status = SCALE_PENDING;
+}
+
+void
 RasterImage::DrawWithPreDownscaleIfNeeded(imgFrame *aFrame,
                                           gfxContext *aContext,
                                           gfxPattern::GraphicsFilter aFilter,
                                           const gfxMatrix &aUserSpaceToImageSpace,
                                           const gfxRect &aFill,
                                           const nsIntRect &aSubimage)
 {
   imgFrame *frame = aFrame;
@@ -2860,45 +2815,40 @@ RasterImage::DrawWithPreDownscaleIfNeede
   gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace;
   imageSpaceToUserSpace.Invert();
   gfxSize scale = imageSpaceToUserSpace.ScaleFactors(true);
   nsIntRect subimage = aSubimage;
 
   if (CanScale(aFilter, scale)) {
     MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
     // If scale factor is still the same that we scaled for and
-    // ScaleWorker has done it's job, then we can use pre-downscaled frame.
+    // ScaleWorker isn't still working, then we can use pre-downscaled frame.
     // If scale factor has changed, order new request.
-    if (mScaleRequest.scale == scale) {
-      if (mScaleRequest.done) {
-        frame = mScaleRequest.dstFrame.get();
-        userSpaceToImageSpace.Multiply(gfxMatrix().Scale(scale.width, scale.height));
-
-        // Since we're switching to a scaled image, we we need to transform the
-        // area of the subimage to draw accordingly, since imgFrame::Draw()
-        // doesn't know about scaled frames.
-        subimage.ScaleRoundOut(scale.width, scale.height);
-      }
-    } else {
-      // FIXME: Current implementation doesn't support pre-downscale
-      // mechanism for multiple images from same src, since we cache
-      // pre-downscaled frame only for the latest requested scale.
-      // The solution is to cache more than one scaled image frame
-      // for each RasterImage.
-      int scaling = mScaleRequest.srcDataLocked ? 1 : 0;
-      if (mLockCount - scaling == 1) {
-        ScaleRequest::Stop(this);
-        mScaleRequest.srcFrame = frame;
-        mScaleRequest.scale = scale;
-        mScaleRequest.stopped = false;
-
-        // We need to make sure that source data is available before asking to scale.
-        if (mScaleRequest.LockSourceData()) {
-          ScaleWorker::Singleton()->RequestScale(this);
-        }
+    // FIXME: Current implementation doesn't support pre-downscale
+    // mechanism for multiple sizes from same src, since we cache
+    // pre-downscaled frame only for the latest requested scale.
+    // The solution is to cache more than one scaled image frame
+    // for each RasterImage.
+    if (mScaleResult.status == SCALE_DONE && mScaleResult.scale == scale) {
+      frame = mScaleResult.frame;
+      userSpaceToImageSpace.Multiply(gfxMatrix().Scale(scale.width, scale.height));
+
+      // Since we're switching to a scaled image, we we need to transform the
+      // area of the subimage to draw accordingly, since imgFrame::Draw()
+      // doesn't know about scaled frames.
+      subimage.ScaleRoundOut(scale.width, scale.height);
+    }
+
+    // If we're not waiting for exactly this result, ask for it.
+    else if (!(mScaleResult.status == SCALE_PENDING && mScaleResult.scale == scale)) {
+      ScaleRequest* request = new ScaleRequest(this, scale, frame);
+
+      if (!ScaleWorker::Singleton()->RequestScale(request, this, frame)) {
+        // Requesting a scale failed. Not much we can do.
+        delete request;
       }
     }
   }
 
   nsIntMargin padding(framerect.x, framerect.y,
                       mSize.width - framerect.XMost(),
                       mSize.height - framerect.YMost());
 
@@ -3028,21 +2978,16 @@ RasterImage::UnlockImage()
     return NS_ERROR_ABORT;
 
   // We're locked, so discarding should not be active
   NS_ABORT_IF_FALSE(!DiscardingActive(), "Locked, but discarding activated");
 
   // Decrement our lock count
   mLockCount--;
 
-  if (ScaleWorker::sSingleton && mLockCount == 0) {
-    MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
-    ScaleRequest::Stop(this);
-  }
-
   // If we've decoded this image once before, we're currently decoding again,
   // and our lock count is now zero (so nothing is forcing us to keep the
   // decoded data around), try to cancel the decode and throw away whatever
   // we've decoded.
   if (mHasBeenDecoded && mDecoder &&
       mLockCount == 0 && CanForciblyDiscard()) {
     PR_LOG(gCompressedImageAccountingLog, PR_LOG_DEBUG,
            ("RasterImage[0x%p] canceling decode because image "
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -29,16 +29,17 @@
 #include "imgFrame.h"
 #include "nsThreadUtils.h"
 #include "DiscardTracker.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/WeakPtr.h"
+#include "gfx2DGlue.h"
 #ifdef DEBUG
   #include "imgIContainerDebug.h"
 #endif
 
 class imgIDecoder;
 class imgIContainerObserver;
 class nsIInputStream;
 
@@ -470,60 +471,132 @@ private:
 
     /* True if we've posted ourselves to the event loop and expect Run() to
      * be called sometime in the future. */
     bool mPendingInEventLoop;
   };
 
   struct ScaleRequest : public LinkedListElement<ScaleRequest>
   {
-    ScaleRequest(RasterImage* aImage)
-      : image(aImage)
-      , srcFrame(nullptr)
-      , dstFrame(nullptr)
-      , scale(0, 0)
+    ScaleRequest(RasterImage* aImage, const gfxSize& aScale, imgFrame* aSrcFrame)
+      : scale(aScale)
+      , dstLocked(false)
       , done(false)
-      , stopped(false)
-      , srcDataLocked(false)
-    {};
+    {
+      MOZ_ASSERT(!aSrcFrame->GetIsPaletted());
+      MOZ_ASSERT(aScale.width > 0 && aScale.height > 0);
 
-    bool LockSourceData()
-    {
-      if (!srcDataLocked) {
-        bool success = true;
-        success = success && NS_SUCCEEDED(image->LockImage());
-        success = success && NS_SUCCEEDED(srcFrame->LockImageData());
-        srcDataLocked = success;
-      }
-      return srcDataLocked;
+      weakImage = aImage->asWeakPtr();
+      srcRect = aSrcFrame->GetRect();
+      dstSize.width = NSToIntRoundUp(srcRect.width * scale.width);
+      dstSize.height = NSToIntRoundUp(srcRect.height * scale.height);
     }
 
-    bool UnlockSourceData()
+    // This can only be called on the main thread.
+    bool GetSurfaces(imgFrame* srcFrame)
     {
-      bool success = true;
-      if (srcDataLocked) {
-        success = success && NS_SUCCEEDED(image->UnlockImage());
-        success = success && NS_SUCCEEDED(srcFrame->UnlockImageData());
+      MOZ_ASSERT(NS_IsMainThread());
+
+      nsRefPtr<RasterImage> image = weakImage.get();
+      if (!image) {
+        return false;
+      }
+
+      bool success = false;
+      if (!dstLocked) {
+        bool srcLocked = NS_SUCCEEDED(srcFrame->LockImageData());
+        dstLocked = NS_SUCCEEDED(dstFrame->LockImageData());
+
+        nsRefPtr<gfxASurface> dstASurf;
+        nsRefPtr<gfxASurface> srcASurf;
+        success = srcLocked && NS_SUCCEEDED(srcFrame->GetSurface(getter_AddRefs(srcASurf)));
+        success = success && dstLocked && NS_SUCCEEDED(dstFrame->GetSurface(getter_AddRefs(dstASurf)));
+
+        success = success && srcLocked && dstLocked && srcASurf && dstASurf;
+
+        if (success) {
+          srcSurface = srcASurf->GetAsImageSurface();
+          dstSurface = dstASurf->GetAsImageSurface();
+          srcData = srcSurface->Data();
+          dstData = dstSurface->Data();
+          srcStride = srcSurface->Stride();
+          dstStride = dstSurface->Stride();
+          srcFormat = mozilla::gfx::ImageFormatToSurfaceFormat(srcFrame->GetFormat());
+        }
 
-        // If unlocking fails, there's nothing we can do to make it work, so we
-        // claim that we're not locked regardless.
-        srcDataLocked = false;
+        // We have references to the Thebes surfaces, so we don't need to leave
+        // the source frame (that we don't own) locked. We'll unlock the
+        // destination frame in ReleaseSurfaces(), below.
+        if (srcLocked) {
+          success = NS_SUCCEEDED(srcFrame->UnlockImageData()) && success;
+        }
+
+        success = success && srcSurface && dstSurface;
+      }
+
+      return success;
+    }
+
+    // This can only be called on the main thread.
+    bool ReleaseSurfaces()
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+
+      nsRefPtr<RasterImage> image = weakImage.get();
+      if (!image) {
+        return false;
+      }
+
+      bool success = false;
+      if (dstLocked) {
+        success = NS_SUCCEEDED(dstFrame->UnlockImageData());
+
+        dstLocked = false;
+        srcData = nullptr;
+        dstData = nullptr;
+        srcSurface = nullptr;
+        dstSurface = nullptr;
       }
       return success;
     }
 
-    static void Stop(RasterImage* aImg);
-
-    RasterImage* const image;
-    imgFrame *srcFrame;
+    // These values may only be touched on the main thread.
+    WeakPtr<RasterImage> weakImage;
     nsAutoPtr<imgFrame> dstFrame;
+    nsRefPtr<gfxImageSurface> srcSurface;
+    nsRefPtr<gfxImageSurface> dstSurface;
+
+    // Below are the values that may be touched on the scaling thread.
     gfxSize scale;
+    uint8_t* srcData;
+    uint8_t* dstData;
+    nsIntRect srcRect;
+    gfxIntSize dstSize;
+    uint32_t srcStride;
+    uint32_t dstStride;
+    mozilla::gfx::SurfaceFormat srcFormat;
+    bool dstLocked;
     bool done;
-    bool stopped;
-    bool srcDataLocked;
+  };
+
+  enum ScaleStatus
+  {
+    SCALE_INVALID,
+    SCALE_PENDING,
+    SCALE_DONE
+  };
+  struct ScaleResult
+  {
+    ScaleResult()
+     : status(SCALE_INVALID)
+    {}
+
+    gfxSize scale;
+    nsAutoPtr<imgFrame> frame;
+    ScaleStatus status;
   };
 
   class ScaleWorker : public nsRunnable
   {
   public:
     static ScaleWorker* Singleton();
 
     NS_IMETHOD Run();
@@ -533,17 +606,17 @@ private:
 
   private: /* methods */
     ScaleWorker()
       : mRequestsMutex("RasterImage.ScaleWorker.mRequestsMutex")
       , mInitialized(false)
     {};
 
     // Note: you MUST call RequestScale with the ScaleWorker mutex held.
-    void RequestScale(RasterImage* aImg);
+    bool RequestScale(ScaleRequest* request, RasterImage* image, imgFrame* aSrcFrame);
 
   private: /* members */
 
     friend class RasterImage;
     LinkedList<ScaleRequest> mScaleRequests;
     Mutex mRequestsMutex;
     bool mInitialized;
   };
@@ -556,31 +629,39 @@ private:
     NS_IMETHOD Run();
 
   /* statics */
     static nsRefPtr<DrawWorker> sSingleton;
 
   private: /* methods */
     DrawWorker() {};
 
-    void RequestDraw(RasterImage* aImg);
+    void RequestDraw(ScaleRequest* request);
 
   private: /* members */
 
     friend class RasterImage;
     LinkedList<ScaleRequest> mDrawRequests;
   };
 
   void DrawWithPreDownscaleIfNeeded(imgFrame *aFrame,
                                     gfxContext *aContext,
                                     gfxPattern::GraphicsFilter aFilter,
                                     const gfxMatrix &aUserSpaceToImageSpace,
                                     const gfxRect &aFill,
                                     const nsIntRect &aSubimage);
 
+  // Call this with a finished ScaleRequest to set this RasterImage's scale
+  // result, or nullptr to reset the scale result.
+  void SetScaleResult(ScaleRequest* request);
+
+  // Call this with a new ScaleRequest to mark this RasterImage's scale result
+  // as waiting for the results of this request.
+  void SetResultPending(ScaleRequest* request);
+
   /**
    * Advances the animation. Typically, this will advance a single frame, but it
    * may advance multiple frames. This may happen if we have infrequently
    * "ticking" refresh drivers (e.g. in background tabs), or extremely short-
    * lived animation frames.
    *
    * @param aTime the time that the animation should advance to. This will
    *              typically be <= TimeStamp::Now().
@@ -782,17 +863,17 @@ private: // data
   nsresult SyncDecode();
   nsresult InitDecoder(bool aDoSizeDecode);
   nsresult WriteToDecoder(const char *aBuffer, uint32_t aCount);
   nsresult DecodeSomeData(uint32_t aMaxBytes);
   bool     IsDecodeFinished();
   TimeStamp mDrawStartTime;
 
   inline bool CanScale(gfxPattern::GraphicsFilter aFilter, gfxSize aScale);
-  ScaleRequest mScaleRequest;
+  ScaleResult mScaleResult;
 
   // Decoder shutdown
   enum eShutdownIntent {
     eShutdownIntent_Done        = 0,
     eShutdownIntent_Interrupted = 1,
     eShutdownIntent_Error       = 2,
     eShutdownIntent_AllCount    = 3
   };