From 12931c48ec338211f0f1298e8e48b3e2c4085cac Mon Sep 17 00:00:00 2001 From: Seth Fowler Date: Sun, 11 Jan 2015 05:34:20 -0800 Subject: [PATCH] Bug 1079627 (Part 3) - Support multiple decoders for a single RasterImage. r=tn --- gfx/thebes/gfxPrefs.h | 5 +- image/src/DecodePool.cpp | 327 +++---- image/src/DecodePool.h | 104 +-- image/src/Decoder.cpp | 129 ++- image/src/Decoder.h | 107 ++- image/src/FrameAnimator.cpp | 5 + image/src/RasterImage.cpp | 1085 ++++++----------------- image/src/RasterImage.h | 135 ++- image/test/crashtests/crashtests.list | 2 +- image/test/reftest/gif/reftest.list | 2 +- image/test/reftest/ico/cur/reftest.list | 2 +- layout/reftests/bugs/reftest.list | 2 +- modules/libpref/init/all.js | 3 - toolkit/content/tests/chrome/chrome.ini | 1 + 14 files changed, 708 insertions(+), 1201 deletions(-) diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index 08dbc4436d2..3c2810ca08d 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -236,16 +236,15 @@ private: DECL_GFX_PREF(Live, "image.high_quality_downscaling.enabled", ImageHQDownscalingEnabled, bool, false); DECL_GFX_PREF(Live, "image.high_quality_downscaling.min_factor", ImageHQDownscalingMinFactor, uint32_t, 1000); DECL_GFX_PREF(Live, "image.high_quality_upscaling.max_size", ImageHQUpscalingMaxSize, uint32_t, 20971520); - DECL_GFX_PREF(Live, "image.mem.decode_bytes_at_a_time", ImageMemDecodeBytesAtATime, uint32_t, 200000); + DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time", ImageMemDecodeBytesAtATime, uint32_t, 200000); DECL_GFX_PREF(Live, "image.mem.decodeondraw", ImageMemDecodeOnDraw, bool, false); DECL_GFX_PREF(Live, "image.mem.discardable", ImageMemDiscardable, bool, false); - DECL_GFX_PREF(Live, "image.mem.max_ms_before_yield", ImageMemMaxMSBeforeYield, uint32_t, 400); DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1); DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb", ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024); DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000); DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor", ImageMemSurfaceCacheSizeFactor, uint32_t, 64); DECL_GFX_PREF(Live, "image.mozsamplesize.enabled", ImageMozSampleSizeEnabled, bool, false); - DECL_GFX_PREF(Live, "image.multithreaded_decoding.limit", ImageMTDecodingLimit, int32_t, -1); + DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit", ImageMTDecodingLimit, int32_t, -1); DECL_GFX_PREF(Once, "layers.acceleration.disabled", LayersAccelerationDisabled, bool, false); DECL_GFX_PREF(Live, "layers.acceleration.draw-fps", LayersDrawFPS, bool, false); diff --git a/image/src/DecodePool.cpp b/image/src/DecodePool.cpp index 027414a5ecf..e747454e8c6 100644 --- a/image/src/DecodePool.cpp +++ b/image/src/DecodePool.cpp @@ -12,7 +12,6 @@ #include "nsCOMPtr.h" #include "nsIObserverService.h" #include "nsIThreadPool.h" -#include "nsProxyRelease.h" #include "nsXPCOMCIDInternal.h" #include "prsystem.h" @@ -42,102 +41,88 @@ public: * Called by the DecodePool when it's done some significant portion of * decoding, so that progress can be recorded and notifications can be sent. */ - static void Dispatch(RasterImage* aImage) + static void Dispatch(RasterImage* aImage, + Progress aProgress, + const nsIntRect& aInvalidRect, + uint32_t aFlags) { - nsCOMPtr worker = new NotifyProgressWorker(aImage); + MOZ_ASSERT(aImage); + + nsCOMPtr worker = + new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aFlags); NS_DispatchToMainThread(worker); } NS_IMETHOD Run() MOZ_OVERRIDE { MOZ_ASSERT(NS_IsMainThread()); - ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor); - - mImage->FinishedSomeDecoding(ShutdownReason::DONE); - + mImage->NotifyProgress(mProgress, mInvalidRect, mFlags); return NS_OK; } private: - explicit NotifyProgressWorker(RasterImage* aImage) + NotifyProgressWorker(RasterImage* aImage, Progress aProgress, + const nsIntRect& aInvalidRect, uint32_t aFlags) : mImage(aImage) + , mProgress(aProgress) + , mInvalidRect(aInvalidRect) + , mFlags(aFlags) { } nsRefPtr mImage; + const Progress mProgress; + const nsIntRect mInvalidRect; + const uint32_t mFlags; +}; + +class NotifyDecodeCompleteWorker : public nsRunnable +{ +public: + /** + * Called by the DecodePool when decoding is complete, so that final cleanup + * can be performed. + */ + static void Dispatch(Decoder* aDecoder) + { + MOZ_ASSERT(aDecoder); + + nsCOMPtr worker = new NotifyDecodeCompleteWorker(aDecoder); + NS_DispatchToMainThread(worker); + } + + NS_IMETHOD Run() MOZ_OVERRIDE + { + MOZ_ASSERT(NS_IsMainThread()); + DecodePool::Singleton()->NotifyDecodeComplete(mDecoder); + return NS_OK; + } + +private: + explicit NotifyDecodeCompleteWorker(Decoder* aDecoder) + : mDecoder(aDecoder) + { } + + nsRefPtr mDecoder; }; class DecodeWorker : public nsRunnable { public: - explicit DecodeWorker(RasterImage* aImage) - : mImage(aImage) - { } + explicit DecodeWorker(Decoder* aDecoder) + : mDecoder(aDecoder) + { + MOZ_ASSERT(mDecoder); + } NS_IMETHOD Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); - - ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor); - - // If we were interrupted, we shouldn't do any work. - if (mImage->mDecodeStatus == DecodeStatus::STOPPED) { - NotifyProgressWorker::Dispatch(mImage); - return NS_OK; - } - - // If someone came along and synchronously decoded us, there's nothing for us to do. - if (!mImage->mDecoder || mImage->IsDecodeFinished()) { - NotifyProgressWorker::Dispatch(mImage); - return NS_OK; - } - - mImage->mDecodeStatus = DecodeStatus::ACTIVE; - - size_t oldByteCount = mImage->mDecoder->BytesDecoded(); - - size_t maxBytes = mImage->mSourceData.Length() - - mImage->mDecoder->BytesDecoded(); - DecodePool::Singleton()->DecodeSomeOfImage(mImage, DecodeUntil::DONE_BYTES, - maxBytes); - - size_t bytesDecoded = mImage->mDecoder->BytesDecoded() - oldByteCount; - - mImage->mDecodeStatus = DecodeStatus::WORK_DONE; - - if (mImage->mDecoder && - !mImage->mError && - !mImage->mPendingError && - !mImage->IsDecodeFinished() && - bytesDecoded < maxBytes && - bytesDecoded > 0) { - // We aren't finished decoding, and we have more data, so add this request - // to the back of the list. - DecodePool::Singleton()->RequestDecode(mImage); - } else { - // Nothing more for us to do - let everyone know what happened. - NotifyProgressWorker::Dispatch(mImage); - } - + DecodePool::Singleton()->Decode(mDecoder); return NS_OK; } -protected: - virtual ~DecodeWorker() - { - // Dispatch mImage to main thread to prevent mImage from being destructed by decode thread. - nsCOMPtr mainThread = do_GetMainThread(); - NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!"); - if (mainThread) { - // Handle ambiguous nsISupports inheritance - RasterImage* rawImg = nullptr; - mImage.swap(rawImg); - DebugOnly rv = NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg)); - MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread"); - } - } - private: - nsRefPtr mImage; + nsRefPtr mDecoder; }; #ifdef MOZ_NUWA_PROCESS @@ -194,13 +179,6 @@ DecodePool::Singleton() return sSingleton; } -already_AddRefed -DecodePool::GetEventTarget() -{ - nsCOMPtr target = do_QueryInterface(mThreadPool); - return target.forget(); -} - DecodePool::DecodePool() : mThreadPoolMutex("Thread Pool") { @@ -258,20 +236,11 @@ DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) } void -DecodePool::RequestDecode(RasterImage* aImage) +DecodePool::AsyncDecode(Decoder* aDecoder) { - MOZ_ASSERT(aImage->mDecoder); - aImage->mDecodingMonitor.AssertCurrentThreadIn(); + MOZ_ASSERT(aDecoder); - if (aImage->mDecodeStatus == DecodeStatus::PENDING || - aImage->mDecodeStatus == DecodeStatus::ACTIVE) { - // The image is already in our list of images to decode, or currently being - // decoded, so we don't have to do anything else. - return; - } - - aImage->mDecodeStatus = DecodeStatus::PENDING; - nsCOMPtr worker = new DecodeWorker(aImage); + nsCOMPtr worker = new DecodeWorker(aDecoder); // Dispatch to the thread pool if it exists. If it doesn't, we're currently // shutting down, so it's OK to just drop the job on the floor. @@ -282,136 +251,90 @@ DecodePool::RequestDecode(RasterImage* aImage) } void -DecodePool::DecodeABitOf(RasterImage* aImage) +DecodePool::SyncDecodeIfSmall(Decoder* aDecoder) { MOZ_ASSERT(NS_IsMainThread()); - aImage->mDecodingMonitor.AssertCurrentThreadIn(); + MOZ_ASSERT(aDecoder); - // If the image is waiting for decode work to be notified, go ahead and do that. - if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) { - aImage->FinishedSomeDecoding(); + if (aDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime())) { + Decode(aDecoder); + return; } - DecodeSomeOfImage(aImage); - - aImage->FinishedSomeDecoding(); - - // If we aren't yet finished decoding and we have more data in hand, add - // this request to the back of the priority list. - if (aImage->mDecoder && - !aImage->mError && - !aImage->IsDecodeFinished() && - aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded()) { - RequestDecode(aImage); - } + AsyncDecode(aDecoder); } -/* static */ void -DecodePool::StopDecoding(RasterImage* aImage) -{ - aImage->mDecodingMonitor.AssertCurrentThreadIn(); - - // If we haven't got a decode request, we're not currently decoding. (Having - // a decode request doesn't imply we *are* decoding, though.) - aImage->mDecodeStatus = DecodeStatus::STOPPED; -} - -nsresult -DecodePool::DecodeUntilSizeAvailable(RasterImage* aImage) +void +DecodePool::SyncDecodeIfPossible(Decoder* aDecoder) { MOZ_ASSERT(NS_IsMainThread()); - ReentrantMonitorAutoEnter lock(aImage->mDecodingMonitor); + Decode(aDecoder); +} - // If the image is waiting for decode work to be notified, go ahead and do that. - if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) { - nsresult rv = aImage->FinishedSomeDecoding(); - if (NS_FAILED(rv)) { - aImage->DoError(); - return rv; +already_AddRefed +DecodePool::GetEventTarget() +{ + MutexAutoLock threadPoolLock(mThreadPoolMutex); + nsCOMPtr target = do_QueryInterface(mThreadPool); + return target.forget(); +} + +already_AddRefed +DecodePool::CreateDecodeWorker(Decoder* aDecoder) +{ + MOZ_ASSERT(aDecoder); + nsCOMPtr worker = new DecodeWorker(aDecoder); + return worker.forget(); +} + +void +DecodePool::Decode(Decoder* aDecoder) +{ + MOZ_ASSERT(aDecoder); + + nsresult rv = aDecoder->Decode(); + + if (NS_SUCCEEDED(rv) && !aDecoder->GetDecodeDone()) { + if (aDecoder->HasProgress()) { + NotifyProgress(aDecoder); } - } - - nsresult rv = DecodeSomeOfImage(aImage, DecodeUntil::SIZE); - if (NS_FAILED(rv)) { - return rv; - } - - return aImage->FinishedSomeDecoding(); -} - -nsresult -DecodePool::DecodeSomeOfImage(RasterImage* aImage, - DecodeUntil aDecodeUntil /* = DecodeUntil::TIME */, - uint32_t bytesToDecode /* = 0 */) -{ - MOZ_ASSERT(aImage->mInitialized, "Worker active for uninitialized container"); - aImage->mDecodingMonitor.AssertCurrentThreadIn(); - - // If an error is flagged, it probably happened while we were waiting - // in the event queue. - if (aImage->mError) { - return NS_OK; - } - - // If there is an error worker pending (say because the main thread has enqueued - // another decode request for us before processing the error worker) then bail out. - if (aImage->mPendingError) { - return NS_OK; - } - - // If mDecoded or we don't have a decoder, we must have finished already (for - // example, a synchronous decode request came while the worker was pending). - if (!aImage->mDecoder || aImage->mDecoded) { - return NS_OK; - } - - nsRefPtr decoderKungFuDeathGrip = aImage->mDecoder; - - uint32_t maxBytes; - if (aImage->mDecoder->IsSizeDecode()) { - // Decode all available data if we're a size decode; they're cheap, and we - // want them to be more or less synchronous. - maxBytes = aImage->mSourceData.Length(); + // The decoder will ensure that a new worker gets enqueued to continue + // decoding when more data is available. } else { - // We're only guaranteed to decode this many bytes, so in particular, - // gfxPrefs::ImageMemDecodeBytesAtATime should be set high enough for us - // to read the size from most images. - maxBytes = gfxPrefs::ImageMemDecodeBytesAtATime(); + NotifyDecodeComplete(aDecoder); + } +} + +void +DecodePool::NotifyProgress(Decoder* aDecoder) +{ + MOZ_ASSERT(aDecoder); + + if (!NS_IsMainThread()) { + NotifyProgressWorker::Dispatch(aDecoder->GetImage(), + aDecoder->TakeProgress(), + aDecoder->TakeInvalidRect(), + aDecoder->GetDecodeFlags()); + return; } - if (bytesToDecode == 0) { - bytesToDecode = aImage->mSourceData.Length() - aImage->mDecoder->BytesDecoded(); + aDecoder->GetImage()->NotifyProgress(aDecoder->TakeProgress(), + aDecoder->TakeInvalidRect(), + aDecoder->GetDecodeFlags()); +} + +void +DecodePool::NotifyDecodeComplete(Decoder* aDecoder) +{ + MOZ_ASSERT(aDecoder); + + if (!NS_IsMainThread()) { + NotifyDecodeCompleteWorker::Dispatch(aDecoder); + return; } - TimeStamp deadline = TimeStamp::Now() + - TimeDuration::FromMilliseconds(gfxPrefs::ImageMemMaxMSBeforeYield()); - - // We keep decoding chunks until: - // * we don't have any data left to decode, - // * the decode completes, - // * we're an DecodeUntil::SIZE decode and we get the size, or - // * we run out of time. - while (aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded() && - bytesToDecode > 0 && - !aImage->IsDecodeFinished() && - !(aDecodeUntil == DecodeUntil::SIZE && aImage->mHasSize)) { - uint32_t chunkSize = min(bytesToDecode, maxBytes); - nsresult rv = aImage->DecodeSomeData(chunkSize); - if (NS_FAILED(rv)) { - aImage->DoError(); - return rv; - } - - bytesToDecode -= chunkSize; - - // Yield if we've been decoding for too long. We check this _after_ decoding - // a chunk to ensure that we don't yield without doing any decoding. - if (aDecodeUntil == DecodeUntil::TIME && TimeStamp::Now() >= deadline) { - break; - } - } - - return NS_OK; + aDecoder->Finish(); + aDecoder->GetImage()->FinalizeDecoder(aDecoder); } } // namespace image diff --git a/image/src/DecodePool.h b/image/src/DecodePool.h index 9b7c75930d7..b21b7fd9c85 100644 --- a/image/src/DecodePool.h +++ b/image/src/DecodePool.h @@ -25,39 +25,15 @@ namespace image { class Decoder; class RasterImage; -MOZ_BEGIN_ENUM_CLASS(DecodeStatus, uint8_t) - INACTIVE, - PENDING, - ACTIVE, - WORK_DONE, - STOPPED -MOZ_END_ENUM_CLASS(DecodeStatus) - -MOZ_BEGIN_ENUM_CLASS(DecodeUntil, uint8_t) - TIME, - SIZE, - DONE_BYTES -MOZ_END_ENUM_CLASS(DecodeUntil) - -MOZ_BEGIN_ENUM_CLASS(ShutdownReason, uint8_t) - DONE, - NOT_NEEDED, - FATAL_ERROR -MOZ_END_ENUM_CLASS(ShutdownReason) - - /** - * DecodePool is a singleton class we use when decoding large images. + * DecodePool is a singleton class that manages decoding of raster images. It + * owns a pool of image decoding threads that are used for asynchronous + * decoding. * - * When we wish to decode an image larger than - * image.mem.max_bytes_for_sync_decode, we call DecodePool::RequestDecode() - * for the image. This adds the image to a queue of pending requests and posts - * the DecodePool singleton to the event queue, if it's not already pending - * there. - * - * When the DecodePool is run from the event queue, it decodes the image (and - * all others it's managing) in chunks, periodically yielding control back to - * the event loop. + * DecodePool allows callers to run a decoder, handling management of the + * decoder's lifecycle and whether it executes on the main thread, + * off-main-thread in the image decoding thread pool, or on some combination of + * the two. */ class DecodePool : public nsIObserver { @@ -67,58 +43,52 @@ public: static DecodePool* Singleton(); - /** - * Ask the DecodePool to asynchronously decode this image. - */ - void RequestDecode(RasterImage* aImage); + /// Ask the DecodePool to run @aDecoder asynchronously and return immediately. + void AsyncDecode(Decoder* aDecoder); /** - * Decode aImage for a short amount of time, and post the remainder to the - * queue. + * Run @aDecoder synchronously if the image it's decoding is small. If the + * image is too large, or if the source data isn't complete yet, run @aDecoder + * asynchronously instead. */ - void DecodeABitOf(RasterImage* aImage); + void SyncDecodeIfSmall(Decoder* aDecoder); /** - * Ask the DecodePool to stop decoding this image. Internally, we also - * call this function when we finish decoding an image. + * Run aDecoder synchronously if at all possible. If it can't complete + * synchronously because the source data isn't complete, asynchronously decode + * the rest. + */ + void SyncDecodeIfPossible(Decoder* aDecoder); + + /** + * Returns an event target interface to the DecodePool's underlying thread + * pool. Callers can use this event target to submit work to the image + * decoding thread pool. * - * Since the DecodePool keeps raw pointers to RasterImages, make sure you - * call this before a RasterImage is destroyed! - */ - static void StopDecoding(RasterImage* aImage); - - /** - * Synchronously decode the beginning of the image until we run out of - * bytes or we get the image's size. Note that this done on a best-effort - * basis; if the size is burried too deep in the image, we'll give up. - * - * @return NS_ERROR if an error is encountered, and NS_OK otherwise. (Note - * that we return NS_OK even when the size was not found.) - */ - nsresult DecodeUntilSizeAvailable(RasterImage* aImage); - - /** - * Returns an event target interface to the thread pool; primarily for - * OnDataAvailable delivery off main thread. - * - * @return An nsIEventTarget interface to mThreadPool. + * @return An nsIEventTarget interface to the thread pool. */ already_AddRefed GetEventTarget(); /** - * Decode some chunks of the given image. If aDecodeUntil is SIZE, - * decode until we have the image's size, then stop. If bytesToDecode is - * non-0, at most bytesToDecode bytes will be decoded. if aDecodeUntil is - * DONE_BYTES, decode until all bytesToDecode bytes are decoded. + * Creates a worker which can be used to attempt further decoding using the + * provided decoder. + * + * @return The new worker, which should be posted to the event target returned + * by GetEventTarget. */ - nsresult DecodeSomeOfImage(RasterImage* aImage, - DecodeUntil aDecodeUntil = DecodeUntil::TIME, - uint32_t bytesToDecode = 0); + already_AddRefed CreateDecodeWorker(Decoder* aDecoder); private: + friend class DecodeWorker; + friend class NotifyDecodeCompleteWorker; + DecodePool(); virtual ~DecodePool(); + void Decode(Decoder* aDecoder); + void NotifyDecodeComplete(Decoder* aDecoder); + void NotifyProgress(Decoder* aDecoder); + static StaticRefPtr sSingleton; // mThreadPoolMutex protects mThreadPool. For all RasterImages R, diff --git a/image/src/Decoder.cpp b/image/src/Decoder.cpp index 6b770bb9331..1b664c9a7df 100644 --- a/image/src/Decoder.cpp +++ b/image/src/Decoder.cpp @@ -7,10 +7,12 @@ #include "Decoder.h" #include "mozilla/gfx/2D.h" +#include "DecodePool.h" +#include "GeckoProfiler.h" #include "imgIContainer.h" #include "nsIConsoleService.h" #include "nsIScriptError.h" -#include "GeckoProfiler.h" +#include "nsProxyRelease.h" #include "nsServiceManagerUtils.h" #include "nsComponentManagerUtils.h" @@ -29,8 +31,11 @@ Decoder::Decoder(RasterImage* aImage) , mDecodeFlags(0) , mBytesDecoded(0) , mSendPartialInvalidations(false) + , mDataDone(false) , mDecodeDone(false) , mDataError(false) + , mDecodeAborted(false) + , mImageIsTransient(false) , mFrameCount(0) , mFailCode(NS_OK) , mInitialized(false) @@ -71,7 +76,7 @@ void Decoder::Init() { // No re-initializing - NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!"); + MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!"); // Fire OnStartDecode at init time to support bug 512435. if (!IsSizeDecode()) { @@ -84,6 +89,69 @@ Decoder::Init() mInitialized = true; } +nsresult +Decoder::Decode() +{ + MOZ_ASSERT(mInitialized, "Should be initialized here"); + MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator"); + + // We keep decoding chunks until the decode completes or there are no more + // chunks available. + while (!GetDecodeDone() && !HasError()) { + auto newState = mIterator->AdvanceOrScheduleResume(this); + + if (newState == SourceBufferIterator::WAITING) { + // We can't continue because the rest of the data hasn't arrived from the + // network yet. We don't have to do anything special; the + // SourceBufferIterator will ensure that Decode() gets called again on a + // DecodePool thread when more data is available. + return NS_OK; + } + + if (newState == SourceBufferIterator::COMPLETE) { + mDataDone = true; + + nsresult finalStatus = mIterator->CompletionStatus(); + if (NS_FAILED(finalStatus)) { + PostDataError(); + } + + return finalStatus; + } + + MOZ_ASSERT(newState == SourceBufferIterator::READY); + + Write(mIterator->Data(), mIterator->Length()); + } + + return HasError() ? NS_ERROR_FAILURE : NS_OK; +} + +void +Decoder::Resume() +{ + DecodePool* decodePool = DecodePool::Singleton(); + MOZ_ASSERT(decodePool); + + nsCOMPtr target = decodePool->GetEventTarget(); + if (MOZ_UNLIKELY(!target)) { + // We're shutting down and the DecodePool's thread pool has been destroyed. + return; + } + + nsCOMPtr worker = decodePool->CreateDecodeWorker(this); + target->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL); +} + +bool +Decoder::ShouldSyncDecode(size_t aByteLimit) +{ + MOZ_ASSERT(aByteLimit > 0); + MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator"); + + return mIterator->RemainingBytesIsNoMoreThan(aByteLimit); +} + void Decoder::Write(const char* aBuffer, uint32_t aCount) { @@ -121,7 +189,7 @@ Decoder::Write(const char* aBuffer, uint32_t aCount) } void -Decoder::Finish(ShutdownReason aReason) +Decoder::Finish() { MOZ_ASSERT(NS_IsMainThread()); @@ -133,9 +201,10 @@ Decoder::Finish(ShutdownReason aReason) if (mInFrame && !HasError()) PostFrameStop(); - // If PostDecodeDone() has not been called, we need to sent teardown - // notifications. - if (!IsSizeDecode() && !mDecodeDone) { + // If PostDecodeDone() has not been called, and this decoder wasn't aborted + // early because of low-memory conditions or losing a race with another + // decoder, we need to send teardown notifications. + if (!IsSizeDecode() && !mDecodeDone && !WasAborted()) { // Log data errors to the error console nsCOMPtr consoleService = @@ -157,22 +226,17 @@ Decoder::Finish(ShutdownReason aReason) } } - bool usable = !HasDecoderError(); - if (aReason != ShutdownReason::NOT_NEEDED && !HasDecoderError()) { - // If we only have a data error, we're usable if we have at least one complete frame. - if (GetCompleteFrameCount() == 0) { - usable = false; - } - } - - // If we're usable, do exactly what we should have when the decoder - // completed. - if (usable) { + // If we only have a data error, we're usable if we have at least one + // complete frame. + if (!HasDecoderError() && GetCompleteFrameCount() > 0) { + // We're usable, so do exactly what we should have when the decoder + // completed. if (mInFrame) { PostFrameStop(); } PostDecodeDone(); } else { + // We're not usable. Record some final progress indicating the error. if (!IsSizeDecode()) { mProgress |= FLAG_DECODE_COMPLETE | FLAG_ONLOAD_UNBLOCKED; } @@ -184,9 +248,21 @@ Decoder::Finish(ShutdownReason aReason) // DecodingComplete calls Optimize(). mImageMetadata.SetOnImage(mImage); - if (mDecodeDone) { + if (HasSize()) { + SetSizeOnImage(); + } + + if (mDecodeDone && !IsSizeDecode()) { MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame"); - mImage->DecodingComplete(mCurrentFrame.get(), mIsAnimated); + + // If this image wasn't animated and isn't a transient image, mark its frame + // as optimizable. We don't support optimizing animated images and + // optimizing transient images isn't worth it. + if (!mIsAnimated && !mImageIsTransient && mCurrentFrame) { + mCurrentFrame->SetOptimizable(); + } + + mImage->OnDecodingComplete(); } } @@ -267,6 +343,12 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum, aFrameNum), Lifetime::Persistent); if (outcome != InsertOutcome::SUCCESS) { + // We either hit InsertOutcome::FAILURE, which is a temporary failure due to + // low memory (we know it's not permanent because we checked CanHold() + // above), or InsertOutcome::FAILURE_ALREADY_PRESENT, which means that + // another decoder beat us to decoding this frame. Either way, we should + // abort this decoder rather than treat this as a real error. + mDecodeAborted = true; ref->Abort(); return RawAccessFrameRef(); } @@ -308,9 +390,12 @@ Decoder::SetSizeOnImage() MOZ_ASSERT(mImageMetadata.HasSize(), "Should have size"); MOZ_ASSERT(mImageMetadata.HasOrientation(), "Should have orientation"); - mImage->SetSize(mImageMetadata.GetWidth(), - mImageMetadata.GetHeight(), - mImageMetadata.GetOrientation()); + nsresult rv = mImage->SetSize(mImageMetadata.GetWidth(), + mImageMetadata.GetHeight(), + mImageMetadata.GetOrientation()); + if (NS_FAILED(rv)) { + PostResizeError(); + } } /* diff --git a/image/src/Decoder.h b/image/src/Decoder.h index fd261f161f3..9644105aeed 100644 --- a/image/src/Decoder.h +++ b/image/src/Decoder.h @@ -12,13 +12,14 @@ #include "DecodePool.h" #include "ImageMetadata.h" #include "Orientation.h" +#include "SourceBuffer.h" #include "mozilla/Telemetry.h" namespace mozilla { namespace image { -class Decoder +class Decoder : public IResumable { public: @@ -26,29 +27,29 @@ public: /** * Initialize an image decoder. Decoders may not be re-initialized. - * - * Notifications Sent: TODO */ void Init(); /** - * Writes data to the decoder. - * - * @param aBuffer buffer containing the data to be written - * @param aCount the number of bytes to write + * Decodes, reading all data currently available in the SourceBuffer. If more + * data is needed, Decode() automatically ensures that it will be called again + * on a DecodePool thread when the data becomes available. * * Any errors are reported by setting the appropriate state on the decoder. - * - * Notifications Sent: TODO */ - void Write(const char* aBuffer, uint32_t aCount); + nsresult Decode(); /** - * Informs the decoder that all the data has been written. - * - * Notifications Sent: TODO + * Cleans up the decoder's state and notifies our image about success or + * failure. May only be called on the main thread. */ - void Finish(ShutdownReason aReason); + void Finish(); + + /** + * Given a maximum number of bytes we're willing to decode, @aByteLimit, + * returns true if we should attempt to run this decoder synchronously. + */ + bool ShouldSyncDecode(size_t aByteLimit); /** * Gets the invalidation region accumulated by the decoder so far, and clears @@ -75,9 +76,20 @@ public: return progress; } + /** + * Returns true if there's any progress to report. + */ + bool HasProgress() const + { + return mProgress != NoProgress || !mInvalidRect.IsEmpty(); + } + // We're not COM-y, so we don't get refcounts by default NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder) + // Implement IResumable. + virtual void Resume() MOZ_OVERRIDE; + /* * State. */ @@ -88,7 +100,7 @@ public: bool IsSizeDecode() { return mSizeDecode; } void SetSizeDecode(bool aSizeDecode) { - NS_ABORT_IF_FALSE(!mInitialized, "Can't set size decode after Init()!"); + MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); mSizeDecode = aSizeDecode; } @@ -110,6 +122,32 @@ public: mSendPartialInvalidations = aSend; } + /** + * Set an iterator to the SourceBuffer which will feed data to this decoder. + * + * This should be called for almost all decoders; the exceptions are the + * contained decoders of an nsICODecoder, which will be fed manually via Write + * instead. + * + * This must be called before Init() is called. + */ + void SetIterator(SourceBufferIterator&& aIterator) + { + MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); + mIterator.emplace(Move(aIterator)); + } + + /** + * Set whether this decoder is associated with a transient image. The decoder + * may choose to avoid certain optimizations that don't pay off for + * short-lived images in this case. + */ + void SetImageIsTransient(bool aIsTransient) + { + MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); + mImageIsTransient = aIsTransient; + } + size_t BytesDecoded() const { return mBytesDecoded; } // The amount of time we've spent inside Write() so far for this decoder. @@ -126,15 +164,28 @@ public: uint32_t GetCompleteFrameCount() { return mInFrame ? mFrameCount - 1 : mFrameCount; } // Error tracking - bool HasError() { return HasDataError() || HasDecoderError(); } - bool HasDataError() { return mDataError; } - bool HasDecoderError() { return NS_FAILED(mFailCode); } - nsresult GetDecoderError() { return mFailCode; } + bool HasError() const { return HasDataError() || HasDecoderError(); } + bool HasDataError() const { return mDataError; } + bool HasDecoderError() const { return NS_FAILED(mFailCode); } + nsresult GetDecoderError() const { return mFailCode; } void PostResizeError() { PostDataError(); } - bool GetDecodeDone() const { - return mDecodeDone; + + bool GetDecodeDone() const + { + return mDecodeDone || (mSizeDecode && HasSize()) || HasError() || mDataDone; } + /** + * Returns true if this decoder was aborted. + * + * This may happen due to a low-memory condition, or because another decoder + * was racing with this one to decode the same frames with the same flags and + * this decoder lost the race. Either way, this is not a permanent situation + * and does not constitute an error, so we don't report any errors when this + * happens. + */ + bool WasAborted() const { return mDecodeAborted; } + // flags. Keep these in sync with imgIContainer.idl. // SetDecodeFlags must be called before Init(), otherwise // default flags are assumed. @@ -264,11 +315,22 @@ protected: uint8_t aPaletteDepth, imgFrame* aPreviousFrame); + /** + * Writes data to the decoder. + * + * @param aBuffer buffer containing the data to be written + * @param aCount the number of bytes to write + * + * Any errors are reported by setting the appropriate state on the decoder. + */ + void Write(const char* aBuffer, uint32_t aCount); + /* * Member variables. * */ nsRefPtr mImage; + Maybe mIterator; RawAccessFrameRef mCurrentFrame; ImageMetadata mImageMetadata; nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame. @@ -286,8 +348,11 @@ protected: uint32_t mDecodeFlags; size_t mBytesDecoded; bool mSendPartialInvalidations; + bool mDataDone; bool mDecodeDone; bool mDataError; + bool mDecodeAborted; + bool mImageIsTransient; private: uint32_t mFrameCount; // Number of frames, including anything in-progress diff --git a/image/src/FrameAnimator.cpp b/image/src/FrameAnimator.cpp index 424e78601a1..bbf384ca2ab 100644 --- a/image/src/FrameAnimator.cpp +++ b/image/src/FrameAnimator.cpp @@ -290,6 +290,11 @@ int32_t FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const { RawAccessFrameRef frame = GetRawFrame(aFrameNum); + if (!frame) { + NS_WARNING("No frame; called GetTimeoutForFrame too early?"); + return 100; + } + AnimationData data = frame->GetAnimationData(); // Ensure a minimal time between updates so we don't throttle the UI thread. diff --git a/image/src/RasterImage.cpp b/image/src/RasterImage.cpp index 01d19fc6c07..e92ac40cd0d 100644 --- a/image/src/RasterImage.cpp +++ b/image/src/RasterImage.cpp @@ -20,6 +20,7 @@ #include "ImageRegion.h" #include "Layers.h" #include "nsPresContext.h" +#include "SourceBuffer.h" #include "SurfaceCache.h" #include "FrameAnimator.h" @@ -68,20 +69,6 @@ DecodeFlags(uint32_t aFlags) return aFlags & DECODE_FLAGS_MASK; } -/* Accounting for compressed data */ -#if defined(PR_LOGGING) -static PRLogModuleInfo * -GetCompressedImageAccountingLog() -{ - static PRLogModuleInfo *sLog; - if (!sLog) - sLog = PR_NewLogModule("CompressedImageAccounting"); - return sLog; -} -#else -#define GetCompressedImageAccountingLog() -#endif - // The maximum number of times any one RasterImage was decoded. This is only // used for statistics. static int32_t sMaxDecodeCount = 0; @@ -274,16 +261,13 @@ RasterImage::RasterImage(ProgressTracker* aProgressTracker, ImageURL* aURI /* = nullptr */) : ImageResource(aURI), // invoke superclass's constructor mSize(0,0), - mFrameDecodeFlags(DECODE_FLAGS_DEFAULT), mLockCount(0), mDecodeCount(0), mRequestedSampleSize(0), #ifdef DEBUG mFramesNotified(0), #endif - mDecodingMonitor("RasterImage Decoding Monitor"), - mDecoder(nullptr), - mDecodeStatus(DecodeStatus::INACTIVE), + mSourceBuffer(new SourceBuffer()), mFrameCount(0), mNotifyProgress(NoProgress), mNotifying(false), @@ -292,12 +276,10 @@ RasterImage::RasterImage(ProgressTracker* aProgressTracker, mTransient(false), mDiscardable(false), mHasSourceData(false), - mDecoded(false), mHasBeenDecoded(false), mPendingAnimation(false), mAnimationFinished(false), - mWantFullDecode(false), - mPendingError(false) + mWantFullDecode(false) { mProgressTrackerInit = new ProgressTrackerInit(this, aProgressTracker); @@ -307,18 +289,14 @@ RasterImage::RasterImage(ProgressTracker* aProgressTracker, //****************************************************************************** RasterImage::~RasterImage() { - if (mDecoder) { - // Kill off our decode request, if it's pending. (If not, this call is - // harmless.) - ReentrantMonitorAutoEnter lock(mDecodingMonitor); - DecodePool::StopDecoding(this); - mDecoder = nullptr; + // Make sure our SourceBuffer is marked as complete. This will ensure that any + // outstanding decoders terminate. + if (!mSourceBuffer->IsComplete()) { + mSourceBuffer->Complete(NS_ERROR_ABORT); } // Release all frames from the surface cache. SurfaceCache::RemoveImage(ImageKey(this)); - - mAnim = nullptr; } /* static */ void @@ -361,14 +339,11 @@ RasterImage::Init(const char* aMimeType, SurfaceCache::LockImage(ImageKey(this)); } - // Instantiate the decoder - nsresult rv = InitDecoder(/* aDoSizeDecode = */ true); - CONTAINER_ENSURE_SUCCESS(rv); - - // If we aren't storing source data, we want to switch from a size decode to - // a full decode as soon as possible. - if (!StoringSourceData()) { - mWantFullDecode = true; + // Create the initial size decoder. + nsresult rv = Decode(DecodeStrategy::ASYNC, DECODE_FLAGS_DEFAULT, + /* aDoSizeDecode = */ true); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; } // Mark us as initialized @@ -405,11 +380,7 @@ RasterImage::RequestRefresh(const TimeStamp& aTime) mFramesNotified++; #endif - UpdateImageContainer(); - - if (mProgressTracker) { - mProgressTracker->SyncNotifyProgress(NoProgress, res.dirtyRect); - } + NotifyProgress(NoProgress, res.dirtyRect); } if (res.animationFinished) { @@ -545,10 +516,6 @@ RasterImage::LookupFrame(uint32_t aFrameNum, // The OS threw this frame away. We need to redecode if we can. MOZ_ASSERT(!mAnim, "Animated frames should be locked"); - // Update our state so the decoder knows what to do. - mFrameDecodeFlags = aFlags & DECODE_FLAGS_MASK; - mDecoded = false; - mFrameCount = 0; WantDecodedFrames(aFlags, aShouldSyncNotify); // If we were able to sync decode, we should already have the frame. If we @@ -883,15 +850,7 @@ RasterImage::UpdateImageContainer() size_t RasterImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const { - // n == 0 is possible for two reasons. - // - This is a zero-length image. - // - We're on a platform where moz_malloc_size_of always returns 0. - // In either case the fallback works appropriately. - size_t n = mSourceData.SizeOfExcludingThis(aMallocSizeOf); - if (n == 0) { - n = mSourceData.Length(); - } - return n; + return mSourceBuffer->SizeOfIncludingThisWithComputedFallback(aMallocSizeOf); } size_t @@ -946,38 +905,39 @@ RasterImage::OnAddedFrame(uint32_t aNewFrameCount, aNewFrameCount == mFrameCount + 1, "Skipped a frame?"); - mFrameCount = aNewFrameCount; + if (aNewFrameCount > mFrameCount) { + mFrameCount = aNewFrameCount; - if (aNewFrameCount == 2) { - // We're becoming animated, so initialize animation stuff. - MOZ_ASSERT(!mAnim, "Already have animation state?"); - mAnim = MakeUnique(this, mSize.ToIntSize(), mAnimationMode); + if (aNewFrameCount == 2) { + // We're becoming animated, so initialize animation stuff. + MOZ_ASSERT(!mAnim, "Already have animation state?"); + mAnim = MakeUnique(this, mSize.ToIntSize(), mAnimationMode); - // We don't support discarding animated images (See bug 414259). - // Lock the image and throw away the key. - // - // Note that this is inefficient, since we could get rid of the source data - // too. However, doing this is actually hard, because we're probably - // mid-decode, and thus we're decoding out of the source buffer. Since we're - // going to fix this anyway later, and since we didn't kill the source data - // in the old world either, locking is acceptable for the moment. - LockImage(); + // We don't support discarding animated images (See bug 414259). + // Lock the image and throw away the key. + // + // Note that this is inefficient, since we could get rid of the source data + // too. However, doing this is actually hard, because we're probably + // mid-decode, and thus we're decoding out of the source buffer. Since we're + // going to fix this anyway later, and since we didn't kill the source data + // in the old world either, locking is acceptable for the moment. + LockImage(); - if (mPendingAnimation && ShouldAnimate()) { - StartAnimation(); + if (mPendingAnimation && ShouldAnimate()) { + StartAnimation(); + } + + if (aNewFrameCount > 1) { + mAnim->UnionFirstFrameRefreshArea(aNewRefreshArea); + } } } - - if (aNewFrameCount > 1) { - mAnim->UnionFirstFrameRefreshArea(aNewRefreshArea); - } } nsresult RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation) { MOZ_ASSERT(NS_IsMainThread()); - mDecodingMonitor.AssertCurrentThreadIn(); if (mError) return NS_ERROR_FAILURE; @@ -993,12 +953,6 @@ RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation) (aHeight != mSize.height) || (aOrientation != mOrientation))) { NS_WARNING("Image changed size on redecode! This should not happen!"); - - // Make the decoder aware of the error so that it doesn't try to call - // FinishInternal during ShutdownDecoder. - if (mDecoder) - mDecoder->PostResizeError(); - DoError(); return NS_ERROR_UNEXPECTED; } @@ -1012,7 +966,7 @@ RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation) } void -RasterImage::DecodingComplete(imgFrame* aFinalFrame, bool aIsAnimated) +RasterImage::OnDecodingComplete() { MOZ_ASSERT(NS_IsMainThread()); @@ -1020,37 +974,13 @@ RasterImage::DecodingComplete(imgFrame* aFinalFrame, bool aIsAnimated) return; } - // Flag that we're done decoding. - // XXX - these should probably be combined when we fix animated image - // discarding with bug 500402. - mDecoded = true; + // Flag that we've been decoded before. mHasBeenDecoded = true; - // If there's only 1 frame, mark it as optimizable. Optimizing animated images - // is not supported. Optimizing transient images isn't worth it. - if (!aIsAnimated && !mTransient && aFinalFrame) { - aFinalFrame->SetOptimizable(); + // Let our FrameAnimator know not to expect any more frames. + if (mAnim) { + mAnim->SetDoneDecoding(true); } - - if (aIsAnimated) { - if (mAnim) { - mAnim->SetDoneDecoding(true); - } else { - NS_DispatchToMainThread( - NS_NewRunnableMethod(this, &RasterImage::MarkAnimationDecoded)); - } - } -} - -void -RasterImage::MarkAnimationDecoded() -{ - NS_ASSERTION(mAnim, "No FrameAnimator in MarkAnimationDecoded - bad event order"); - if (!mAnim) { - return; - } - - mAnim->SetDoneDecoding(true); } NS_IMETHODIMP @@ -1134,16 +1064,7 @@ RasterImage::ResetAnimation() MOZ_ASSERT(mAnim, "Should have a FrameAnimator"); mAnim->ResetAnimation(); - UpdateImageContainer(); - - // Note - We probably want to kick off a redecode somewhere around here when - // we fix bug 500402. - - // Update display - if (mProgressTracker) { - nsIntRect rect = mAnim->GetFirstFrameRefreshArea(); - mProgressTracker->SyncNotifyProgress(NoProgress, rect); - } + NotifyProgress(NoProgress, mAnim->GetFirstFrameRefreshArea()); // Start the animation again. It may not have been running before, if // mAnimationFinished was true before entering this function. @@ -1191,138 +1112,45 @@ RasterImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) } nsresult -RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount) -{ - ReentrantMonitorAutoEnter lock(mDecodingMonitor); - - if (mError) - return NS_ERROR_FAILURE; - - NS_ENSURE_ARG_POINTER(aBuffer); - nsresult rv = NS_OK; - - // We should not call this if we're not initialized - NS_ABORT_IF_FALSE(mInitialized, "Calling AddSourceData() on uninitialized " - "RasterImage!"); - - // We should not call this if we're already finished adding source data - NS_ABORT_IF_FALSE(!mHasSourceData, "Calling AddSourceData() after calling " - "sourceDataComplete()!"); - - // Image is already decoded, we shouldn't be getting data, but it could - // be extra garbage data at the end of a file. - if (mDecoded) { - return NS_OK; - } - - // If we're not storing source data and we've previously gotten the size, - // write the data directly to the decoder. (If we haven't gotten the size, - // we'll queue up the data and write it out when we do.) - if (!StoringSourceData() && mHasSize) { - rv = WriteToDecoder(aBuffer, aCount); - CONTAINER_ENSURE_SUCCESS(rv); - - rv = FinishedSomeDecoding(); - CONTAINER_ENSURE_SUCCESS(rv); - } - - // Otherwise, we're storing data in the source buffer - else { - - // Store the data - char *newElem = mSourceData.AppendElements(aBuffer, aCount); - if (!newElem) - return NS_ERROR_OUT_OF_MEMORY; - - if (mDecoder) { - DecodePool::Singleton()->RequestDecode(this); - } - } - - return NS_OK; -} - -/* Note! buf must be declared as char buf[9]; */ -// just used for logging and hashing the header -static void -get_header_str (char *buf, char *data, size_t data_len) -{ - int i; - int n; - static char hex[] = "0123456789abcdef"; - - n = data_len < 4 ? data_len : 4; - - for (i = 0; i < n; i++) { - buf[i * 2] = hex[(data[i] >> 4) & 0x0f]; - buf[i * 2 + 1] = hex[data[i] & 0x0f]; - } - - buf[i * 2] = 0; -} - -nsresult -RasterImage::DoImageDataComplete() +RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus, + bool aLastPart) { MOZ_ASSERT(NS_IsMainThread()); - if (mError) - return NS_ERROR_FAILURE; - - // If we've been called before, ignore. Otherwise, flag that we have everything - if (mHasSourceData) - return NS_OK; + // Record that we have all the data we're going to get now. mHasSourceData = true; - // If there's a decoder open, synchronously decode the beginning of the image - // to check for errors and get the image's size. (If we already have the - // image's size, this does nothing.) Then kick off an async decode of the - // rest of the image. - if (mDecoder) { - nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this); - CONTAINER_ENSURE_SUCCESS(rv); + // Let decoders know that there won't be any more data coming. + mSourceBuffer->Complete(aStatus); + + if (!mHasSize) { + // We need to guarantee that we've gotten the image's size, or at least + // determined that we won't be able to get it, before we deliver the load + // event. That means we have to do a synchronous size decode here. + Decode(DecodeStrategy::SYNC_IF_POSSIBLE, DECODE_FLAGS_DEFAULT, + /* aDoSizeDecode = */ true); } - { - ReentrantMonitorAutoEnter lock(mDecodingMonitor); - - // Free up any extra space in the backing buffer - mSourceData.Compact(); - } - - // Log header information - if (PR_LOG_TEST(GetCompressedImageAccountingLog(), PR_LOG_DEBUG)) { - char buf[9]; - get_header_str(buf, mSourceData.Elements(), mSourceData.Length()); - PR_LOG (GetCompressedImageAccountingLog(), PR_LOG_DEBUG, - ("CompressedImageAccounting: RasterImage::SourceDataComplete() - data " - "is done for container %p (%s) - header %p is 0x%s (length %d)", - this, - mSourceDataMimeType.get(), - mSourceData.Elements(), - buf, - mSourceData.Length())); - } - - return NS_OK; -} - -nsresult -RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus, bool aLastPart) -{ - nsresult finalStatus = DoImageDataComplete(); - - // Give precedence to Necko failure codes. - if (NS_FAILED(aStatus)) + // Determine our final status, giving precedence to Necko failure codes. We + // check after running the size decode above in case it triggered an error. + nsresult finalStatus = mError ? NS_ERROR_FAILURE : NS_OK; + if (NS_FAILED(aStatus)) { finalStatus = aStatus; - - // We just recorded OnStopRequest; we need to inform our listeners. - { - ReentrantMonitorAutoEnter lock(mDecodingMonitor); - FinishedSomeDecoding(ShutdownReason::DONE, - LoadCompleteProgress(aLastPart, mError, finalStatus)); } + // 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); + NotifyProgress(loadProgress); + return finalStatus; } @@ -1335,13 +1163,13 @@ RasterImage::OnImageDataAvailable(nsIRequest*, { nsresult rv; - // WriteToRasterImage always consumes everything it gets - // if it doesn't run out of memory + // WriteToSourceBuffer always consumes everything it gets if it doesn't run + // out of memory. uint32_t bytesRead; - rv = aInStr->ReadSegments(WriteToRasterImage, this, aCount, &bytesRead); + rv = aInStr->ReadSegments(WriteToSourceBuffer, this, aCount, &bytesRead); NS_ABORT_IF_FALSE(bytesRead == aCount || HasError() || NS_FAILED(rv), - "WriteToRasterImage should consume everything if ReadSegments succeeds or " + "WriteToSourceBuffer should consume everything if ReadSegments succeeds or " "the image must be in error!"); return rv; @@ -1354,11 +1182,9 @@ RasterImage::GetEventTarget() } nsresult -RasterImage::SetSourceSizeHint(uint32_t sizeHint) +RasterImage::SetSourceSizeHint(uint32_t aSizeHint) { - if (sizeHint && StoringSourceData()) - return mSourceData.SetCapacity(sizeHint) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; - return NS_OK; + return mSourceBuffer->ExpectLength(aSizeHint); } /********* Methods to implement lazy allocation of nsIProperties object *************/ @@ -1414,100 +1240,83 @@ void RasterImage::Discard() { MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(CanDiscard(), "Asked to discard but can't"); - - // We should never discard when we have an active decoder - NS_ABORT_IF_FALSE(!mDecoder, "Asked to discard with open decoder!"); - - // As soon as an image becomes animated, it becomes non-discardable and any - // timers are cancelled. - NS_ABORT_IF_FALSE(!mAnim, "Asked to discard for animated image!"); + MOZ_ASSERT(!mAnim, "Asked to discard for animated image"); // Delete all the decoded frames. SurfaceCache::RemoveImage(ImageKey(this)); - // Flag that we no longer have decoded frames for this image - mDecoded = false; - mFrameCount = 0; - - // Notify that we discarded + // Notify that we discarded. if (mProgressTracker) { mProgressTracker->OnDiscard(); } - - mDecodeStatus = DecodeStatus::INACTIVE; } bool RasterImage::CanDiscard() { return mHasSourceData && // ...have the source data... - !mDecoder && // Can't discard with an open decoder !mAnim; // Can never discard animated images } -// Helper method to determine if we're storing the source data in a buffer -// or just writing it directly to the decoder -bool -RasterImage::StoringSourceData() const { - return !mTransient; -} - - -// Sets up a decoder for this image. It is an error to call this function -// when decoding is already in process (ie - when mDecoder is non-null). -nsresult -RasterImage::InitDecoder(bool aDoSizeDecode) +// Sets up a decoder for this image. +already_AddRefed +RasterImage::CreateDecoder(bool aDoSizeDecode, uint32_t aFlags) { - // Ensure that the decoder is not already initialized - NS_ABORT_IF_FALSE(!mDecoder, "Calling InitDecoder() while already decoding!"); - - // We shouldn't be firing up a decoder if we already have the frames decoded - NS_ABORT_IF_FALSE(!mDecoded, "Calling InitDecoder() but already decoded!"); - // Make sure we actually get size before doing a full decode. - if (!aDoSizeDecode) { - NS_ABORT_IF_FALSE(mHasSize, "Must do a size decode before a full decode!"); + if (aDoSizeDecode) { + MOZ_ASSERT(!mHasSize, "Should not do unnecessary size decodes"); + } else { + MOZ_ASSERT(mHasSize, "Must do a size decode before a full decode!"); } - // Figure out which decoder we want + // Figure out which decoder we want. eDecoderType type = GetDecoderType(mSourceDataMimeType.get()); - CONTAINER_ENSURE_TRUE(type != eDecoderType_unknown, NS_IMAGELIB_ERROR_NO_DECODER); + if (type == eDecoderType_unknown) { + return nullptr; + } // Instantiate the appropriate decoder. + nsRefPtr decoder; switch (type) { case eDecoderType_png: - mDecoder = new nsPNGDecoder(this); + decoder = new nsPNGDecoder(this); break; case eDecoderType_gif: - mDecoder = new nsGIFDecoder2(this); + decoder = new nsGIFDecoder2(this); break; case eDecoderType_jpeg: // If we have all the data we don't want to waste cpu time doing // a progressive decode. - mDecoder = new nsJPEGDecoder(this, - mHasBeenDecoded ? Decoder::SEQUENTIAL : - Decoder::PROGRESSIVE); + decoder = new nsJPEGDecoder(this, + mHasBeenDecoded ? Decoder::SEQUENTIAL : + Decoder::PROGRESSIVE); break; case eDecoderType_bmp: - mDecoder = new nsBMPDecoder(this); + decoder = new nsBMPDecoder(this); break; case eDecoderType_ico: - mDecoder = new nsICODecoder(this); + decoder = new nsICODecoder(this); break; case eDecoderType_icon: - mDecoder = new nsIconDecoder(this); + decoder = new nsIconDecoder(this); break; default: MOZ_ASSERT_UNREACHABLE("Unknown decoder type"); } - // Initialize the decoder - mDecoder->SetSizeDecode(aDoSizeDecode); - mDecoder->SetDecodeFlags(mFrameDecodeFlags); - mDecoder->SetSendPartialInvalidations(!mHasBeenDecoded); - mDecoder->Init(); - CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError()); + MOZ_ASSERT(decoder, "Should have a decoder now"); + + // Initialize the decoder. + decoder->SetSizeDecode(aDoSizeDecode); + decoder->SetSendPartialInvalidations(!mHasBeenDecoded); + decoder->SetImageIsTransient(mTransient); + decoder->SetDecodeFlags(DecodeFlags(aFlags)); + decoder->SetIterator(mSourceBuffer->Iterator()); + decoder->Init(); + + if (NS_FAILED(decoder->GetDecoderError())) { + return nullptr; + } if (!aDoSizeDecode) { Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Subtract(mDecodeCount); @@ -1525,102 +1334,29 @@ RasterImage::InitDecoder(bool aDoSizeDecode) } } - return NS_OK; + return decoder.forget(); } -// Flushes, closes, and nulls-out a decoder. Cleans up any related decoding -// state. It is an error to call this function when there is no initialized -// decoder. -// -// aReason specifies why the shutdown is happening. If aReason is -// ShutdownReason::DONE, an error is flagged if we didn't get what we should -// have out of the decode. If aReason is ShutdownReason::NOT_NEEDED, we don't -// check this. If aReason is ShutdownReason::FATAL_ERROR, we shut down in error -// mode. -nsresult -RasterImage::ShutdownDecoder(ShutdownReason aReason) -{ - MOZ_ASSERT(NS_IsMainThread()); - mDecodingMonitor.AssertCurrentThreadIn(); - - // Ensure that the decoder is initialized - NS_ABORT_IF_FALSE(mDecoder, "Calling ShutdownDecoder() with no active decoder!"); - - // Figure out what kind of decode we were doing before we get rid of our decoder - bool wasSizeDecode = mDecoder->IsSizeDecode(); - - // Finalize the decoder - // null out mDecoder, _then_ check for errors on the close (otherwise the - // error routine might re-invoke ShutdownDecoder) - nsRefPtr decoder = mDecoder; - mDecoder = nullptr; - - decoder->Finish(aReason); - - // Kill off our decode request, if it's pending. (If not, this call is - // harmless.) - DecodePool::StopDecoding(this); - - nsresult decoderStatus = decoder->GetDecoderError(); - if (NS_FAILED(decoderStatus)) { - DoError(); - return decoderStatus; - } - - // We just shut down the decoder. If we didn't get what we want, but expected - // to, flag an error - bool succeeded = wasSizeDecode ? mHasSize : mDecoded; - if ((aReason == ShutdownReason::DONE) && !succeeded) { - DoError(); - return NS_ERROR_FAILURE; - } - - // If we finished a full decode, and we're not meant to be storing source - // data, stop storing it. - if (!wasSizeDecode && !StoringSourceData()) { - mSourceData.Clear(); - } - - return NS_OK; -} - -// Writes the data to the decoder, updating the total number of bytes written. -nsresult -RasterImage::WriteToDecoder(const char *aBuffer, uint32_t aCount) -{ - mDecodingMonitor.AssertCurrentThreadIn(); - - // We should have a decoder - NS_ABORT_IF_FALSE(mDecoder, "Trying to write to null decoder!"); - - // Write - nsRefPtr kungFuDeathGrip = mDecoder; - mDecoder->Write(aBuffer, aCount); - - CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError()); - - return NS_OK; -} - -// This function is called in situations where it's clear that we want the -// frames in decoded form (Draw, LookupFrame, etc). If we're completely decoded, -// this method resets the discard timer (if we're discardable), since wanting -// the frames now is a good indicator of wanting them again soon. If we're not -// decoded, this method kicks off asynchronous decoding to generate the frames. -nsresult +void RasterImage::WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify) { - // Request a decode, which does nothing if we're already decoded. if (aShouldSyncNotify) { // We can sync notify, which means we can also sync decode. if (aFlags & FLAG_SYNC_DECODE) { - return SyncDecode(); + Decode(DecodeStrategy::SYNC_IF_POSSIBLE, aFlags); + return; } - return StartDecoding(); + + // Here we are explicitly trading off flashing for responsiveness in the + // case that we're redecoding an image (see bug 845147). + Decode(mHasBeenDecoded ? DecodeStrategy::ASYNC + : DecodeStrategy::SYNC_FOR_SMALL_IMAGES, + aFlags); + return; } // We can't sync notify, so do an async decode. - return RequestDecodeCore(ASYNCHRONOUS); + Decode(DecodeStrategy::ASYNC, aFlags); } //****************************************************************************** @@ -1628,7 +1364,24 @@ RasterImage::WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify) NS_IMETHODIMP RasterImage::RequestDecode() { - return RequestDecodeCore(SYNCHRONOUS_NOTIFY); + MOZ_ASSERT(NS_IsMainThread()); + + if (mError) { + return NS_ERROR_FAILURE; + } + if (!mHasSize) { + mWantFullDecode = true; + return NS_OK; + } + + // Look up the first frame of the image, which will implicitly start decoding + // if it's not available right now. + // XXX(seth): Passing false for aShouldSyncNotify here has the effect of + // decoding asynchronously, but that's not obvious from the argument name. + // This API needs to be reworked. + LookupFrame(0, mSize, DECODE_FLAGS_DEFAULT, /* aShouldSyncNotify = */ false); + + return NS_OK; } /* void startDecode() */ @@ -1639,223 +1392,86 @@ RasterImage::StartDecoding() return NS_DispatchToMainThread( NS_NewRunnableMethod(this, &RasterImage::StartDecoding)); } - // Here we are explicitly trading off flashing for responsiveness in the case - // that we're redecoding an image (see bug 845147). - return RequestDecodeCore(mHasBeenDecoded ? - SYNCHRONOUS_NOTIFY : SYNCHRONOUS_NOTIFY_AND_SOME_DECODE); + + if (mError) { + return NS_ERROR_FAILURE; + } + if (!mHasSize) { + mWantFullDecode = true; + return NS_OK; + } + + // Look up the first frame of the image, which will implicitly start decoding + // if it's not available right now. + // XXX(seth): Passing true for aShouldSyncNotify here has the effect of + // synchronously decoding small images, but that's not obvious from the + // argument name. This API needs to be reworked. + LookupFrame(0, mSize, DECODE_FLAGS_DEFAULT, /* aShouldSyncNotify = */ true); + + return NS_OK; } bool RasterImage::IsDecoded() { - return mDecoded || mError; + // XXX(seth): We need to get rid of this; it's not reliable. + return mHasBeenDecoded || mError; } NS_IMETHODIMP -RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType) +RasterImage::Decode(DecodeStrategy aStrategy, + uint32_t aFlags, + bool aDoSizeDecode /* = false */) { - MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aDoSizeDecode || NS_IsMainThread()); - nsresult rv; - - if (mError) + if (mError) { return NS_ERROR_FAILURE; + } - // If we're already decoded, there's nothing to do. - if (mDecoded) + // If we don't have a size yet, we can't do any other decoding. + if (!mHasSize && !aDoSizeDecode) { + mWantFullDecode = true; return NS_OK; + } - // If we have a size decoder open, make sure we get the size - if (mDecoder && mDecoder->IsSizeDecode()) { - nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this); - CONTAINER_ENSURE_SUCCESS(rv); + // Create a decoder. + nsRefPtr decoder = CreateDecoder(aDoSizeDecode, aFlags); + if (!decoder) { + return NS_ERROR_FAILURE; + } - // If we didn't get the size out of the image, we won't until we get more - // data, so signal that we want a full decode and give up for now. - if (!mHasSize) { - mWantFullDecode = true; + // Send out early notifications right away. (Unless this is a size decode, + // which doesn't send out any notifications until the end.) + if (!aDoSizeDecode) { + NotifyProgress(decoder->TakeProgress(), + decoder->TakeInvalidRect(), + decoder->GetDecodeFlags()); + } + + if (mHasSourceData) { + // If we have all the data, we can sync decode if requested. + if (aStrategy == DecodeStrategy::SYNC_FOR_SMALL_IMAGES) { + PROFILER_LABEL_PRINTF("DecodePool", "SyncDecodeIfSmall", + js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get()); + DecodePool::Singleton()->SyncDecodeIfSmall(decoder); + return NS_OK; + } + + if (aStrategy == DecodeStrategy::SYNC_IF_POSSIBLE) { + PROFILER_LABEL_PRINTF("DecodePool", "SyncDecodeIfPossible", + js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get()); + DecodePool::Singleton()->SyncDecodeIfPossible(decoder); return NS_OK; } } - // If the image is waiting for decode work to be notified, go ahead and do that. - if (mDecodeStatus == DecodeStatus::WORK_DONE && - aDecodeType == SYNCHRONOUS_NOTIFY) { - ReentrantMonitorAutoEnter lock(mDecodingMonitor); - nsresult rv = FinishedSomeDecoding(); - CONTAINER_ENSURE_SUCCESS(rv); - } - - // If we're fully decoded, we have nothing to do. We need this check after - // DecodeUntilSizeAvailable and FinishedSomeDecoding because they can result - // in us finishing an in-progress decode (or kicking off and finishing a - // synchronous decode if we're already waiting on a full decode). - if (mDecoded) { - return NS_OK; - } - - // If we've already got a full decoder running, and have already decoded - // some bytes, we have nothing to do if we haven't been asked to do some - // sync decoding - if (mDecoder && !mDecoder->IsSizeDecode() && mDecoder->BytesDecoded() > 0 && - aDecodeType != SYNCHRONOUS_NOTIFY_AND_SOME_DECODE) { - return NS_OK; - } - - ReentrantMonitorAutoEnter lock(mDecodingMonitor); - - // If we don't have any bytes to flush to the decoder, we can't do anything. - // mDecoder->BytesDecoded() can be bigger than mSourceData.Length() if we're - // not storing the source data. - if (mDecoder && mDecoder->BytesDecoded() > mSourceData.Length()) { - return NS_OK; - } - - // After acquiring the lock we may have finished some more decoding, so - // we need to repeat the following three checks after getting the lock. - - // If the image is waiting for decode work to be notified, go ahead and do that. - if (mDecodeStatus == DecodeStatus::WORK_DONE && aDecodeType != ASYNCHRONOUS) { - nsresult rv = FinishedSomeDecoding(); - CONTAINER_ENSURE_SUCCESS(rv); - } - - // If we're fully decoded, we have nothing to do. We need this check after - // DecodeUntilSizeAvailable and FinishedSomeDecoding because they can result - // in us finishing an in-progress decode (or kicking off and finishing a - // synchronous decode if we're already waiting on a full decode). - if (mDecoded) { - return NS_OK; - } - - // If we've already got a full decoder running, and have already - // decoded some bytes, we have nothing to do. - if (mDecoder && !mDecoder->IsSizeDecode() && mDecoder->BytesDecoded() > 0) { - return NS_OK; - } - - // If we have a size decode open, interrupt it and shut it down; or if - // the decoder has different flags than what we need - if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) { - nsresult rv = FinishedSomeDecoding(ShutdownReason::NOT_NEEDED); - CONTAINER_ENSURE_SUCCESS(rv); - } - - // If we don't have a decoder, create one - if (!mDecoder) { - rv = InitDecoder(/* aDoSizeDecode = */ false); - CONTAINER_ENSURE_SUCCESS(rv); - - rv = FinishedSomeDecoding(); - CONTAINER_ENSURE_SUCCESS(rv); - } - - MOZ_ASSERT(mDecoder); - - // If we've read all the data we have, we're done - if (mHasSourceData && mDecoder->BytesDecoded() == mSourceData.Length()) { - return NS_OK; - } - - // If we can do decoding now, do so. Small images will decode completely, - // large images will decode a bit and post themselves to the event loop - // to finish decoding. - if (!mDecoded && mHasSourceData && aDecodeType == SYNCHRONOUS_NOTIFY_AND_SOME_DECODE) { - PROFILER_LABEL_PRINTF("RasterImage", "DecodeABitOf", - js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get()); - - DecodePool::Singleton()->DecodeABitOf(this); - return NS_OK; - } - - if (!mDecoded) { - // If we get this far, dispatch the worker. We do this instead of starting - // any immediate decoding to guarantee that all our decode notifications are - // dispatched asynchronously, and to ensure we stay responsive. - DecodePool::Singleton()->RequestDecode(this); - } - + // Perform an async decode. We also take this path if we don't have all the + // source data yet, since sync decoding is impossible in that situation. + DecodePool::Singleton()->AsyncDecode(decoder); return NS_OK; } -// Synchronously decodes as much data as possible -nsresult -RasterImage::SyncDecode() -{ - PROFILER_LABEL_PRINTF("RasterImage", "SyncDecode", - js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get()); - - // If we have a size decoder open, make sure we get the size - if (mDecoder && mDecoder->IsSizeDecode()) { - nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this); - CONTAINER_ENSURE_SUCCESS(rv); - - // If we didn't get the size out of the image, we won't until we get more - // data, so signal that we want a full decode and give up for now. - if (!mHasSize) { - mWantFullDecode = true; - return NS_ERROR_NOT_AVAILABLE; - } - } - - ReentrantMonitorAutoEnter lock(mDecodingMonitor); - - // If the image is waiting for decode work to be notified, go ahead and do that. - if (mDecodeStatus == DecodeStatus::WORK_DONE) { - nsresult rv = FinishedSomeDecoding(); - CONTAINER_ENSURE_SUCCESS(rv); - } - - nsresult rv; - - // If we're decoded already, or decoding until the size was available - // finished us as a side-effect, no worries - if (mDecoded) - return NS_OK; - - // If we don't have any bytes to flush to the decoder, we can't do anything. - // mDecoder->BytesDecoded() can be bigger than mSourceData.Length() if we're - // not storing the source data. - if (mDecoder && mDecoder->BytesDecoded() > mSourceData.Length()) { - return NS_OK; - } - - // If we have a decoder open with different flags than what we need, shut it - // down - if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) { - nsresult rv = FinishedSomeDecoding(ShutdownReason::NOT_NEEDED); - CONTAINER_ENSURE_SUCCESS(rv); - - if (mDecoded && mAnim) { - // We can't redecode animated images, so we'll have to give up. - return NS_ERROR_NOT_AVAILABLE; - } - } - - // If we don't have a decoder, create one - if (!mDecoder) { - rv = InitDecoder(/* aDoSizeDecode = */ false); - CONTAINER_ENSURE_SUCCESS(rv); - } - - MOZ_ASSERT(mDecoder); - - // Write everything we have - rv = DecodeSomeData(mSourceData.Length() - mDecoder->BytesDecoded()); - CONTAINER_ENSURE_SUCCESS(rv); - - rv = FinishedSomeDecoding(); - CONTAINER_ENSURE_SUCCESS(rv); - - // If our decoder's still open, there's still work to be done. - if (mDecoder) { - DecodePool::Singleton()->RequestDecode(this); - } - - // All good if no errors! - return mError ? NS_ERROR_FAILURE : NS_OK; -} - bool RasterImage::CanScale(GraphicsFilter aFilter, const nsIntSize& aSize, @@ -1865,12 +1481,13 @@ RasterImage::CanScale(GraphicsFilter aFilter, // The high-quality scaler requires Skia. return false; #else - // Check basic requirements: HQ downscaling is enabled, we're decoded, the - // flags allow us to do it, and a 'good' filter is being used. The flags may - // ask us not to scale because the caller isn't drawing to the window. If - // we're drawing to something else (e.g. a canvas) we usually have no way of - // updating what we've drawn, so HQ scaling is useless. - if (!gfxPrefs::ImageHQDownscalingEnabled() || !mDecoded || + // Check basic requirements: HQ downscaling is enabled, we have all the source + // data and know our size, the flags allow us to do it, and a 'good' filter is + // being used. The flags may ask us not to scale because the caller isn't + // drawing to the window. If we're drawing to something else (e.g. a canvas) + // we usually have no way of updating what we've drawn, so HQ scaling is + // useless. + if (!gfxPrefs::ImageHQDownscalingEnabled() || !mHasSize || !mHasSourceData || !(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) || aFilter != GraphicsFilter::FILTER_GOOD) { return false; @@ -1914,12 +1531,9 @@ RasterImage::CanScale(GraphicsFilter aFilter, void RasterImage::NotifyNewScaledFrame() { - if (mProgressTracker) { - // Send an invalidation so observers will repaint and can take advantage of - // the new scaled frame if possible. - nsIntRect rect(0, 0, mSize.width, mSize.height); - mProgressTracker->SyncNotifyProgress(NoProgress, rect); - } + // Send an invalidation so observers will repaint and can take advantage of + // the new scaled frame if possible. + NotifyProgress(NoProgress, nsIntRect(0, 0, mSize.width, mSize.height)); } void @@ -2038,36 +1652,31 @@ RasterImage::Draw(gfxContext* aContext, mProgressTracker->OnUnlockedDraw(); } - // We use !mDecoded && mHasSourceData to mean discarded. - if (!mDecoded && mHasSourceData) { - mDrawStartTime = TimeStamp::Now(); - } - - // If a synchronous draw is requested, flush anything that might be sitting around - if (aFlags & FLAG_SYNC_DECODE) { - nsresult rv = SyncDecode(); - NS_ENSURE_SUCCESS(rv, rv); - } - // XXX(seth): For now, we deliberately don't look up a frame of size aSize // (though DrawWithPreDownscaleIfNeeded will do so later). It doesn't make // sense to do so until we support downscale-during-decode. Right now we need // to make sure that we always touch an mSize-sized frame so that we have // something to HQ scale. - DrawableFrameRef ref = LookupFrame(GetRequestedFrameIndex(aWhichFrame), - mSize, aFlags); + DrawableFrameRef ref = + LookupFrame(GetRequestedFrameIndex(aWhichFrame), mSize, aFlags); if (!ref) { // Getting the frame (above) touches the image and kicks off decoding. + if (mDrawStartTime.IsNull()) { + mDrawStartTime = TimeStamp::Now(); + } return NS_OK; } + bool shouldRecordTelemetry = !mDrawStartTime.IsNull() && + ref->IsImageComplete(); + DrawWithPreDownscaleIfNeeded(Move(ref), aContext, aSize, aRegion, aFilter, aFlags); - if (mDecoded && !mDrawStartTime.IsNull()) { + if (shouldRecordTelemetry) { TimeDuration drawLatency = TimeStamp::Now() - mDrawStartTime; - Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY, int32_t(drawLatency.ToMicroseconds())); - // clear the value of mDrawStartTime + Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY, + int32_t(drawLatency.ToMicroseconds())); mDrawStartTime = TimeStamp(); } @@ -2119,19 +1728,6 @@ RasterImage::UnlockImage() SurfaceCache::UnlockImage(ImageKey(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 && !mAnim) { - PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG, - ("RasterImage[0x%p] canceling decode because image " - "is now unlocked.", this)); - ReentrantMonitorAutoEnter lock(mDecodingMonitor); - FinishedSomeDecoding(ShutdownReason::NOT_NEEDED); - return NS_OK; - } - return NS_OK; } @@ -2142,7 +1738,6 @@ RasterImage::RequestDiscard() { if (mDiscardable && // Enabled at creation time... mLockCount == 0 && // ...not temporarily disabled... - mDecoded && // ...and have something to discard. CanDiscard()) { Discard(); } @@ -2150,63 +1745,6 @@ RasterImage::RequestDiscard() return NS_OK; } -// Flushes up to aMaxBytes to the decoder. -nsresult -RasterImage::DecodeSomeData(size_t aMaxBytes) -{ - MOZ_ASSERT(mDecoder, "Should have a decoder"); - - mDecodingMonitor.AssertCurrentThreadIn(); - - // If we have nothing else to decode, return. - if (mDecoder->BytesDecoded() == mSourceData.Length()) { - return NS_OK; - } - - MOZ_ASSERT(mDecoder->BytesDecoded() < mSourceData.Length()); - - // write the proper amount of data - size_t bytesToDecode = min(aMaxBytes, - mSourceData.Length() - mDecoder->BytesDecoded()); - return WriteToDecoder(mSourceData.Elements() + mDecoder->BytesDecoded(), - bytesToDecode); - -} - -// There are various indicators that tell us we're finished with the decode -// task at hand and can shut down the decoder. -// -// This method may not be called if there is no decoder. -bool -RasterImage::IsDecodeFinished() -{ - // Precondition - mDecodingMonitor.AssertCurrentThreadIn(); - MOZ_ASSERT(mDecoder, "Should have a decoder"); - - // The decode is complete if we got what we wanted. - if (mDecoder->IsSizeDecode()) { - if (mDecoder->HasSize()) { - return true; - } - } else if (mDecoder->GetDecodeDone()) { - return true; - } - - // Otherwise, if we have all the source data and wrote all the source data, - // we're done. - // - // (NB - This can be the case even for non-erroneous images because - // Decoder::GetDecodeDone() might not return true until after we call - // Decoder::Finish() in ShutdownDecoder()) - if (mHasSourceData && (mDecoder->BytesDecoded() == mSourceData.Length())) { - return true; - } - - // If we get here, assume it's not finished. - return false; -} - // Indempotent error flagging routine. If a decoder is open, shuts it down. void RasterImage::DoError() @@ -2221,14 +1759,6 @@ RasterImage::DoError() return; } - // Calling FinishedSomeDecoding requires us to be in the decoding monitor. - ReentrantMonitorAutoEnter lock(mDecodingMonitor); - - // If we're mid-decode, shut down the decoder. - if (mDecoder) { - FinishedSomeDecoding(ShutdownReason::FATAL_ERROR); - } - // Put the container in an error state. mError = true; @@ -2239,11 +1769,8 @@ RasterImage::DoError() /* static */ void RasterImage::HandleErrorWorker::DispatchIfNeeded(RasterImage* aImage) { - if (!aImage->mPendingError) { - aImage->mPendingError = true; - nsRefPtr worker = new HandleErrorWorker(aImage); - NS_DispatchToMainThread(worker); - } + nsRefPtr worker = new HandleErrorWorker(aImage); + NS_DispatchToMainThread(worker); } RasterImage::HandleErrorWorker::HandleErrorWorker(RasterImage* aImage) @@ -2264,12 +1791,12 @@ RasterImage::HandleErrorWorker::Run() // RasterImage without processing. The RasterImage is passed as the closure. // Always reads everything it gets, even if the data is erroneous. NS_METHOD -RasterImage::WriteToRasterImage(nsIInputStream* /* unused */, - void* aClosure, - const char* aFromRawSegment, - uint32_t /* unused */, - uint32_t aCount, - uint32_t* aWriteCount) +RasterImage::WriteToSourceBuffer(nsIInputStream* /* unused */, + void* aClosure, + const char* aFromRawSegment, + uint32_t /* unused */, + uint32_t aCount, + uint32_t* aWriteCount) { // Retrieve the RasterImage RasterImage* image = static_cast(aClosure); @@ -2278,7 +1805,7 @@ RasterImage::WriteToRasterImage(nsIInputStream* /* unused */, // here, because returning an error means that ReadSegments stops // reading data, violating our invariant that we read everything we get. // If we hit OOM then we fail and the load is aborted. - nsresult rv = image->AddSourceData(aFromRawSegment, aCount); + nsresult rv = image->mSourceBuffer->Append(aFromRawSegment, aCount); if (rv == NS_ERROR_OUT_OF_MEMORY) { image->DoError(); return rv; @@ -2310,103 +1837,19 @@ RasterImage::GetFramesNotified(uint32_t *aFramesNotified) } #endif -nsresult -RasterImage::RequestDecodeIfNeeded(nsresult aStatus, - ShutdownReason aReason, - bool aDone, - bool aWasSize) +void +RasterImage::NotifyProgress(Progress aProgress, + const nsIntRect& aInvalidRect /* = nsIntRect() */, + uint32_t aFlags /* = DECODE_FLAGS_DEFAULT */) { MOZ_ASSERT(NS_IsMainThread()); - // If we were a size decode and a full decode was requested, now's the time. - if (NS_SUCCEEDED(aStatus) && - aReason == ShutdownReason::DONE && - aDone && - aWasSize && - mWantFullDecode) { - mWantFullDecode = false; - - // If we're not meant to be storing source data and we just got the size, - // we need to synchronously flush all the data we got to a full decoder. - // When that decoder is shut down, we'll also clear our source data. - return StoringSourceData() ? RequestDecode() - : SyncDecode(); - } - - // We don't need a full decode right now, so just return the existing status. - return aStatus; -} - -nsresult -RasterImage::FinishedSomeDecoding(ShutdownReason aReason /* = ShutdownReason::DONE */, - Progress aProgress /* = NoProgress */) -{ - MOZ_ASSERT(NS_IsMainThread()); - - mDecodingMonitor.AssertCurrentThreadIn(); - - // Ensure that, if the decoder is the last reference to the image, we don't - // destroy it by destroying the decoder. + // Ensure that we stay alive long enough to finish notifying. nsRefPtr image(this); - bool done = false; - bool wasSize = false; - bool wasDefaultFlags = false; - nsIntRect invalidRect; - nsresult rv = NS_OK; + bool wasDefaultFlags = aFlags == DECODE_FLAGS_DEFAULT; Progress progress = aProgress; - - if (image->mDecoder) { - invalidRect = image->mDecoder->TakeInvalidRect(); - progress |= image->mDecoder->TakeProgress(); - wasDefaultFlags = image->mDecoder->GetDecodeFlags() == DECODE_FLAGS_DEFAULT; - - if (!image->mDecoder->IsSizeDecode() && image->mDecoder->ChunkCount()) { - Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, - image->mDecoder->ChunkCount()); - } - - if (!image->mHasSize && image->mDecoder->HasSize()) { - image->mDecoder->SetSizeOnImage(); - } - - // If the decode finished, or we're specifically being told to shut down, - // tell the image and shut down the decoder. - if (image->IsDecodeFinished() || aReason != ShutdownReason::DONE) { - done = true; - - // Hold on to a reference to the decoder until we're done with it - nsRefPtr decoder = image->mDecoder; - - wasSize = decoder->IsSizeDecode(); - - // Do some telemetry if this isn't a size decode. - if (!wasSize) { - Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME, - int32_t(decoder->DecodeTime().ToMicroseconds())); - - // We record the speed for only some decoders. The rest have - // SpeedHistogram return HistogramCount. - Telemetry::ID id = decoder->SpeedHistogram(); - if (id < Telemetry::HistogramCount) { - int32_t KBps = int32_t(decoder->BytesDecoded() / - (1024 * decoder->DecodeTime().ToSeconds())); - Telemetry::Accumulate(id, KBps); - } - } - - // We need to shut down the decoder first, in order to ensure all - // decoding routines have been finished. - rv = image->ShutdownDecoder(aReason); - if (NS_FAILED(rv)) { - image->DoError(); - } - - // If there were any final changes, grab them. - invalidRect.Union(decoder->TakeInvalidRect()); - progress |= decoder->TakeProgress(); - } - } + nsIntRect invalidRect = aInvalidRect; if (!invalidRect.IsEmpty() && wasDefaultFlags) { // Update our image container since we're invalidating. @@ -2417,7 +1860,7 @@ RasterImage::FinishedSomeDecoding(ShutdownReason aReason /* = ShutdownReason::DO // Accumulate the progress changes. We don't permit recursive notifications // because they cause subtle concurrency bugs, so we'll delay sending out // the notifications until we pop back to the lowest invocation of - // FinishedSomeDecoding on the stack. + // NotifyProgress on the stack. mNotifyProgress |= progress; mNotifyInvalidRect.Union(invalidRect); } else { @@ -2442,8 +1885,58 @@ RasterImage::FinishedSomeDecoding(ShutdownReason aReason /* = ShutdownReason::DO mNotifyInvalidRect = nsIntRect(); } } +} - return RequestDecodeIfNeeded(rv, aReason, done, wasSize); +void +RasterImage::FinalizeDecoder(Decoder* aDecoder) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aDecoder); + MOZ_ASSERT(mError || mHasSize || !aDecoder->HasSize(), + "Should have handed off size by now"); + + // Send out any final notifications. + NotifyProgress(aDecoder->TakeProgress(), + aDecoder->TakeInvalidRect(), + aDecoder->GetDecodeFlags()); + + bool wasSize = aDecoder->IsSizeDecode(); + bool done = aDecoder->GetDecodeDone(); + + if (!wasSize && aDecoder->ChunkCount()) { + Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, + aDecoder->ChunkCount()); + } + + if (done) { + // Do some telemetry if this isn't a size decode. + if (!wasSize) { + Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME, + int32_t(aDecoder->DecodeTime().ToMicroseconds())); + + // We record the speed for only some decoders. The rest have + // SpeedHistogram return HistogramCount. + Telemetry::ID id = aDecoder->SpeedHistogram(); + if (id < Telemetry::HistogramCount) { + int32_t KBps = int32_t(aDecoder->BytesDecoded() / + (1024 * aDecoder->DecodeTime().ToSeconds())); + Telemetry::Accumulate(id, KBps); + } + } + + // Detect errors. + if (aDecoder->HasError() && !aDecoder->WasAborted()) { + DoError(); + } else if (wasSize && !mHasSize) { + DoError(); + } + } + + // If we were a size decode and a full decode was requested, now's the time. + if (done && wasSize && mWantFullDecode) { + mWantFullDecode = false; + RequestDecode(); + } } already_AddRefed diff --git a/image/src/RasterImage.h b/image/src/RasterImage.h index 34e94150461..3685d4159b2 100644 --- a/image/src/RasterImage.h +++ b/image/src/RasterImage.h @@ -29,7 +29,6 @@ #include "nsIObserver.h" #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" -#include "mozilla/ReentrantMonitor.h" #include "mozilla/TimeStamp.h" #include "mozilla/TypedEnum.h" #include "mozilla/WeakPtr.h" @@ -132,6 +131,13 @@ namespace image { class Decoder; class FrameAnimator; +class SourceBuffer; + +MOZ_BEGIN_ENUM_CLASS(DecodeStrategy, uint8_t) + ASYNC, + SYNC_FOR_SMALL_IMAGES, + SYNC_IF_POSSIBLE +MOZ_END_ENUM_CLASS(DecodeStrategy) class RasterImage MOZ_FINAL : public ImageResource , public nsIProperties @@ -162,10 +168,10 @@ public: virtual void OnSurfaceDiscarded() MOZ_OVERRIDE; // Raster-specific methods - static NS_METHOD WriteToRasterImage(nsIInputStream* aIn, void* aClosure, - const char* aFromRawSegment, - uint32_t aToOffset, uint32_t aCount, - uint32_t* aWriteCount); + static NS_METHOD WriteToSourceBuffer(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount); /* The total number of frames in this image. */ uint32_t GetNumFrames() const { return mFrameCount; } @@ -196,25 +202,36 @@ public: */ void SetLoopCount(int32_t aLoopCount); - /* notification that the entire image has been decoded */ - void DecodingComplete(imgFrame* aFinalFrame, bool aIsAnimated); - void MarkAnimationDecoded(); + /// Notification that the entire image has been decoded. + void OnDecodingComplete(); + + /** + * Sends the provided progress notifications to ProgressTracker. + * + * Main-thread only. + * + * @param aProgress The progress notifications to send. + * @param aInvalidRect An invalidation rect to send. + * @param aFlags The decode flags used by the decoder that generated + * these notifications, or DECODE_FLAGS_DEFAULT if the + * notifications don't come from a decoder. + */ + void NotifyProgress(Progress aProgress, + const nsIntRect& aInvalidRect = nsIntRect(), + uint32_t aFlags = 0); + + /** + * Records telemetry and does final teardown of the provided decoder. + * + * Main-thread only. + */ + void FinalizeDecoder(Decoder* aDecoder); ////////////////////////////////////////////////////////////////////////////// // Network callbacks. ////////////////////////////////////////////////////////////////////////////// - /* Add compressed source data to the imgContainer. - * - * The decoder will use this data, either immediately or at draw time, to - * decode the image. - * - * XXX This method's only caller (WriteToContainer) ignores the return - * value. Should this just return void? - */ - nsresult AddSourceData(const char *aBuffer, uint32_t aCount); - virtual nsresult OnImageDataAvailable(nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aInStr, @@ -238,7 +255,7 @@ public: * Thus, pre-allocation simplifies code and reduces the total number of * allocations. */ - nsresult SetSourceSizeHint(uint32_t sizeHint); + nsresult SetSourceSizeHint(uint32_t aSizeHint); /* Provide a hint for the requested resolution of the resulting image. */ void SetRequestedResolution(const nsIntSize requestedResolution) { @@ -268,14 +285,6 @@ public: static void Initialize(); private: - friend class DecodePool; - friend class DecodeWorker; - friend class FrameNeededWorker; - friend class NotifyProgressWorker; - - nsresult FinishedSomeDecoding(ShutdownReason aReason = ShutdownReason::DONE, - Progress aProgress = NoProgress); - void DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef, gfxContext* aContext, const nsIntSize& aSize, @@ -305,44 +314,34 @@ private: size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation, MallocSizeOf aMallocSizeOf) const; - nsresult DoImageDataComplete(); - already_AddRefed GetCurrentImage(); void UpdateImageContainer(); - enum RequestDecodeType { - ASYNCHRONOUS, - SYNCHRONOUS_NOTIFY, - SYNCHRONOUS_NOTIFY_AND_SOME_DECODE - }; - NS_IMETHOD RequestDecodeCore(RequestDecodeType aDecodeType); - // We would like to just check if we have a zero lock count, but we can't do // that for animated images because in EnsureAnimExists we lock the image and // never unlock so that animated images always have their lock count >= 1. In // that case we use our animation consumers count as a proxy for lock count. bool IsUnlocked() { return (mLockCount == 0 || (mAnim && mAnimationConsumers == 0)); } + + ////////////////////////////////////////////////////////////////////////////// + // Decoding. + ////////////////////////////////////////////////////////////////////////////// + + already_AddRefed CreateDecoder(bool aDoSizeDecode, uint32_t aFlags); + + void WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify); + + NS_IMETHOD Decode(DecodeStrategy aStrategy, uint32_t aFlags, + bool aDoSizeDecode = false); + private: // data nsIntSize mSize; Orientation mOrientation; - // Whether our frames were decoded using any special flags. - // Some flags (e.g. unpremultiplied data) may not be compatible - // with the browser's needs for displaying the image to the user. - // As such, we may need to redecode if we're being asked for - // a frame with different flags. 0 indicates default flags. - // - // Valid flag bits are imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA - // and imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION. - uint32_t mFrameDecodeFlags; - nsCOMPtr mProperties; - //! All the frames of the image. - // IMPORTANT: if you use mAnim in a method, call EnsureImageIsDecoded() first to ensure - // that the frames actually exist (they may have been discarded to save memory, or - // we maybe decoding on draw). + /// If this image is animated, a FrameAnimator which manages its animation. UniquePtr mAnim; // Image locking. @@ -371,18 +370,8 @@ private: // data uint32_t mFramesNotified; #endif - // Below are the pieces of data that can be accessed on more than one thread - // at once, and hence need to be locked by mDecodingMonitor. - - // BEGIN LOCKED MEMBER VARIABLES - ReentrantMonitor mDecodingMonitor; - - FallibleTArray mSourceData; - - // Decoder and friends - nsRefPtr mDecoder; - DecodeStatus mDecodeStatus; - // END LOCKED MEMBER VARIABLES + // The source data for this image. + nsRefPtr mSourceBuffer; // The number of frames this image has. uint32_t mFrameCount; @@ -398,10 +387,7 @@ private: // data bool mTransient:1; // Is the image short-lived? bool mDiscardable:1; // Is container discardable? bool mHasSourceData:1; // Do we have source data? - - // Do we have the frames in decoded form? - bool mDecoded:1; - bool mHasBeenDecoded:1; + bool mHasBeenDecoded:1; // Decoded at least once? // Whether we're waiting to start animation. If we get a StartAnimation() call // but we don't yet have more than one frame, mPendingAnimation is set so that @@ -416,27 +402,11 @@ private: // data // off a full decode. bool mWantFullDecode:1; - // Set when a decode worker detects an error off-main-thread. Once the error - // is handled on the main thread, mError is set, but mPendingError is used to - // stop decode work immediately. - bool mPendingError:1; - - // Decoding - nsresult RequestDecodeIfNeeded(nsresult aStatus, ShutdownReason aReason, - bool aDone, bool aWasSize); - nsresult WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify); - nsresult SyncDecode(); - nsresult InitDecoder(bool aDoSizeDecode); - nsresult WriteToDecoder(const char *aBuffer, uint32_t aCount); - nsresult DecodeSomeData(size_t aMaxBytes); - bool IsDecodeFinished(); TimeStamp mDrawStartTime; // Initializes ProgressTracker and resets it on RasterImage destruction. nsAutoPtr mProgressTrackerInit; - nsresult ShutdownDecoder(ShutdownReason aReason); - ////////////////////////////////////////////////////////////////////////////// // Scaling. @@ -477,7 +447,6 @@ private: // data // Helpers bool CanDiscard(); - bool StoringSourceData() const; protected: explicit RasterImage(ProgressTracker* aProgressTracker = nullptr, diff --git a/image/test/crashtests/crashtests.list b/image/test/crashtests/crashtests.list index 6fc77231c98..05d24af3a2d 100644 --- a/image/test/crashtests/crashtests.list +++ b/image/test/crashtests/crashtests.list @@ -16,7 +16,7 @@ skip-if(AddressSanitizer) load invalid-size-second-frame.gif # Animated gifs with a very large canvas, but tiny actual content. load delaytest.html?523528-1.gif -load delaytest.html?523528-2.gif +skip load delaytest.html?523528-2.gif # disabled due to bug 1079627 # this would have exposed the leak discovered in bug 642902 load invalid-icc-profile.jpg diff --git a/image/test/reftest/gif/reftest.list b/image/test/reftest/gif/reftest.list index c6b21314b55..a3ed573dc38 100644 --- a/image/test/reftest/gif/reftest.list +++ b/image/test/reftest/gif/reftest.list @@ -48,4 +48,4 @@ skip == test_bug641198.html animation2a-finalframe.gif # bug 773482 # won't be in the text of the contents themselves. --$(boundary)\r\n means # "Here is the beginning of a boundary," and --$(boundary)-- means "All done # sending you parts.") -skip-if(B2G) HTTP == webcam.html blue.gif # bug 773482 +skip-if(B2G) random-if(browserIsRemote) HTTP == webcam.html blue.gif # bug 773482 diff --git a/image/test/reftest/ico/cur/reftest.list b/image/test/reftest/ico/cur/reftest.list index 635136506bc..68c0b6d2ab3 100644 --- a/image/test/reftest/ico/cur/reftest.list +++ b/image/test/reftest/ico/cur/reftest.list @@ -1,4 +1,4 @@ # ICO BMP and PNG mixed tests -== wrapper.html?pointer.cur wrapper.html?pointer.png +random == wrapper.html?pointer.cur wrapper.html?pointer.png diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index aa81d389c4c..f7d0b529ef7 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -971,7 +971,7 @@ fails == 413027-3.html 413027-3-ref.html == 413286-5.html 413286-5-ref.html == 413286-6.html 413286-6-ref.html skip-if(cocoaWidget) == 413292-1.html 413292-1-ref.html # disabling due to failure loading on some mac tinderboxes. See bug 432954 -fuzzy-if(Android&&AndroidVersion>=15,11,15) == 413361-1.html 413361-1-ref.html +fuzzy-if(Android&&AndroidVersion>=15,11,15) fuzzy-if(B2G,11,17) == 413361-1.html 413361-1-ref.html == 413840-background-unchanged.html 413840-background-unchanged-ref.html == 413840-ltr-offsets.html 413840-ltr-offsets-ref.html == 413840-rtl-offsets.html 413840-rtl-offsets-ref.html diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 8709c4bbce6..c43114b1485 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -3791,9 +3791,6 @@ 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); -// The longest time we can spend in an iteration of an async decode -pref("image.mem.max_ms_before_yield", 5); - // Minimum timeout for expiring unused images from the surface cache, in // milliseconds. This controls how long we store cached temporary surfaces. pref("image.mem.surfacecache.min_expiration_ms", 60000); // 60ms diff --git a/toolkit/content/tests/chrome/chrome.ini b/toolkit/content/tests/chrome/chrome.ini index 7d925595794..0ed34104ba5 100644 --- a/toolkit/content/tests/chrome/chrome.ini +++ b/toolkit/content/tests/chrome/chrome.ini @@ -138,6 +138,7 @@ skip-if = buildapp == 'mulet' [test_popupremoving_frame.xul] [test_position.xul] [test_preferences.xul] +skip-if = toolkit != "cocoa" [test_preferences_beforeaccept.xul] support-files = window_preferences_beforeaccept.xul [test_preferences_onsyncfrompreference.xul]