Bug 514033 - Error recovery for imagelib - part 9 - Coalesce end-of-decode notifications into superclass.r=joe,a=blocker
authorBobby Holley <bobbyholley@gmail.com>
Sun, 12 Sep 2010 08:22:30 -0700
changeset 53669 0d39ca861f0abf0ef4edb6450ecfe316385eb7cf
parent 53668 f5b73f397fed5d871ddf613917fbfbe08b7aa4f1
child 53670 af488514712678b27dcafd3bb5b5ad7d63e57e97
push idunknown
push userunknown
push dateunknown
reviewersjoe, blocker
bugs514033
milestone2.0b6pre
Bug 514033 - Error recovery for imagelib - part 9 - Coalesce end-of-decode notifications into superclass.r=joe,a=blocker
modules/libpr0n/decoders/nsBMPDecoder.cpp
modules/libpr0n/decoders/nsGIFDecoder2.cpp
modules/libpr0n/decoders/nsGIFDecoder2.h
modules/libpr0n/decoders/nsICODecoder.cpp
modules/libpr0n/decoders/nsIconDecoder.cpp
modules/libpr0n/decoders/nsIconDecoder.h
modules/libpr0n/decoders/nsJPEGDecoder.cpp
modules/libpr0n/decoders/nsJPEGDecoder.h
modules/libpr0n/decoders/nsPNGDecoder.cpp
modules/libpr0n/decoders/nsPNGDecoder.h
modules/libpr0n/src/Decoder.cpp
modules/libpr0n/src/Decoder.h
--- a/modules/libpr0n/decoders/nsBMPDecoder.cpp
+++ b/modules/libpr0n/decoders/nsBMPDecoder.cpp
@@ -84,21 +84,17 @@ void
 nsBMPDecoder::FinishInternal()
 {
     // We should never make multiple frames
     NS_ABORT_IF_FALSE(GetFrameCount() <= 1, "Multiple BMP frames?");
 
     // Send notifications if appropriate
     if (!IsSizeDecode() && !IsError() && (GetFrameCount() == 1)) {
         PostFrameStop();
-        mImage->DecodingComplete();
-        if (mObserver) {
-            mObserver->OnStopContainer(nsnull, mImage);
-            mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
-        }
+        PostDecodeDone();
     }
 }
 
 // ----------------------------------------
 // Actual Data Processing
 // ----------------------------------------
 
 static void calcBitmask(PRUint32 aMask, PRUint8& aBegin, PRUint8& aLength)
--- a/modules/libpr0n/decoders/nsGIFDecoder2.cpp
+++ b/modules/libpr0n/decoders/nsGIFDecoder2.cpp
@@ -130,20 +130,21 @@ nsGIFDecoder2::~nsGIFDecoder2()
 {
   PR_FREEIF(mGIFStruct.local_colormap);
 }
 
 void
 nsGIFDecoder2::FinishInternal()
 {
   // If the GIF got cut off, handle it anyway
-  if (!IsSizeDecode() && !IsError()) {
+  if (!IsSizeDecode() && !IsError() && mGIFOpen) {
     if (mCurrentFrame == mGIFStruct.images_decoded)
       EndImageFrame();
-    EndGIF(/* aSuccess = */ PR_TRUE);
+    PostDecodeDone();
+    mGIFOpen = PR_FALSE;
   }
 
   mImage->SetLoopCount(mGIFStruct.loop_count);
 }
 
 // Push any new rows according to mCurrentPass/mLastFlushedPass and
 // mCurrentRow/mLastFlushedRow.  Note: caller is responsible for
 // updating mlastFlushed{Row,Pass}.
@@ -188,34 +189,16 @@ void nsGIFDecoder2::BeginGIF()
   PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
 
   // If we're doing a size decode, we have what we came for
   if (IsSizeDecode())
     return;
 }
 
 //******************************************************************************
-void nsGIFDecoder2::EndGIF(PRBool aSuccess)
-{
-  if (!mGIFOpen)
-    return;
-
-  if (aSuccess)
-    mImage->DecodingComplete();
-
-  if (mObserver) {
-    mObserver->OnStopContainer(nsnull, mImage);
-    mObserver->OnStopDecode(nsnull, aSuccess ? NS_OK : NS_ERROR_FAILURE,
-                            nsnull);
-  }
-
-  mGIFOpen = PR_FALSE;
-}
-
-//******************************************************************************
 nsresult nsGIFDecoder2::BeginImageFrame(gfx_depth aDepth)
 {
   PRUint32 imageDataLength;
   nsresult rv;
   gfxASurface::gfxImageFormat format;
   if (mGIFStruct.is_transparent)
     format = gfxASurface::ImageFormatARGB32;
   else
@@ -1062,40 +1045,38 @@ nsGIFDecoder2::WriteInternal(const char 
       } else {
         /* See if there are any more images in this sequence. */
         EndImageFrame();
         GETN(1, gif_image_start);
       }
       break;
 
     case gif_done:
-      EndGIF(/* aSuccess = */ PR_TRUE);
+      PostDecodeDone();
+      mGIFOpen = PR_FALSE;
       goto done;
 
     case gif_error:
       PostDataError();
-      EndGIF(/* aSuccess = */ PR_FALSE);
       return;
 
     // Handle out of memory errors
     case gif_oom:
       PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
-      EndGIF(/* aSuccess = */ PR_FALSE);
       return;
 
     // We shouldn't ever get here.
     default:
       break;
     }
   }
 
   // if an error state is set but no data remains, code flow reaches here
   if (mGIFStruct.state == gif_error) {
       PostDataError();
-      EndGIF(/* aSuccess = */ PR_FALSE);
       return;
   }
   
   // Copy the leftover into mGIFStruct.hold
   mGIFStruct.bytes_in_hold = len;
   if (len) {
     // Add what we have sofar to the block
     PRUint8* p = (mGIFStruct.state == gif_global_colormap) ? (PRUint8*)mGIFStruct.global_colormap :
--- a/modules/libpr0n/decoders/nsGIFDecoder2.h
+++ b/modules/libpr0n/decoders/nsGIFDecoder2.h
@@ -64,17 +64,16 @@ public:
   virtual void WriteInternal(const char* aBuffer, PRUint32 aCount);
   virtual void FinishInternal();
 
 private:
   /* These functions will be called when the decoder has a decoded row,
    * frame size information, etc. */
 
   void      BeginGIF();
-  void      EndGIF(PRBool aSuccess);
   nsresult  BeginImageFrame(gfx_depth aDepth);
   void      EndImageFrame();
   void      FlushImageData();
   void      FlushImageData(PRUint32 fromRow, PRUint32 rows);
 
   nsresult  GifWrite(const PRUint8 * buf, PRUint32 numbytes);
   PRUint32  OutputRow();
   PRBool    DoLzw(const PRUint8 *q);
--- a/modules/libpr0n/decoders/nsICODecoder.cpp
+++ b/modules/libpr0n/decoders/nsICODecoder.cpp
@@ -109,21 +109,17 @@ nsICODecoder::FinishInternal()
   // Send notifications if appropriate
   if (!IsSizeDecode() && !IsError() && (GetFrameCount() == 1)) {
 
     // Invalidate
     nsIntRect r(0, 0, mDirEntry.mWidth, mDirEntry.mHeight);
     PostInvalidation(r);
 
     PostFrameStop();
-    mImage->DecodingComplete();
-    if (mObserver) {
-      mObserver->OnStopContainer(nsnull, 0);
-      mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
-    }
+    PostDecodeDone();
   }
 }
 
 void
 nsICODecoder::WriteInternal(const char* aBuffer, PRUint32 aCount)
 {
   // No forgiveness
   if (IsError())
--- a/modules/libpr0n/decoders/nsIconDecoder.cpp
+++ b/modules/libpr0n/decoders/nsIconDecoder.cpp
@@ -51,35 +51,25 @@ namespace mozilla {
 namespace imagelib {
 
 nsIconDecoder::nsIconDecoder() :
   mWidth(-1),
   mHeight(-1),
   mPixBytesRead(0),
   mPixBytesTotal(0),
   mImageData(nsnull),
-  mState(iconStateStart),
-  mNotifiedDone(PR_FALSE)
+  mState(iconStateStart)
 {
   // Nothing to do
 }
 
 nsIconDecoder::~nsIconDecoder()
 { }
 
 void
-nsIconDecoder::FinishInternal()
-{
-  // If we haven't notified of completion yet for a full/success decode, we
-  // didn't finish. Notify in error mode
-  if (!IsSizeDecode() && !mNotifiedDone)
-    NotifyDone(/* aSuccess = */ PR_FALSE);
-}
-
-void
 nsIconDecoder::WriteInternal(const char *aBuffer, PRUint32 aCount)
 {
   nsresult rv;
 
   if (IsError())
     return;
 
   // We put this here to avoid errors about crossing initialization with case
@@ -149,45 +139,26 @@ nsIconDecoder::WriteInternal(const char 
 
         // Book Keeping
         aBuffer += bytesToRead;
         aCount -= bytesToRead;
         mPixBytesRead += bytesToRead;
 
         // If we've got all the pixel bytes, we're finished
         if (mPixBytesRead == mPixBytesTotal) {
-          NotifyDone(/* aSuccess = */ PR_TRUE);
+          PostFrameStop();
+          PostDecodeDone();
           mState = iconStateFinished;
         }
         break;
 
       case iconStateFinished:
 
         // Consume all excess data silently
         aCount = 0;
 
         break;
     }
   }
 }
 
-void
-nsIconDecoder::NotifyDone(PRBool aSuccess)
-{
-  // We should only call this once
-  NS_ABORT_IF_FALSE(!mNotifiedDone, "Calling NotifyDone twice");
-
-  // Notify
-  PostFrameStop();
-  if (aSuccess)
-    mImage->DecodingComplete();
-  if (mObserver) {
-    mObserver->OnStopContainer(nsnull, mImage);
-    mObserver->OnStopDecode(nsnull, aSuccess ? NS_OK : NS_ERROR_FAILURE,
-                            nsnull);
-  }
-
-  // Flag that we've notified
-  mNotifiedDone = PR_TRUE;
-}
-
 } // namespace imagelib
 } // namespace mozilla
--- a/modules/libpr0n/decoders/nsIconDecoder.h
+++ b/modules/libpr0n/decoders/nsIconDecoder.h
@@ -73,27 +73,23 @@ class RasterImage;
 class nsIconDecoder : public Decoder
 {
 public:
 
   nsIconDecoder();
   virtual ~nsIconDecoder();
 
   virtual void WriteInternal(const char* aBuffer, PRUint32 aCount);
-  virtual void FinishInternal();
 
   PRUint8 mWidth;
   PRUint8 mHeight;
   PRUint32 mPixBytesRead;
   PRUint32 mPixBytesTotal;
   PRUint8* mImageData;
   PRUint32 mState;
-
-  PRBool mNotifiedDone;
-  void NotifyDone(PRBool aSuccess);
 };
 
 enum {
   iconStateStart      = 0,
   iconStateHaveHeight = 1,
   iconStateReadPixels = 2,
   iconStateFinished   = 3
 };
--- a/modules/libpr0n/decoders/nsJPEGDecoder.cpp
+++ b/modules/libpr0n/decoders/nsJPEGDecoder.cpp
@@ -94,17 +94,16 @@ METHODDEF(void) my_error_exit (j_common_
 /* Normal JFIF markers can't have more bytes than this. */
 #define MAX_JPEG_MARKER_LENGTH  (((PRUint32)1 << 16) - 1)
 
 
 nsJPEGDecoder::nsJPEGDecoder()
 {
   mState = JPEG_HEADER;
   mReading = PR_TRUE;
-  mNotifiedDone = PR_FALSE;
   mImageData = nsnull;
 
   mBytesToSkip = 0;
   memset(&mInfo, 0, sizeof(jpeg_decompress_struct));
   memset(&mSourceMgr, 0, sizeof(mSourceMgr));
   mInfo.client_data = (void*)this;
 
   mSegment = nsnull;
@@ -182,26 +181,16 @@ nsJPEGDecoder::FinishInternal()
    * XXXbholley - It seems wrong that this should be necessary, but at the
    * moment I'm just folding the contents of Flush() into Close() so that
    * we can get rid of it.
    */
   if ((mState != JPEG_DONE && mState != JPEG_SINK_NON_JPEG_TRAILER) &&
       (mState != JPEG_ERROR) &&
       !IsSizeDecode())
     this->Write(nsnull, 0);
-
-  /* If we already know we're in an error state, don't
-     bother flagging another one here. */
-  if (mState == JPEG_ERROR)
-    return;
-
-  /* If we're doing a full decode and haven't notified of completion yet,
-   * we must not have got everything we wanted. Send error notifications. */
-  if (!IsSizeDecode() && !mNotifiedDone)
-    NotifyDone(/* aSuccess = */ PR_FALSE);
 }
 
 void
 nsJPEGDecoder::WriteInternal(const char *aBuffer, PRUint32 aCount)
 {
   mSegment = (const JOCTET *)aBuffer;
   mSegmentLen = aCount;
 
@@ -549,33 +538,20 @@ nsJPEGDecoder::WriteInternal(const char 
   }
 
   PR_LOG(gJPEGDecoderAccountingLog, PR_LOG_DEBUG,
          ("} (end of function)"));
   return;
 }
 
 void
-nsJPEGDecoder::NotifyDone(PRBool aSuccess)
+nsJPEGDecoder::NotifyDone()
 {
-  // We should only be called once
-  NS_ABORT_IF_FALSE(!mNotifiedDone, "calling NotifyDone twice!");
-
-  // Notify
   PostFrameStop();
-  if (aSuccess)
-    mImage->DecodingComplete();
-  if (mObserver) {
-    mObserver->OnStopContainer(nsnull, mImage);
-    mObserver->OnStopDecode(nsnull, aSuccess ? NS_OK : NS_ERROR_FAILURE,
-                            nsnull);
-  }
-
-  // Mark that we've been called
-  mNotifiedDone = PR_TRUE;
+  PostDecodeDone();
 }
 
 void
 nsJPEGDecoder::OutputScanlines(PRBool* suspend)
 {
   *suspend = PR_FALSE;
 
   const PRUint32 top = mInfo.output_scanline;
@@ -881,18 +857,18 @@ term_source (j_decompress_ptr jd)
 {
   nsJPEGDecoder *decoder = (nsJPEGDecoder *)(jd->client_data);
 
   // This function shouldn't be called if we ran into an error we didn't
   // recover from.
   NS_ABORT_IF_FALSE(decoder->mState != JPEG_ERROR,
                     "Calling term_source on a JPEG with mState == JPEG_ERROR!");
 
-  // Notify
-  decoder->NotifyDone(/* aSuccess = */ PR_TRUE);
+  // Notify using a helper method to get around protectedness issues.
+  decoder->NotifyDone();
 }
 
 } // namespace imagelib
 } // namespace mozilla
 
 
 /**************** YCbCr -> Cairo's RGB24/ARGB32 conversion: most common case **************/
 
--- a/modules/libpr0n/decoders/nsJPEGDecoder.h
+++ b/modules/libpr0n/decoders/nsJPEGDecoder.h
@@ -88,17 +88,17 @@ class nsJPEGDecoder : public Decoder
 public:
   nsJPEGDecoder();
   virtual ~nsJPEGDecoder();
 
   virtual void InitInternal();
   virtual void WriteInternal(const char* aBuffer, PRUint32 aCount);
   virtual void FinishInternal();
 
-  void NotifyDone(PRBool aSuccess);
+  void NotifyDone();
 
 protected:
   void OutputScanlines(PRBool* suspend);
 
 public:
   PRUint8 *mImageData;
 
   struct jpeg_decompress_struct mInfo;
@@ -118,15 +118,14 @@ public:
 
   JOCTET  *mProfile;
   PRUint32 mProfileLength;
 
   qcms_profile *mInProfile;
   qcms_transform *mTransform;
 
   PRPackedBool mReading;
-  PRPackedBool mNotifiedDone;
 };
 
 } // namespace imagelib
 } // namespace mozilla
 
 #endif // nsJPEGDecoder_h__
--- a/modules/libpr0n/decoders/nsPNGDecoder.cpp
+++ b/modules/libpr0n/decoders/nsPNGDecoder.cpp
@@ -81,18 +81,17 @@ static PRLogModuleInfo *gPNGDecoderAccou
 static const PRUint8 pngSignatureBytes[] =
                { 137, 80, 78, 71, 13, 10, 26, 10 };
 
 nsPNGDecoder::nsPNGDecoder() :
   mPNG(nsnull), mInfo(nsnull),
   mCMSLine(nsnull), interlacebuf(nsnull),
   mInProfile(nsnull), mTransform(nsnull),
   mHeaderBuf(nsnull), mHeaderBytesRead(0),
-  mChannels(0), mFrameIsHidden(PR_FALSE),
-  mNotifiedDone(PR_FALSE)
+  mChannels(0), mFrameIsHidden(PR_FALSE)
 {
 }
 
 nsPNGDecoder::~nsPNGDecoder()
 {
   if (mPNG)
     png_destroy_read_struct(&mPNG, mInfo ? &mInfo : NULL, NULL);
   if (mCMSLine)
@@ -285,26 +284,16 @@ nsPNGDecoder::InitInternal()
   png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this),
                               nsPNGDecoder::info_callback,
                               nsPNGDecoder::row_callback,
                               nsPNGDecoder::end_callback);
 
 }
 
 void
-nsPNGDecoder::FinishInternal()
-{
-
-  // If we're a full/success decode but haven't sent stop notifications yet,
-  // we didn't get all the data we needed. Send error notifications.
-  if (!IsSizeDecode() && !mNotifiedDone)
-    NotifyDone(/* aSuccess = */ PR_FALSE);
-}
-
-void
 nsPNGDecoder::WriteInternal(const char *aBuffer, PRUint32 aCount)
 {
   // We use gotos, so we need to declare variables here
   PRUint32 width = 0;
   PRUint32 height = 0;
 
   // No forgiveness if we previously hit an error
   if (IsError())
@@ -363,36 +352,16 @@ nsPNGDecoder::WriteInternal(const char *
     }
 
     // Pass the data off to libpng
     png_process_data(mPNG, mInfo, (unsigned char *)aBuffer, aCount);
 
   }
 }
 
-void
-nsPNGDecoder::NotifyDone(PRBool aSuccess)
-{
-  // We should only be called once
-  NS_ABORT_IF_FALSE(!mNotifiedDone, "Calling NotifyDone twice!");
-
-  // Notify
-  EndImageFrame();
-  if (aSuccess)
-    mImage->DecodingComplete();
-  if (mObserver) {
-    mObserver->OnStopContainer(nsnull, mImage);
-    mObserver->OnStopDecode(nsnull, aSuccess ? NS_OK : NS_ERROR_FAILURE,
-                            nsnull);
-  }
-
-  // Mark that we've been called
-  mNotifiedDone = PR_TRUE;
-}
-
 // Sets up gamma pre-correction in libpng before our callback gets called.
 // We need to do this if we don't end up with a CMS profile.
 static void
 PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr)
 {
   double aGamma;
 
   if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) {
@@ -858,17 +827,18 @@ nsPNGDecoder::end_callback(png_structp p
 #ifdef PNG_APNG_SUPPORTED
   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) {
     PRInt32 num_plays = png_get_num_plays(png_ptr, info_ptr);
     decoder->mImage->SetLoopCount(num_plays - 1);
   }
 #endif
 
   // Send final notifications
-  decoder->NotifyDone(/* aSuccess = */ PR_TRUE);
+  decoder->EndImageFrame();
+  decoder->PostDecodeDone();
 }
 
 
 void
 nsPNGDecoder::error_callback(png_structp png_ptr, png_const_charp error_msg)
 {
   PR_LOG(gPNGLog, PR_LOG_ERROR, ("libpng error: %s\n", error_msg));
   longjmp(png_jmpbuf(png_ptr), 1);
--- a/modules/libpr0n/decoders/nsPNGDecoder.h
+++ b/modules/libpr0n/decoders/nsPNGDecoder.h
@@ -59,25 +59,23 @@ class RasterImage;
 class nsPNGDecoder : public Decoder
 {
 public:
   nsPNGDecoder();
   virtual ~nsPNGDecoder();
 
   virtual void InitInternal();
   virtual void WriteInternal(const char* aBuffer, PRUint32 aCount);
-  virtual void FinishInternal();
 
   void CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
                    PRInt32 width, PRInt32 height,
                    gfxASurface::gfxImageFormat format);
   void SetAnimFrameInfo();
 
   void EndImageFrame();
-  void NotifyDone(PRBool aSuccess);
 
 public:
   png_structp mPNG;
   png_infop mInfo;
   nsIntRect mFrameRect;
   PRUint8 *mCMSLine;
   PRUint8 *interlacebuf;
   PRUint8 *mImageData;
@@ -88,17 +86,16 @@ public:
 
   // For size decodes
   PRUint8 *mHeaderBuf;
   PRUint32 mHeaderBytesRead;
 
   PRUint8 mChannels;
   PRPackedBool mFrameHasNoAlpha;
   PRPackedBool mFrameIsHidden;
-  PRPackedBool mNotifiedDone;
 
   /*
    * libpng callbacks
    *
    * We put these in the class so that they can access protected members.
    */
   static void PNGAPI info_callback(png_structp png_ptr, png_infop info_ptr);
   static void PNGAPI row_callback(png_structp png_ptr, png_bytep new_row,
--- a/modules/libpr0n/src/Decoder.cpp
+++ b/modules/libpr0n/src/Decoder.cpp
@@ -42,16 +42,17 @@ namespace mozilla {
 namespace imagelib {
 
 Decoder::Decoder()
   : mFrameCount(0)
   , mFailCode(NS_OK)
   , mInitialized(false)
   , mSizeDecode(false)
   , mInFrame(false)
+  , mDecodeDone(false)
   , mDataError(false)
 {
 }
 
 Decoder::~Decoder()
 {
   NS_WARN_IF_FALSE(!mInFrame, "Shutting down decoder mid-frame!");
   mInitialized = false;
@@ -92,17 +93,33 @@ Decoder::Write(const char* aBuffer, PRUi
   return IsError() ? NS_ERROR_FAILURE : NS_OK;
 }
 
 nsresult
 Decoder::Finish()
 {
   // Implementation-specific finalization
   FinishInternal();
-  return IsError() ? NS_ERROR_FAILURE : NS_OK;
+
+  // If the implementation left us mid-frame, finish that up.
+  if (mInFrame)
+    PostFrameStop();
+
+  if (IsError())
+    return NS_ERROR_FAILURE;
+
+  // If the implementation didn't post success, we assume failure
+  if (!IsSizeDecode() && !mDecodeDone) {
+    if (mObserver) {
+      mObserver->OnStopContainer(nsnull, mImage);
+      mObserver->OnStopDecode(nsnull, NS_ERROR_FAILURE, nsnull);
+    }
+  }
+
+  return NS_OK;
 }
 
 void
 Decoder::FlushInvalidations()
 {
   // If we've got an empty invalidation rect, we have nothing to do
   if (mInvalidRect.IsEmpty())
     return;
@@ -196,16 +213,32 @@ Decoder::PostInvalidation(nsIntRect& aRe
   // We should be mid-frame
   NS_ABORT_IF_FALSE(mInFrame, "Can't invalidate when not mid-frame!");
 
   // Account for the new region
   mInvalidRect.UnionRect(mInvalidRect, aRect);
 }
 
 void
+Decoder::PostDecodeDone()
+{
+  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!");
+  mDecodeDone = true;
+
+  // Notify
+  mImage->DecodingComplete();
+  if (mObserver) {
+    mObserver->OnStopContainer(nsnull, mImage);
+    mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
+  }
+}
+
+void
 Decoder::PostDataError()
 {
   mDataError = true;
 
   //XXXbholley - we should probably log to the web console here
 }
 
 void
--- a/modules/libpr0n/src/Decoder.h
+++ b/modules/libpr0n/src/Decoder.h
@@ -148,16 +148,23 @@ protected:
   // notifications, and does internal book-keeping.
   void PostFrameStart();
   void PostFrameStop();
 
   // Called by the decoders when they have a region to invalidate. We may not
   // actually pass these invalidations on right away.
   void PostInvalidation(nsIntRect& aRect);
 
+  // Called by the decoders when they have successfully decoded the image. This
+  // may occur as the result of the decoder getting to the appropriate point in
+  // the stream, or by us calling FinishInternal().
+  //
+  // May not be called mid-frame.
+  void PostDecodeDone();
+
   // Data errors are the fault of the source data, decoder errors are our fault
   void PostDataError();
   void PostDecoderError(nsresult aFailCode);
 
   /*
    * Member variables.
    *
    * XXX - Some of these become private later in the patch stack.
@@ -169,15 +176,16 @@ protected:
 
   nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame.
 
   nsresult mFailCode;
 
   bool mInitialized;
   bool mSizeDecode;
   bool mInFrame;
+  bool mDecodeDone;
   bool mDataError;
 };
 
 } // namespace imagelib
 } // namespace mozilla
 
 #endif // MOZILLA_IMAGELIB_DECODER_H_