From 8604a9cc7124e2a3a54001d3875f8f31f8876143 Mon Sep 17 00:00:00 2001 From: Sebastian Hengst Date: Sat, 22 Jul 2017 11:03:46 +0200 Subject: [PATCH] Backed out changeset 18614b05270d (bug 1315554) --- image/decoders/nsICODecoder.cpp | 328 +++++++++++--------------------- image/decoders/nsICODecoder.h | 50 +++-- 2 files changed, 149 insertions(+), 229 deletions(-) diff --git a/image/decoders/nsICODecoder.cpp b/image/decoders/nsICODecoder.cpp index b7ee9fffc7cc..fe5ec14bab98 100644 --- a/image/decoders/nsICODecoder.cpp +++ b/image/decoders/nsICODecoder.cpp @@ -55,7 +55,9 @@ nsICODecoder::nsICODecoder(RasterImage* aImage) : Decoder(aImage) , mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE), Transition::TerminateSuccess()) - , mDirEntry(nullptr) + , mBiggestResourceColorDepth(0) + , mBestResourceDelta(INT_MIN) + , mBestResourceColorDepth(0) , mNumIcons(0) , mCurrIcon(0) , mBPP(0) @@ -149,176 +151,104 @@ nsICODecoder::ReadDirEntry(const char* aData) { mCurrIcon++; - // Ensure the resource has an offset past the ICO headers. - uint32_t offset = LittleEndian::readUint32(aData + 12); - if (offset >= FirstResourceOffset()) { - // Read the directory entry. - IconDirEntryEx e; - e.mWidth = aData[0]; - e.mHeight = aData[1]; - e.mColorCount = aData[2]; - e.mReserved = aData[3]; - e.mPlanes = LittleEndian::readUint16(aData + 4); - e.mBitCount = LittleEndian::readUint16(aData + 6); - e.mBytesInRes = LittleEndian::readUint32(aData + 8); - e.mImageOffset = offset; - e.mSize = IntSize(e.mWidth, e.mHeight); - if (e.mWidth == 0 || e.mHeight == 0) { - mUnsizedDirEntries.AppendElement(e); - } else { - mDirEntries.AppendElement(e); + // Read the directory entry. + IconDirEntry e; + e.mWidth = aData[0]; + e.mHeight = aData[1]; + e.mColorCount = aData[2]; + e.mReserved = aData[3]; + e.mPlanes = LittleEndian::readUint16(aData + 4); + e.mBitCount = LittleEndian::readUint16(aData + 6); + e.mBytesInRes = LittleEndian::readUint32(aData + 8); + e.mImageOffset = LittleEndian::readUint32(aData + 12); + + // If an explicit output size was specified, we'll try to select the resource + // that matches it best below. + const Maybe desiredSize = ExplicitOutputSize(); + + // 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 don't have a + // specific desired size, 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 (!desiredSize) { + mDirEntry = e; + } + } + + mImageMetadata.AddNativeSize(entrySize); + + if (desiredSize) { + // 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 desired size. We will choose the + // smallest resource that is greater than or equal to the desired size (i.e. + // we assume it's better to downscale a larger icon than to upscale a + // smaller one). + int32_t delta = std::min(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) { - if (mUnsizedDirEntries.IsEmpty()) { - return Transition::To(ICOState::FINISHED_DIR_ENTRY, 0); + // Ensure the resource we selected has an offset past the ICO headers. + if (mDirEntry.mImageOffset < FirstResourceOffset()) { + return Transition::TerminateFailure(); } - return Transition::To(ICOState::ITERATE_UNSIZED_DIR_ENTRY, 0); + + // 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 (HasError()) { + return Transition::TerminateFailure(); + } + + if (IsMetadataDecode()) { + return Transition::TerminateSuccess(); + } + + // If the resource we selected matches the output size perfectly, we don't + // need to do any downscaling. + if (GetRealSize() == OutputSize()) { + MOZ_ASSERT_IF(desiredSize, GetRealSize() == *desiredSize); + MOZ_ASSERT_IF(!desiredSize, GetRealSize() == Size()); + 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::IterateUnsizedDirEntry() -{ - MOZ_ASSERT(!mUnsizedDirEntries.IsEmpty()); - - if (!mDirEntry) { - // The first time we are here, there is no entry selected. We must prepare a - // new iterator for the contained decoder to advance as it wills. Cloning at - // this point ensures it will begin at the end of the dir entries. - mReturnIterator.emplace(mLexer.Clone(*mIterator, SIZE_MAX)); - } else { - // We have already selected an entry which means a metadata decoder has - // finished. Verify the size is valid and if so, add to the discovered - // resources. - if (mDirEntry->mSize.width > 0 && mDirEntry->mSize.height > 0) { - mDirEntries.AppendElement(*mDirEntry); - } - - // Remove the entry from the unsized list either way. - mDirEntry = nullptr; - mUnsizedDirEntries.RemoveElementAt(0); - - // Our iterator is at an unknown point, so reset it to the point that we - // saved. - mIterator.reset(); - mIterator.emplace(mLexer.Clone(*mReturnIterator, SIZE_MAX)); - } - - // There are no more unsized entries, so we can finally decide which entry to - // select for decoding. - if (mUnsizedDirEntries.IsEmpty()) { - mReturnIterator.reset(); - return Transition::To(ICOState::FINISHED_DIR_ENTRY, 0); - } - - // Move to the resource data to start metadata decoding. - mDirEntry = &mUnsizedDirEntries[0]; - size_t offsetToResource = mDirEntry->mImageOffset - FirstResourceOffset(); - return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE, - ICOState::SKIP_TO_RESOURCE, - offsetToResource); -} - -LexerTransition -nsICODecoder::FinishDirEntry() -{ - MOZ_ASSERT(!mDirEntry); - - if (mDirEntries.IsEmpty()) { - return Transition::TerminateFailure(); - } - - // If an explicit output size was specified, we'll try to select the resource - // that matches it best below. - const Maybe desiredSize = ExplicitOutputSize(); - - // Determine the biggest resource. We always use the biggest resource for the - // intrinsic size, and if we don't have a specific desired size, we select it - // as the best resource as well. - int32_t bestDelta = INT32_MIN; - IconDirEntryEx* biggestEntry = nullptr; - - for (size_t i = 0; i < mDirEntries.Length(); ++i) { - IconDirEntryEx& e = mDirEntries[i]; - mImageMetadata.AddNativeSize(e.mSize); - - if (!biggestEntry || - (e.mBitCount >= biggestEntry->mBitCount && - e.mSize.width * e.mSize.height >= - biggestEntry->mSize.width * biggestEntry->mSize.height)) { - biggestEntry = &e; - - if (!desiredSize) { - mDirEntry = &e; - } - } - - if (desiredSize) { - // 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 desired size. We will choose the - // smallest resource that is greater than or equal to the desired size (i.e. - // we assume it's better to downscale a larger icon than to upscale a - // smaller one). - int32_t delta = std::min(e.mSize.width - desiredSize->width, - e.mSize.height - desiredSize->height); - if (!mDirEntry || - (e.mBitCount >= mDirEntry->mBitCount && - ((bestDelta < 0 && delta >= bestDelta) || - (delta >= 0 && delta <= bestDelta)))) { - mDirEntry = &e; - bestDelta = delta; - } - } - } - - MOZ_ASSERT(mDirEntry); - MOZ_ASSERT(biggestEntry); - - // 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(biggestEntry->mXHotspot, - biggestEntry->mYHotspot); - } - - // 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(biggestEntry->mSize.width, biggestEntry->mSize.height); - if (HasError()) { - return Transition::TerminateFailure(); - } - - if (IsMetadataDecode()) { - return Transition::TerminateSuccess(); - } - - // If the resource we selected matches the output size perfectly, we don't - // need to do any downscaling. - if (mDirEntry->mSize == OutputSize()) { - MOZ_ASSERT_IF(desiredSize, mDirEntry->mSize == *desiredSize); - MOZ_ASSERT_IF(!desiredSize, mDirEntry->mSize == Size()); - mDownscaler.reset(); - } - - size_t offsetToResource = mDirEntry->mImageOffset - FirstResourceOffset(); - return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE, - ICOState::SKIP_TO_RESOURCE, - offsetToResource); -} - LexerTransition nsICODecoder::SniffResource(const char* aData) { - MOZ_ASSERT(mDirEntry); - // We have BITMAPINFOSIZE bytes buffered at this point. We know an embedded // BMP will have at least that many bytes by definition. We can also infer // that any valid embedded PNG will contain that many bytes as well because: @@ -333,28 +263,25 @@ nsICODecoder::SniffResource(const char* aData) bool isPNG = !memcmp(aData, nsPNGDecoder::pngSignatureBytes, PNGSIGNATURESIZE); if (isPNG) { - if (mDirEntry->mBytesInRes <= BITMAPINFOSIZE) { + if (mDirEntry.mBytesInRes <= BITMAPINFOSIZE) { return Transition::TerminateFailure(); } // Prepare a new iterator for the contained decoder to advance as it wills. // Cloning at the point ensures it will begin at the resource offset. SourceBufferIterator containedIterator - = mLexer.Clone(*mIterator, mDirEntry->mBytesInRes); + = mLexer.Clone(*mIterator, mDirEntry.mBytesInRes); // Create a PNG decoder which will do the rest of the work for us. - bool metadataDecode = mReturnIterator.isSome(); - Maybe expectedSize = metadataDecode ? Nothing() - : Some(mDirEntry->mSize); mContainedDecoder = DecoderFactory::CreateDecoderForICOResource(DecoderType::PNG, Move(containedIterator), WrapNotNull(this), - metadataDecode, - expectedSize); + false, + Some(GetRealSize())); // Read in the rest of the PNG unbuffered. - size_t toRead = mDirEntry->mBytesInRes - BITMAPINFOSIZE; + size_t toRead = mDirEntry.mBytesInRes - BITMAPINFOSIZE; return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE, ICOState::READ_RESOURCE, toRead); @@ -383,8 +310,6 @@ nsICODecoder::ReadResource() LexerTransition nsICODecoder::ReadBIH(const char* aData) { - MOZ_ASSERT(mDirEntry); - // Extract the BPP from the BIH header; it should be trusted over the one // we have from the ICO header which is usually set to 0. mBPP = LittleEndian::readUint16(aData + 14); @@ -407,19 +332,16 @@ nsICODecoder::ReadBIH(const char* aData) // Prepare a new iterator for the contained decoder to advance as it wills. // Cloning at the point ensures it will begin at the resource offset. SourceBufferIterator containedIterator - = mLexer.Clone(*mIterator, mDirEntry->mBytesInRes); + = mLexer.Clone(*mIterator, mDirEntry.mBytesInRes); // 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. - bool metadataDecode = mReturnIterator.isSome(); - Maybe expectedSize = metadataDecode ? Nothing() - : Some(mDirEntry->mSize); mContainedDecoder = DecoderFactory::CreateDecoderForICOResource(DecoderType::BMP, Move(containedIterator), WrapNotNull(this), - metadataDecode, - expectedSize, + false, + Some(GetRealSize()), Some(dataOffset)); RefPtr bmpDecoder = @@ -430,15 +352,10 @@ nsICODecoder::ReadBIH(const char* aData) return Transition::TerminateFailure(); } - // If this is a metadata decode, FinishResource will any necessary checks. - if (mContainedDecoder->IsMetadataDecode()) { - return Transition::To(ICOState::FINISHED_RESOURCE, 0); - } - // 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; + bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry.mBytesInRes; ICOState afterBMPState = hasANDMask ? ICOState::PREPARE_FOR_MASK : ICOState::FINISHED_RESOURCE; @@ -451,9 +368,6 @@ nsICODecoder::ReadBIH(const char* aData) LexerTransition nsICODecoder::PrepareForMask() { - MOZ_ASSERT(mDirEntry); - MOZ_ASSERT(mContainedDecoder->GetDecodeDone()); - // We have received all of the data required by the BMP decoder so flushing // here guarantees the decode has finished. if (!FlushContainedDecoder()) { @@ -471,22 +385,24 @@ nsICODecoder::PrepareForMask() // 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; + MOZ_ASSERT(bmpLengthWithHeader < mDirEntry.mBytesInRes); + uint32_t maskLength = mDirEntry.mBytesInRes - bmpLengthWithHeader; - // If the BMP provides its own transparency, we ignore the AND mask. - if (bmpDecoder->HasTransparency()) { + // If the BMP provides its own transparency, we ignore the AND mask. We can + // also obviously ignore it if the image has zero width or zero height. + if (bmpDecoder->HasTransparency() || + GetRealWidth() == 0 || GetRealHeight() == 0) { return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE, ICOState::SKIP_MASK, maskLength); } // Compute the row size for the mask. - mMaskRowSize = ((mDirEntry->mSize.width + 31) / 32) * 4; // + 31 to round up + 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 * mDirEntry->mSize.height; + uint32_t expectedLength = mMaskRowSize * GetRealHeight(); if (maskLength < expectedLength) { return Transition::TerminateFailure(); } @@ -500,7 +416,7 @@ nsICODecoder::PrepareForMask() mDownscaler->TargetSize().height * sizeof(uint32_t)); mMaskBuffer = MakeUnique(bmpDecoder->GetImageDataLength()); - nsresult rv = mDownscaler->BeginFrame(mDirEntry->mSize, Nothing(), + nsresult rv = mDownscaler->BeginFrame(GetRealSize(), Nothing(), mMaskBuffer.get(), /* aHasAlpha = */ true, /* aFlipVertically = */ true); @@ -509,7 +425,7 @@ nsICODecoder::PrepareForMask() } } - mCurrMaskLine = mDirEntry->mSize.height; + mCurrMaskLine = GetRealHeight(); return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize); } @@ -517,8 +433,6 @@ nsICODecoder::PrepareForMask() LexerTransition nsICODecoder::ReadMaskRow(const char* aData) { - MOZ_ASSERT(mDirEntry); - mCurrMaskLine--; uint8_t sawTransparency = 0; @@ -532,7 +446,7 @@ nsICODecoder::ReadMaskRow(const char* aData) uint32_t* decoded = nullptr; if (mDownscaler) { // Initialize the row to all white and fully opaque. - memset(mDownscaler->RowBuffer(), 0xFF, mDirEntry->mSize.width * sizeof(uint32_t)); + memset(mDownscaler->RowBuffer(), 0xFF, GetRealWidth() * sizeof(uint32_t)); decoded = reinterpret_cast(mDownscaler->RowBuffer()); } else { @@ -543,11 +457,11 @@ nsICODecoder::ReadMaskRow(const char* aData) return Transition::TerminateFailure(); } - decoded = imageData + mCurrMaskLine * mDirEntry->mSize.width; + decoded = imageData + mCurrMaskLine * GetRealWidth(); } MOZ_ASSERT(decoded); - uint32_t* decodedRowEnd = decoded + mDirEntry->mSize.width; + uint32_t* decodedRowEnd = decoded + GetRealWidth(); // Iterate simultaneously through the AND mask and the image data. while (mask < maskRowEnd) { @@ -607,8 +521,6 @@ nsICODecoder::FinishMask() LexerTransition nsICODecoder::FinishResource() { - MOZ_ASSERT(mDirEntry); - // We have received all of the data required by the PNG/BMP decoder so // flushing here guarantees the decode has finished. if (!FlushContainedDecoder()) { @@ -617,13 +529,11 @@ nsICODecoder::FinishResource() MOZ_ASSERT(mContainedDecoder->GetDecodeDone()); - // If it is a metadata decode, all we were trying to get was the size - // information missing from the dir entry. - if (mContainedDecoder->IsMetadataDecode()) { - if (mContainedDecoder->HasSize()) { - mDirEntry->mSize = mContainedDecoder->Size(); - } - return Transition::To(ICOState::ITERATE_UNSIZED_DIR_ENTRY, 0); + // 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->Size() != GetRealSize()) { + return Transition::TerminateFailure(); } // Raymond Chen says that 32bpp only are valid PNG ICOs @@ -632,10 +542,6 @@ nsICODecoder::FinishResource() return Transition::TerminateFailure(); } - // This size from the resource should match that from the dir entry. - MOZ_ASSERT_IF(mContainedDecoder->HasSize(), - mContainedDecoder->Size() == mDirEntry->mSize); - // Finalize the frame which we deferred to ensure we could modify the final // result (e.g. to apply the BMP mask). MOZ_ASSERT(!mContainedDecoder->GetFinalizeFrames()); @@ -658,10 +564,6 @@ nsICODecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) return ReadHeader(aData); case ICOState::DIR_ENTRY: return ReadDirEntry(aData); - case ICOState::FINISHED_DIR_ENTRY: - return FinishDirEntry(); - case ICOState::ITERATE_UNSIZED_DIR_ENTRY: - return IterateUnsizedDirEntry(); case ICOState::SKIP_TO_RESOURCE: return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE); case ICOState::FOUND_RESOURCE: diff --git a/image/decoders/nsICODecoder.h b/image/decoders/nsICODecoder.h index 6682733fa09c..1a697637d59d 100644 --- a/image/decoders/nsICODecoder.h +++ b/image/decoders/nsICODecoder.h @@ -24,8 +24,6 @@ enum class ICOState { HEADER, DIR_ENTRY, - FINISHED_DIR_ENTRY, - ITERATE_UNSIZED_DIR_ENTRY, SKIP_TO_RESOURCE, FOUND_RESOURCE, SNIFF_RESOURCE, @@ -42,6 +40,30 @@ class nsICODecoder : public Decoder public: virtual ~nsICODecoder() { } + /// @return the width of the icon directory entry @aEntry. + static uint32_t GetRealWidth(const IconDirEntry& aEntry) + { + return aEntry.mWidth == 0 ? 256 : aEntry.mWidth; + } + + /// @return the width of the selected directory entry (mDirEntry). + uint32_t GetRealWidth() const { return GetRealWidth(mDirEntry); } + + /// @return the height of the icon directory entry @aEntry. + static uint32_t GetRealHeight(const IconDirEntry& aEntry) + { + return aEntry.mHeight == 0 ? 256 : aEntry.mHeight; + } + + /// @return the height of the selected directory entry (mDirEntry). + uint32_t GetRealHeight() const { return GetRealHeight(mDirEntry); } + + /// @return the size of the selected directory entry (mDirEntry). + gfx::IntSize GetRealSize() const + { + return gfx::IntSize(GetRealWidth(), GetRealHeight()); + } + /// @return The offset from the beginning of the ICO to the first resource. size_t FirstResourceOffset() const; @@ -68,8 +90,6 @@ private: LexerTransition ReadHeader(const char* aData); LexerTransition ReadDirEntry(const char* aData); - LexerTransition IterateUnsizedDirEntry(); - LexerTransition FinishDirEntry(); LexerTransition SniffResource(const char* aData); LexerTransition ReadResource(); LexerTransition ReadBIH(const char* aData); @@ -78,20 +98,18 @@ private: LexerTransition FinishMask(); LexerTransition FinishResource(); - struct IconDirEntryEx : public IconDirEntry { - gfx::IntSize mSize; - }; - StreamingLexer mLexer; // The lexer. RefPtr mContainedDecoder; // Either a BMP or PNG decoder. - Maybe mReturnIterator; // Iterator to save return point. - UniquePtr mMaskBuffer; // A temporary buffer for the alpha mask. - nsTArray mDirEntries; // Valid dir entries with a size. - nsTArray mUnsizedDirEntries; // Dir entries without a size. - IconDirEntryEx* mDirEntry; // The dir entry for the selected resource. - uint16_t mNumIcons; // Stores the number of icons in the ICO file. - uint16_t mCurrIcon; // Stores the current dir entry index we are processing. - uint16_t mBPP; // The BPP of the resource we're decoding. + UniquePtr mMaskBuffer; // A temporary buffer for the alpha mask. + IconDirEntry mDirEntry; // The dir entry for the selected resource. + gfx::IntSize mBiggestResourceSize; // Used to select the intrinsic size. + gfx::IntSize mBiggestResourceHotSpot; // Used to select the intrinsic size. + uint16_t mBiggestResourceColorDepth; // Used to select the intrinsic size. + int32_t mBestResourceDelta; // Used to select the best resource. + uint16_t mBestResourceColorDepth; // Used to select the best resource. + uint16_t mNumIcons; // Stores the number of icons in the ICO file. + uint16_t mCurrIcon; // Stores the current dir entry index we are processing. + uint16_t mBPP; // The BPP of the resource we're decoding. uint32_t mMaskRowSize; // The size in bytes of each row in the BMP alpha mask. uint32_t mCurrMaskLine; // The line of the BMP alpha mask we're processing. bool mIsCursor; // Is this ICO a cursor?