Bug 1079627 (Part 3) - Support multiple decoders for a single RasterImage. r=tn

This commit is contained in:
Seth Fowler 2015-01-11 05:34:20 -08:00
parent c0f12016a1
commit 12931c48ec
14 changed files with 708 additions and 1201 deletions

View File

@ -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);

View File

@ -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<nsIRunnable> worker = new NotifyProgressWorker(aImage);
MOZ_ASSERT(aImage);
nsCOMPtr<nsIRunnable> 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<RasterImage> 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<nsIRunnable> 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<Decoder> 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<nsIThread> 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<nsresult> rv = NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg));
MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread");
}
}
private:
nsRefPtr<RasterImage> mImage;
nsRefPtr<Decoder> mDecoder;
};
#ifdef MOZ_NUWA_PROCESS
@ -194,13 +179,6 @@ DecodePool::Singleton()
return sSingleton;
}
already_AddRefed<nsIEventTarget>
DecodePool::GetEventTarget()
{
nsCOMPtr<nsIEventTarget> 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<nsIRunnable> worker = new DecodeWorker(aImage);
nsCOMPtr<nsIRunnable> 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<nsIEventTarget>
DecodePool::GetEventTarget()
{
MutexAutoLock threadPoolLock(mThreadPoolMutex);
nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool);
return target.forget();
}
already_AddRefed<nsIRunnable>
DecodePool::CreateDecodeWorker(Decoder* aDecoder)
{
MOZ_ASSERT(aDecoder);
nsCOMPtr<nsIRunnable> 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<Decoder> 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

View File

@ -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<nsIEventTarget> 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<nsIRunnable> 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<DecodePool> sSingleton;
// mThreadPoolMutex protects mThreadPool. For all RasterImages R,

View File

@ -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<nsIEventTarget> target = decodePool->GetEventTarget();
if (MOZ_UNLIKELY(!target)) {
// We're shutting down and the DecodePool's thread pool has been destroyed.
return;
}
nsCOMPtr<nsIRunnable> 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<nsIConsoleService> 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();
}
}
/*

View File

@ -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<RasterImage> mImage;
Maybe<SourceBufferIterator> 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

View File

@ -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.

File diff suppressed because it is too large Load Diff

View File

@ -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<layers::Image> 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<Decoder> 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<nsIProperties> 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<FrameAnimator> 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<char> mSourceData;
// Decoder and friends
nsRefPtr<Decoder> mDecoder;
DecodeStatus mDecodeStatus;
// END LOCKED MEMBER VARIABLES
// The source data for this image.
nsRefPtr<SourceBuffer> 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<ProgressTrackerInit> mProgressTrackerInit;
nsresult ShutdownDecoder(ShutdownReason aReason);
//////////////////////////////////////////////////////////////////////////////
// Scaling.
@ -477,7 +447,6 @@ private: // data
// Helpers
bool CanDiscard();
bool StoringSourceData() const;
protected:
explicit RasterImage(ProgressTracker* aProgressTracker = nullptr,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]