Bug 1201796 (Part 4) - Add downscale-during-decode support for the ICO decoder. r=tn,a=lizzard
authorSeth Fowler <mark.seth.fowler@gmail.com>
Tue, 15 Sep 2015 23:42:59 -0700
changeset 296348 99e1eba4483684b5adf5a681fb1a79941a40f7f3
parent 296347 77d98a49781220b88bbca64924f655e535848e03
child 296349 4e6a6482a65a91ca18974aa56e57da233a47ed22
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn, lizzard
bugs1201796
milestone43.0a2
Bug 1201796 (Part 4) - Add downscale-during-decode support for the ICO decoder. r=tn,a=lizzard
image/ImageFactory.cpp
image/decoders/nsICODecoder.cpp
image/decoders/nsICODecoder.h
image/test/mochitest/test_has_transparency.html
image/test/reftest/ico/cur/pointer.cur
image/test/reftest/ico/ico-mixed/reftest.list
image/test/reftest/ico/ico-png/transparent-png.ico
image/test/unit/test_imgtools.js
--- a/image/ImageFactory.cpp
+++ b/image/ImageFactory.cpp
@@ -33,16 +33,17 @@ ImageFactory::Initialize()
 { }
 
 static bool
 ShouldDownscaleDuringDecode(const nsCString& aMimeType)
 {
   DecoderType type = DecoderFactory::GetDecoderType(aMimeType.get());
   return type == DecoderType::JPEG ||
          type == DecoderType::ICON ||
+         type == DecoderType::ICO ||
          type == DecoderType::PNG ||
          type == DecoderType::BMP ||
          type == DecoderType::GIF;
 }
 
 static uint32_t
 ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart)
 {
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -9,23 +9,24 @@
 #include <stdlib.h>
 
 #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;
-static const uint32_t PREFICONSIZE = 16;
 
 // ----------------------------------------
 // Actual Data Processing
 // ----------------------------------------
 
 uint32_t
 nsICODecoder::CalcAlphaRowSize()
 {
@@ -55,25 +56,40 @@ nsICODecoder::GetNumColors()
     }
   }
   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)
 { }
 
+nsresult
+nsICODecoder::SetTargetSize(const nsIntSize& aSize)
+{
+  // Make sure the size is reasonable.
+  if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Create a downscaler that we'll filter our output through.
+  mDownscaler.emplace(aSize);
+
+  return NS_OK;
+}
+
 void
 nsICODecoder::FinishInternal()
 {
   // We shouldn't be called in error cases
   MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
 
   GetFinalStateFromContainedDecoder();
 }
@@ -217,46 +233,39 @@ nsICODecoder::ReadBIHSize(const char* aB
 {
   const int8_t* bih = reinterpret_cast<const int8_t*>(aBIH);
   int32_t headerSize;
   memcpy(&headerSize, bih, sizeof(headerSize));
   NativeEndian::swapFromLittleEndianInPlace(&headerSize, 1);
   return headerSize;
 }
 
-void
-nsICODecoder::SetHotSpotIfCursor()
-{
-  if (!mIsCursor) {
-    return;
-  }
-
-  mImageMetadata.SetHotspot(mDirEntry.mXHotspot, mDirEntry.mYHotspot);
-}
-
 LexerTransition<ICOState>
 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<const uint16_t*>(aData + 4));
   if (mNumIcons == 0) {
     return Transition::Terminate(ICOState::SUCCESS); // Nothing to do.
   }
 
-  // If we didn't get a #-moz-resolution, default to PREFICONSIZE.
-  if (mResolution.width == 0 && mResolution.height == 0) {
-    mResolution.SizeTo(PREFICONSIZE, PREFICONSIZE);
-  }
+  // 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,
@@ -283,45 +292,79 @@ nsICODecoder::ReadDirEntry(const char* a
   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);
 
-  // Calculate the delta between this image'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 images, 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 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).
-  int32_t delta = GetRealWidth(e) - mResolution.width +
-                  GetRealHeight(e) - mResolution.height;
-  if (e.mBitCount >= mBestResourceColorDepth &&
-      ((mBestResourceDelta < 0 && delta >= mBestResourceDelta) ||
-       (delta >= 0 && delta <= mBestResourceDelta))) {
-    mBestResourceDelta = delta;
+  // 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;
+    }
+  }
 
-    // Ensure mImageOffset is >= size of the direntry headers (bug #245631).
-    if (e.mImageOffset < FirstResourceOffset()) {
+  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);
     }
 
-    mBestResourceColorDepth = e.mBitCount;
-    mDirEntry = e;
-  }
+    // 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);
+    }
 
-  if (mCurrIcon == mNumIcons) {
-    PostSize(GetRealWidth(mDirEntry), GetRealHeight(mDirEntry));
+    // 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);
 }
@@ -334,16 +377,19 @@ nsICODecoder::SniffResource(const char* 
   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);
@@ -358,16 +404,19 @@ nsICODecoder::SniffResource(const char* 
     // 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<int32_t>(BITMAPINFOSIZE)) {
       return Transition::Terminate(ICOState::FAILURE);
     }
 
@@ -384,18 +433,17 @@ LexerTransition<ICOState>
 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 (!IsMetadataDecode() &&
-      !static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
+  if (!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
     return Transition::Terminate(ICOState::FAILURE);
   }
 
   return Transition::ContinueUnbuffered(ICOState::READ_PNG);
 }
 
 LexerTransition<ICOState>
 nsICODecoder::ReadBIH(const char* aData)
@@ -415,19 +463,16 @@ nsICODecoder::ReadBIH(const char* aData)
     return Transition::Terminate(ICOState::FAILURE);
   }
 
   if (!WriteToContainedDecoder(reinterpret_cast<const char*>(bfhBuffer),
                                sizeof(bfhBuffer))) {
     return Transition::Terminate(ICOState::FAILURE);
   }
 
-  // Set up the cursor hot spot if one is present.
-  SetHotSpotIfCursor();
-
   // 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<int8_t*>(mBIHraw))) {
     return Transition::Terminate(ICOState::FAILURE);
   }
 
   // Fix the ICO width from the BIH.
@@ -562,19 +607,18 @@ nsICODecoder::ReadMaskRow(const char* aD
   return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
 }
 
 LexerTransition<ICOState>
 nsICODecoder::FinishResource()
 {
   // Make sure the actual size of the resource matches the size in the directory
   // entry. If not, we consider the image corrupt.
-  IntSize expectedSize(GetRealWidth(mDirEntry), GetRealHeight(mDirEntry));
   if (mContainedDecoder->HasSize() &&
-      mContainedDecoder->GetSize() != expectedSize) {
+      mContainedDecoder->GetSize() != GetRealSize()) {
     return Transition::Terminate(ICOState::FAILURE);
   }
 
   return Transition::Terminate(ICOState::SUCCESS);
 }
 
 void
 nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -38,16 +38,18 @@ enum class ICOState
   FINISHED_RESOURCE
 };
 
 class nsICODecoder : public Decoder
 {
 public:
   virtual ~nsICODecoder() { }
 
+  nsresult SetTargetSize(const nsIntSize& aSize) override;
+
   /// @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); }
@@ -56,19 +58,20 @@ public:
   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); }
 
-  virtual void SetResolution(const gfx::IntSize& aResolution) override
+  /// @return the size of the selected directory entry (mDirEntry).
+  gfx::IntSize GetRealSize() const
   {
-    mResolution = aResolution;
+    return gfx::IntSize(GetRealWidth(), GetRealHeight());
   }
 
   /// @return The offset from the beginning of the ICO to the first resource.
   size_t FirstResourceOffset() const;
 
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
   virtual void FinishInternal() override;
   virtual void FinishWithErrorInternal() override;
@@ -81,18 +84,16 @@ 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);
 
   // Gets decoder state from the contained decoder so it's visible externally.
   void GetFinalStateFromContainedDecoder();
 
-  // Sets the hotspot property of if we have a cursor
-  void SetHotSpotIfCursor();
   // Creates a bitmap file header buffer, returns true if successful
   bool FillBitmapFileHeaderBuffer(int8_t* bfh);
   // Fixes the ICO height to match that of the BIH.
   // and also fixes the BIH height to be /2 of what it was.
   // See definition for explanation.
   // Returns false if invalid information is contained within.
   bool FixBitmapHeight(int8_t* bih);
   // Fixes the ICO width to match that of the BIH.
@@ -113,20 +114,23 @@ private:
   LexerTransition<ICOState> ReadPNG(const char* aData, uint32_t aLen);
   LexerTransition<ICOState> ReadBIH(const char* aData);
   LexerTransition<ICOState> ReadBMP(const char* aData, uint32_t aLen);
   LexerTransition<ICOState> PrepareForMask();
   LexerTransition<ICOState> ReadMaskRow(const char* aData);
   LexerTransition<ICOState> FinishResource();
 
   StreamingLexer<ICOState, 32> mLexer; // The lexer.
+  Maybe<Downscaler> mDownscaler;       // Our downscaler, if we're downscaling.
   nsRefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder.
-  gfx::IntSize mResolution;            // The requested -moz-resolution.
   char mBIHraw[40];                    // The bitmap information header.
   IconDirEntry mDirEntry;              // The dir entry for the selected resource.
+  IntSize mBiggestResourceSize;        // Used to select the intrinsic size.
+  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?
--- a/image/test/mochitest/test_has_transparency.html
+++ b/image/test/mochitest/test_has_transparency.html
@@ -52,18 +52,19 @@ function testFiles() {
   yield ["damon.jpg", false];
 
   // Most BMPs are not transparent. (The TestMetadata GTest, which will
   // eventually replace this test totally, has coverage for the kinds that can be
   // transparent.)
   yield ["opaque.bmp", false];
 
   // ICO files which contain BMPs have an additional type of transparency - the
-  // AND mask - that warrants separate testing.
-  yield ["ico-bmp-opaque.ico", false];
+  // AND mask - that warrants separate testing. (Although, after bug 1201796,
+  // all ICOs are considered transparent.)
+  yield ["ico-bmp-opaque.ico", true];
   yield ["ico-bmp-transparent.ico", true];
 
   // SVGs are always transparent.
   yield ["lime100x100.svg", true];
 }
 
 function loadNext() {
   var currentFile = "";
index d37f2f1cc0534d9aec717a087a13dd4eb5b83a8f..025ebaed1ff12279d4e07cfcaf3bd1cbbeb0884b
GIT binary patch
literal 4286
zc%1E%KTE?v7{=dHP&Ye?bg`qWQ^xuQ{7ilmCk4T!L){cgx1vzWK~0Gw1dWm*5HJvk
z;q^J&lWS;Igv&YHQ-0-mkM{Rm{tz|r>2yeb(|L<%hlut8Ms!H?`Da?>;^N}s;_|Q4
z-e|VL9XJ687X1C;V8B8M#`@Nv=Nh~LPn&u2?7r`_?11&ep#LS9%~<as&wknKaS#M@
z-Umk}{aKc=-T{a(4Ebg>k~3k-t^9eOv)<w2?3AM@lItszXIK8BC|K{1BniiH%!_AZ
z2^0RZELrc6rs-D)c^$NSVZ_hKcO53<vAP!C&;R&0|GEzTbZX39^;bJQKHM91ukx!7
zPq^o%-Ru0X(Q_^CRsK5NRe#-X-EYIK{I=Z6Z_BOut+-{X@7{0n-kq}xwg4S3*w)ws
SY7f6@fF{@k*k)`qw)q3j(ZA6E
--- a/image/test/reftest/ico/ico-mixed/reftest.list
+++ b/image/test/reftest/ico/ico-mixed/reftest.list
@@ -1,14 +1,3 @@
 # ICO BMP and PNG mixed tests
 
-== mixed-bmp-png.ico mixed-bmp-png.png
-
-# Using media fragments to select different resolutions
-
-== mixed-bmp-png.ico#-moz-resolution=8,8 mixed-bmp-png.png
-== mixed-bmp-png.ico#test=true&-moz-resolution=8,8&other mixed-bmp-png.png
-== mixed-bmp-png.ico#-moz-resolution=32,32 mixed-bmp-png32.png
-== mixed-bmp-png.ico#-moz-resolution=39,39 mixed-bmp-png48.png
-== mixed-bmp-png.ico#-moz-resolution=40,40 mixed-bmp-png48.png
-== mixed-bmp-png.ico#-moz-resolution=48,48 mixed-bmp-png48.png
-== mixed-bmp-png.ico#-moz-resolution=64,64 mixed-bmp-png48.png
-== mixed-bmp-png.ico#-moz-resolution=64 mixed-bmp-png.png # Bad syntax will fall back to lowest resolution
+== mixed-bmp-png.ico mixed-bmp-png48.png
index df422207e7b9918f582896c7d7889c5103adf8f7..cc8a4a31db973df11dd9c21b56d12eabab61cb62
GIT binary patch
literal 1150
zc$|fkyGsK>5XM)uH-*?(c&LSqg4hIn{t2Sut2#uZrEjqEkyeOl?1ESavGoP9N(6-y
zAF&bCXdyv7&qjjCINu~2HYd>uU&v18_s#6yo>CS32?P{>TWY#WsamB}GmBH4$>$}i
z)W2~NQZ_5WBA<}6|KP0axf<tgqnA!*nnYJK0lx?2pw?uG$ofbhxtC{2>g?zttG-dx
z+R{eL69aS|?e}}QrU&zm=Xp{OZef0jHY3rZVzF(?z1|aMGv_F<HjCo%-C|#8WO$6=
ziGT;ZZ*J0*J&u?j_H8=1r)x|WGeGFAf5QWwhDRd2@+Lh{r)Sr3aBxVe)M;_8y%6_X
zed5s_e3mt418>&*1HDo6^z<y}0luN`flia3@;}ZVJ<EmefjJPFZCiNE;CbJ2hS+<#
z!0h1H*TS;KIWV7q$Kvd(<iNwHTyH2eIjz%s%-Z0gsqrlD%@q&So2<SY5S~>b<UTX&
zXb<e=a6em|!_VsT<8bu-W6ol^+5;LLe4){y9cH^q?lU|yk6$kHCGSAN!!bR~nYx1K
l;@RGkK}`wk^Re}r`h?uC_xHV8sgp{jRx9%QFLWIkus;g)Rv-WX
--- a/image/test/unit/test_imgtools.js
+++ b/image/test/unit/test_imgtools.js
@@ -686,30 +686,32 @@ imgFile = do_get_file(imgName);
 
 istream = getFileInputStream(imgFile);
 do_check_eq(istream.available(), 17759);
 var errsrc = "none";
 
 try {
   container = imgTools.decodeImage(istream, inMimeType);
 
-  // We should never hit this - decodeImage throws an assertion because the
-  // image decoded doesn't have enough frames.
+  // We expect to hit an error during encoding because the ICO header of the
+  // image is fine, but the actual resources are corrupt. Since decodeImage()
+  // only performs a metadata decode, it doesn't decode far enough to realize
+  // this, but we'll find out when we do a full decode during encodeImage().
   try {
       istream = imgTools.encodeImage(container, "image/png");
   } catch (e) {
       err = e;
       errsrc = "encode";
   }
 } catch (e) {
   err = e;
   errsrc = "decode";
 }
 
-do_check_eq(errsrc, "decode");
+do_check_eq(errsrc, "encode");
 checkExpectedError(/NS_ERROR_FAILURE/, err);
 
 
 /* ========== bug 815359  ========== */
 testnum = 815359;
 testdesc = "test correct ico hotspots (bug 815359)";
 
 imgName = "bug815359.ico";