Back out 8 changesets (bug 1207355) for OS X 10.10 reftest failures in generated-content/
authorPhil Ringnalda <philringnalda@gmail.com>
Wed, 28 Oct 2015 22:57:43 -0700
changeset 303546 b4c323832f317d650acd4bd7066d9432f3c1768c
parent 303545 593bccc56191fe7d1fc0757257de5be103f142a8
child 303547 e8610a7c6b3e9cdd7cbf30f36aed92e634abd4bc
push id5392
push userraliiev@mozilla.com
push dateMon, 14 Dec 2015 20:08:23 +0000
treeherdermozilla-beta@16ce8562a975 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1207355
milestone44.0a1
backs outaafd6db2fbb452350c42a1d839ce96ec6e183b53
9dd950b837fb0759ac94e33506b0ca375b19d893
e941e0e106a1f501424cb08f304455cb1cf9d8b2
ecebca101fcbe546e187abed4f0469c0fd96d920
08f2017137e1f29200354398311f5af96dbbe834
3dc69e37c9b4d369c459c1e33b7b9e5597277925
bcdf51edb121aac9f3dcf7fedebd84ba4db3039e
1d4c00dbf49a26cd3b5819fd7c9cce4a6a2c5b63
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
Back out 8 changesets (bug 1207355) for OS X 10.10 reftest failures in generated-content/ CLOSED TREE Backed out changeset aafd6db2fbb4 (bug 1207355) Backed out changeset 9dd950b837fb (bug 1207355) Backed out changeset e941e0e106a1 (bug 1207355) Backed out changeset ecebca101fcb (bug 1207355) Backed out changeset 08f2017137e1 (bug 1207355) Backed out changeset 3dc69e37c9b4 (bug 1207355) Backed out changeset bcdf51edb121 (bug 1207355) Backed out changeset 1d4c00dbf49a (bug 1207355)
b2g/app/b2g.js
dom/base/nsDocument.cpp
dom/base/nsImageLoadingContent.cpp
image/DynamicImage.cpp
image/ImageWrapper.cpp
image/MultipartImage.cpp
image/RasterImage.cpp
image/VectorImage.cpp
image/imgIContainer.idl
image/imgIRequest.idl
image/imgLoader.cpp
image/imgRequest.cpp
image/imgRequest.h
image/imgRequestProxy.cpp
image/test/unit/image_load_helpers.js
layout/base/nsPresShell.cpp
layout/generic/nsBulletFrame.cpp
layout/generic/nsImageFrame.cpp
layout/generic/nsImageFrame.h
layout/style/nsStyleStruct.h
modules/libpref/init/all.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -338,16 +338,17 @@ pref("image.mem.allow_locking_in_content
 // Limit the surface cache to 1/8 of main memory or 128MB, whichever is smaller.
 // Almost everything that was factored into 'max_decoded_image_kb' is now stored
 // in the surface cache.  1/8 of main memory is 32MB on a 256MB device, which is
 // about the same as the old 'max_decoded_image_kb'.
 pref("image.mem.surfacecache.max_size_kb", 131072);  // 128MB
 pref("image.mem.surfacecache.size_factor", 8);  // 1/8 of main memory
 pref("image.mem.surfacecache.discard_factor", 2);  // Discard 1/2 of the surface cache at a time.
 pref("image.mem.surfacecache.min_expiration_ms", 86400000); // 24h, we rely on the out of memory hook
+pref("image.onload.decode.limit", 24); /* don't decode more than 24 images eagerly */
 
 // XXX this isn't a good check for "are touch events supported", but
 // we don't really have a better one at the moment.
 // enable touch events interfaces
 pref("dom.w3c_touch_events.enabled", 1);
 pref("dom.w3c_touch_events.safetyX", 0); // escape borders in units of 1/240"
 pref("dom.w3c_touch_events.safetyY", 120); // escape borders in units of 1/240"
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2112,23 +2112,31 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
     MediaQueryList *mql = static_cast<MediaQueryList*>(l);
     mql->RemoveAllListeners();
     l = next;
   }
 
   tmp->mInUnlinkOrDeletion = false;
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
+static bool sPrefsInitialized = false;
+static uint32_t sOnloadDecodeLimit = 0;
+
 nsresult
 nsDocument::Init()
 {
   if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
     return NS_ERROR_ALREADY_INITIALIZED;
   }
 
+  if (!sPrefsInitialized) {
+    sPrefsInitialized = true;
+    Preferences::AddUintVarCache(&sOnloadDecodeLimit, "image.onload.decode.limit", 0);
+  }
+
   // Force initialization.
   nsINode::nsSlots* slots = Slots();
 
   // Prepend self as mutation-observer whether we need it or not (some
   // subclasses currently do, other don't). This is because the code in
   // nsNodeUtils always notifies the first observer first, expecting the
   // first observer to be the document.
   NS_ENSURE_TRUE(slots->mMutationObservers.PrependElementUnlessExists(static_cast<nsIMutationObserver*>(this)),
@@ -10509,18 +10517,22 @@ nsDocument::AddImage(imgIRequest* aImage
 
   // Put the image in the hashtable, with the proper count.
   mImageTracker.Put(aImage, oldCount + 1);
 
   nsresult rv = NS_OK;
 
   // If this is the first insertion and we're locking images, lock this image
   // too.
-  if (oldCount == 0 && mLockingImages) {
-    rv = aImage->LockImage();
+  if (oldCount == 0) {
+    if (mLockingImages)
+      rv = aImage->LockImage();
+    if (NS_SUCCEEDED(rv) && (!sOnloadDecodeLimit ||
+                             mImageTracker.Count() < sOnloadDecodeLimit))
+      rv = aImage->StartDecoding();
   }
 
   // If this is the first insertion and we're animating images, request
   // that this image be animated too.
   if (oldCount == 0 && mAnimatingImages) {
     nsresult rv2 = aImage->IncrementAnimationConsumers();
     rv = NS_SUCCEEDED(rv) ? rv2 : rv;
   }
@@ -10641,16 +10653,17 @@ nsDocument::NotifyMediaFeatureValuesChan
   }
 }
 
 PLDHashOperator LockEnumerator(imgIRequest* aKey,
                                uint32_t aData,
                                void*    userArg)
 {
   aKey->LockImage();
+  aKey->RequestDecode();
   return PL_DHASH_NEXT;
 }
 
 PLDHashOperator UnlockEnumerator(imgIRequest* aKey,
                                  uint32_t aData,
                                  void*    userArg)
 {
   aKey->UnlockImage();
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -224,16 +224,63 @@ nsImageLoadingContent::OnLoadComplete(im
 
   // If the pending request is loaded, switch to it.
   if (aRequest == mPendingRequest) {
     MakePendingRequestCurrent();
   }
   MOZ_ASSERT(aRequest == mCurrentRequest,
              "One way or another, we should be current by now");
 
+  // We just loaded all the data we're going to get. If we're visible and
+  // haven't done an initial paint (*), we want to make sure the image starts
+  // decoding immediately, for two reasons:
+  //
+  // 1) This image is sitting idle but might need to be decoded as soon as we
+  // start painting, in which case we've wasted time.
+  //
+  // 2) We want to block onload until all visible images are decoded. We do this
+  // by blocking onload until all in-progress decodes get at least one frame
+  // decoded. However, if all the data comes in while painting is suppressed
+  // (ie, before the initial paint delay is finished), we fire onload without
+  // doing a paint first. This means that decode-on-draw images don't start
+  // decoding, so we can't wait for them to finish. See bug 512435.
+  //
+  // (*) IsPaintingSuppressed returns false if we haven't gotten the initial
+  // reflow yet, so we have to test !DidInitialize || IsPaintingSuppressed.
+  // It's possible for painting to be suppressed for reasons other than the
+  // initial paint delay (for example, being in the bfcache), but we probably
+  // aren't loading images in those situations.
+
+  // XXXkhuey should this be GetOurCurrentDoc?  Decoding if we're not in
+  // the document seems silly.
+  nsIDocument* doc = GetOurOwnerDoc();
+  nsIPresShell* shell = doc ? doc->GetShell() : nullptr;
+  if (shell && shell->IsVisible() &&
+      (!shell->DidInitialize() || shell->IsPaintingSuppressed())) {
+
+    nsIFrame* f = GetOurPrimaryFrame();
+    // If we haven't gotten a frame yet either we aren't going to (so don't
+    // bother kicking off a decode), or we will get very soon on the next
+    // refresh driver tick when it flushes. And it will most likely be a
+    // specific image type frame (we only create generic (ie inline) type
+    // frames for images that don't have a size, and since we have all the data
+    // we should have the size) which will check its own visibility on its
+    // first reflow.
+    if (f) {
+      // If we've gotten a frame and that frame has called FrameCreate and that
+      // frame has been reflowed then we know that it checked it's own visibility
+      // so we can trust our visible count and we don't start decode if we are not
+      // visible.
+      if (!mFrameCreateCalled || (f->GetStateBits() & NS_FRAME_FIRST_REFLOW) ||
+          mVisibleCount > 0 || shell->AssumeAllImagesVisible()) {
+        mCurrentRequest->StartDecoding();
+      }
+    }
+  }
+
   // Fire the appropriate DOM event.
   if (NS_SUCCEEDED(aStatus)) {
     FireEvent(NS_LITERAL_STRING("load"));
   } else {
     FireEvent(NS_LITERAL_STRING("error"));
   }
 
   nsCOMPtr<nsINode> thisNode = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
--- a/image/DynamicImage.cpp
+++ b/image/DynamicImage.cpp
@@ -242,16 +242,22 @@ DynamicImage::Draw(gfxContext* aContext,
   aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height));
 
   gfxUtils::DrawPixelSnapped(aContext, mDrawable, drawableSize, region,
                              SurfaceFormat::B8G8R8A8, aFilter);
   return DrawResult::SUCCESS;
 }
 
 NS_IMETHODIMP
+DynamicImage::RequestDecode()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 DynamicImage::StartDecoding()
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DynamicImage::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags)
 {
--- a/image/ImageWrapper.cpp
+++ b/image/ImageWrapper.cpp
@@ -210,16 +210,22 @@ ImageWrapper::Draw(gfxContext* aContext,
                    const Maybe<SVGImageContext>& aSVGContext,
                    uint32_t aFlags)
 {
   return mInnerImage->Draw(aContext, aSize, aRegion, aWhichFrame,
                            aFilter, aSVGContext, aFlags);
 }
 
 NS_IMETHODIMP
+ImageWrapper::RequestDecode()
+{
+  return mInnerImage->RequestDecode();
+}
+
+NS_IMETHODIMP
 ImageWrapper::StartDecoding()
 {
   return mInnerImage->StartDecoding();
 }
 
 NS_IMETHODIMP
 ImageWrapper::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags)
 {
--- a/image/MultipartImage.cpp
+++ b/image/MultipartImage.cpp
@@ -65,26 +65,16 @@ public:
 
   virtual void OnLoadComplete(bool aLastPart) override
   {
     if (!mImage) {
       // We've already finished observing the last image we were given.
       return;
     }
 
-    // Retrieve the image's intrinsic size.
-    int32_t width = 0;
-    int32_t height = 0;
-    mImage->GetWidth(&width);
-    mImage->GetHeight(&height);
-
-    // Request decoding at the intrinsic size.
-    mImage->RequestDecodeForSize(IntSize(width, height),
-                                 imgIContainer::DECODE_FLAGS_DEFAULT);
-
     // If there's already an error, we may never get a FRAME_COMPLETE
     // notification, so go ahead and notify our owner right away.
     RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
     if (tracker->GetProgress() & FLAG_HAS_ERROR) {
       FinishObserving();
     }
   }
 
@@ -130,16 +120,17 @@ MultipartImage::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mTracker, "Should've called SetProgressTracker() by now");
 
   // Start observing the first part.
   RefPtr<ProgressTracker> firstPartTracker =
     InnerImage()->GetProgressTracker();
   firstPartTracker->AddObserver(this);
+  InnerImage()->RequestDecode();
   InnerImage()->IncrementAnimationConsumers();
 }
 
 MultipartImage::~MultipartImage()
 {
   // Ask our ProgressTracker to drop its weak reference to us.
   mTracker->ResetImage();
 }
@@ -158,16 +149,17 @@ MultipartImage::BeginTransitionToPart(Im
     MOZ_ASSERT(!mNextPart);
   }
 
   mNextPart = aNextPart;
 
   // Start observing the next part; we'll complete the transition when
   // NextPartObserver calls FinishTransition.
   mNextPartObserver->BeginObserving(mNextPart);
+  mNextPart->RequestDecode();
   mNextPart->IncrementAnimationConsumers();
 }
 
 static Progress
 FilterProgress(Progress aProgress)
 {
   // Filter out onload blocking notifications, since we don't want to block
   // onload for multipart images.
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -314,19 +314,16 @@ RasterImage::LookupFrame(uint32_t aFrame
   // If we're opaque, we don't need to care about premultiplied alpha, because
   // that can only matter for frames with transparency.
   if (IsOpaque()) {
     aFlags &= ~FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
   }
 
   IntSize requestedSize = CanDownscaleDuringDecode(aSize, aFlags)
                         ? aSize : mSize;
-  if (requestedSize.IsEmpty()) {
-    return DrawableFrameRef();  // Can't decode to a surface of zero size.
-  }
 
   LookupResult result = LookupFrameInternal(aFrameNum, requestedSize, aFlags);
 
   if (!result && !mHasSize) {
     // We can't request a decode without knowing our intrinsic size. Give up.
     return DrawableFrameRef();
   }
 
@@ -1161,41 +1158,42 @@ RasterImage::Discard()
 }
 
 bool
 RasterImage::CanDiscard() {
   return mHasSourceData &&       // ...have the source data...
          !mAnim;                 // Can never discard animated images
 }
 
+//******************************************************************************
+
+NS_IMETHODIMP
+RasterImage::RequestDecode()
+{
+  return RequestDecodeForSize(mSize, DECODE_FLAGS_DEFAULT);
+}
+
+
 NS_IMETHODIMP
 RasterImage::StartDecoding()
 {
-  if (mError) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (!mHasSize) {
-    mWantFullDecode = true;
-    return NS_OK;
-  }
-
   return RequestDecodeForSize(mSize, FLAG_SYNC_DECODE_IF_FAST);
 }
 
 NS_IMETHODIMP
 RasterImage::RequestDecodeForSize(const IntSize& aSize, uint32_t aFlags)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mError) {
     return NS_ERROR_FAILURE;
   }
 
   if (!mHasSize) {
+    mWantFullDecode = true;
     return NS_OK;
   }
 
   // Decide whether to sync decode images we can decode quickly. Here we are
   // explicitly trading off flashing for responsiveness in the case that we're
   // redecoding an image (see bug 845147).
   bool shouldSyncDecodeIfFast =
     !mHasBeenDecoded && (aFlags & FLAG_SYNC_DECODE_IF_FAST);
@@ -1785,17 +1783,17 @@ RasterImage::FinalizeDecoder(Decoder* aD
       mLoadProgress = Nothing();
       NotifyProgress(FLAG_ONLOAD_UNBLOCKED);
     }
   }
 
   // If we were a metadata decode and a full decode was requested, do it.
   if (done && wasMetadata && mWantFullDecode) {
     mWantFullDecode = false;
-    RequestDecodeForSize(mSize, DECODE_FLAGS_DEFAULT);
+    RequestDecode();
   }
 }
 
 void
 RasterImage::ReportDecoderError(Decoder* aDecoder)
 {
   nsCOMPtr<nsIConsoleService> consoleService =
     do_GetService(NS_CONSOLESERVICE_CONTRACTID);
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -959,16 +959,25 @@ void
 VectorImage::RecoverFromLossOfSurfaces()
 {
   NS_WARNING("An imgFrame became invalid. Attempting to recover...");
 
   // Discard all existing frames, since they're probably all now invalid.
   SurfaceCache::RemoveImage(ImageKey(this));
 }
 
+//******************************************************************************
+
+NS_IMETHODIMP
+VectorImage::RequestDecode()
+{
+  // Nothing to do for SVG images
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 VectorImage::StartDecoding()
 {
   // Nothing to do for SVG images
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/image/imgIContainer.idl
+++ b/image/imgIContainer.idl
@@ -70,17 +70,17 @@ native nsIntSizeByVal(nsIntSize);
 
 /**
  * imgIContainer is the interface that represents an image. It allows
  * access to frames as Thebes surfaces. It also allows drawing of images
  * onto Thebes contexts.
  *
  * Internally, imgIContainer also manages animation of images.
  */
-[scriptable, builtinclass, uuid(a8dbee24-ff86-4755-b40e-51175caf31af)]
+[scriptable, builtinclass, uuid(7c795421-a79c-43ac-9e20-6d4e8a9dfb76)]
 interface imgIContainer : nsISupports
 {
   /**
    * The width of the container rectangle.  In the case of any error,
    * zero is returned, and an exception will be thrown.
    */
   readonly attribute int32_t width;
 
@@ -378,26 +378,32 @@ interface imgIContainer : nsISupports
        [const] in ImageRegion aRegion,
        in uint32_t aWhichFrame,
        in Filter aFilter,
        [const] in MaybeSVGImageContext aSVGContext,
        in uint32_t aFlags);
 
   /*
    * Ensures that an image is decoding. Calling this function guarantees that
-   * the image will at some point fire off decode notifications. Images that
-   * can be decoded "quickly" according to some heuristic will be decoded
-   * synchronously.
+   * the image will at some point fire off decode notifications. Calling draw()
+   * or getFrame() triggers the same mechanism internally. Thus, if you want to
+   * be sure that the image will be decoded but don't want to access it until
+   * then, you must call requestDecode().
+   */
+  void requestDecode();
+
+  /*
+   * This is equivalent to requestDecode() but it also synchronously decodes
+   * images that can be decoded "quickly" according to some heuristic.
    */
   [noscript] void startDecoding();
 
   /*
-   * This method triggers decoding for an image, but unlike startDecoding() it
-   * enables the caller to provide more detailed information about the decode
-   * request.
+   * This method is equivalent to requestDecode(), but enables the caller to
+   * provide more detailed information about the decode request.
    *
    * @param aSize The size to which the image should be scaled while decoding,
    *              if possible. If the image cannot be scaled to this size while
    *              being decoded, it will be decoded at its intrinsic size.
    * @param aFlags Flags of the FLAG_* variety. Only the decode flags
    *               (FLAG_DECODE_*) and FLAG_SYNC_DECODE (which will
    *               synchronously decode images that can be decoded "quickly",
    *               just like startDecoding() does) are accepted; others will be
--- a/image/imgIRequest.idl
+++ b/image/imgIRequest.idl
@@ -14,17 +14,17 @@ interface nsIPrincipal;
 
 /**
  * imgIRequest interface
  *
  * @author Stuart Parmenter <stuart@mozilla.com>
  * @version 0.1
  * @see imagelib2
  */
-[scriptable, builtinclass, uuid(db0a945c-3883-424a-98d0-2ee0523b0255)]
+[scriptable, builtinclass, uuid(4cb01f0a-ef94-4345-a8d7-1a93f15ff548)]
 interface imgIRequest : nsIRequest
 {
   /**
    * the image container...
    * @return the image object associated with the request.
    * @attention NEED DOCS
    */
   readonly attribute imgIContainer image;
@@ -140,24 +140,25 @@ interface imgIRequest : nsIRequest
    * NOTE: You should not use this in any new code; instead, use cancel(). Note
    * that cancel() is asynchronous, which means that some time after you call
    * it, the listener/observer will get an OnStopRequest(). This means that, if
    * you're the observer, you can't call cancel() from your destructor.
    */
   void cancelAndForgetObserver(in nsresult aStatus);
 
   /**
-   * Requests a synchronous decode for the image.
+   * Requests a decode for the image.
    *
-   * imgIContainer has a startDecoding() method, but callers may want to request
+   * imgIContainer has a requestDecode() method, but callers may want to request
    * a decode before the container has necessarily been instantiated. Calling
-   * startDecoding() on the imgIRequest simply forwards along the request if the
-   * container already exists, or calls it once the container becomes available
-   * if it does not yet exist.
+   * requestDecode() on the imgIRequest simply forwards along the request if the
+   * container already exists, or calls it once it gets OnStartContainer if the
+   * container does not yet exist.
    */
+  void requestDecode();
   void startDecoding();
 
   /**
    * Locks an image. If the image does not exist yet, locks it once it becomes
    * available. The lock persists for the lifetime of the imgIRequest (until
    * unlockImage is called) even if the underlying image changes.
    *
    * If you don't call unlockImage() by the time this imgIRequest goes away, it
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -1585,17 +1585,17 @@ imgLoader::ValidateRequestWithNewChannel
     }
 
     if (*aProxyRequest) {
       imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest);
 
       // We will send notifications from imgCacheValidator::OnStartRequest().
       // In the mean time, we must defer notifications because we are added to
       // the imgRequest's proxy list, and we can get extra notifications
-      // resulting from methods such as StartDecoding(). See bug 579122.
+      // resulting from methods such as RequestDecode(). See bug 579122.
       proxy->SetNotificationsDeferred(true);
 
       // Attach the proxy without notifying
       request->GetValidator()->AddProxy(proxy);
     }
 
     return NS_SUCCEEDED(rv);
 
@@ -1661,17 +1661,17 @@ imgLoader::ValidateRequestWithNewChannel
       listener = corsproxy;
     }
 
     request->SetValidator(hvc);
 
     // We will send notifications from imgCacheValidator::OnStartRequest().
     // In the mean time, we must defer notifications because we are added to
     // the imgRequest's proxy list, and we can get extra notifications
-    // resulting from methods such as StartDecoding(). See bug 579122.
+    // resulting from methods such as RequestDecode(). See bug 579122.
     req->SetNotificationsDeferred(true);
 
     // Add the proxy without notifying
     hvc->AddProxy(req);
 
     mozilla::net::PredictorLearn(aURI, aInitialDocumentURI,
         nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, aLoadGroup);
 
--- a/image/imgRequest.cpp
+++ b/image/imgRequest.cpp
@@ -408,17 +408,17 @@ void
 imgRequest::ContinueEvict()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   RemoveFromCache();
 }
 
 void
-imgRequest::StartDecoding()
+imgRequest::RequestDecode()
 {
   MutexAutoLock lock(mMutex);
   mDecodeRequested = true;
 }
 
 bool
 imgRequest::IsDecodeRequested() const
 {
@@ -1048,17 +1048,17 @@ imgRequest::FinishPreparingForNewPart(co
     MOZ_ASSERT(progressTracker->HasImage());
   }
 
   if (aResult.mShouldResetCacheEntry) {
     ResetCacheEntry();
   }
 
   if (IsDecodeRequested()) {
-    aResult.mImage->StartDecoding();
+    aResult.mImage->RequestDecode();
   }
 }
 
 NS_IMETHODIMP
 imgRequest::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
                             nsIInputStream* aInStr, uint64_t aOffset,
                             uint32_t aCount)
 {
--- a/image/imgRequest.h
+++ b/image/imgRequest.h
@@ -90,17 +90,17 @@ public:
 
   // Called or dispatched by cancel for main thread only execution.
   void ContinueCancel(nsresult aStatus);
 
   // Called or dispatched by EvictFromCache for main thread only execution.
   void ContinueEvict();
 
   // Request that we start decoding the image as soon as data becomes available.
-  void StartDecoding();
+  void RequestDecode();
 
   inline uint64_t InnerWindowID() const {
     return mInnerWindowId;
   }
 
   // Set the cache validation information (expiry time, whether we must
   // validate, etc) on the cache entry based on the request information.
   // If this function is called multiple times, the information set earliest
@@ -215,17 +215,17 @@ private:
 
   void FinishPreparingForNewPart(const NewPartResult& aResult);
 
   void Cancel(nsresult aStatus);
 
   // Update the cache entry size based on the image container.
   void UpdateCacheEntrySize();
 
-  /// Returns true if StartDecoding() was called.
+  /// Returns true if RequestDecode() was called.
   bool IsDecodeRequested() const;
 
   // Weak reference to parent loader; this request cannot outlive its owner.
   imgLoader* mLoader;
   nsCOMPtr<nsIRequest> mRequest;
   // The original URI we were loaded with. This is the same as the URI we are
   // keyed on in the cache. We store a string here to avoid off main thread
   // refcounting issues with nsStandardURL.
--- a/image/imgRequestProxy.cpp
+++ b/image/imgRequestProxy.cpp
@@ -203,16 +203,25 @@ imgRequestProxy::ChangeOwner(imgRequest*
   while (mLockCount) {
     UnlockImage();
   }
 
   // If we're holding animation requests, undo them.
   uint32_t oldAnimationConsumers = mAnimationConsumers;
   ClearAnimationConsumers();
 
+  // Were we decoded before?
+  bool wasDecoded = false;
+  RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
+  if (progressTracker->HasImage() &&
+      progressTracker->GetImageStatus() &
+        imgIRequest::STATUS_FRAME_COMPLETE) {
+    wasDecoded = true;
+  }
+
   GetOwner()->RemoveProxy(this, NS_IMAGELIB_CHANGING_OWNER);
 
   mBehaviour->SetOwner(aNewOwner);
 
   // If we were locked, apply the locks here
   for (uint32_t i = 0; i < oldLockCount; i++) {
     LockImage();
   }
@@ -221,19 +230,19 @@ imgRequestProxy::ChangeOwner(imgRequest*
   // do this *after* RemoveProxy, which clears out animation consumers
   // (see bug 601723).
   for (uint32_t i = 0; i < oldAnimationConsumers; i++) {
     IncrementAnimationConsumers();
   }
 
   GetOwner()->AddProxy(this);
 
-  // If we'd previously requested a synchronous decode, request a decode on the
-  // new image.
-  if (mDecodeRequested) {
+  // If we were decoded, or if we'd previously requested a decode, request a
+  // decode on the new image
+  if (wasDecoded || mDecodeRequested) {
     StartDecoding();
   }
 
   return NS_OK;
 }
 
 void
 imgRequestProxy::AddToLoadGroup()
@@ -366,23 +375,42 @@ imgRequestProxy::StartDecoding()
   mDecodeRequested = true;
 
   RefPtr<Image> image = GetImage();
   if (image) {
     return image->StartDecoding();
   }
 
   if (GetOwner()) {
-    GetOwner()->StartDecoding();
+    GetOwner()->RequestDecode();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+imgRequestProxy::RequestDecode()
+{
+  // Flag this, so we know to transfer the request if our owner changes
+  mDecodeRequested = true;
+
+  RefPtr<Image> image = GetImage();
+  if (image) {
+    return image->RequestDecode();
+  }
+
+  if (GetOwner()) {
+    GetOwner()->RequestDecode();
+  }
+
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
 imgRequestProxy::LockImage()
 {
   mLockCount++;
   RefPtr<Image> image = GetImage();
   if (image) {
     return image->LockImage();
   }
   return NS_OK;
--- a/image/test/unit/image_load_helpers.js
+++ b/image/test/unit/image_load_helpers.js
@@ -43,16 +43,22 @@ function ImageListener(start_callback, s
     do_check_false(this.synchronous);
 
     this.state |= DECODE_COMPLETE;
   }
   this.loadComplete = function onLoadcomplete(aRequest)
   {
     do_check_false(this.synchronous);
 
+    try {
+      aRequest.requestDecode();
+    } catch (e) {
+      do_print("requestDecode threw " + e);
+    }
+
     this.state |= LOAD_COMPLETE;
 
     if (this.stop_callback)
       this.stop_callback(this, aRequest);
   }
   this.frameUpdate = function onFrameUpdate(aRequest)
   {
   }
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -163,17 +163,16 @@
 #include "Layers.h"
 #include "LayerTreeInvalidation.h"
 #include "mozilla/css/ImageLoader.h"
 #include "mozilla/dom/DocumentTimeline.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "nsCanvasFrame.h"
 #include "nsIImageLoadingContent.h"
-#include "nsImageFrame.h"
 #include "nsIScreen.h"
 #include "nsIScreenManager.h"
 #include "nsPlaceholderFrame.h"
 #include "nsTransitionManager.h"
 #include "ChildIterator.h"
 #include "RestyleManager.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIDragSession.h"
@@ -10697,33 +10696,17 @@ PresShell::SetIsActive(bool aIsActive)
 /*
  * Determines the current image locking state. Called when one of the
  * dependent factors changes.
  */
 nsresult
 PresShell::UpdateImageLockingState()
 {
   // We're locked if we're both thawed and active.
-  bool locked = !mFrozen && mIsActive;
-
-  nsresult rv = mDocument->SetImageLockingState(locked);
-
-  if (locked) {
-    // Request decodes for visible images; we want to start decoding as
-    // quickly as possible when we get foregrounded to minimize flashing.
-    for (auto iter = mVisibleImages.Iter(); !iter.Done(); iter.Next()) {
-      nsCOMPtr<nsIContent> content = do_QueryInterface(iter.Get()->GetKey());
-      nsImageFrame* imageFrame = do_QueryFrame(content->GetPrimaryFrame());
-      if (imageFrame) {
-        imageFrame->MaybeDecodeForPredictedSize();
-      }
-    }
-  }
-
-  return rv;
+  return mDocument->SetImageLockingState(!mFrozen && mIsActive);
 }
 
 PresShell*
 PresShell::GetRootPresShell()
 {
   if (mPresContext) {
     nsPresContext* rootPresContext = mPresContext->GetRootPresContext();
     if (rootPresContext) {
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -707,30 +707,19 @@ nsBulletFrame::Notify(imgIRequest *aRequ
       RegisterImageRequest(/* aKnownToBeAnimated = */ true);
     }
   }
 
   if (aType == imgINotificationObserver::LOAD_COMPLETE) {
     // Unconditionally start decoding for now.
     // XXX(seth): We eventually want to decide whether to do this based on
     // visibility. We should get that for free from bug 1091236.
-    nsCOMPtr<imgIContainer> container;
-    aRequest->GetImage(getter_AddRefs(container));
-    if (container) {
-      // Retrieve the intrinsic size of the image.
-      int32_t width = 0;
-      int32_t height = 0;
-      container->GetWidth(&width);
-      container->GetHeight(&height);
-
-      // Request a decode at that size.
-      container->RequestDecodeForSize(IntSize(width, height),
-                                      imgIContainer::DECODE_FLAGS_DEFAULT);
+    if (aRequest == mImageRequest) {
+      mImageRequest->RequestDecode();
     }
-
     InvalidateFrame();
   }
 
   if (aType == imgINotificationObserver::DECODE_COMPLETE) {
     if (nsIDocument* parent = GetOurCurrentDoc()) {
       nsCOMPtr<imgIContainer> container;
       aRequest->GetImage(getter_AddRefs(container));
       if (container) {
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1809,21 +1809,17 @@ nsImageFrame::BuildDisplayList(nsDisplay
       // indicating the status
       aLists.Content()->AppendNewToTop(new (aBuilder)
         nsDisplayAltFeedback(aBuilder, this));
 
       // This image is visible (we are being asked to paint it) but it's not
       // decoded yet. And we are not going to ask the image to draw, so this
       // may be the only chance to tell it that it should decode.
       if (currentRequest) {
-        uint32_t status = 0;
-        currentRequest->GetImageStatus(&status);
-        if (!(status & imgIRequest::STATUS_DECODE_COMPLETE)) {
-          MaybeDecodeForPredictedSize();
-        }
+        currentRequest->RequestDecode();
       }
     } else {
       aLists.Content()->AppendNewToTop(new (aBuilder)
         nsDisplayImage(aBuilder, this, mImage));
 
       // If we were previously displaying an icon, we're not anymore
       if (mDisplayingIcon) {
         gIconLoad->RemoveIconObserver(this);
@@ -2294,23 +2290,25 @@ nsresult nsImageFrame::LoadIcons(nsPresC
   nsresult rv;
   // create a loader and load the images
   rv = LoadIcon(loadingSrc,
                 aPresContext,
                 getter_AddRefs(gIconLoad->mLoadingImage));
   if (NS_FAILED(rv)) {
     return rv;
   }
+  gIconLoad->mLoadingImage->RequestDecode();
 
   rv = LoadIcon(brokenSrc,
                 aPresContext,
                 getter_AddRefs(gIconLoad->mBrokenImage));
   if (NS_FAILED(rv)) {
     return rv;
   }
+  gIconLoad->mBrokenImage->RequestDecode();
 
   return rv;
 }
 
 NS_IMPL_ISUPPORTS(nsImageFrame::IconLoad, nsIObserver,
                   imgINotificationObserver)
 
 static const char* kIconLoadPrefs[] = {
@@ -2370,45 +2368,23 @@ void nsImageFrame::IconLoad::GetPrefs()
   mPrefShowPlaceholders =
     Preferences::GetBool("browser.display.show_image_placeholders", true);
 
   mPrefShowLoadingPlaceholder =
     Preferences::GetBool("browser.display.show_loading_image_placeholder", true);
 }
 
 NS_IMETHODIMP
-nsImageFrame::IconLoad::Notify(imgIRequest* aRequest,
-                               int32_t aType,
-                               const nsIntRect* aData)
+nsImageFrame::IconLoad::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
 {
-  MOZ_ASSERT(aRequest);
-
   if (aType != imgINotificationObserver::LOAD_COMPLETE &&
       aType != imgINotificationObserver::FRAME_UPDATE) {
     return NS_OK;
   }
 
-  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
-    nsCOMPtr<imgIContainer> image;
-    aRequest->GetImage(getter_AddRefs(image));
-    if (!image) {
-      return NS_ERROR_FAILURE;
-    }
-
-    // Retrieve the image's intrinsic size.
-    int32_t width = 0;
-    int32_t height = 0;
-    image->GetWidth(&width);
-    image->GetHeight(&height);
-
-    // Request a decode at that size.
-    image->RequestDecodeForSize(IntSize(width, height),
-                                imgIContainer::DECODE_FLAGS_DEFAULT);
-  }
-
   nsTObserverArray<nsImageFrame*>::ForwardIterator iter(mIconObservers);
   nsImageFrame *frame;
   while (iter.HasMore()) {
     frame = iter.GetNext();
     frame->InvalidateFrame();
   }
 
   return NS_OK;
--- a/layout/generic/nsImageFrame.h
+++ b/layout/generic/nsImageFrame.h
@@ -225,17 +225,16 @@ protected:
    * for our image at the size we predict it will be drawn next time it's
    * painted.
    */
   void MaybeDecodeForPredictedSize();
 
 protected:
   friend class nsImageListener;
   friend class nsImageLoadingContent;
-  friend class PresShell;
 
   nsresult OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage);
   nsresult OnFrameUpdate(imgIRequest* aRequest, const nsIntRect* aRect);
   nsresult OnLoadComplete(imgIRequest* aRequest, nsresult aStatus);
 
   /**
    * Notification that aRequest will now be the current request.
    */
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -957,16 +957,19 @@ struct nsStyleBorder {
       (HasVisibleStyle(aSide) ? mBorder.Side(aSide) : 0);
   }
 
   inline bool IsBorderImageLoaded() const
   {
     return mBorderImageSource.IsLoaded();
   }
 
+  // Defined in nsStyleStructInlines.h
+  inline nsresult RequestDecode();
+
   void GetBorderColor(mozilla::css::Side aSide, nscolor& aColor,
                       bool& aForeground) const
   {
     aForeground = false;
     NS_ASSERTION(aSide <= NS_SIDE_LEFT, "bad side");
     if ((mBorderStyle[aSide] & BORDER_COLOR_SPECIAL) == 0)
       aColor = mBorderColor[aSide];
     else if (mBorderStyle[aSide] & BORDER_COLOR_FOREGROUND)
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4147,16 +4147,19 @@ pref("image.mem.surfacecache.discard_fac
 // How many threads we'll use for multithreaded decoding. If < 0, will be
 // automatically determined based on the system's number of cores.
 pref("image.multithreaded_decoding.limit", -1);
 
 // Limit for the canvas image cache. 0 means we don't limit the size of the
 // cache.
 pref("canvas.image.cache.limit", 0);
 
+// How many images to eagerly decode on a given page. 0 means "no limit".
+pref("image.onload.decode.limit", 0);
+
 // WebGL prefs
 #ifdef ANDROID
 // Disable MSAA on mobile.
 pref("gl.msaa-level", 0);
 #else
 pref("gl.msaa-level", 2);
 #endif
 pref("gl.require-hardware", false);