Bug 1118655 - Use decode-on-draw only, and ignore RequestDecode and the like, when APZ and downscale-during-decode are enabled. r=tn a=lmandel
authorSeth Fowler <seth@mozilla.com>
Mon, 19 Jan 2015 15:46:55 -0800
changeset 249823 d55d3ca3e6853d17b3b51de4ed6f03f16e8baa33
parent 249822 86dd3d6b0ecc4e70fa67e09694c5cf19675e31b8
child 249824 5d1ed8fe429396a0f8e0acc303714b85558c85b1
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn, lmandel
bugs1118655
milestone37.0a2
Bug 1118655 - Use decode-on-draw only, and ignore RequestDecode and the like, when APZ and downscale-during-decode are enabled. r=tn a=lmandel
b2g/app/b2g.js
image/src/ImageFactory.cpp
image/src/RasterImage.cpp
image/src/RasterImage.h
layout/generic/nsImageFrame.cpp
layout/xul/nsImageBoxFrame.cpp
mobile/android/app/mobile.js
modules/libpref/init/all.js
toolkit/system/gnome/nsAlertsIconListener.cpp
widget/cocoa/nsMenuItemIconX.mm
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -316,17 +316,17 @@ pref("media.cache_readahead_limit", 30);
 // Enable/Disable Gonk Decoder Module
 pref("media.fragmented-mp4.gonk.enabled", true);
 #endif
 // The default number of decoded video frames that are enqueued in
 // MediaDecoderReader's mVideoQueue.
 pref("media.video-queue.default-size", 3);
 
 // optimize images' memory usage
-pref("image.mem.decodeondraw", true);
+pref("image.mem.decodeondraw", false);
 pref("image.mem.allow_locking_in_content_processes", false); /* don't allow image locking */
 // 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.
--- a/image/src/ImageFactory.cpp
+++ b/image/src/ImageFactory.cpp
@@ -40,17 +40,18 @@ ShouldDownscaleDuringDecode(const nsCStr
 
 static uint32_t
 ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart)
 {
   nsresult rv;
 
   // We default to the static globals.
   bool isDiscardable = gfxPrefs::ImageMemDiscardable();
-  bool doDecodeOnDraw = gfxPrefs::ImageMemDecodeOnDraw();
+  bool doDecodeOnDraw = gfxPrefs::ImageMemDecodeOnDraw() &&
+                        gfxPrefs::AsyncPanZoomEnabled();
   bool doDownscaleDuringDecode = gfxPrefs::ImageDownscaleDuringDecodeEnabled();
 
   // We want UI to be as snappy as possible and not to flicker. Disable
   // discarding and decode-on-draw for chrome URLS.
   bool isChrome = false;
   rv = uri->SchemeIs("chrome", &isChrome);
   if (NS_SUCCEEDED(rv) && isChrome) {
     isDiscardable = doDecodeOnDraw = false;
@@ -59,19 +60,22 @@ ComputeImageFlags(ImageURL* uri, const n
   // We don't want resources like the "loading" icon to be discardable or
   // decode-on-draw either.
   bool isResource = false;
   rv = uri->SchemeIs("resource", &isResource);
   if (NS_SUCCEEDED(rv) && isResource) {
     isDiscardable = doDecodeOnDraw = false;
   }
 
-  // Downscale-during-decode is only enabled for certain content types.
-  if (doDownscaleDuringDecode && !ShouldDownscaleDuringDecode(aMimeType)) {
+  // Downscale-during-decode and decode-on-draw are only enabled for certain
+  // content types.
+  if ((doDownscaleDuringDecode || doDecodeOnDraw) &&
+      !ShouldDownscaleDuringDecode(aMimeType)) {
     doDownscaleDuringDecode = false;
+    doDecodeOnDraw = false;
   }
 
   // For multipart/x-mixed-replace, we basically want a direct channel to the
   // decoder. Disable everything for this case.
   if (isMultiPart) {
     isDiscardable = doDecodeOnDraw = doDownscaleDuringDecode = false;
   }
 
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -30,16 +30,17 @@
 #include "nsBMPDecoder.h"
 #include "nsICODecoder.h"
 #include "nsIconDecoder.h"
 
 #include "gfxContext.h"
 
 #include "mozilla/gfx/2D.h"
 #include "mozilla/DebugOnly.h"
+#include "mozilla/Likely.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/Move.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Services.h"
 #include <stdint.h>
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/ClearOnShutdown.h"
@@ -266,16 +267,17 @@ RasterImage::RasterImage(ProgressTracker
   mDecodeCount(0),
   mRequestedSampleSize(0),
 #ifdef DEBUG
   mFramesNotified(0),
 #endif
   mSourceBuffer(new SourceBuffer()),
   mFrameCount(0),
   mHasSize(false),
+  mBlockedOnload(false),
   mDecodeOnDraw(false),
   mTransient(false),
   mDiscardable(false),
   mHasSourceData(false),
   mHasBeenDecoded(false),
   mPendingAnimation(false),
   mAnimationFinished(false),
   mWantFullDecode(false)
@@ -1156,36 +1158,68 @@ RasterImage::OnImageDataComplete(nsIRequ
     finalStatus = aStatus;
   }
 
   // If loading failed, report an error.
   if (NS_FAILED(finalStatus)) {
     DoError();
   }
 
-  // Notify our listeners, which will fire this image's load event.
   MOZ_ASSERT(mHasSize || mError, "Need to know size before firing load event");
   MOZ_ASSERT(!mHasSize ||
              (mProgressTracker->GetProgress() & FLAG_SIZE_AVAILABLE),
              "Should have notified that the size is available if we have it");
+
   Progress loadProgress = LoadCompleteProgress(aLastPart, mError, finalStatus);
+
+  if (mBlockedOnload) {
+    // For decode-on-draw images, we want to send notifications as if we've
+    // already finished decoding. Otherwise some observers will never even try
+    // to draw.
+    MOZ_ASSERT(mDecodeOnDraw, "Blocked onload but not decode-on-draw");
+    loadProgress |= FLAG_FRAME_COMPLETE |
+                    FLAG_DECODE_COMPLETE |
+                    FLAG_ONLOAD_UNBLOCKED;
+  }
+
+  // Notify our listeners, which will fire this image's load event.
   NotifyProgress(loadProgress);
 
   return finalStatus;
 }
 
+void
+RasterImage::BlockOnloadForDecodeOnDraw()
+{
+  if (mHasSourceData) {
+    // OnImageDataComplete got called before we got to run. No point in blocking
+    // onload now.
+    return;
+  }
+
+  // Block onload. We'll unblock it in OnImageDataComplete.
+  mBlockedOnload = true;
+  NotifyProgress(FLAG_DECODE_STARTED | FLAG_ONLOAD_BLOCKED);
+}
+
 nsresult
 RasterImage::OnImageDataAvailable(nsIRequest*,
                                   nsISupports*,
                                   nsIInputStream* aInStr,
-                                  uint64_t,
+                                  uint64_t aOffset,
                                   uint32_t aCount)
 {
   nsresult rv;
 
+  if (MOZ_UNLIKELY(mDecodeOnDraw && aOffset == 0)) {
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewRunnableMethod(this, &RasterImage::BlockOnloadForDecodeOnDraw);
+    NS_DispatchToMainThread(runnable);
+  }
+
   // WriteToSourceBuffer always consumes everything it gets if it doesn't run
   // out of memory.
   uint32_t bytesRead;
   rv = aInStr->ReadSegments(WriteToSourceBuffer, this, aCount, &bytesRead);
 
   NS_ABORT_IF_FALSE(bytesRead == aCount || HasError() || NS_FAILED(rv),
     "WriteToSourceBuffer should consume everything if ReadSegments succeeds or "
     "the image must be in error!");
@@ -1395,28 +1429,38 @@ RasterImage::WantDecodedFrames(const nsI
   Decode(DecodeStrategy::ASYNC, Some(aSize), aFlags);
 }
 
 //******************************************************************************
 /* void requestDecode() */
 NS_IMETHODIMP
 RasterImage::RequestDecode()
 {
+  // For decode-on-draw images, we only act on RequestDecodeForSize.
+  if (mDecodeOnDraw) {
+    return NS_OK;
+  }
+
   return RequestDecodeForSize(mSize, DECODE_FLAGS_DEFAULT);
 }
 
 /* void startDecode() */
 NS_IMETHODIMP
 RasterImage::StartDecoding()
 {
   if (!NS_IsMainThread()) {
     return NS_DispatchToMainThread(
       NS_NewRunnableMethod(this, &RasterImage::StartDecoding));
   }
 
+  // For decode-on-draw images, we only act on RequestDecodeForSize.
+  if (mDecodeOnDraw) {
+    return NS_OK;
+  }
+
   return RequestDecodeForSize(mSize, FLAG_SYNC_DECODE);
 }
 
 NS_IMETHODIMP
 RasterImage::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -1447,17 +1491,17 @@ RasterImage::RequestDecodeForSize(const 
 
   return NS_OK;
 }
 
 bool
 RasterImage::IsDecoded()
 {
   // XXX(seth): We need to get rid of this; it's not reliable.
-  return mHasBeenDecoded || mError;
+  return mHasBeenDecoded || mError || (mDecodeOnDraw && mHasSourceData);
 }
 
 NS_IMETHODIMP
 RasterImage::Decode(DecodeStrategy aStrategy,
                     const Maybe<nsIntSize>& aSize,
                     uint32_t aFlags)
 {
   MOZ_ASSERT(!aSize || NS_IsMainThread());
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -237,16 +237,18 @@ public:
                                         nsIInputStream* aInStr,
                                         uint64_t aSourceOffset,
                                         uint32_t aCount) MOZ_OVERRIDE;
   virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
                                        nsISupports* aContext,
                                        nsresult aStatus,
                                        bool aLastPart) MOZ_OVERRIDE;
 
+  void BlockOnloadForDecodeOnDraw();
+
   /**
    * A hint of the number of bytes of source data that the image contains. If
    * called early on, this can help reduce copying and reallocations by
    * appropriately preallocating the source data buffer.
    *
    * We take this approach rather than having the source data management code do
    * something more complicated (like chunklisting) because HTTP is by far the
    * dominant source of images, and the Content-Length header is quite reliable.
@@ -382,16 +384,17 @@ private: // data
   // The source data for this image.
   nsRefPtr<SourceBuffer>     mSourceBuffer;
 
   // The number of frames this image has.
   uint32_t                   mFrameCount;
 
   // Boolean flags (clustered together to conserve space):
   bool                       mHasSize:1;       // Has SetSize() been called?
+  bool                       mBlockedOnload:1; // Did send BLOCK_ONLOAD?
   bool                       mDecodeOnDraw:1;  // Decoding on draw?
   bool                       mTransient:1;     // Is the image short-lived?
   bool                       mDiscardable:1;   // Is container discardable?
   bool                       mHasSourceData:1; // Do we have source data?
   bool                       mHasBeenDecoded:1; // Decoded at least once?
   bool                       mDownscaleDuringDecode:1;
 
   // Whether we're waiting to start animation. If we get a StartAnimation() call
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1376,18 +1376,17 @@ nsDisplayImage::ComputeInvalidationRegio
   nsDisplayImageContainer::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
 }
 
 already_AddRefed<ImageContainer>
 nsDisplayImage::GetContainer(LayerManager* aManager,
                              nsDisplayListBuilder* aBuilder)
 {
   nsRefPtr<ImageContainer> container;
-  nsresult rv = mImage->GetImageContainer(aManager, getter_AddRefs(container));
-  NS_ENSURE_SUCCESS(rv, nullptr);
+  mImage->GetImageContainer(aManager, getter_AddRefs(container));
   return container.forget();
 }
 
 gfxRect
 nsDisplayImage::GetDestRect()
 {
   int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
 
--- a/layout/xul/nsImageBoxFrame.cpp
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -429,18 +429,17 @@ nsImageBoxFrame::GetContainer(LayerManag
 
   nsCOMPtr<imgIContainer> imgCon;
   mImageRequest->GetImage(getter_AddRefs(imgCon));
   if (!imgCon) {
     return nullptr;
   }
   
   nsRefPtr<ImageContainer> container;
-  nsresult rv = imgCon->GetImageContainer(aManager, getter_AddRefs(container));
-  NS_ENSURE_SUCCESS(rv, nullptr);
+  imgCon->GetImageContainer(aManager, getter_AddRefs(container));
   return container.forget();
 }
 
 
 //
 // DidSetStyleContext
 //
 // When the style context changes, make sure that all of our image is up to date.
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -564,17 +564,17 @@ pref("media.video-queue.default-size", 3
 
 // Enable the MediaCodec PlatformDecoderModule by default.
 pref("media.fragmented-mp4.exposed", true);
 pref("media.fragmented-mp4.enabled", true);
 pref("media.fragmented-mp4.android-media-codec.enabled", true);
 pref("media.fragmented-mp4.android-media-codec.preferred", true);
 
 // optimize images memory usage
-pref("image.mem.decodeondraw", true);
+pref("image.mem.decodeondraw", false);
 
 #ifdef NIGHTLY_BUILD
 // Shumway component (SWF player) is disabled by default. Also see bug 904346.
 pref("shumway.disabled", true);
 #endif
 
 // enable touch events interfaces
 pref("dom.w3c_touch_events.enabled", 1);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3779,17 +3779,17 @@ pref("image.high_quality_upscaling.max_s
 //
 
 // Discards inactive image frames and re-decodes them on demand from
 // compressed data.
 pref("image.mem.discardable", true);
 
 // Prevents images from automatically being decoded on load, instead allowing
 // them to be decoded on demand when they are drawn.
-pref("image.mem.decodeondraw", true);
+pref("image.mem.decodeondraw", false);
 
 // Allows image locking of decoded image data in content processes.
 pref("image.mem.allow_locking_in_content_processes", true);
 
 // Chunk size for calls to the image decoders
 pref("image.mem.decode_bytes_at_a_time", 16384);
 
 // Minimum timeout for expiring unused images from the surface cache, in
--- a/toolkit/system/gnome/nsAlertsIconListener.cpp
+++ b/toolkit/system/gnome/nsAlertsIconListener.cpp
@@ -130,16 +130,26 @@ nsAlertsIconListener::OnLoadComplete(img
     // We have an error getting the image. Display the notification with no icon.
     ShowAlert(nullptr);
 
     // Cancel any pending request
     mIconRequest->Cancel(NS_BINDING_ABORTED);
     mIconRequest = nullptr;
   }
 
+  nsCOMPtr<imgIContainer> image;
+  rv = aRequest->GetImage(getter_AddRefs(image));
+  MOZ_ASSERT(image);
+
+  // Ask the image to decode at its intrinsic size.
+  int32_t width = 0, height = 0;
+  image->GetWidth(&width);
+  image->GetHeight(&height);
+  image->RequestDecodeForSize(nsIntSize(width, height), imgIContainer::FLAG_NONE);
+
   return NS_OK;
 }
 
 nsresult
 nsAlertsIconListener::OnFrameComplete(imgIRequest* aRequest)
 {
   NS_ASSERTION(mIconRequest == aRequest, "aRequest does not match!");
 
@@ -222,18 +232,16 @@ nsAlertsIconListener::StartRequest(const
                                    this, nullptr,
                                    aInPrivateBrowsing ? nsIRequest::LOAD_ANONYMOUS :
                                                         nsIRequest::LOAD_NORMAL,
                                    nullptr, 0 /* use default */,
                                    getter_AddRefs(mIconRequest));
   if (NS_FAILED(rv))
     return rv;
 
-  mIconRequest->StartDecoding();
-
   return NS_OK;
 }
 
 void
 nsAlertsIconListener::SendCallback()
 {
   if (mAlertListener)
     mAlertListener->Observe(nullptr, "alertclickcallback", mAlertCookie.get());
--- a/widget/cocoa/nsMenuItemIconX.mm
+++ b/widget/cocoa/nsMenuItemIconX.mm
@@ -309,33 +309,51 @@ nsMenuItemIconX::LoadIcon(nsIURI* aIconU
   nsresult rv = loader->LoadImage(aIconURI, nullptr, nullptr,
                                   mozilla::net::RP_Default,
                                   nullptr, loadGroup, this,
                                   nullptr, nsIRequest::LOAD_NORMAL, nullptr,
                                   nsIContentPolicy::TYPE_IMAGE, EmptyString(),
                                   getter_AddRefs(mIconRequest));
   if (NS_FAILED(rv)) return rv;
 
-  // We need to request the icon be decoded (bug 573583, bug 705516).
-  mIconRequest->StartDecoding();
-
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 //
 // imgINotificationObserver
 //
 
 NS_IMETHODIMP
 nsMenuItemIconX::Notify(imgIRequest* aRequest,
                         int32_t aType,
                         const nsIntRect* aData)
 {
+  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+    // Make sure the image loaded successfully.
+    uint32_t status = imgIRequest::STATUS_ERROR;
+    if (NS_FAILED(aRequest->GetImageStatus(&status)) ||
+        (status & imgIRequest::STATUS_ERROR)) {
+      mIconRequest->Cancel(NS_BINDING_ABORTED);
+      mIconRequest = nullptr;
+      return NS_ERROR_FAILURE;
+    }
+
+    nsCOMPtr<imgIContainer> image;
+    aRequest->GetImage(getter_AddRefs(image));
+    MOZ_ASSERT(image);
+
+    // Ask the image to decode at its intrinsic size.
+    int32_t width = 0, height = 0;
+    image->GetWidth(&width);
+    image->GetHeight(&height);
+    image->RequestDecodeForSize(nsIntSize(width, height), imgIContainer::FLAG_NONE);
+  }
+
   if (aType == imgINotificationObserver::FRAME_COMPLETE) {
     return OnFrameComplete(aRequest);
   }
 
   if (aType == imgINotificationObserver::DECODE_COMPLETE) {
     if (mIconRequest && mIconRequest == aRequest) {
       mIconRequest->Cancel(NS_BINDING_ABORTED);
       mIconRequest = nullptr;