author | Tom Schuster <evilpies@gmail.com> |
Sun, 11 Jan 2015 20:43:32 +0100 | |
changeset 223154 | 8f3cc2c90893ab18bcb9eda7961ff3fc08022a99 |
parent 223153 | a8044fd506db631fe74958c453dec459f10e3b11 |
child 223155 | 238f460e7aedc87a4ce3fdff8b9479256d3f3d58 |
push id | 53837 |
push user | evilpies@gmail.com |
push date | Sun, 11 Jan 2015 19:49:14 +0000 |
treeherder | mozilla-inbound@8f3cc2c90893 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
bugs | 1117607, 1118087, 1118092, 1118105, 1030372, 1079627 |
milestone | 37.0a1 |
backs out | b4ebefd0f7e3a0814ea9d9f42dc0da74be3fb998 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -231,25 +231,26 @@ private: DECL_GFX_PREF(Live, "gl.msaa-level", MSAALevel, uint32_t, 2); DECL_GFX_PREF(Once, "image.cache.timeweight", ImageCacheTimeWeight, int32_t, 500); DECL_GFX_PREF(Once, "image.cache.size", ImageCacheSize, int32_t, 5*1024*1024); DECL_GFX_PREF(Live, "image.high_quality_downscaling.enabled", ImageHQDownscalingEnabled, bool, false); DECL_GFX_PREF(Live, "image.high_quality_downscaling.min_factor", ImageHQDownscalingMinFactor, uint32_t, 1000); DECL_GFX_PREF(Live, "image.high_quality_upscaling.max_size", ImageHQUpscalingMaxSize, uint32_t, 20971520); - DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time", ImageMemDecodeBytesAtATime, uint32_t, 200000); + DECL_GFX_PREF(Live, "image.mem.decode_bytes_at_a_time", ImageMemDecodeBytesAtATime, uint32_t, 200000); DECL_GFX_PREF(Live, "image.mem.decodeondraw", ImageMemDecodeOnDraw, bool, false); DECL_GFX_PREF(Live, "image.mem.discardable", ImageMemDiscardable, bool, false); + DECL_GFX_PREF(Live, "image.mem.max_ms_before_yield", ImageMemMaxMSBeforeYield, uint32_t, 400); DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1); DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb", ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024); DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000); DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor", ImageMemSurfaceCacheSizeFactor, uint32_t, 64); DECL_GFX_PREF(Live, "image.mozsamplesize.enabled", ImageMozSampleSizeEnabled, bool, false); - DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit", ImageMTDecodingLimit, int32_t, -1); + DECL_GFX_PREF(Live, "image.multithreaded_decoding.limit", ImageMTDecodingLimit, int32_t, -1); DECL_GFX_PREF(Once, "layers.acceleration.disabled", LayersAccelerationDisabled, bool, false); DECL_GFX_PREF(Live, "layers.acceleration.draw-fps", LayersDrawFPS, bool, false); DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.print-histogram", FPSPrintHistogram, bool, false); DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.write-to-file", WriteFPSToFile, bool, false); DECL_GFX_PREF(Once, "layers.acceleration.force-enabled", LayersAccelerationForceEnabled, bool, false); DECL_GFX_PREF(Once, "layers.async-video.enabled", AsyncVideoEnabled, bool, true); DECL_GFX_PREF(Once, "layers.async-video-oop.enabled", AsyncVideoOOPEnabled, bool, true);
--- a/image/decoders/nsBMPDecoder.cpp +++ b/image/decoders/nsBMPDecoder.cpp @@ -33,17 +33,17 @@ GetBMPLog() return sBMPLog; } #endif // Convert from row (1..height) to absolute line (0..height-1) #define LINE(row) ((mBIH.height < 0) ? (-mBIH.height - (row)) : ((row) - 1)) #define PIXEL_OFFSET(row, col) (LINE(row) * mBIH.width + col) -nsBMPDecoder::nsBMPDecoder(RasterImage* aImage) +nsBMPDecoder::nsBMPDecoder(RasterImage& aImage) : Decoder(aImage) , mPos(0) , mLOH(WIN_V3_HEADER_LENGTH) , mNumColors(0) , mColors(nullptr) , mRow(nullptr) , mRowBytes(0) , mCurLine(1) // Otherwise decoder will never start. @@ -434,26 +434,21 @@ nsBMPDecoder::WriteInternal(const char* // + 4 because the line is padded to a 4 bit boundary, but // I don't want to make exact calculations here, that's unnecessary. // Also, it compensates rounding error. if (!mRow) { PostDataError(); return; } } - - MOZ_ASSERT(!mImageData, "Already have a buffer allocated?"); - nsresult rv = AllocateFrame(0, nsIntRect(nsIntPoint(), GetSize()), - gfx::SurfaceFormat::B8G8R8A8); - if (NS_FAILED(rv)) { + if (!mImageData) { + PostDecoderError(NS_ERROR_FAILURE); return; } - MOZ_ASSERT(mImageData, "Should have a buffer now"); - // Prepare for transparency if ((mBIH.compression == BI_RLE8) || (mBIH.compression == BI_RLE4)) { // Clear the image, as the RLE may jump over areas memset(mImageData, 0, mImageDataLength); } } if (mColors && mPos >= mLOH) {
--- a/image/decoders/nsBMPDecoder.h +++ b/image/decoders/nsBMPDecoder.h @@ -18,17 +18,17 @@ namespace image { class RasterImage; /// Decoder for BMP-Files, as used by Windows and OS/2 class nsBMPDecoder : public Decoder { public: - explicit nsBMPDecoder(RasterImage* aImage); + explicit nsBMPDecoder(RasterImage& aImage); ~nsBMPDecoder(); // Specifies whether or not the BMP file will contain alpha data // If set to true and the BMP is 32BPP, the alpha data will be // retrieved from the 4th byte of image data per pixel void SetUseAlphaData(bool useAlphaData); // Obtains the bits per pixel from the internal BIH header
--- a/image/decoders/nsGIFDecoder2.cpp +++ b/image/decoders/nsGIFDecoder2.cpp @@ -63,17 +63,17 @@ namespace image { mGIFStruct.state = (s); \ PR_END_MACRO // Get a 16-bit value stored in little-endian format #define GETINT16(p) ((p)[1]<<8|(p)[0]) ////////////////////////////////////////////////////////////////////// // GIF Decoder Implementation -nsGIFDecoder2::nsGIFDecoder2(RasterImage* aImage) +nsGIFDecoder2::nsGIFDecoder2(RasterImage& aImage) : Decoder(aImage) , mCurrentRow(-1) , mLastFlushedRow(-1) , mOldColor(0) , mCurrentFrameIndex(-1) , mCurrentPass(0) , mLastFlushedPass(0) , mGIFOpen(false) @@ -156,52 +156,58 @@ nsGIFDecoder2::BeginGIF() } mGIFOpen = true; PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height); } //****************************************************************************** -nsresult +void nsGIFDecoder2::BeginImageFrame(uint16_t aDepth) { MOZ_ASSERT(HasSize()); gfx::SurfaceFormat format; if (mGIFStruct.is_transparent) { format = gfx::SurfaceFormat::B8G8R8A8; PostHasTransparency(); } else { format = gfx::SurfaceFormat::B8G8R8X8; } - nsIntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset, - mGIFStruct.width, mGIFStruct.height); - // Use correct format, RGB for first frame, PAL for following frames // and include transparency to allow for optimization of opaque images - nsresult rv = NS_OK; if (mGIFStruct.images_decoded) { - // Image data is stored with original depth and palette. - rv = AllocateFrame(mGIFStruct.images_decoded, frameRect, format, aDepth); + // Image data is stored with original depth and palette + NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset, + mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height, + format, aDepth); } else { - if (!nsIntRect(nsIntPoint(), GetSize()).IsEqualEdges(frameRect)) { + nsRefPtr<imgFrame> currentFrame = GetCurrentFrame(); + + // Our first full frame is automatically created by the image decoding + // infrastructure. Just use it as long as it matches up. + if (!currentFrame->GetRect().IsEqualEdges(nsIntRect(mGIFStruct.x_offset, + mGIFStruct.y_offset, + mGIFStruct.width, + mGIFStruct.height))) { + // We need padding on the first frame, which means that we don't draw into // part of the image at all. Report that as transparency. PostHasTransparency(); + + // Regardless of depth of input, image is decoded into 24bit RGB + NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset, + mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height, + format); } - - // Regardless of depth of input, the first frame is decoded into 24bit RGB. - rv = AllocateFrame(mGIFStruct.images_decoded, frameRect, format); } mCurrentFrameIndex = mGIFStruct.images_decoded; - - return rv; } //****************************************************************************** void nsGIFDecoder2::EndImageFrame() { Opacity opacity = Opacity::SOME_TRANSPARENCY; @@ -946,23 +952,37 @@ nsGIFDecoder2::WriteInternal(const char* depth = (q[8]&0x07) + 1; } uint32_t realDepth = depth; while (mGIFStruct.tpixel >= (1 << realDepth) && (realDepth < 8)) { realDepth++; } // Mask to limit the color values within the colormap mColorMask = 0xFF >> (8 - realDepth); + BeginImageFrame(realDepth); - if (NS_FAILED(BeginImageFrame(realDepth))) { - mGIFStruct.state = gif_error; - return; + if (NeedsNewFrame()) { + // We now need a new frame from the decoder framework. We leave all our + // data in the buffer as if it wasn't consumed, copy to our hold and + // return to the decoder framework. + uint32_t size = + len + mGIFStruct.bytes_to_consume + mGIFStruct.bytes_in_hold; + if (size) { + if (SetHold(q, + mGIFStruct.bytes_to_consume + mGIFStruct.bytes_in_hold, + buf, len)) { + // Back into the decoder infrastructure so we can get called again. + GETN(9, gif_image_header_continue); + return; + } + } + break; + } else { + // FALL THROUGH } - - // FALL THROUGH } case gif_image_header_continue: { // While decoders can reuse frames, we unconditionally increment // mGIFStruct.images_decoded when we're done with a frame, so we both can // and need to zero out the colormap and image data after every new frame. memset(mImageData, 0, mImageDataLength); if (mColormap) {
--- a/image/decoders/nsGIFDecoder2.h +++ b/image/decoders/nsGIFDecoder2.h @@ -18,29 +18,29 @@ class RasterImage; ////////////////////////////////////////////////////////////////////// // nsGIFDecoder2 Definition class nsGIFDecoder2 : public Decoder { public: - explicit nsGIFDecoder2(RasterImage* aImage); + explicit nsGIFDecoder2(RasterImage& aImage); ~nsGIFDecoder2(); virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE; virtual void FinishInternal() MOZ_OVERRIDE; virtual Telemetry::ID SpeedHistogram() MOZ_OVERRIDE; private: // These functions will be called when the decoder has a decoded row, // frame size information, etc. void BeginGIF(); - nsresult BeginImageFrame(uint16_t aDepth); + void BeginImageFrame(uint16_t aDepth); void EndImageFrame(); void FlushImageData(); void FlushImageData(uint32_t fromRow, uint32_t rows); nsresult GifWrite(const uint8_t* buf, uint32_t numbytes); uint32_t OutputRow(); bool DoLzw(const uint8_t* q); bool SetHold(const uint8_t* buf, uint32_t count,
--- a/image/decoders/nsICODecoder.cpp +++ b/image/decoders/nsICODecoder.cpp @@ -53,17 +53,17 @@ nsICODecoder::GetNumColors() default: numColors = (uint16_t)-1; } } return numColors; } -nsICODecoder::nsICODecoder(RasterImage* aImage) +nsICODecoder::nsICODecoder(RasterImage& aImage) : Decoder(aImage) { mPos = mImageOffset = mCurrIcon = mNumIcons = mBPP = mRowBytes = 0; mIsPNG = false; mRow = nullptr; mOldLine = mCurLine = 1; // Otherwise decoder will never start } @@ -77,23 +77,20 @@ nsICODecoder::~nsICODecoder() void nsICODecoder::FinishInternal() { // We shouldn't be called in error cases NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call FinishInternal after error!"); // Finish the internally used decoder as well if (mContainedDecoder) { - if (!mContainedDecoder->HasError()) { - mContainedDecoder->FinishInternal(); - } + mContainedDecoder->FinishSharedDecoder(); mDecodeDone = mContainedDecoder->GetDecodeDone(); mProgress |= mContainedDecoder->TakeProgress(); mInvalidRect.Union(mContainedDecoder->TakeInvalidRect()); - mCurrentFrame = mContainedDecoder->GetCurrentFrameRef(); } } // Returns a buffer filled with the bitmap file header in little endian: // Signature 2 bytes 'BM' // FileSize 4 bytes File size in bytes // reserved 4 bytes unused (=0) // DataOffset 4 bytes File offset to Raster Data @@ -247,17 +244,17 @@ nsICODecoder::WriteInternal(const char* aCount -= 2; } if (mNumIcons == 0) { return; // Nothing to do. } uint16_t colorDepth = 0; - nsIntSize prefSize = mImage->GetRequestedResolution(); + nsIntSize prefSize = mImage.GetRequestedResolution(); if (prefSize.width == 0 && prefSize.height == 0) { prefSize.SizeTo(PREFICONSIZE, PREFICONSIZE); } // A measure of the difference in size between the entry we've found // and the requested size. We will choose the smallest image that is // >= requested size (i.e. we assume it's better to downscale a larger // icon than to upscale a smaller one). @@ -340,18 +337,19 @@ nsICODecoder::WriteInternal(const char* aCount -= toCopy; aBuffer += toCopy; mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes, PNGSIGNATURESIZE); if (mIsPNG) { mContainedDecoder = new nsPNGDecoder(mImage); mContainedDecoder->SetSizeDecode(IsSizeDecode()); - mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations); - mContainedDecoder->Init(); + mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength, + mColormap, mColormapSize, + Move(mRefForContainedDecoder)); if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) { return; } } } // If we have a PNG, let the PNG decoder do all of the rest of the work if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) { @@ -417,18 +415,19 @@ nsICODecoder::WriteInternal(const char* // Init the bitmap decoder which will do most of the work for us // It will do everything except the AND mask which isn't present in bitmaps // bmpDecoder is for local scope ease, it will be freed by mContainedDecoder nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage); mContainedDecoder = bmpDecoder; bmpDecoder->SetUseAlphaData(true); mContainedDecoder->SetSizeDecode(IsSizeDecode()); - mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations); - mContainedDecoder->Init(); + mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength, + mColormap, mColormapSize, + Move(mRefForContainedDecoder)); // The ICO format when containing a BMP does not include the 14 byte // bitmap file header. To use the code of the BMP decoder we need to // generate this header ourselves and feed it to the BMP decoder. int8_t bfhBuffer[BMPFILEHEADERSIZE]; if (!FillBitmapFileHeaderBuffer(bfhBuffer)) { PostDataError(); return; @@ -621,10 +620,40 @@ nsICODecoder::ProcessDirEntry(IconDirEnt aTarget.mBitCount = LittleEndian::readUint16(&aTarget.mBitCount); memcpy(&aTarget.mBytesInRes, mDirEntryArray + 8, sizeof(aTarget.mBytesInRes)); aTarget.mBytesInRes = LittleEndian::readUint32(&aTarget.mBytesInRes); memcpy(&aTarget.mImageOffset, mDirEntryArray + 12, sizeof(aTarget.mImageOffset)); aTarget.mImageOffset = LittleEndian::readUint32(&aTarget.mImageOffset); } +bool +nsICODecoder::NeedsNewFrame() const +{ + if (mContainedDecoder) { + return mContainedDecoder->NeedsNewFrame(); + } + + return Decoder::NeedsNewFrame(); +} + +nsresult +nsICODecoder::AllocateFrame() +{ + nsresult rv; + + if (mContainedDecoder) { + rv = mContainedDecoder->AllocateFrame(); + mCurrentFrame = mContainedDecoder->GetCurrentFrameRef(); + mProgress |= mContainedDecoder->TakeProgress(); + mInvalidRect.Union(mContainedDecoder->TakeInvalidRect()); + return rv; + } + + // Grab a strong ref that we'll later hand over to the contained decoder. This + // lets us avoid creating a RawAccessFrameRef off-main-thread. + rv = Decoder::AllocateFrame(); + mRefForContainedDecoder = GetCurrentFrameRef(); + return rv; +} + } // namespace image } // namespace mozilla
--- a/image/decoders/nsICODecoder.h +++ b/image/decoders/nsICODecoder.h @@ -18,33 +18,37 @@ namespace mozilla { namespace image { class RasterImage; class nsICODecoder : public Decoder { public: - explicit nsICODecoder(RasterImage* aImage); + explicit nsICODecoder(RasterImage& aImage); virtual ~nsICODecoder(); // Obtains the width of the icon directory entry uint32_t GetRealWidth() const { return mDirEntry.mWidth == 0 ? 256 : mDirEntry.mWidth; } // Obtains the height of the icon directory entry uint32_t GetRealHeight() const { return mDirEntry.mHeight == 0 ? 256 : mDirEntry.mHeight; } virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE; virtual void FinishInternal() MOZ_OVERRIDE; + virtual nsresult AllocateFrame() MOZ_OVERRIDE; + +protected: + virtual bool NeedsNewFrame() const MOZ_OVERRIDE; private: // Writes to the contained decoder and sets the appropriate errors // Returns true if there are no errors. bool WriteToContainedDecoder(const char* aBuffer, uint32_t aCount); // Processes a single dir entry of the icon resource void ProcessDirEntry(IconDirEntry& aTarget); @@ -74,16 +78,17 @@ private: uint16_t mNumIcons; // Stores the number of icons in the ICO file uint16_t mCurrIcon; // Stores the current dir entry index we are processing uint32_t mImageOffset; // Stores the offset of the image data we want uint8_t* mRow; // Holds one raw line of the image int32_t mCurLine; // Line index of the image that's currently being decoded uint32_t mRowBytes; // How many bytes of the row were already received int32_t mOldLine; // Previous index of the line nsRefPtr<Decoder> mContainedDecoder; // Contains either a BMP or PNG resource + RawAccessFrameRef mRefForContainedDecoder; // Avoid locking off-main-thread char mDirEntryArray[ICODIRENTRYSIZE]; // Holds the current dir entry buffer IconDirEntry mDirEntry; // Holds a decoded dir entry // Holds the potential bytes that can be a PNG signature char mSignature[PNGSIGNATURESIZE]; // Holds the potential bytes for a bitmap information header char mBIHraw[40]; // Stores whether or not the icon file we are processing has type 1 (icon)
--- a/image/decoders/nsIconDecoder.cpp +++ b/image/decoders/nsIconDecoder.cpp @@ -10,17 +10,17 @@ #include "nsRect.h" #include "nsError.h" #include "RasterImage.h" #include <algorithm> namespace mozilla { namespace image { -nsIconDecoder::nsIconDecoder(RasterImage* aImage) +nsIconDecoder::nsIconDecoder(RasterImage& aImage) : Decoder(aImage), mWidth(-1), mHeight(-1), mPixBytesRead(0), mState(iconStateStart) { // Nothing to do } @@ -68,28 +68,21 @@ nsIconDecoder::WriteInternal(const char* } // If We're doing a size decode, we're done if (IsSizeDecode()) { mState = iconStateFinished; break; } - { - MOZ_ASSERT(!mImageData, "Already have a buffer allocated?"); - nsresult rv = AllocateFrame(0, nsIntRect(nsIntPoint(), GetSize()), - gfx::SurfaceFormat::B8G8R8A8); - if (NS_FAILED(rv)) { - mState = iconStateFinished; - return; - } + if (!mImageData) { + PostDecoderError(NS_ERROR_OUT_OF_MEMORY); + return; } - MOZ_ASSERT(mImageData, "Should have a buffer now"); - // Book Keeping aBuffer++; aCount--; mState = iconStateReadPixels; break; case iconStateReadPixels: {
--- a/image/decoders/nsIconDecoder.h +++ b/image/decoders/nsIconDecoder.h @@ -33,17 +33,17 @@ class RasterImage; // // //////////////////////////////////////////////////////////////////////////////// class nsIconDecoder : public Decoder { public: - explicit nsIconDecoder(RasterImage* aImage); + explicit nsIconDecoder(RasterImage& aImage); virtual ~nsIconDecoder(); virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE; uint8_t mWidth; uint8_t mHeight; uint32_t mPixBytesRead; uint32_t mState;
--- a/image/decoders/nsJPEGDecoder.cpp +++ b/image/decoders/nsJPEGDecoder.cpp @@ -79,17 +79,17 @@ METHODDEF(boolean) fill_input_buffer (j_ METHODDEF(void) skip_input_data (j_decompress_ptr jd, long num_bytes); METHODDEF(void) term_source (j_decompress_ptr jd); METHODDEF(void) my_error_exit (j_common_ptr cinfo); // Normal JFIF markers can't have more bytes than this. #define MAX_JPEG_MARKER_LENGTH (((uint32_t)1 << 16) - 1) -nsJPEGDecoder::nsJPEGDecoder(RasterImage* aImage, +nsJPEGDecoder::nsJPEGDecoder(RasterImage& aImage, Decoder::DecodeStyle aDecodeStyle) : Decoder(aImage) , mDecodeStyle(aDecodeStyle) { mState = JPEG_HEADER; mReading = true; mImageData = nullptr; @@ -232,17 +232,17 @@ nsJPEGDecoder::WriteInternal(const char* // Step 3: read file parameters with jpeg_read_header() if (jpeg_read_header(&mInfo, TRUE) == JPEG_SUSPENDED) { PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, ("} (JPEG_SUSPENDED)")); return; // I/O suspension } - int sampleSize = mImage->GetRequestedSampleSize(); + int sampleSize = mImage.GetRequestedSampleSize(); if (sampleSize > 0) { mInfo.scale_num = 1; mInfo.scale_denom = sampleSize; } // Used to set up image size so arrays can be allocated jpeg_calc_output_dimensions(&mInfo); @@ -381,28 +381,24 @@ nsJPEGDecoder::WriteInternal(const char* } } // Don't allocate a giant and superfluous memory buffer // when not doing a progressive decode. mInfo.buffered_image = mDecodeStyle == PROGRESSIVE && jpeg_has_multiple_scans(&mInfo); - MOZ_ASSERT(!mImageData, "Already have a buffer allocated?"); - nsresult rv = AllocateFrame(0, nsIntRect(nsIntPoint(), GetSize()), - gfx::SurfaceFormat::B8G8R8A8); - if (NS_FAILED(rv)) { + if (!mImageData) { mState = JPEG_ERROR; + PostDecoderError(NS_ERROR_OUT_OF_MEMORY); PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, ("} (could not initialize image frame)")); return; } - MOZ_ASSERT(mImageData, "Should have a buffer now"); - PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG, (" JPEGDecoderAccounting: nsJPEGDecoder::" "Write -- created image frame with %ux%u pixels", mInfo.output_width, mInfo.output_height)); mState = JPEG_START_DECOMPRESS; }
--- a/image/decoders/nsJPEGDecoder.h +++ b/image/decoders/nsJPEGDecoder.h @@ -47,17 +47,17 @@ typedef enum { } jstate; class RasterImage; struct Orientation; class nsJPEGDecoder : public Decoder { public: - nsJPEGDecoder(RasterImage* aImage, Decoder::DecodeStyle aDecodeStyle); + nsJPEGDecoder(RasterImage& aImage, Decoder::DecodeStyle aDecodeStyle); virtual ~nsJPEGDecoder(); virtual void InitInternal() MOZ_OVERRIDE; virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE; virtual void FinishInternal() MOZ_OVERRIDE; virtual Telemetry::ID SpeedHistogram() MOZ_OVERRIDE; void NotifyDone();
--- a/image/decoders/nsPNGDecoder.cpp +++ b/image/decoders/nsPNGDecoder.cpp @@ -102,17 +102,17 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameIn } } #endif // First 8 bytes of a PNG file const uint8_t nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; -nsPNGDecoder::nsPNGDecoder(RasterImage* aImage) +nsPNGDecoder::nsPNGDecoder(RasterImage& aImage) : Decoder(aImage), mPNG(nullptr), mInfo(nullptr), mCMSLine(nullptr), interlacebuf(nullptr), mInProfile(nullptr), mTransform(nullptr), mHeaderBytesRead(0), mCMSMode(0), mChannels(0), mFrameIsHidden(false), mDisablePremultipliedAlpha(false), mNumFrames(0) @@ -136,69 +136,62 @@ nsPNGDecoder::~nsPNGDecoder() // mTransform belongs to us only if mInProfile is non-null if (mTransform) { qcms_transform_release(mTransform); } } } // CreateFrame() is used for both simple and animated images -nsresult -nsPNGDecoder::CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset, - int32_t aWidth, int32_t aHeight, - gfx::SurfaceFormat aFormat) +void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset, + int32_t width, int32_t height, + gfx::SurfaceFormat format) { MOZ_ASSERT(HasSize()); - if (aFormat == gfx::SurfaceFormat::B8G8R8A8) { + if (format == gfx::SurfaceFormat::B8G8R8A8) { PostHasTransparency(); } - nsIntRect frameRect(aXOffset, aYOffset, aWidth, aHeight); - if (mNumFrames == 0 && - !nsIntRect(nsIntPoint(), GetSize()).IsEqualEdges(frameRect)) { - // We need padding on the first frame, which means that we don't draw into - // part of the image at all. Report that as transparency. - PostHasTransparency(); + // Our first full frame is automatically created by the image decoding + // infrastructure. Just use it as long as it matches up. + nsIntRect neededRect(x_offset, y_offset, width, height); + nsRefPtr<imgFrame> currentFrame = GetCurrentFrame(); + if (!currentFrame->GetRect().IsEqualEdges(neededRect)) { + if (mNumFrames == 0) { + // We need padding on the first frame, which means that we don't draw into + // part of the image at all. Report that as transparency. + PostHasTransparency(); + } + + NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format); + } else if (mNumFrames != 0) { + NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format); } - // XXX(seth): Some tests depend on the first frame of PNGs being B8G8R8A8. - // This is something we should fix. - gfx::SurfaceFormat format = aFormat; - if (mNumFrames == 0) { - format = gfx::SurfaceFormat::B8G8R8A8; - } - - nsresult rv = AllocateFrame(mNumFrames, frameRect, format); - if (NS_FAILED(rv)) { - return rv; - } - - mFrameRect = frameRect; + mFrameRect = neededRect; mFrameHasNoAlpha = true; PR_LOG(GetPNGDecoderAccountingLog(), PR_LOG_DEBUG, ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created " "image frame with %dx%d pixels in container %p", - aWidth, aHeight, + width, height, &mImage)); #ifdef PNG_APNG_SUPPORTED if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) { mAnimInfo = AnimFrameInfo(mPNG, mInfo); if (mAnimInfo.mDispose == DisposalMethod::CLEAR) { // We may have to display the background under this image during // animation playback, so we regard it as transparent. PostHasTransparency(); } } #endif - - return NS_OK; } // set timeout and frame disposal method for the current frame void nsPNGDecoder::EndImageFrame() { if (mFrameIsHidden) { return; @@ -651,21 +644,17 @@ nsPNGDecoder::info_callback(png_structp png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback, nullptr); } if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) { decoder->mFrameIsHidden = true; } else { #endif - nsresult rv = decoder->CreateFrame(0, 0, width, height, decoder->format); - if (NS_FAILED(rv)) { - png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY - } - MOZ_ASSERT(decoder->mImageData, "Should have a buffer now"); + decoder->CreateFrame(0, 0, width, height, decoder->format); #ifdef PNG_APNG_SUPPORTED } #endif if (decoder->mTransform && (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) { uint32_t bpp[] = { 0, 3, 4, 3, 4 }; decoder->mCMSLine = @@ -678,16 +667,22 @@ nsPNGDecoder::info_callback(png_structp if (interlace_type == PNG_INTERLACE_ADAM7) { if (height < INT32_MAX / (width * channels)) { decoder->interlacebuf = (uint8_t*)moz_malloc(channels * width * height); } if (!decoder->interlacebuf) { png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY } } + + if (decoder->NeedsNewFrame()) { + // We know that we need a new frame, so pause input so the decoder + // infrastructure can give it to us. + png_process_data_pause(png_ptr, /* save = */ 1); + } } void nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass) { /* libpng comments: * @@ -840,22 +835,23 @@ nsPNGDecoder::frame_info_callback(png_st // Only the first frame can be hidden, so unhide unconditionally here. decoder->mFrameIsHidden = false; x_offset = png_get_next_frame_x_offset(png_ptr, decoder->mInfo); y_offset = png_get_next_frame_y_offset(png_ptr, decoder->mInfo); width = png_get_next_frame_width(png_ptr, decoder->mInfo); height = png_get_next_frame_height(png_ptr, decoder->mInfo); - nsresult rv = - decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format); - if (NS_FAILED(rv)) { - png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY + decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format); + + if (decoder->NeedsNewFrame()) { + // We know that we need a new frame, so pause input so the decoder + // infrastructure can give it to us. + png_process_data_pause(png_ptr, /* save = */ 1); } - MOZ_ASSERT(decoder->mImageData, "Should have a buffer now"); } #endif void nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr) { /* libpng comments: *
--- a/image/decoders/nsPNGDecoder.h +++ b/image/decoders/nsPNGDecoder.h @@ -19,26 +19,26 @@ namespace mozilla { namespace image { class RasterImage; class nsPNGDecoder : public Decoder { public: - explicit nsPNGDecoder(RasterImage* aImage); + explicit nsPNGDecoder(RasterImage& aImage); virtual ~nsPNGDecoder(); virtual void InitInternal() MOZ_OVERRIDE; virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE; virtual Telemetry::ID SpeedHistogram() MOZ_OVERRIDE; - nsresult CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset, - int32_t aWidth, int32_t aHeight, - gfx::SurfaceFormat aFormat); + void CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset, + int32_t width, int32_t height, + gfx::SurfaceFormat format); void EndImageFrame(); // Check if PNG is valid ICO (32bpp RGBA) // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx bool IsValidICO() const { // If there are errors in the call to png_get_IHDR, the error_callback in // nsPNGDecoder.cpp is called. In this error callback we do a longjmp, so
--- a/image/src/DecodePool.cpp +++ b/image/src/DecodePool.cpp @@ -7,16 +7,17 @@ #include <algorithm> #include "mozilla/ClearOnShutdown.h" #include "nsAutoPtr.h" #include "nsCOMPtr.h" #include "nsIObserverService.h" #include "nsIThreadPool.h" +#include "nsProxyRelease.h" #include "nsXPCOMCIDInternal.h" #include "prsystem.h" #ifdef MOZ_NUWA_PROCESS #include "ipc/Nuwa.h" #endif #include "gfxPrefs.h" @@ -36,98 +37,112 @@ namespace image { class NotifyProgressWorker : public nsRunnable { public: /** * Called by the DecodePool when it's done some significant portion of * decoding, so that progress can be recorded and notifications can be sent. */ - static void Dispatch(RasterImage* aImage, - Progress aProgress, - const nsIntRect& aInvalidRect, - uint32_t aFlags) + static void Dispatch(RasterImage* aImage) { - MOZ_ASSERT(aImage); - - nsCOMPtr<nsIRunnable> worker = - new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aFlags); + nsCOMPtr<nsIRunnable> worker = new NotifyProgressWorker(aImage); NS_DispatchToMainThread(worker); } NS_IMETHOD Run() MOZ_OVERRIDE { MOZ_ASSERT(NS_IsMainThread()); - mImage->NotifyProgress(mProgress, mInvalidRect, mFlags); + ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor); + + mImage->FinishedSomeDecoding(ShutdownReason::DONE); + return NS_OK; } private: - NotifyProgressWorker(RasterImage* aImage, Progress aProgress, - const nsIntRect& aInvalidRect, uint32_t aFlags) + explicit NotifyProgressWorker(RasterImage* aImage) : mImage(aImage) - , mProgress(aProgress) - , mInvalidRect(aInvalidRect) - , mFlags(aFlags) { } nsRefPtr<RasterImage> mImage; - const Progress mProgress; - const nsIntRect mInvalidRect; - const uint32_t mFlags; -}; - -class NotifyDecodeCompleteWorker : public nsRunnable -{ -public: - /** - * Called by the DecodePool when decoding is complete, so that final cleanup - * can be performed. - */ - static void Dispatch(Decoder* aDecoder) - { - MOZ_ASSERT(aDecoder); - - nsCOMPtr<nsIRunnable> worker = new NotifyDecodeCompleteWorker(aDecoder); - NS_DispatchToMainThread(worker); - } - - NS_IMETHOD Run() MOZ_OVERRIDE - { - MOZ_ASSERT(NS_IsMainThread()); - DecodePool::Singleton()->NotifyDecodeComplete(mDecoder); - return NS_OK; - } - -private: - explicit NotifyDecodeCompleteWorker(Decoder* aDecoder) - : mDecoder(aDecoder) - { } - - nsRefPtr<Decoder> mDecoder; }; class DecodeWorker : public nsRunnable { public: - explicit DecodeWorker(Decoder* aDecoder) - : mDecoder(aDecoder) - { - MOZ_ASSERT(mDecoder); - } + explicit DecodeWorker(RasterImage* aImage) + : mImage(aImage) + { } NS_IMETHOD Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); - DecodePool::Singleton()->Decode(mDecoder); + + ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor); + + // If we were interrupted, we shouldn't do any work. + if (mImage->mDecodeStatus == DecodeStatus::STOPPED) { + NotifyProgressWorker::Dispatch(mImage); + return NS_OK; + } + + // If someone came along and synchronously decoded us, there's nothing for us to do. + if (!mImage->mDecoder || mImage->IsDecodeFinished()) { + NotifyProgressWorker::Dispatch(mImage); + return NS_OK; + } + + mImage->mDecodeStatus = DecodeStatus::ACTIVE; + + size_t oldByteCount = mImage->mDecoder->BytesDecoded(); + + size_t maxBytes = mImage->mSourceData.Length() - + mImage->mDecoder->BytesDecoded(); + DecodePool::Singleton()->DecodeSomeOfImage(mImage, DecodeUntil::DONE_BYTES, + maxBytes); + + size_t bytesDecoded = mImage->mDecoder->BytesDecoded() - oldByteCount; + + mImage->mDecodeStatus = DecodeStatus::WORK_DONE; + + if (mImage->mDecoder && + !mImage->mError && + !mImage->mPendingError && + !mImage->IsDecodeFinished() && + bytesDecoded < maxBytes && + bytesDecoded > 0) { + // We aren't finished decoding, and we have more data, so add this request + // to the back of the list. + DecodePool::Singleton()->RequestDecode(mImage); + } else { + // Nothing more for us to do - let everyone know what happened. + NotifyProgressWorker::Dispatch(mImage); + } + return NS_OK; } +protected: + virtual ~DecodeWorker() + { + // Dispatch mImage to main thread to prevent mImage from being destructed by decode thread. + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!"); + if (mainThread) { + // Handle ambiguous nsISupports inheritance + RasterImage* rawImg = nullptr; + mImage.swap(rawImg); + DebugOnly<nsresult> rv = NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg)); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread"); + } + } + private: - nsRefPtr<Decoder> mDecoder; + nsRefPtr<RasterImage> mImage; }; #ifdef MOZ_NUWA_PROCESS class RIDThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener { public: NS_DECL_THREADSAFE_ISUPPORTS @@ -174,16 +189,23 @@ DecodePool::Singleton() MOZ_ASSERT(NS_IsMainThread()); sSingleton = new DecodePool(); ClearOnShutdown(&sSingleton); } return sSingleton; } +already_AddRefed<nsIEventTarget> +DecodePool::GetEventTarget() +{ + nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool); + return target.forget(); +} + DecodePool::DecodePool() : mThreadPoolMutex("Thread Pool") { mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID); MOZ_RELEASE_ASSERT(mThreadPool, "Should succeed in creating image decoding thread pool"); mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder")); @@ -231,111 +253,166 @@ DecodePool::Observe(nsISupports*, const if (threadPool) { threadPool->Shutdown(); } return NS_OK; } void -DecodePool::AsyncDecode(Decoder* aDecoder) +DecodePool::RequestDecode(RasterImage* aImage) { - MOZ_ASSERT(aDecoder); + MOZ_ASSERT(aImage->mDecoder); + aImage->mDecodingMonitor.AssertCurrentThreadIn(); - nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aDecoder); + if (aImage->mDecodeStatus == DecodeStatus::PENDING || + aImage->mDecodeStatus == DecodeStatus::ACTIVE) { + // The image is already in our list of images to decode, or currently being + // decoded, so we don't have to do anything else. + return; + } + + aImage->mDecodeStatus = DecodeStatus::PENDING; + nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aImage); // Dispatch to the thread pool if it exists. If it doesn't, we're currently // shutting down, so it's OK to just drop the job on the floor. MutexAutoLock threadPoolLock(mThreadPoolMutex); if (mThreadPool) { mThreadPool->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL); } } void -DecodePool::SyncDecodeIfSmall(Decoder* aDecoder) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(aDecoder); - - if (aDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime())) { - Decode(aDecoder); - return; - } - - AsyncDecode(aDecoder); -} - -void -DecodePool::SyncDecodeIfPossible(Decoder* aDecoder) +DecodePool::DecodeABitOf(RasterImage* aImage) { MOZ_ASSERT(NS_IsMainThread()); - Decode(aDecoder); -} + aImage->mDecodingMonitor.AssertCurrentThreadIn(); -already_AddRefed<nsIEventTarget> -DecodePool::GetEventTarget() -{ - MutexAutoLock threadPoolLock(mThreadPoolMutex); - nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool); - return target.forget(); -} + // If the image is waiting for decode work to be notified, go ahead and do that. + if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) { + aImage->FinishedSomeDecoding(); + } + + DecodeSomeOfImage(aImage); -already_AddRefed<nsIRunnable> -DecodePool::CreateDecodeWorker(Decoder* aDecoder) -{ - MOZ_ASSERT(aDecoder); - nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aDecoder); - return worker.forget(); -} + aImage->FinishedSomeDecoding(); -void -DecodePool::Decode(Decoder* aDecoder) -{ - MOZ_ASSERT(aDecoder); - - nsresult rv = aDecoder->Decode(); - - if (NS_SUCCEEDED(rv) && !aDecoder->GetDecodeDone()) { - if (aDecoder->HasProgress()) { - NotifyProgress(aDecoder); - } - // The decoder will ensure that a new worker gets enqueued to continue - // decoding when more data is available. - } else { - NotifyDecodeComplete(aDecoder); + // If we aren't yet finished decoding and we have more data in hand, add + // this request to the back of the priority list. + if (aImage->mDecoder && + !aImage->mError && + !aImage->IsDecodeFinished() && + aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded()) { + RequestDecode(aImage); } } -void -DecodePool::NotifyProgress(Decoder* aDecoder) +/* static */ void +DecodePool::StopDecoding(RasterImage* aImage) { - MOZ_ASSERT(aDecoder); + aImage->mDecodingMonitor.AssertCurrentThreadIn(); + + // If we haven't got a decode request, we're not currently decoding. (Having + // a decode request doesn't imply we *are* decoding, though.) + aImage->mDecodeStatus = DecodeStatus::STOPPED; +} + +nsresult +DecodePool::DecodeUntilSizeAvailable(RasterImage* aImage) +{ + MOZ_ASSERT(NS_IsMainThread()); + ReentrantMonitorAutoEnter lock(aImage->mDecodingMonitor); + + // If the image is waiting for decode work to be notified, go ahead and do that. + if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) { + nsresult rv = aImage->FinishedSomeDecoding(); + if (NS_FAILED(rv)) { + aImage->DoError(); + return rv; + } + } - if (!NS_IsMainThread()) { - NotifyProgressWorker::Dispatch(aDecoder->GetImage(), - aDecoder->TakeProgress(), - aDecoder->TakeInvalidRect(), - aDecoder->GetDecodeFlags()); - return; + nsresult rv = DecodeSomeOfImage(aImage, DecodeUntil::SIZE); + if (NS_FAILED(rv)) { + return rv; + } + + return aImage->FinishedSomeDecoding(); +} + +nsresult +DecodePool::DecodeSomeOfImage(RasterImage* aImage, + DecodeUntil aDecodeUntil /* = DecodeUntil::TIME */, + uint32_t bytesToDecode /* = 0 */) +{ + MOZ_ASSERT(aImage->mInitialized, "Worker active for uninitialized container"); + aImage->mDecodingMonitor.AssertCurrentThreadIn(); + + // If an error is flagged, it probably happened while we were waiting + // in the event queue. + if (aImage->mError) { + return NS_OK; + } + + // If there is an error worker pending (say because the main thread has enqueued + // another decode request for us before processing the error worker) then bail out. + if (aImage->mPendingError) { + return NS_OK; } - aDecoder->GetImage()->NotifyProgress(aDecoder->TakeProgress(), - aDecoder->TakeInvalidRect(), - aDecoder->GetDecodeFlags()); -} + // If mDecoded or we don't have a decoder, we must have finished already (for + // example, a synchronous decode request came while the worker was pending). + if (!aImage->mDecoder || aImage->mDecoded) { + return NS_OK; + } + + nsRefPtr<Decoder> decoderKungFuDeathGrip = aImage->mDecoder; -void -DecodePool::NotifyDecodeComplete(Decoder* aDecoder) -{ - MOZ_ASSERT(aDecoder); + uint32_t maxBytes; + if (aImage->mDecoder->IsSizeDecode()) { + // Decode all available data if we're a size decode; they're cheap, and we + // want them to be more or less synchronous. + maxBytes = aImage->mSourceData.Length(); + } else { + // We're only guaranteed to decode this many bytes, so in particular, + // gfxPrefs::ImageMemDecodeBytesAtATime should be set high enough for us + // to read the size from most images. + maxBytes = gfxPrefs::ImageMemDecodeBytesAtATime(); + } - if (!NS_IsMainThread()) { - NotifyDecodeCompleteWorker::Dispatch(aDecoder); - return; + if (bytesToDecode == 0) { + bytesToDecode = aImage->mSourceData.Length() - aImage->mDecoder->BytesDecoded(); } - aDecoder->Finish(); - aDecoder->GetImage()->FinalizeDecoder(aDecoder); + TimeStamp deadline = TimeStamp::Now() + + TimeDuration::FromMilliseconds(gfxPrefs::ImageMemMaxMSBeforeYield()); + + // We keep decoding chunks until: + // * we don't have any data left to decode, + // * the decode completes, + // * we're an DecodeUntil::SIZE decode and we get the size, or + // * we run out of time. + while (aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded() && + bytesToDecode > 0 && + !aImage->IsDecodeFinished() && + !(aDecodeUntil == DecodeUntil::SIZE && aImage->mHasSize)) { + uint32_t chunkSize = min(bytesToDecode, maxBytes); + nsresult rv = aImage->DecodeSomeData(chunkSize); + if (NS_FAILED(rv)) { + aImage->DoError(); + return rv; + } + + bytesToDecode -= chunkSize; + + // Yield if we've been decoding for too long. We check this _after_ decoding + // a chunk to ensure that we don't yield without doing any decoding. + if (aDecodeUntil == DecodeUntil::TIME && TimeStamp::Now() >= deadline) { + break; + } + } + + return NS_OK; } } // namespace image } // namespace mozilla
--- a/image/src/DecodePool.h +++ b/image/src/DecodePool.h @@ -20,80 +20,110 @@ class nsIThreadPool; namespace mozilla { namespace image { class Decoder; class RasterImage; +MOZ_BEGIN_ENUM_CLASS(DecodeStatus, uint8_t) + INACTIVE, + PENDING, + ACTIVE, + WORK_DONE, + STOPPED +MOZ_END_ENUM_CLASS(DecodeStatus) + +MOZ_BEGIN_ENUM_CLASS(DecodeUntil, uint8_t) + TIME, + SIZE, + DONE_BYTES +MOZ_END_ENUM_CLASS(DecodeUntil) + +MOZ_BEGIN_ENUM_CLASS(ShutdownReason, uint8_t) + DONE, + NOT_NEEDED, + FATAL_ERROR +MOZ_END_ENUM_CLASS(ShutdownReason) + + /** - * DecodePool is a singleton class that manages decoding of raster images. It - * owns a pool of image decoding threads that are used for asynchronous - * decoding. + * DecodePool is a singleton class we use when decoding large images. * - * DecodePool allows callers to run a decoder, handling management of the - * decoder's lifecycle and whether it executes on the main thread, - * off-main-thread in the image decoding thread pool, or on some combination of - * the two. + * When we wish to decode an image larger than + * image.mem.max_bytes_for_sync_decode, we call DecodePool::RequestDecode() + * for the image. This adds the image to a queue of pending requests and posts + * the DecodePool singleton to the event queue, if it's not already pending + * there. + * + * When the DecodePool is run from the event queue, it decodes the image (and + * all others it's managing) in chunks, periodically yielding control back to + * the event loop. */ class DecodePool : public nsIObserver { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIOBSERVER static DecodePool* Singleton(); - /// Ask the DecodePool to run @aDecoder asynchronously and return immediately. - void AsyncDecode(Decoder* aDecoder); + /** + * Ask the DecodePool to asynchronously decode this image. + */ + void RequestDecode(RasterImage* aImage); /** - * Run @aDecoder synchronously if the image it's decoding is small. If the - * image is too large, or if the source data isn't complete yet, run @aDecoder - * asynchronously instead. + * Decode aImage for a short amount of time, and post the remainder to the + * queue. */ - void SyncDecodeIfSmall(Decoder* aDecoder); + void DecodeABitOf(RasterImage* aImage); /** - * Run aDecoder synchronously if at all possible. If it can't complete - * synchronously because the source data isn't complete, asynchronously decode - * the rest. + * Ask the DecodePool to stop decoding this image. Internally, we also + * call this function when we finish decoding an image. + * + * Since the DecodePool keeps raw pointers to RasterImages, make sure you + * call this before a RasterImage is destroyed! */ - void SyncDecodeIfPossible(Decoder* aDecoder); + static void StopDecoding(RasterImage* aImage); /** - * Returns an event target interface to the DecodePool's underlying thread - * pool. Callers can use this event target to submit work to the image - * decoding thread pool. + * Synchronously decode the beginning of the image until we run out of + * bytes or we get the image's size. Note that this done on a best-effort + * basis; if the size is burried too deep in the image, we'll give up. * - * @return An nsIEventTarget interface to the thread pool. + * @return NS_ERROR if an error is encountered, and NS_OK otherwise. (Note + * that we return NS_OK even when the size was not found.) + */ + nsresult DecodeUntilSizeAvailable(RasterImage* aImage); + + /** + * Returns an event target interface to the thread pool; primarily for + * OnDataAvailable delivery off main thread. + * + * @return An nsIEventTarget interface to mThreadPool. */ already_AddRefed<nsIEventTarget> GetEventTarget(); /** - * Creates a worker which can be used to attempt further decoding using the - * provided decoder. - * - * @return The new worker, which should be posted to the event target returned - * by GetEventTarget. + * Decode some chunks of the given image. If aDecodeUntil is SIZE, + * decode until we have the image's size, then stop. If bytesToDecode is + * non-0, at most bytesToDecode bytes will be decoded. if aDecodeUntil is + * DONE_BYTES, decode until all bytesToDecode bytes are decoded. */ - already_AddRefed<nsIRunnable> CreateDecodeWorker(Decoder* aDecoder); + nsresult DecodeSomeOfImage(RasterImage* aImage, + DecodeUntil aDecodeUntil = DecodeUntil::TIME, + uint32_t bytesToDecode = 0); private: - friend class DecodeWorker; - friend class NotifyDecodeCompleteWorker; - DecodePool(); virtual ~DecodePool(); - void Decode(Decoder* aDecoder); - void NotifyDecodeComplete(Decoder* aDecoder); - void NotifyProgress(Decoder* aDecoder); - static StaticRefPtr<DecodePool> sSingleton; // mThreadPoolMutex protects mThreadPool. For all RasterImages R, // R::mDecodingMonitor must be acquired before mThreadPoolMutex // if both are acquired; the other order may cause deadlock. Mutex mThreadPoolMutex; nsCOMPtr<nsIThreadPool> mThreadPool; };
--- a/image/src/Decoder.cpp +++ b/image/src/Decoder.cpp @@ -2,314 +2,343 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Decoder.h" #include "mozilla/gfx/2D.h" -#include "DecodePool.h" -#include "GeckoProfiler.h" #include "imgIContainer.h" #include "nsIConsoleService.h" #include "nsIScriptError.h" -#include "nsProxyRelease.h" +#include "GeckoProfiler.h" #include "nsServiceManagerUtils.h" #include "nsComponentManagerUtils.h" using mozilla::gfx::IntSize; using mozilla::gfx::SurfaceFormat; namespace mozilla { namespace image { -Decoder::Decoder(RasterImage* aImage) +Decoder::Decoder(RasterImage &aImage) : mImage(aImage) , mProgress(NoProgress) , mImageData(nullptr) , mColormap(nullptr) , mChunkCount(0) , mDecodeFlags(0) , mBytesDecoded(0) - , mSendPartialInvalidations(false) - , mDataDone(false) , mDecodeDone(false) , mDataError(false) - , mDecodeAborted(false) - , mImageIsTransient(false) , mFrameCount(0) , mFailCode(NS_OK) + , mNeedsNewFrame(false) + , mNeedsToFlushData(false) , mInitialized(false) , mSizeDecode(false) , mInFrame(false) , mIsAnimated(false) { } Decoder::~Decoder() { MOZ_ASSERT(mProgress == NoProgress, "Destroying Decoder without taking all its progress changes"); MOZ_ASSERT(mInvalidRect.IsEmpty(), "Destroying Decoder without taking all its invalidations"); mInitialized = false; - - if (!NS_IsMainThread()) { - // Dispatch mImage to main thread to prevent it from being destructed by the - // decode thread. - nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); - NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!"); - if (mainThread) { - // Handle ambiguous nsISupports inheritance. - RasterImage* rawImg = nullptr; - mImage.swap(rawImg); - DebugOnly<nsresult> rv = - NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg)); - MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread"); - } - } } /* * Common implementation of the decoder interface. */ void Decoder::Init() { // No re-initializing - MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!"); + NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!"); // Fire OnStartDecode at init time to support bug 512435. if (!IsSizeDecode()) { mProgress |= FLAG_DECODE_STARTED | FLAG_ONLOAD_BLOCKED; } // Implementation-specific initialization InitInternal(); mInitialized = true; } -nsresult -Decoder::Decode() +// Initializes a decoder whose image and observer is already being used by a +// parent decoder +void +Decoder::InitSharedDecoder(uint8_t* aImageData, uint32_t aImageDataLength, + uint32_t* aColormap, uint32_t aColormapSize, + RawAccessFrameRef&& aFrameRef) { - MOZ_ASSERT(mInitialized, "Should be initialized here"); - MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator"); - - // We keep decoding chunks until the decode completes or there are no more - // chunks available. - while (!GetDecodeDone() && !HasError()) { - auto newState = mIterator->AdvanceOrScheduleResume(this); + // No re-initializing + NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!"); - if (newState == SourceBufferIterator::WAITING) { - // We can't continue because the rest of the data hasn't arrived from the - // network yet. We don't have to do anything special; the - // SourceBufferIterator will ensure that Decode() gets called again on a - // DecodePool thread when more data is available. - return NS_OK; - } - - if (newState == SourceBufferIterator::COMPLETE) { - mDataDone = true; + mImageData = aImageData; + mImageDataLength = aImageDataLength; + mColormap = aColormap; + mColormapSize = aColormapSize; + mCurrentFrame = Move(aFrameRef); - nsresult finalStatus = mIterator->CompletionStatus(); - if (NS_FAILED(finalStatus)) { - PostDataError(); - } - - return finalStatus; - } - - MOZ_ASSERT(newState == SourceBufferIterator::READY); - - Write(mIterator->Data(), mIterator->Length()); + // We have all the frame data, so we've started the frame. + if (!IsSizeDecode()) { + mFrameCount++; + PostFrameStart(); } - return HasError() ? NS_ERROR_FAILURE : NS_OK; -} - -void -Decoder::Resume() -{ - DecodePool* decodePool = DecodePool::Singleton(); - MOZ_ASSERT(decodePool); - - nsCOMPtr<nsIEventTarget> target = decodePool->GetEventTarget(); - if (MOZ_UNLIKELY(!target)) { - // We're shutting down and the DecodePool's thread pool has been destroyed. - return; - } - - nsCOMPtr<nsIRunnable> worker = decodePool->CreateDecodeWorker(this); - target->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL); -} - -bool -Decoder::ShouldSyncDecode(size_t aByteLimit) -{ - MOZ_ASSERT(aByteLimit > 0); - MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator"); - - return mIterator->RemainingBytesIsNoMoreThan(aByteLimit); + // Implementation-specific initialization + InitInternal(); + mInitialized = true; } void Decoder::Write(const char* aBuffer, uint32_t aCount) { PROFILER_LABEL("ImageDecoder", "Write", js::ProfileEntry::Category::GRAPHICS); - MOZ_ASSERT(aBuffer, "Should have a buffer"); - MOZ_ASSERT(aCount > 0, "Should have a non-zero count"); - // We're strict about decoder errors MOZ_ASSERT(!HasDecoderError(), "Not allowed to make more decoder calls after error!"); // Begin recording telemetry data. TimeStamp start = TimeStamp::Now(); mChunkCount++; // Keep track of the total number of bytes written. mBytesDecoded += aCount; + // If we're flushing data, clear the flag. + if (aBuffer == nullptr && aCount == 0) { + MOZ_ASSERT(mNeedsToFlushData, "Flushing when we don't need to"); + mNeedsToFlushData = false; + } + // If a data error occured, just ignore future data. if (HasDataError()) return; if (IsSizeDecode() && HasSize()) { // More data came in since we found the size. We have nothing to do here. return; } + MOZ_ASSERT(!NeedsNewFrame() || HasDataError(), + "Should not need a new frame before writing anything"); + MOZ_ASSERT(!NeedsToFlushData() || HasDataError(), + "Should not need to flush data before writing anything"); + // Pass the data along to the implementation. WriteInternal(aBuffer, aCount); + // If we need a new frame to proceed, let's create one and call it again. + while (NeedsNewFrame() && !HasDataError()) { + MOZ_ASSERT(!IsSizeDecode(), "Shouldn't need new frame for size decode"); + + nsresult rv = AllocateFrame(); + + if (NS_SUCCEEDED(rv)) { + // Use the data we saved when we asked for a new frame. + WriteInternal(nullptr, 0); + } + + mNeedsToFlushData = false; + } + // Finish telemetry. mDecodeTime += (TimeStamp::Now() - start); } void -Decoder::Finish() +Decoder::Finish(ShutdownReason aReason) { MOZ_ASSERT(NS_IsMainThread()); // Implementation-specific finalization if (!HasError()) FinishInternal(); // If the implementation left us mid-frame, finish that up. if (mInFrame && !HasError()) PostFrameStop(); - // If PostDecodeDone() has not been called, and this decoder wasn't aborted - // early because of low-memory conditions or losing a race with another - // decoder, we need to send teardown notifications. - if (!IsSizeDecode() && !mDecodeDone && !WasAborted()) { + // If PostDecodeDone() has not been called, we need to sent teardown + // notifications. + if (!IsSizeDecode() && !mDecodeDone) { // Log data errors to the error console nsCOMPtr<nsIConsoleService> consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID); nsCOMPtr<nsIScriptError> errorObject = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); if (consoleService && errorObject && !HasDecoderError()) { nsAutoString msg(NS_LITERAL_STRING("Image corrupt or truncated: ") + - NS_ConvertUTF8toUTF16(mImage->GetURIString())); + NS_ConvertUTF8toUTF16(mImage.GetURIString())); if (NS_SUCCEEDED(errorObject->InitWithWindowID( msg, - NS_ConvertUTF8toUTF16(mImage->GetURIString()), + NS_ConvertUTF8toUTF16(mImage.GetURIString()), EmptyString(), 0, 0, nsIScriptError::errorFlag, - "Image", mImage->InnerWindowID() + "Image", mImage.InnerWindowID() ))) { consoleService->LogMessage(errorObject); } } - // If we only have a data error, we're usable if we have at least one - // complete frame. - if (!HasDecoderError() && GetCompleteFrameCount() > 0) { - // We're usable, so do exactly what we should have when the decoder - // completed. + bool usable = !HasDecoderError(); + if (aReason != ShutdownReason::NOT_NEEDED && !HasDecoderError()) { + // If we only have a data error, we're usable if we have at least one complete frame. + if (GetCompleteFrameCount() == 0) { + usable = false; + } + } + + // If we're usable, do exactly what we should have when the decoder + // completed. + if (usable) { if (mInFrame) { PostFrameStop(); } PostDecodeDone(); } else { - // We're not usable. Record some final progress indicating the error. if (!IsSizeDecode()) { mProgress |= FLAG_DECODE_COMPLETE | FLAG_ONLOAD_UNBLOCKED; } mProgress |= FLAG_HAS_ERROR; } } // Set image metadata before calling DecodingComplete, because // DecodingComplete calls Optimize(). - mImageMetadata.SetOnImage(mImage); + mImageMetadata.SetOnImage(&mImage); - if (HasSize()) { - SetSizeOnImage(); + if (mDecodeDone) { + MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame"); + mImage.DecodingComplete(mCurrentFrame.get()); } - - if (mDecodeDone && !IsSizeDecode()) { - MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame"); +} - // If this image wasn't animated and isn't a transient image, mark its frame - // as optimizable. We don't support optimizing animated images and - // optimizing transient images isn't worth it. - if (!mIsAnimated && !mImageIsTransient && mCurrentFrame) { - mCurrentFrame->SetOptimizable(); - } +void +Decoder::FinishSharedDecoder() +{ + MOZ_ASSERT(NS_IsMainThread()); - mImage->OnDecodingComplete(); + if (!HasError()) { + FinishInternal(); } } nsresult -Decoder::AllocateFrame(uint32_t aFrameNum, - const nsIntRect& aFrameRect, - gfx::SurfaceFormat aFormat, - uint8_t aPaletteDepth /* = 0 */) +Decoder::AllocateFrame() { - mCurrentFrame = AllocateFrameInternal(aFrameNum, aFrameRect, mDecodeFlags, - aFormat, aPaletteDepth, - mCurrentFrame.get()); + MOZ_ASSERT(mNeedsNewFrame); + + mCurrentFrame = EnsureFrame(mNewFrameData.mFrameNum, + mNewFrameData.mFrameRect, + mDecodeFlags, + mNewFrameData.mFormat, + mNewFrameData.mPaletteDepth, + mCurrentFrame.get()); if (mCurrentFrame) { // Gather the raw pointers the decoders will use. mCurrentFrame->GetImageData(&mImageData, &mImageDataLength); mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize); - if (aFrameNum + 1 == mFrameCount) { + if (mNewFrameData.mFrameNum + 1 == mFrameCount) { PostFrameStart(); } } else { PostDataError(); } + // Mark ourselves as not needing another frame before talking to anyone else + // so they can tell us if they need yet another. + mNeedsNewFrame = false; + + // If we've received any data at all, we may have pending data that needs to + // be flushed now that we have a frame to decode into. + if (mBytesDecoded > 0) { + mNeedsToFlushData = true; + } + return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE; } RawAccessFrameRef -Decoder::AllocateFrameInternal(uint32_t aFrameNum, - const nsIntRect& aFrameRect, - uint32_t aDecodeFlags, - SurfaceFormat aFormat, - uint8_t aPaletteDepth, - imgFrame* aPreviousFrame) +Decoder::EnsureFrame(uint32_t aFrameNum, + const nsIntRect& aFrameRect, + uint32_t aDecodeFlags, + SurfaceFormat aFormat, + uint8_t aPaletteDepth, + imgFrame* aPreviousFrame) { if (mDataError || NS_FAILED(mFailCode)) { return RawAccessFrameRef(); } - if (aFrameNum != mFrameCount) { - MOZ_ASSERT_UNREACHABLE("Allocating frames out of order"); + MOZ_ASSERT(aFrameNum <= mFrameCount, "Invalid frame index!"); + if (aFrameNum > mFrameCount) { + return RawAccessFrameRef(); + } + + // Adding a frame that doesn't already exist. This is the normal case. + if (aFrameNum == mFrameCount) { + return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat, + aPaletteDepth, aPreviousFrame); + } + + // We're replacing a frame. It must be the first frame; there's no reason to + // ever replace any other frame, since the first frame is the only one we + // speculatively allocate without knowing what the decoder really needs. + // XXX(seth): I'm not convinced there's any reason to support this at all. We + // should figure out how to avoid triggering this and rip it out. + MOZ_ASSERT(aFrameNum == 0, "Replacing a frame other than the first?"); + MOZ_ASSERT(mFrameCount == 1, "Should have only one frame"); + MOZ_ASSERT(aPreviousFrame, "Need the previous frame to replace"); + if (aFrameNum != 0 || !aPreviousFrame || mFrameCount != 1) { + return RawAccessFrameRef(); + } + + MOZ_ASSERT(!aPreviousFrame->GetRect().IsEqualEdges(aFrameRect) || + aPreviousFrame->GetFormat() != aFormat || + aPreviousFrame->GetPaletteDepth() != aPaletteDepth, + "Replacing first frame with the same kind of frame?"); + + // Remove the old frame from the SurfaceCache. + IntSize prevFrameSize = aPreviousFrame->GetImageSize(); + SurfaceCache::RemoveSurface(ImageKey(&mImage), + RasterSurfaceKey(prevFrameSize, aDecodeFlags, 0)); + mFrameCount = 0; + mInFrame = false; + + // Add the new frame as usual. + return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat, + aPaletteDepth, nullptr); +} + +RawAccessFrameRef +Decoder::InternalAddFrame(uint32_t aFrameNum, + const nsIntRect& aFrameRect, + uint32_t aDecodeFlags, + SurfaceFormat aFormat, + uint8_t aPaletteDepth, + imgFrame* aPreviousFrame) +{ + MOZ_ASSERT(aFrameNum <= mFrameCount, "Invalid frame index!"); + if (aFrameNum > mFrameCount) { return RawAccessFrameRef(); } MOZ_ASSERT(mImageMetadata.HasSize()); nsIntSize imageSize(mImageMetadata.GetWidth(), mImageMetadata.GetHeight()); if (imageSize.width <= 0 || imageSize.height <= 0 || aFrameRect.width <= 0 || aFrameRect.height <= 0) { NS_WARNING("Trying to add frame with zero or negative size"); @@ -327,34 +356,26 @@ Decoder::AllocateFrameInternal(uint32_t if (NS_FAILED(frame->InitForDecoder(imageSize, aFrameRect, aFormat, aPaletteDepth, nonPremult))) { NS_WARNING("imgFrame::Init should succeed"); return RawAccessFrameRef(); } RawAccessFrameRef ref = frame->RawAccessRef(); if (!ref) { - frame->Abort(); return RawAccessFrameRef(); } - InsertOutcome outcome = - SurfaceCache::Insert(frame, ImageKey(mImage.get()), + bool succeeded = + SurfaceCache::Insert(frame, ImageKey(&mImage), RasterSurfaceKey(imageSize.ToIntSize(), aDecodeFlags, aFrameNum), Lifetime::Persistent); - if (outcome != InsertOutcome::SUCCESS) { - // We either hit InsertOutcome::FAILURE, which is a temporary failure due to - // low memory (we know it's not permanent because we checked CanHold() - // above), or InsertOutcome::FAILURE_ALREADY_PRESENT, which means that - // another decoder beat us to decoding this frame. Either way, we should - // abort this decoder rather than treat this as a real error. - mDecodeAborted = true; - ref->Abort(); + if (!succeeded) { return RawAccessFrameRef(); } nsIntRect refreshArea; if (aFrameNum == 1) { MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated"); aPreviousFrame->SetRawAccessOnly(); @@ -374,33 +395,30 @@ Decoder::AllocateFrameInternal(uint32_t ref->SetRawAccessOnly(); // Some GIFs are huge but only have a small area that they animate. We only // need to refresh that small area when frame 0 comes around again. refreshArea.UnionRect(refreshArea, frame->GetRect()); } mFrameCount++; - mImage->OnAddedFrame(mFrameCount, refreshArea); + mImage.OnAddedFrame(mFrameCount, refreshArea); return ref; } void Decoder::SetSizeOnImage() { MOZ_ASSERT(mImageMetadata.HasSize(), "Should have size"); MOZ_ASSERT(mImageMetadata.HasOrientation(), "Should have orientation"); - nsresult rv = mImage->SetSize(mImageMetadata.GetWidth(), - mImageMetadata.GetHeight(), - mImageMetadata.GetOrientation()); - if (NS_FAILED(rv)) { - PostResizeError(); - } + mImage.SetSize(mImageMetadata.GetWidth(), + mImageMetadata.GetHeight(), + mImageMetadata.GetOrientation()); } /* * Hook stubs. Override these as necessary in decoder implementations. */ void Decoder::InitInternal() { } void Decoder::WriteInternal(const char* aBuffer, uint32_t aCount) { } @@ -460,37 +478,28 @@ Decoder::PostFrameStop(Opacity aFrameOpa MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one"); // Update our state mInFrame = false; mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod); mProgress |= FLAG_FRAME_COMPLETE | FLAG_ONLOAD_UNBLOCKED; - - // If we're not sending partial invalidations, then we send an invalidation - // here when the first frame is complete. - if (!mSendPartialInvalidations && !mIsAnimated) { - mInvalidRect.UnionRect(mInvalidRect, mCurrentFrame->GetRect()); - } } void Decoder::PostInvalidation(nsIntRect& aRect) { // We should be mid-frame NS_ABORT_IF_FALSE(mInFrame, "Can't invalidate when not mid-frame!"); NS_ABORT_IF_FALSE(mCurrentFrame, "Can't invalidate when not mid-frame!"); - // Record this invalidation, unless we're not sending partial invalidations - // or we're past the first frame. - if (mSendPartialInvalidations && !mIsAnimated) { - mInvalidRect.UnionRect(mInvalidRect, aRect); - mCurrentFrame->ImageUpdated(aRect); - } + // Account for the new region + mInvalidRect.UnionRect(mInvalidRect, aRect); + mCurrentFrame->ImageUpdated(aRect); } void Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */) { NS_ABORT_IF_FALSE(!IsSizeDecode(), "Can't be done with decoding with size decode!"); NS_ABORT_IF_FALSE(!mInFrame, "Can't be done decoding if we're mid-frame!"); NS_ABORT_IF_FALSE(!mDecodeDone, "Decode already done!"); @@ -500,32 +509,42 @@ Decoder::PostDecodeDone(int32_t aLoopCou mProgress |= FLAG_DECODE_COMPLETE; } void Decoder::PostDataError() { mDataError = true; - - if (mInFrame && mCurrentFrame) { - mCurrentFrame->Abort(); - } } void Decoder::PostDecoderError(nsresult aFailureCode) { NS_ABORT_IF_FALSE(NS_FAILED(aFailureCode), "Not a failure code!"); mFailCode = aFailureCode; // XXXbholley - we should report the image URI here, but imgContainer // needs to know its URI first NS_WARNING("Image decoding error - This is probably a bug!"); +} - if (mInFrame && mCurrentFrame) { - mCurrentFrame->Abort(); - } +void +Decoder::NeedNewFrame(uint32_t framenum, uint32_t x_offset, uint32_t y_offset, + uint32_t width, uint32_t height, + gfx::SurfaceFormat format, + uint8_t palette_depth /* = 0 */) +{ + // Decoders should never call NeedNewFrame without yielding back to Write(). + MOZ_ASSERT(!mNeedsNewFrame); + + // We don't want images going back in time or skipping frames. + MOZ_ASSERT(framenum == mFrameCount || framenum == (mFrameCount - 1)); + + mNewFrameData = NewFrameData(framenum, + nsIntRect(x_offset, y_offset, width, height), + format, palette_depth); + mNeedsNewFrame = true; } } // namespace image } // namespace mozilla
--- a/image/src/Decoder.h +++ b/image/src/Decoder.h @@ -7,54 +7,76 @@ #define MOZILLA_IMAGELIB_DECODER_H_ #include "FrameAnimator.h" #include "RasterImage.h" #include "mozilla/RefPtr.h" #include "DecodePool.h" #include "ImageMetadata.h" #include "Orientation.h" -#include "SourceBuffer.h" #include "mozilla/Telemetry.h" namespace mozilla { namespace image { -class Decoder : public IResumable +class Decoder { public: - explicit Decoder(RasterImage* aImage); + explicit Decoder(RasterImage& aImage); /** * Initialize an image decoder. Decoders may not be re-initialized. + * + * Notifications Sent: TODO */ void Init(); /** - * Decodes, reading all data currently available in the SourceBuffer. If more - * data is needed, Decode() automatically ensures that it will be called again - * on a DecodePool thread when the data becomes available. + * Initializes a decoder whose image and observer is already being used by a + * parent decoder. Decoders may not be re-initialized. * - * Any errors are reported by setting the appropriate state on the decoder. + * Notifications Sent: TODO */ - nsresult Decode(); + void InitSharedDecoder(uint8_t* aImageData, uint32_t aImageDataLength, + uint32_t* aColormap, uint32_t aColormapSize, + RawAccessFrameRef&& aFrameRef); /** - * Cleans up the decoder's state and notifies our image about success or - * failure. May only be called on the main thread. + * Writes data to the decoder. + * + * If aBuffer is null and aCount is 0, Write() flushes any buffered data to + * the decoder. Data is buffered if the decoder wasn't able to completely + * decode it because it needed a new frame. If it's necessary to flush data, + * NeedsToFlushData() will return true. + * + * @param aBuffer buffer containing the data to be written + * @param aCount the number of bytes to write + * + * Any errors are reported by setting the appropriate state on the decoder. + * + * Notifications Sent: TODO */ - void Finish(); + void Write(const char* aBuffer, uint32_t aCount); /** - * Given a maximum number of bytes we're willing to decode, @aByteLimit, - * returns true if we should attempt to run this decoder synchronously. + * Informs the decoder that all the data has been written. + * + * Notifications Sent: TODO */ - bool ShouldSyncDecode(size_t aByteLimit); + void Finish(ShutdownReason aReason); + + /** + * Informs the shared decoder that all the data has been written. + * Should only be used if InitSharedDecoder was useed + * + * Notifications Sent: TODO + */ + void FinishSharedDecoder(); /** * Gets the invalidation region accumulated by the decoder so far, and clears * the decoder's invalidation region. This means that each call to * TakeInvalidRect() returns only the invalidation region accumulated since * the last call to TakeInvalidRect(). */ nsIntRect TakeInvalidRect() @@ -71,171 +93,118 @@ public: */ Progress TakeProgress() { Progress progress = mProgress; mProgress = NoProgress; return progress; } - /** - * Returns true if there's any progress to report. - */ - bool HasProgress() const - { - return mProgress != NoProgress || !mInvalidRect.IsEmpty(); - } - // We're not COM-y, so we don't get refcounts by default NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder) - // Implement IResumable. - virtual void Resume() MOZ_OVERRIDE; - /* * State. */ // If we're doing a "size decode", we more or less pass through the image // data, stopping only to scoop out the image dimensions. A size decode // must be enabled by SetSizeDecode() _before_calling Init(). bool IsSizeDecode() { return mSizeDecode; } void SetSizeDecode(bool aSizeDecode) { - MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); + NS_ABORT_IF_FALSE(!mInitialized, "Can't set size decode after Init()!"); mSizeDecode = aSizeDecode; } - /** - * Set whether should send partial invalidations. - * - * If @aSend is true, we'll send partial invalidations when decoding the first - * frame of the image, so image notifications observers will be able to - * gradually draw in the image as it downloads. - * - * If @aSend is false (the default), we'll only send an invalidation when we - * complete the first frame. - * - * This must be called before Init() is called. - */ - void SetSendPartialInvalidations(bool aSend) - { - MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); - mSendPartialInvalidations = aSend; - } - - /** - * Set an iterator to the SourceBuffer which will feed data to this decoder. - * - * This should be called for almost all decoders; the exceptions are the - * contained decoders of an nsICODecoder, which will be fed manually via Write - * instead. - * - * This must be called before Init() is called. - */ - void SetIterator(SourceBufferIterator&& aIterator) - { - MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); - mIterator.emplace(Move(aIterator)); - } - - /** - * Set whether this decoder is associated with a transient image. The decoder - * may choose to avoid certain optimizations that don't pay off for - * short-lived images in this case. - */ - void SetImageIsTransient(bool aIsTransient) - { - MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); - mImageIsTransient = aIsTransient; - } - size_t BytesDecoded() const { return mBytesDecoded; } // The amount of time we've spent inside Write() so far for this decoder. TimeDuration DecodeTime() const { return mDecodeTime; } // The number of times Write() has been called so far for this decoder. uint32_t ChunkCount() const { return mChunkCount; } // The number of frames we have, including anything in-progress. Thus, this // is only 0 if we haven't begun any frames. uint32_t GetFrameCount() { return mFrameCount; } // The number of complete frames we have (ie, not including anything in-progress). uint32_t GetCompleteFrameCount() { return mInFrame ? mFrameCount - 1 : mFrameCount; } // Error tracking - bool HasError() const { return HasDataError() || HasDecoderError(); } - bool HasDataError() const { return mDataError; } - bool HasDecoderError() const { return NS_FAILED(mFailCode); } - nsresult GetDecoderError() const { return mFailCode; } + bool HasError() { return HasDataError() || HasDecoderError(); } + bool HasDataError() { return mDataError; } + bool HasDecoderError() { return NS_FAILED(mFailCode); } + nsresult GetDecoderError() { return mFailCode; } void PostResizeError() { PostDataError(); } - - bool GetDecodeDone() const - { - return mDecodeDone || (mSizeDecode && HasSize()) || HasError() || mDataDone; + bool GetDecodeDone() const { + return mDecodeDone; } - /** - * Returns true if this decoder was aborted. - * - * This may happen due to a low-memory condition, or because another decoder - * was racing with this one to decode the same frames with the same flags and - * this decoder lost the race. Either way, this is not a permanent situation - * and does not constitute an error, so we don't report any errors when this - * happens. - */ - bool WasAborted() const { return mDecodeAborted; } - // flags. Keep these in sync with imgIContainer.idl. // SetDecodeFlags must be called before Init(), otherwise // default flags are assumed. enum { DECODER_NO_PREMULTIPLY_ALPHA = 0x2, // imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA DECODER_NO_COLORSPACE_CONVERSION = 0x4 // imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION }; enum DecodeStyle { PROGRESSIVE, // produce intermediate frames representing the partial state of the image SEQUENTIAL // decode to final image immediately }; void SetDecodeFlags(uint32_t aFlags) { mDecodeFlags = aFlags; } uint32_t GetDecodeFlags() { return mDecodeFlags; } - nsIntSize GetSize() const { return mImageMetadata.GetSize(); } bool HasSize() const { return mImageMetadata.HasSize(); } void SetSizeOnImage(); + void SetSize(const nsIntSize& aSize, const Orientation& aOrientation) + { + PostSize(aSize.width, aSize.height, aOrientation); + } + // Use HistogramCount as an invalid Histogram ID virtual Telemetry::ID SpeedHistogram() { return Telemetry::HistogramCount; } ImageMetadata& GetImageMetadata() { return mImageMetadata; } - /** - * Returns a weak pointer to the image associated with this decoder. - */ - RasterImage* GetImage() const { MOZ_ASSERT(mImage); return mImage.get(); } + // Tell the decoder infrastructure to allocate a frame. By default, frame 0 + // is created as an ARGB frame with no offset and with size width * height. + // If decoders need something different, they must ask for it. + // This is called by decoders when they need a new frame. These decoders + // must then save the data they have been sent but not yet processed and + // return from WriteInternal. When the new frame is created, WriteInternal + // will be called again with nullptr and 0 as arguments. + void NeedNewFrame(uint32_t frameNum, uint32_t x_offset, uint32_t y_offset, + uint32_t width, uint32_t height, + gfx::SurfaceFormat format, + uint8_t palette_depth = 0); + virtual bool NeedsNewFrame() const { return mNeedsNewFrame; } + + + // Try to allocate a frame as described in mNewFrameData and return the + // status code from that attempt. Clears mNewFrameData. + virtual nsresult AllocateFrame(); already_AddRefed<imgFrame> GetCurrentFrame() { nsRefPtr<imgFrame> frame = mCurrentFrame.get(); return frame.forget(); } RawAccessFrameRef GetCurrentFrameRef() { return mCurrentFrame ? mCurrentFrame->RawAccessRef() : RawAccessFrameRef(); } protected: - friend class nsICODecoder; - virtual ~Decoder(); /* * Internal hooks. Decoder implementations may override these and * only these methods. */ virtual void InitInternal(); virtual void WriteInternal(const char* aBuffer, uint32_t aCount); @@ -290,80 +259,92 @@ protected: // For animated images, specify the loop count. -1 means loop forever, 0 // means a single iteration, stopping on the last frame. void PostDecodeDone(int32_t aLoopCount = 0); // Data errors are the fault of the source data, decoder errors are our fault void PostDataError(); void PostDecoderError(nsresult aFailCode); + // Returns true if we may have stored data that we need to flush now that we + // have a new frame to decode into. Callers can use Write() to actually + // flush the data; see the documentation for that method. + bool NeedsToFlushData() const { return mNeedsToFlushData; } + /** - * Allocates a new frame, making it our new current frame if successful. - * - * The @aFrameNum parameter only exists as a sanity check; it's illegal to - * create a new frame anywhere but immediately after the existing frames. - * + * Ensures that a given frame number exists with the given parameters, and + * returns a RawAccessFrameRef for that frame. + * It is not possible to create sparse frame arrays; you can only append + * frames to the current frame array, or if there is only one frame in the + * array, replace that frame. * If a non-paletted frame is desired, pass 0 for aPaletteDepth. */ - nsresult AllocateFrame(uint32_t aFrameNum, - const nsIntRect& aFrameRect, - gfx::SurfaceFormat aFormat, - uint8_t aPaletteDepth = 0); + RawAccessFrameRef EnsureFrame(uint32_t aFrameNum, + const nsIntRect& aFrameRect, + uint32_t aDecodeFlags, + gfx::SurfaceFormat aFormat, + uint8_t aPaletteDepth, + imgFrame* aPreviousFrame); - RawAccessFrameRef AllocateFrameInternal(uint32_t aFrameNum, - const nsIntRect& aFrameRect, - uint32_t aDecodeFlags, - gfx::SurfaceFormat aFormat, - uint8_t aPaletteDepth, - imgFrame* aPreviousFrame); - - /** - * Writes data to the decoder. - * - * @param aBuffer buffer containing the data to be written - * @param aCount the number of bytes to write - * - * Any errors are reported by setting the appropriate state on the decoder. - */ - void Write(const char* aBuffer, uint32_t aCount); - + RawAccessFrameRef InternalAddFrame(uint32_t aFrameNum, + const nsIntRect& aFrameRect, + uint32_t aDecodeFlags, + gfx::SurfaceFormat aFormat, + uint8_t aPaletteDepth, + imgFrame* aPreviousFrame); /* * Member variables. * */ - nsRefPtr<RasterImage> mImage; - Maybe<SourceBufferIterator> mIterator; + RasterImage &mImage; RawAccessFrameRef mCurrentFrame; ImageMetadata mImageMetadata; nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame. Progress mProgress; uint8_t* mImageData; // Pointer to image data in either Cairo or 8bit format uint32_t mImageDataLength; uint32_t* mColormap; // Current colormap to be used in Cairo format uint32_t mColormapSize; // Telemetry data for this decoder. TimeDuration mDecodeTime; uint32_t mChunkCount; uint32_t mDecodeFlags; size_t mBytesDecoded; - bool mSendPartialInvalidations; - bool mDataDone; bool mDecodeDone; bool mDataError; - bool mDecodeAborted; - bool mImageIsTransient; private: uint32_t mFrameCount; // Number of frames, including anything in-progress nsresult mFailCode; + struct NewFrameData + { + NewFrameData() { } + + NewFrameData(uint32_t aFrameNum, const nsIntRect& aFrameRect, + gfx::SurfaceFormat aFormat, uint8_t aPaletteDepth) + : mFrameNum(aFrameNum) + , mFrameRect(aFrameRect) + , mFormat(aFormat) + , mPaletteDepth(aPaletteDepth) + { } + + uint32_t mFrameNum; + nsIntRect mFrameRect; + gfx::SurfaceFormat mFormat; + uint8_t mPaletteDepth; + }; + + NewFrameData mNewFrameData; + bool mNeedsNewFrame; + bool mNeedsToFlushData; bool mInitialized; bool mSizeDecode; bool mInFrame; bool mIsAnimated; }; } // namespace image } // namespace mozilla
--- a/image/src/FrameAnimator.cpp +++ b/image/src/FrameAnimator.cpp @@ -84,18 +84,17 @@ FrameAnimator::AdvanceFrame(TimeStamp aT int32_t timeout = 0; RefreshResult ret; RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex); // If we're done decoding, we know we've got everything we're going to get. // If we aren't, we only display fully-downloaded frames; everything else // gets delayed. - bool canDisplay = mDoneDecoding || - (nextFrame && nextFrame->IsImageComplete()); + bool canDisplay = mDoneDecoding || (nextFrame && nextFrame->ImageComplete()); if (!canDisplay) { // Uh oh, the frame we want to show is currently being decoded (partial) // Wait until the next refresh driver tick and try again return ret; } // If we're done decoding the next frame, go ahead and display it now and @@ -285,21 +284,16 @@ FrameAnimator::GetCompositedFrame(uint32 MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "About to return a paletted frame"); return ref; } int32_t FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const { RawAccessFrameRef frame = GetRawFrame(aFrameNum); - if (!frame) { - NS_WARNING("No frame; called GetTimeoutForFrame too early?"); - return 100; - } - AnimationData data = frame->GetAnimationData(); // Ensure a minimal time between updates so we don't throttle the UI thread. // consider 0 == unspecified and make it fast but not too fast. Unless we // have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug // 207059. The behavior of recent IE and Opera versions seems to be: // IE 6/Win: // 10 - 50ms go 100ms @@ -596,30 +590,32 @@ FrameAnimator::DoBlend(nsIntRect* aDirty AnimationData compositingPrevFrameData = mCompositingPrevFrame->GetAnimationData(); CopyFrameImage(compositingFrameData.mRawData, compositingFrameData.mRect, compositingPrevFrameData.mRawData, compositingPrevFrameData.mRect); - - mCompositingPrevFrame->Finish(); } // blit next frame into it's correct spot DrawFrameTo(nextFrameData.mRawData, nextFrameData.mRect, nextFrameData.mPaletteDataLength, nextFrameData.mHasAlpha, compositingFrameData.mRawData, compositingFrameData.mRect, nextFrameData.mBlendMethod); // Tell the image that it is fully 'downloaded'. - mCompositingFrame->Finish(); + nsresult rv = + mCompositingFrame->ImageUpdated(compositingFrameData.mRect); + if (NS_FAILED(rv)) { + return false; + } mLastCompositedFrameIndex = int32_t(aNextFrameIndex); return true; } //****************************************************************************** // Fill aFrame with black. Does also clears the mask.
--- a/image/src/ImageMetadata.h +++ b/image/src/ImageMetadata.h @@ -48,17 +48,16 @@ public: } } bool HasSize() const { return mSize.isSome(); } bool HasOrientation() const { return mOrientation.isSome(); } int32_t GetWidth() const { return mSize->width; } int32_t GetHeight() const { return mSize->height; } - nsIntSize GetSize() const { return *mSize; } Orientation GetOrientation() const { return *mOrientation; } private: // The hotspot found on cursors, or -1 if none was found. int32_t mHotspotX; int32_t mHotspotY; // The loop count for animated images, or -1 for infinite loop.
--- a/image/src/RasterImage.cpp +++ b/image/src/RasterImage.cpp @@ -15,17 +15,16 @@ #include "Decoder.h" #include "nsAutoPtr.h" #include "prenv.h" #include "prsystem.h" #include "ImageContainer.h" #include "ImageRegion.h" #include "Layers.h" #include "nsPresContext.h" -#include "SourceBuffer.h" #include "SurfaceCache.h" #include "FrameAnimator.h" #include "nsPNGDecoder.h" #include "nsGIFDecoder2.h" #include "nsJPEGDecoder.h" #include "nsBMPDecoder.h" #include "nsICODecoder.h" @@ -64,16 +63,30 @@ using std::min; #define DECODE_FLAGS_DEFAULT 0 static uint32_t DecodeFlags(uint32_t aFlags) { return aFlags & DECODE_FLAGS_MASK; } +/* Accounting for compressed data */ +#if defined(PR_LOGGING) +static PRLogModuleInfo * +GetCompressedImageAccountingLog() +{ + static PRLogModuleInfo *sLog; + if (!sLog) + sLog = PR_NewLogModule("CompressedImageAccounting"); + return sLog; +} +#else +#define GetCompressedImageAccountingLog() +#endif + // The maximum number of times any one RasterImage was decoded. This is only // used for statistics. static int32_t sMaxDecodeCount = 0; /* We define our own error checking macros here for 2 reasons: * * 1) Most of the failures we encounter here will (hopefully) be * the result of decoding failures (ie, bad data) and not code @@ -186,17 +199,17 @@ public: bool succeeded = gfx::Scale(srcData.mRawData, srcData.mSize.width, srcData.mSize.height, srcData.mBytesPerRow, dstData.mRawData, mDstSize.width, mDstSize.height, dstData.mBytesPerRow, srcData.mFormat); if (succeeded) { // Mark the frame as complete and discardable. mDstRef->ImageUpdated(mDstRef->GetRect()); - MOZ_ASSERT(mDstRef->IsImageComplete(), + MOZ_ASSERT(mDstRef->ImageComplete(), "Incomplete, but just updated the entire frame"); } // We need to send notifications and release our references on the main // thread, so finish up there. mState = succeeded ? eFinish : eFinishWithError; NS_DispatchToMainThread(this); } else if (mState == eFinish) { @@ -256,52 +269,61 @@ NS_IMPL_ISUPPORTS(RasterImage, imgIConta imgIContainerDebug) #endif //****************************************************************************** RasterImage::RasterImage(ProgressTracker* aProgressTracker, ImageURL* aURI /* = nullptr */) : ImageResource(aURI), // invoke superclass's constructor mSize(0,0), + mFrameDecodeFlags(DECODE_FLAGS_DEFAULT), mLockCount(0), mDecodeCount(0), mRequestedSampleSize(0), #ifdef DEBUG mFramesNotified(0), #endif - mSourceBuffer(new SourceBuffer()), + mDecodingMonitor("RasterImage Decoding Monitor"), + mDecoder(nullptr), + mDecodeStatus(DecodeStatus::INACTIVE), mFrameCount(0), mNotifyProgress(NoProgress), mNotifying(false), mHasSize(false), mDecodeOnDraw(false), mTransient(false), mDiscardable(false), mHasSourceData(false), + mDecoded(false), mHasBeenDecoded(false), mPendingAnimation(false), mAnimationFinished(false), - mWantFullDecode(false) + mWantFullDecode(false), + mPendingError(false) { mProgressTrackerInit = new ProgressTrackerInit(this, aProgressTracker); Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0); } //****************************************************************************** RasterImage::~RasterImage() { - // Make sure our SourceBuffer is marked as complete. This will ensure that any - // outstanding decoders terminate. - if (!mSourceBuffer->IsComplete()) { - mSourceBuffer->Complete(NS_ERROR_ABORT); + if (mDecoder) { + // Kill off our decode request, if it's pending. (If not, this call is + // harmless.) + ReentrantMonitorAutoEnter lock(mDecodingMonitor); + DecodePool::StopDecoding(this); + mDecoder = nullptr; } // Release all frames from the surface cache. SurfaceCache::RemoveImage(ImageKey(this)); + + mAnim = nullptr; } /* static */ void RasterImage::Initialize() { // Create our singletons now, so we don't have to worry about what thread // they're created on. DecodePool::Singleton(); @@ -334,21 +356,24 @@ RasterImage::Init(const char* aMimeType, mDecodeOnDraw = !!(aFlags & INIT_FLAG_DECODE_ON_DRAW); mTransient = !!(aFlags & INIT_FLAG_TRANSIENT); // Lock this image's surfaces in the SurfaceCache if we're not discardable. if (!mDiscardable) { SurfaceCache::LockImage(ImageKey(this)); } - // Create the initial size decoder. - nsresult rv = Decode(DecodeStrategy::ASYNC, DECODE_FLAGS_DEFAULT, - /* aDoSizeDecode = */ true); - if (NS_FAILED(rv)) { - return NS_ERROR_FAILURE; + // Instantiate the decoder + nsresult rv = InitDecoder(/* aDoSizeDecode = */ true); + CONTAINER_ENSURE_SUCCESS(rv); + + // If we aren't storing source data, we want to switch from a size decode to + // a full decode as soon as possible. + if (!StoringSourceData()) { + mWantFullDecode = true; } // Mark us as initialized mInitialized = true; return NS_OK; } @@ -375,17 +400,21 @@ RasterImage::RequestRefresh(const TimeSt if (res.frameAdvanced) { // Notify listeners that our frame has actually changed, but do this only // once for all frames that we've now passed (if AdvanceFrame() was called // more than once). #ifdef DEBUG mFramesNotified++; #endif - NotifyProgress(NoProgress, res.dirtyRect); + UpdateImageContainer(); + + if (mProgressTracker) { + mProgressTracker->SyncNotifyProgress(NoProgress, res.dirtyRect); + } } if (res.animationFinished) { mAnimationFinished = true; EvaluateAnimation(); } } @@ -511,42 +540,37 @@ RasterImage::LookupFrame(uint32_t aFrame ref = LookupFrameInternal(aFrameNum, aSize, aFlags ^ FLAG_DECODE_NO_PREMULTIPLY_ALPHA); } if (!ref) { // The OS threw this frame away. We need to redecode if we can. MOZ_ASSERT(!mAnim, "Animated frames should be locked"); + // Update our state so the decoder knows what to do. + mFrameDecodeFlags = aFlags & DECODE_FLAGS_MASK; + mDecoded = false; + mFrameCount = 0; WantDecodedFrames(aFlags, aShouldSyncNotify); - // If we were able to sync decode, we should already have the frame. If we - // had to decode asynchronously, maybe we've gotten lucky. + // See if we managed to redecode enough to get the frame we want. ref = LookupFrameInternal(aFrameNum, aSize, aFlags); if (!ref) { // We didn't successfully redecode, so just fail. return DrawableFrameRef(); } } if (ref->GetCompositingFailed()) { return DrawableFrameRef(); } MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "Should not have paletted frame"); - // Sync decoding guarantees that we got the frame, but if it's owned by an - // async decoder that's currently running, the contents of the frame may not - // be available yet. Make sure we get everything. - if (ref && mHasSourceData && aShouldSyncNotify && - (aFlags & FLAG_SYNC_DECODE)) { - ref->WaitUntilComplete(); - } - return ref; } uint32_t RasterImage::GetCurrentFrameIndex() const { if (mAnim) { return mAnim->GetCurrentAnimationFrameIndex(); @@ -845,17 +869,25 @@ RasterImage::UpdateImageContainer() } mImageContainer->SetCurrentImage(image); } size_t RasterImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const { - return mSourceBuffer->SizeOfIncludingThisWithComputedFallback(aMallocSizeOf); + // n == 0 is possible for two reasons. + // - This is a zero-length image. + // - We're on a platform where moz_malloc_size_of always returns 0. + // In either case the fallback works appropriately. + size_t n = mSourceData.SizeOfExcludingThis(aMallocSizeOf); + if (n == 0) { + n = mSourceData.Length(); + } + return n; } size_t RasterImage::SizeOfDecoded(gfxMemoryLocation aLocation, MallocSizeOf aMallocSizeOf) const { size_t n = 0; n += SurfaceCache::SizeOfSurfaces(ImageKey(this), aLocation, aMallocSizeOf); @@ -896,93 +928,106 @@ RasterImage::OnAddedFrame(uint32_t aNewF { if (!NS_IsMainThread()) { nsCOMPtr<nsIRunnable> runnable = new OnAddedFrameRunnable(this, aNewFrameCount, aNewRefreshArea); NS_DispatchToMainThread(runnable); return; } - MOZ_ASSERT(aNewFrameCount <= mFrameCount || - aNewFrameCount == mFrameCount + 1, - "Skipped a frame?"); - - if (aNewFrameCount > mFrameCount) { - mFrameCount = aNewFrameCount; - - if (aNewFrameCount == 2) { - // We're becoming animated, so initialize animation stuff. - MOZ_ASSERT(!mAnim, "Already have animation state?"); - mAnim = MakeUnique<FrameAnimator>(this, mSize.ToIntSize(), mAnimationMode); - - // We don't support discarding animated images (See bug 414259). - // Lock the image and throw away the key. - // - // Note that this is inefficient, since we could get rid of the source data - // too. However, doing this is actually hard, because we're probably - // mid-decode, and thus we're decoding out of the source buffer. Since we're - // going to fix this anyway later, and since we didn't kill the source data - // in the old world either, locking is acceptable for the moment. - LockImage(); - - if (mPendingAnimation && ShouldAnimate()) { - StartAnimation(); - } - - if (aNewFrameCount > 1) { - mAnim->UnionFirstFrameRefreshArea(aNewRefreshArea); - } + MOZ_ASSERT((mFrameCount == 1 && aNewFrameCount == 1) || + mFrameCount < aNewFrameCount, + "Frame count running backwards"); + + mFrameCount = aNewFrameCount; + + if (aNewFrameCount == 2) { + // We're becoming animated, so initialize animation stuff. + MOZ_ASSERT(!mAnim, "Already have animation state?"); + mAnim = MakeUnique<FrameAnimator>(this, mSize.ToIntSize(), mAnimationMode); + + // We don't support discarding animated images (See bug 414259). + // Lock the image and throw away the key. + // + // Note that this is inefficient, since we could get rid of the source data + // too. However, doing this is actually hard, because we're probably + // mid-decode, and thus we're decoding out of the source buffer. Since we're + // going to fix this anyway later, and since we didn't kill the source data + // in the old world either, locking is acceptable for the moment. + LockImage(); + + if (mPendingAnimation && ShouldAnimate()) { + StartAnimation(); } } + + if (aNewFrameCount > 1) { + mAnim->UnionFirstFrameRefreshArea(aNewRefreshArea); + } } nsresult RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation) { MOZ_ASSERT(NS_IsMainThread()); + mDecodingMonitor.AssertCurrentThreadIn(); if (mError) return NS_ERROR_FAILURE; // Ensure that we have positive values // XXX - Why isn't the size unsigned? Should this be changed? if ((aWidth < 0) || (aHeight < 0)) return NS_ERROR_INVALID_ARG; // if we already have a size, check the new size against the old one if (mHasSize && ((aWidth != mSize.width) || (aHeight != mSize.height) || (aOrientation != mOrientation))) { NS_WARNING("Image changed size on redecode! This should not happen!"); + + // Make the decoder aware of the error so that it doesn't try to call + // FinishInternal during ShutdownDecoder. + if (mDecoder) + mDecoder->PostResizeError(); + DoError(); return NS_ERROR_UNEXPECTED; } // Set the size and flag that we have it mSize.SizeTo(aWidth, aHeight); mOrientation = aOrientation; mHasSize = true; return NS_OK; } void -RasterImage::OnDecodingComplete() +RasterImage::DecodingComplete(imgFrame* aFinalFrame) { MOZ_ASSERT(NS_IsMainThread()); if (mError) { return; } - // Flag that we've been decoded before. + // Flag that we're done decoding. + // XXX - these should probably be combined when we fix animated image + // discarding with bug 500402. + mDecoded = true; mHasBeenDecoded = true; - // Let our FrameAnimator know not to expect any more frames. + // If there's only 1 frame, mark it as optimizable. Optimizing animated images + // is not supported. Optimizing transient images isn't worth it. + if (GetNumFrames() == 1 && !mTransient && aFinalFrame) { + aFinalFrame->SetOptimizable(); + } + if (mAnim) { mAnim->SetDoneDecoding(true); } } NS_IMETHODIMP RasterImage::SetAnimationMode(uint16_t aAnimationMode) { @@ -1059,17 +1104,26 @@ RasterImage::ResetAnimation() mAnimationFinished = false; if (mAnimating) StopAnimation(); MOZ_ASSERT(mAnim, "Should have a FrameAnimator"); mAnim->ResetAnimation(); - NotifyProgress(NoProgress, mAnim->GetFirstFrameRefreshArea()); + UpdateImageContainer(); + + // Note - We probably want to kick off a redecode somewhere around here when + // we fix bug 500402. + + // Update display + if (mProgressTracker) { + nsIntRect rect = mAnim->GetFirstFrameRefreshArea(); + mProgressTracker->SyncNotifyProgress(NoProgress, rect); + } // Start the animation again. It may not have been running before, if // mAnimationFinished was true before entering this function. EvaluateAnimation(); return NS_OK; } @@ -1107,89 +1161,184 @@ RasterImage::SetLoopCount(int32_t aLoopC NS_IMETHODIMP_(nsIntRect) RasterImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) { return aRect; } nsresult -RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus, - bool aLastPart) +RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount) +{ + ReentrantMonitorAutoEnter lock(mDecodingMonitor); + + if (mError) + return NS_ERROR_FAILURE; + + NS_ENSURE_ARG_POINTER(aBuffer); + nsresult rv = NS_OK; + + // We should not call this if we're not initialized + NS_ABORT_IF_FALSE(mInitialized, "Calling AddSourceData() on uninitialized " + "RasterImage!"); + + // We should not call this if we're already finished adding source data + NS_ABORT_IF_FALSE(!mHasSourceData, "Calling AddSourceData() after calling " + "sourceDataComplete()!"); + + // Image is already decoded, we shouldn't be getting data, but it could + // be extra garbage data at the end of a file. + if (mDecoded) { + return NS_OK; + } + + // If we're not storing source data and we've previously gotten the size, + // write the data directly to the decoder. (If we haven't gotten the size, + // we'll queue up the data and write it out when we do.) + if (!StoringSourceData() && mHasSize) { + rv = WriteToDecoder(aBuffer, aCount); + CONTAINER_ENSURE_SUCCESS(rv); + + rv = FinishedSomeDecoding(); + CONTAINER_ENSURE_SUCCESS(rv); + } + + // Otherwise, we're storing data in the source buffer + else { + + // Store the data + char *newElem = mSourceData.AppendElements(aBuffer, aCount); + if (!newElem) + return NS_ERROR_OUT_OF_MEMORY; + + if (mDecoder) { + DecodePool::Singleton()->RequestDecode(this); + } + } + + return NS_OK; +} + +/* Note! buf must be declared as char buf[9]; */ +// just used for logging and hashing the header +static void +get_header_str (char *buf, char *data, size_t data_len) +{ + int i; + int n; + static char hex[] = "0123456789abcdef"; + + n = data_len < 4 ? data_len : 4; + + for (i = 0; i < n; i++) { + buf[i * 2] = hex[(data[i] >> 4) & 0x0f]; + buf[i * 2 + 1] = hex[data[i] & 0x0f]; + } + + buf[i * 2] = 0; +} + +nsresult +RasterImage::DoImageDataComplete() { MOZ_ASSERT(NS_IsMainThread()); - // Record that we have all the data we're going to get now. + if (mError) + return NS_ERROR_FAILURE; + + // If we've been called before, ignore. Otherwise, flag that we have everything + if (mHasSourceData) + return NS_OK; mHasSourceData = true; - // Let decoders know that there won't be any more data coming. - mSourceBuffer->Complete(aStatus); - - if (!mHasSize) { - // We need to guarantee that we've gotten the image's size, or at least - // determined that we won't be able to get it, before we deliver the load - // event. That means we have to do a synchronous size decode here. - Decode(DecodeStrategy::SYNC_IF_POSSIBLE, DECODE_FLAGS_DEFAULT, - /* aDoSizeDecode = */ true); + // If there's a decoder open, synchronously decode the beginning of the image + // to check for errors and get the image's size. (If we already have the + // image's size, this does nothing.) Then kick off an async decode of the + // rest of the image. + if (mDecoder) { + nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this); + CONTAINER_ENSURE_SUCCESS(rv); + } + + { + ReentrantMonitorAutoEnter lock(mDecodingMonitor); + + // Free up any extra space in the backing buffer + mSourceData.Compact(); } - // Determine our final status, giving precedence to Necko failure codes. We - // check after running the size decode above in case it triggered an error. - nsresult finalStatus = mError ? NS_ERROR_FAILURE : NS_OK; - if (NS_FAILED(aStatus)) { - finalStatus = aStatus; + // Log header information + if (PR_LOG_TEST(GetCompressedImageAccountingLog(), PR_LOG_DEBUG)) { + char buf[9]; + get_header_str(buf, mSourceData.Elements(), mSourceData.Length()); + PR_LOG (GetCompressedImageAccountingLog(), PR_LOG_DEBUG, + ("CompressedImageAccounting: RasterImage::SourceDataComplete() - data " + "is done for container %p (%s) - header %p is 0x%s (length %d)", + this, + mSourceDataMimeType.get(), + mSourceData.Elements(), + buf, + mSourceData.Length())); } - // If loading failed, report an error. - if (NS_FAILED(finalStatus)) { - DoError(); + return NS_OK; +} + +nsresult +RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus, bool aLastPart) +{ + nsresult finalStatus = DoImageDataComplete(); + + // Give precedence to Necko failure codes. + if (NS_FAILED(aStatus)) + finalStatus = aStatus; + + // We just recorded OnStopRequest; we need to inform our listeners. + { + ReentrantMonitorAutoEnter lock(mDecodingMonitor); + FinishedSomeDecoding(ShutdownReason::DONE, + LoadCompleteProgress(aLastPart, mError, finalStatus)); } - // Notify our listeners, which will fire this image's load event. - MOZ_ASSERT(mHasSize || mError, "Need to know size before firing load event"); - MOZ_ASSERT(!mHasSize || - (mProgressTracker->GetProgress() & FLAG_SIZE_AVAILABLE), - "Should have notified that the size is available if we have it"); - Progress loadProgress = LoadCompleteProgress(aLastPart, mError, finalStatus); - NotifyProgress(loadProgress); - return finalStatus; } nsresult RasterImage::OnImageDataAvailable(nsIRequest*, nsISupports*, nsIInputStream* aInStr, uint64_t, uint32_t aCount) { nsresult rv; - // WriteToSourceBuffer always consumes everything it gets if it doesn't run - // out of memory. + // WriteToRasterImage always consumes everything it gets + // if it doesn't run out of memory uint32_t bytesRead; - rv = aInStr->ReadSegments(WriteToSourceBuffer, this, aCount, &bytesRead); + rv = aInStr->ReadSegments(WriteToRasterImage, this, aCount, &bytesRead); NS_ABORT_IF_FALSE(bytesRead == aCount || HasError() || NS_FAILED(rv), - "WriteToSourceBuffer should consume everything if ReadSegments succeeds or " + "WriteToRasterImage should consume everything if ReadSegments succeeds or " "the image must be in error!"); return rv; } /* static */ already_AddRefed<nsIEventTarget> RasterImage::GetEventTarget() { return DecodePool::Singleton()->GetEventTarget(); } nsresult -RasterImage::SetSourceSizeHint(uint32_t aSizeHint) +RasterImage::SetSourceSizeHint(uint32_t sizeHint) { - return mSourceBuffer->ExpectLength(aSizeHint); + if (sizeHint && StoringSourceData()) + return mSourceData.SetCapacity(sizeHint) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + return NS_OK; } /********* Methods to implement lazy allocation of nsIProperties object *************/ NS_IMETHODIMP RasterImage::Get(const char *prop, const nsIID & iid, void * *result) { if (!mProperties) return NS_ERROR_FAILURE; @@ -1235,93 +1384,118 @@ RasterImage::GetKeys(uint32_t *count, ch } return mProperties->GetKeys(count, keys); } void RasterImage::Discard() { MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(CanDiscard(), "Asked to discard but can't"); - MOZ_ASSERT(!mAnim, "Asked to discard for animated image"); + + // We should never discard when we have an active decoder + NS_ABORT_IF_FALSE(!mDecoder, "Asked to discard with open decoder!"); + + // As soon as an image becomes animated, it becomes non-discardable and any + // timers are cancelled. + NS_ABORT_IF_FALSE(!mAnim, "Asked to discard for animated image!"); // Delete all the decoded frames. SurfaceCache::RemoveImage(ImageKey(this)); - // Notify that we discarded. + // Flag that we no longer have decoded frames for this image + mDecoded = false; + mFrameCount = 0; + + // Notify that we discarded if (mProgressTracker) { mProgressTracker->OnDiscard(); } + + mDecodeStatus = DecodeStatus::INACTIVE; } bool RasterImage::CanDiscard() { return mHasSourceData && // ...have the source data... + !mDecoder && // Can't discard with an open decoder !mAnim; // Can never discard animated images } -// Sets up a decoder for this image. -already_AddRefed<Decoder> -RasterImage::CreateDecoder(bool aDoSizeDecode, uint32_t aFlags) +// Helper method to determine if we're storing the source data in a buffer +// or just writing it directly to the decoder +bool +RasterImage::StoringSourceData() const { + return !mTransient; +} + + +// Sets up a decoder for this image. It is an error to call this function +// when decoding is already in process (ie - when mDecoder is non-null). +nsresult +RasterImage::InitDecoder(bool aDoSizeDecode) { + // Ensure that the decoder is not already initialized + NS_ABORT_IF_FALSE(!mDecoder, "Calling InitDecoder() while already decoding!"); + + // We shouldn't be firing up a decoder if we already have the frames decoded + NS_ABORT_IF_FALSE(!mDecoded, "Calling InitDecoder() but already decoded!"); + // Make sure we actually get size before doing a full decode. - if (aDoSizeDecode) { - MOZ_ASSERT(!mHasSize, "Should not do unnecessary size decodes"); - } else { - MOZ_ASSERT(mHasSize, "Must do a size decode before a full decode!"); + if (!aDoSizeDecode) { + NS_ABORT_IF_FALSE(mHasSize, "Must do a size decode before a full decode!"); } - // Figure out which decoder we want. + // Figure out which decoder we want eDecoderType type = GetDecoderType(mSourceDataMimeType.get()); - if (type == eDecoderType_unknown) { - return nullptr; - } - - // Instantiate the appropriate decoder. - nsRefPtr<Decoder> decoder; + CONTAINER_ENSURE_TRUE(type != eDecoderType_unknown, NS_IMAGELIB_ERROR_NO_DECODER); + + // Instantiate the appropriate decoder switch (type) { case eDecoderType_png: - decoder = new nsPNGDecoder(this); + mDecoder = new nsPNGDecoder(*this); break; case eDecoderType_gif: - decoder = new nsGIFDecoder2(this); + mDecoder = new nsGIFDecoder2(*this); break; case eDecoderType_jpeg: // If we have all the data we don't want to waste cpu time doing - // a progressive decode. - decoder = new nsJPEGDecoder(this, - mHasBeenDecoded ? Decoder::SEQUENTIAL : - Decoder::PROGRESSIVE); + // a progressive decode + mDecoder = new nsJPEGDecoder(*this, + mHasBeenDecoded ? Decoder::SEQUENTIAL : + Decoder::PROGRESSIVE); break; case eDecoderType_bmp: - decoder = new nsBMPDecoder(this); + mDecoder = new nsBMPDecoder(*this); break; case eDecoderType_ico: - decoder = new nsICODecoder(this); + mDecoder = new nsICODecoder(*this); break; case eDecoderType_icon: - decoder = new nsIconDecoder(this); + mDecoder = new nsIconDecoder(*this); break; default: - MOZ_ASSERT_UNREACHABLE("Unknown decoder type"); + NS_ABORT_IF_FALSE(0, "Shouldn't get here!"); } - MOZ_ASSERT(decoder, "Should have a decoder now"); - - // Initialize the decoder. - decoder->SetSizeDecode(aDoSizeDecode); - decoder->SetSendPartialInvalidations(!mHasBeenDecoded); - decoder->SetImageIsTransient(mTransient); - decoder->SetDecodeFlags(DecodeFlags(aFlags)); - decoder->SetIterator(mSourceBuffer->Iterator()); - decoder->Init(); - - if (NS_FAILED(decoder->GetDecoderError())) { - return nullptr; + // Initialize the decoder + mDecoder->SetSizeDecode(aDoSizeDecode); + mDecoder->SetDecodeFlags(mFrameDecodeFlags); + if (!aDoSizeDecode) { + // We already have the size; tell the decoder so it can preallocate a + // frame. By default, we create an ARGB frame with no offset. If decoders + // need a different type, they need to ask for it themselves. + mDecoder->SetSize(mSize, mOrientation); + mDecoder->NeedNewFrame(0, 0, 0, mSize.width, mSize.height, + SurfaceFormat::B8G8R8A8); + mDecoder->AllocateFrame(); } + mDecoder->Init(); + CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError()); if (!aDoSizeDecode) { Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Subtract(mDecodeCount); mDecodeCount++; Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(mDecodeCount); if (mDecodeCount > sMaxDecodeCount) { // Don't subtract out 0 from the histogram, because that causes its count @@ -1329,170 +1503,362 @@ RasterImage::CreateDecoder(bool aDoSizeD if (sMaxDecodeCount > 0) { Telemetry::GetHistogramById(Telemetry::IMAGE_MAX_DECODE_COUNT)->Subtract(sMaxDecodeCount); } sMaxDecodeCount = mDecodeCount; Telemetry::GetHistogramById(Telemetry::IMAGE_MAX_DECODE_COUNT)->Add(sMaxDecodeCount); } } - return decoder.forget(); + return NS_OK; } -void +// Flushes, closes, and nulls-out a decoder. Cleans up any related decoding +// state. It is an error to call this function when there is no initialized +// decoder. +// +// aReason specifies why the shutdown is happening. If aReason is +// ShutdownReason::DONE, an error is flagged if we didn't get what we should +// have out of the decode. If aReason is ShutdownReason::NOT_NEEDED, we don't +// check this. If aReason is ShutdownReason::FATAL_ERROR, we shut down in error +// mode. +nsresult +RasterImage::ShutdownDecoder(ShutdownReason aReason) +{ + MOZ_ASSERT(NS_IsMainThread()); + mDecodingMonitor.AssertCurrentThreadIn(); + + // Ensure that the decoder is initialized + NS_ABORT_IF_FALSE(mDecoder, "Calling ShutdownDecoder() with no active decoder!"); + + // Figure out what kind of decode we were doing before we get rid of our decoder + bool wasSizeDecode = mDecoder->IsSizeDecode(); + + // Finalize the decoder + // null out mDecoder, _then_ check for errors on the close (otherwise the + // error routine might re-invoke ShutdownDecoder) + nsRefPtr<Decoder> decoder = mDecoder; + mDecoder = nullptr; + + decoder->Finish(aReason); + + // Kill off our decode request, if it's pending. (If not, this call is + // harmless.) + DecodePool::StopDecoding(this); + + nsresult decoderStatus = decoder->GetDecoderError(); + if (NS_FAILED(decoderStatus)) { + DoError(); + return decoderStatus; + } + + // We just shut down the decoder. If we didn't get what we want, but expected + // to, flag an error + bool succeeded = wasSizeDecode ? mHasSize : mDecoded; + if ((aReason == ShutdownReason::DONE) && !succeeded) { + DoError(); + return NS_ERROR_FAILURE; + } + + // If we finished a full decode, and we're not meant to be storing source + // data, stop storing it. + if (!wasSizeDecode && !StoringSourceData()) { + mSourceData.Clear(); + } + + return NS_OK; +} + +// Writes the data to the decoder, updating the total number of bytes written. +nsresult +RasterImage::WriteToDecoder(const char *aBuffer, uint32_t aCount) +{ + mDecodingMonitor.AssertCurrentThreadIn(); + + // We should have a decoder + NS_ABORT_IF_FALSE(mDecoder, "Trying to write to null decoder!"); + + // Write + nsRefPtr<Decoder> kungFuDeathGrip = mDecoder; + mDecoder->Write(aBuffer, aCount); + + CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError()); + + return NS_OK; +} + +// This function is called in situations where it's clear that we want the +// frames in decoded form (Draw, LookupFrame, etc). If we're completely decoded, +// this method resets the discard timer (if we're discardable), since wanting +// the frames now is a good indicator of wanting them again soon. If we're not +// decoded, this method kicks off asynchronous decoding to generate the frames. +nsresult RasterImage::WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify) { + // Request a decode, which does nothing if we're already decoded. if (aShouldSyncNotify) { // We can sync notify, which means we can also sync decode. if (aFlags & FLAG_SYNC_DECODE) { - Decode(DecodeStrategy::SYNC_IF_POSSIBLE, aFlags); - return; + return SyncDecode(); } - - // Here we are explicitly trading off flashing for responsiveness in the - // case that we're redecoding an image (see bug 845147). - Decode(mHasBeenDecoded ? DecodeStrategy::ASYNC - : DecodeStrategy::SYNC_FOR_SMALL_IMAGES, - aFlags); - return; + return StartDecoding(); } // We can't sync notify, so do an async decode. - Decode(DecodeStrategy::ASYNC, aFlags); + return RequestDecodeCore(ASYNCHRONOUS); } //****************************************************************************** /* void requestDecode() */ NS_IMETHODIMP RasterImage::RequestDecode() { - MOZ_ASSERT(NS_IsMainThread()); - - if (mError) { - return NS_ERROR_FAILURE; - } - if (!mHasSize) { - mWantFullDecode = true; - return NS_OK; - } - - // Look up the first frame of the image, which will implicitly start decoding - // if it's not available right now. - // XXX(seth): Passing false for aShouldSyncNotify here has the effect of - // decoding asynchronously, but that's not obvious from the argument name. - // This API needs to be reworked. - LookupFrame(0, mSize, DECODE_FLAGS_DEFAULT, /* aShouldSyncNotify = */ false); - - return NS_OK; + return RequestDecodeCore(SYNCHRONOUS_NOTIFY); } /* void startDecode() */ NS_IMETHODIMP RasterImage::StartDecoding() { if (!NS_IsMainThread()) { return NS_DispatchToMainThread( NS_NewRunnableMethod(this, &RasterImage::StartDecoding)); } - - if (mError) { - return NS_ERROR_FAILURE; - } - if (!mHasSize) { - mWantFullDecode = true; - return NS_OK; - } - - // Look up the first frame of the image, which will implicitly start decoding - // if it's not available right now. - // XXX(seth): Passing true for aShouldSyncNotify here has the effect of - // synchronously decoding small images, but that's not obvious from the - // argument name. This API needs to be reworked. - LookupFrame(0, mSize, DECODE_FLAGS_DEFAULT, /* aShouldSyncNotify = */ true); - - return NS_OK; + // Here we are explicitly trading off flashing for responsiveness in the case + // that we're redecoding an image (see bug 845147). + return RequestDecodeCore(mHasBeenDecoded ? + SYNCHRONOUS_NOTIFY : SYNCHRONOUS_NOTIFY_AND_SOME_DECODE); } bool RasterImage::IsDecoded() { - // XXX(seth): We need to get rid of this; it's not reliable. - return mHasBeenDecoded || mError; + return mDecoded || mError; } NS_IMETHODIMP -RasterImage::Decode(DecodeStrategy aStrategy, - uint32_t aFlags, - bool aDoSizeDecode /* = false */) +RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType) { - MOZ_ASSERT(aDoSizeDecode || NS_IsMainThread()); - - if (mError) { - return NS_ERROR_FAILURE; - } - - // If we don't have a size yet, we can't do any other decoding. - if (!mHasSize && !aDoSizeDecode) { - mWantFullDecode = true; - return NS_OK; - } - - // Create a decoder. - nsRefPtr<Decoder> decoder = CreateDecoder(aDoSizeDecode, aFlags); - if (!decoder) { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + + if (mError) return NS_ERROR_FAILURE; - } - - // Send out early notifications right away. (Unless this is a size decode, - // which doesn't send out any notifications until the end.) - if (!aDoSizeDecode) { - NotifyProgress(decoder->TakeProgress(), - decoder->TakeInvalidRect(), - decoder->GetDecodeFlags()); - } - - if (mHasSourceData) { - // If we have all the data, we can sync decode if requested. - if (aStrategy == DecodeStrategy::SYNC_FOR_SMALL_IMAGES) { - PROFILER_LABEL_PRINTF("DecodePool", "SyncDecodeIfSmall", - js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get()); - DecodePool::Singleton()->SyncDecodeIfSmall(decoder); - return NS_OK; - } - - if (aStrategy == DecodeStrategy::SYNC_IF_POSSIBLE) { - PROFILER_LABEL_PRINTF("DecodePool", "SyncDecodeIfPossible", - js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get()); - DecodePool::Singleton()->SyncDecodeIfPossible(decoder); + + // If we're already decoded, there's nothing to do. + if (mDecoded) + return NS_OK; + + // If we have a size decoder open, make sure we get the size + if (mDecoder && mDecoder->IsSizeDecode()) { + nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this); + CONTAINER_ENSURE_SUCCESS(rv); + + // If we didn't get the size out of the image, we won't until we get more + // data, so signal that we want a full decode and give up for now. + if (!mHasSize) { + mWantFullDecode = true; return NS_OK; } } - // Perform an async decode. We also take this path if we don't have all the - // source data yet, since sync decoding is impossible in that situation. - DecodePool::Singleton()->AsyncDecode(decoder); + // If the image is waiting for decode work to be notified, go ahead and do that. + if (mDecodeStatus == DecodeStatus::WORK_DONE && + aDecodeType == SYNCHRONOUS_NOTIFY) { + ReentrantMonitorAutoEnter lock(mDecodingMonitor); + nsresult rv = FinishedSomeDecoding(); + CONTAINER_ENSURE_SUCCESS(rv); + } + + // If we're fully decoded, we have nothing to do. We need this check after + // DecodeUntilSizeAvailable and FinishedSomeDecoding because they can result + // in us finishing an in-progress decode (or kicking off and finishing a + // synchronous decode if we're already waiting on a full decode). + if (mDecoded) { + return NS_OK; + } + + // If we've already got a full decoder running, and have already decoded + // some bytes, we have nothing to do if we haven't been asked to do some + // sync decoding + if (mDecoder && !mDecoder->IsSizeDecode() && mDecoder->BytesDecoded() > 0 && + aDecodeType != SYNCHRONOUS_NOTIFY_AND_SOME_DECODE) { + return NS_OK; + } + + ReentrantMonitorAutoEnter lock(mDecodingMonitor); + + // If we don't have any bytes to flush to the decoder, we can't do anything. + // mDecoder->BytesDecoded() can be bigger than mSourceData.Length() if we're + // not storing the source data. + if (mDecoder && mDecoder->BytesDecoded() > mSourceData.Length()) { + return NS_OK; + } + + // After acquiring the lock we may have finished some more decoding, so + // we need to repeat the following three checks after getting the lock. + + // If the image is waiting for decode work to be notified, go ahead and do that. + if (mDecodeStatus == DecodeStatus::WORK_DONE && aDecodeType != ASYNCHRONOUS) { + nsresult rv = FinishedSomeDecoding(); + CONTAINER_ENSURE_SUCCESS(rv); + } + + // If we're fully decoded, we have nothing to do. We need this check after + // DecodeUntilSizeAvailable and FinishedSomeDecoding because they can result + // in us finishing an in-progress decode (or kicking off and finishing a + // synchronous decode if we're already waiting on a full decode). + if (mDecoded) { + return NS_OK; + } + + // If we've already got a full decoder running, and have already + // decoded some bytes, we have nothing to do. + if (mDecoder && !mDecoder->IsSizeDecode() && mDecoder->BytesDecoded() > 0) { + return NS_OK; + } + + // If we have a size decode open, interrupt it and shut it down; or if + // the decoder has different flags than what we need + if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) { + nsresult rv = FinishedSomeDecoding(ShutdownReason::NOT_NEEDED); + CONTAINER_ENSURE_SUCCESS(rv); + } + + // If we don't have a decoder, create one + if (!mDecoder) { + rv = InitDecoder(/* aDoSizeDecode = */ false); + CONTAINER_ENSURE_SUCCESS(rv); + + rv = FinishedSomeDecoding(); + CONTAINER_ENSURE_SUCCESS(rv); + } + + MOZ_ASSERT(mDecoder); + + // If we've read all the data we have, we're done + if (mHasSourceData && mDecoder->BytesDecoded() == mSourceData.Length()) { + return NS_OK; + } + + // If we can do decoding now, do so. Small images will decode completely, + // large images will decode a bit and post themselves to the event loop + // to finish decoding. + if (!mDecoded && mHasSourceData && aDecodeType == SYNCHRONOUS_NOTIFY_AND_SOME_DECODE) { + PROFILER_LABEL_PRINTF("RasterImage", "DecodeABitOf", + js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get()); + + DecodePool::Singleton()->DecodeABitOf(this); + return NS_OK; + } + + if (!mDecoded) { + // If we get this far, dispatch the worker. We do this instead of starting + // any immediate decoding to guarantee that all our decode notifications are + // dispatched asynchronously, and to ensure we stay responsive. + DecodePool::Singleton()->RequestDecode(this); + } + return NS_OK; } +// Synchronously decodes as much data as possible +nsresult +RasterImage::SyncDecode() +{ + PROFILER_LABEL_PRINTF("RasterImage", "SyncDecode", + js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get()); + + // If we have a size decoder open, make sure we get the size + if (mDecoder && mDecoder->IsSizeDecode()) { + nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this); + CONTAINER_ENSURE_SUCCESS(rv); + + // If we didn't get the size out of the image, we won't until we get more + // data, so signal that we want a full decode and give up for now. + if (!mHasSize) { + mWantFullDecode = true; + return NS_ERROR_NOT_AVAILABLE; + } + } + + ReentrantMonitorAutoEnter lock(mDecodingMonitor); + + // If the image is waiting for decode work to be notified, go ahead and do that. + if (mDecodeStatus == DecodeStatus::WORK_DONE) { + nsresult rv = FinishedSomeDecoding(); + CONTAINER_ENSURE_SUCCESS(rv); + } + + nsresult rv; + + // If we're decoded already, or decoding until the size was available + // finished us as a side-effect, no worries + if (mDecoded) + return NS_OK; + + // If we don't have any bytes to flush to the decoder, we can't do anything. + // mDecoder->BytesDecoded() can be bigger than mSourceData.Length() if we're + // not storing the source data. + if (mDecoder && mDecoder->BytesDecoded() > mSourceData.Length()) { + return NS_OK; + } + + // If we have a decoder open with different flags than what we need, shut it + // down + if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) { + nsresult rv = FinishedSomeDecoding(ShutdownReason::NOT_NEEDED); + CONTAINER_ENSURE_SUCCESS(rv); + + if (mDecoded && mAnim) { + // We can't redecode animated images, so we'll have to give up. + return NS_ERROR_NOT_AVAILABLE; + } + } + + // If we don't have a decoder, create one + if (!mDecoder) { + rv = InitDecoder(/* aDoSizeDecode = */ false); + CONTAINER_ENSURE_SUCCESS(rv); + } + + MOZ_ASSERT(mDecoder); + + // Write everything we have + rv = DecodeSomeData(mSourceData.Length() - mDecoder->BytesDecoded()); + CONTAINER_ENSURE_SUCCESS(rv); + + rv = FinishedSomeDecoding(); + CONTAINER_ENSURE_SUCCESS(rv); + + // If our decoder's still open, there's still work to be done. + if (mDecoder) { + DecodePool::Singleton()->RequestDecode(this); + } + + // All good if no errors! + return mError ? NS_ERROR_FAILURE : NS_OK; +} + bool RasterImage::CanScale(GraphicsFilter aFilter, const nsIntSize& aSize, uint32_t aFlags) { #ifndef MOZ_ENABLE_SKIA // The high-quality scaler requires Skia. return false; #else - // Check basic requirements: HQ downscaling is enabled, we have all the source - // data and know our size, the flags allow us to do it, and a 'good' filter is - // being used. The flags may ask us not to scale because the caller isn't - // drawing to the window. If we're drawing to something else (e.g. a canvas) - // we usually have no way of updating what we've drawn, so HQ scaling is - // useless. - if (!gfxPrefs::ImageHQDownscalingEnabled() || !mHasSize || !mHasSourceData || + // Check basic requirements: HQ downscaling is enabled, we're decoded, the + // flags allow us to do it, and a 'good' filter is being used. The flags may + // ask us not to scale because the caller isn't drawing to the window. If + // we're drawing to something else (e.g. a canvas) we usually have no way of + // updating what we've drawn, so HQ scaling is useless. + if (!gfxPrefs::ImageHQDownscalingEnabled() || !mDecoded || !(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) || aFilter != GraphicsFilter::FILTER_GOOD) { return false; } // We don't use the scaler for animated or transient images to avoid doing a // bunch of work on an image that just gets thrown away. if (mAnim || mTransient) { @@ -1526,28 +1892,31 @@ RasterImage::CanScale(GraphicsFilter aFi gfxFloat minFactor = gfxPrefs::ImageHQDownscalingMinFactor() / 1000.0; return (scale.width < minFactor || scale.height < minFactor); #endif } void RasterImage::NotifyNewScaledFrame() { - // Send an invalidation so observers will repaint and can take advantage of - // the new scaled frame if possible. - NotifyProgress(NoProgress, nsIntRect(0, 0, mSize.width, mSize.height)); + if (mProgressTracker) { + // Send an invalidation so observers will repaint and can take advantage of + // the new scaled frame if possible. + nsIntRect rect(0, 0, mSize.width, mSize.height); + mProgressTracker->SyncNotifyProgress(NoProgress, rect); + } } void RasterImage::RequestScale(imgFrame* aFrame, uint32_t aFlags, const nsIntSize& aSize) { // We don't scale frames which aren't fully decoded. - if (!aFrame->IsImageComplete()) { + if (!aFrame->ImageComplete()) { return; } // We can't scale frames that need padding or are single pixel. if (aFrame->NeedsPadding() || aFrame->IsSinglePixel()) { return; } @@ -1586,17 +1955,17 @@ RasterImage::DrawWithPreDownscaleIfNeede DecodeFlags(aFlags), 0)); if (!frameRef) { // We either didn't have a matching scaled frame or the OS threw it away. // Request a new one so we'll be ready next time. For now, we'll fall back // to aFrameRef below. RequestScale(aFrameRef.get(), aFlags, aSize); } - if (frameRef && !frameRef->IsImageComplete()) { + if (frameRef && !frameRef->ImageComplete()) { frameRef.reset(); // We're still scaling, so we can't use this yet. } } gfxContextMatrixAutoSaveRestore saveMatrix(aContext); ImageRegion region(aRegion); if (!frameRef) { frameRef = Move(aFrameRef); @@ -1647,41 +2016,46 @@ RasterImage::Draw(gfxContext* aContext, return NS_ERROR_FAILURE; NS_ENSURE_ARG_POINTER(aContext); if (IsUnlocked() && mProgressTracker) { mProgressTracker->OnUnlockedDraw(); } + // We use !mDecoded && mHasSourceData to mean discarded. + if (!mDecoded && mHasSourceData) { + mDrawStartTime = TimeStamp::Now(); + } + + // If a synchronous draw is requested, flush anything that might be sitting around + if (aFlags & FLAG_SYNC_DECODE) { + nsresult rv = SyncDecode(); + NS_ENSURE_SUCCESS(rv, rv); + } + // XXX(seth): For now, we deliberately don't look up a frame of size aSize // (though DrawWithPreDownscaleIfNeeded will do so later). It doesn't make // sense to do so until we support downscale-during-decode. Right now we need // to make sure that we always touch an mSize-sized frame so that we have // something to HQ scale. - DrawableFrameRef ref = - LookupFrame(GetRequestedFrameIndex(aWhichFrame), mSize, aFlags); + DrawableFrameRef ref = LookupFrame(GetRequestedFrameIndex(aWhichFrame), + mSize, aFlags); if (!ref) { // Getting the frame (above) touches the image and kicks off decoding. - if (mDrawStartTime.IsNull()) { - mDrawStartTime = TimeStamp::Now(); - } return NS_OK; } - bool shouldRecordTelemetry = !mDrawStartTime.IsNull() && - ref->IsImageComplete(); - DrawWithPreDownscaleIfNeeded(Move(ref), aContext, aSize, aRegion, aFilter, aFlags); - if (shouldRecordTelemetry) { + if (mDecoded && !mDrawStartTime.IsNull()) { TimeDuration drawLatency = TimeStamp::Now() - mDrawStartTime; - Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY, - int32_t(drawLatency.ToMicroseconds())); + Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY, int32_t(drawLatency.ToMicroseconds())); + // clear the value of mDrawStartTime mDrawStartTime = TimeStamp(); } return NS_OK; } //****************************************************************************** /* void lockImage() */ @@ -1723,59 +2097,141 @@ RasterImage::UnlockImage() // Decrement our lock count mLockCount--; // Unlock this image's surfaces in the SurfaceCache. if (mLockCount == 0 ) { SurfaceCache::UnlockImage(ImageKey(this)); } + // If we've decoded this image once before, we're currently decoding again, + // and our lock count is now zero (so nothing is forcing us to keep the + // decoded data around), try to cancel the decode and throw away whatever + // we've decoded. + if (mHasBeenDecoded && mDecoder && mLockCount == 0 && !mAnim) { + PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG, + ("RasterImage[0x%p] canceling decode because image " + "is now unlocked.", this)); + ReentrantMonitorAutoEnter lock(mDecodingMonitor); + FinishedSomeDecoding(ShutdownReason::NOT_NEEDED); + return NS_OK; + } + return NS_OK; } //****************************************************************************** /* void requestDiscard() */ NS_IMETHODIMP RasterImage::RequestDiscard() { if (mDiscardable && // Enabled at creation time... mLockCount == 0 && // ...not temporarily disabled... + mDecoded && // ...and have something to discard. CanDiscard()) { Discard(); } return NS_OK; } +// Flushes up to aMaxBytes to the decoder. +nsresult +RasterImage::DecodeSomeData(size_t aMaxBytes) +{ + MOZ_ASSERT(mDecoder, "Should have a decoder"); + + mDecodingMonitor.AssertCurrentThreadIn(); + + // If we have nothing else to decode, return. + if (mDecoder->BytesDecoded() == mSourceData.Length()) { + return NS_OK; + } + + MOZ_ASSERT(mDecoder->BytesDecoded() < mSourceData.Length()); + + // write the proper amount of data + size_t bytesToDecode = min(aMaxBytes, + mSourceData.Length() - mDecoder->BytesDecoded()); + return WriteToDecoder(mSourceData.Elements() + mDecoder->BytesDecoded(), + bytesToDecode); + +} + +// There are various indicators that tell us we're finished with the decode +// task at hand and can shut down the decoder. +// +// This method may not be called if there is no decoder. +bool +RasterImage::IsDecodeFinished() +{ + // Precondition + mDecodingMonitor.AssertCurrentThreadIn(); + MOZ_ASSERT(mDecoder, "Should have a decoder"); + + // The decode is complete if we got what we wanted. + if (mDecoder->IsSizeDecode()) { + if (mDecoder->HasSize()) { + return true; + } + } else if (mDecoder->GetDecodeDone()) { + return true; + } + + // Otherwise, if we have all the source data and wrote all the source data, + // we're done. + // + // (NB - This can be the case even for non-erroneous images because + // Decoder::GetDecodeDone() might not return true until after we call + // Decoder::Finish() in ShutdownDecoder()) + if (mHasSourceData && (mDecoder->BytesDecoded() == mSourceData.Length())) { + return true; + } + + // If we get here, assume it's not finished. + return false; +} + // Indempotent error flagging routine. If a decoder is open, shuts it down. void RasterImage::DoError() { // If we've flagged an error before, we have nothing to do if (mError) return; // We can't safely handle errors off-main-thread, so dispatch a worker to do it. if (!NS_IsMainThread()) { HandleErrorWorker::DispatchIfNeeded(this); return; } + // Calling FinishedSomeDecoding requires us to be in the decoding monitor. + ReentrantMonitorAutoEnter lock(mDecodingMonitor); + + // If we're mid-decode, shut down the decoder. + if (mDecoder) { + FinishedSomeDecoding(ShutdownReason::FATAL_ERROR); + } + // Put the container in an error state. mError = true; // Log our error LOG_CONTAINER_ERROR; } /* static */ void RasterImage::HandleErrorWorker::DispatchIfNeeded(RasterImage* aImage) { - nsRefPtr<HandleErrorWorker> worker = new HandleErrorWorker(aImage); - NS_DispatchToMainThread(worker); + if (!aImage->mPendingError) { + aImage->mPendingError = true; + nsRefPtr<HandleErrorWorker> worker = new HandleErrorWorker(aImage); + NS_DispatchToMainThread(worker); + } } RasterImage::HandleErrorWorker::HandleErrorWorker(RasterImage* aImage) : mImage(aImage) { MOZ_ASSERT(mImage, "Should have image"); } @@ -1786,31 +2242,31 @@ RasterImage::HandleErrorWorker::Run() return NS_OK; } // nsIInputStream callback to copy the incoming image data directly to the // RasterImage without processing. The RasterImage is passed as the closure. // Always reads everything it gets, even if the data is erroneous. NS_METHOD -RasterImage::WriteToSourceBuffer(nsIInputStream* /* unused */, - void* aClosure, - const char* aFromRawSegment, - uint32_t /* unused */, - uint32_t aCount, - uint32_t* aWriteCount) +RasterImage::WriteToRasterImage(nsIInputStream* /* unused */, + void* aClosure, + const char* aFromRawSegment, + uint32_t /* unused */, + uint32_t aCount, + uint32_t* aWriteCount) { // Retrieve the RasterImage RasterImage* image = static_cast<RasterImage*>(aClosure); // Copy the source data. Unless we hit OOM, we squelch the return value // here, because returning an error means that ReadSegments stops // reading data, violating our invariant that we read everything we get. // If we hit OOM then we fail and the load is aborted. - nsresult rv = image->mSourceBuffer->Append(aFromRawSegment, aCount); + nsresult rv = image->AddSourceData(aFromRawSegment, aCount); if (rv == NS_ERROR_OUT_OF_MEMORY) { image->DoError(); return rv; } // We wrote everything we got *aWriteCount = aCount; @@ -1832,40 +2288,134 @@ RasterImage::GetFramesNotified(uint32_t NS_ENSURE_ARG_POINTER(aFramesNotified); *aFramesNotified = mFramesNotified; return NS_OK; } #endif -void -RasterImage::NotifyProgress(Progress aProgress, - const nsIntRect& aInvalidRect /* = nsIntRect() */, - uint32_t aFlags /* = DECODE_FLAGS_DEFAULT */) +nsresult +RasterImage::RequestDecodeIfNeeded(nsresult aStatus, + ShutdownReason aReason, + bool aDone, + bool aWasSize) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // If we were a size decode and a full decode was requested, now's the time. + if (NS_SUCCEEDED(aStatus) && + aReason == ShutdownReason::DONE && + aDone && + aWasSize && + mWantFullDecode) { + mWantFullDecode = false; + + // If we're not meant to be storing source data and we just got the size, + // we need to synchronously flush all the data we got to a full decoder. + // When that decoder is shut down, we'll also clear our source data. + return StoringSourceData() ? RequestDecode() + : SyncDecode(); + } + + // We don't need a full decode right now, so just return the existing status. + return aStatus; +} + +nsresult +RasterImage::FinishedSomeDecoding(ShutdownReason aReason /* = ShutdownReason::DONE */, + Progress aProgress /* = NoProgress */) { MOZ_ASSERT(NS_IsMainThread()); - // Ensure that we stay alive long enough to finish notifying. + mDecodingMonitor.AssertCurrentThreadIn(); + + // Ensure that, if the decoder is the last reference to the image, we don't + // destroy it by destroying the decoder. nsRefPtr<RasterImage> image(this); - bool wasDefaultFlags = aFlags == DECODE_FLAGS_DEFAULT; + bool done = false; + bool wasSize = false; + bool wasDefaultFlags = false; + nsIntRect invalidRect; + nsresult rv = NS_OK; Progress progress = aProgress; - nsIntRect invalidRect = aInvalidRect; - + + if (image->mDecoder) { + invalidRect = image->mDecoder->TakeInvalidRect(); + progress |= image->mDecoder->TakeProgress(); + wasDefaultFlags = image->mDecoder->GetDecodeFlags() == DECODE_FLAGS_DEFAULT; + + if (!image->mDecoder->IsSizeDecode() && image->mDecoder->ChunkCount()) { + Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, + image->mDecoder->ChunkCount()); + } + + if (!image->mHasSize && image->mDecoder->HasSize()) { + image->mDecoder->SetSizeOnImage(); + } + + // If the decode finished, or we're specifically being told to shut down, + // tell the image and shut down the decoder. + if (image->IsDecodeFinished() || aReason != ShutdownReason::DONE) { + done = true; + + // Hold on to a reference to the decoder until we're done with it + nsRefPtr<Decoder> decoder = image->mDecoder; + + wasSize = decoder->IsSizeDecode(); + + // Do some telemetry if this isn't a size decode. + if (!wasSize) { + Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME, + int32_t(decoder->DecodeTime().ToMicroseconds())); + + // We record the speed for only some decoders. The rest have + // SpeedHistogram return HistogramCount. + Telemetry::ID id = decoder->SpeedHistogram(); + if (id < Telemetry::HistogramCount) { + int32_t KBps = int32_t(decoder->BytesDecoded() / + (1024 * decoder->DecodeTime().ToSeconds())); + Telemetry::Accumulate(id, KBps); + } + } + + // We need to shut down the decoder first, in order to ensure all + // decoding routines have been finished. + rv = image->ShutdownDecoder(aReason); + if (NS_FAILED(rv)) { + image->DoError(); + } + + // If there were any final changes, grab them. + invalidRect.Union(decoder->TakeInvalidRect()); + progress |= decoder->TakeProgress(); + } + } + + if (GetCurrentFrameIndex() > 0) { + // Don't send invalidations for animated frames after the first; let + // RequestRefresh take care of that. + invalidRect = nsIntRect(); + } + if (mHasBeenDecoded && !invalidRect.IsEmpty()) { + // Don't send partial invalidations if we've been decoded before. + invalidRect = mDecoded ? GetFirstFrameRect() + : nsIntRect(); + } if (!invalidRect.IsEmpty() && wasDefaultFlags) { // Update our image container since we're invalidating. UpdateImageContainer(); } if (mNotifying) { // Accumulate the progress changes. We don't permit recursive notifications // because they cause subtle concurrency bugs, so we'll delay sending out // the notifications until we pop back to the lowest invocation of - // NotifyProgress on the stack. + // FinishedSomeDecoding on the stack. mNotifyProgress |= progress; mNotifyInvalidRect.Union(invalidRect); } else { MOZ_ASSERT(mNotifyProgress == NoProgress && mNotifyInvalidRect.IsEmpty(), "Shouldn't have an accumulated change at this point"); progress = image->mProgressTracker->Difference(progress); @@ -1880,68 +2430,18 @@ RasterImage::NotifyProgress(Progress aPr // notifications for them next. progress = image->mProgressTracker->Difference(mNotifyProgress); mNotifyProgress = NoProgress; invalidRect = mNotifyInvalidRect; mNotifyInvalidRect = nsIntRect(); } } -} - -void -RasterImage::FinalizeDecoder(Decoder* aDecoder) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(aDecoder); - MOZ_ASSERT(mError || mHasSize || !aDecoder->HasSize(), - "Should have handed off size by now"); - - // Send out any final notifications. - NotifyProgress(aDecoder->TakeProgress(), - aDecoder->TakeInvalidRect(), - aDecoder->GetDecodeFlags()); - - bool wasSize = aDecoder->IsSizeDecode(); - bool done = aDecoder->GetDecodeDone(); - - if (!wasSize && aDecoder->ChunkCount()) { - Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, - aDecoder->ChunkCount()); - } - - if (done) { - // Do some telemetry if this isn't a size decode. - if (!wasSize) { - Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME, - int32_t(aDecoder->DecodeTime().ToMicroseconds())); - - // We record the speed for only some decoders. The rest have - // SpeedHistogram return HistogramCount. - Telemetry::ID id = aDecoder->SpeedHistogram(); - if (id < Telemetry::HistogramCount) { - int32_t KBps = int32_t(aDecoder->BytesDecoded() / - (1024 * aDecoder->DecodeTime().ToSeconds())); - Telemetry::Accumulate(id, KBps); - } - } - - // Detect errors. - if (aDecoder->HasError() && !aDecoder->WasAborted()) { - DoError(); - } else if (wasSize && !mHasSize) { - DoError(); - } - } - - // If we were a size decode and a full decode was requested, now's the time. - if (done && wasSize && mWantFullDecode) { - mWantFullDecode = false; - RequestDecode(); - } + + return RequestDecodeIfNeeded(rv, aReason, done, wasSize); } already_AddRefed<imgIContainer> RasterImage::Unwrap() { nsCOMPtr<imgIContainer> self(this); return self.forget(); } @@ -1962,17 +2462,17 @@ RasterImage::OptimalImageSizeForDest(con if (CanScale(aFilter, destSize, aFlags)) { DrawableFrameRef frameRef = SurfaceCache::Lookup(ImageKey(this), RasterSurfaceKey(destSize.ToIntSize(), DecodeFlags(aFlags), 0)); - if (frameRef && frameRef->IsImageComplete()) { + if (frameRef && frameRef->ImageComplete()) { return destSize; // We have an existing HQ scale for this size. } if (!frameRef) { // We could HQ scale to this size, but we haven't. Request a scale now. frameRef = LookupFrame(GetRequestedFrameIndex(aWhichFrame), mSize, aFlags); if (frameRef) { RequestScale(frameRef.get(), aFlags, destSize);
--- a/image/src/RasterImage.h +++ b/image/src/RasterImage.h @@ -24,16 +24,17 @@ #include "nsTArray.h" #include "imgFrame.h" #include "nsThreadUtils.h" #include "DecodePool.h" #include "Orientation.h" #include "nsIObserver.h" #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/ReentrantMonitor.h" #include "mozilla/TimeStamp.h" #include "mozilla/TypedEnum.h" #include "mozilla/WeakPtr.h" #include "mozilla/UniquePtr.h" #ifdef DEBUG #include "imgIContainerDebug.h" #endif @@ -126,23 +127,16 @@ class LayerManager; class ImageContainer; class Image; } namespace image { class Decoder; class FrameAnimator; -class SourceBuffer; - -MOZ_BEGIN_ENUM_CLASS(DecodeStrategy, uint8_t) - ASYNC, - SYNC_FOR_SMALL_IMAGES, - SYNC_IF_POSSIBLE -MOZ_END_ENUM_CLASS(DecodeStrategy) class RasterImage MOZ_FINAL : public ImageResource , public nsIProperties , public SupportsWeakPtr<RasterImage> #ifdef DEBUG , public imgIContainerDebug #endif { @@ -163,20 +157,20 @@ public: // Methods inherited from Image nsresult Init(const char* aMimeType, uint32_t aFlags) MOZ_OVERRIDE; virtual void OnSurfaceDiscarded() MOZ_OVERRIDE; // Raster-specific methods - static NS_METHOD WriteToSourceBuffer(nsIInputStream* aIn, void* aClosure, - const char* aFromRawSegment, - uint32_t aToOffset, uint32_t aCount, - uint32_t* aWriteCount); + static NS_METHOD WriteToRasterImage(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount); /* The total number of frames in this image. */ uint32_t GetNumFrames() const { return mFrameCount; } virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE; virtual size_t SizeOfDecoded(gfxMemoryLocation aLocation, MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE; @@ -197,46 +191,34 @@ public: nsresult SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation); /** * Number of times to loop the image. * @note -1 means forever. */ void SetLoopCount(int32_t aLoopCount); - /// Notification that the entire image has been decoded. - void OnDecodingComplete(); - - /** - * Sends the provided progress notifications to ProgressTracker. - * - * Main-thread only. - * - * @param aProgress The progress notifications to send. - * @param aInvalidRect An invalidation rect to send. - * @param aFlags The decode flags used by the decoder that generated - * these notifications, or DECODE_FLAGS_DEFAULT if the - * notifications don't come from a decoder. - */ - void NotifyProgress(Progress aProgress, - const nsIntRect& aInvalidRect = nsIntRect(), - uint32_t aFlags = 0); - - /** - * Records telemetry and does final teardown of the provided decoder. - * - * Main-thread only. - */ - void FinalizeDecoder(Decoder* aDecoder); + /* notification that the entire image has been decoded */ + void DecodingComplete(imgFrame* aFinalFrame); ////////////////////////////////////////////////////////////////////////////// // Network callbacks. ////////////////////////////////////////////////////////////////////////////// + /* Add compressed source data to the imgContainer. + * + * The decoder will use this data, either immediately or at draw time, to + * decode the image. + * + * XXX This method's only caller (WriteToContainer) ignores the return + * value. Should this just return void? + */ + nsresult AddSourceData(const char *aBuffer, uint32_t aCount); + virtual nsresult OnImageDataAvailable(nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aInStr, uint64_t aSourceOffset, uint32_t aCount) MOZ_OVERRIDE; virtual nsresult OnImageDataComplete(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus, @@ -250,17 +232,17 @@ public: * appropriately preallocating the source data buffer. * * We take this approach rather than having the source data management code do * something more complicated (like chunklisting) because HTTP is by far the * dominant source of images, and the Content-Length header is quite reliable. * Thus, pre-allocation simplifies code and reduces the total number of * allocations. */ - nsresult SetSourceSizeHint(uint32_t aSizeHint); + nsresult SetSourceSizeHint(uint32_t sizeHint); /* Provide a hint for the requested resolution of the resulting image. */ void SetRequestedResolution(const nsIntSize requestedResolution) { mRequestedResolution = requestedResolution; } nsIntSize GetRequestedResolution() { return mRequestedResolution; @@ -280,16 +262,24 @@ public: GetURI()->GetSpec(spec); } return spec; } static void Initialize(); private: + friend class DecodePool; + friend class DecodeWorker; + friend class FrameNeededWorker; + friend class NotifyProgressWorker; + + nsresult FinishedSomeDecoding(ShutdownReason aReason = ShutdownReason::DONE, + Progress aProgress = NoProgress); + void DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef, gfxContext* aContext, const nsIntSize& aSize, const ImageRegion& aRegion, GraphicsFilter aFilter, uint32_t aFlags); TemporaryRef<gfx::SourceSurface> CopyFrame(uint32_t aWhichFrame, @@ -309,44 +299,54 @@ private: uint32_t GetCurrentFrameIndex() const; uint32_t GetRequestedFrameIndex(uint32_t aWhichFrame) const; nsIntRect GetFirstFrameRect(); size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation, MallocSizeOf aMallocSizeOf) const; + nsresult DoImageDataComplete(); + already_AddRefed<layers::Image> GetCurrentImage(); void UpdateImageContainer(); + enum RequestDecodeType { + ASYNCHRONOUS, + SYNCHRONOUS_NOTIFY, + SYNCHRONOUS_NOTIFY_AND_SOME_DECODE + }; + NS_IMETHOD RequestDecodeCore(RequestDecodeType aDecodeType); + // We would like to just check if we have a zero lock count, but we can't do // that for animated images because in EnsureAnimExists we lock the image and // never unlock so that animated images always have their lock count >= 1. In // that case we use our animation consumers count as a proxy for lock count. bool IsUnlocked() { return (mLockCount == 0 || (mAnim && mAnimationConsumers == 0)); } - - ////////////////////////////////////////////////////////////////////////////// - // Decoding. - ////////////////////////////////////////////////////////////////////////////// - - already_AddRefed<Decoder> CreateDecoder(bool aDoSizeDecode, uint32_t aFlags); - - void WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify); - - NS_IMETHOD Decode(DecodeStrategy aStrategy, uint32_t aFlags, - bool aDoSizeDecode = false); - private: // data nsIntSize mSize; Orientation mOrientation; + // Whether our frames were decoded using any special flags. + // Some flags (e.g. unpremultiplied data) may not be compatible + // with the browser's needs for displaying the image to the user. + // As such, we may need to redecode if we're being asked for + // a frame with different flags. 0 indicates default flags. + // + // Valid flag bits are imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA + // and imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION. + uint32_t mFrameDecodeFlags; + nsCOMPtr<nsIProperties> mProperties; - /// If this image is animated, a FrameAnimator which manages its animation. + //! All the frames of the image. + // IMPORTANT: if you use mAnim in a method, call EnsureImageIsDecoded() first to ensure + // that the frames actually exist (they may have been discarded to save memory, or + // we maybe decoding on draw). UniquePtr<FrameAnimator> mAnim; // Image locking. uint32_t mLockCount; // Source data members nsCString mSourceDataMimeType; @@ -365,53 +365,82 @@ private: // data // If not cached in mImageContainer, this might have our image container WeakPtr<layers::ImageContainer> mImageContainerCache; #ifdef DEBUG uint32_t mFramesNotified; #endif - // The source data for this image. - nsRefPtr<SourceBuffer> mSourceBuffer; + // Below are the pieces of data that can be accessed on more than one thread + // at once, and hence need to be locked by mDecodingMonitor. + + // BEGIN LOCKED MEMBER VARIABLES + ReentrantMonitor mDecodingMonitor; + + FallibleTArray<char> mSourceData; + + // Decoder and friends + nsRefPtr<Decoder> mDecoder; + DecodeStatus mDecodeStatus; + // END LOCKED MEMBER VARIABLES // The number of frames this image has. uint32_t mFrameCount; // Notification state. Used to avoid recursive notifications. Progress mNotifyProgress; nsIntRect mNotifyInvalidRect; bool mNotifying:1; // Boolean flags (clustered together to conserve space): bool mHasSize:1; // Has SetSize() been called? bool mDecodeOnDraw:1; // Decoding on draw? bool mTransient:1; // Is the image short-lived? bool mDiscardable:1; // Is container discardable? bool mHasSourceData:1; // Do we have source data? - bool mHasBeenDecoded:1; // Decoded at least once? + + // Do we have the frames in decoded form? + bool mDecoded:1; + bool mHasBeenDecoded:1; // Whether we're waiting to start animation. If we get a StartAnimation() call // but we don't yet have more than one frame, mPendingAnimation is set so that // we know to start animation later if/when we have more frames. bool mPendingAnimation:1; // Whether the animation can stop, due to running out // of frames, or no more owning request bool mAnimationFinished:1; // Whether, once we are done doing a size decode, we should immediately kick // off a full decode. bool mWantFullDecode:1; + // Set when a decode worker detects an error off-main-thread. Once the error + // is handled on the main thread, mError is set, but mPendingError is used to + // stop decode work immediately. + bool mPendingError:1; + + // Decoding + nsresult RequestDecodeIfNeeded(nsresult aStatus, ShutdownReason aReason, + bool aDone, bool aWasSize); + nsresult WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify); + nsresult SyncDecode(); + nsresult InitDecoder(bool aDoSizeDecode); + nsresult WriteToDecoder(const char *aBuffer, uint32_t aCount); + nsresult DecodeSomeData(size_t aMaxBytes); + bool IsDecodeFinished(); TimeStamp mDrawStartTime; // Initializes ProgressTracker and resets it on RasterImage destruction. nsAutoPtr<ProgressTrackerInit> mProgressTrackerInit; + nsresult ShutdownDecoder(ShutdownReason aReason); + ////////////////////////////////////////////////////////////////////////////// // Scaling. ////////////////////////////////////////////////////////////////////////////// // Initiates an HQ scale for the given frame, if possible. void RequestScale(imgFrame* aFrame, uint32_t aFlags, const nsIntSize& aSize); @@ -442,16 +471,17 @@ private: // data private: explicit HandleErrorWorker(RasterImage* aImage); nsRefPtr<RasterImage> mImage; }; // Helpers bool CanDiscard(); + bool StoringSourceData() const; protected: explicit RasterImage(ProgressTracker* aProgressTracker = nullptr, ImageURL* aURI = nullptr); bool ShouldAnimate() MOZ_OVERRIDE; friend class ImageFactory;
deleted file mode 100644 --- a/image/src/SourceBuffer.cpp +++ /dev/null @@ -1,470 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "SourceBuffer.h" - -#include <algorithm> -#include <cmath> -#include <cstring> -#include "mozilla/Likely.h" -#include "MainThreadUtils.h" -#include "SurfaceCache.h" - -using std::max; -using std::min; - -namespace mozilla { -namespace image { - -////////////////////////////////////////////////////////////////////////////// -// SourceBufferIterator implementation. -////////////////////////////////////////////////////////////////////////////// - -SourceBufferIterator::State -SourceBufferIterator::AdvanceOrScheduleResume(IResumable* aConsumer) -{ - MOZ_ASSERT(mOwner); - return mOwner->AdvanceIteratorOrScheduleResume(*this, aConsumer); -} - -bool -SourceBufferIterator::RemainingBytesIsNoMoreThan(size_t aBytes) const -{ - MOZ_ASSERT(mOwner); - return mOwner->RemainingBytesIsNoMoreThan(*this, aBytes); -} - - -////////////////////////////////////////////////////////////////////////////// -// SourceBuffer implementation. -////////////////////////////////////////////////////////////////////////////// - -SourceBuffer::SourceBuffer() - : mMutex("image::SourceBuffer") -{ } - -SourceBuffer::~SourceBuffer() -{ - // FallibleTArray doesn't call destructors so we have to manually release. - for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) { - mChunks[i].Release(); - } -} - -nsresult -SourceBuffer::AppendChunk(Maybe<Chunk>&& aChunk) -{ - mMutex.AssertCurrentThreadOwns(); - -#ifdef DEBUG - if (mChunks.Length() > 0) { - NS_WARNING("Appending an extra chunk for SourceBuffer"); - } -#endif - - if (MOZ_UNLIKELY(!aChunk)) { - return NS_ERROR_OUT_OF_MEMORY; - } - - if (MOZ_UNLIKELY(aChunk->AllocationFailed())) { - return NS_ERROR_OUT_OF_MEMORY; - } - - if (MOZ_UNLIKELY(!mChunks.AppendElement(Move(*aChunk)))) { - return NS_ERROR_OUT_OF_MEMORY; - } - - return NS_OK; -} - -Maybe<SourceBuffer::Chunk> -SourceBuffer::CreateChunk(size_t aCapacity) -{ - if (MOZ_UNLIKELY(aCapacity == 0)) { - MOZ_ASSERT_UNREACHABLE("Appending a chunk of zero size?"); - return Nothing(); - } - - // Protect against overflow. - if (MOZ_UNLIKELY(SIZE_MAX - aCapacity < MIN_CHUNK_CAPACITY)) { - return Nothing(); - } - - // Round up to the next multiple of MIN_CHUNK_CAPACITY (which should be the - // size of a page). - size_t roundedCapacity = - (aCapacity + MIN_CHUNK_CAPACITY - 1) & ~(MIN_CHUNK_CAPACITY - 1); - MOZ_ASSERT(roundedCapacity >= aCapacity, "Bad math?"); - MOZ_ASSERT(roundedCapacity - aCapacity < MIN_CHUNK_CAPACITY, "Bad math?"); - - // Use the size of the SurfaceCache as an additional heuristic to avoid - // allocating huge buffers. Generally images do not get smaller when decoded, - // so if we could store the source data in the SurfaceCache, we assume that - // there's no way we'll be able to store the decoded version. - if (MOZ_UNLIKELY(!SurfaceCache::CanHold(roundedCapacity))) { - return Nothing(); - } - - return Some(Chunk(roundedCapacity)); -} - -size_t -SourceBuffer::FibonacciCapacityWithMinimum(size_t aMinCapacity) -{ - mMutex.AssertCurrentThreadOwns(); - - // We grow the source buffer using a Fibonacci growth rate. - - size_t length = mChunks.Length(); - - if (length == 0) { - return aMinCapacity; - } - - if (length == 1) { - return max(2 * mChunks[0].Capacity(), aMinCapacity); - } - - return max(mChunks[length - 1].Capacity() + mChunks[length - 2].Capacity(), - aMinCapacity); -} - -void -SourceBuffer::AddWaitingConsumer(IResumable* aConsumer) -{ - mMutex.AssertCurrentThreadOwns(); - - if (MOZ_UNLIKELY(NS_IsMainThread())) { - NS_WARNING("SourceBuffer consumer on the main thread needed to wait"); - } - - mWaitingConsumers.AppendElement(aConsumer); -} - -void -SourceBuffer::ResumeWaitingConsumers() -{ - mMutex.AssertCurrentThreadOwns(); - - if (mWaitingConsumers.Length() == 0) { - return; - } - - for (uint32_t i = 0 ; i < mWaitingConsumers.Length() ; ++i) { - mWaitingConsumers[i]->Resume(); - mWaitingConsumers[i] = nullptr; - } - - mWaitingConsumers.Clear(); -} - -nsresult -SourceBuffer::ExpectLength(size_t aExpectedLength) -{ - MOZ_ASSERT(aExpectedLength > 0, "Zero expected size?"); - - MutexAutoLock lock(mMutex); - - if (MOZ_UNLIKELY(mStatus)) { - MOZ_ASSERT_UNREACHABLE("ExpectLength after SourceBuffer is complete"); - return NS_OK; - } - - if (MOZ_UNLIKELY(mChunks.Length() > 0)) { - MOZ_ASSERT_UNREACHABLE("Duplicate or post-Append call to ExpectLength"); - return NS_OK; - } - - if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aExpectedLength))))) { - return HandleError(NS_ERROR_OUT_OF_MEMORY); - } - - return NS_OK; -} - -nsresult -SourceBuffer::Append(const char* aData, size_t aLength) -{ - MOZ_ASSERT(aData, "Should have a buffer"); - MOZ_ASSERT(aLength > 0, "Writing a zero-sized chunk"); - - size_t currentChunkCapacity = 0; - size_t currentChunkLength = 0; - char* currentChunkData = nullptr; - size_t currentChunkRemaining = 0; - size_t forCurrentChunk = 0; - size_t forNextChunk = 0; - size_t nextChunkCapacity = 0; - - { - MutexAutoLock lock(mMutex); - - if (MOZ_UNLIKELY(mStatus)) { - // This SourceBuffer is already complete; ignore further data. - return NS_ERROR_FAILURE; - } - - if (MOZ_UNLIKELY(mChunks.Length() == 0)) { - if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aLength))))) { - return HandleError(NS_ERROR_OUT_OF_MEMORY); - } - } - - // Copy out the current chunk's information so we can release the lock. - // Note that this wouldn't be safe if multiple producers were allowed! - Chunk& currentChunk = mChunks.LastElement(); - currentChunkCapacity = currentChunk.Capacity(); - currentChunkLength = currentChunk.Length(); - currentChunkData = currentChunk.Data(); - - // Partition this data between the current chunk and the next chunk. - // (Because we always allocate a chunk big enough to fit everything passed - // to Append, we'll never need more than those two chunks to store - // everything.) - currentChunkRemaining = currentChunkCapacity - currentChunkLength; - forCurrentChunk = min(aLength, currentChunkRemaining); - forNextChunk = aLength - forCurrentChunk; - - // If we'll need another chunk, determine what its capacity should be while - // we still hold the lock. - nextChunkCapacity = forNextChunk > 0 - ? FibonacciCapacityWithMinimum(forNextChunk) - : 0; - } - - // Write everything we can fit into the current chunk. - MOZ_ASSERT(currentChunkLength + forCurrentChunk <= currentChunkCapacity); - memcpy(currentChunkData + currentChunkLength, aData, forCurrentChunk); - - // If there's something left, create a new chunk and write it there. - Maybe<Chunk> nextChunk; - if (forNextChunk > 0) { - MOZ_ASSERT(nextChunkCapacity >= forNextChunk, "Next chunk too small?"); - nextChunk = CreateChunk(nextChunkCapacity); - if (MOZ_LIKELY(nextChunk && !nextChunk->AllocationFailed())) { - memcpy(nextChunk->Data(), aData + forCurrentChunk, forNextChunk); - nextChunk->AddLength(forNextChunk); - } - } - - // Update shared data structures. - { - MutexAutoLock lock(mMutex); - - // Update the length of the current chunk. - Chunk& currentChunk = mChunks.LastElement(); - MOZ_ASSERT(currentChunk.Data() == currentChunkData, "Multiple producers?"); - MOZ_ASSERT(currentChunk.Length() == currentChunkLength, - "Multiple producers?"); - - currentChunk.AddLength(forCurrentChunk); - - // If we created a new chunk, add it to the series. - if (forNextChunk > 0) { - if (MOZ_UNLIKELY(!nextChunk)) { - return HandleError(NS_ERROR_OUT_OF_MEMORY); - } - - if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(Move(nextChunk))))) { - return HandleError(NS_ERROR_OUT_OF_MEMORY); - } - } - - // Resume any waiting readers now that there's new data. - ResumeWaitingConsumers(); - } - - return NS_OK; -} - -void -SourceBuffer::Complete(nsresult aStatus) -{ - MutexAutoLock lock(mMutex); - - if (MOZ_UNLIKELY(mStatus)) { - MOZ_ASSERT_UNREACHABLE("Called Complete more than once"); - return; - } - - if (MOZ_UNLIKELY(NS_SUCCEEDED(aStatus) && IsEmpty())) { - // It's illegal to succeed without writing anything. - aStatus = NS_ERROR_FAILURE; - } - - mStatus = Some(aStatus); - - // Resume any waiting consumers now that we're complete. - ResumeWaitingConsumers(); -} - -bool -SourceBuffer::IsComplete() -{ - MutexAutoLock lock(mMutex); - return bool(mStatus); -} - -size_t -SourceBuffer::SizeOfIncludingThisWithComputedFallback(MallocSizeOf - aMallocSizeOf) const -{ - MutexAutoLock lock(mMutex); - - size_t n = aMallocSizeOf(this); - n += mChunks.SizeOfExcludingThis(aMallocSizeOf); - - for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) { - size_t chunkSize = aMallocSizeOf(mChunks[i].Data()); - - if (chunkSize == 0) { - // We're on a platform where moz_malloc_size_of always returns 0. - chunkSize = mChunks[i].Capacity(); - } - - n += chunkSize; - } - - return n; -} - -bool -SourceBuffer::RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator, - size_t aBytes) const -{ - MutexAutoLock lock(mMutex); - - // If we're not complete, we always say no. - if (!mStatus) { - return false; - } - - // If the iterator's at the end, the answer is trivial. - if (!aIterator.HasMore()) { - return true; - } - - uint32_t iteratorChunk = aIterator.mData.mIterating.mChunk; - size_t iteratorOffset = aIterator.mData.mIterating.mOffset; - size_t iteratorLength = aIterator.mData.mIterating.mLength; - - // Include the bytes the iterator is currently pointing to in the limit, so - // that the current chunk doesn't have to be a special case. - size_t bytes = aBytes + iteratorOffset + iteratorLength; - - // Count the length over all of our chunks, starting with the one that the - // iterator is currently pointing to. (This is O(N), but N is expected to be - // ~1, so it doesn't seem worth caching the length separately.) - size_t lengthSoFar = 0; - for (uint32_t i = iteratorChunk ; i < mChunks.Length() ; ++i) { - lengthSoFar += mChunks[i].Length(); - if (lengthSoFar > bytes) { - return false; - } - } - - return true; -} - -SourceBufferIterator::State -SourceBuffer::AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator, - IResumable* aConsumer) -{ - MutexAutoLock lock(mMutex); - - if (MOZ_UNLIKELY(!aIterator.HasMore())) { - MOZ_ASSERT_UNREACHABLE("Should not advance a completed iterator"); - return SourceBufferIterator::COMPLETE; - } - - if (MOZ_UNLIKELY(mStatus && NS_FAILED(*mStatus))) { - // This SourceBuffer is complete due to an error; all reads fail. - return aIterator.SetComplete(*mStatus); - } - - if (MOZ_UNLIKELY(mChunks.Length() == 0)) { - // We haven't gotten an initial chunk yet. - AddWaitingConsumer(aConsumer); - return aIterator.SetWaiting(); - } - - uint32_t iteratorChunkIdx = aIterator.mData.mIterating.mChunk; - MOZ_ASSERT(iteratorChunkIdx < mChunks.Length()); - - const Chunk& currentChunk = mChunks[iteratorChunkIdx]; - size_t iteratorEnd = aIterator.mData.mIterating.mOffset + - aIterator.mData.mIterating.mLength; - MOZ_ASSERT(iteratorEnd <= currentChunk.Length()); - MOZ_ASSERT(iteratorEnd <= currentChunk.Capacity()); - - if (iteratorEnd < currentChunk.Length()) { - // There's more data in the current chunk. - return aIterator.SetReady(iteratorChunkIdx, currentChunk.Data(), - iteratorEnd, currentChunk.Length() - iteratorEnd); - } - - if (iteratorEnd == currentChunk.Capacity() && - !IsLastChunk(iteratorChunkIdx)) { - // Advance to the next chunk. - const Chunk& nextChunk = mChunks[iteratorChunkIdx + 1]; - return aIterator.SetReady(iteratorChunkIdx + 1, nextChunk.Data(), 0, - nextChunk.Length()); - } - - MOZ_ASSERT(IsLastChunk(iteratorChunkIdx), "Should've advanced"); - - if (mStatus) { - // There's no more data and this SourceBuffer completed successfully. - MOZ_ASSERT(NS_SUCCEEDED(*mStatus), "Handled failures earlier"); - return aIterator.SetComplete(*mStatus); - } - - // We're not complete, but there's no more data right now. Arrange to wake up - // the consumer when we get more data. - AddWaitingConsumer(aConsumer); - return aIterator.SetWaiting(); -} - -nsresult -SourceBuffer::HandleError(nsresult aError) -{ - MOZ_ASSERT(NS_FAILED(aError), "Should have an error here"); - MOZ_ASSERT(aError == NS_ERROR_OUT_OF_MEMORY, - "Unexpected error; may want to notify waiting readers, which " - "HandleError currently doesn't do"); - - mMutex.AssertCurrentThreadOwns(); - - NS_WARNING("SourceBuffer encountered an unrecoverable error"); - - // Record the error. - mStatus = Some(aError); - - // Drop our references to waiting readers. - for (uint32_t i = 0 ; i < mWaitingConsumers.Length() ; ++i) { - mWaitingConsumers[i] = nullptr; - } - mWaitingConsumers.Clear(); - - return *mStatus; -} - -bool -SourceBuffer::IsEmpty() -{ - mMutex.AssertCurrentThreadOwns(); - return mChunks.Length() == 0 || - mChunks[0].Length() == 0; -} - -bool -SourceBuffer::IsLastChunk(uint32_t aChunk) -{ - mMutex.AssertCurrentThreadOwns(); - return aChunk + 1 == mChunks.Length(); -} - -} // namespace image -} // namespace mozilla
deleted file mode 100644 --- a/image/src/SourceBuffer.h +++ /dev/null @@ -1,371 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * SourceBuffer is a single producer, multiple consumer data structure used for - * storing image source (compressed) data. - */ - -#ifndef MOZILLA_IMAGELIB_SOURCEBUFFER_H_ -#define MOZILLA_IMAGELIB_SOURCEBUFFER_H_ - -#include "mozilla/Maybe.h" -#include "mozilla/MemoryReporting.h" -#include "mozilla/Mutex.h" -#include "mozilla/Move.h" -#include "mozilla/MemoryReporting.h" -#include "mozilla/RefPtr.h" -#include "mozilla/UniquePtr.h" -#include "nsRefPtr.h" -#include "nsTArray.h" - -namespace mozilla { -namespace image { - -class SourceBuffer; - -/** - * IResumable is an interface for classes that can schedule themselves to resume - * their work later. An implementation of IResumable generally should post a - * runnable to some event target which continues the work of the task. - */ -struct IResumable -{ - MOZ_DECLARE_REFCOUNTED_TYPENAME(IResumable) - - // Subclasses may or may not be XPCOM classes, so we just require that they - // implement AddRef and Release. - NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; - NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0; - - virtual void Resume() = 0; - -protected: - virtual ~IResumable() { } -}; - -/** - * SourceBufferIterator is a class that allows consumers of image source data to - * read the contents of a SourceBuffer sequentially. - * - * Consumers can advance through the SourceBuffer by calling - * AdvanceOrScheduleResume() repeatedly. After every advance, they should call - * check the return value, which will tell them the iterator's new state. - * - * If WAITING is returned, AdvanceOrScheduleResume() has arranged - * to call the consumer's Resume() method later, so the consumer should save its - * state if needed and stop running. - * - * If the iterator's new state is READY, then the consumer can call Data() and - * Length() to read new data from the SourceBuffer. - * - * Finally, in the COMPLETE state the consumer can call CompletionStatus() to - * get the status passed to SourceBuffer::Complete(). - */ -class SourceBufferIterator MOZ_FINAL -{ -public: - enum State { - START, // The iterator is at the beginning of the buffer. - READY, // The iterator is pointing to new data. - WAITING, // The iterator is blocked and the caller must yield. - COMPLETE // The iterator is pointing to the end of the buffer. - }; - - explicit SourceBufferIterator(SourceBuffer* aOwner) - : mOwner(aOwner) - , mState(START) - { - MOZ_ASSERT(aOwner); - mData.mIterating.mChunk = 0; - mData.mIterating.mData = nullptr; - mData.mIterating.mOffset = 0; - mData.mIterating.mLength = 0; - } - - SourceBufferIterator(SourceBufferIterator&& aOther) - : mOwner(Move(aOther.mOwner)) - , mState(aOther.mState) - , mData(aOther.mData) - { } - - SourceBufferIterator& operator=(SourceBufferIterator&& aOther) - { - mOwner = Move(aOther.mOwner); - mState = aOther.mState; - mData = aOther.mData; - return *this; - } - - /** - * Returns true if there are no more than @aBytes remaining in the - * SourceBuffer. If the SourceBuffer is not yet complete, returns false. - */ - bool RemainingBytesIsNoMoreThan(size_t aBytes) const; - - /** - * Advances the iterator through the SourceBuffer if possible. If not, - * arranges to call the @aConsumer's Resume() method when more data is - * available. - */ - State AdvanceOrScheduleResume(IResumable* aConsumer); - - /// If at the end, returns the status passed to SourceBuffer::Complete(). - nsresult CompletionStatus() const - { - MOZ_ASSERT(mState == COMPLETE, "Calling CompletionStatus() in the wrong state"); - return mState == COMPLETE ? mData.mAtEnd.mStatus : NS_OK; - } - - /// If we're ready to read, returns a pointer to the new data. - const char* Data() const - { - MOZ_ASSERT(mState == READY, "Calling Data() in the wrong state"); - return mState == READY ? mData.mIterating.mData + mData.mIterating.mOffset - : nullptr; - } - - /// If we're ready to read, returns the length of the new data. - size_t Length() const - { - MOZ_ASSERT(mState == READY, "Calling Length() in the wrong state"); - return mState == READY ? mData.mIterating.mLength : 0; - } - -private: - friend class SourceBuffer; - - SourceBufferIterator(const SourceBufferIterator&) = delete; - SourceBufferIterator& operator=(const SourceBufferIterator&) = delete; - - bool HasMore() const { return mState != COMPLETE; } - - State SetReady(uint32_t aChunk, const char* aData, - size_t aOffset, size_t aLength) - { - MOZ_ASSERT(mState != COMPLETE); - mData.mIterating.mChunk = aChunk; - mData.mIterating.mData = aData; - mData.mIterating.mOffset = aOffset; - mData.mIterating.mLength = aLength; - return mState = READY; - } - - State SetWaiting() - { - MOZ_ASSERT(mState != COMPLETE); - MOZ_ASSERT(mState != WAITING, "Did we get a spurious wakeup somehow?"); - return mState = WAITING; - } - - State SetComplete(nsresult aStatus) - { - mData.mAtEnd.mStatus = aStatus; - return mState = COMPLETE; - } - - nsRefPtr<SourceBuffer> mOwner; - - State mState; - - /** - * This union contains our iteration state if we're still iterating (for - * states START, READY, and WAITING) and the status the SourceBuffer was - * completed with if we're in state COMPLETE. - */ - union { - struct { - uint32_t mChunk; - const char* mData; - size_t mOffset; - size_t mLength; - } mIterating; - struct { - nsresult mStatus; - } mAtEnd; - } mData; -}; - -/** - * SourceBuffer is a parallel data structure used for storing image source - * (compressed) data. - * - * SourceBuffer is a single producer, multiple consumer data structure. The - * single producer calls Append() to append data to the buffer. In parallel, - * multiple consumers can call Iterator(), which returns a SourceBufferIterator - * that they can use to iterate through the buffer. The SourceBufferIterator - * returns a series of pointers which remain stable for lifetime of the - * SourceBuffer, and the data they point to is immutable, ensuring that the - * producer never interferes with the consumers. - * - * In order to avoid blocking, SourceBuffer works with SourceBufferIterator to - * keep a list of consumers which are waiting for new data, and to resume them - * when the producer appends more. All consumers must implement the IResumable - * interface to make this possible. - * - * XXX(seth): We should add support for compacting a SourceBuffer. To do this, - * we need to have SourceBuffer keep track of how many live - * SourceBufferIterator's point to it. When the SourceBuffer is complete and no - * live SourceBufferIterator's for it remain, we can compact its contents into a - * single chunk. - */ -class SourceBuffer MOZ_FINAL -{ -public: - MOZ_DECLARE_REFCOUNTED_TYPENAME(image::SourceBuffer) - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(image::SourceBuffer) - - SourceBuffer(); - - ////////////////////////////////////////////////////////////////////////////// - // Producer methods. - ////////////////////////////////////////////////////////////////////////////// - - /** - * If the producer knows how long the source data will be, it should call - * ExpectLength, which enables SourceBuffer to preallocate its buffer. - */ - nsresult ExpectLength(size_t aExpectedLength); - - /// Append the provided data to the buffer. - nsresult Append(const char* aData, size_t aLength); - - /** - * Mark the buffer complete, with a status that will be available to - * consumers. Further calls to Append() are forbidden after Complete(). - */ - void Complete(nsresult aStatus); - - /// Returns true if the buffer is complete. - bool IsComplete(); - - /// Memory reporting. - size_t SizeOfIncludingThisWithComputedFallback(MallocSizeOf) const; - - - ////////////////////////////////////////////////////////////////////////////// - // Consumer methods. - ////////////////////////////////////////////////////////////////////////////// - - /// Returns an iterator to this SourceBuffer. - SourceBufferIterator Iterator() { return SourceBufferIterator(this); } - - -private: - friend class SourceBufferIterator; - - virtual ~SourceBuffer(); - - - ////////////////////////////////////////////////////////////////////////////// - // Chunk type and chunk-related methods. - ////////////////////////////////////////////////////////////////////////////// - - class Chunk - { - public: - explicit Chunk(size_t aCapacity) - : mCapacity(aCapacity) - , mLength(0) - , mData(MakeUnique<char[]>(mCapacity)) - { - MOZ_ASSERT(aCapacity > 0, "Creating zero-capacity chunk"); - } - - Chunk(Chunk&& aOther) - : mCapacity(aOther.mCapacity) - , mLength(aOther.mLength) - , mData(Move(aOther.mData)) - { - aOther.mCapacity = aOther.mLength = 0; - } - - Chunk& operator=(Chunk&& aOther) - { - mCapacity = aOther.mCapacity; - mLength = aOther.mLength; - mData = Move(aOther.mData); - aOther.mCapacity = aOther.mLength = 0; - return *this; - } - - bool AllocationFailed() const { return !mData; } - size_t Capacity() const { return mCapacity; } - size_t Length() const { return mLength; } - char* Data() const { return mData.get(); } - - void AddLength(size_t aAdditionalLength) - { - MOZ_ASSERT(mLength + aAdditionalLength <= mCapacity); - mLength += aAdditionalLength; - } - - void Release() - { - mCapacity = mLength = 0; - mData.reset(); - } - - private: - Chunk(const Chunk&) = delete; - Chunk& operator=(const Chunk&) = delete; - - size_t mCapacity; - size_t mLength; - UniquePtr<char[]> mData; - }; - - nsresult AppendChunk(Maybe<Chunk>&& aChunk); - Maybe<Chunk> CreateChunk(size_t aCapacity); - size_t FibonacciCapacityWithMinimum(size_t aMinCapacity); - - - ////////////////////////////////////////////////////////////////////////////// - // Iterator / consumer methods. - ////////////////////////////////////////////////////////////////////////////// - - void AddWaitingConsumer(IResumable* aConsumer); - void ResumeWaitingConsumers(); - - typedef SourceBufferIterator::State State; - - State AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator, - IResumable* aConsumer); - bool RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator, - size_t aBytes) const; - - - ////////////////////////////////////////////////////////////////////////////// - // Helper methods. - ////////////////////////////////////////////////////////////////////////////// - - nsresult HandleError(nsresult aError); - bool IsEmpty(); - bool IsLastChunk(uint32_t aChunk); - - - ////////////////////////////////////////////////////////////////////////////// - // Member variables. - ////////////////////////////////////////////////////////////////////////////// - - static const size_t MIN_CHUNK_CAPACITY = 4096; - - /// All private members are protected by mMutex. - mutable Mutex mMutex; - - /// The data in this SourceBuffer, stored as a series of Chunks. - FallibleTArray<Chunk> mChunks; - - /// Consumers which are waiting to be notified when new data is available. - nsTArray<nsRefPtr<IResumable>> mWaitingConsumers; - - /// If present, marks this SourceBuffer complete with the given final status. - Maybe<nsresult> mStatus; -}; - -} // namespace image -} // namespace mozilla - -#endif // MOZILLA_IMAGELIB_SOURCEBUFFER_H_
--- a/image/src/SurfaceCache.cpp +++ b/image/src/SurfaceCache.cpp @@ -299,32 +299,29 @@ private: UnregisterWeakMemoryReporter(this); } public: void InitMemoryReporter() { RegisterWeakMemoryReporter(this); } Mutex& GetMutex() { return mMutex; } - InsertOutcome Insert(imgFrame* aSurface, - const Cost aCost, - const ImageKey aImageKey, - const SurfaceKey& aSurfaceKey, - Lifetime aLifetime) + bool Insert(imgFrame* aSurface, + const Cost aCost, + const ImageKey aImageKey, + const SurfaceKey& aSurfaceKey, + Lifetime aLifetime) { - // If this is a duplicate surface, refuse to replace the original. - if (MOZ_UNLIKELY(Lookup(aImageKey, aSurfaceKey))) { - return InsertOutcome::FAILURE_ALREADY_PRESENT; - } + MOZ_ASSERT(!Lookup(aImageKey, aSurfaceKey), + "Inserting a duplicate surface into the SurfaceCache"); // If this is bigger than we can hold after discarding everything we can, // refuse to cache it. - if (MOZ_UNLIKELY(!CanHoldAfterDiscarding(aCost))) { - return InsertOutcome::FAILURE; - } + if (!CanHoldAfterDiscarding(aCost)) + return false; // Remove elements in order of cost until we can fit this in the cache. Note // that locked surfaces aren't in mCosts, so we never remove them here. while (aCost > mAvailableCost) { MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and it still won't fit"); Remove(mCosts.LastElement().GetSurface()); } @@ -339,26 +336,26 @@ public: nsRefPtr<CachedSurface> surface = new CachedSurface(aSurface, aCost, aImageKey, aSurfaceKey, aLifetime); // We require that locking succeed if the image is locked and the surface is // persistent; the caller may need to know this to handle errors correctly. if (cache->IsLocked() && aLifetime == Lifetime::Persistent) { surface->SetLocked(true); if (!surface->IsLocked()) { - return InsertOutcome::FAILURE; + return false; } } // Insert. MOZ_ASSERT(aCost <= mAvailableCost, "Inserting despite too large a cost"); cache->Insert(aSurfaceKey, surface); StartTracking(surface); - return InsertOutcome::SUCCESS; + return true; } void Remove(CachedSurface* aSurface) { MOZ_ASSERT(aSurface, "Should have a surface"); ImageKey imageKey = aSurface->GetImageKey(); nsRefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey); @@ -793,24 +790,24 @@ SurfaceCache::Lookup(const ImageKey a if (!sInstance) { return DrawableFrameRef(); } MutexAutoLock lock(sInstance->GetMutex()); return sInstance->Lookup(aImageKey, aSurfaceKey); } -/* static */ InsertOutcome +/* static */ bool SurfaceCache::Insert(imgFrame* aSurface, const ImageKey aImageKey, const SurfaceKey& aSurfaceKey, Lifetime aLifetime) { if (!sInstance) { - return InsertOutcome::FAILURE; + return false; } MutexAutoLock lock(sInstance->GetMutex()); Cost cost = ComputeCost(aSurfaceKey.Size()); return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey, aLifetime); } /* static */ bool @@ -819,26 +816,16 @@ SurfaceCache::CanHold(const IntSize& aSi if (!sInstance) { return false; } Cost cost = ComputeCost(aSize); return sInstance->CanHold(cost); } -/* static */ bool -SurfaceCache::CanHold(size_t aSize) -{ - if (!sInstance) { - return false; - } - - return sInstance->CanHold(aSize); -} - /* static */ void SurfaceCache::LockImage(Image* aImageKey) { if (sInstance) { MutexAutoLock lock(sInstance->GetMutex()); return sInstance->LockImage(aImageKey); } }
--- a/image/src/SurfaceCache.h +++ b/image/src/SurfaceCache.h @@ -109,22 +109,16 @@ VectorSurfaceKey(const gfx::IntSize& aSi return SurfaceKey(aSize, aSVGContext, aAnimationTime, 0); } MOZ_BEGIN_ENUM_CLASS(Lifetime, uint8_t) Transient, Persistent MOZ_END_ENUM_CLASS(Lifetime) -MOZ_BEGIN_ENUM_CLASS(InsertOutcome, uint8_t) - SUCCESS, // Success (but see Insert documentation). - FAILURE, // Couldn't insert (e.g., for capacity reasons). - FAILURE_ALREADY_PRESENT // A surface with the same key is already present. -MOZ_END_ENUM_CLASS(InsertOutcome) - /** * SurfaceCache is an imagelib-global service that allows caching of temporary * surfaces. Surfaces normally expire from the cache automatically if they go * too long without being accessed. * * SurfaceCache does not hold surfaces directly; instead, it holds imgFrame * objects, which hold surfaces but also layer on additional features specific * to imagelib's needs like animation, padding support, and transparent support @@ -174,74 +168,69 @@ struct SurfaceCache * * @return a DrawableFrameRef to the imgFrame wrapping the requested surface, * or an empty DrawableFrameRef if not found. */ static DrawableFrameRef Lookup(const ImageKey aImageKey, const SurfaceKey& aSurfaceKey); /** - * Insert a surface into the cache. If a surface with the same ImageKey and - * SurfaceKey is already in the cache, Insert returns FAILURE_ALREADY_PRESENT. + * Insert a surface into the cache. It is an error to call this function + * without first calling Lookup to verify that the surface is not already in + * the cache. * * Each surface in the cache has a lifetime, either Transient or Persistent. * Transient surfaces can expire from the cache at any time. Persistent * surfaces can ordinarily also expire from the cache at any time, but if the * image they're associated with is locked, then these surfaces will never * expire. This means that surfaces which cannot be rematerialized should be * inserted with a persistent lifetime *after* the image is locked with * LockImage(); if you use the other order, the surfaces might expire before * LockImage() gets called. * * If a surface cannot be rematerialized, it may be important to know whether - * it was inserted into the cache successfully. Insert() returns FAILURE if it + * it was inserted into the cache successfully. Insert() returns false if it * failed to insert the surface, which could happen because of capacity * reasons, or because it was already freed by the OS. If you aren't inserting * a surface with persistent lifetime, or if the surface isn't associated with - * a locked image, checking for SUCCESS or FAILURE is useless: the surface - * might expire immediately after being inserted, even though Insert() - * returned SUCCESS. Thus, many callers do not need to check the result of - * Insert() at all. + * a locked image, the return value is useless: the surface might expire + * immediately after being inserted, even though Insert() returned true. Thus, + * most callers do not need to check the return value. * * @param aTarget The new surface (wrapped in an imgFrame) to insert into * the cache. * @param aImageKey Key data identifying which image the surface belongs to. * @param aSurfaceKey Key data which uniquely identifies the requested surface. * @param aLifetime Whether this is a transient surface that can always be * allowed to expire, or a persistent surface that * shouldn't expire if the image is locked. - * @return SUCCESS if the surface was inserted successfully. (But see above - * for more information about when you should check this.) - * FAILURE if the surface could not be inserted, e.g. for capacity - * reasons. (But see above for more information about when you - * should check this.) - * FAILURE_ALREADY_PRESENT if a surface with the same ImageKey and - * SurfaceKey already exists in the cache. + * @return false if the surface could not be inserted. Only check this if + * inserting a persistent surface associated with a locked image (see + * above for more information). */ - static InsertOutcome Insert(imgFrame* aSurface, - const ImageKey aImageKey, - const SurfaceKey& aSurfaceKey, - Lifetime aLifetime); + static bool Insert(imgFrame* aSurface, + const ImageKey aImageKey, + const SurfaceKey& aSurfaceKey, + Lifetime aLifetime); /** * Checks if a surface of a given size could possibly be stored in the cache. * If CanHold() returns false, Insert() will always fail to insert the * surface, but the inverse is not true: Insert() may take more information * into account than just image size when deciding whether to cache the * surface, so Insert() may still fail even if CanHold() returns true. * * Use CanHold() to avoid the need to create a temporary surface when we know * for sure the cache can't hold it. * * @param aSize The dimensions of a surface in pixels. * * @return false if the surface cache can't hold a surface of that size. */ static bool CanHold(const IntSize& aSize); - static bool CanHold(size_t aSize); /** * Locks an image, preventing any of that image's surfaces from expiring * unless they have a transient lifetime. * * Regardless of locking, any of an image's surfaces may be removed using * RemoveSurface(), and all of an image's surfaces are removed by * RemoveImage(), whether the image is locked or not.
--- a/image/src/imgFrame.cpp +++ b/image/src/imgFrame.cpp @@ -125,24 +125,23 @@ static bool AllowedImageAndFrameDimensio if (!imageRect.Contains(aFrameRect)) { return false; } return true; } imgFrame::imgFrame() - : mMonitor("imgFrame") + : mMutex("imgFrame") , mDecoded(0, 0, 0, 0) , mLockCount(0) , mTimeout(100) , mDisposalMethod(DisposalMethod::NOT_SPECIFIED) , mBlendMethod(BlendMethod::OVER) , mHasNoAlpha(false) - , mAborted(false) , mPalettedImageData(nullptr) , mPaletteDepth(0) , mNonPremult(false) , mSinglePixel(false) , mCompositingFailed(false) , mOptimizable(false) { static bool hasCheckedOptimize = false; @@ -151,83 +150,74 @@ imgFrame::imgFrame() gDisableOptimize = true; } hasCheckedOptimize = true; } } imgFrame::~imgFrame() { -#ifdef DEBUG - MonitorAutoLock lock(mMonitor); - MOZ_ASSERT(mAborted || IsImageCompleteInternal()); -#endif - moz_free(mPalettedImageData); mPalettedImageData = nullptr; } nsresult imgFrame::InitForDecoder(const nsIntSize& aImageSize, const nsIntRect& aRect, SurfaceFormat aFormat, uint8_t aPaletteDepth /* = 0 */, bool aNonPremult /* = false */) { // Assert for properties that should be verified by decoders, // warn for properties related to bad content. if (!AllowedImageAndFrameDimensions(aImageSize, aRect)) { NS_WARNING("Should have legal image size"); - mAborted = true; return NS_ERROR_FAILURE; } mImageSize = aImageSize.ToIntSize(); mOffset.MoveTo(aRect.x, aRect.y); mSize.SizeTo(aRect.width, aRect.height); mFormat = aFormat; mPaletteDepth = aPaletteDepth; mNonPremult = aNonPremult; if (aPaletteDepth != 0) { // We're creating for a paletted image. if (aPaletteDepth > 8) { NS_WARNING("Should have legal palette depth"); NS_ERROR("This Depth is not supported"); - mAborted = true; return NS_ERROR_FAILURE; } // Use the fallible allocator here. Paletted images always use 1 byte per // pixel, so calculating the amount of memory we need is straightforward. mPalettedImageData = static_cast<uint8_t*>(moz_malloc(PaletteDataLength() + (mSize.width * mSize.height))); if (!mPalettedImageData) NS_WARNING("moz_malloc for paletted image data should succeed"); NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY); } else { MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitForDecoder() twice?"); mVBuf = AllocateBufferForImage(mSize, mFormat); if (!mVBuf) { - mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } if (mVBuf->OnHeap()) { int32_t stride = VolatileSurfaceStride(mSize, mFormat); VolatileBufferPtr<uint8_t> ptr(mVBuf); memset(ptr, 0, stride * mSize.height); } mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat); if (!mImageSurface) { NS_WARNING("Failed to create VolatileDataSourceSurface"); - mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } } return NS_OK; } nsresult @@ -236,17 +226,16 @@ imgFrame::InitWithDrawable(gfxDrawable* const SurfaceFormat aFormat, GraphicsFilter aFilter, uint32_t aImageFlags) { // Assert for properties that should be verified by decoders, // warn for properties related to bad content. if (!AllowedImageSize(aSize.width, aSize.height)) { NS_WARNING("Should have legal image size"); - mAborted = true; return NS_ERROR_FAILURE; } mImageSize = aSize.ToIntSize(); mOffset.MoveTo(0, 0); mSize.SizeTo(aSize.width, aSize.height); mFormat = aFormat; @@ -259,24 +248,22 @@ imgFrame::InitWithDrawable(gfxDrawable* if (canUseDataSurface) { // It's safe to use data surfaces for content on this platform, so we can // get away with using volatile buffers. MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitWithDrawable() twice?"); mVBuf = AllocateBufferForImage(mSize, mFormat); if (!mVBuf) { - mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } int32_t stride = VolatileSurfaceStride(mSize, mFormat); VolatileBufferPtr<uint8_t> ptr(mVBuf); if (!ptr) { - mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } if (mVBuf->OnHeap()) { memset(ptr, 0, stride * mSize.height); } mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat); target = gfxPlatform::GetPlatform()-> @@ -288,50 +275,44 @@ imgFrame::InitWithDrawable(gfxDrawable* // the documentation for this method. MOZ_ASSERT(!mOptSurface, "Called imgFrame::InitWithDrawable() twice?"); target = gfxPlatform::GetPlatform()-> CreateOffscreenContentDrawTarget(mSize, mFormat); } if (!target) { - mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } // Draw using the drawable the caller provided. nsIntRect imageRect(0, 0, mSize.width, mSize.height); nsRefPtr<gfxContext> ctx = new gfxContext(target); gfxUtils::DrawPixelSnapped(ctx, aDrawable, ThebesIntSize(mSize), ImageRegion::Create(imageRect), mFormat, aFilter, aImageFlags); if (canUseDataSurface && !mImageSurface) { NS_WARNING("Failed to create VolatileDataSourceSurface"); - mAborted = true; return NS_ERROR_OUT_OF_MEMORY; } if (!canUseDataSurface) { // We used an offscreen surface, which is an "optimized" surface from // imgFrame's perspective. mOptSurface = target->Snapshot(); } - // If we reach this point, we should regard ourselves as complete. - mDecoded = GetRect(); - MOZ_ASSERT(IsImageComplete()); - return NS_OK; } nsresult imgFrame::Optimize() { MOZ_ASSERT(NS_IsMainThread()); - mMonitor.AssertCurrentThreadOwns(); + mMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(mLockCount == 1, "Should only optimize when holding the lock exclusively"); // Don't optimize during shutdown because gfxPlatform may not be available. if (ShutdownTracker::ShutdownHasStarted()) return NS_OK; if (!mOptimizable || gDisableOptimize) @@ -469,17 +450,17 @@ imgFrame::SurfaceForDrawing(bool bool aDoTile, gfxContext* aContext, const nsIntMargin& aPadding, gfxRect& aImageRect, ImageRegion& aRegion, SourceSurface* aSurface) { MOZ_ASSERT(NS_IsMainThread()); - mMonitor.AssertCurrentThreadOwns(); + mMutex.AssertCurrentThreadOwns(); IntSize size(int32_t(aImageRect.Width()), int32_t(aImageRect.Height())); if (!aDoPadding && !aDoPartialDecode) { NS_ASSERTION(!mSinglePixel, "This should already have been handled"); return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, ThebesIntSize(size)), mFormat); } gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height); @@ -530,25 +511,25 @@ bool imgFrame::Draw(gfxContext* aContext MOZ_ASSERT(NS_IsMainThread()); NS_ASSERTION(!aRegion.Rect().IsEmpty(), "Drawing empty region!"); NS_ASSERTION(!aRegion.IsRestricted() || !aRegion.Rect().Intersect(aRegion.Restriction()).IsEmpty(), "We must be allowed to sample *some* source pixels!"); NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!"); - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); nsIntMargin padding(mOffset.y, mImageSize.width - (mOffset.x + mSize.width), mImageSize.height - (mOffset.y + mSize.height), mOffset.x); bool doPadding = padding != nsIntMargin(0,0,0,0); - bool doPartialDecode = !IsImageCompleteInternal(); + bool doPartialDecode = !ImageCompleteInternal(); if (mSinglePixel && !doPadding && !doPartialDecode) { if (mSinglePixelColor.a == 0.0) { return true; } RefPtr<DrawTarget> dt = aContext->GetDrawTarget(); dt->FillRect(ToRect(aRegion.Rect()), ColorPattern(mSinglePixelColor), @@ -583,47 +564,40 @@ bool imgFrame::Draw(gfxContext* aContext aFilter, aImageFlags); } return true; } nsresult imgFrame::ImageUpdated(const nsIntRect& aUpdateRect) { - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); return ImageUpdatedInternal(aUpdateRect); } nsresult imgFrame::ImageUpdatedInternal(const nsIntRect& aUpdateRect) { - mMonitor.AssertCurrentThreadOwns(); + mMutex.AssertCurrentThreadOwns(); mDecoded.UnionRect(mDecoded, aUpdateRect); // clamp to bounds, in case someone sends a bogus updateRect (I'm looking at // you, gif decoder) nsIntRect boundsRect(mOffset, nsIntSize(mSize.width, mSize.height)); mDecoded.IntersectRect(mDecoded, boundsRect); - // If the image is now complete, wake up anyone who's waiting. - if (IsImageCompleteInternal()) { - mMonitor.NotifyAll(); - } - return NS_OK; } void -imgFrame::Finish(Opacity aFrameOpacity /* = Opacity::SOME_TRANSPARENCY */, - DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */, - int32_t aRawTimeout /* = 0 */, - BlendMethod aBlendMethod /* = BlendMethod::OVER */) +imgFrame::Finish(Opacity aFrameOpacity, DisposalMethod aDisposalMethod, + int32_t aRawTimeout, BlendMethod aBlendMethod) { - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); if (aFrameOpacity == Opacity::OPAQUE) { mHasNoAlpha = true; } mDisposalMethod = aDisposalMethod; mTimeout = aRawTimeout; @@ -634,34 +608,34 @@ imgFrame::Finish(Opacity aFrameOpacity / nsIntRect imgFrame::GetRect() const { return nsIntRect(mOffset, nsIntSize(mSize.width, mSize.height)); } int32_t imgFrame::GetStride() const { - mMonitor.AssertCurrentThreadOwns(); + mMutex.AssertCurrentThreadOwns(); if (mImageSurface) { return mImageSurface->Stride(); } return VolatileSurfaceStride(mSize, mFormat); } SurfaceFormat imgFrame::GetFormat() const { - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); return mFormat; } uint32_t imgFrame::GetImageBytesPerRow() const { - mMonitor.AssertCurrentThreadOwns(); + mMutex.AssertCurrentThreadOwns(); if (mVBuf) return mSize.width * BytesPerPixel(mFormat); if (mPaletteDepth) return mSize.width; return 0; @@ -670,24 +644,24 @@ uint32_t imgFrame::GetImageBytesPerRow() uint32_t imgFrame::GetImageDataLength() const { return GetImageBytesPerRow() * mSize.height; } void imgFrame::GetImageData(uint8_t** aData, uint32_t* aLength) const { - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); GetImageDataInternal(aData, aLength); } void imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const { - mMonitor.AssertCurrentThreadOwns(); + mMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); if (mImageSurface) { *aData = mVBufPtr; MOZ_ASSERT(*aData, "mImageSurface is non-null, but mVBufPtr is null in GetImageData"); } else if (mPalettedImageData) { *aData = mPalettedImageData + PaletteDataLength(); MOZ_ASSERT(*aData, "mPalettedImageData is non-null, but result is null in GetImageData"); @@ -731,17 +705,17 @@ uint32_t* imgFrame::GetPaletteData() con uint32_t length; GetPaletteData(&data, &length); return data; } nsresult imgFrame::LockImageData() { - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); MOZ_ASSERT(mLockCount >= 0, "Unbalanced locks and unlocks"); if (mLockCount < 0) { return NS_ERROR_FAILURE; } mLockCount++; @@ -763,17 +737,17 @@ imgFrame::LockImageData() return Deoptimize(); } nsresult imgFrame::Deoptimize() { MOZ_ASSERT(NS_IsMainThread()); - mMonitor.AssertCurrentThreadOwns(); + mMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(!mImageSurface); if (!mImageSurface) { if (mVBuf) { VolatileBufferPtr<uint8_t> ref(mVBuf); if (ref.WasBufferPurged()) return NS_ERROR_FAILURE; @@ -832,17 +806,17 @@ imgFrame::Deoptimize() mVBufPtr = mVBuf; return NS_OK; } void imgFrame::AssertImageDataLocked() const { #ifdef DEBUG - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); #endif } class UnlockImageDataRunnable : public nsRunnable { public: explicit UnlockImageDataRunnable(imgFrame* aTarget) @@ -855,26 +829,23 @@ public: private: nsRefPtr<imgFrame> mTarget; }; nsresult imgFrame::UnlockImageData() { - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); MOZ_ASSERT(mLockCount > 0, "Unlocking an unlocked image!"); if (mLockCount <= 0) { return NS_ERROR_FAILURE; } - MOZ_ASSERT(mLockCount > 1 || IsImageCompleteInternal() || mAborted, - "Should have marked complete or aborted before unlocking"); - // If we're about to become unlocked, we don't need to hold on to our data // surface anymore. (But we don't need to do anything for paletted images, // which don't have surfaces.) if (mLockCount == 1 && !mPalettedImageData) { // We can't safely optimize off-main-thread, so create a runnable to do it. if (!NS_IsMainThread()) { nsCOMPtr<nsIRunnable> runnable = new UnlockImageDataRunnable(this); NS_DispatchToMainThread(runnable); @@ -922,24 +893,24 @@ imgFrame::IsSinglePixel() const { MOZ_ASSERT(NS_IsMainThread()); return mSinglePixel; } TemporaryRef<SourceSurface> imgFrame::GetSurface() { - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); return GetSurfaceInternal(); } TemporaryRef<SourceSurface> imgFrame::GetSurfaceInternal() { - mMonitor.AssertCurrentThreadOwns(); + mMutex.AssertCurrentThreadOwns(); if (mOptSurface) { if (mOptSurface->IsValid()) return mOptSurface; else mOptSurface = nullptr; } @@ -954,34 +925,34 @@ imgFrame::GetSurfaceInternal() return nullptr; return CreateLockedSurface(mVBuf, mSize, mFormat); } TemporaryRef<DrawTarget> imgFrame::GetDrawTarget() { - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); uint8_t* data; uint32_t length; GetImageDataInternal(&data, &length); if (!data) { return nullptr; } int32_t stride = GetStride(); return gfxPlatform::GetPlatform()-> CreateDrawTargetForData(data, mSize, stride, mFormat); } AnimationData imgFrame::GetAnimationData() const { - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); uint8_t* data; if (mPalettedImageData) { data = mPalettedImageData; } else { uint32_t length; GetImageDataInternal(&data, &length); @@ -991,67 +962,38 @@ imgFrame::GetAnimationData() const return AnimationData(data, PaletteDataLength(), mTimeout, GetRect(), mBlendMethod, mDisposalMethod, hasAlpha); } ScalingData imgFrame::GetScalingData() const { - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); MOZ_ASSERT(mLockCount > 0, "Image data should be locked"); MOZ_ASSERT(!GetIsPaletted(), "GetScalingData can't handle paletted images"); uint8_t* data; uint32_t length; GetImageDataInternal(&data, &length); return ScalingData(data, mSize, GetImageBytesPerRow(), mFormat); } -void -imgFrame::Abort() +bool +imgFrame::ImageComplete() const { - MonitorAutoLock lock(mMonitor); - - mAborted = true; - - // Wake up anyone who's waiting. - if (IsImageCompleteInternal()) { - mMonitor.NotifyAll(); - } + MutexAutoLock lock(mMutex); + return ImageCompleteInternal(); } bool -imgFrame::IsImageComplete() const -{ - MonitorAutoLock lock(mMonitor); - return IsImageCompleteInternal(); -} - -void -imgFrame::WaitUntilComplete() const +imgFrame::ImageCompleteInternal() const { - MonitorAutoLock lock(mMonitor); - - while (true) { - // Return if we're aborted or complete. - if (mAborted || IsImageCompleteInternal()) { - return; - } - - // Not complete yet, so we'll have to wait. - mMonitor.Wait(); - } -} - -bool -imgFrame::IsImageCompleteInternal() const -{ - mMonitor.AssertCurrentThreadOwns(); + mMutex.AssertCurrentThreadOwns(); return mDecoded.IsEqualInterior(nsIntRect(mOffset.x, mOffset.y, mSize.width, mSize.height)); } bool imgFrame::GetCompositingFailed() const { MOZ_ASSERT(NS_IsMainThread()); return mCompositingFailed; @@ -1062,17 +1004,17 @@ void imgFrame::SetCompositingFailed(bool MOZ_ASSERT(NS_IsMainThread()); mCompositingFailed = val; } size_t imgFrame::SizeOfExcludingThis(gfxMemoryLocation aLocation, MallocSizeOf aMallocSizeOf) const { - MonitorAutoLock lock(mMonitor); + MutexAutoLock lock(mMutex); // aMallocSizeOf is only used if aLocation==gfxMemoryLocation::IN_PROCESS_HEAP. It // should be nullptr otherwise. NS_ABORT_IF_FALSE( (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP && aMallocSizeOf) || (aLocation != gfxMemoryLocation::IN_PROCESS_HEAP && !aMallocSizeOf), "mismatch between aLocation and aMallocSizeOf");
--- a/image/src/imgFrame.h +++ b/image/src/imgFrame.h @@ -3,18 +3,18 @@ * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef imgFrame_h #define imgFrame_h #include "mozilla/MemoryReporting.h" -#include "mozilla/Monitor.h" #include "mozilla/Move.h" +#include "mozilla/Mutex.h" #include "mozilla/TypedEnum.h" #include "mozilla/VolatileBuffer.h" #include "gfxDrawable.h" #include "imgIContainer.h" #include "MainThreadUtils.h" namespace mozilla { namespace image { @@ -177,58 +177,29 @@ public: bool Draw(gfxContext* aContext, const ImageRegion& aRegion, GraphicsFilter aFilter, uint32_t aImageFlags); nsresult ImageUpdated(const nsIntRect &aUpdateRect); /** * Mark this imgFrame as completely decoded, and set final options. * - * You must always call either Finish() or Abort() before releasing the last - * RawAccessFrameRef pointing to an imgFrame. - * * @param aFrameOpacity Whether this imgFrame is opaque. * @param aDisposalMethod For animation frames, how this imgFrame is cleared * from the compositing frame before the next frame is * displayed. * @param aRawTimeout For animation frames, the timeout in milliseconds * before the next frame is displayed. This timeout is * not necessarily the timeout that will actually be * used; see FrameAnimator::GetTimeoutForFrame. * @param aBlendMethod For animation frames, a blending method to be used * when compositing this frame. */ - void Finish(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY, - DisposalMethod aDisposalMethod = DisposalMethod::KEEP, - int32_t aRawTimeout = 0, - BlendMethod aBlendMethod = BlendMethod::OVER); - - /** - * Mark this imgFrame as aborted. This informs the imgFrame that if it isn't - * completely decoded now, it never will be. - * - * You must always call either Finish() or Abort() before releasing the last - * RawAccessFrameRef pointing to an imgFrame. - */ - void Abort(); - - /** - * Returns true if this imgFrame is completely decoded. - */ - bool IsImageComplete() const; - - /** - * Blocks until this imgFrame is either completely decoded, or is marked as - * aborted. - * - * Note that calling this on the main thread _blocks the main thread_. Be very - * careful in your use of this method to avoid excessive main thread jank or - * deadlock. - */ - void WaitUntilComplete() const; + void Finish(Opacity aFrameOpacity, DisposalMethod aDisposalMethod, + int32_t aRawTimeout, BlendMethod aBlendMethod); IntSize GetImageSize() { return mImageSize; } nsIntRect GetRect() const; IntSize GetSize() const { return mSize; } bool NeedsPadding() const { return mOffset != nsIntPoint(0, 0); } void GetImageData(uint8_t **aData, uint32_t *length) const; uint8_t* GetImageData() const; @@ -242,16 +213,18 @@ public: * * This should only be used for assertions. */ SurfaceFormat GetFormat() const; AnimationData GetAnimationData() const; ScalingData GetScalingData() const; + bool ImageComplete() const; + bool GetCompositingFailed() const; void SetCompositingFailed(bool val); void SetOptimizable(); Color SinglePixelColor() const; bool IsSinglePixel() const; @@ -267,17 +240,17 @@ private: // methods nsresult LockImageData(); nsresult UnlockImageData(); nsresult Optimize(); nsresult Deoptimize(); void AssertImageDataLocked() const; - bool IsImageCompleteInternal() const; + bool ImageCompleteInternal() const; nsresult ImageUpdatedInternal(const nsIntRect& aUpdateRect); void GetImageDataInternal(uint8_t **aData, uint32_t *length) const; uint32_t GetImageBytesPerRow() const; uint32_t GetImageDataLength() const; int32_t GetStride() const; TemporaryRef<SourceSurface> GetSurfaceInternal(); uint32_t PaletteDataLength() const @@ -305,20 +278,20 @@ private: // methods SourceSurface* aSurface); private: // data friend class DrawableFrameRef; friend class RawAccessFrameRef; friend class UnlockImageDataRunnable; ////////////////////////////////////////////////////////////////////////////// - // Thread-safe mutable data, protected by mMonitor. + // Thread-safe mutable data, protected by mMutex. ////////////////////////////////////////////////////////////////////////////// - mutable Monitor mMonitor; + mutable Mutex mMutex; RefPtr<DataSourceSurface> mImageSurface; RefPtr<SourceSurface> mOptSurface; RefPtr<VolatileBuffer> mVBuf; VolatileBufferPtr<uint8_t> mVBufPtr; nsIntRect mDecoded; @@ -329,17 +302,16 @@ private: // data //! Raw timeout for this frame. (See FrameAnimator::GetTimeoutForFrame.) int32_t mTimeout; // -1 means display forever. DisposalMethod mDisposalMethod; BlendMethod mBlendMethod; SurfaceFormat mFormat; bool mHasNoAlpha; - bool mAborted; ////////////////////////////////////////////////////////////////////////////// // Effectively const data, only mutated in the Init methods. ////////////////////////////////////////////////////////////////////////////// IntSize mImageSize; IntSize mSize;
--- a/image/src/moz.build +++ b/image/src/moz.build @@ -28,17 +28,16 @@ UNIFIED_SOURCES += [ 'ImageOps.cpp', 'ImageWrapper.cpp', 'imgFrame.cpp', 'imgTools.cpp', 'MultipartImage.cpp', 'OrientedImage.cpp', 'ScriptedNotificationObserver.cpp', 'ShutdownTracker.cpp', - 'SourceBuffer.cpp', 'SurfaceCache.cpp', 'SVGDocumentWrapper.cpp', 'VectorImage.cpp', ] # These files can't be unified because of ImageLogging.h #include order issues. SOURCES += [ 'imgLoader.cpp',
--- a/image/test/crashtests/crashtests.list +++ b/image/test/crashtests/crashtests.list @@ -11,17 +11,17 @@ load colormap-range.gif # they have image sizes of 65535x65535 which is larger than we allow load invalid-size.gif # this image has a valid size for the first frame, but the second frame is 65535x65535 # AddressSanitizer sometimes fails with a CHECK on an allocation caused by this. skip-if(AddressSanitizer) load invalid-size-second-frame.gif # Animated gifs with a very large canvas, but tiny actual content. load delaytest.html?523528-1.gif -skip load delaytest.html?523528-2.gif # disabled due to bug 1079627 +load delaytest.html?523528-2.gif # this would have exposed the leak discovered in bug 642902 load invalid-icc-profile.jpg # maximum (256) width and height icons that we currently (due to bug 668068) # interpret as 0-width and 0-height. load 256-width.ico load 256-height.ico
--- a/image/test/reftest/gif/reftest.list +++ b/image/test/reftest/gif/reftest.list @@ -12,18 +12,17 @@ == small-background-size.gif small-background-size-ref.gif == small-background-size-2.gif small-background-size-2-ref.gif # a transparent gif that disposes previous frames with clear; we must properly # clear each frame to pass. random == delaytest.html?transparent-animation.gif transparent-animation-finalframe.gif # incorrect timing dependence (bug 558678) # test for bug 641198 -# Marked random because of increased failure frequency after bug 1117607. -skip == test_bug641198.html animation2a-finalframe.gif # bug 773482 +skip-if(B2G) random-if(Android) == test_bug641198.html animation2a-finalframe.gif # bug 773482 # Bug 1062886: a gif with a single color and an offset == one-color-offset.gif one-color-offset-ref.gif # Bug 1068230 == tile-transform.html tile-transform-ref.html # webcam-simulacrum.mgif is a hand-edited file containing red.gif and blue.gif, @@ -43,10 +42,9 @@ skip == test_bug641198.html animation2a- # \r\n # <contents of blue.gif> (no newline) # --BOUNDARYOMG--\r\n # # (The boundary is arbitrary, and just has to be defined as something that # won't be in the text of the contents themselves. --$(boundary)\r\n means # "Here is the beginning of a boundary," and --$(boundary)-- means "All done # sending you parts.") -skip-if(B2G) random-if(browserIsRemote) HTTP == webcam.html blue.gif # bug 773482 - +skip-if(B2G) HTTP == webcam.html blue.gif # bug 773482
--- a/image/test/reftest/ico/cur/reftest.list +++ b/image/test/reftest/ico/cur/reftest.list @@ -1,4 +1,4 @@ # ICO BMP and PNG mixed tests -random == wrapper.html?pointer.cur wrapper.html?pointer.png +== wrapper.html?pointer.cur wrapper.html?pointer.png
--- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -966,17 +966,17 @@ fails == 413027-3.html 413027-3-ref.html == 413286-2b.html 413286-2-ref.html == 413286-2c.html 413286-2-ref.html == 413286-3.html 413286-3-ref.html == 413286-4a.html 413286-4-ref.html == 413286-4b.html 413286-4-ref.html == 413286-5.html 413286-5-ref.html == 413286-6.html 413286-6-ref.html skip-if(cocoaWidget) == 413292-1.html 413292-1-ref.html # disabling due to failure loading on some mac tinderboxes. See bug 432954 -fuzzy-if(Android&&AndroidVersion>=15,11,15) fuzzy-if(B2G,11,17) == 413361-1.html 413361-1-ref.html +fuzzy-if(Android&&AndroidVersion>=15,11,15) == 413361-1.html 413361-1-ref.html == 413840-background-unchanged.html 413840-background-unchanged-ref.html == 413840-ltr-offsets.html 413840-ltr-offsets-ref.html == 413840-rtl-offsets.html 413840-rtl-offsets-ref.html == 413840-pushed-line-bullet.html 413840-pushed-line-bullet-ref.html == 413840-bullet-first-line.html 413840-bullet-first-line-ref.html == 413982.html 413982-ref.html == 414123.xhtml 414123-ref.xhtml == 414638.html 414638-ref.html
--- a/media/mtransport/runnable_utils.h +++ b/media/mtransport/runnable_utils.h @@ -31,20 +31,17 @@ RunOnThreadInternal(nsIEventTarget *thre nsresult rv; rv = thread->IsOnCurrentThread(&on); // If the target thread has already shut down, we don't want to assert. if (rv != NS_ERROR_NOT_INITIALIZED) { MOZ_ASSERT(NS_SUCCEEDED(rv)); } - if (NS_WARN_IF(NS_FAILED(rv))) { - // we're going to destroy the runnable on this thread! - return rv; - } + NS_ENSURE_SUCCESS(rv, rv); if (!on) { return thread->Dispatch(runnable_ref, flags); } } return runnable_ref->Run(); } template<RunnableResult result>
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -3786,16 +3786,19 @@ pref("image.mem.discardable", true); pref("image.mem.decodeondraw", true); // Allows image locking of decoded image data in content processes. pref("image.mem.allow_locking_in_content_processes", true); // Chunk size for calls to the image decoders pref("image.mem.decode_bytes_at_a_time", 16384); +// The longest time we can spend in an iteration of an async decode +pref("image.mem.max_ms_before_yield", 5); + // Minimum timeout for expiring unused images from the surface cache, in // milliseconds. This controls how long we store cached temporary surfaces. pref("image.mem.surfacecache.min_expiration_ms", 60000); // 60ms // Maximum size for the surface cache, in kilobytes. pref("image.mem.surfacecache.max_size_kb", 1048576); // 1GB // The surface cache's size, within the constraints of the maximum size set
--- a/netwerk/sctp/datachannel/DataChannel.cpp +++ b/netwerk/sctp/datachannel/DataChannel.cpp @@ -36,17 +36,16 @@ #include "nsIObserver.h" #include "mozilla/Services.h" #include "nsProxyRelease.h" #include "nsThread.h" #include "nsThreadUtils.h" #include "nsAutoPtr.h" #include "nsNetUtil.h" #include "mozilla/StaticPtr.h" -#include "mozilla/unused.h" #ifdef MOZ_PEERCONNECTION #include "mtransport/runnable_utils.h" #endif #define DATACHANNEL_LOG(args) LOG(args) #include "DataChannel.h" #include "DataChannelProtocol.h" @@ -2306,17 +2305,17 @@ DataChannelConnection::SendBinary(DataCh class ReadBlobRunnable : public nsRunnable { public: ReadBlobRunnable(DataChannelConnection* aConnection, uint16_t aStream, nsIInputStream* aBlob) : mConnection(aConnection), mStream(aStream), mBlob(aBlob) - {} + { } NS_IMETHODIMP Run() { // ReadBlob() is responsible to releasing the reference DataChannelConnection *self = mConnection; self->ReadBlob(mConnection.forget(), mStream, mBlob); return NS_OK; } @@ -2345,89 +2344,49 @@ DataChannelConnection::SendBlob(uint16_t } } nsCOMPtr<nsIRunnable> runnable = new ReadBlobRunnable(this, stream, aBlob); mInternalIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL); return 0; } -class DataChannelBlobSendRunnable : public nsRunnable -{ -public: - DataChannelBlobSendRunnable(already_AddRefed<DataChannelConnection>& aConnection, - uint16_t aStream) - : mConnection(aConnection) - , mStream(aStream) {} - - ~DataChannelBlobSendRunnable() - { - if (!NS_IsMainThread() && mConnection) { - MOZ_ASSERT(false); - // explicitly leak the connection if destroyed off mainthread - unused << mConnection.forget().take(); - } - } - - NS_IMETHODIMP Run() - { - ASSERT_WEBRTC(NS_IsMainThread()); - - mConnection->SendBinaryMsg(mStream, mData); - mConnection = nullptr; - return NS_OK; - } - - // explicitly public so we can avoid allocating twice and copying - nsCString mData; - -private: - // Note: we can be destroyed off the target thread, so be careful not to let this - // get Released()ed on the temp thread! - nsRefPtr<DataChannelConnection> mConnection; - uint16_t mStream; -}; - void DataChannelConnection::ReadBlob(already_AddRefed<DataChannelConnection> aThis, uint16_t aStream, nsIInputStream* aBlob) { // NOTE: 'aThis' has been forgotten by the caller to avoid releasing // it off mainthread; if PeerConnectionImpl has released then we want // ~DataChannelConnection() to run on MainThread // XXX to do this safely, we must enqueue these atomically onto the - // output socket. We need a sender thread(s?) to enqueue data into the + // output socket. We need a sender thread(s?) to enque data into the // socket and to avoid main-thread IO that might block. Even on a // background thread, we may not want to block on one stream's data. // I.e. run non-blocking and service multiple channels. // For now as a hack, send as a single blast of queued packets which may // be deferred until buffer space is available. + nsCString temp; uint64_t len; nsCOMPtr<nsIThread> mainThread; NS_GetMainThread(getter_AddRefs(mainThread)); - // Must not let Dispatching it cause the DataChannelConnection to get - // released on the wrong thread. Using WrapRunnable(nsRefPtr<DataChannelConnection>(aThis),... - // will occasionally cause aThis to get released on this thread. Also, an explicit Runnable - // lets us avoid copying the blob data an extra time. - nsRefPtr<DataChannelBlobSendRunnable> runnable = new DataChannelBlobSendRunnable(aThis, - aStream); - // avoid copying the blob data by passing the mData from the runnable if (NS_FAILED(aBlob->Available(&len)) || - NS_FAILED(NS_ReadInputStreamToString(aBlob, runnable->mData, len))) { + NS_FAILED(NS_ReadInputStreamToString(aBlob, temp, len))) { // Bug 966602: Doesn't return an error to the caller via onerror. // We must release DataChannelConnection on MainThread to avoid issues (bug 876167) - // aThis is now owned by the runnable; release it there - NS_ProxyRelease(mainThread, runnable); + NS_ProxyRelease(mainThread, aThis.take()); return; } aBlob->Close(); - NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); + RUN_ON_THREAD(mainThread, WrapRunnable(nsRefPtr<DataChannelConnection>(aThis), + &DataChannelConnection::SendBinaryMsg, + aStream, temp), + NS_DISPATCH_NORMAL); } void DataChannelConnection::GetStreamIds(std::vector<uint16_t>* aStreamList) { ASSERT_WEBRTC(NS_IsMainThread()); for (uint32_t i = 0; i < mStreams.Length(); ++i) { if (mStreams[i]) {
--- a/toolkit/content/tests/chrome/chrome.ini +++ b/toolkit/content/tests/chrome/chrome.ini @@ -133,17 +133,16 @@ skip-if = buildapp == 'mulet' [test_popup_scaled.xul] [test_popup_tree.xul] [test_popuphidden.xul] [test_popupincontent.xul] [test_popupremoving.xul] [test_popupremoving_frame.xul] [test_position.xul] [test_preferences.xul] -skip-if = toolkit != "cocoa" [test_preferences_beforeaccept.xul] support-files = window_preferences_beforeaccept.xul [test_preferences_onsyncfrompreference.xul] support-files = window_preferences_onsyncfrompreference.xul [test_progressmeter.xul] [test_props.xul] [test_radio.xul] [test_richlist_direction.xul]