michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Must #include ImageLogging.h before any IPDL-generated files or other files that #include prlog.h michael@0: #include "ImageLogging.h" michael@0: michael@0: #include "RasterImage.h" michael@0: michael@0: #include "base/histogram.h" michael@0: #include "gfxPlatform.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "imgDecoderObserver.h" michael@0: #include "nsError.h" michael@0: #include "Decoder.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "prenv.h" michael@0: #include "prsystem.h" michael@0: #include "ImageContainer.h" michael@0: #include "Layers.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIThreadPool.h" michael@0: #include "nsXPCOMCIDInternal.h" michael@0: #include "nsIObserverService.h" michael@0: #include "FrameAnimator.h" michael@0: michael@0: #include "nsPNGDecoder.h" michael@0: #include "nsGIFDecoder2.h" michael@0: #include "nsJPEGDecoder.h" michael@0: #include "nsBMPDecoder.h" michael@0: #include "nsICODecoder.h" michael@0: #include "nsIconDecoder.h" michael@0: michael@0: #include "gfxContext.h" michael@0: michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "mozilla/RefPtr.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "mozilla/ClearOnShutdown.h" michael@0: #include "mozilla/gfx/Scale.h" michael@0: michael@0: #include "GeckoProfiler.h" michael@0: #include "gfx2DGlue.h" michael@0: #include michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: #include "ipc/Nuwa.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::gfx; michael@0: using namespace mozilla::image; michael@0: using namespace mozilla::layers; michael@0: michael@0: // a mask for flags that will affect the decoding michael@0: #define DECODE_FLAGS_MASK (imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA | imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) michael@0: #define DECODE_FLAGS_DEFAULT 0 michael@0: michael@0: /* Accounting for compressed data */ michael@0: #if defined(PR_LOGGING) michael@0: static PRLogModuleInfo * michael@0: GetCompressedImageAccountingLog() michael@0: { michael@0: static PRLogModuleInfo *sLog; michael@0: if (!sLog) michael@0: sLog = PR_NewLogModule("CompressedImageAccounting"); michael@0: return sLog; michael@0: } michael@0: #else michael@0: #define GetCompressedImageAccountingLog() michael@0: #endif michael@0: michael@0: // Tweakable progressive decoding parameters. These are initialized to 0 here michael@0: // because otherwise, we have to initialize them in a static initializer, which michael@0: // makes us slower to start up. michael@0: static uint32_t gDecodeBytesAtATime = 0; michael@0: static uint32_t gMaxMSBeforeYield = 0; michael@0: static bool gHQDownscaling = false; michael@0: // This is interpreted as a floating-point value / 1000 michael@0: static uint32_t gHQDownscalingMinFactor = 1000; michael@0: static bool gMultithreadedDecoding = true; michael@0: static int32_t gDecodingThreadLimit = -1; michael@0: // The number of pixels in a 5 megapixel decoded image. michael@0: // Equivalent to an example 3125x1600 resolution. michael@0: static uint32_t gHQUpscalingMaxSize = 20971520; michael@0: michael@0: // The maximum number of times any one RasterImage was decoded. This is only michael@0: // used for statistics. michael@0: static int32_t sMaxDecodeCount = 0; michael@0: michael@0: static void michael@0: InitPrefCaches() michael@0: { michael@0: Preferences::AddUintVarCache(&gDecodeBytesAtATime, michael@0: "image.mem.decode_bytes_at_a_time", 200000); michael@0: Preferences::AddUintVarCache(&gMaxMSBeforeYield, michael@0: "image.mem.max_ms_before_yield", 400); michael@0: Preferences::AddBoolVarCache(&gHQDownscaling, michael@0: "image.high_quality_downscaling.enabled", false); michael@0: Preferences::AddUintVarCache(&gHQDownscalingMinFactor, michael@0: "image.high_quality_downscaling.min_factor", 1000); michael@0: Preferences::AddBoolVarCache(&gMultithreadedDecoding, michael@0: "image.multithreaded_decoding.enabled", true); michael@0: Preferences::AddIntVarCache(&gDecodingThreadLimit, michael@0: "image.multithreaded_decoding.limit", -1); michael@0: Preferences::AddUintVarCache(&gHQUpscalingMaxSize, michael@0: "image.high_quality_upscaling.max_size", 20971520); michael@0: } michael@0: michael@0: /* We define our own error checking macros here for 2 reasons: michael@0: * michael@0: * 1) Most of the failures we encounter here will (hopefully) be michael@0: * the result of decoding failures (ie, bad data) and not code michael@0: * failures. As such, we don't want to clutter up debug consoles michael@0: * with spurious messages about NS_ENSURE_SUCCESS failures. michael@0: * michael@0: * 2) We want to set the internal error flag, shutdown properly, michael@0: * and end up in an error state. michael@0: * michael@0: * So this macro should be called when the desired failure behavior michael@0: * is to put the container into an error state and return failure. michael@0: * It goes without saying that macro won't compile outside of a michael@0: * non-static RasterImage method. michael@0: */ michael@0: #define LOG_CONTAINER_ERROR \ michael@0: PR_BEGIN_MACRO \ michael@0: PR_LOG (GetImgLog(), PR_LOG_ERROR, \ michael@0: ("RasterImage: [this=%p] Error " \ michael@0: "detected at line %u for image of " \ michael@0: "type %s\n", this, __LINE__, \ michael@0: mSourceDataMimeType.get())); \ michael@0: PR_END_MACRO michael@0: michael@0: #define CONTAINER_ENSURE_SUCCESS(status) \ michael@0: PR_BEGIN_MACRO \ michael@0: nsresult _status = status; /* eval once */ \ michael@0: if (NS_FAILED(_status)) { \ michael@0: LOG_CONTAINER_ERROR; \ michael@0: DoError(); \ michael@0: return _status; \ michael@0: } \ michael@0: PR_END_MACRO michael@0: michael@0: #define CONTAINER_ENSURE_TRUE(arg, rv) \ michael@0: PR_BEGIN_MACRO \ michael@0: if (!(arg)) { \ michael@0: LOG_CONTAINER_ERROR; \ michael@0: DoError(); \ michael@0: return rv; \ michael@0: } \ michael@0: PR_END_MACRO michael@0: michael@0: michael@0: michael@0: static int num_containers; michael@0: static int num_discardable_containers; michael@0: static int64_t total_source_bytes; michael@0: static int64_t discardable_source_bytes; michael@0: michael@0: /* Are we globally disabling image discarding? */ michael@0: static bool michael@0: DiscardingEnabled() michael@0: { michael@0: static bool inited; michael@0: static bool enabled; michael@0: michael@0: if (!inited) { michael@0: inited = true; michael@0: michael@0: enabled = (PR_GetEnv("MOZ_DISABLE_IMAGE_DISCARD") == nullptr); michael@0: } michael@0: michael@0: return enabled; michael@0: } michael@0: michael@0: class ScaleRequest michael@0: { michael@0: public: michael@0: ScaleRequest(RasterImage* aImage, const gfxSize& aScale, imgFrame* aSrcFrame) michael@0: : scale(aScale) michael@0: , dstLocked(false) michael@0: , done(false) michael@0: , stopped(false) michael@0: { michael@0: MOZ_ASSERT(!aSrcFrame->GetIsPaletted()); michael@0: MOZ_ASSERT(aScale.width > 0 && aScale.height > 0); michael@0: michael@0: weakImage = aImage->asWeakPtr(); michael@0: srcRect = aSrcFrame->GetRect(); michael@0: michael@0: nsIntRect dstRect = srcRect; michael@0: dstRect.ScaleRoundOut(scale.width, scale.height); michael@0: dstSize = dstRect.Size(); michael@0: } michael@0: michael@0: // This can only be called on the main thread. michael@0: bool GetSurfaces(imgFrame* srcFrame) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsRefPtr image = weakImage.get(); michael@0: if (!image) { michael@0: return false; michael@0: } michael@0: michael@0: bool success = false; michael@0: if (!dstLocked) { michael@0: // We need to hold a lock onto the RasterImage object itself so that michael@0: // it (and its associated imgFrames) aren't marked as discardable. michael@0: bool imgLocked = NS_SUCCEEDED(image->LockImage()); michael@0: bool srcLocked = NS_SUCCEEDED(srcFrame->LockImageData()); michael@0: dstLocked = NS_SUCCEEDED(dstFrame->LockImageData()); michael@0: michael@0: nsRefPtr dstASurf; michael@0: nsRefPtr srcASurf; michael@0: success = srcLocked && NS_SUCCEEDED(srcFrame->GetSurface(getter_AddRefs(srcASurf))); michael@0: success = success && dstLocked && NS_SUCCEEDED(dstFrame->GetSurface(getter_AddRefs(dstASurf))); michael@0: michael@0: success = success && imgLocked && srcLocked && dstLocked && srcASurf && dstASurf; michael@0: michael@0: if (success) { michael@0: srcSurface = srcASurf->GetAsImageSurface(); michael@0: dstSurface = dstASurf->GetAsImageSurface(); michael@0: srcData = srcSurface->Data(); michael@0: dstData = dstSurface->Data(); michael@0: srcStride = srcSurface->Stride(); michael@0: dstStride = dstSurface->Stride(); michael@0: srcFormat = mozilla::gfx::ImageFormatToSurfaceFormat(srcFrame->GetFormat()); michael@0: } michael@0: michael@0: // We have references to the Thebes surfaces, so we don't need to leave michael@0: // the source frame (that we don't own) locked. We'll unlock the michael@0: // destination frame in ReleaseSurfaces(), below. michael@0: if (srcLocked) { michael@0: success = NS_SUCCEEDED(srcFrame->UnlockImageData()) && success; michael@0: } michael@0: michael@0: success = success && srcSurface && dstSurface; michael@0: } michael@0: michael@0: return success; michael@0: } michael@0: michael@0: // This can only be called on the main thread. michael@0: bool ReleaseSurfaces() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsRefPtr image = weakImage.get(); michael@0: if (!image) { michael@0: return false; michael@0: } michael@0: michael@0: bool success = false; michael@0: if (dstLocked) { michael@0: if (DiscardingEnabled()) michael@0: dstFrame->SetDiscardable(); michael@0: success = NS_SUCCEEDED(dstFrame->UnlockImageData()); michael@0: success = success && NS_SUCCEEDED(image->UnlockImage()); michael@0: michael@0: dstLocked = false; michael@0: srcData = nullptr; michael@0: dstData = nullptr; michael@0: srcSurface = nullptr; michael@0: dstSurface = nullptr; michael@0: } michael@0: return success; michael@0: } michael@0: michael@0: // These values may only be touched on the main thread. michael@0: WeakPtr weakImage; michael@0: nsAutoPtr dstFrame; michael@0: nsRefPtr srcSurface; michael@0: nsRefPtr dstSurface; michael@0: michael@0: // Below are the values that may be touched on the scaling thread. michael@0: gfxSize scale; michael@0: uint8_t* srcData; michael@0: uint8_t* dstData; michael@0: nsIntRect srcRect; michael@0: gfxIntSize dstSize; michael@0: uint32_t srcStride; michael@0: uint32_t dstStride; michael@0: mozilla::gfx::SurfaceFormat srcFormat; michael@0: bool dstLocked; michael@0: bool done; michael@0: // This boolean is accessed from both threads simultaneously without locking. michael@0: // That's safe because stopping a ScaleRequest is strictly an optimization; michael@0: // if we're not cache-coherent, at worst we'll do extra work. michael@0: bool stopped; michael@0: }; michael@0: michael@0: class DrawRunner : public nsRunnable michael@0: { michael@0: public: michael@0: DrawRunner(ScaleRequest* request) michael@0: : mScaleRequest(request) michael@0: {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: // ScaleWorker is finished with this request, so we can unlock the data now. michael@0: mScaleRequest->ReleaseSurfaces(); michael@0: michael@0: nsRefPtr image = mScaleRequest->weakImage.get(); michael@0: michael@0: if (image) { michael@0: RasterImage::ScaleStatus status; michael@0: if (mScaleRequest->done) { michael@0: status = RasterImage::SCALE_DONE; michael@0: } else { michael@0: status = RasterImage::SCALE_INVALID; michael@0: } michael@0: michael@0: image->ScalingDone(mScaleRequest, status); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: /* members */ michael@0: nsAutoPtr mScaleRequest; michael@0: }; michael@0: michael@0: class ScaleRunner : public nsRunnable michael@0: { michael@0: public: michael@0: ScaleRunner(RasterImage* aImage, const gfxSize& aScale, imgFrame* aSrcFrame) michael@0: { michael@0: nsAutoPtr request(new ScaleRequest(aImage, aScale, aSrcFrame)); michael@0: michael@0: // Destination is unconditionally ARGB32 because that's what the scaler michael@0: // outputs. michael@0: request->dstFrame = new imgFrame(); michael@0: nsresult rv = request->dstFrame->Init(0, 0, request->dstSize.width, request->dstSize.height, michael@0: gfxImageFormat::ARGB32); michael@0: michael@0: if (NS_FAILED(rv) || !request->GetSurfaces(aSrcFrame)) { michael@0: return; michael@0: } michael@0: michael@0: aImage->ScalingStart(request); michael@0: michael@0: mScaleRequest = request; michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: // An alias just for ease of typing michael@0: ScaleRequest* request = mScaleRequest; michael@0: michael@0: if (!request->stopped) { michael@0: request->done = mozilla::gfx::Scale(request->srcData, request->srcRect.width, request->srcRect.height, request->srcStride, michael@0: request->dstData, request->dstSize.width, request->dstSize.height, request->dstStride, michael@0: request->srcFormat); michael@0: } else { michael@0: request->done = false; michael@0: } michael@0: michael@0: // OK, we've got a new scaled image. Let's get the main thread to unlock and michael@0: // redraw it. michael@0: nsRefPtr runner = new DrawRunner(mScaleRequest.forget()); michael@0: NS_DispatchToMainThread(runner, NS_DISPATCH_NORMAL); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool IsOK() const { return !!mScaleRequest; } michael@0: michael@0: private: michael@0: nsAutoPtr mScaleRequest; michael@0: }; michael@0: michael@0: namespace mozilla { michael@0: namespace image { michael@0: michael@0: /* static */ StaticRefPtr RasterImage::DecodePool::sSingleton; michael@0: static nsCOMPtr sScaleWorkerThread = nullptr; michael@0: michael@0: #ifndef DEBUG michael@0: NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties) michael@0: #else michael@0: NS_IMPL_ISUPPORTS(RasterImage, imgIContainer, nsIProperties, michael@0: imgIContainerDebug) michael@0: #endif michael@0: michael@0: //****************************************************************************** michael@0: RasterImage::RasterImage(imgStatusTracker* aStatusTracker, michael@0: ImageURL* aURI /* = nullptr */) : michael@0: ImageResource(aURI), // invoke superclass's constructor michael@0: mSize(0,0), michael@0: mFrameDecodeFlags(DECODE_FLAGS_DEFAULT), michael@0: mMultipartDecodedFrame(nullptr), michael@0: mAnim(nullptr), michael@0: mLockCount(0), michael@0: mDecodeCount(0), michael@0: mRequestedSampleSize(0), michael@0: #ifdef DEBUG michael@0: mFramesNotified(0), michael@0: #endif michael@0: mDecodingMonitor("RasterImage Decoding Monitor"), michael@0: mDecoder(nullptr), michael@0: mBytesDecoded(0), michael@0: mInDecoder(false), michael@0: mStatusDiff(ImageStatusDiff::NoChange()), michael@0: mNotifying(false), michael@0: mHasSize(false), michael@0: mDecodeOnDraw(false), michael@0: mMultipart(false), michael@0: mDiscardable(false), michael@0: mHasSourceData(false), michael@0: mDecoded(false), michael@0: mHasBeenDecoded(false), michael@0: mAnimationFinished(false), michael@0: mFinishing(false), michael@0: mInUpdateImageContainer(false), michael@0: mWantFullDecode(false), michael@0: mPendingError(false), michael@0: mScaleRequest(nullptr) michael@0: { michael@0: mStatusTrackerInit = new imgStatusTrackerInit(this, aStatusTracker); michael@0: michael@0: // Set up the discard tracker node. michael@0: mDiscardTrackerNode.img = this; michael@0: Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0); michael@0: michael@0: // Statistics michael@0: num_containers++; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: RasterImage::~RasterImage() michael@0: { michael@0: // Discardable statistics michael@0: if (mDiscardable) { michael@0: num_discardable_containers--; michael@0: discardable_source_bytes -= mSourceData.Length(); michael@0: michael@0: PR_LOG (GetCompressedImageAccountingLog(), PR_LOG_DEBUG, michael@0: ("CompressedImageAccounting: destroying RasterImage %p. " michael@0: "Total Containers: %d, Discardable containers: %d, " michael@0: "Total source bytes: %lld, Source bytes for discardable containers %lld", michael@0: this, michael@0: num_containers, michael@0: num_discardable_containers, michael@0: total_source_bytes, michael@0: discardable_source_bytes)); michael@0: } michael@0: michael@0: if (mDecoder) { michael@0: // Kill off our decode request, if it's pending. (If not, this call is michael@0: // harmless.) michael@0: ReentrantMonitorAutoEnter lock(mDecodingMonitor); michael@0: DecodePool::StopDecoding(this); michael@0: mDecoder = nullptr; michael@0: michael@0: // Unlock the last frame (if we have any). Our invariant is that, while we michael@0: // have a decoder open, the last frame is always locked. michael@0: // This would be done in ShutdownDecoder, but since mDecoder is non-null, michael@0: // we didn't call ShutdownDecoder and we need to do it manually. michael@0: if (GetNumFrames() > 0) { michael@0: imgFrame *curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1); michael@0: curframe->UnlockImageData(); michael@0: } michael@0: } michael@0: michael@0: delete mAnim; michael@0: mAnim = nullptr; michael@0: delete mMultipartDecodedFrame; michael@0: michael@0: // Total statistics michael@0: num_containers--; michael@0: total_source_bytes -= mSourceData.Length(); michael@0: michael@0: if (NS_IsMainThread()) { michael@0: DiscardTracker::Remove(&mDiscardTrackerNode); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: RasterImage::Initialize() michael@0: { michael@0: InitPrefCaches(); michael@0: michael@0: // Create our singletons now, so we don't have to worry about what thread michael@0: // they're created on. michael@0: DecodePool::Singleton(); michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::Init(const char* aMimeType, michael@0: uint32_t aFlags) michael@0: { michael@0: // We don't support re-initialization michael@0: if (mInitialized) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: // Not sure an error can happen before init, but be safe michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ENSURE_ARG_POINTER(aMimeType); michael@0: michael@0: // We must be non-discardable and non-decode-on-draw for michael@0: // multipart channels michael@0: NS_ABORT_IF_FALSE(!(aFlags & INIT_FLAG_MULTIPART) || michael@0: (!(aFlags & INIT_FLAG_DISCARDABLE) && michael@0: !(aFlags & INIT_FLAG_DECODE_ON_DRAW)), michael@0: "Can't be discardable or decode-on-draw for multipart"); michael@0: michael@0: // Store initialization data michael@0: mSourceDataMimeType.Assign(aMimeType); michael@0: mDiscardable = !!(aFlags & INIT_FLAG_DISCARDABLE); michael@0: mDecodeOnDraw = !!(aFlags & INIT_FLAG_DECODE_ON_DRAW); michael@0: mMultipart = !!(aFlags & INIT_FLAG_MULTIPART); michael@0: michael@0: // Statistics michael@0: if (mDiscardable) { michael@0: num_discardable_containers++; michael@0: discardable_source_bytes += mSourceData.Length(); michael@0: } michael@0: michael@0: // Instantiate the decoder michael@0: nsresult rv = InitDecoder(/* aDoSizeDecode = */ true); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: michael@0: // If we aren't storing source data, we want to switch from a size decode to michael@0: // a full decode as soon as possible. michael@0: if (!StoringSourceData()) { michael@0: mWantFullDecode = true; michael@0: } michael@0: michael@0: // Mark us as initialized michael@0: mInitialized = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: // [notxpcom] void requestRefresh ([const] in TimeStamp aTime); michael@0: NS_IMETHODIMP_(void) michael@0: RasterImage::RequestRefresh(const mozilla::TimeStamp& aTime) michael@0: { michael@0: EvaluateAnimation(); michael@0: michael@0: if (!mAnimating) { michael@0: return; michael@0: } michael@0: michael@0: FrameAnimator::RefreshResult res; michael@0: if (mAnim) { michael@0: res = mAnim->RequestRefresh(aTime); michael@0: } michael@0: michael@0: if (res.frameAdvanced) { michael@0: // Notify listeners that our frame has actually changed, but do this only michael@0: // once for all frames that we've now passed (if AdvanceFrame() was called michael@0: // more than once). michael@0: #ifdef DEBUG michael@0: mFramesNotified++; michael@0: #endif michael@0: michael@0: UpdateImageContainer(); michael@0: michael@0: // Explicitly call this on mStatusTracker so we're sure to not interfere michael@0: // with the decoding process michael@0: if (mStatusTracker) michael@0: mStatusTracker->FrameChanged(&res.dirtyRect); michael@0: } michael@0: michael@0: if (res.animationFinished) { michael@0: mAnimationFinished = true; michael@0: EvaluateAnimation(); michael@0: } michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* readonly attribute int32_t width; */ michael@0: NS_IMETHODIMP michael@0: RasterImage::GetWidth(int32_t *aWidth) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aWidth); michael@0: michael@0: if (mError) { michael@0: *aWidth = 0; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: *aWidth = mSize.width; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* readonly attribute int32_t height; */ michael@0: NS_IMETHODIMP michael@0: RasterImage::GetHeight(int32_t *aHeight) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aHeight); michael@0: michael@0: if (mError) { michael@0: *aHeight = 0; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: *aHeight = mSize.height; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [noscript] readonly attribute nsSize intrinsicSize; */ michael@0: NS_IMETHODIMP michael@0: RasterImage::GetIntrinsicSize(nsSize* aSize) michael@0: { michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: *aSize = nsSize(nsPresContext::CSSPixelsToAppUnits(mSize.width), michael@0: nsPresContext::CSSPixelsToAppUnits(mSize.height)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [noscript] readonly attribute nsSize intrinsicRatio; */ michael@0: NS_IMETHODIMP michael@0: RasterImage::GetIntrinsicRatio(nsSize* aRatio) michael@0: { michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: *aRatio = nsSize(mSize.width, mSize.height); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(Orientation) michael@0: RasterImage::GetOrientation() michael@0: { michael@0: return mOrientation; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* unsigned short GetType(); */ michael@0: NS_IMETHODIMP michael@0: RasterImage::GetType(uint16_t *aType) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aType); michael@0: michael@0: *aType = GetType(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [noscript, notxpcom] uint16_t GetType(); */ michael@0: NS_IMETHODIMP_(uint16_t) michael@0: RasterImage::GetType() michael@0: { michael@0: return imgIContainer::TYPE_RASTER; michael@0: } michael@0: michael@0: imgFrame* michael@0: RasterImage::GetImgFrameNoDecode(uint32_t framenum) michael@0: { michael@0: if (!mAnim) { michael@0: NS_ASSERTION(framenum == 0, "Don't ask for a frame > 0 if we're not animated!"); michael@0: return mFrameBlender.GetFrame(0); michael@0: } michael@0: return mFrameBlender.GetFrame(framenum); michael@0: } michael@0: michael@0: imgFrame* michael@0: RasterImage::GetImgFrame(uint32_t framenum) michael@0: { michael@0: nsresult rv = WantDecodedFrames(); michael@0: CONTAINER_ENSURE_TRUE(NS_SUCCEEDED(rv), nullptr); michael@0: return GetImgFrameNoDecode(framenum); michael@0: } michael@0: michael@0: imgFrame* michael@0: RasterImage::GetDrawableImgFrame(uint32_t framenum) michael@0: { michael@0: imgFrame* frame = nullptr; michael@0: michael@0: if (mMultipart && framenum == GetCurrentImgFrameIndex()) { michael@0: // In the multipart case we prefer to use mMultipartDecodedFrame, which is michael@0: // the most recent one we completely decoded, rather than display the real michael@0: // current frame and risk severe tearing. michael@0: frame = mMultipartDecodedFrame; michael@0: } michael@0: michael@0: if (!frame) { michael@0: frame = GetImgFrame(framenum); michael@0: } michael@0: michael@0: // We will return a paletted frame if it's not marked as compositing failed michael@0: // so we can catch crashes for reasons we haven't investigated. michael@0: if (frame && frame->GetCompositingFailed()) michael@0: return nullptr; michael@0: michael@0: if (frame) { michael@0: frame->ApplyDirtToSurfaces(); michael@0: } michael@0: michael@0: return frame; michael@0: } michael@0: michael@0: uint32_t michael@0: RasterImage::GetCurrentImgFrameIndex() const michael@0: { michael@0: if (mAnim) michael@0: return mAnim->GetCurrentAnimationFrameIndex(); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: imgFrame* michael@0: RasterImage::GetCurrentImgFrame() michael@0: { michael@0: return GetImgFrame(GetCurrentImgFrameIndex()); michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [notxpcom] boolean frameIsOpaque(in uint32_t aWhichFrame); */ michael@0: NS_IMETHODIMP_(bool) michael@0: RasterImage::FrameIsOpaque(uint32_t aWhichFrame) michael@0: { michael@0: if (aWhichFrame > FRAME_MAX_VALUE) { michael@0: NS_WARNING("aWhichFrame outside valid range!"); michael@0: return false; michael@0: } michael@0: michael@0: if (mError) michael@0: return false; michael@0: michael@0: // See if we can get an image frame. michael@0: imgFrame* frame = aWhichFrame == FRAME_FIRST ? GetImgFrameNoDecode(0) michael@0: : GetImgFrameNoDecode(GetCurrentImgFrameIndex()); michael@0: michael@0: // If we don't get a frame, the safe answer is "not opaque". michael@0: if (!frame) michael@0: return false; michael@0: michael@0: // Other, the frame is transparent if either: michael@0: // 1. It needs a background. michael@0: // 2. Its size doesn't cover our entire area. michael@0: nsIntRect framerect = frame->GetRect(); michael@0: return !frame->GetNeedsBackground() && michael@0: framerect.IsEqualInterior(nsIntRect(0, 0, mSize.width, mSize.height)); michael@0: } michael@0: michael@0: nsIntRect michael@0: RasterImage::FrameRect(uint32_t aWhichFrame) michael@0: { michael@0: if (aWhichFrame > FRAME_MAX_VALUE) { michael@0: NS_WARNING("aWhichFrame outside valid range!"); michael@0: return nsIntRect(); michael@0: } michael@0: michael@0: // Get the requested frame. michael@0: imgFrame* frame = aWhichFrame == FRAME_FIRST ? GetImgFrameNoDecode(0) michael@0: : GetImgFrameNoDecode(GetCurrentImgFrameIndex()); michael@0: michael@0: // If we have the frame, use that rectangle. michael@0: if (frame) { michael@0: return frame->GetRect(); michael@0: } michael@0: michael@0: // If the frame doesn't exist, we return the empty rectangle. It's not clear michael@0: // whether this is appropriate in general, but at the moment the only michael@0: // consumer of this method is imgStatusTracker (when it wants to figure out michael@0: // dirty rectangles to send out batched observer updates). This should michael@0: // probably be revisited when we fix bug 503973. michael@0: return nsIntRect(); michael@0: } michael@0: michael@0: uint32_t michael@0: RasterImage::GetCurrentFrameIndex() michael@0: { michael@0: return GetCurrentImgFrameIndex(); michael@0: } michael@0: michael@0: uint32_t michael@0: RasterImage::GetNumFrames() const michael@0: { michael@0: return mFrameBlender.GetNumFrames(); michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* readonly attribute boolean animated; */ michael@0: NS_IMETHODIMP michael@0: RasterImage::GetAnimated(bool *aAnimated) michael@0: { michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ENSURE_ARG_POINTER(aAnimated); michael@0: michael@0: // If we have mAnim, we can know for sure michael@0: if (mAnim) { michael@0: *aAnimated = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Otherwise, we need to have been decoded to know for sure, since if we were michael@0: // decoded at least once mAnim would have been created for animated images michael@0: if (!mHasBeenDecoded) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // We know for sure michael@0: *aAnimated = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [notxpcom] int32_t getFirstFrameDelay (); */ michael@0: NS_IMETHODIMP_(int32_t) michael@0: RasterImage::GetFirstFrameDelay() michael@0: { michael@0: if (mError) michael@0: return -1; michael@0: michael@0: bool animated = false; michael@0: if (NS_FAILED(GetAnimated(&animated)) || !animated) michael@0: return -1; michael@0: michael@0: return mFrameBlender.GetTimeoutForFrame(0); michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::CopyFrame(uint32_t aWhichFrame, michael@0: uint32_t aFlags, michael@0: gfxImageSurface **_retval) michael@0: { michael@0: if (aWhichFrame > FRAME_MAX_VALUE) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Disallowed in the API michael@0: if (mInDecoder && (aFlags & imgIContainer::FLAG_SYNC_DECODE)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!ApplyDecodeFlags(aFlags, aWhichFrame)) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // If requested, synchronously flush any data we have lying around to the decoder michael@0: if (aFlags & FLAG_SYNC_DECODE) { michael@0: rv = SyncDecode(); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: } michael@0: michael@0: NS_ENSURE_ARG_POINTER(_retval); michael@0: michael@0: // Get the frame. If it's not there, it's probably the caller's fault for michael@0: // not waiting for the data to be loaded from the network or not passing michael@0: // FLAG_SYNC_DECODE michael@0: uint32_t frameIndex = (aWhichFrame == FRAME_FIRST) ? michael@0: 0 : GetCurrentImgFrameIndex(); michael@0: imgFrame *frame = GetDrawableImgFrame(frameIndex); michael@0: if (!frame) { michael@0: *_retval = nullptr; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsRefPtr pattern; michael@0: frame->GetPattern(getter_AddRefs(pattern)); michael@0: nsIntRect intframerect = frame->GetRect(); michael@0: gfxRect framerect(intframerect.x, intframerect.y, intframerect.width, intframerect.height); michael@0: michael@0: // Create a 32-bit image surface of our size, but draw using the frame's michael@0: // rect, implicitly padding the frame out to the image's size. michael@0: nsRefPtr imgsurface = new gfxImageSurface(gfxIntSize(mSize.width, mSize.height), michael@0: gfxImageFormat::ARGB32); michael@0: gfxContext ctx(imgsurface); michael@0: ctx.SetOperator(gfxContext::OPERATOR_SOURCE); michael@0: ctx.Rectangle(framerect); michael@0: ctx.Translate(framerect.TopLeft()); michael@0: ctx.SetPattern(pattern); michael@0: ctx.Fill(); michael@0: michael@0: imgsurface.forget(_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [noscript] SourceSurface getFrame(in uint32_t aWhichFrame, michael@0: * in uint32_t aFlags); */ michael@0: NS_IMETHODIMP_(TemporaryRef) michael@0: RasterImage::GetFrame(uint32_t aWhichFrame, michael@0: uint32_t aFlags) michael@0: { michael@0: MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE); michael@0: michael@0: if (aWhichFrame > FRAME_MAX_VALUE) michael@0: return nullptr; michael@0: michael@0: if (mError) michael@0: return nullptr; michael@0: michael@0: // Disallowed in the API michael@0: if (mInDecoder && (aFlags & imgIContainer::FLAG_SYNC_DECODE)) michael@0: return nullptr; michael@0: michael@0: if (!ApplyDecodeFlags(aFlags, aWhichFrame)) michael@0: return nullptr; michael@0: michael@0: // If the caller requested a synchronous decode, do it michael@0: if (aFlags & FLAG_SYNC_DECODE) { michael@0: nsresult rv = SyncDecode(); michael@0: CONTAINER_ENSURE_TRUE(NS_SUCCEEDED(rv), nullptr); michael@0: } michael@0: michael@0: // Get the frame. If it's not there, it's probably the caller's fault for michael@0: // not waiting for the data to be loaded from the network or not passing michael@0: // FLAG_SYNC_DECODE michael@0: uint32_t frameIndex = (aWhichFrame == FRAME_FIRST) ? michael@0: 0 : GetCurrentImgFrameIndex(); michael@0: imgFrame *frame = GetDrawableImgFrame(frameIndex); michael@0: if (!frame) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr framesurf; michael@0: michael@0: // If this frame covers the entire image, we can just reuse its existing michael@0: // surface. michael@0: nsIntRect framerect = frame->GetRect(); michael@0: if (framerect.x == 0 && framerect.y == 0 && michael@0: framerect.width == mSize.width && michael@0: framerect.height == mSize.height) { michael@0: frame->GetSurface(getter_AddRefs(framesurf)); michael@0: if (!framesurf && !frame->IsSinglePixel()) { michael@0: // No reason to be optimized away here - the OS threw out the data michael@0: if (!(aFlags & FLAG_SYNC_DECODE)) michael@0: return nullptr; michael@0: michael@0: // Unconditionally call ForceDiscard() here because GetSurface can only michael@0: // return null when we can forcibly discard and redecode. There are two michael@0: // other cases where GetSurface() can return null - when it is a single michael@0: // pixel image, which we check before getting here, or when this is an michael@0: // indexed image, in which case we shouldn't be in this function at all. michael@0: // The only remaining possibility is that SetDiscardable() was called on michael@0: // this imgFrame, which implies the image can be redecoded. michael@0: ForceDiscard(); michael@0: return GetFrame(aWhichFrame, aFlags); michael@0: } michael@0: } michael@0: michael@0: // The image doesn't have a surface because it's been optimized away. Create michael@0: // one. michael@0: if (!framesurf) { michael@0: nsRefPtr imgsurf; michael@0: CopyFrame(aWhichFrame, aFlags, getter_AddRefs(imgsurf)); michael@0: framesurf = imgsurf; michael@0: } michael@0: michael@0: RefPtr result; michael@0: michael@0: // As far as Moz2D is concerned, SourceSurface contains premultiplied alpha. michael@0: // If we're abusing it to contain non-premultiplied alpha then we want to michael@0: // avoid having Moz2D do any conversions on it (like copy to another michael@0: // surface). Hence why we try to wrap framesurf's data here for michael@0: // FLAG_DECODE_NO_PREMULTIPLY_ALPHA. michael@0: if ((aFlags & FLAG_WANT_DATA_SURFACE) != 0 || michael@0: (aFlags & FLAG_DECODE_NO_PREMULTIPLY_ALPHA) != 0) { michael@0: result = gfxPlatform::GetPlatform()->GetWrappedDataSourceSurface(framesurf); michael@0: } michael@0: if (!result) { michael@0: result = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, michael@0: framesurf); michael@0: } michael@0: return result.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: RasterImage::GetCurrentImage() michael@0: { michael@0: if (!mDecoded) { michael@0: // We can't call StartDecoding because that can synchronously notify michael@0: // which can cause DOM modification michael@0: RequestDecodeCore(ASYNCHRONOUS); michael@0: return nullptr; michael@0: } michael@0: michael@0: RefPtr surface = GetFrame(FRAME_CURRENT, FLAG_NONE); michael@0: if (!surface) { michael@0: // The OS threw out some or all of our buffer. Start decoding again. michael@0: // GetFrame will only return null in the case that the image was michael@0: // discarded. We already checked that the image is decoded, so other michael@0: // error paths are not possible. michael@0: ForceDiscard(); michael@0: RequestDecodeCore(ASYNCHRONOUS); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!mImageContainer) { michael@0: mImageContainer = LayerManager::CreateImageContainer(); michael@0: } michael@0: michael@0: CairoImage::Data cairoData; michael@0: GetWidth(&cairoData.mSize.width); michael@0: GetHeight(&cairoData.mSize.height); michael@0: cairoData.mSourceSurface = surface; michael@0: michael@0: nsRefPtr image = mImageContainer->CreateImage(ImageFormat::CAIRO_SURFACE); michael@0: NS_ASSERTION(image, "Failed to create Image"); michael@0: michael@0: static_cast(image.get())->SetData(cairoData); michael@0: michael@0: return image.forget(); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: RasterImage::GetImageContainer(LayerManager* aManager, ImageContainer **_retval) michael@0: { michael@0: int32_t maxTextureSize = aManager->GetMaxTextureSize(); michael@0: if (mSize.width > maxTextureSize || mSize.height > maxTextureSize) { michael@0: *_retval = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (IsUnlocked() && mStatusTracker) { michael@0: mStatusTracker->OnUnlockedDraw(); michael@0: } michael@0: michael@0: if (!mImageContainer) { michael@0: mImageContainer = mImageContainerCache; michael@0: } michael@0: michael@0: if (mImageContainer) { michael@0: *_retval = mImageContainer; michael@0: NS_ADDREF(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr image = GetCurrentImage(); michael@0: if (!image) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: mImageContainer->SetCurrentImageInTransaction(image); michael@0: michael@0: *_retval = mImageContainer; michael@0: NS_ADDREF(*_retval); michael@0: // We only need to be careful about holding on to the image when it is michael@0: // discardable by the OS. michael@0: if (CanForciblyDiscardAndRedecode()) { michael@0: mImageContainerCache = mImageContainer->asWeakPtr(); michael@0: mImageContainer = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: RasterImage::UpdateImageContainer() michael@0: { michael@0: if (!mImageContainer || IsInUpdateImageContainer()) { michael@0: return; michael@0: } michael@0: michael@0: SetInUpdateImageContainer(true); michael@0: michael@0: nsRefPtr image = GetCurrentImage(); michael@0: if (!image) { michael@0: return; michael@0: } michael@0: mImageContainer->SetCurrentImage(image); michael@0: SetInUpdateImageContainer(false); michael@0: } michael@0: michael@0: size_t michael@0: RasterImage::HeapSizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: // n == 0 is possible for two reasons. michael@0: // - This is a zero-length image. michael@0: // - We're on a platform where moz_malloc_size_of always returns 0. michael@0: // In either case the fallback works appropriately. michael@0: size_t n = mSourceData.SizeOfExcludingThis(aMallocSizeOf); michael@0: if (n == 0) { michael@0: n = mSourceData.Length(); michael@0: } michael@0: return n; michael@0: } michael@0: michael@0: size_t michael@0: RasterImage::SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation, michael@0: MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t n = mFrameBlender.SizeOfDecodedWithComputedFallbackIfHeap(aLocation, aMallocSizeOf); michael@0: michael@0: if (mScaleResult.status == SCALE_DONE) { michael@0: n += mScaleResult.frame->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation, aMallocSizeOf); michael@0: } michael@0: michael@0: return n; michael@0: } michael@0: michael@0: size_t michael@0: RasterImage::HeapSizeOfDecodedWithComputedFallback(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation::IN_PROCESS_HEAP, michael@0: aMallocSizeOf); michael@0: } michael@0: michael@0: size_t michael@0: RasterImage::NonHeapSizeOfDecoded() const michael@0: { michael@0: return SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation::IN_PROCESS_NONHEAP, michael@0: nullptr); michael@0: } michael@0: michael@0: size_t michael@0: RasterImage::OutOfProcessSizeOfDecoded() const michael@0: { michael@0: return SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation::OUT_OF_PROCESS, michael@0: nullptr); michael@0: } michael@0: michael@0: void michael@0: RasterImage::EnsureAnimExists() michael@0: { michael@0: if (!mAnim) { michael@0: michael@0: // Create the animation context michael@0: mAnim = new FrameAnimator(mFrameBlender, mAnimationMode); michael@0: michael@0: // We don't support discarding animated images (See bug 414259). michael@0: // Lock the image and throw away the key. michael@0: // michael@0: // Note that this is inefficient, since we could get rid of the source michael@0: // data too. However, doing this is actually hard, because we're probably michael@0: // calling ensureAnimExists mid-decode, and thus we're decoding out of michael@0: // the source buffer. Since we're going to fix this anyway later, and michael@0: // since we didn't kill the source data in the old world either, locking michael@0: // is acceptable for the moment. michael@0: LockImage(); michael@0: michael@0: // Notify our observers that we are starting animation. michael@0: nsRefPtr statusTracker = CurrentStatusTracker(); michael@0: statusTracker->RecordImageIsAnimated(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::InternalAddFrameHelper(uint32_t framenum, imgFrame *aFrame, michael@0: uint8_t **imageData, uint32_t *imageLength, michael@0: uint32_t **paletteData, uint32_t *paletteLength, michael@0: imgFrame** aRetFrame) michael@0: { michael@0: NS_ABORT_IF_FALSE(framenum <= GetNumFrames(), "Invalid frame index!"); michael@0: if (framenum > GetNumFrames()) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nsAutoPtr frame(aFrame); michael@0: michael@0: // We are in the middle of decoding. This will be unlocked when we finish michael@0: // decoding or switch to another frame. michael@0: frame->LockImageData(); michael@0: michael@0: if (paletteData && paletteLength) michael@0: frame->GetPaletteData(paletteData, paletteLength); michael@0: michael@0: frame->GetImageData(imageData, imageLength); michael@0: michael@0: *aRetFrame = frame; michael@0: michael@0: mFrameBlender.InsertFrame(framenum, frame.forget()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::InternalAddFrame(uint32_t framenum, michael@0: int32_t aX, int32_t aY, michael@0: int32_t aWidth, int32_t aHeight, michael@0: gfxImageFormat aFormat, michael@0: uint8_t aPaletteDepth, michael@0: uint8_t **imageData, michael@0: uint32_t *imageLength, michael@0: uint32_t **paletteData, michael@0: uint32_t *paletteLength, michael@0: imgFrame** aRetFrame) michael@0: { michael@0: // We assume that we're in the middle of decoding because we unlock the michael@0: // previous frame when we create a new frame, and only when decoding do we michael@0: // lock frames. michael@0: NS_ABORT_IF_FALSE(mDecoder, "Only decoders may add frames!"); michael@0: michael@0: NS_ABORT_IF_FALSE(framenum <= GetNumFrames(), "Invalid frame index!"); michael@0: if (framenum > GetNumFrames()) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nsAutoPtr frame(new imgFrame()); michael@0: michael@0: nsresult rv = frame->Init(aX, aY, aWidth, aHeight, aFormat, aPaletteDepth); michael@0: if (!(mSize.width > 0 && mSize.height > 0)) michael@0: NS_WARNING("Shouldn't call InternalAddFrame with zero size"); michael@0: if (!NS_SUCCEEDED(rv)) michael@0: NS_WARNING("imgFrame::Init should succeed"); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We know we are in a decoder. Therefore, we must unlock the previous frame michael@0: // when we move on to decoding into the next frame. michael@0: if (GetNumFrames() > 0) { michael@0: imgFrame *prevframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1); michael@0: prevframe->UnlockImageData(); michael@0: } michael@0: michael@0: if (GetNumFrames() == 0) { michael@0: return InternalAddFrameHelper(framenum, frame.forget(), imageData, imageLength, michael@0: paletteData, paletteLength, aRetFrame); michael@0: } michael@0: michael@0: if (GetNumFrames() == 1) { michael@0: // Since we're about to add our second frame, initialize animation stuff michael@0: EnsureAnimExists(); michael@0: michael@0: // If we dispose of the first frame by clearing it, then the michael@0: // First Frame's refresh area is all of itself. michael@0: // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR) michael@0: int32_t frameDisposalMethod = mFrameBlender.RawGetFrame(0)->GetFrameDisposalMethod(); michael@0: if (frameDisposalMethod == FrameBlender::kDisposeClear || michael@0: frameDisposalMethod == FrameBlender::kDisposeRestorePrevious) michael@0: mAnim->SetFirstFrameRefreshArea(mFrameBlender.RawGetFrame(0)->GetRect()); michael@0: } michael@0: michael@0: // Calculate firstFrameRefreshArea michael@0: // Some gifs are huge but only have a small area that they animate michael@0: // We only need to refresh that small area when Frame 0 comes around again michael@0: mAnim->UnionFirstFrameRefreshArea(frame->GetRect()); michael@0: michael@0: rv = InternalAddFrameHelper(framenum, frame.forget(), imageData, imageLength, michael@0: paletteData, paletteLength, aRetFrame); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: RasterImage::ApplyDecodeFlags(uint32_t aNewFlags, uint32_t aWhichFrame) michael@0: { michael@0: if (mFrameDecodeFlags == (aNewFlags & DECODE_FLAGS_MASK)) michael@0: return true; // Not asking very much of us here. michael@0: michael@0: if (mDecoded) { michael@0: // If the requested frame is opaque and the current and new decode flags michael@0: // only differ in the premultiply alpha bit then we can use the existing michael@0: // frame, we don't need to discard and re-decode. michael@0: uint32_t currentNonAlphaFlags = michael@0: (mFrameDecodeFlags & DECODE_FLAGS_MASK) & ~FLAG_DECODE_NO_PREMULTIPLY_ALPHA; michael@0: uint32_t newNonAlphaFlags = michael@0: (aNewFlags & DECODE_FLAGS_MASK) & ~FLAG_DECODE_NO_PREMULTIPLY_ALPHA; michael@0: if (currentNonAlphaFlags == newNonAlphaFlags && FrameIsOpaque(aWhichFrame)) { michael@0: return true; michael@0: } michael@0: michael@0: // if we can't discard, then we're screwed; we have no way michael@0: // to re-decode. Similarly if we aren't allowed to do a sync michael@0: // decode. michael@0: if (!(aNewFlags & FLAG_SYNC_DECODE)) michael@0: return false; michael@0: if (!CanForciblyDiscardAndRedecode()) michael@0: return false; michael@0: ForceDiscard(); michael@0: } michael@0: michael@0: mFrameDecodeFlags = aNewFlags & DECODE_FLAGS_MASK; michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mDecodingMonitor.AssertCurrentThreadIn(); michael@0: michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Ensure that we have positive values michael@0: // XXX - Why isn't the size unsigned? Should this be changed? michael@0: if ((aWidth < 0) || (aHeight < 0)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // if we already have a size, check the new size against the old one michael@0: if (!mMultipart && mHasSize && michael@0: ((aWidth != mSize.width) || michael@0: (aHeight != mSize.height) || michael@0: (aOrientation != mOrientation))) { michael@0: NS_WARNING("Image changed size on redecode! This should not happen!"); michael@0: michael@0: // Make the decoder aware of the error so that it doesn't try to call michael@0: // FinishInternal during ShutdownDecoder. michael@0: if (mDecoder) michael@0: mDecoder->PostResizeError(); michael@0: michael@0: DoError(); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // Set the size and flag that we have it michael@0: mSize.SizeTo(aWidth, aHeight); michael@0: mOrientation = aOrientation; michael@0: mHasSize = true; michael@0: michael@0: mFrameBlender.SetSize(mSize); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::EnsureFrame(uint32_t aFrameNum, int32_t aX, int32_t aY, michael@0: int32_t aWidth, int32_t aHeight, michael@0: gfxImageFormat aFormat, michael@0: uint8_t aPaletteDepth, michael@0: uint8_t **imageData, uint32_t *imageLength, michael@0: uint32_t **paletteData, uint32_t *paletteLength, michael@0: imgFrame** aRetFrame) michael@0: { michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ENSURE_ARG_POINTER(imageData); michael@0: NS_ENSURE_ARG_POINTER(imageLength); michael@0: NS_ENSURE_ARG_POINTER(aRetFrame); michael@0: NS_ABORT_IF_FALSE(aFrameNum <= GetNumFrames(), "Invalid frame index!"); michael@0: michael@0: if (aPaletteDepth > 0) { michael@0: NS_ENSURE_ARG_POINTER(paletteData); michael@0: NS_ENSURE_ARG_POINTER(paletteLength); michael@0: } michael@0: michael@0: if (aFrameNum > GetNumFrames()) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // Adding a frame that doesn't already exist. michael@0: if (aFrameNum == GetNumFrames()) { michael@0: return InternalAddFrame(aFrameNum, aX, aY, aWidth, aHeight, aFormat, michael@0: aPaletteDepth, imageData, imageLength, michael@0: paletteData, paletteLength, aRetFrame); michael@0: } michael@0: michael@0: imgFrame *frame = mFrameBlender.RawGetFrame(aFrameNum); michael@0: if (!frame) { michael@0: return InternalAddFrame(aFrameNum, aX, aY, aWidth, aHeight, aFormat, michael@0: aPaletteDepth, imageData, imageLength, michael@0: paletteData, paletteLength, aRetFrame); michael@0: } michael@0: michael@0: // See if we can re-use the frame that already exists. michael@0: nsIntRect rect = frame->GetRect(); michael@0: if (rect.x == aX && rect.y == aY && rect.width == aWidth && michael@0: rect.height == aHeight && frame->GetFormat() == aFormat && michael@0: frame->GetPaletteDepth() == aPaletteDepth) { michael@0: frame->GetImageData(imageData, imageLength); michael@0: if (paletteData) { michael@0: frame->GetPaletteData(paletteData, paletteLength); michael@0: } michael@0: michael@0: *aRetFrame = frame; michael@0: michael@0: // We can re-use the frame if it has image data. michael@0: if (*imageData && paletteData && *paletteData) { michael@0: return NS_OK; michael@0: } michael@0: if (*imageData && !paletteData) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // Not reusable, so replace the frame directly. michael@0: michael@0: // We know this frame is already locked, because it's the one we're currently michael@0: // writing to. michael@0: frame->UnlockImageData(); michael@0: michael@0: mFrameBlender.RemoveFrame(aFrameNum); michael@0: nsAutoPtr newFrame(new imgFrame()); michael@0: nsresult rv = newFrame->Init(aX, aY, aWidth, aHeight, aFormat, aPaletteDepth); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return InternalAddFrameHelper(aFrameNum, newFrame.forget(), imageData, michael@0: imageLength, paletteData, paletteLength, michael@0: aRetFrame); michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::EnsureFrame(uint32_t aFramenum, int32_t aX, int32_t aY, michael@0: int32_t aWidth, int32_t aHeight, michael@0: gfxImageFormat aFormat, michael@0: uint8_t** imageData, uint32_t* imageLength, michael@0: imgFrame** aFrame) michael@0: { michael@0: return EnsureFrame(aFramenum, aX, aY, aWidth, aHeight, aFormat, michael@0: /* aPaletteDepth = */ 0, imageData, imageLength, michael@0: /* aPaletteData = */ nullptr, michael@0: /* aPaletteLength = */ nullptr, michael@0: aFrame); michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::SetFrameAsNonPremult(uint32_t aFrameNum, bool aIsNonPremult) michael@0: { michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ABORT_IF_FALSE(aFrameNum < GetNumFrames(), "Invalid frame index!"); michael@0: if (aFrameNum >= GetNumFrames()) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: imgFrame* frame = mFrameBlender.RawGetFrame(aFrameNum); michael@0: NS_ABORT_IF_FALSE(frame, "Calling SetFrameAsNonPremult on frame that doesn't exist!"); michael@0: NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); michael@0: michael@0: frame->SetAsNonPremult(aIsNonPremult); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::DecodingComplete() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Flag that we're done decoding. michael@0: // XXX - these should probably be combined when we fix animated image michael@0: // discarding with bug 500402. michael@0: mDecoded = true; michael@0: mHasBeenDecoded = true; michael@0: michael@0: nsresult rv; michael@0: michael@0: // We now have one of the qualifications for discarding. Re-evaluate. michael@0: if (CanDiscard()) { michael@0: NS_ABORT_IF_FALSE(!DiscardingActive(), michael@0: "We shouldn't have been discardable before this"); michael@0: rv = DiscardTracker::Reset(&mDiscardTrackerNode); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: } michael@0: michael@0: // If there's only 1 frame, optimize it. Optimizing animated images michael@0: // is not supported. michael@0: // michael@0: // We don't optimize the frame for multipart images because we reuse michael@0: // the frame. michael@0: if ((GetNumFrames() == 1) && !mMultipart) { michael@0: // CanForciblyDiscard is used instead of CanForciblyDiscardAndRedecode michael@0: // because we know decoding is complete at this point and this is not michael@0: // an animation michael@0: if (DiscardingEnabled() && CanForciblyDiscard()) { michael@0: mFrameBlender.RawGetFrame(0)->SetDiscardable(); michael@0: } michael@0: rv = mFrameBlender.RawGetFrame(0)->Optimize(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Double-buffer our frame in the multipart case, since we'll start decoding michael@0: // into the first frame again immediately and this produces severe tearing. michael@0: if (mMultipart) { michael@0: if (GetNumFrames() == 1) { michael@0: mMultipartDecodedFrame = mFrameBlender.SwapFrame(GetCurrentFrameIndex(), michael@0: mMultipartDecodedFrame); michael@0: } else { michael@0: // Don't double buffer for animated multipart images. It entails more michael@0: // complexity and it's not really needed since we already are smart about michael@0: // not displaying the still-decoding frame of an animated image. We may michael@0: // have already stored an extra frame, though, so we'll release it here. michael@0: delete mMultipartDecodedFrame; michael@0: mMultipartDecodedFrame = nullptr; michael@0: } michael@0: } michael@0: michael@0: if (mAnim) { michael@0: mAnim->SetDoneDecoding(true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RasterImage::SetAnimationMode(uint16_t aAnimationMode) michael@0: { michael@0: if (mAnim) { michael@0: mAnim->SetAnimationMode(aAnimationMode); michael@0: } michael@0: return SetAnimationModeInternal(aAnimationMode); michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void StartAnimation () */ michael@0: nsresult michael@0: RasterImage::StartAnimation() michael@0: { michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ABORT_IF_FALSE(ShouldAnimate(), "Should not animate!"); michael@0: michael@0: EnsureAnimExists(); michael@0: michael@0: imgFrame* currentFrame = GetCurrentImgFrame(); michael@0: // A timeout of -1 means we should display this frame forever. michael@0: if (currentFrame && mFrameBlender.GetTimeoutForFrame(GetCurrentImgFrameIndex()) < 0) { michael@0: mAnimationFinished = true; michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: if (mAnim) { michael@0: // We need to set the time that this initial frame was first displayed, as michael@0: // this is used in AdvanceFrame(). michael@0: mAnim->InitAnimationFrameTimeIfNecessary(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void stopAnimation (); */ michael@0: nsresult michael@0: RasterImage::StopAnimation() michael@0: { michael@0: NS_ABORT_IF_FALSE(mAnimating, "Should be animating!"); michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (mError) { michael@0: rv = NS_ERROR_FAILURE; michael@0: } else { michael@0: mAnim->SetAnimationFrameTime(TimeStamp()); michael@0: } michael@0: michael@0: mAnimating = false; michael@0: return rv; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void resetAnimation (); */ michael@0: NS_IMETHODIMP michael@0: RasterImage::ResetAnimation() michael@0: { michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (mAnimationMode == kDontAnimMode || michael@0: !mAnim || mAnim->GetCurrentAnimationFrameIndex() == 0) michael@0: return NS_OK; michael@0: michael@0: mAnimationFinished = false; michael@0: michael@0: if (mAnimating) michael@0: StopAnimation(); michael@0: michael@0: mFrameBlender.ResetAnimation(); michael@0: mAnim->ResetAnimation(); michael@0: michael@0: UpdateImageContainer(); michael@0: michael@0: // Note - We probably want to kick off a redecode somewhere around here when michael@0: // we fix bug 500402. michael@0: michael@0: // Update display michael@0: if (mStatusTracker) { michael@0: nsIntRect rect = mAnim->GetFirstFrameRefreshArea(); michael@0: mStatusTracker->FrameChanged(&rect); michael@0: } michael@0: michael@0: // Start the animation again. It may not have been running before, if michael@0: // mAnimationFinished was true before entering this function. michael@0: EvaluateAnimation(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: // [notxpcom] void setAnimationStartTime ([const] in TimeStamp aTime); michael@0: NS_IMETHODIMP_(void) michael@0: RasterImage::SetAnimationStartTime(const mozilla::TimeStamp& aTime) michael@0: { michael@0: if (mError || mAnimationMode == kDontAnimMode || mAnimating || !mAnim) michael@0: return; michael@0: michael@0: mAnim->SetAnimationFrameTime(aTime); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(float) michael@0: RasterImage::GetFrameIndex(uint32_t aWhichFrame) michael@0: { michael@0: MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument"); michael@0: return (aWhichFrame == FRAME_FIRST || !mAnim) michael@0: ? 0.0f michael@0: : mAnim->GetCurrentAnimationFrameIndex(); michael@0: } michael@0: michael@0: void michael@0: RasterImage::SetLoopCount(int32_t aLoopCount) michael@0: { michael@0: if (mError) michael@0: return; michael@0: michael@0: if (mAnim) { michael@0: // No need to set this if we're not an animation michael@0: mFrameBlender.SetLoopCount(aLoopCount); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount) michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mDecodingMonitor); michael@0: michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ENSURE_ARG_POINTER(aBuffer); michael@0: nsresult rv = NS_OK; michael@0: michael@0: // We should not call this if we're not initialized michael@0: NS_ABORT_IF_FALSE(mInitialized, "Calling AddSourceData() on uninitialized " michael@0: "RasterImage!"); michael@0: michael@0: // We should not call this if we're already finished adding source data michael@0: NS_ABORT_IF_FALSE(!mHasSourceData, "Calling AddSourceData() after calling " michael@0: "sourceDataComplete()!"); michael@0: michael@0: // This call should come straight from necko - no reentrancy allowed michael@0: NS_ABORT_IF_FALSE(!mInDecoder, "Re-entrant call to AddSourceData!"); michael@0: michael@0: // Image is already decoded, we shouldn't be getting data, but it could michael@0: // be extra garbage data at the end of a file. michael@0: if (mDecoded) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Starting a new part's frames, let's clean up before we add any michael@0: // This needs to happen just before we start getting EnsureFrame() call(s), michael@0: // so that there's no gap for anything to miss us. michael@0: if (mMultipart && mBytesDecoded == 0) { michael@0: // Our previous state may have been animated, so let's clean up michael@0: if (mAnimating) michael@0: StopAnimation(); michael@0: mAnimationFinished = false; michael@0: if (mAnim) { michael@0: delete mAnim; michael@0: mAnim = nullptr; michael@0: } michael@0: // If there's only one frame, this could cause flickering michael@0: int old_frame_count = GetNumFrames(); michael@0: if (old_frame_count > 1) { michael@0: mFrameBlender.ClearFrames(); michael@0: } michael@0: } michael@0: michael@0: // If we're not storing source data and we've previously gotten the size, michael@0: // write the data directly to the decoder. (If we haven't gotten the size, michael@0: // we'll queue up the data and write it out when we do.) michael@0: if (!StoringSourceData() && mHasSize) { michael@0: rv = WriteToDecoder(aBuffer, aCount, DECODE_SYNC); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: michael@0: // We're not storing source data, so this data is probably coming straight michael@0: // from the network. In this case, we want to display data as soon as we michael@0: // get it, so we want to flush invalidations after every write. michael@0: nsRefPtr kungFuDeathGrip = mDecoder; michael@0: mInDecoder = true; michael@0: mDecoder->FlushInvalidations(); michael@0: mInDecoder = false; michael@0: michael@0: rv = FinishedSomeDecoding(); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: } michael@0: michael@0: // Otherwise, we're storing data in the source buffer michael@0: else { michael@0: michael@0: // Store the data michael@0: char *newElem = mSourceData.AppendElements(aBuffer, aCount); michael@0: if (!newElem) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (mDecoder) { michael@0: DecodePool::Singleton()->RequestDecode(this); michael@0: } michael@0: } michael@0: michael@0: // Statistics michael@0: total_source_bytes += aCount; michael@0: if (mDiscardable) michael@0: discardable_source_bytes += aCount; michael@0: PR_LOG (GetCompressedImageAccountingLog(), PR_LOG_DEBUG, michael@0: ("CompressedImageAccounting: Added compressed data to RasterImage %p (%s). " michael@0: "Total Containers: %d, Discardable containers: %d, " michael@0: "Total source bytes: %lld, Source bytes for discardable containers %lld", michael@0: this, michael@0: mSourceDataMimeType.get(), michael@0: num_containers, michael@0: num_discardable_containers, michael@0: total_source_bytes, michael@0: discardable_source_bytes)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* Note! buf must be declared as char buf[9]; */ michael@0: // just used for logging and hashing the header michael@0: static void michael@0: get_header_str (char *buf, char *data, size_t data_len) michael@0: { michael@0: int i; michael@0: int n; michael@0: static char hex[] = "0123456789abcdef"; michael@0: michael@0: n = data_len < 4 ? data_len : 4; michael@0: michael@0: for (i = 0; i < n; i++) { michael@0: buf[i * 2] = hex[(data[i] >> 4) & 0x0f]; michael@0: buf[i * 2 + 1] = hex[data[i] & 0x0f]; michael@0: } michael@0: michael@0: buf[i * 2] = 0; michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::DoImageDataComplete() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // If we've been called before, ignore. Otherwise, flag that we have everything michael@0: if (mHasSourceData) michael@0: return NS_OK; michael@0: mHasSourceData = true; michael@0: michael@0: // If there's a decoder open, synchronously decode the beginning of the image michael@0: // to check for errors and get the image's size. (If we already have the michael@0: // image's size, this does nothing.) Then kick off an async decode of the michael@0: // rest of the image. michael@0: if (mDecoder) { michael@0: nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: } michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mDecodingMonitor); michael@0: michael@0: // If we're not storing any source data, then there's nothing more we can do michael@0: // once we've tried decoding for size. michael@0: if (!StoringSourceData() && mDecoder) { michael@0: nsresult rv = ShutdownDecoder(eShutdownIntent_Done); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: } michael@0: michael@0: // If DecodeUntilSizeAvailable didn't finish the decode, let the decode worker michael@0: // finish decoding this image. michael@0: if (mDecoder) { michael@0: DecodePool::Singleton()->RequestDecode(this); michael@0: } michael@0: michael@0: // Free up any extra space in the backing buffer michael@0: mSourceData.Compact(); michael@0: } michael@0: michael@0: // Log header information michael@0: if (PR_LOG_TEST(GetCompressedImageAccountingLog(), PR_LOG_DEBUG)) { michael@0: char buf[9]; michael@0: get_header_str(buf, mSourceData.Elements(), mSourceData.Length()); michael@0: PR_LOG (GetCompressedImageAccountingLog(), PR_LOG_DEBUG, michael@0: ("CompressedImageAccounting: RasterImage::SourceDataComplete() - data " michael@0: "is done for container %p (%s) - header %p is 0x%s (length %d)", michael@0: this, michael@0: mSourceDataMimeType.get(), michael@0: mSourceData.Elements(), michael@0: buf, michael@0: mSourceData.Length())); michael@0: } michael@0: michael@0: // We now have one of the qualifications for discarding. Re-evaluate. michael@0: if (CanDiscard()) { michael@0: nsresult rv = DiscardTracker::Reset(&mDiscardTrackerNode); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus, bool aLastPart) michael@0: { michael@0: nsresult finalStatus = DoImageDataComplete(); michael@0: michael@0: // Give precedence to Necko failure codes. michael@0: if (NS_FAILED(aStatus)) michael@0: finalStatus = aStatus; michael@0: michael@0: // We just recorded OnStopRequest; we need to inform our listeners. michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mDecodingMonitor); michael@0: michael@0: nsRefPtr statusTracker = CurrentStatusTracker(); michael@0: statusTracker->GetDecoderObserver()->OnStopRequest(aLastPart, finalStatus); michael@0: michael@0: FinishedSomeDecoding(); michael@0: } michael@0: michael@0: return finalStatus; michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::OnImageDataAvailable(nsIRequest*, michael@0: nsISupports*, michael@0: nsIInputStream* aInStr, michael@0: uint64_t, michael@0: uint32_t aCount) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // WriteToRasterImage always consumes everything it gets michael@0: // if it doesn't run out of memory michael@0: uint32_t bytesRead; michael@0: rv = aInStr->ReadSegments(WriteToRasterImage, this, aCount, &bytesRead); michael@0: michael@0: NS_ABORT_IF_FALSE(bytesRead == aCount || HasError(), michael@0: "WriteToRasterImage should consume everything or the image must be in error!"); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::OnNewSourceData() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // The source data should be complete before calling this michael@0: NS_ABORT_IF_FALSE(mHasSourceData, michael@0: "Calling NewSourceData before SourceDataComplete!"); michael@0: if (!mHasSourceData) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: // Only supported for multipart channels. It wouldn't be too hard to change this, michael@0: // but it would involve making sure that things worked for decode-on-draw and michael@0: // discarding. Presently there's no need for this, so we don't. michael@0: NS_ABORT_IF_FALSE(mMultipart, "NewSourceData only supported for multipart"); michael@0: if (!mMultipart) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: // We're multipart, so we shouldn't be storing source data michael@0: NS_ABORT_IF_FALSE(!StoringSourceData(), michael@0: "Shouldn't be storing source data for multipart"); michael@0: michael@0: // We're not storing the source data and we got SourceDataComplete. We should michael@0: // have shut down the previous decoder michael@0: NS_ABORT_IF_FALSE(!mDecoder, "Shouldn't have a decoder in NewSourceData"); michael@0: michael@0: // The decoder was shut down and we didn't flag an error, so we should be decoded michael@0: NS_ABORT_IF_FALSE(mDecoded, "Should be decoded in NewSourceData"); michael@0: michael@0: // Reset some flags michael@0: mDecoded = false; michael@0: mHasSourceData = false; michael@0: mHasSize = false; michael@0: mWantFullDecode = true; michael@0: mDecodeRequest = nullptr; michael@0: michael@0: if (mAnim) { michael@0: mAnim->SetDoneDecoding(false); michael@0: } michael@0: michael@0: // We always need the size first. michael@0: rv = InitDecoder(/* aDoSizeDecode = */ true); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::SetSourceSizeHint(uint32_t sizeHint) michael@0: { michael@0: if (sizeHint && StoringSourceData()) michael@0: return mSourceData.SetCapacity(sizeHint) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /********* Methods to implement lazy allocation of nsIProperties object *************/ michael@0: NS_IMETHODIMP michael@0: RasterImage::Get(const char *prop, const nsIID & iid, void * *result) michael@0: { michael@0: if (!mProperties) michael@0: return NS_ERROR_FAILURE; michael@0: return mProperties->Get(prop, iid, result); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RasterImage::Set(const char *prop, nsISupports *value) michael@0: { michael@0: if (!mProperties) michael@0: mProperties = do_CreateInstance("@mozilla.org/properties;1"); michael@0: if (!mProperties) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: return mProperties->Set(prop, value); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RasterImage::Has(const char *prop, bool *_retval) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(_retval); michael@0: if (!mProperties) { michael@0: *_retval = false; michael@0: return NS_OK; michael@0: } michael@0: return mProperties->Has(prop, _retval); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RasterImage::Undefine(const char *prop) michael@0: { michael@0: if (!mProperties) michael@0: return NS_ERROR_FAILURE; michael@0: return mProperties->Undefine(prop); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RasterImage::GetKeys(uint32_t *count, char ***keys) michael@0: { michael@0: if (!mProperties) { michael@0: *count = 0; michael@0: *keys = nullptr; michael@0: return NS_OK; michael@0: } michael@0: return mProperties->GetKeys(count, keys); michael@0: } michael@0: michael@0: void michael@0: RasterImage::Discard(bool force) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // We should be ok for discard michael@0: NS_ABORT_IF_FALSE(force ? CanForciblyDiscard() : CanDiscard(), "Asked to discard but can't!"); michael@0: michael@0: // We should never discard when we have an active decoder michael@0: NS_ABORT_IF_FALSE(!mDecoder, "Asked to discard with open decoder!"); michael@0: michael@0: // As soon as an image becomes animated, it becomes non-discardable and any michael@0: // timers are cancelled. michael@0: NS_ABORT_IF_FALSE(!mAnim, "Asked to discard for animated image!"); michael@0: michael@0: // For post-operation logging michael@0: int old_frame_count = GetNumFrames(); michael@0: michael@0: // Delete all the decoded frames michael@0: mFrameBlender.Discard(); michael@0: michael@0: // Clear our downscaled frame. michael@0: mScaleResult.status = SCALE_INVALID; michael@0: mScaleResult.frame = nullptr; michael@0: michael@0: // Clear the last decoded multipart frame. michael@0: delete mMultipartDecodedFrame; michael@0: mMultipartDecodedFrame = nullptr; michael@0: michael@0: // Flag that we no longer have decoded frames for this image michael@0: mDecoded = false; michael@0: michael@0: // Notify that we discarded michael@0: if (mStatusTracker) michael@0: mStatusTracker->OnDiscard(); michael@0: michael@0: mDecodeRequest = nullptr; michael@0: michael@0: if (force) michael@0: DiscardTracker::Remove(&mDiscardTrackerNode); michael@0: michael@0: // Log michael@0: PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG, michael@0: ("CompressedImageAccounting: discarded uncompressed image " michael@0: "data from RasterImage %p (%s) - %d frames (cached count: %d); " michael@0: "Total Containers: %d, Discardable containers: %d, " michael@0: "Total source bytes: %lld, Source bytes for discardable containers %lld", michael@0: this, michael@0: mSourceDataMimeType.get(), michael@0: old_frame_count, michael@0: GetNumFrames(), michael@0: num_containers, michael@0: num_discardable_containers, michael@0: total_source_bytes, michael@0: discardable_source_bytes)); michael@0: } michael@0: michael@0: // Helper method to determine if we can discard an image michael@0: bool michael@0: RasterImage::CanDiscard() { michael@0: return (DiscardingEnabled() && // Globally enabled... michael@0: mDiscardable && // ...Enabled at creation time... michael@0: (mLockCount == 0) && // ...not temporarily disabled... michael@0: mHasSourceData && // ...have the source data... michael@0: mDecoded); // ...and have something to discard. michael@0: } michael@0: michael@0: bool michael@0: RasterImage::CanForciblyDiscard() { michael@0: return mDiscardable && // ...Enabled at creation time... michael@0: mHasSourceData; // ...have the source data... michael@0: } michael@0: michael@0: bool michael@0: RasterImage::CanForciblyDiscardAndRedecode() { michael@0: return mDiscardable && // ...Enabled at creation time... michael@0: mHasSourceData && // ...have the source data... michael@0: !mDecoder && // Can't discard with an open decoder michael@0: !mAnim; // Can never discard animated images michael@0: } michael@0: michael@0: // Helper method to tell us whether the clock is currently running for michael@0: // discarding this image. Mainly for assertions. michael@0: bool michael@0: RasterImage::DiscardingActive() { michael@0: return mDiscardTrackerNode.isInList(); michael@0: } michael@0: michael@0: // Helper method to determine if we're storing the source data in a buffer michael@0: // or just writing it directly to the decoder michael@0: bool michael@0: RasterImage::StoringSourceData() const { michael@0: return (mDecodeOnDraw || mDiscardable); michael@0: } michael@0: michael@0: michael@0: // Sets up a decoder for this image. It is an error to call this function michael@0: // when decoding is already in process (ie - when mDecoder is non-null). michael@0: nsresult michael@0: RasterImage::InitDecoder(bool aDoSizeDecode) michael@0: { michael@0: // Ensure that the decoder is not already initialized michael@0: NS_ABORT_IF_FALSE(!mDecoder, "Calling InitDecoder() while already decoding!"); michael@0: michael@0: // We shouldn't be firing up a decoder if we already have the frames decoded michael@0: NS_ABORT_IF_FALSE(!mDecoded, "Calling InitDecoder() but already decoded!"); michael@0: michael@0: // Since we're not decoded, we should not have a discard timer active michael@0: NS_ABORT_IF_FALSE(!DiscardingActive(), "Discard Timer active in InitDecoder()!"); michael@0: michael@0: // Make sure we actually get size before doing a full decode. michael@0: if (!aDoSizeDecode) { michael@0: NS_ABORT_IF_FALSE(mHasSize, "Must do a size decode before a full decode!"); michael@0: } michael@0: michael@0: // Figure out which decoder we want michael@0: eDecoderType type = GetDecoderType(mSourceDataMimeType.get()); michael@0: CONTAINER_ENSURE_TRUE(type != eDecoderType_unknown, NS_IMAGELIB_ERROR_NO_DECODER); michael@0: michael@0: // Instantiate the appropriate decoder michael@0: switch (type) { michael@0: case eDecoderType_png: michael@0: mDecoder = new nsPNGDecoder(*this); michael@0: break; michael@0: case eDecoderType_gif: michael@0: mDecoder = new nsGIFDecoder2(*this); michael@0: break; michael@0: case eDecoderType_jpeg: michael@0: // If we have all the data we don't want to waste cpu time doing michael@0: // a progressive decode michael@0: mDecoder = new nsJPEGDecoder(*this, michael@0: mHasBeenDecoded ? Decoder::SEQUENTIAL : michael@0: Decoder::PROGRESSIVE); michael@0: break; michael@0: case eDecoderType_bmp: michael@0: mDecoder = new nsBMPDecoder(*this); michael@0: break; michael@0: case eDecoderType_ico: michael@0: mDecoder = new nsICODecoder(*this); michael@0: break; michael@0: case eDecoderType_icon: michael@0: mDecoder = new nsIconDecoder(*this); michael@0: break; michael@0: default: michael@0: NS_ABORT_IF_FALSE(0, "Shouldn't get here!"); michael@0: } michael@0: michael@0: // If we already have frames, we're probably in the multipart/x-mixed-replace michael@0: // case. Regardless, we need to lock the last frame. Our invariant is that, michael@0: // while we have a decoder open, the last frame is always locked. michael@0: if (GetNumFrames() > 0) { michael@0: imgFrame *curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1); michael@0: curframe->LockImageData(); michael@0: } michael@0: michael@0: // Initialize the decoder michael@0: if (!mDecodeRequest) { michael@0: mDecodeRequest = new DecodeRequest(this); michael@0: } michael@0: MOZ_ASSERT(mDecodeRequest->mStatusTracker); michael@0: MOZ_ASSERT(mDecodeRequest->mStatusTracker->GetDecoderObserver()); michael@0: mDecoder->SetObserver(mDecodeRequest->mStatusTracker->GetDecoderObserver()); michael@0: mDecoder->SetSizeDecode(aDoSizeDecode); michael@0: mDecoder->SetDecodeFlags(mFrameDecodeFlags); michael@0: if (!aDoSizeDecode) { michael@0: // We already have the size; tell the decoder so it can preallocate a michael@0: // frame. By default, we create an ARGB frame with no offset. If decoders michael@0: // need a different type, they need to ask for it themselves. michael@0: mDecoder->NeedNewFrame(0, 0, 0, mSize.width, mSize.height, michael@0: gfxImageFormat::ARGB32); michael@0: mDecoder->AllocateFrame(); michael@0: } michael@0: mDecoder->Init(); michael@0: CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError()); michael@0: michael@0: if (!aDoSizeDecode) { michael@0: Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Subtract(mDecodeCount); michael@0: mDecodeCount++; michael@0: Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(mDecodeCount); michael@0: michael@0: if (mDecodeCount > sMaxDecodeCount) { michael@0: // Don't subtract out 0 from the histogram, because that causes its count michael@0: // to go negative, which is not kosher. michael@0: if (sMaxDecodeCount > 0) { michael@0: Telemetry::GetHistogramById(Telemetry::IMAGE_MAX_DECODE_COUNT)->Subtract(sMaxDecodeCount); michael@0: } michael@0: sMaxDecodeCount = mDecodeCount; michael@0: Telemetry::GetHistogramById(Telemetry::IMAGE_MAX_DECODE_COUNT)->Add(sMaxDecodeCount); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Flushes, closes, and nulls-out a decoder. Cleans up any related decoding michael@0: // state. It is an error to call this function when there is no initialized michael@0: // decoder. michael@0: // michael@0: // aIntent specifies the intent of the shutdown. If aIntent is michael@0: // eShutdownIntent_Done, an error is flagged if we didn't get what we should michael@0: // have out of the decode. If aIntent is eShutdownIntent_NotNeeded, we don't michael@0: // check this. If aIntent is eShutdownIntent_Error, we shut down in error mode. michael@0: nsresult michael@0: RasterImage::ShutdownDecoder(eShutdownIntent aIntent) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mDecodingMonitor.AssertCurrentThreadIn(); michael@0: michael@0: // Ensure that our intent is valid michael@0: NS_ABORT_IF_FALSE((aIntent >= 0) && (aIntent < eShutdownIntent_AllCount), michael@0: "Invalid shutdown intent"); michael@0: michael@0: // Ensure that the decoder is initialized michael@0: NS_ABORT_IF_FALSE(mDecoder, "Calling ShutdownDecoder() with no active decoder!"); michael@0: michael@0: // Figure out what kind of decode we were doing before we get rid of our decoder michael@0: bool wasSizeDecode = mDecoder->IsSizeDecode(); michael@0: michael@0: // Finalize the decoder michael@0: // null out mDecoder, _then_ check for errors on the close (otherwise the michael@0: // error routine might re-invoke ShutdownDecoder) michael@0: nsRefPtr decoder = mDecoder; michael@0: mDecoder = nullptr; michael@0: michael@0: mFinishing = true; michael@0: mInDecoder = true; michael@0: decoder->Finish(aIntent); michael@0: mInDecoder = false; michael@0: mFinishing = false; michael@0: michael@0: // Unlock the last frame (if we have any). Our invariant is that, while we michael@0: // have a decoder open, the last frame is always locked. michael@0: if (GetNumFrames() > 0) { michael@0: imgFrame *curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1); michael@0: curframe->UnlockImageData(); michael@0: } michael@0: michael@0: // Kill off our decode request, if it's pending. (If not, this call is michael@0: // harmless.) michael@0: DecodePool::StopDecoding(this); michael@0: michael@0: nsresult decoderStatus = decoder->GetDecoderError(); michael@0: if (NS_FAILED(decoderStatus)) { michael@0: DoError(); michael@0: return decoderStatus; michael@0: } michael@0: michael@0: // We just shut down the decoder. If we didn't get what we want, but expected michael@0: // to, flag an error michael@0: bool failed = false; michael@0: if (wasSizeDecode && !mHasSize) michael@0: failed = true; michael@0: if (!wasSizeDecode && !mDecoded) michael@0: failed = true; michael@0: if ((aIntent == eShutdownIntent_Done) && failed) { michael@0: DoError(); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // If we finished a full decode, and we're not meant to be storing source michael@0: // data, stop storing it. michael@0: if (!wasSizeDecode && !StoringSourceData()) { michael@0: mSourceData.Clear(); michael@0: } michael@0: michael@0: mBytesDecoded = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Writes the data to the decoder, updating the total number of bytes written. michael@0: nsresult michael@0: RasterImage::WriteToDecoder(const char *aBuffer, uint32_t aCount, DecodeStrategy aStrategy) michael@0: { michael@0: mDecodingMonitor.AssertCurrentThreadIn(); michael@0: michael@0: // We should have a decoder michael@0: NS_ABORT_IF_FALSE(mDecoder, "Trying to write to null decoder!"); michael@0: michael@0: // Write michael@0: nsRefPtr kungFuDeathGrip = mDecoder; michael@0: mInDecoder = true; michael@0: mDecoder->Write(aBuffer, aCount, aStrategy); michael@0: mInDecoder = false; michael@0: michael@0: CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError()); michael@0: michael@0: // Keep track of the total number of bytes written over the lifetime of the michael@0: // decoder michael@0: mBytesDecoded += aCount; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This function is called in situations where it's clear that we want the michael@0: // frames in decoded form (Draw, GetFrame, etc). If we're completely decoded, michael@0: // this method resets the discard timer (if we're discardable), since wanting michael@0: // the frames now is a good indicator of wanting them again soon. If we're not michael@0: // decoded, this method kicks off asynchronous decoding to generate the frames. michael@0: nsresult michael@0: RasterImage::WantDecodedFrames() michael@0: { michael@0: nsresult rv; michael@0: michael@0: // If we can discard, the clock should be running. Reset it. michael@0: if (CanDiscard()) { michael@0: NS_ABORT_IF_FALSE(DiscardingActive(), michael@0: "Decoded and discardable but discarding not activated!"); michael@0: rv = DiscardTracker::Reset(&mDiscardTrackerNode); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: } michael@0: michael@0: // Request a decode (no-op if we're decoded) michael@0: return StartDecoding(); michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void requestDecode() */ michael@0: NS_IMETHODIMP michael@0: RasterImage::RequestDecode() michael@0: { michael@0: return RequestDecodeCore(SYNCHRONOUS_NOTIFY); michael@0: } michael@0: michael@0: /* void startDecode() */ michael@0: NS_IMETHODIMP michael@0: RasterImage::StartDecoding() michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: return NS_DispatchToMainThread( michael@0: NS_NewRunnableMethod(this, &RasterImage::StartDecoding)); michael@0: } michael@0: // Here we are explicitly trading off flashing for responsiveness in the case michael@0: // that we're redecoding an image (see bug 845147). michael@0: return RequestDecodeCore(mHasBeenDecoded ? michael@0: SYNCHRONOUS_NOTIFY : SYNCHRONOUS_NOTIFY_AND_SOME_DECODE); michael@0: } michael@0: michael@0: bool michael@0: RasterImage::IsDecoded() michael@0: { michael@0: return mDecoded || mError; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // If we're already decoded, there's nothing to do. michael@0: if (mDecoded) michael@0: return NS_OK; michael@0: michael@0: // mFinishing protects against the case when we enter RequestDecode from michael@0: // ShutdownDecoder -- in that case, we're done with the decode, we're just michael@0: // not quite ready to admit it. See bug 744309. michael@0: if (mFinishing) michael@0: return NS_OK; michael@0: michael@0: // If we're currently waiting for a new frame, we can't do anything until michael@0: // that frame is allocated. michael@0: if (mDecoder && mDecoder->NeedsNewFrame()) michael@0: return NS_OK; michael@0: michael@0: // If our callstack goes through a size decoder, we have a problem. michael@0: // We need to shutdown the size decode and replace it with a full michael@0: // decoder, but can't do that from within the decoder itself. Thus, we post michael@0: // an asynchronous event to the event loop to do it later. Since michael@0: // RequestDecode() is an asynchronous function this works fine (though it's michael@0: // a little slower). michael@0: if (mInDecoder) { michael@0: nsRefPtr requestor = new imgDecodeRequestor(*this); michael@0: return NS_DispatchToCurrentThread(requestor); michael@0: } michael@0: michael@0: // If we have a size decoder open, make sure we get the size michael@0: if (mDecoder && mDecoder->IsSizeDecode()) { michael@0: nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: michael@0: // If we didn't get the size out of the image, we won't until we get more michael@0: // data, so signal that we want a full decode and give up for now. michael@0: if (!mHasSize) { michael@0: mWantFullDecode = true; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: ReentrantMonitorAutoEnter lock(mDecodingMonitor); michael@0: michael@0: // If we don't have any bytes to flush to the decoder, we can't do anything. michael@0: // mBytesDecoded can be bigger than mSourceData.Length() if we're not storing michael@0: // the source data. michael@0: if (mBytesDecoded > mSourceData.Length()) michael@0: return NS_OK; michael@0: michael@0: // If the image is waiting for decode work to be notified, go ahead and do that. michael@0: if (mDecodeRequest && michael@0: mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE && michael@0: aDecodeType != ASYNCHRONOUS) { michael@0: nsresult rv = FinishedSomeDecoding(); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: } michael@0: michael@0: // If we're fully decoded, we have nothing to do. We need this check after michael@0: // DecodeUntilSizeAvailable and FinishedSomeDecoding because they can result michael@0: // in us finishing an in-progress decode (or kicking off and finishing a michael@0: // synchronous decode if we're already waiting on a full decode). michael@0: if (mDecoded) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If we've already got a full decoder running, and have already michael@0: // decoded some bytes, we have nothing to do michael@0: if (mDecoder && !mDecoder->IsSizeDecode() && mBytesDecoded) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If we have a size decode open, interrupt it and shut it down; or if michael@0: // the decoder has different flags than what we need michael@0: if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) { michael@0: nsresult rv = FinishedSomeDecoding(eShutdownIntent_NotNeeded); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: } michael@0: michael@0: // If we don't have a decoder, create one michael@0: if (!mDecoder) { michael@0: rv = InitDecoder(/* aDoSizeDecode = */ false); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: michael@0: rv = FinishedSomeDecoding(); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: michael@0: MOZ_ASSERT(mDecoder); michael@0: } michael@0: michael@0: // If we've read all the data we have, we're done michael@0: if (mHasSourceData && mBytesDecoded == mSourceData.Length()) michael@0: return NS_OK; michael@0: michael@0: // If we can do decoding now, do so. Small images will decode completely, michael@0: // large images will decode a bit and post themselves to the event loop michael@0: // to finish decoding. michael@0: if (!mDecoded && !mInDecoder && mHasSourceData && aDecodeType == SYNCHRONOUS_NOTIFY_AND_SOME_DECODE) { michael@0: PROFILER_LABEL_PRINTF("RasterImage", "DecodeABitOf", "%s", GetURIString().get()); michael@0: DecodePool::Singleton()->DecodeABitOf(this, DECODE_SYNC); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mDecoded) { michael@0: // If we get this far, dispatch the worker. We do this instead of starting michael@0: // any immediate decoding to guarantee that all our decode notifications are michael@0: // dispatched asynchronously, and to ensure we stay responsive. michael@0: DecodePool::Singleton()->RequestDecode(this); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Synchronously decodes as much data as possible michael@0: nsresult michael@0: RasterImage::SyncDecode() michael@0: { michael@0: PROFILER_LABEL_PRINTF("RasterImage", "SyncDecode", "%s", GetURIString().get());; michael@0: michael@0: // If we have a size decoder open, make sure we get the size michael@0: if (mDecoder && mDecoder->IsSizeDecode()) { michael@0: nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: michael@0: // If we didn't get the size out of the image, we won't until we get more michael@0: // data, so signal that we want a full decode and give up for now. michael@0: if (!mHasSize) { michael@0: mWantFullDecode = true; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } michael@0: michael@0: ReentrantMonitorAutoEnter lock(mDecodingMonitor); michael@0: michael@0: // We really have no good way of forcing a synchronous decode if we're being michael@0: // called in a re-entrant manner (ie, from an event listener fired by a michael@0: // decoder), because the decoding machinery is already tied up. We thus explicitly michael@0: // disallow this type of call in the API, and check for it in API methods. michael@0: NS_ABORT_IF_FALSE(!mInDecoder, "Yikes, forcing sync in reentrant call!"); michael@0: michael@0: if (mDecodeRequest) { michael@0: // If the image is waiting for decode work to be notified, go ahead and do that. michael@0: if (mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) { michael@0: nsresult rv = FinishedSomeDecoding(); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: } michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: // If we're decoded already, or decoding until the size was available michael@0: // finished us as a side-effect, no worries michael@0: if (mDecoded) michael@0: return NS_OK; michael@0: michael@0: // If we don't have any bytes to flush to the decoder, we can't do anything. michael@0: // mBytesDecoded can be bigger than mSourceData.Length() if we're not storing michael@0: // the source data. michael@0: if (mBytesDecoded > mSourceData.Length()) michael@0: return NS_OK; michael@0: michael@0: // If we have a decoder open with different flags than what we need, shut it michael@0: // down michael@0: if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) { michael@0: nsresult rv = FinishedSomeDecoding(eShutdownIntent_NotNeeded); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: michael@0: if (mDecoded) { michael@0: // If we've finished decoding we need to discard so we can re-decode michael@0: // with the new flags. If we can't discard then there isn't michael@0: // anything we can do. michael@0: if (!CanForciblyDiscardAndRedecode()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: ForceDiscard(); michael@0: } michael@0: } michael@0: michael@0: // If we're currently waiting on a new frame for this image, we have to create michael@0: // it now. michael@0: if (mDecoder && mDecoder->NeedsNewFrame()) { michael@0: mDecoder->AllocateFrame(); michael@0: mDecodeRequest->mAllocatedNewFrame = true; michael@0: } michael@0: michael@0: // If we don't have a decoder, create one michael@0: if (!mDecoder) { michael@0: rv = InitDecoder(/* aDoSizeDecode = */ false); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: } michael@0: michael@0: // Write everything we have michael@0: rv = DecodeSomeData(mSourceData.Length() - mBytesDecoded, DECODE_SYNC); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: michael@0: // When we're doing a sync decode, we want to get as much information from the michael@0: // image as possible. We've send the decoder all of our data, so now's a good michael@0: // time to flush any invalidations (in case we don't have all the data and what michael@0: // we got left us mid-frame). michael@0: nsRefPtr kungFuDeathGrip = mDecoder; michael@0: mInDecoder = true; michael@0: mDecoder->FlushInvalidations(); michael@0: mInDecoder = false; michael@0: michael@0: rv = FinishedSomeDecoding(); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: michael@0: // If our decoder's still open, there's still work to be done. michael@0: if (mDecoder) { michael@0: DecodePool::Singleton()->RequestDecode(this); michael@0: } michael@0: michael@0: // All good if no errors! michael@0: return mError ? NS_ERROR_FAILURE : NS_OK; michael@0: } michael@0: michael@0: bool michael@0: RasterImage::CanQualityScale(const gfxSize& scale) michael@0: { michael@0: // If target size is 1:1 with original, don't scale. michael@0: if (scale.width == 1.0 && scale.height == 1.0) michael@0: return false; michael@0: michael@0: // To save memory don't quality upscale images bigger than the limit. michael@0: if (scale.width > 1.0 || scale.height > 1.0) { michael@0: uint32_t scaled_size = static_cast(mSize.width * mSize.height * scale.width * scale.height); michael@0: if (scaled_size > gHQUpscalingMaxSize) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: RasterImage::CanScale(GraphicsFilter aFilter, michael@0: gfxSize aScale, uint32_t aFlags) michael@0: { michael@0: // The high-quality scaler requires Skia. michael@0: #ifdef MOZ_ENABLE_SKIA michael@0: // We don't use the scaler for animated or multipart images to avoid doing a michael@0: // bunch of work on an image that just gets thrown away. michael@0: // We only use the scaler when drawing to the window because, if we're not michael@0: // drawing to a window (eg a canvas), updates to that image will be ignored. michael@0: if (gHQDownscaling && aFilter == GraphicsFilter::FILTER_GOOD && michael@0: !mAnim && mDecoded && !mMultipart && CanQualityScale(aScale) && michael@0: (aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING)) { michael@0: gfxFloat factor = gHQDownscalingMinFactor / 1000.0; michael@0: michael@0: return (aScale.width < factor || aScale.height < factor); michael@0: } michael@0: #endif michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: RasterImage::ScalingStart(ScaleRequest* request) michael@0: { michael@0: MOZ_ASSERT(request); michael@0: mScaleResult.scale = request->scale; michael@0: mScaleResult.status = SCALE_PENDING; michael@0: mScaleRequest = request; michael@0: } michael@0: michael@0: void michael@0: RasterImage::ScalingDone(ScaleRequest* request, ScaleStatus status) michael@0: { michael@0: MOZ_ASSERT(status == SCALE_DONE || status == SCALE_INVALID); michael@0: MOZ_ASSERT(request); michael@0: michael@0: if (status == SCALE_DONE) { michael@0: MOZ_ASSERT(request->done); michael@0: michael@0: imgFrame *scaledFrame = request->dstFrame.forget(); michael@0: scaledFrame->ImageUpdated(scaledFrame->GetRect()); michael@0: scaledFrame->ApplyDirtToSurfaces(); michael@0: michael@0: if (mStatusTracker) { michael@0: mStatusTracker->FrameChanged(&request->srcRect); michael@0: } michael@0: michael@0: mScaleResult.status = SCALE_DONE; michael@0: mScaleResult.frame = scaledFrame; michael@0: mScaleResult.scale = request->scale; michael@0: } else { michael@0: mScaleResult.status = SCALE_INVALID; michael@0: mScaleResult.frame = nullptr; michael@0: } michael@0: michael@0: // If we were waiting for this scale to come through, forget the scale michael@0: // request. Otherwise, we still have a scale outstanding that it's possible michael@0: // for us to (want to) stop. michael@0: if (mScaleRequest == request) { michael@0: mScaleRequest = nullptr; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: RasterImage::DrawWithPreDownscaleIfNeeded(imgFrame *aFrame, michael@0: gfxContext *aContext, michael@0: GraphicsFilter aFilter, michael@0: const gfxMatrix &aUserSpaceToImageSpace, michael@0: const gfxRect &aFill, michael@0: const nsIntRect &aSubimage, michael@0: uint32_t aFlags) michael@0: { michael@0: imgFrame *frame = aFrame; michael@0: nsIntRect framerect = frame->GetRect(); michael@0: gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace; michael@0: gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace; michael@0: imageSpaceToUserSpace.Invert(); michael@0: gfxSize scale = imageSpaceToUserSpace.ScaleFactors(true); michael@0: nsIntRect subimage = aSubimage; michael@0: nsRefPtr surf; michael@0: michael@0: if (CanScale(aFilter, scale, aFlags) && !frame->IsSinglePixel()) { michael@0: // If scale factor is still the same that we scaled for and michael@0: // ScaleWorker isn't still working, then we can use pre-downscaled frame. michael@0: // If scale factor has changed, order new request. michael@0: // FIXME: Current implementation doesn't support pre-downscale michael@0: // mechanism for multiple sizes from same src, since we cache michael@0: // pre-downscaled frame only for the latest requested scale. michael@0: // The solution is to cache more than one scaled image frame michael@0: // for each RasterImage. michael@0: bool needScaleReq; michael@0: if (mScaleResult.status == SCALE_DONE && mScaleResult.scale == scale) { michael@0: // Grab and hold the surface to make sure the OS didn't destroy it michael@0: mScaleResult.frame->GetSurface(getter_AddRefs(surf)); michael@0: needScaleReq = !surf; michael@0: if (surf) { michael@0: frame = mScaleResult.frame; michael@0: userSpaceToImageSpace.Multiply(gfxMatrix().Scale(scale.width, michael@0: scale.height)); michael@0: michael@0: // Since we're switching to a scaled image, we need to transform the michael@0: // area of the subimage to draw accordingly, since imgFrame::Draw() michael@0: // doesn't know about scaled frames. michael@0: subimage.ScaleRoundOut(scale.width, scale.height); michael@0: } michael@0: } else { michael@0: needScaleReq = !(mScaleResult.status == SCALE_PENDING && michael@0: mScaleResult.scale == scale); michael@0: } michael@0: michael@0: // If we're not waiting for exactly this result, and there's only one michael@0: // instance of this image on this page, ask for a scale. michael@0: if (needScaleReq && mLockCount == 1) { michael@0: if (NS_FAILED(frame->LockImageData())) { michael@0: frame->UnlockImageData(); michael@0: return false; michael@0: } michael@0: michael@0: // If we have an outstanding request, signal it to stop (if it can). michael@0: if (mScaleRequest) { michael@0: mScaleRequest->stopped = true; michael@0: } michael@0: michael@0: nsRefPtr runner = new ScaleRunner(this, scale, frame); michael@0: if (runner->IsOK()) { michael@0: if (!sScaleWorkerThread) { michael@0: NS_NewNamedThread("Image Scaler", getter_AddRefs(sScaleWorkerThread)); michael@0: ClearOnShutdown(&sScaleWorkerThread); michael@0: } michael@0: michael@0: sScaleWorkerThread->Dispatch(runner, NS_DISPATCH_NORMAL); michael@0: } michael@0: frame->UnlockImageData(); michael@0: } michael@0: } michael@0: michael@0: nsIntMargin padding(framerect.y, michael@0: mSize.width - framerect.XMost(), michael@0: mSize.height - framerect.YMost(), michael@0: framerect.x); michael@0: michael@0: return frame->Draw(aContext, aFilter, userSpaceToImageSpace, michael@0: aFill, padding, subimage, aFlags); michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* [noscript] void draw(in gfxContext aContext, michael@0: * in gfxGraphicsFilter aFilter, michael@0: * [const] in gfxMatrix aUserSpaceToImageSpace, michael@0: * [const] in gfxRect aFill, michael@0: * [const] in nsIntRect aSubimage, michael@0: * [const] in nsIntSize aViewportSize, michael@0: * [const] in SVGImageContext aSVGContext, michael@0: * in uint32_t aWhichFrame, michael@0: * in uint32_t aFlags); */ michael@0: NS_IMETHODIMP michael@0: RasterImage::Draw(gfxContext *aContext, michael@0: GraphicsFilter aFilter, michael@0: const gfxMatrix &aUserSpaceToImageSpace, michael@0: const gfxRect &aFill, michael@0: const nsIntRect &aSubimage, michael@0: const nsIntSize& /*aViewportSize - ignored*/, michael@0: const SVGImageContext* /*aSVGContext - ignored*/, michael@0: uint32_t aWhichFrame, michael@0: uint32_t aFlags) michael@0: { michael@0: if (aWhichFrame > FRAME_MAX_VALUE) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Disallowed in the API michael@0: if (mInDecoder && (aFlags & imgIContainer::FLAG_SYNC_DECODE)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Illegal -- you can't draw with non-default decode flags. michael@0: // (Disabling colorspace conversion might make sense to allow, but michael@0: // we don't currently.) michael@0: if ((aFlags & DECODE_FLAGS_MASK) != DECODE_FLAGS_DEFAULT) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ENSURE_ARG_POINTER(aContext); michael@0: michael@0: // We can only draw without discarding and redecoding in these cases: michael@0: // * We have the default decode flags. michael@0: // * We have exactly FLAG_DECODE_NO_PREMULTIPLY_ALPHA and the current frame michael@0: // is opaque. michael@0: bool haveDefaultFlags = (mFrameDecodeFlags == DECODE_FLAGS_DEFAULT); michael@0: bool haveSafeAlphaFlags = michael@0: (mFrameDecodeFlags == FLAG_DECODE_NO_PREMULTIPLY_ALPHA) && michael@0: FrameIsOpaque(FRAME_CURRENT); michael@0: michael@0: if (!(haveDefaultFlags || haveSafeAlphaFlags)) { michael@0: if (!CanForciblyDiscardAndRedecode()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: ForceDiscard(); michael@0: michael@0: mFrameDecodeFlags = DECODE_FLAGS_DEFAULT; michael@0: } michael@0: michael@0: // If this image is a candidate for discarding, reset its position in the michael@0: // discard tracker so we're less likely to discard it right away. michael@0: // michael@0: // (We don't normally draw unlocked images, so this conditition will usually michael@0: // be false. But we will draw unlocked images if image locking is globally michael@0: // disabled via the image.mem.allow_locking_in_content_processes pref.) michael@0: if (DiscardingActive()) { michael@0: DiscardTracker::Reset(&mDiscardTrackerNode); michael@0: } michael@0: michael@0: michael@0: if (IsUnlocked() && mStatusTracker) { michael@0: mStatusTracker->OnUnlockedDraw(); michael@0: } michael@0: michael@0: // We use !mDecoded && mHasSourceData to mean discarded. michael@0: if (!mDecoded && mHasSourceData) { michael@0: mDrawStartTime = TimeStamp::Now(); michael@0: } michael@0: michael@0: // If a synchronous draw is requested, flush anything that might be sitting around michael@0: if (aFlags & FLAG_SYNC_DECODE) { michael@0: nsresult rv = SyncDecode(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: uint32_t frameIndex = aWhichFrame == FRAME_FIRST ? 0 michael@0: : GetCurrentImgFrameIndex(); michael@0: imgFrame* frame = GetDrawableImgFrame(frameIndex); michael@0: if (!frame) { michael@0: return NS_OK; // Getting the frame (above) touches the image and kicks off decoding michael@0: } michael@0: michael@0: bool drawn = DrawWithPreDownscaleIfNeeded(frame, aContext, aFilter, michael@0: aUserSpaceToImageSpace, aFill, michael@0: aSubimage, aFlags); michael@0: if (!drawn) { michael@0: // The OS threw out some or all of our buffer. Start decoding again. michael@0: ForceDiscard(); michael@0: WantDecodedFrames(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mDecoded && !mDrawStartTime.IsNull()) { michael@0: TimeDuration drawLatency = TimeStamp::Now() - mDrawStartTime; michael@0: Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY, int32_t(drawLatency.ToMicroseconds())); michael@0: // clear the value of mDrawStartTime michael@0: mDrawStartTime = TimeStamp(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void lockImage() */ michael@0: NS_IMETHODIMP michael@0: RasterImage::LockImage() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), michael@0: "Main thread to encourage serialization with UnlockImage"); michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Cancel the discard timer if it's there michael@0: DiscardTracker::Remove(&mDiscardTrackerNode); michael@0: michael@0: // Increment the lock count michael@0: mLockCount++; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void unlockImage() */ michael@0: NS_IMETHODIMP michael@0: RasterImage::UnlockImage() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), michael@0: "Main thread to encourage serialization with LockImage"); michael@0: if (mError) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // It's an error to call this function if the lock count is 0 michael@0: NS_ABORT_IF_FALSE(mLockCount > 0, michael@0: "Calling UnlockImage with mLockCount == 0!"); michael@0: if (mLockCount == 0) michael@0: return NS_ERROR_ABORT; michael@0: michael@0: // We're locked, so discarding should not be active michael@0: NS_ABORT_IF_FALSE(!DiscardingActive(), "Locked, but discarding activated"); michael@0: michael@0: // Decrement our lock count michael@0: mLockCount--; michael@0: michael@0: // If we've decoded this image once before, we're currently decoding again, michael@0: // and our lock count is now zero (so nothing is forcing us to keep the michael@0: // decoded data around), try to cancel the decode and throw away whatever michael@0: // we've decoded. michael@0: if (mHasBeenDecoded && mDecoder && michael@0: mLockCount == 0 && CanForciblyDiscard()) { michael@0: PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG, michael@0: ("RasterImage[0x%p] canceling decode because image " michael@0: "is now unlocked.", this)); michael@0: ReentrantMonitorAutoEnter lock(mDecodingMonitor); michael@0: FinishedSomeDecoding(eShutdownIntent_NotNeeded); michael@0: ForceDiscard(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Otherwise, we might still be a candidate for discarding in the future. If michael@0: // we are, add ourselves to the discard tracker. michael@0: if (CanDiscard()) { michael@0: nsresult rv = DiscardTracker::Reset(&mDiscardTrackerNode); michael@0: CONTAINER_ENSURE_SUCCESS(rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* void requestDiscard() */ michael@0: NS_IMETHODIMP michael@0: RasterImage::RequestDiscard() michael@0: { michael@0: if (CanDiscard() && CanForciblyDiscardAndRedecode()) { michael@0: ForceDiscard(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Flushes up to aMaxBytes to the decoder. michael@0: nsresult michael@0: RasterImage::DecodeSomeData(uint32_t aMaxBytes, DecodeStrategy aStrategy) michael@0: { michael@0: // We should have a decoder if we get here michael@0: NS_ABORT_IF_FALSE(mDecoder, "trying to decode without decoder!"); michael@0: michael@0: mDecodingMonitor.AssertCurrentThreadIn(); michael@0: michael@0: // First, if we've just been called because we allocated a frame on the main michael@0: // thread, let the decoder deal with the data it set aside at that time by michael@0: // passing it a null buffer. michael@0: if (mDecodeRequest->mAllocatedNewFrame) { michael@0: mDecodeRequest->mAllocatedNewFrame = false; michael@0: nsresult rv = WriteToDecoder(nullptr, 0, aStrategy); michael@0: if (NS_FAILED(rv) || mDecoder->NeedsNewFrame()) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // If we have nothing else to decode, return michael@0: if (mBytesDecoded == mSourceData.Length()) michael@0: return NS_OK; michael@0: michael@0: MOZ_ASSERT(mBytesDecoded < mSourceData.Length()); michael@0: michael@0: // write the proper amount of data michael@0: uint32_t bytesToDecode = std::min(aMaxBytes, michael@0: mSourceData.Length() - mBytesDecoded); michael@0: nsresult rv = WriteToDecoder(mSourceData.Elements() + mBytesDecoded, michael@0: bytesToDecode, michael@0: aStrategy); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // There are various indicators that tell us we're finished with the decode michael@0: // task at hand and can shut down the decoder. michael@0: // michael@0: // This method may not be called if there is no decoder. michael@0: bool michael@0: RasterImage::IsDecodeFinished() michael@0: { michael@0: // Precondition michael@0: mDecodingMonitor.AssertCurrentThreadIn(); michael@0: NS_ABORT_IF_FALSE(mDecoder, "Can't call IsDecodeFinished() without decoder!"); michael@0: michael@0: // The decode is complete if we got what we wanted. michael@0: if (mDecoder->IsSizeDecode()) { michael@0: if (mDecoder->HasSize()) { michael@0: return true; michael@0: } michael@0: } else if (mDecoder->GetDecodeDone()) { michael@0: return true; michael@0: } michael@0: michael@0: // If the decoder returned because it needed a new frame and we haven't michael@0: // written to it since then, the decoder may be storing data that it hasn't michael@0: // decoded yet. michael@0: if (mDecoder->NeedsNewFrame() || michael@0: (mDecodeRequest && mDecodeRequest->mAllocatedNewFrame)) { michael@0: return false; michael@0: } michael@0: michael@0: // Otherwise, if we have all the source data and wrote all the source data, michael@0: // we're done. michael@0: // michael@0: // (NB - This can be the case even for non-erroneous images because michael@0: // Decoder::GetDecodeDone() might not return true until after we call michael@0: // Decoder::Finish() in ShutdownDecoder()) michael@0: if (mHasSourceData && (mBytesDecoded == mSourceData.Length())) { michael@0: return true; michael@0: } michael@0: michael@0: // If we get here, assume it's not finished. michael@0: return false; michael@0: } michael@0: michael@0: // Indempotent error flagging routine. If a decoder is open, shuts it down. michael@0: void michael@0: RasterImage::DoError() michael@0: { michael@0: // If we've flagged an error before, we have nothing to do michael@0: if (mError) michael@0: return; michael@0: michael@0: // We can't safely handle errors off-main-thread, so dispatch a worker to do it. michael@0: if (!NS_IsMainThread()) { michael@0: HandleErrorWorker::DispatchIfNeeded(this); michael@0: return; michael@0: } michael@0: michael@0: // Calling FinishedSomeDecoding and CurrentStatusTracker requires us to be in michael@0: // the decoding monitor. michael@0: ReentrantMonitorAutoEnter lock(mDecodingMonitor); michael@0: michael@0: // If we're mid-decode, shut down the decoder. michael@0: if (mDecoder) { michael@0: FinishedSomeDecoding(eShutdownIntent_Error); michael@0: } michael@0: michael@0: // Put the container in an error state. michael@0: mError = true; michael@0: michael@0: nsRefPtr statusTracker = CurrentStatusTracker(); michael@0: statusTracker->GetDecoderObserver()->OnError(); michael@0: michael@0: // Log our error michael@0: LOG_CONTAINER_ERROR; michael@0: } michael@0: michael@0: /* static */ void michael@0: RasterImage::HandleErrorWorker::DispatchIfNeeded(RasterImage* aImage) michael@0: { michael@0: if (!aImage->mPendingError) { michael@0: aImage->mPendingError = true; michael@0: nsRefPtr worker = new HandleErrorWorker(aImage); michael@0: NS_DispatchToMainThread(worker); michael@0: } michael@0: } michael@0: michael@0: RasterImage::HandleErrorWorker::HandleErrorWorker(RasterImage* aImage) michael@0: : mImage(aImage) michael@0: { michael@0: MOZ_ASSERT(mImage, "Should have image"); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RasterImage::HandleErrorWorker::Run() michael@0: { michael@0: mImage->DoError(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsIInputStream callback to copy the incoming image data directly to the michael@0: // RasterImage without processing. The RasterImage is passed as the closure. michael@0: // Always reads everything it gets, even if the data is erroneous. michael@0: NS_METHOD michael@0: RasterImage::WriteToRasterImage(nsIInputStream* /* unused */, michael@0: void* aClosure, michael@0: const char* aFromRawSegment, michael@0: uint32_t /* unused */, michael@0: uint32_t aCount, michael@0: uint32_t* aWriteCount) michael@0: { michael@0: // Retrieve the RasterImage michael@0: RasterImage* image = static_cast(aClosure); michael@0: michael@0: // Copy the source data. Unless we hit OOM, we squelch the return value michael@0: // here, because returning an error means that ReadSegments stops michael@0: // reading data, violating our invariant that we read everything we get. michael@0: // If we hit OOM then we fail and the load is aborted. michael@0: nsresult rv = image->AddSourceData(aFromRawSegment, aCount); michael@0: if (rv == NS_ERROR_OUT_OF_MEMORY) { michael@0: image->DoError(); michael@0: return rv; michael@0: } michael@0: michael@0: // We wrote everything we got michael@0: *aWriteCount = aCount; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: RasterImage::ShouldAnimate() michael@0: { michael@0: return ImageResource::ShouldAnimate() && GetNumFrames() >= 2 && michael@0: !mAnimationFinished; michael@0: } michael@0: michael@0: /* readonly attribute uint32_t framesNotified; */ michael@0: #ifdef DEBUG michael@0: NS_IMETHODIMP michael@0: RasterImage::GetFramesNotified(uint32_t *aFramesNotified) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aFramesNotified); michael@0: michael@0: *aFramesNotified = mFramesNotified; michael@0: michael@0: return NS_OK; michael@0: } michael@0: #endif michael@0: michael@0: nsresult michael@0: RasterImage::RequestDecodeIfNeeded(nsresult aStatus, michael@0: eShutdownIntent aIntent, michael@0: bool aDone, michael@0: bool aWasSize) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // If we were a size decode and a full decode was requested, now's the time. michael@0: if (NS_SUCCEEDED(aStatus) && michael@0: aIntent == eShutdownIntent_Done && michael@0: aDone && michael@0: aWasSize && michael@0: mWantFullDecode) { michael@0: mWantFullDecode = false; michael@0: michael@0: // If we're not meant to be storing source data and we just got the size, michael@0: // we need to synchronously flush all the data we got to a full decoder. michael@0: // When that decoder is shut down, we'll also clear our source data. michael@0: return StoringSourceData() ? RequestDecode() michael@0: : SyncDecode(); michael@0: } michael@0: michael@0: // We don't need a full decode right now, so just return the existing status. michael@0: return aStatus; michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_Done */, michael@0: DecodeRequest* aRequest /* = nullptr */) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mDecodingMonitor.AssertCurrentThreadIn(); michael@0: michael@0: nsRefPtr request; michael@0: if (aRequest) { michael@0: request = aRequest; michael@0: } else { michael@0: request = mDecodeRequest; michael@0: } michael@0: michael@0: // Ensure that, if the decoder is the last reference to the image, we don't michael@0: // destroy it by destroying the decoder. michael@0: nsRefPtr image(this); michael@0: michael@0: bool done = false; michael@0: bool wasSize = false; michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (image->mDecoder) { michael@0: image->mDecoder->MarkFrameDirty(); michael@0: michael@0: if (request && request->mChunkCount && !image->mDecoder->IsSizeDecode()) { michael@0: Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, request->mChunkCount); michael@0: } michael@0: michael@0: if (!image->mHasSize && image->mDecoder->HasSize()) { michael@0: image->mDecoder->SetSizeOnImage(); michael@0: } michael@0: michael@0: // If the decode finished, or we're specifically being told to shut down, michael@0: // tell the image and shut down the decoder. michael@0: if (image->IsDecodeFinished() || aIntent != eShutdownIntent_Done) { michael@0: done = true; michael@0: michael@0: // Hold on to a reference to the decoder until we're done with it michael@0: nsRefPtr decoder = image->mDecoder; michael@0: michael@0: wasSize = decoder->IsSizeDecode(); michael@0: michael@0: // Do some telemetry if this isn't a size decode. michael@0: if (request && !wasSize) { michael@0: Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME, michael@0: int32_t(request->mDecodeTime.ToMicroseconds())); michael@0: michael@0: // We record the speed for only some decoders. The rest have michael@0: // SpeedHistogram return HistogramCount. michael@0: Telemetry::ID id = decoder->SpeedHistogram(); michael@0: if (id < Telemetry::HistogramCount) { michael@0: int32_t KBps = int32_t(request->mImage->mBytesDecoded / michael@0: (1024 * request->mDecodeTime.ToSeconds())); michael@0: Telemetry::Accumulate(id, KBps); michael@0: } michael@0: } michael@0: michael@0: // We need to shut down the decoder first, in order to ensure all michael@0: // decoding routines have been finished. michael@0: rv = image->ShutdownDecoder(aIntent); michael@0: if (NS_FAILED(rv)) { michael@0: image->DoError(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: ImageStatusDiff diff = michael@0: request ? image->mStatusTracker->Difference(request->mStatusTracker) michael@0: : image->mStatusTracker->DecodeStateAsDifference(); michael@0: image->mStatusTracker->ApplyDifference(diff); michael@0: michael@0: if (mNotifying) { michael@0: // Accumulate the status changes. We don't permit recursive notifications michael@0: // because they cause subtle concurrency bugs, so we'll delay sending out michael@0: // the notifications until we pop back to the lowest invocation of michael@0: // FinishedSomeDecoding on the stack. michael@0: NS_WARNING("Recursively notifying in RasterImage::FinishedSomeDecoding!"); michael@0: mStatusDiff.Combine(diff); michael@0: } else { michael@0: MOZ_ASSERT(mStatusDiff.IsNoChange(), "Shouldn't have an accumulated change at this point"); michael@0: michael@0: while (!diff.IsNoChange()) { michael@0: // Tell the observers what happened. michael@0: mNotifying = true; michael@0: image->mStatusTracker->SyncNotifyDifference(diff); michael@0: mNotifying = false; michael@0: michael@0: // Gather any status changes that may have occurred as a result of sending michael@0: // out the previous notifications. If there were any, we'll send out michael@0: // notifications for them next. michael@0: diff = mStatusDiff; michael@0: mStatusDiff = ImageStatusDiff::NoChange(); michael@0: } michael@0: } michael@0: michael@0: return RequestDecodeIfNeeded(rv, aIntent, done, wasSize); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(RasterImage::DecodePool, michael@0: nsIObserver) michael@0: michael@0: /* static */ RasterImage::DecodePool* michael@0: RasterImage::DecodePool::Singleton() michael@0: { michael@0: if (!sSingleton) { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: sSingleton = new DecodePool(); michael@0: ClearOnShutdown(&sSingleton); michael@0: } michael@0: michael@0: return sSingleton; michael@0: } michael@0: michael@0: already_AddRefed michael@0: RasterImage::DecodePool::GetEventTarget() michael@0: { michael@0: nsCOMPtr target = do_QueryInterface(mThreadPool); michael@0: return target.forget(); michael@0: } michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: michael@0: class RIDThreadPoolListener : public nsIThreadPoolListener michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSITHREADPOOLLISTENER michael@0: michael@0: RIDThreadPoolListener() {} michael@0: ~RIDThreadPoolListener() {} michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(RIDThreadPoolListener, nsIThreadPoolListener) michael@0: michael@0: NS_IMETHODIMP michael@0: RIDThreadPoolListener::OnThreadCreated() michael@0: { michael@0: if (IsNuwaProcess()) { michael@0: NuwaMarkCurrentThread((void (*)(void *))nullptr, nullptr); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RIDThreadPoolListener::OnThreadShuttingDown() michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: #endif // MOZ_NUWA_PROCESS michael@0: michael@0: RasterImage::DecodePool::DecodePool() michael@0: : mThreadPoolMutex("Thread Pool") michael@0: { michael@0: if (gMultithreadedDecoding) { michael@0: mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID); michael@0: if (mThreadPool) { michael@0: mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder")); michael@0: uint32_t limit; michael@0: if (gDecodingThreadLimit <= 0) { michael@0: limit = std::max(PR_GetNumberOfProcessors(), 2) - 1; michael@0: } else { michael@0: limit = static_cast(gDecodingThreadLimit); michael@0: } michael@0: michael@0: mThreadPool->SetThreadLimit(limit); michael@0: mThreadPool->SetIdleThreadLimit(limit); michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: if (IsNuwaProcess()) { michael@0: mThreadPool->SetListener(new RIDThreadPoolListener()); michael@0: } michael@0: #endif michael@0: michael@0: nsCOMPtr obsSvc = mozilla::services::GetObserverService(); michael@0: if (obsSvc) { michael@0: obsSvc->AddObserver(this, "xpcom-shutdown-threads", false); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: RasterImage::DecodePool::~DecodePool() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!"); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RasterImage::DecodePool::Observe(nsISupports *subject, const char *topic, michael@0: const char16_t *data) michael@0: { michael@0: NS_ASSERTION(strcmp(topic, "xpcom-shutdown-threads") == 0, "oops"); michael@0: michael@0: nsCOMPtr threadPool; michael@0: michael@0: { michael@0: MutexAutoLock threadPoolLock(mThreadPoolMutex); michael@0: threadPool = mThreadPool; michael@0: mThreadPool = nullptr; michael@0: } michael@0: michael@0: if (threadPool) { michael@0: threadPool->Shutdown(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: RasterImage::DecodePool::RequestDecode(RasterImage* aImg) michael@0: { michael@0: MOZ_ASSERT(aImg->mDecoder); michael@0: aImg->mDecodingMonitor.AssertCurrentThreadIn(); michael@0: michael@0: // If we're currently waiting on a new frame for this image, we can't do any michael@0: // decoding. michael@0: if (!aImg->mDecoder->NeedsNewFrame()) { michael@0: // No matter whether this is currently being decoded, we need to update the michael@0: // number of bytes we want it to decode. michael@0: aImg->mDecodeRequest->mBytesToDecode = aImg->mSourceData.Length() - aImg->mBytesDecoded; michael@0: michael@0: if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_PENDING || michael@0: aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_ACTIVE) { michael@0: // The image is already in our list of images to decode, or currently being michael@0: // decoded, so we don't have to do anything else. michael@0: return; michael@0: } michael@0: michael@0: aImg->mDecodeRequest->mRequestStatus = DecodeRequest::REQUEST_PENDING; michael@0: nsRefPtr job = new DecodeJob(aImg->mDecodeRequest, aImg); michael@0: michael@0: MutexAutoLock threadPoolLock(mThreadPoolMutex); michael@0: if (!gMultithreadedDecoding || !mThreadPool) { michael@0: NS_DispatchToMainThread(job); michael@0: } else { michael@0: mThreadPool->Dispatch(job, nsIEventTarget::DISPATCH_NORMAL); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: RasterImage::DecodePool::DecodeABitOf(RasterImage* aImg, DecodeStrategy aStrategy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: aImg->mDecodingMonitor.AssertCurrentThreadIn(); michael@0: michael@0: if (aImg->mDecodeRequest) { michael@0: // If the image is waiting for decode work to be notified, go ahead and do that. michael@0: if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) { michael@0: aImg->FinishedSomeDecoding(); michael@0: } michael@0: } michael@0: michael@0: DecodeSomeOfImage(aImg, aStrategy); michael@0: michael@0: aImg->FinishedSomeDecoding(); michael@0: michael@0: // If the decoder needs a new frame, enqueue an event to get it; that event michael@0: // will enqueue another decode request when it's done. michael@0: if (aImg->mDecoder && aImg->mDecoder->NeedsNewFrame()) { michael@0: FrameNeededWorker::GetNewFrame(aImg); michael@0: } else { michael@0: // If we aren't yet finished decoding and we have more data in hand, add michael@0: // this request to the back of the priority list. michael@0: if (aImg->mDecoder && michael@0: !aImg->mError && michael@0: !aImg->IsDecodeFinished() && michael@0: aImg->mSourceData.Length() > aImg->mBytesDecoded) { michael@0: RequestDecode(aImg); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: RasterImage::DecodePool::StopDecoding(RasterImage* aImg) michael@0: { michael@0: aImg->mDecodingMonitor.AssertCurrentThreadIn(); michael@0: michael@0: // If we haven't got a decode request, we're not currently decoding. (Having michael@0: // a decode request doesn't imply we *are* decoding, though.) michael@0: if (aImg->mDecodeRequest) { michael@0: aImg->mDecodeRequest->mRequestStatus = DecodeRequest::REQUEST_STOPPED; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RasterImage::DecodePool::DecodeJob::Run() michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor); michael@0: michael@0: // If we were interrupted, we shouldn't do any work. michael@0: if (mRequest->mRequestStatus == DecodeRequest::REQUEST_STOPPED) { michael@0: DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage, mRequest); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If someone came along and synchronously decoded us, there's nothing for us to do. michael@0: if (!mImage->mDecoder || mImage->IsDecodeFinished()) { michael@0: DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage, mRequest); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If we're a decode job that's been enqueued since a previous decode that michael@0: // still needs a new frame, we can't do anything. Wait until the michael@0: // FrameNeededWorker enqueues another frame. michael@0: if (mImage->mDecoder->NeedsNewFrame()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mRequest->mRequestStatus = DecodeRequest::REQUEST_ACTIVE; michael@0: michael@0: uint32_t oldByteCount = mImage->mBytesDecoded; michael@0: michael@0: DecodeType type = DECODE_TYPE_UNTIL_DONE_BYTES; michael@0: michael@0: // Multithreaded decoding can be disabled. If we've done so, we don't want to michael@0: // monopolize the main thread, and will allow a timeout in DecodeSomeOfImage. michael@0: if (NS_IsMainThread()) { michael@0: type = DECODE_TYPE_UNTIL_TIME; michael@0: } michael@0: michael@0: DecodePool::Singleton()->DecodeSomeOfImage(mImage, DECODE_ASYNC, type, mRequest->mBytesToDecode); michael@0: michael@0: uint32_t bytesDecoded = mImage->mBytesDecoded - oldByteCount; michael@0: michael@0: mRequest->mRequestStatus = DecodeRequest::REQUEST_WORK_DONE; michael@0: michael@0: // If the decoder needs a new frame, enqueue an event to get it; that event michael@0: // will enqueue another decode request when it's done. michael@0: if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) { michael@0: FrameNeededWorker::GetNewFrame(mImage); michael@0: } michael@0: // If we aren't yet finished decoding and we have more data in hand, add michael@0: // this request to the back of the list. michael@0: else if (mImage->mDecoder && michael@0: !mImage->mError && michael@0: !mImage->mPendingError && michael@0: !mImage->IsDecodeFinished() && michael@0: bytesDecoded < mRequest->mBytesToDecode && michael@0: bytesDecoded > 0) { michael@0: DecodePool::Singleton()->RequestDecode(mImage); michael@0: } else { michael@0: // Nothing more for us to do - let everyone know what happened. michael@0: DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage, mRequest); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: RasterImage::DecodePool::DecodeJob::~DecodeJob() michael@0: { michael@0: if (gMultithreadedDecoding) { michael@0: // Dispatch mImage to main thread to prevent mImage from being destructed by decode thread. michael@0: nsCOMPtr mainThread = do_GetMainThread(); michael@0: NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!"); michael@0: if (mainThread) { michael@0: // Handle ambiguous nsISupports inheritance michael@0: RasterImage* rawImg = nullptr; michael@0: mImage.swap(rawImg); michael@0: DebugOnly rv = NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg)); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::DecodePool::DecodeUntilSizeAvailable(RasterImage* aImg) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: ReentrantMonitorAutoEnter lock(aImg->mDecodingMonitor); michael@0: michael@0: if (aImg->mDecodeRequest) { michael@0: // If the image is waiting for decode work to be notified, go ahead and do that. michael@0: if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) { michael@0: nsresult rv = aImg->FinishedSomeDecoding(); michael@0: if (NS_FAILED(rv)) { michael@0: aImg->DoError(); michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // We use DECODE_ASYNC here because we just want to get the size information michael@0: // here and defer the rest of the work. michael@0: nsresult rv = DecodeSomeOfImage(aImg, DECODE_ASYNC, DECODE_TYPE_UNTIL_SIZE); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // If the decoder needs a new frame, enqueue an event to get it; that event michael@0: // will enqueue another decode request when it's done. michael@0: if (aImg->mDecoder && aImg->mDecoder->NeedsNewFrame()) { michael@0: FrameNeededWorker::GetNewFrame(aImg); michael@0: } else { michael@0: rv = aImg->FinishedSomeDecoding(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: RasterImage::DecodePool::DecodeSomeOfImage(RasterImage* aImg, michael@0: DecodeStrategy aStrategy, michael@0: DecodeType aDecodeType /* = DECODE_TYPE_UNTIL_TIME */, michael@0: uint32_t bytesToDecode /* = 0 */) michael@0: { michael@0: NS_ABORT_IF_FALSE(aImg->mInitialized, michael@0: "Worker active for uninitialized container!"); michael@0: aImg->mDecodingMonitor.AssertCurrentThreadIn(); michael@0: michael@0: // If an error is flagged, it probably happened while we were waiting michael@0: // in the event queue. michael@0: if (aImg->mError) michael@0: return NS_OK; michael@0: michael@0: // If there is an error worker pending (say because the main thread has enqueued michael@0: // another decode request for us before processing the error worker) then bail out. michael@0: if (aImg->mPendingError) michael@0: return NS_OK; michael@0: michael@0: // If mDecoded or we don't have a decoder, we must have finished already (for michael@0: // example, a synchronous decode request came while the worker was pending). michael@0: if (!aImg->mDecoder || aImg->mDecoded) michael@0: return NS_OK; michael@0: michael@0: // If we're doing synchronous decodes, and we're waiting on a new frame for michael@0: // this image, get it now. michael@0: if (aStrategy == DECODE_SYNC && aImg->mDecoder->NeedsNewFrame()) { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: aImg->mDecoder->AllocateFrame(); michael@0: aImg->mDecodeRequest->mAllocatedNewFrame = true; michael@0: } michael@0: michael@0: // If we're not synchronous, we can't allocate a frame right now. michael@0: else if (aImg->mDecoder->NeedsNewFrame()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr decoderKungFuDeathGrip = aImg->mDecoder; michael@0: michael@0: uint32_t maxBytes; michael@0: if (aImg->mDecoder->IsSizeDecode()) { michael@0: // Decode all available data if we're a size decode; they're cheap, and we michael@0: // want them to be more or less synchronous. michael@0: maxBytes = aImg->mSourceData.Length(); michael@0: } else { michael@0: // We're only guaranteed to decode this many bytes, so in particular, michael@0: // gDecodeBytesAtATime should be set high enough for us to read the size michael@0: // from most images. michael@0: maxBytes = gDecodeBytesAtATime; michael@0: } michael@0: michael@0: if (bytesToDecode == 0) { michael@0: bytesToDecode = aImg->mSourceData.Length() - aImg->mBytesDecoded; michael@0: } michael@0: michael@0: int32_t chunkCount = 0; michael@0: TimeStamp start = TimeStamp::Now(); michael@0: TimeStamp deadline = start + TimeDuration::FromMilliseconds(gMaxMSBeforeYield); michael@0: michael@0: // We keep decoding chunks until: michael@0: // * we don't have any data left to decode, michael@0: // * the decode completes, michael@0: // * we're an UNTIL_SIZE decode and we get the size, or michael@0: // * we run out of time. michael@0: // We also try to decode at least one "chunk" if we've allocated a new frame, michael@0: // even if we have no more data to send to the decoder. michael@0: while ((aImg->mSourceData.Length() > aImg->mBytesDecoded && michael@0: bytesToDecode > 0 && michael@0: !aImg->IsDecodeFinished() && michael@0: !(aDecodeType == DECODE_TYPE_UNTIL_SIZE && aImg->mHasSize) && michael@0: !aImg->mDecoder->NeedsNewFrame()) || michael@0: (aImg->mDecodeRequest && aImg->mDecodeRequest->mAllocatedNewFrame)) { michael@0: chunkCount++; michael@0: uint32_t chunkSize = std::min(bytesToDecode, maxBytes); michael@0: nsresult rv = aImg->DecodeSomeData(chunkSize, aStrategy); michael@0: if (NS_FAILED(rv)) { michael@0: aImg->DoError(); michael@0: return rv; michael@0: } michael@0: michael@0: bytesToDecode -= chunkSize; michael@0: michael@0: // Yield if we've been decoding for too long. We check this _after_ decoding michael@0: // a chunk to ensure that we don't yield without doing any decoding. michael@0: if (aDecodeType == DECODE_TYPE_UNTIL_TIME && TimeStamp::Now() >= deadline) michael@0: break; michael@0: } michael@0: michael@0: if (aImg->mDecodeRequest) { michael@0: aImg->mDecodeRequest->mDecodeTime += (TimeStamp::Now() - start); michael@0: aImg->mDecodeRequest->mChunkCount += chunkCount; michael@0: } michael@0: michael@0: // Flush invalidations (and therefore paint) now that we've decoded all the michael@0: // chunks we're going to. michael@0: // michael@0: // However, don't paint if: michael@0: // michael@0: // * This was an until-size decode. Until-size decodes are always followed michael@0: // by normal decodes, so don't bother painting. michael@0: // michael@0: // * The decoder flagged an error. The decoder may have written garbage michael@0: // into the output buffer; don't paint it to the screen. michael@0: // michael@0: // * We have all the source data. This disables progressive display of michael@0: // previously-decoded images, thus letting us finish decoding faster, michael@0: // since we don't waste time painting while we decode. michael@0: // Decoder::PostFrameStop() will flush invalidations once the decode is michael@0: // done. michael@0: michael@0: if (aDecodeType != DECODE_TYPE_UNTIL_SIZE && michael@0: !aImg->mDecoder->HasError() && michael@0: !aImg->mHasSourceData) { michael@0: aImg->mInDecoder = true; michael@0: aImg->mDecoder->FlushInvalidations(); michael@0: aImg->mInDecoder = false; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: RasterImage::DecodeDoneWorker::DecodeDoneWorker(RasterImage* image, DecodeRequest* request) michael@0: : mImage(image) michael@0: , mRequest(request) michael@0: {} michael@0: michael@0: void michael@0: RasterImage::DecodeDoneWorker::NotifyFinishedSomeDecoding(RasterImage* image, DecodeRequest* request) michael@0: { michael@0: image->mDecodingMonitor.AssertCurrentThreadIn(); michael@0: michael@0: nsCOMPtr worker = new DecodeDoneWorker(image, request); michael@0: NS_DispatchToMainThread(worker); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RasterImage::DecodeDoneWorker::Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor); michael@0: michael@0: mImage->FinishedSomeDecoding(eShutdownIntent_Done, mRequest); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: RasterImage::FrameNeededWorker::FrameNeededWorker(RasterImage* image) michael@0: : mImage(image) michael@0: {} michael@0: michael@0: michael@0: void michael@0: RasterImage::FrameNeededWorker::GetNewFrame(RasterImage* image) michael@0: { michael@0: nsCOMPtr worker = new FrameNeededWorker(image); michael@0: NS_DispatchToMainThread(worker); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RasterImage::FrameNeededWorker::Run() michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor); michael@0: nsresult rv = NS_OK; michael@0: michael@0: // If we got a synchronous decode in the mean time, we don't need to do michael@0: // anything. michael@0: if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) { michael@0: rv = mImage->mDecoder->AllocateFrame(); michael@0: mImage->mDecodeRequest->mAllocatedNewFrame = true; michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv) && mImage->mDecoder) { michael@0: // By definition, we're not done decoding, so enqueue us for more decoding. michael@0: DecodePool::Singleton()->RequestDecode(mImage); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace image michael@0: } // namespace mozilla