/* vim:set tw=80 expandtab softtabstop=2 ts=2 sw=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/. */ /* This is a Cross-Platform ICO Decoder, which should work everywhere, including * Big-Endian machines like the PowerPC. */ #include #include "mozilla/Endian.h" #include "mozilla/Move.h" #include "nsICODecoder.h" #include "RasterImage.h" using namespace mozilla::gfx; namespace mozilla { namespace image { // Constants. static const uint32_t ICOHEADERSIZE = 6; static const uint32_t BITMAPINFOSIZE = 40; // ---------------------------------------- // Actual Data Processing // ---------------------------------------- uint32_t nsICODecoder::CalcAlphaRowSize() { // Calculate rowsize in DWORD's and then return in # of bytes uint32_t rowSize = (GetRealWidth() + 31) / 32; // + 31 to round up return rowSize * 4; // Return rowSize in bytes } // Obtains the number of colors from the bits per pixel uint16_t nsICODecoder::GetNumColors() { uint16_t numColors = 0; if (mBPP <= 8) { switch (mBPP) { case 1: numColors = 2; break; case 4: numColors = 16; break; case 8: numColors = 256; break; default: numColors = (uint16_t)-1; } } return numColors; } nsICODecoder::nsICODecoder(RasterImage* aImage) : Decoder(aImage) , mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE)) , mBiggestResourceColorDepth(0) , mBestResourceDelta(INT_MIN) , mBestResourceColorDepth(0) , mNumIcons(0) , mCurrIcon(0) , mBPP(0) , mMaskRowSize(0) , mCurrMaskLine(0) , mIsCursor(false) , mHasMaskAlpha(false) { } void nsICODecoder::FinishInternal() { // We shouldn't be called in error cases MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!"); GetFinalStateFromContainedDecoder(); } void nsICODecoder::FinishWithErrorInternal() { GetFinalStateFromContainedDecoder(); } void nsICODecoder::GetFinalStateFromContainedDecoder() { if (!mContainedDecoder) { return; } // Finish the internally used decoder. mContainedDecoder->CompleteDecode(); mDecodeDone = mContainedDecoder->GetDecodeDone(); mDataError = mDataError || mContainedDecoder->HasDataError(); mFailCode = NS_SUCCEEDED(mFailCode) ? mContainedDecoder->GetDecoderError() : mFailCode; mDecodeAborted = mContainedDecoder->WasAborted(); mProgress |= mContainedDecoder->TakeProgress(); mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect()); mCurrentFrame = mContainedDecoder->GetCurrentFrameRef(); MOZ_ASSERT(HasError() || !mCurrentFrame || mCurrentFrame->IsImageComplete()); } // 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 // Returns true if successful bool nsICODecoder::FillBitmapFileHeaderBuffer(int8_t* bfh) { memset(bfh, 0, 14); bfh[0] = 'B'; bfh[1] = 'M'; int32_t dataOffset = 0; int32_t fileSize = 0; dataOffset = bmp::FileHeader::LENGTH + BITMAPINFOSIZE; // The color table is present only if BPP is <= 8 if (mDirEntry.mBitCount <= 8) { uint16_t numColors = GetNumColors(); if (numColors == (uint16_t)-1) { return false; } dataOffset += 4 * numColors; fileSize = dataOffset + GetRealWidth() * GetRealHeight(); } else { fileSize = dataOffset + (mDirEntry.mBitCount * GetRealWidth() * GetRealHeight()) / 8; } NativeEndian::swapToLittleEndianInPlace(&fileSize, 1); memcpy(bfh + 2, &fileSize, sizeof(fileSize)); NativeEndian::swapToLittleEndianInPlace(&dataOffset, 1); memcpy(bfh + 10, &dataOffset, sizeof(dataOffset)); return true; } // A BMP inside of an ICO has *2 height because of the AND mask // that follows the actual bitmap. The BMP shouldn't know about // this difference though. bool nsICODecoder::FixBitmapHeight(int8_t* bih) { // Get the height from the BMP file information header int32_t height; memcpy(&height, bih + 8, sizeof(height)); NativeEndian::swapFromLittleEndianInPlace(&height, 1); // BMPs can be stored inverted by having a negative height height = abs(height); // The bitmap height is by definition * 2 what it should be to account for // the 'AND mask'. It is * 2 even if the `AND mask` is not present. height /= 2; if (height > 256) { return false; } // We should always trust the height from the bitmap itself instead of // the ICO height. So fix the ICO height. if (height == 256) { mDirEntry.mHeight = 0; } else { mDirEntry.mHeight = (int8_t)height; } // Fix the BMP height in the BIH so that the BMP decoder can work properly NativeEndian::swapToLittleEndianInPlace(&height, 1); memcpy(bih + 8, &height, sizeof(height)); return true; } // We should always trust the contained resource for the width // information over our own information. bool nsICODecoder::FixBitmapWidth(int8_t* bih) { // Get the width from the BMP file information header int32_t width; memcpy(&width, bih + 4, sizeof(width)); NativeEndian::swapFromLittleEndianInPlace(&width, 1); if (width > 256) { return false; } // We should always trust the width from the bitmap itself instead of // the ICO width. if (width == 256) { mDirEntry.mWidth = 0; } else { mDirEntry.mWidth = (int8_t)width; } return true; } // The BMP information header's bits per pixel should be trusted // more than what we have. Usually the ICO's BPP is set to 0. int32_t nsICODecoder::ReadBPP(const char* aBIH) { const int8_t* bih = reinterpret_cast(aBIH); int32_t bitsPerPixel; memcpy(&bitsPerPixel, bih + 14, sizeof(bitsPerPixel)); NativeEndian::swapFromLittleEndianInPlace(&bitsPerPixel, 1); return bitsPerPixel; } int32_t nsICODecoder::ReadBIHSize(const char* aBIH) { const int8_t* bih = reinterpret_cast(aBIH); int32_t headerSize; memcpy(&headerSize, bih, sizeof(headerSize)); NativeEndian::swapFromLittleEndianInPlace(&headerSize, 1); return headerSize; } LexerTransition nsICODecoder::ReadHeader(const char* aData) { // If the third byte is 1, this is an icon. If 2, a cursor. if ((aData[2] != 1) && (aData[2] != 2)) { return Transition::Terminate(ICOState::FAILURE); } mIsCursor = (aData[2] == 2); // The fifth and sixth bytes specify the number of resources in the file. mNumIcons = LittleEndian::readUint16(reinterpret_cast(aData + 4)); if (mNumIcons == 0) { return Transition::Terminate(ICOState::SUCCESS); // Nothing to do. } // Downscale-during-decode can end up decoding different resources in the ICO // file depending on the target size. Since the resources are not necessarily // scaled versions of the same image, some may be transparent and some may not // be. We could be precise about transparency if we decoded the metadata of // every resource, but for now we don't and it's safest to assume that // transparency could be present. PostHasTransparency(); return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE); } size_t nsICODecoder::FirstResourceOffset() const { MOZ_ASSERT(mNumIcons > 0, "Calling FirstResourceOffset before processing header"); // The first resource starts right after the directory, which starts right // after the ICO header. return ICOHEADERSIZE + mNumIcons * ICODIRENTRYSIZE; } LexerTransition nsICODecoder::ReadDirEntry(const char* aData) { mCurrIcon++; // Read the directory entry. IconDirEntry e; memset(&e, 0, sizeof(e)); memcpy(&e.mWidth, aData, sizeof(e.mWidth)); memcpy(&e.mHeight, aData + 1, sizeof(e.mHeight)); memcpy(&e.mColorCount, aData + 2, sizeof(e.mColorCount)); memcpy(&e.mReserved, aData + 3, sizeof(e.mReserved)); memcpy(&e.mPlanes, aData + 4, sizeof(e.mPlanes)); e.mPlanes = LittleEndian::readUint16(&e.mPlanes); memcpy(&e.mBitCount, aData + 6, sizeof(e.mBitCount)); e.mBitCount = LittleEndian::readUint16(&e.mBitCount); memcpy(&e.mBytesInRes, aData + 8, sizeof(e.mBytesInRes)); e.mBytesInRes = LittleEndian::readUint32(&e.mBytesInRes); memcpy(&e.mImageOffset, aData + 12, sizeof(e.mImageOffset)); e.mImageOffset = LittleEndian::readUint32(&e.mImageOffset); // Determine if this is the biggest resource we've seen so far. We always use // the biggest resource for the intrinsic size, and if we're not downscaling, // we select it as the best resource as well. IntSize entrySize(GetRealWidth(e), GetRealHeight(e)); if (e.mBitCount >= mBiggestResourceColorDepth && entrySize.width * entrySize.height >= mBiggestResourceSize.width * mBiggestResourceSize.height) { mBiggestResourceSize = entrySize; mBiggestResourceColorDepth = e.mBitCount; mBiggestResourceHotSpot = IntSize(e.mXHotspot, e.mYHotspot); if (!mDownscaler) { mDirEntry = e; } } if (mDownscaler) { // Calculate the delta between this resource's size and the desired size, so // we can see if it is better than our current-best option. In the case of // several equally-good resources, we use the last one. "Better" in this // case is determined by |delta|, a measure of the difference in size // between the entry we've found and the downscaler's target size. We will // choose the smallest resource that is >= the target size (i.e. we assume // it's better to downscale a larger icon than to upscale a smaller one). IntSize desiredSize = mDownscaler->TargetSize(); int32_t delta = entrySize.width - desiredSize.width + entrySize.height - desiredSize.height; if (e.mBitCount >= mBestResourceColorDepth && ((mBestResourceDelta < 0 && delta >= mBestResourceDelta) || (delta >= 0 && delta <= mBestResourceDelta))) { mBestResourceDelta = delta; mBestResourceColorDepth = e.mBitCount; mDirEntry = e; } } if (mCurrIcon == mNumIcons) { // Ensure the resource we selected has an offset past the ICO headers. if (mDirEntry.mImageOffset < FirstResourceOffset()) { return Transition::Terminate(ICOState::FAILURE); } // If this is a cursor, set the hotspot. We use the hotspot from the biggest // resource since we also use that resource for the intrinsic size. if (mIsCursor) { mImageMetadata.SetHotspot(mBiggestResourceHotSpot.width, mBiggestResourceHotSpot.height); } // We always report the biggest resource's size as the intrinsic size; this // is necessary for downscale-during-decode to work since we won't even // attempt to *upscale* while decoding. PostSize(mBiggestResourceSize.width, mBiggestResourceSize.height); if (IsMetadataDecode()) { return Transition::Terminate(ICOState::SUCCESS); } // If the resource we selected matches the downscaler's target size // perfectly, we don't need to do any downscaling. if (mDownscaler && GetRealSize() == mDownscaler->TargetSize()) { mDownscaler.reset(); } size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset(); return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE, ICOState::SKIP_TO_RESOURCE, offsetToResource); } return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE); } LexerTransition nsICODecoder::SniffResource(const char* aData) { // We use the first PNGSIGNATURESIZE bytes to determine whether this resource // is a PNG or a BMP. bool isPNG = !memcmp(aData, nsPNGDecoder::pngSignatureBytes, PNGSIGNATURESIZE); if (isPNG) { // Create a PNG decoder which will do the rest of the work for us. mContainedDecoder = new nsPNGDecoder(mImage); mContainedDecoder->SetMetadataDecode(IsMetadataDecode()); mContainedDecoder->SetDecoderFlags(GetDecoderFlags()); mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags()); if (mDownscaler) { mContainedDecoder->SetTargetSize(mDownscaler->TargetSize()); } mContainedDecoder->Init(); if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) { return Transition::Terminate(ICOState::FAILURE); } if (mDirEntry.mBytesInRes <= PNGSIGNATURESIZE) { return Transition::Terminate(ICOState::FAILURE); } // Read in the rest of the PNG unbuffered. size_t toRead = mDirEntry.mBytesInRes - PNGSIGNATURESIZE; return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE, ICOState::READ_PNG, toRead); } else { // Create a BMP decoder which will do most of the work for us; the exception // is the AND mask, which isn't present in standalone BMPs. nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage); mContainedDecoder = bmpDecoder; bmpDecoder->SetUseAlphaData(true); mContainedDecoder->SetMetadataDecode(IsMetadataDecode()); mContainedDecoder->SetDecoderFlags(GetDecoderFlags()); mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags()); if (mDownscaler) { mContainedDecoder->SetTargetSize(mDownscaler->TargetSize()); } mContainedDecoder->Init(); // Make sure we have a sane size for the bitmap information header. int32_t bihSize = ReadBIHSize(aData); if (bihSize != static_cast(BITMAPINFOSIZE)) { return Transition::Terminate(ICOState::FAILURE); } // Buffer the first part of the bitmap information header. memcpy(mBIHraw, aData, PNGSIGNATURESIZE); // Read in the rest of the bitmap information header. return Transition::To(ICOState::READ_BIH, BITMAPINFOSIZE - PNGSIGNATURESIZE); } } LexerTransition nsICODecoder::ReadPNG(const char* aData, uint32_t aLen) { if (!WriteToContainedDecoder(aData, aLen)) { return Transition::Terminate(ICOState::FAILURE); } // Raymond Chen says that 32bpp only are valid PNG ICOs // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx if (!static_cast(mContainedDecoder.get())->IsValidICO()) { return Transition::Terminate(ICOState::FAILURE); } return Transition::ContinueUnbuffered(ICOState::READ_PNG); } LexerTransition nsICODecoder::ReadBIH(const char* aData) { // Buffer the rest of the bitmap information header. memcpy(mBIHraw + PNGSIGNATURESIZE, aData, BITMAPINFOSIZE - PNGSIGNATURESIZE); // Extracting the BPP from the BIH header; it should be trusted over the one // we have from the ICO header. mBPP = ReadBPP(mBIHraw); // 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)) { return Transition::Terminate(ICOState::FAILURE); } if (!WriteToContainedDecoder(reinterpret_cast(bfhBuffer), sizeof(bfhBuffer))) { return Transition::Terminate(ICOState::FAILURE); } // Fix the ICO height from the BIH. It needs to be halved so our BMP decoder // will understand, because the BMP decoder doesn't expect the alpha mask that // follows the BMP data in an ICO. if (!FixBitmapHeight(reinterpret_cast(mBIHraw))) { return Transition::Terminate(ICOState::FAILURE); } // Fix the ICO width from the BIH. if (!FixBitmapWidth(reinterpret_cast(mBIHraw))) { return Transition::Terminate(ICOState::FAILURE); } // Write out the BMP's bitmap info header. if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) { return Transition::Terminate(ICOState::FAILURE); } // Sometimes the ICO BPP header field is not filled out so we should trust the // contained resource over our own information. // XXX(seth): Is this ever different than the value we obtained from // ReadBPP() above? RefPtr bmpDecoder = static_cast(mContainedDecoder.get()); mBPP = bmpDecoder->GetBitsPerPixel(); // Check to make sure we have valid color settings. uint16_t numColors = GetNumColors(); if (numColors == uint16_t(-1)) { return Transition::Terminate(ICOState::FAILURE); } // Do we have an AND mask on this BMP? If so, we need to read it after we read // the BMP data itself. uint32_t bmpDataLength = bmpDecoder->GetCompressedImageSize() + 4 * numColors; bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry.mBytesInRes; ICOState afterBMPState = hasANDMask ? ICOState::PREPARE_FOR_MASK : ICOState::FINISHED_RESOURCE; // Read in the rest of the BMP unbuffered. return Transition::ToUnbuffered(afterBMPState, ICOState::READ_BMP, bmpDataLength); } LexerTransition nsICODecoder::ReadBMP(const char* aData, uint32_t aLen) { if (!WriteToContainedDecoder(aData, aLen)) { return Transition::Terminate(ICOState::FAILURE); } return Transition::ContinueUnbuffered(ICOState::READ_BMP); } LexerTransition nsICODecoder::PrepareForMask() { RefPtr bmpDecoder = static_cast(mContainedDecoder.get()); uint16_t numColors = GetNumColors(); MOZ_ASSERT(numColors != uint16_t(-1)); // Determine the length of the AND mask. uint32_t bmpLengthWithHeader = BITMAPINFOSIZE + bmpDecoder->GetCompressedImageSize() + 4 * numColors; MOZ_ASSERT(bmpLengthWithHeader < mDirEntry.mBytesInRes); uint32_t maskLength = mDirEntry.mBytesInRes - bmpLengthWithHeader; // If we have a 32-bpp BMP with alpha data, we ignore the AND mask. We can // also obviously ignore it if the image has zero width or zero height. if ((bmpDecoder->GetBitsPerPixel() == 32 && bmpDecoder->HasAlphaData()) || GetRealWidth() == 0 || GetRealHeight() == 0) { return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE, ICOState::SKIP_MASK, maskLength); } // Compute the row size for the mask. mMaskRowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up // If the expected size of the AND mask is larger than its actual size, then // we must have a truncated (and therefore corrupt) AND mask. uint32_t expectedLength = mMaskRowSize * GetRealHeight(); if (maskLength < expectedLength) { return Transition::Terminate(ICOState::FAILURE); } // If we're downscaling, the mask is the wrong size for the surface we've // produced, so we need to downscale the mask into a temporary buffer and then // combine the mask's alpha values with the color values from the image. if (mDownscaler) { MOZ_ASSERT(bmpDecoder->GetImageDataLength() == mDownscaler->TargetSize().width * mDownscaler->TargetSize().height * sizeof(uint32_t)); mMaskBuffer = MakeUnique(bmpDecoder->GetImageDataLength()); nsresult rv = mDownscaler->BeginFrame(GetRealSize(), Nothing(), mMaskBuffer.get(), /* aHasAlpha = */ true, /* aFlipVertically = */ true); if (NS_FAILED(rv)) { return Transition::Terminate(ICOState::FAILURE); } } mCurrMaskLine = GetRealHeight(); return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize); } LexerTransition nsICODecoder::ReadMaskRow(const char* aData) { mCurrMaskLine--; uint8_t sawTransparency = 0; // Get the mask row we're reading. const uint8_t* mask = reinterpret_cast(aData); const uint8_t* maskRowEnd = mask + mMaskRowSize; // Get the corresponding row of the mask buffer (if we're downscaling) or the // decoded image data (if we're not). uint32_t* decoded = nullptr; if (mDownscaler) { // Initialize the row to all white and fully opaque. memset(mDownscaler->RowBuffer(), 0xFF, GetRealWidth() * sizeof(uint32_t)); decoded = reinterpret_cast(mDownscaler->RowBuffer()); } else { RefPtr bmpDecoder = static_cast(mContainedDecoder.get()); uint32_t* imageData = bmpDecoder->GetImageData(); if (!imageData) { return Transition::Terminate(ICOState::FAILURE); } decoded = imageData + mCurrMaskLine * GetRealWidth(); } MOZ_ASSERT(decoded); uint32_t* decodedRowEnd = decoded + GetRealWidth(); // Iterate simultaneously through the AND mask and the image data. while (mask < maskRowEnd) { uint8_t idx = *mask++; sawTransparency |= idx; for (uint8_t bit = 0x80; bit && decoded < decodedRowEnd; bit >>= 1) { // Clear pixel completely for transparency. if (idx & bit) { *decoded = 0; } decoded++; } } if (mDownscaler) { mDownscaler->CommitRow(); } // If any bits are set in sawTransparency, then we know at least one pixel was // transparent. if (sawTransparency) { mHasMaskAlpha = true; } if (mCurrMaskLine == 0) { return Transition::To(ICOState::FINISH_MASK, 0); } return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize); } LexerTransition nsICODecoder::FinishMask() { // If we're downscaling, we now have the appropriate alpha values in // mMaskBuffer. We just need to transfer them to the image. if (mDownscaler) { // Retrieve the image data. RefPtr bmpDecoder = static_cast(mContainedDecoder.get()); uint8_t* imageData = reinterpret_cast(bmpDecoder->GetImageData()); if (!imageData) { return Transition::Terminate(ICOState::FAILURE); } // Iterate through the alpha values, copying from mask to image. MOZ_ASSERT(mMaskBuffer); MOZ_ASSERT(bmpDecoder->GetImageDataLength() > 0); for (size_t i = 3 ; i < bmpDecoder->GetImageDataLength() ; i += 4) { imageData[i] = mMaskBuffer[i]; } } // If the mask contained any transparent pixels, record that fact. if (mHasMaskAlpha) { PostHasTransparency(); RefPtr bmpDecoder = static_cast(mContainedDecoder.get()); bmpDecoder->SetHasAlphaData(); } return Transition::To(ICOState::FINISHED_RESOURCE, 0); } LexerTransition nsICODecoder::FinishResource() { // Make sure the actual size of the resource matches the size in the directory // entry. If not, we consider the image corrupt. if (mContainedDecoder->HasSize() && mContainedDecoder->GetSize() != GetRealSize()) { return Transition::Terminate(ICOState::FAILURE); } return Transition::Terminate(ICOState::SUCCESS); } void nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount) { MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!"); MOZ_ASSERT(aBuffer); MOZ_ASSERT(aCount > 0); Maybe terminalState = mLexer.Lex(aBuffer, aCount, [=](ICOState aState, const char* aData, size_t aLength) { switch (aState) { case ICOState::HEADER: return ReadHeader(aData); case ICOState::DIR_ENTRY: return ReadDirEntry(aData); case ICOState::SKIP_TO_RESOURCE: return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE); case ICOState::FOUND_RESOURCE: return Transition::To(ICOState::SNIFF_RESOURCE, PNGSIGNATURESIZE); case ICOState::SNIFF_RESOURCE: return SniffResource(aData); case ICOState::READ_PNG: return ReadPNG(aData, aLength); case ICOState::READ_BIH: return ReadBIH(aData); case ICOState::READ_BMP: return ReadBMP(aData, aLength); case ICOState::PREPARE_FOR_MASK: return PrepareForMask(); case ICOState::READ_MASK_ROW: return ReadMaskRow(aData); case ICOState::FINISH_MASK: return FinishMask(); case ICOState::SKIP_MASK: return Transition::ContinueUnbuffered(ICOState::SKIP_MASK); case ICOState::FINISHED_RESOURCE: return FinishResource(); default: MOZ_ASSERT_UNREACHABLE("Unknown ICOState"); return Transition::Terminate(ICOState::FAILURE); } }); if (!terminalState) { return; // Need more data. } if (*terminalState == ICOState::FAILURE) { PostDataError(); return; } MOZ_ASSERT(*terminalState == ICOState::SUCCESS); } bool nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount) { mContainedDecoder->Write(aBuffer, aCount); mProgress |= mContainedDecoder->TakeProgress(); mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect()); if (mContainedDecoder->HasDataError()) { PostDataError(); } if (mContainedDecoder->HasDecoderError()) { PostDecoderError(mContainedDecoder->GetDecoderError()); } return !HasError(); } } // namespace image } // namespace mozilla