Bug 435296 - Decode-On-Draw. r=joe,roc,bz,dolske,peterw sr=vlad
authorBobby Holley <bobbyholley@stanford.edu>
Sat, 12 Sep 2009 16:44:18 -0600
changeset 32424 9f856f094fea4e212c71e8f9fdb4ca4e64867316
parent 32423 7df4c375164fd13c2290e178f0e11dc5559b81b0
child 32425 03d92c1c09fffa9d4d70b910c97efe15ab1669f5
push id9068
push userbobbyholley@stanford.edu
push dateSat, 12 Sep 2009 22:46:32 +0000
treeherdermozilla-central@faf7eff2f2ea [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoe, roc, bz, dolske, peterw, vlad
bugs435296
milestone1.9.3a1pre
Bug 435296 - Decode-On-Draw. r=joe,roc,bz,dolske,peterw sr=vlad
browser/components/shell/src/nsWindowsShellService.cpp
content/base/src/nsImageLoadingContent.cpp
content/base/src/nsStubImageDecoderObserver.cpp
content/canvas/src/nsCanvasRenderingContext2D.cpp
content/canvas/test/test_canvas.html
content/svg/content/src/nsSVGFilters.cpp
layout/base/nsCSSRendering.cpp
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/base/nsImageLoader.cpp
layout/base/nsImageLoader.h
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/base/nsPresContext.cpp
layout/base/nsPresShell.cpp
layout/base/tests/test_bug445810.html
layout/generic/nsBulletFrame.cpp
layout/generic/nsFrame.cpp
layout/generic/nsImageFrame.cpp
layout/generic/nsImageFrame.h
layout/generic/nsPageFrame.cpp
layout/generic/nsSimplePageSequence.cpp
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/nsStyleStructInlines.h
layout/svg/base/src/nsSVGImageFrame.cpp
layout/xul/base/src/nsImageBoxFrame.cpp
layout/xul/base/src/nsImageBoxFrame.h
layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
modules/libpr0n/build/nsImageModule.cpp
modules/libpr0n/decoders/bmp/nsBMPDecoder.cpp
modules/libpr0n/decoders/bmp/nsBMPDecoder.h
modules/libpr0n/decoders/bmp/nsICODecoder.cpp
modules/libpr0n/decoders/bmp/nsICODecoder.h
modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp
modules/libpr0n/decoders/gif/nsGIFDecoder2.h
modules/libpr0n/decoders/icon/nsIconDecoder.cpp
modules/libpr0n/decoders/icon/nsIconDecoder.h
modules/libpr0n/decoders/icon/nsIconModule.cpp
modules/libpr0n/decoders/jpeg/nsJPEGDecoder.cpp
modules/libpr0n/decoders/jpeg/nsJPEGDecoder.h
modules/libpr0n/decoders/png/nsPNGDecoder.cpp
modules/libpr0n/decoders/png/nsPNGDecoder.h
modules/libpr0n/public/Makefile.in
modules/libpr0n/public/imgIContainer.idl
modules/libpr0n/public/imgIDecoder.idl
modules/libpr0n/public/imgIDecoderObserver.idl
modules/libpr0n/public/imgILoad.idl
modules/libpr0n/public/imgIRequest.idl
modules/libpr0n/public/imgITools.idl
modules/libpr0n/src/imgContainer.cpp
modules/libpr0n/src/imgContainer.h
modules/libpr0n/src/imgLoader.cpp
modules/libpr0n/src/imgLoader.h
modules/libpr0n/src/imgRequest.cpp
modules/libpr0n/src/imgRequest.h
modules/libpr0n/src/imgRequestProxy.cpp
modules/libpr0n/src/imgRequestProxy.h
modules/libpr0n/src/imgTools.cpp
modules/libpr0n/test/mochitest/Makefile.in
modules/libpr0n/test/mochitest/imgutils.js
modules/libpr0n/test/reftest/apng/delaytest.html
modules/libpr0n/test/unit/test_imgtools.js
toolkit/system/gnome/nsAlertsIconListener.cpp
widget/src/cocoa/nsClipboard.mm
widget/src/cocoa/nsMenuItemIconX.mm
widget/src/gtk2/nsImageToPixbuf.cpp
widget/src/os2/nsWindow.cpp
widget/src/windows/nsImageClipboard.cpp
widget/src/windows/nsWindowGfx.cpp
widget/src/xpwidgets/nsBaseDragService.cpp
--- a/browser/components/shell/src/nsWindowsShellService.cpp
+++ b/browser/components/shell/src/nsWindowsShellService.cpp
@@ -519,17 +519,19 @@ nsWindowsShellService::SetShouldCheckDef
 
   return NS_OK;
 }
 
 static nsresult
 WriteBitmap(nsIFile* aFile, imgIContainer* aImage)
 {
   nsRefPtr<gfxImageSurface> image;
-  nsresult rv = aImage->CopyCurrentFrame(getter_AddRefs(image));
+  nsresult rv = aImage->CopyFrame(imgIContainer::FRAME_FIRST,
+                                  imgIContainer::FLAG_SYNC_DECODE,
+                                  getter_AddRefs(image));
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRInt32 width = image->Width();
   PRInt32 height = image->Height();
 
   PRUint8* bits = image->Data();
   PRUint32 length = image->GetDataSize();
   PRUint32 bpr = PRUint32(image->Stride());
--- a/content/base/src/nsImageLoadingContent.cpp
+++ b/content/base/src/nsImageLoadingContent.cpp
@@ -263,16 +263,24 @@ nsImageLoadingContent::OnStopDecode(imgI
 NS_IMETHODIMP
 nsImageLoadingContent::OnStopRequest(imgIRequest* aRequest, PRBool aLastPart)
 {
   LOOP_OVER_OBSERVERS(OnStopRequest(aRequest, aLastPart));
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsImageLoadingContent::OnDiscard(imgIRequest *aRequest)
+{
+  LOOP_OVER_OBSERVERS(OnDiscard(aRequest));
+
+  return NS_OK;
+}
+
 /*
  * nsIImageLoadingContent impl
  */
 
 NS_IMETHODIMP
 nsImageLoadingContent::GetLoadingEnabled(PRBool *aLoadingEnabled)
 {
   *aLoadingEnabled = mLoadingEnabled;
--- a/content/base/src/nsStubImageDecoderObserver.cpp
+++ b/content/base/src/nsStubImageDecoderObserver.cpp
@@ -95,14 +95,20 @@ nsStubImageDecoderObserver::OnStopDecode
 
 NS_IMETHODIMP
 nsStubImageDecoderObserver::OnStopRequest(imgIRequest *aRequest, 
                                           PRBool aIsLastPart)
 {
     return NS_OK;
 }
 
+NS_IMETHODIMP 
+nsStubImageDecoderObserver::OnDiscard(imgIRequest *aRequest)
+{
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 nsStubImageDecoderObserver::FrameChanged(imgIContainer *aContainer,
                                          nsIntRect * aDirtyRect)
 {
     return NS_OK;
 }
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp
@@ -1324,18 +1324,21 @@ nsCanvasRenderingContext2D::CreatePatter
         extend = gfxPattern::EXTEND_REPEAT;
     } else if (repeat.EqualsLiteral("no-repeat")) {
         extend = gfxPattern::EXTEND_NONE;
     } else {
         // XXX ERRMSG we need to report an error to developers here! (bug 329026)
         return NS_ERROR_DOM_SYNTAX_ERR;
     }
 
+    // The canvas spec says that createPattern should use the first frame
+    // of animated images
     nsLayoutUtils::SurfaceFromElementResult res =
-        nsLayoutUtils::SurfaceFromElement(image, nsLayoutUtils::SFE_WANT_NEW_SURFACE);
+        nsLayoutUtils::SurfaceFromElement(image, nsLayoutUtils::SFE_WANT_FIRST_FRAME |
+                                                 nsLayoutUtils::SFE_WANT_NEW_SURFACE);
     if (!res.mSurface)
         return NS_ERROR_NOT_AVAILABLE;
 
     nsRefPtr<gfxPattern> thebespat = new gfxPattern(res.mSurface);
 
     thebespat->SetExtend(extend);
 
     nsRefPtr<nsCanvasPattern> pat = new nsCanvasPattern(thebespat, res.mPrincipal,
@@ -2960,26 +2963,30 @@ nsCanvasRenderingContext2D::DrawImage()
                                  NS_GET_IID(nsIDOMElement),
                                  ctx, argv[0]))
         return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
 
     gfxMatrix matrix;
     nsRefPtr<gfxPattern> pattern;
     nsRefPtr<gfxPath> path;
 
+    // The canvas spec says that drawImage should draw the first frame
+    // of animated images
+    PRUint32 sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME;
     nsLayoutUtils::SurfaceFromElementResult res =
-        nsLayoutUtils::SurfaceFromElement(imgElt);
+        nsLayoutUtils::SurfaceFromElement(imgElt, sfeFlags);
     if (!res.mSurface)
         return NS_ERROR_NOT_AVAILABLE;
 
 #ifndef WINCE
     // On non-CE, force a copy if we're using drawImage with our destination
     // as a source to work around some Cairo self-copy semantics issues.
     if (res.mSurface == mSurface) {
-        res = nsLayoutUtils::SurfaceFromElement(imgElt, nsLayoutUtils::SFE_WANT_NEW_SURFACE);
+        sfeFlags |= nsLayoutUtils::SFE_WANT_NEW_SURFACE;
+        res = nsLayoutUtils::SurfaceFromElement(imgElt, sfeFlags);
         if (!res.mSurface)
             return NS_ERROR_NOT_AVAILABLE;
     }
 #endif
 
     nsRefPtr<gfxASurface> imgsurf = res.mSurface;
     nsCOMPtr<nsIPrincipal> principal = res.mPrincipal;
     gfxIntSize imgSize = res.mSize;
--- a/content/canvas/test/test_canvas.html
+++ b/content/canvas/test/test_canvas.html
@@ -2826,17 +2826,17 @@ var canvas108 = document.getElementById(
 var ctx108 = canvas108.getContext('2d');
 var isDone_test_2d_drawImage_animated_apng = false;
 
 function test_2d_drawImage_animated_apng() {
 
 deferTest();
 setTimeout(wrapFunction(function () {
     ctx108.drawImage(document.getElementById('anim-gr_1.png'), 0, 0);
-    todo_isPixel(ctx108, 50,25, 0,255,0,255, 2);
+    isPixel(ctx108, 50,25, 0,255,0,255, 2);
 	isDone_test_2d_drawImage_animated_apng = true;
 }), 5000);
 
 
 }
 </script>
 <img src="image_anim-gr.png" id="anim-gr_1.png" class="resource">
 
@@ -2851,17 +2851,17 @@ var canvas109 = document.getElementById(
 var ctx109 = canvas109.getContext('2d');
 var isDone_test_2d_drawImage_animated_gif = false;
 
 function test_2d_drawImage_animated_gif() {
 
 deferTest();
 setTimeout(wrapFunction(function () {
     ctx109.drawImage(document.getElementById('anim-gr_1.gif'), 0, 0);
-    todo_isPixel(ctx109, 50,25, 0,255,0,255, 2);
+    isPixel(ctx109, 50,25, 0,255,0,255, 2);
 	isDone_test_2d_drawImage_animated_gif = true;
 }), 5000);
 
 
 }
 </script>
 <img src="image_anim-gr.gif" id="anim-gr_1.gif" class="resource">
 
@@ -14485,18 +14485,18 @@ function test_2d_pattern_animated_gif() 
 
 deferTest();
 setTimeout(function () {
     var pattern = ctx458.createPattern(document.getElementById('anim-gr_2.gif'), 'repeat');
     ctx458.fillStyle = pattern;
     ctx458.fillRect(0, 0, 50, 50);
     setTimeout(wrapFunction(function () {
         ctx458.fillRect(50, 0, 50, 50);
-        todo_isPixel(ctx458, 25,25, 0,255,0,255, 2);
-        todo_isPixel(ctx458, 75,25, 0,255,0,255, 2);
+        isPixel(ctx458, 25,25, 0,255,0,255, 2);
+        isPixel(ctx458, 75,25, 0,255,0,255, 2);
 		isDone_test_2d_pattern_animated_gif = true;
     }), 2500);
 }, 2500);
 
 
 }
 </script>
 <img src="image_anim-gr.gif" id="anim-gr_2.gif" class="resource">
--- a/content/svg/content/src/nsSVGFilters.cpp
+++ b/content/svg/content/src/nsSVGFilters.cpp
@@ -5388,17 +5388,19 @@ nsSVGFEImageElement::Filter(nsSVGFilterI
              getter_AddRefs(currentRequest));
 
   nsCOMPtr<imgIContainer> imageContainer;
   if (currentRequest)
     currentRequest->GetImage(getter_AddRefs(imageContainer));
 
   nsRefPtr<gfxASurface> currentFrame;
   if (imageContainer)
-    imageContainer->GetCurrentFrame(getter_AddRefs(currentFrame));
+    imageContainer->GetFrame(imgIContainer::FRAME_CURRENT,
+                             imgIContainer::FLAG_NONE,
+                             getter_AddRefs(currentFrame));
 
   // We need to wrap the surface in a pattern to have somewhere to set the
   // graphics filter.
   nsRefPtr<gfxPattern> thebesPattern;
   if (currentFrame)
     thebesPattern = new gfxPattern(currentFrame);
 
   if (thebesPattern) {
@@ -5477,16 +5479,22 @@ nsSVGFEImageElement::FrameChanged(imgICo
 }
 
 NS_IMETHODIMP
 nsSVGFEImageElement::OnStartContainer(imgIRequest *aRequest,
                                       imgIContainer *aContainer)
 {
   nsresult rv =
     nsImageLoadingContent::OnStartContainer(aRequest, aContainer);
+
+  // Request a decode
+  NS_ABORT_IF_FALSE(aContainer, "who sent the notification then?");
+  aContainer->RequestDecode();
+
+  // We have a size - invalidate
   Invalidate();
   return rv;
 }
 
 //----------------------------------------------------------------------
 // helper methods
 
 void
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -2099,17 +2099,17 @@ DrawBorderImage(nsPresContext*       aPr
   imgIRequest *req = aBorderStyle.GetBorderImage();
 
 #ifdef DEBUG
   {
     PRUint32 status = imgIRequest::STATUS_ERROR;
     if (req)
       req->GetImageStatus(&status);
 
-    NS_ASSERTION(req && (status & imgIRequest::STATUS_FRAME_COMPLETE),
+    NS_ASSERTION(req && (status & imgIRequest::STATUS_LOAD_COMPLETE),
                  "no image to draw");
   }
 #endif
 
   // Get the actual image, and determine where the split points are.
   // Note that mBorderImageSplit is in image pixels, not necessarily
   // CSS pixels.
 
@@ -2295,31 +2295,33 @@ DrawBorderImageComponent(nsIRenderingCon
                          PRUint8              aHFill,
                          PRUint8              aVFill,
                          const nsSize&        aUnitSize)
 {
   if (aFill.IsEmpty() || aSrc.IsEmpty())
     return;
 
   nsCOMPtr<imgIContainer> subImage;
-  if (NS_FAILED(aImage->ExtractCurrentFrame(aSrc, getter_AddRefs(subImage))))
+  if (NS_FAILED(aImage->ExtractFrame(imgIContainer::FRAME_CURRENT, aSrc,
+                                     imgIContainer::FLAG_SYNC_DECODE,
+                                     getter_AddRefs(subImage))))
     return;
 
   gfxPattern::GraphicsFilter graphicsFilter =
     nsLayoutUtils::GetGraphicsFilterForFrame(aForFrame);
 
   // If we have no tiling in either direction, we can skip the intermediate
   // scaling step.
   if ((aHFill == NS_STYLE_BORDER_IMAGE_STRETCH &&
        aVFill == NS_STYLE_BORDER_IMAGE_STRETCH) ||
       (aUnitSize.width == aFill.width &&
        aUnitSize.height == aFill.height)) {
     nsLayoutUtils::DrawSingleImage(&aRenderingContext, subImage,
                                    graphicsFilter,
-                                   aFill, aDirtyRect);
+                                   aFill, aDirtyRect, imgIContainer::FLAG_NONE);
     return;
   }
 
   // Compute the scale and position of the master copy of the image.
   nsRect tile;
   switch (aHFill) {
   case NS_STYLE_BORDER_IMAGE_STRETCH:
     tile.x = aFill.x;
@@ -2354,17 +2356,18 @@ DrawBorderImageComponent(nsIRenderingCon
     tile.height = aFill.height/ceil(gfxFloat(aFill.height)/aUnitSize.height);
     break;
 
   default:
     NS_NOTREACHED("unrecognized border-image fill style");
   }
 
   nsLayoutUtils::DrawImage(&aRenderingContext, subImage, graphicsFilter,
-                           tile, aFill, tile.TopLeft(), aDirtyRect);
+                           tile, aFill, tile.TopLeft(), aDirtyRect,
+                           imgIContainer::FLAG_NONE);
 }
 
 // Begin table border-collapsing section
 // These functions were written to not disrupt the normal ones and yet satisfy some additional requirements
 // At some point, all functions should be unified to include the additional functionality that these provide
 
 static nscoord
 RoundIntToPixel(nscoord aValue, 
@@ -3081,16 +3084,19 @@ ImageRenderer::ImageRenderer(nsIFrame* a
 ImageRenderer::~ImageRenderer()
 {
 }
 
 PRBool
 ImageRenderer::PrepareImage()
 {
   if (mImage.IsEmpty() || !mImage.IsComplete()) {
+    // Make sure the image is actually decoding
+    mImage.RequestDecode();
+
     // We can not prepare the image for rendering if it is not fully loaded.
     return PR_FALSE;
   }
 
   switch (mType) {
     case eStyleImageType_Image:
     {
       nsCOMPtr<imgIContainer> srcImage;
@@ -3110,18 +3116,20 @@ ImageRenderer::PrepareImage()
           // The cropped image has zero size
           return PR_FALSE;
         }
         if (isEntireImage) {
           // The cropped image is identical to the source image
           mImageContainer.swap(srcImage);
         } else {
           nsCOMPtr<imgIContainer> subImage;
-          nsresult rv = srcImage->ExtractCurrentFrame(actualCropRect,
-                                                      getter_AddRefs(subImage));
+          nsresult rv = srcImage->ExtractFrame(imgIContainer::FRAME_CURRENT,
+                                               actualCropRect,
+                                               imgIContainer::FLAG_NONE,
+                                               getter_AddRefs(subImage));
           if (NS_FAILED(rv)) {
             NS_WARNING("The cropped image contains no pixels to draw; "
                        "maybe the crop rect is outside the image frame rect");
             return PR_FALSE;
           }
           mImageContainer.swap(subImage);
         }
       }
@@ -3185,17 +3193,17 @@ ImageRenderer::Draw(nsPresContext*      
 
   if (aDest.IsEmpty() || aFill.IsEmpty())
     return;
 
   switch (mType) {
     case eStyleImageType_Image:
       nsLayoutUtils::DrawImage(&aRenderingContext, mImageContainer,
           nsLayoutUtils::GetGraphicsFilterForFrame(mForFrame),
-          aDest, aFill, aAnchor, aDirty);
+          aDest, aFill, aAnchor, aDirty, imgIContainer::FLAG_NONE);
       break;
     case eStyleImageType_Gradient:
       nsCSSRendering::PaintGradient(aPresContext, aRenderingContext,
           mGradientData, aDirty, aDest, aFill, aRepeat);
       break;
     case eStyleImageType_Null:
     default:
       break;
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -68,17 +68,18 @@ nsDisplayListBuilder::nsDisplayListBuild
       mSaveVisibleRegionOfMovingContent(nsnull),
       mIgnoreScrollFrame(nsnull),
       mCurrentTableItem(nsnull),
       mBuildCaret(aBuildCaret),
       mEventDelivery(aIsForEvents),
       mIsAtRootOfPseudoStackingContext(PR_FALSE),
       mPaintAllFrames(PR_FALSE),
       mAccurateVisibleRegions(PR_FALSE),
-      mInTransform(PR_FALSE) {
+      mInTransform(PR_FALSE),
+      mSyncDecodeImages(PR_FALSE) {
   PL_InitArenaPool(&mPool, "displayListArena", 1024, sizeof(void*)-1);
 
   nsPresContext* pc = aReferenceFrame->PresContext();
   nsIPresShell *shell = pc->PresShell();
   PRBool suppressed;
   shell->IsPaintingSuppressed(&suppressed);
   mIsBackgroundOnly = suppressed;
   if (pc->IsRenderingOnlySelection()) {
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -286,16 +286,30 @@ public:
   PRBool IsInTransform() { return mInTransform; }
   /**
    * Indicate whether or not we're directly or indirectly under and
    * nsDisplayTransform or SVG foreignObject.
    */
   void SetInTransform(PRBool aInTransform) { mInTransform = aInTransform; }
 
   /**
+   * @return PR_TRUE if images have been set to decode synchronously.
+   */
+  PRBool ShouldSyncDecodeImages() { return mSyncDecodeImages; }
+
+  /**
+   * Indicates whether we should synchronously decode images. If true, we decode
+   * and draw whatever image data has been loaded. If false, we just draw
+   * whatever has already been decoded.
+   */
+  void SetSyncDecodeImages(PRBool aSyncDecodeImages) {
+    mSyncDecodeImages = aSyncDecodeImages;
+  }
+
+  /**
    * Subtracts aRegion from *aVisibleRegion. We avoid letting
    * aVisibleRegion become overcomplex by simplifying it if necessary ---
    * unless mAccurateVisibleRegions is set, in which case we let it
    * get arbitrarily complex.
    */
   void SubtractFromVisibleRegion(nsRegion* aVisibleRegion,
                                  const nsRegion& aRegion);
 
@@ -391,16 +405,17 @@ private:
   PRPackedBool                   mEventDelivery;
   PRPackedBool                   mIsBackgroundOnly;
   PRPackedBool                   mIsAtRootOfPseudoStackingContext;
   PRPackedBool                   mPaintAllFrames;
   PRPackedBool                   mAccurateVisibleRegions;
   // True when we're building a display list that's directly or indirectly
   // under an nsDisplayTransform
   PRPackedBool                   mInTransform;
+  PRPackedBool                   mSyncDecodeImages;
 };
 
 class nsDisplayItem;
 class nsDisplayList;
 /**
  * nsDisplayItems are put in singly-linked lists rooted in an nsDisplayList.
  * nsDisplayItemLink holds the link. The lists are linked from lowest to
  * highest in z-order.
--- a/layout/base/nsImageLoader.cpp
+++ b/layout/base/nsImageLoader.cpp
@@ -58,39 +58,39 @@
 #include "nsStyleContext.h"
 #include "nsGkAtoms.h"
 
 // Paint forcing
 #include "prenv.h"
 
 NS_IMPL_ISUPPORTS2(nsImageLoader, imgIDecoderObserver, imgIContainerObserver)
 
-nsImageLoader::nsImageLoader(nsIFrame *aFrame, PRBool aReflowOnLoad, 
+nsImageLoader::nsImageLoader(nsIFrame *aFrame, PRUint32 aActions,
                              nsImageLoader *aNextLoader)
   : mFrame(aFrame),
-    mReflowOnLoad(aReflowOnLoad),
+    mActions(aActions),
     mNextLoader(aNextLoader)
 {
 }
 
 nsImageLoader::~nsImageLoader()
 {
   mFrame = nsnull;
 
   if (mRequest) {
     mRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
   }
 }
 
 /* static */ already_AddRefed<nsImageLoader>
 nsImageLoader::Create(nsIFrame *aFrame, imgIRequest *aRequest, 
-                      PRBool aReflowOnLoad, nsImageLoader *aNextLoader)
+                      PRUint32 aActions, nsImageLoader *aNextLoader)
 {
   nsRefPtr<nsImageLoader> loader =
-    new nsImageLoader(aFrame, aReflowOnLoad, aNextLoader);
+    new nsImageLoader(aFrame, aActions, aNextLoader);
 
   loader->Load(aRequest);
 
   return loader.forget();
 }
 
 void
 nsImageLoader::Destroy()
@@ -134,90 +134,103 @@ nsImageLoader::Load(imgIRequest *aImage)
   return rv;
 }
 
                     
 
 NS_IMETHODIMP nsImageLoader::OnStartContainer(imgIRequest *aRequest,
                                               imgIContainer *aImage)
 {
-  if (aImage)
-  {
-    /* Get requested animation policy from the pres context:
-     *   normal = 0
-     *   one frame = 1
-     *   one loop = 2
-     */
-    aImage->SetAnimationMode(mFrame->PresContext()->ImageAnimationMode());
-    // Ensure the animation (if any) is started.
-    aImage->StartAnimation();
-  }
+  NS_ABORT_IF_FALSE(aImage, "Who's calling us then?");
+
+  /* Get requested animation policy from the pres context:
+   *   normal = 0
+   *   one frame = 1
+   *   one loop = 2
+   */
+  aImage->SetAnimationMode(mFrame->PresContext()->ImageAnimationMode());
+  // Ensure the animation (if any) is started.
+  aImage->StartAnimation();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP nsImageLoader::OnStopFrame(imgIRequest *aRequest,
                                          PRUint32 aFrame)
 {
   if (!mFrame)
     return NS_ERROR_FAILURE;
-  
-#ifdef NS_DEBUG
-// Make sure the image request status's STATUS_FRAME_COMPLETE flag has been set to ensure
-// the image will be painted when invalidated
-  if (aRequest) {
-   PRUint32 status = imgIRequest::STATUS_ERROR;
-   nsresult rv = aRequest->GetImageStatus(&status);
-   if (NS_SUCCEEDED(rv)) {
-     NS_ASSERTION((status & imgIRequest::STATUS_FRAME_COMPLETE), "imgIRequest::STATUS_FRAME_COMPLETE not set");
-   }
-  }
-#endif
 
   if (!mRequest) {
     // We're in the middle of a paint anyway
     return NS_OK;
   }
-  
-  // Draw the background image
-  RedrawDirtyFrame(nsnull);
+
+  // Take requested actions
+  if (mActions & ACTION_REFLOW_ON_DECODE) {
+    DoReflow();
+  }
+  if (mActions & ACTION_REDRAW_ON_DECODE) {
+    DoRedraw(nsnull);
+  }
   return NS_OK;
 }
 
+NS_IMETHODIMP nsImageLoader::OnStopRequest(imgIRequest *aRequest,
+                                           PRBool aLastPart)
+{
+  if (!mFrame)
+    return NS_ERROR_FAILURE;
+
+  if (!mRequest) {
+    // We're in the middle of a paint anyway
+    return NS_OK;
+  }
+
+  // Take requested actions
+  if (mActions & ACTION_REFLOW_ON_LOAD) {
+    DoReflow();
+  }
+  if (mActions & ACTION_REDRAW_ON_LOAD) {
+    DoRedraw(nsnull);
+  }
+}
+
 NS_IMETHODIMP nsImageLoader::FrameChanged(imgIContainer *aContainer,
                                           nsIntRect *dirtyRect)
 {
   if (!mFrame)
     return NS_ERROR_FAILURE;
 
   if (!mRequest) {
     // We're in the middle of a paint anyway
     return NS_OK;
   }
   
   nsRect r = dirtyRect->ToAppUnits(nsPresContext::AppUnitsPerCSSPixel());
 
-  RedrawDirtyFrame(&r);
+  DoRedraw(&r);
 
   return NS_OK;
 }
 
+void
+nsImageLoader::DoReflow()
+{
+  nsIPresShell *shell = mFrame->PresContext()->GetPresShell();
+#ifdef DEBUG
+  nsresult rv =
+#endif
+    shell->FrameNeedsReflow(mFrame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Could not reflow after loading border-image");
+}
 
 void
-nsImageLoader::RedrawDirtyFrame(const nsRect* aDamageRect)
+nsImageLoader::DoRedraw(const nsRect* aDamageRect)
 {
-  if (mReflowOnLoad) {
-    nsIPresShell *shell = mFrame->PresContext()->GetPresShell();
-#ifdef DEBUG
-    nsresult rv = 
-#endif
-      shell->FrameNeedsReflow(mFrame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
-    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Could not reflow after loading border-image");
-    // The reflow might not do all the invalidation we need, so continue
-    // on with the invalidation codepath.
-  }
   // NOTE: It is not sufficient to invalidate only the size of the image:
   //       the image may be tiled! 
   //       The best option is to call into the frame, however lacking this
   //       we have to at least invalidate the frame's bounds, hence
   //       as long as we have a frame we'll use its size.
   //
 
   // Invalidate the entire frame
--- a/layout/base/nsImageLoader.h
+++ b/layout/base/nsImageLoader.h
@@ -52,45 +52,60 @@ class nsIURI;
  * Image loaders pass notifications for background and border image
  * loading and animation on to the frames.
  *
  * Each frame's image loaders form a linked list.
  */
 class nsImageLoader : public nsStubImageDecoderObserver
 {
 private:
-  nsImageLoader(nsIFrame *aFrame, PRBool aReflowOnLoad,
+  nsImageLoader(nsIFrame *aFrame, PRUint32 aActions,
                 nsImageLoader *aNextLoader);
   virtual ~nsImageLoader();
 
 public:
+  /*
+   * Flags to specify actions that can be taken for the image at various
+   * times. Reflows always occur before redraws. "Decode" refers to one
+   * frame being available, whereas "load" refers to all the data being loaded
+   * from the network.
+   */
+  enum {
+    ACTION_REFLOW_ON_DECODE = 0x01,
+    ACTION_REDRAW_ON_DECODE = 0x02,
+    ACTION_REFLOW_ON_LOAD   = 0x04,
+    ACTION_REDRAW_ON_LOAD   = 0x08
+  };
   static already_AddRefed<nsImageLoader>
     Create(nsIFrame *aFrame, imgIRequest *aRequest,
-           PRBool aReflowOnLoad, nsImageLoader *aNextLoader);
+           PRUint32 aActions, nsImageLoader *aNextLoader);
 
   NS_DECL_ISUPPORTS
 
   // imgIDecoderObserver (override nsStubImageDecoderObserver)
   NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
   NS_IMETHOD OnStopFrame(imgIRequest *aRequest, PRUint32 aFrame);
+  NS_IMETHOD OnStopRequest(imgIRequest *aRequest, PRBool aLastPart);
   // Do not override OnDataAvailable since background images are not
   // displayed incrementally; they are displayed after the entire image
   // has been loaded.
   // Note: Images referenced by the <img> element are displayed
   // incrementally in nsImageFrame.cpp.
 
   // imgIContainerObserver (override nsStubImageDecoderObserver)
   NS_IMETHOD FrameChanged(imgIContainer *aContainer, nsIntRect *dirtyRect);
 
   void Destroy();
 
   imgIRequest *GetRequest() { return mRequest; }
   nsImageLoader *GetNextLoader() { return mNextLoader; }
 
 private:
   nsresult Load(imgIRequest *aImage);
-  void RedrawDirtyFrame(const nsRect* aDamageRect);
+  void DoReflow();
+  /* if aDamageRect is nsnull, the whole frame is redrawn. */
+  void DoRedraw(const nsRect* aDamageRect);
 
   nsIFrame *mFrame;
   nsCOMPtr<imgIRequest> mRequest;
-  PRBool mReflowOnLoad;
+  PRUint32 mActions;
   nsRefPtr<nsImageLoader> mNextLoader;
 };
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1056,16 +1056,19 @@ nsLayoutUtils::PaintFrame(nsIRenderingCo
   nsAutoDisableGetUsedXAssertions disableAssert;
 
   nsDisplayListBuilder builder(aFrame, PR_FALSE, PR_TRUE);
   nsDisplayList list;
   nsRect dirtyRect = aDirtyRegion.GetBounds();
   if (aFlags & PAINT_IN_TRANSFORM) {
     builder.SetInTransform(PR_TRUE);
   }
+  if (aFlags & PAINT_SYNC_DECODE_IMAGES) {
+    builder.SetSyncDecodeImages(PR_TRUE);
+  }
   nsresult rv;
 
   builder.EnterPresShell(aFrame, dirtyRect);
 
   rv = aFrame->BuildDisplayListForStackingContext(&builder, dirtyRect, &list);
 
   if (NS_SUCCEEDED(rv) && aFrame->GetType() == nsGkAtoms::pageContentFrame) {
     // We may need to paint out-of-flow frames whose placeholders are
@@ -2769,17 +2772,18 @@ MapToFloatUserPixels(const gfxSize& aSiz
 static nsresult
 DrawImageInternal(nsIRenderingContext* aRenderingContext,
                   imgIContainer*       aImage,
                   gfxPattern::GraphicsFilter aGraphicsFilter,
                   const nsRect&        aDest,
                   const nsRect&        aFill,
                   const nsPoint&       aAnchor,
                   const nsRect&        aDirty,
-                  const nsIntSize&     aImageSize)
+                  const nsIntSize&     aImageSize,
+                  PRUint32             aImageFlags)
 {
   if (aDest.IsEmpty() || aFill.IsEmpty())
     return NS_OK;
 
   nsCOMPtr<nsIDeviceContext> dc;
   aRenderingContext->GetDeviceContext(*getter_AddRefs(dc));
   gfxFloat appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
   gfxContext *ctx = aRenderingContext->ThebesContext();
@@ -2867,25 +2871,27 @@ DrawImageInternal(nsIRenderingContext* a
   // computed for edge pixels.
   if (didSnap && !transform.HasNonIntegerTranslation()) {
     dirty.RoundOut();
     finalFillRect = fill.Intersect(dirty);
   }
   if (finalFillRect.IsEmpty())
     return NS_OK;
 
-  aImage->Draw(ctx, aGraphicsFilter, transform, finalFillRect, intSubimage);
+  aImage->Draw(ctx, aGraphicsFilter, transform, finalFillRect, intSubimage,
+               aImageFlags);
   return NS_OK;
 }
 
 /* static */ nsresult
 nsLayoutUtils::DrawSingleUnscaledImage(nsIRenderingContext* aRenderingContext,
                                        imgIContainer*       aImage,
                                        const nsPoint&       aDest,
                                        const nsRect&        aDirty,
+                                       PRUint32             aImageFlags,
                                        const nsRect*        aSourceArea)
 {
   nsIntSize imageSize;
   aImage->GetWidth(&imageSize.width);
   aImage->GetHeight(&imageSize.height);
   NS_ENSURE_TRUE(imageSize.width > 0 && imageSize.height > 0, NS_ERROR_FAILURE);
 
   nscoord appUnitsPerCSSPixel = nsIDeviceContext::AppUnitsPerCSSPixel();
@@ -2901,25 +2907,26 @@ nsLayoutUtils::DrawSingleUnscaledImage(n
 
   nsRect dest(aDest - source.TopLeft(), size);
   nsRect fill(aDest, source.Size());
   // Ensure that only a single image tile is drawn. If aSourceArea extends
   // outside the image bounds, we want to honor the aSourceArea-to-aDest
   // translation but we don't want to actually tile the image.
   fill.IntersectRect(fill, dest);
   return DrawImageInternal(aRenderingContext, aImage, gfxPattern::FILTER_NEAREST,
-                           dest, fill, aDest, aDirty, imageSize);
+                           dest, fill, aDest, aDirty, imageSize, aImageFlags);
 }
  
 /* static */ nsresult
 nsLayoutUtils::DrawSingleImage(nsIRenderingContext* aRenderingContext,
                                imgIContainer*       aImage,
                                gfxPattern::GraphicsFilter aGraphicsFilter,
                                const nsRect&        aDest,
                                const nsRect&        aDirty,
+                               PRUint32             aImageFlags,
                                const nsRect*        aSourceArea)
 {
   nsIntSize imageSize;
   aImage->GetWidth(&imageSize.width);
   aImage->GetHeight(&imageSize.height);
   NS_ENSURE_TRUE(imageSize.width > 0 && imageSize.height > 0, NS_ERROR_FAILURE);
 
   nsRect source;
@@ -2934,36 +2941,37 @@ nsLayoutUtils::DrawSingleImage(nsIRender
   nsRect dest = nsLayoutUtils::GetWholeImageDestination(imageSize, source,
                                                         aDest);
   // Ensure that only a single image tile is drawn. If aSourceArea extends
   // outside the image bounds, we want to honor the aSourceArea-to-aDest
   // transform but we don't want to actually tile the image.
   nsRect fill;
   fill.IntersectRect(aDest, dest);
   return DrawImageInternal(aRenderingContext, aImage, aGraphicsFilter, dest, fill,
-                           fill.TopLeft(), aDirty, imageSize);
+                           fill.TopLeft(), aDirty, imageSize, aImageFlags);
 }
 
 /* static */ nsresult
 nsLayoutUtils::DrawImage(nsIRenderingContext* aRenderingContext,
                          imgIContainer*       aImage,
                          gfxPattern::GraphicsFilter aGraphicsFilter,
                          const nsRect&        aDest,
                          const nsRect&        aFill,
                          const nsPoint&       aAnchor,
-                         const nsRect&        aDirty)
+                         const nsRect&        aDirty,
+                         PRUint32             aImageFlags)
 {
   nsIntSize imageSize;
   aImage->GetWidth(&imageSize.width);
   aImage->GetHeight(&imageSize.height);
   NS_ENSURE_TRUE(imageSize.width > 0 && imageSize.height > 0, NS_ERROR_FAILURE);
 
   return DrawImageInternal(aRenderingContext, aImage, aGraphicsFilter,
                            aDest, aFill, aAnchor, aDirty,
-                           imageSize);
+                           imageSize, aImageFlags);
 }
 
 /* static */ nsRect
 nsLayoutUtils::GetWholeImageDestination(const nsIntSize& aWholeImageSize,
                                         const nsRect& aImageSourceArea,
                                         const nsRect& aDestArea)
 {
   double scaleX = double(aDestArea.width)/aImageSourceArea.width;
@@ -3359,18 +3367,23 @@ nsLayoutUtils::SurfaceFromElement(nsIDOM
       return result;
   }
 
   nsCOMPtr<imgIContainer> imgContainer;
   rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
   if (NS_FAILED(rv) || !imgContainer)
     return result;
 
+  PRUint32 whichFrame = (aSurfaceFlags & SFE_WANT_FIRST_FRAME)
+                        ? (PRUint32) imgIContainer::FRAME_FIRST
+                        : (PRUint32) imgIContainer::FRAME_CURRENT;
   nsRefPtr<gfxASurface> framesurf;
-  rv = imgContainer->GetCurrentFrame(getter_AddRefs(framesurf));
+  rv = imgContainer->GetFrame(whichFrame,
+                              imgIContainer::FLAG_SYNC_DECODE,
+                              getter_AddRefs(framesurf));
   if (NS_FAILED(rv))
     return result;
 
   PRInt32 imgWidth, imgHeight;
   rv = imgContainer->GetWidth(&imgWidth);
   rv |= imgContainer->GetHeight(&imgHeight);
   if (NS_FAILED(rv))
     return result;
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -57,16 +57,17 @@ class nsIFontMetrics;
 #include "nsAutoPtr.h"
 #include "nsStyleSet.h"
 #include "nsIView.h"
 #include "nsIFrame.h"
 #include "nsThreadUtils.h"
 #include "nsIPresShell.h"
 #include "nsIPrincipal.h"
 #include "gfxPattern.h"
+#include "imgIContainer.h"
 
 class nsBlockFrame;
 class nsTextFragment;
 
 /**
  * nsLayoutUtils is a namespace class used for various helper
  * functions that are useful in multiple places in layout.  The goal
  * is not to define multiple copies of the same static helper.
@@ -471,29 +472,34 @@ public:
    *
    * @param aRect The graphics rect to round outward.
    * @param aFactor The number of app units per graphics unit.
    * @return The smallest rectangle in app space that contains aRect.
    */
   static nsRect RoundGfxRectToAppRect(const gfxRect &aRect, float aFactor);
 
 
-  enum { PAINT_IN_TRANSFORM = 0x01 };
+  enum {
+    PAINT_IN_TRANSFORM = 0x01,
+    PAINT_SYNC_DECODE_IMAGES = 0x02
+  };
+
   /**
    * Given aFrame, the root frame of a stacking context, paint it and its
    * descendants to aRenderingContext. 
    * @param aRenderingContext a rendering context translated so that (0,0)
    * is the origin of aFrame; for best results, (0,0) should transform
    * to pixel-aligned coordinates
    * @param aDirtyRegion the region that must be painted, in the coordinates
    * of aFrame
    * @param aBackstop paint the dirty area with this color before drawing
    * the actual content; pass NS_RGBA(0,0,0,0) to draw no background
    * @param aFlags if PAINT_IN_TRANSFORM is set, then we assume
-   * this is inside a transform or SVG foreignObject.
+   * this is inside a transform or SVG foreignObject. If
+   * PAINT_SYNC_DECODE_IMAGES is set, we force synchronous decode on all images.
    */
   static nsresult PaintFrame(nsIRenderingContext* aRenderingContext, nsIFrame* aFrame,
                              const nsRegion& aDirtyRegion, nscolor aBackstop,
                              PRUint32 aFlags = 0);
 
   /**
    * @param aRootFrame the root frame of the tree to be displayed
    * @param aMovingFrame a frame that has moved
@@ -875,64 +881,70 @@ public:
    *                            app units.
    *   @param aImage            The image.
    *   @param aDest             Where one copy of the image should mapped to.
    *   @param aFill             The area to be filled with copies of the
    *                            image.
    *   @param aAnchor           A point in aFill which we will ensure is
    *                            pixel-aligned in the output.
    *   @param aDirty            Pixels outside this area may be skipped.
+   *   @param aImageFlags       Image flags of the imgIContainer::FLAG_* variety
    */
   static nsresult DrawImage(nsIRenderingContext* aRenderingContext,
                             imgIContainer*       aImage,
                             gfxPattern::GraphicsFilter aGraphicsFilter,
                             const nsRect&        aDest,
                             const nsRect&        aFill,
                             const nsPoint&       aAnchor,
-                            const nsRect&        aDirty);
+                            const nsRect&        aDirty,
+                            PRUint32             aImageFlags);
 
   /**
    * Draw a whole image without scaling or tiling.
    *
    *   @param aRenderingContext Where to draw the image, set up with an
    *                            appropriate scale and transform for drawing in
    *                            app units.
    *   @param aImage            The image.
    *   @param aDest             The top-left where the image should be drawn
    *   @param aDirty            Pixels outside this area may be skipped.
+   *   @param aImageFlags       Image flags of the imgIContainer::FLAG_* variety
    *   @param aSourceArea       If non-null, this area is extracted from
    *                            the image and drawn at aDest. It's
    *                            in appunits. For best results it should
    *                            be aligned with image pixels.
    */
   static nsresult DrawSingleUnscaledImage(nsIRenderingContext* aRenderingContext,
                                           imgIContainer*       aImage,
                                           const nsPoint&       aDest,
                                           const nsRect&        aDirty,
+                                          PRUint32             aImageFlags,
                                           const nsRect*        aSourceArea = nsnull);
 
   /**
    * Draw a whole image without tiling.
    *
    *   @param aRenderingContext Where to draw the image, set up with an
    *                            appropriate scale and transform for drawing in
    *                            app units.
    *   @param aImage            The image.
    *   @param aDest             The area that the image should fill
    *   @param aDirty            Pixels outside this area may be skipped.
    *   @param aSourceArea       If non-null, this area is extracted from
    *                            the image and drawn in aDest. It's
    *                            in appunits. For best results it should
    *                            be aligned with image pixels.
+   *   @param aImageFlags       Image flags of the imgIContainer::FLAG_* variety
    */
   static nsresult DrawSingleImage(nsIRenderingContext* aRenderingContext,
                                   imgIContainer*       aImage,
                                   gfxPattern::GraphicsFilter aGraphicsFilter,
                                   const nsRect&        aDest,
                                   const nsRect&        aDirty,
+                                  PRUint32             aImageFlags,
                                   const nsRect*        aSourceArea = nsnull);
 
   /**
    * Given a source area of an image (in appunits) and a destination area
    * that we want to map that source area too, computes the area that
    * would be covered by the whole image. This is useful for passing to
    * the aDest parameter of DrawImage, when we want to draw a subimage
    * of an overall image.
@@ -1064,17 +1076,20 @@ public:
    * The above results are modified by the below flags (copying,
    * forcing image surface, etc.).
    */
 
   enum {
     /* Always create a new surface for the result */
     SFE_WANT_NEW_SURFACE   = 1 << 0,
     /* When creating a new surface, create an image surface */
-    SFE_WANT_IMAGE_SURFACE = 1 << 1
+    SFE_WANT_IMAGE_SURFACE = 1 << 1,
+    /* Whether to extract the first frame (as opposed to the
+       current frame) in the case that the element is an image. */
+    SFE_WANT_FIRST_FRAME = 1 << 2
   };
 
   struct SurfaceFromElementResult {
     /* mSurface will contain the resulting surface, or will be NULL on error */
     nsRefPtr<gfxASurface> mSurface;
     /* The size of the surface */
     gfxIntSize mSize;
     /* The principal associated with the element whose surface was returned */
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -1271,30 +1271,34 @@ nsPresContext::SetImageLoaders(nsIFrame*
 
 void
 nsPresContext::SetupBackgroundImageLoaders(nsIFrame* aFrame,
                                      const nsStyleBackground* aStyleBackground)
 {
   nsRefPtr<nsImageLoader> loaders;
   NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, aStyleBackground) {
     if (aStyleBackground->mLayers[i].mImage.GetType() == eStyleImageType_Image) {
+      PRUint32 actions = nsImageLoader::ACTION_REDRAW_ON_DECODE;
       imgIRequest *image = aStyleBackground->mLayers[i].mImage.GetImageData();
-      loaders = nsImageLoader::Create(aFrame, image, PR_FALSE, loaders);
+      loaders = nsImageLoader::Create(aFrame, image, actions, loaders);
     }
   }
   SetImageLoaders(aFrame, BACKGROUND_IMAGE, loaders);
 }
 
 void
 nsPresContext::SetupBorderImageLoaders(nsIFrame* aFrame,
                                        const nsStyleBorder* aStyleBorder)
 {
+  PRUint32 actions = nsImageLoader::ACTION_REDRAW_ON_LOAD;
+  if (aStyleBorder->ImageBorderDiffers())
+    actions |= nsImageLoader::ACTION_REFLOW_ON_LOAD;
   nsRefPtr<nsImageLoader> loader =
     nsImageLoader::Create(aFrame, aStyleBorder->GetBorderImage(),
-                          aStyleBorder->ImageBorderDiffers(), nsnull);
+                          actions, nsnull);
   SetImageLoaders(aFrame, BORDER_IMAGE, loader);
 }
 
 void
 nsPresContext::StopImagesFor(nsIFrame* aTargetFrame)
 {
   for (PRUint32 i = 0; i < IMAGE_LOAD_TYPE_COUNT; ++i)
     SetImageLoaders(aTargetFrame, ImageLoadType(i), nsnull);
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5177,16 +5177,17 @@ PresShell::RenderDocument(const nsRect& 
     nsIFrame* rootScrollFrame = GetRootScrollFrame();
     if ((aFlags & RENDER_IGNORE_VIEWPORT_SCROLLING) && rootScrollFrame) {
       nsPoint pos = GetRootScrollFrameAsScrollable()->GetScrollPosition();
       rect.MoveBy(-pos);
       builder.SetIgnoreScrollFrame(rootScrollFrame);
     }
 
     builder.SetBackgroundOnly(PR_FALSE);
+    builder.SetSyncDecodeImages(PR_TRUE);
     builder.EnterPresShell(rootFrame, rect);
 
     // Add the canvas background color.
     nsresult rv =
       rootFrame->PresContext()->PresShell()->AddCanvasBackgroundColorItem(
         builder, list, rootFrame);
 
     if (NS_SUCCEEDED(rv)) {
--- a/layout/base/tests/test_bug445810.html
+++ b/layout/base/tests/test_bug445810.html
@@ -2,26 +2,34 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=445810
 -->
 <head>
   <title>Test for Bug 445810</title>
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/modules/libpr0n/test/mochitest/imgutils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=445810">Mozilla Bug 445810</a>
 <div><p id="display"></p></div>
+<div style="display: none;"><img id="currimg"></div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 445810 **/
 
+// Once the border image is loaded, it isn't necessarily decoded. We need to
+// force a decode, but only have a good way of doing that for image elements,
+// not border images. However, we can take advantage of the image cache (which
+// shares images of the same url) and assign the same url to both.
+var currImageElem = document.getElementById("currimg");
+
 function new_image_url()
 {
   var width = 10;
   var height = 10;
 
   var canvas = document.createElement("canvas");
   canvas.setAttribute("width", width);
   canvas.setAttribute("height", height);
@@ -55,51 +63,75 @@ p.style.height = "5px";
 is(divcs.width, "5px", "correct width without a border");
 is(divcs.height, "5px", "correct height without a border");
 p.style.border = "3px solid";
 is(divcs.width, "11px", // 3 (border-left) + 5 (width) + 3 (border-right)
    "correct width without a border image");
 is(divcs.height, "11px", // 3 (border-top) + 5 (height) + 3 (border-bottom)
    "correct height without a border image");
 
-p.style.MozBorderImage = "url( " + new_image_url() + ") 2 2 2 2 / 7px 2px";
+currImageElem.src = new_image_url();
+p.style.MozBorderImage = "url( " + currImageElem.src + ") 2 2 2 2 / 7px 2px";
 is(divcs.width, "11px", "border image not loaded yet");
 is(divcs.height, "11px", "border image not loaded yet");
+currImageElem.onload = step2;
 
-setTimeout(step2, 0);
 function step2() {
+  // We got here through onload
+  ok(isImageLoaded("currimg"), "image loaded");
+
+  // Force a decode
+  forceDecode("currimg");
+  ok(isFrameDecoded("currimg"), "frame decoded");
+
   is(divcs.width, "9px", "border image loading caused reflow");
   is(divcs.height, "19px", "border image loading caused reflow");
 
   p.style.border = "";
   is(divcs.width, "9px", "border image still shows with border-style:none");
   is(divcs.height, "19px", "border image still shows with border-style:none");
 
   p.style.MozBorderImage = "";
   is(divcs.width, "5px", "correct width without a border");
   is(divcs.height, "5px", "correct height without a border");
 
-  p.style.MozBorderImage = "url( " + new_image_url() + ") 2 2 2 2 / 7px 2px";
+  currImageElem.src = new_image_url();
+  p.style.MozBorderImage = "url( " + currImageElem.src + ") 2 2 2 2 / 7px 2px";
   is(divcs.width, "5px", "border image not loaded yet");
   is(divcs.height, "5px", "border image not loaded yet");
-  setTimeout(step3, 0);
+  currImageElem.onload = step3;
 }
 
 function step3() {
+  // We got here through onload
+  ok(isImageLoaded("currimg"), "image loaded");
+
+  // Force a decode
+  forceDecode("currimg");
+  ok(isFrameDecoded("currimg"), "frame decoded");
+
   is(divcs.width, "9px", "border image loading caused reflow");
   is(divcs.height, "19px", "border image loading caused reflow");
 
-  p.style.MozBorderImage = "url( " + new_image_url() + ") 2 2 2 2";
+  currImageElem.src = new_image_url();
+  p.style.MozBorderImage = "url( " + currImageElem.src + ") 2 2 2 2";
   p.style.border = "3px none";
   is(divcs.width, "5px", "border image not loaded yet");
   is(divcs.height, "5px", "border image not loaded yet");
-  setTimeout(step4, 0);
+  currImageElem.onload = step4;
 }
 
 function step4() {
+  // We got here through onload
+  ok(isImageLoaded("currimg"), "image loaded");
+
+  // Force a decode
+  forceDecode("currimg");
+  ok(isFrameDecoded("currimg"), "frame decoded");
+
   is(divcs.width, "11px", "border image loading caused reflow");
   is(divcs.height, "11px", "border image loading caused reflow");
 
   SimpleTest.finish();
 }
 
 </script>
 </pre>
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -236,17 +236,17 @@ nsBulletFrame::PaintBullet(nsIRenderingC
       nsCOMPtr<imgIContainer> imageCon;
       mImageRequest->GetImage(getter_AddRefs(imageCon));
       if (imageCon) {
         nsRect dest(mPadding.left, mPadding.top,
                     mRect.width - (mPadding.left + mPadding.right),
                     mRect.height - (mPadding.top + mPadding.bottom));
         nsLayoutUtils::DrawSingleImage(&aRenderingContext,
              imageCon, nsLayoutUtils::GetGraphicsFilterForFrame(this),
-             dest + aPt, aDirtyRect);
+             dest + aPt, aDirtyRect, imgIContainer::FLAG_NONE);
         return;
       }
     }
   }
 
   const nsStyleColor* myColor = GetStyleColor();
 
   nsCOMPtr<nsIFontMetrics> fm;
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -6090,17 +6090,17 @@ void nsFrame::FillCursorInformationFromS
   aCursor.mHaveHotspot = PR_FALSE;
   aCursor.mHotspotX = aCursor.mHotspotY = 0.0f;
 
   for (nsCursorImage *item = ui->mCursorArray,
                  *item_end = ui->mCursorArray + ui->mCursorArrayLength;
        item < item_end; ++item) {
     PRUint32 status;
     nsresult rv = item->mImage->GetImageStatus(&status);
-    if (NS_SUCCEEDED(rv) && (status & imgIRequest::STATUS_FRAME_COMPLETE)) {
+    if (NS_SUCCEEDED(rv) && (status & imgIRequest::STATUS_LOAD_COMPLETE)) {
       // This is the one we want
       item->mImage->GetImage(getter_AddRefs(aCursor.mContainer));
       aCursor.mHaveHotspot = item->mHaveHotspot;
       aCursor.mHotspotX = item->mHotspotX;
       aCursor.mHotspotY = item->mHotspotY;
       break;
     }
   }
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1045,17 +1045,18 @@ nsImageFrame::DisplayAltFeedback(nsIRend
     if (imageStatus & imgIRequest::STATUS_FRAME_COMPLETE) {
       nsCOMPtr<imgIContainer> imgCon;
       aRequest->GetImage(getter_AddRefs(imgCon));
       NS_ABORT_IF_FALSE(imgCon, "Frame Complete, but no image container?");
       nsRect dest((vis->mDirection == NS_STYLE_DIRECTION_RTL) ?
                   inner.XMost() - size : inner.x,
                   inner.y, size, size);
       nsLayoutUtils::DrawSingleImage(&aRenderingContext, imgCon,
-        nsLayoutUtils::GetGraphicsFilterForFrame(this), dest, aDirtyRect);
+        nsLayoutUtils::GetGraphicsFilterForFrame(this), dest, aDirtyRect,
+        imgIContainer::FLAG_NONE);
       iconUsed = PR_TRUE;
     }
 
     // if we could not draw the icon, flag that we're waiting for it and
     // just draw some graffiti in the mean time
     if (!iconUsed) {
       nscolor oldColor;
       nscoord iconXPos = (vis->mDirection ==   NS_STYLE_DIRECTION_RTL) ?
@@ -1139,32 +1140,37 @@ public:
 private:
   nsCOMPtr<imgIContainer> mImage;
 };
 
 void
 nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder,
      nsIRenderingContext* aCtx, const nsRect& aDirtyRect) {
   static_cast<nsImageFrame*>(mFrame)->
-    PaintImage(*aCtx, aBuilder->ToReferenceFrame(mFrame), aDirtyRect, mImage);
+    PaintImage(*aCtx, aBuilder->ToReferenceFrame(mFrame), aDirtyRect, mImage,
+               aBuilder->ShouldSyncDecodeImages()
+                 ? (PRUint32) imgIContainer::FLAG_SYNC_DECODE
+                 : (PRUint32) imgIContainer::FLAG_NONE);
 }
 
 void
 nsImageFrame::PaintImage(nsIRenderingContext& aRenderingContext, nsPoint aPt,
-                         const nsRect& aDirtyRect, imgIContainer* aImage)
+                         const nsRect& aDirtyRect, imgIContainer* aImage,
+                         PRUint32 aFlags)
 {
   // Render the image into our content area (the area inside
   // the borders and padding)
   NS_ASSERTION(GetInnerArea().width == mComputedSize.width, "bad width");
   nsRect inner = GetInnerArea() + aPt;
   nsRect dest(inner.TopLeft(), mComputedSize);
   dest.y -= GetContinuationOffset();
 
   nsLayoutUtils::DrawSingleImage(&aRenderingContext, aImage,
-    nsLayoutUtils::GetGraphicsFilterForFrame(this), dest, aDirtyRect);
+    nsLayoutUtils::GetGraphicsFilterForFrame(this), dest, aDirtyRect,
+    aFlags);
 
   nsPresContext* presContext = PresContext();
   nsImageMap* map = GetImageMap(presContext);
   if (nsnull != map) {
     aRenderingContext.PushState();
     aRenderingContext.SetColor(NS_RGB(0, 0, 0));
     aRenderingContext.SetLineStyle(nsLineStyle_kDotted);
     aRenderingContext.Translate(inner.x, inner.y);
@@ -1844,16 +1850,22 @@ nsImageFrame::IconLoad::OnStopRequest(im
     frame = iter.GetNext();
     frame->Invalidate(frame->GetRect());
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsImageFrame::IconLoad::OnDiscard(imgIRequest *aRequest)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsImageFrame::IconLoad::FrameChanged(imgIContainer *aContainer,
                                      nsIntRect * aDirtyRect)
 {
   nsTObserverArray<nsImageFrame*>::ForwardIterator iter(mIconObservers);
   nsImageFrame *frame;
   while (iter.HasMore()) {
     frame = iter.GetNext();
     frame->Invalidate(frame->GetRect());
--- a/layout/generic/nsImageFrame.h
+++ b/layout/generic/nsImageFrame.h
@@ -210,17 +210,18 @@ protected:
                         nsIRenderingContext& aContext);
 
   void DisplayAltText(nsPresContext*      aPresContext,
                       nsIRenderingContext& aRenderingContext,
                       const nsString&      aAltText,
                       const nsRect&        aRect);
 
   void PaintImage(nsIRenderingContext& aRenderingContext, nsPoint aPt,
-                  const nsRect& aDirtyRect, imgIContainer* aImage);
+                  const nsRect& aDirtyRect, imgIContainer* aImage,
+                  PRUint32 aFlags);
                   
 protected:
   friend class nsImageListener;
   nsresult OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
   nsresult OnDataAvailable(imgIRequest *aRequest, PRBool aCurrentFrame,
                            const nsIntRect *rect);
   nsresult OnStopDecode(imgIRequest *aRequest,
                         nsresult aStatus,
--- a/layout/generic/nsPageFrame.cpp
+++ b/layout/generic/nsPageFrame.cpp
@@ -576,17 +576,18 @@ nsPageFrame::PaintPageContent(nsIRenderi
   }
   aRenderingContext.SetClipRect(clipRect, nsClipCombine_kIntersect);
 
   nsRect backgroundRect = nsRect(nsPoint(0, 0), pageContentFrame->GetSize());
   nsCSSRendering::PaintBackground(PresContext(), aRenderingContext, this,
                                   rect, backgroundRect, 0);
 
   nsLayoutUtils::PaintFrame(&aRenderingContext, pageContentFrame,
-                            nsRegion(rect), NS_RGBA(0,0,0,0));
+                            nsRegion(rect), NS_RGBA(0,0,0,0),
+                            nsLayoutUtils::PAINT_SYNC_DECODE_IMAGES);
 
   aRenderingContext.PopState();
 }
 
 void
 nsPageFrame::SetSharedPageData(nsSharedPageData* aPD) 
 { 
   mPD = aPD;
--- a/layout/generic/nsSimplePageSequence.cpp
+++ b/layout/generic/nsSimplePageSequence.cpp
@@ -634,17 +634,18 @@ nsSimplePageSequenceFrame::PrintNextPage
         renderingContext->ThebesContext()->Rotate(M_PI/2);
       }
 #endif // XP_UNIX && !XP_MACOSX
 
       nsRect drawingRect(nsPoint(0, 0),
                          mCurrentPageFrame->GetSize());
       nsRegion drawingRegion(drawingRect);
       nsLayoutUtils::PaintFrame(renderingContext, mCurrentPageFrame,
-                                drawingRegion, NS_RGBA(0,0,0,0));
+                                drawingRegion, NS_RGBA(0,0,0,0),
+                                nsLayoutUtils::PAINT_SYNC_DECODE_IMAGES);
 
       if (mSelectionHeight >= 0 && selectionY < mSelectionHeight) {
         selectionY += height;
         printedPageNum++;
         pf->SetPageNumInfo(printedPageNum, mTotalPages);
         conFrame->SetPosition(conFrame->GetPosition() + nsPoint(0, -height));
         nsContainerFrame::PositionChildViews(conFrame);
 
@@ -702,17 +703,18 @@ nsSimplePageSequenceFrame::PaintPageSequ
   // Loop over the pages and paint them.
   nsIFrame* child = GetFirstChild(nsnull);
   while (child) {
     nsPoint pt = child->GetPosition();
     // The rendering context has to be translated before each call to PaintFrame
     aRenderingContext.PushState();
     aRenderingContext.Translate(pt.x, pt.y);
     nsLayoutUtils::PaintFrame(&aRenderingContext, child,
-                              nsRegion(rect - pt), NS_RGBA(0,0,0,0));
+                              nsRegion(rect - pt), NS_RGBA(0,0,0,0),
+                              nsLayoutUtils::PAINT_SYNC_DECODE_IMAGES);
     aRenderingContext.PopState();
     child = child->GetNextSibling();
   }
 
   aRenderingContext.PopState();
 }
 
 NS_IMETHODIMP
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1467,16 +1467,24 @@ nsStyleImage::ComputeActualCropRect(nsIn
   nsIntRect imageRect(nsIntPoint(0, 0), imageSize);
   aActualCropRect.IntersectRect(imageRect, cropRect);
 
   if (aIsEntireImage)
     *aIsEntireImage = (aActualCropRect == imageRect);
   return PR_TRUE;
 }
 
+nsresult
+nsStyleImage::RequestDecode()
+{
+  if ((mType == eStyleImageType_Image) && mImage)
+    return mImage->RequestDecode();
+  return NS_OK;
+}
+
 PRBool
 nsStyleImage::IsOpaque() const
 {
   if (!IsComplete())
     return PR_FALSE;
 
   if (mType == eStyleImageType_Gradient) {
     // We could check if every stop color of the gradient is non-transparent.
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -246,16 +246,20 @@ struct nsStyleImage {
    * @param aIsEntireImage PR_TRUE iff |aActualCropRect| is identical to the
    * source image bounds.
    * @return PR_TRUE iff |aActualCropRect| holds a meaningful value.
    */
   PRBool ComputeActualCropRect(nsIntRect& aActualCropRect,
                                PRBool* aIsEntireImage = nsnull) const;
 
   /**
+   * Requests a decode on the image.
+   */
+  nsresult RequestDecode();
+  /**
    * @return PR_TRUE if the item is definitely opaque --- i.e., paints every
    * pixel within its bounds opaquely, and the bounds contains at least a pixel.
    */
   PRBool IsOpaque() const;
   /**
    * @return PR_TRUE if this image is fully loaded, and its size is calculated;
    * always returns PR_TRUE if |mType| is |eStyleImageType_Gradient|.
    */
@@ -780,16 +784,17 @@ struct nsStyleBorder {
     mBorderStyle[aSide] &= ~BORDER_STYLE_MASK; 
     mBorderStyle[aSide] |= (aStyle & BORDER_STYLE_MASK);
     mComputedBorder.side(aSide) =
       (HasVisibleStyle(aSide) ? mBorder.side(aSide) : 0);
   }
 
   // Defined in nsStyleStructInlines.h
   inline PRBool IsBorderImageLoaded() const;
+  inline nsresult RequestDecode();
 
   void GetBorderColor(PRUint8 aSide, nscolor& aColor,
                       PRBool& aForeground) const
   {
     aForeground = PR_FALSE;
     NS_ASSERTION(aSide <= NS_SIDE_LEFT, "bad side"); 
     if ((mBorderStyle[aSide] & BORDER_COLOR_SPECIAL) == 0)
       aColor = mBorderColor[aSide]; 
--- a/layout/style/nsStyleStructInlines.h
+++ b/layout/style/nsStyleStructInlines.h
@@ -46,25 +46,34 @@
 
 #include "nsStyleStruct.h"
 #include "imgIRequest.h"
 
 inline void
 nsStyleBorder::SetBorderImage(imgIRequest* aImage)
 {
   mBorderImage = aImage;
+
+  /*
+   * Request a decode to jump start decoding, and lock it to make sure it
+   * stays decoded.
+   */
+  if (mBorderImage) {
+    mBorderImage->RequestDecode();
+    mBorderImage->LockImage();
+  }
 }
 
 inline imgIRequest*
 nsStyleBorder::GetBorderImage() const
 {
   return mBorderImage;
 }
 
 inline PRBool nsStyleBorder::IsBorderImageLoaded() const
 {
   PRUint32 status;
   return mBorderImage &&
          NS_SUCCEEDED(mBorderImage->GetImageStatus(&status)) &&
-         (status & imgIRequest::STATUS_FRAME_COMPLETE);
+         (status & imgIRequest::STATUS_LOAD_COMPLETE);
 }
 
 #endif /* !defined(nsStyleStructInlines_h_) */
--- a/layout/svg/base/src/nsSVGImageFrame.cpp
+++ b/layout/svg/base/src/nsSVGImageFrame.cpp
@@ -232,19 +232,25 @@ nsSVGImageFrame::PaintSVG(nsSVGRenderSta
     if (imageLoader)
       imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
                               getter_AddRefs(currentRequest));
 
     if (currentRequest)
       currentRequest->GetImage(getter_AddRefs(mImageContainer));
   }
 
+  // XXXbholley - I don't think huge images in SVGs are common enough to
+  // warrant worrying about the responsiveness impact of doing synchronous
+  // decodes. The extra code complexity of determinining when we want to
+  // force sync probably just isn't worth it, so always pass FLAG_SYNC_DECODE
   nsRefPtr<gfxASurface> currentFrame;
   if (mImageContainer)
-    mImageContainer->GetCurrentFrame(getter_AddRefs(currentFrame));
+    mImageContainer->GetFrame(imgIContainer::FRAME_CURRENT,
+                              imgIContainer::FLAG_SYNC_DECODE,
+                              getter_AddRefs(currentFrame));
 
   // We need to wrap the surface in a pattern to have somewhere to set the
   // graphics filter.
   nsRefPtr<gfxPattern> thebesPattern;
   if (currentFrame)
     thebesPattern = new gfxPattern(currentFrame);
 
   if (thebesPattern) {
--- a/layout/xul/base/src/nsImageBoxFrame.cpp
+++ b/layout/xul/base/src/nsImageBoxFrame.cpp
@@ -328,17 +328,20 @@ public:
      const nsRect& aDirtyRect);
   NS_DISPLAY_DECL_NAME("XULImage")
 };
 
 void nsDisplayXULImage::Paint(nsDisplayListBuilder* aBuilder,
      nsIRenderingContext* aCtx, const nsRect& aDirtyRect)
 {
   static_cast<nsImageBoxFrame*>(mFrame)->
-    PaintImage(*aCtx, aDirtyRect, aBuilder->ToReferenceFrame(mFrame));
+    PaintImage(*aCtx, aDirtyRect, aBuilder->ToReferenceFrame(mFrame),
+               aBuilder->ShouldSyncDecodeImages()
+                 ? (PRUint32) imgIContainer::FLAG_SYNC_DECODE
+                 : (PRUint32) imgIContainer::FLAG_NONE);
 }
 
 NS_IMETHODIMP
 nsImageBoxFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                   const nsRect&           aDirtyRect,
                                   const nsDisplayListSet& aLists)
 {       
   nsresult rv = nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
@@ -354,17 +357,18 @@ nsImageBoxFrame::BuildDisplayList(nsDisp
   if (!IsVisibleForPainting(aBuilder))
     return NS_OK;
 
   return aLists.Content()->AppendNewToTop(new (aBuilder) nsDisplayXULImage(this));
 }
 
 void
 nsImageBoxFrame::PaintImage(nsIRenderingContext& aRenderingContext,
-                            const nsRect& aDirtyRect, nsPoint aPt)
+                            const nsRect& aDirtyRect, nsPoint aPt,
+                            PRUint32 aFlags)
 {
   nsRect rect;
   GetClientRect(rect);
 
   rect += aPt;
 
   if (!mImageRequest)
     return;
@@ -376,17 +380,17 @@ nsImageBoxFrame::PaintImage(nsIRendering
 
   nsCOMPtr<imgIContainer> imgCon;
   mImageRequest->GetImage(getter_AddRefs(imgCon));
 
   if (imgCon) {
     PRBool hasSubRect = !mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0);
     nsLayoutUtils::DrawSingleImage(&aRenderingContext, imgCon,
         nsLayoutUtils::GetGraphicsFilterForFrame(this),
-        rect, dirty, hasSubRect ? &mSubRect : nsnull);
+        rect, dirty, aFlags, hasSubRect ? &mSubRect : nsnull);
   }
 }
 
 
 //
 // DidSetStyleContext
 //
 // When the style context changes, make sure that all of our image is up to date.
--- a/layout/xul/base/src/nsImageBoxFrame.h
+++ b/layout/xul/base/src/nsImageBoxFrame.h
@@ -122,17 +122,17 @@ public:
                           nsresult status,
                           const PRUnichar *statusArg);
   NS_IMETHOD FrameChanged(imgIContainer *container, nsIntRect *dirtyRect);
 
   virtual ~nsImageBoxFrame();
 
   void  PaintImage(nsIRenderingContext& aRenderingContext,
                    const nsRect& aDirtyRect,
-                   nsPoint aPt);
+                   nsPoint aPt, PRUint32 aFlags);
 
 protected:
   nsImageBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext);
 
   virtual void GetImageSize();
 
 private:
 
--- a/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
+++ b/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
@@ -3394,17 +3394,17 @@ nsTreeBodyFrame::PaintTwisty(PRInt32    
 
         // Center the image. XXX Obey vertical-align style prop?
         if (imageSize.height < twistyRect.height) {
           pt.y += (twistyRect.height - imageSize.height)/2;
         }
           
         // Paint the image.
         nsLayoutUtils::DrawSingleUnscaledImage(&aRenderingContext, image,
-            pt, aDirtyRect, &imageSize);
+            pt, aDirtyRect, imgIContainer::FLAG_NONE, &imageSize);
       }
     }
   }
 }
 
 void
 nsTreeBodyFrame::PaintImage(PRInt32              aRowIndex,
                             nsTreeColumn*        aColumn,
@@ -3525,17 +3525,18 @@ nsTreeBodyFrame::PaintImage(PRInt32     
     image->GetWidth(&rawImageSize.width);
     image->GetHeight(&rawImageSize.height);
     nsRect wholeImageDest =
       nsLayoutUtils::GetWholeImageDestination(rawImageSize, sourceRect,
           nsRect(destRect.TopLeft(), imageDestSize));
 
     nsLayoutUtils::DrawImage(&aRenderingContext, image,
         nsLayoutUtils::GetGraphicsFilterForFrame(this),
-        wholeImageDest, destRect, destRect.TopLeft(), aDirtyRect);
+        wholeImageDest, destRect, destRect.TopLeft(), aDirtyRect,
+        imgIContainer::FLAG_NONE);
   }
 
   // Update the aRemainingWidth and aCurrX values.
   imageRect.Inflate(imageMargin);
   aRemainingWidth -= imageRect.width;
   if (!isRTL)
     aCurrX += imageRect.width;
 }
@@ -3701,17 +3702,17 @@ nsTreeBodyFrame::PaintCheckbox(PRInt32  
     }
 
     if (imageSize.width < checkboxRect.width) {
       pt.x += (checkboxRect.width - imageSize.width)/2;
     }
 
     // Paint the image.
     nsLayoutUtils::DrawSingleUnscaledImage(&aRenderingContext, image,
-        pt, aDirtyRect, &imageSize);
+        pt, aDirtyRect, imgIContainer::FLAG_NONE, &imageSize);
   }
 }
 
 void
 nsTreeBodyFrame::PaintProgressMeter(PRInt32              aRowIndex,
                                     nsTreeColumn*        aColumn,
                                     const nsRect&        aProgressMeterRect,
                                     nsPresContext*      aPresContext,
@@ -3766,17 +3767,18 @@ nsTreeBodyFrame::PaintProgressMeter(PRIn
     if (image) {
       PRInt32 width, height;
       image->GetWidth(&width);
       image->GetHeight(&height);
       nsSize size(width*nsIDeviceContext::AppUnitsPerCSSPixel(),
                   height*nsIDeviceContext::AppUnitsPerCSSPixel());
       nsLayoutUtils::DrawImage(&aRenderingContext, image,
           nsLayoutUtils::GetGraphicsFilterForFrame(this),
-          nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(), aDirtyRect);
+          nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(),
+          aDirtyRect, imgIContainer::FLAG_NONE);
     } else {
       aRenderingContext.FillRect(meterRect);
     }
   }
   else if (state == nsITreeView::PROGRESS_UNDETERMINED) {
     // Adjust the rect for its border and padding.
     AdjustForBorderPadding(meterContext, meterRect);
 
@@ -3786,17 +3788,18 @@ nsTreeBodyFrame::PaintProgressMeter(PRIn
     if (image) {
       PRInt32 width, height;
       image->GetWidth(&width);
       image->GetHeight(&height);
       nsSize size(width*nsIDeviceContext::AppUnitsPerCSSPixel(),
                   height*nsIDeviceContext::AppUnitsPerCSSPixel());
       nsLayoutUtils::DrawImage(&aRenderingContext, image,
           nsLayoutUtils::GetGraphicsFilterForFrame(this),
-          nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(), aDirtyRect);
+          nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(),
+          aDirtyRect, imgIContainer::FLAG_NONE);
     }
   }
 }
 
 
 void
 nsTreeBodyFrame::PaintDropFeedback(const nsRect&        aDropFeedbackRect,
                                    nsPresContext*      aPresContext,
--- a/modules/libpr0n/build/nsImageModule.cpp
+++ b/modules/libpr0n/build/nsImageModule.cpp
@@ -185,17 +185,17 @@ static NS_METHOD ImageUnregisterProc(nsI
 static const nsModuleComponentInfo components[] =
 {
   { "image cache",
     NS_IMGLOADER_CID,
     "@mozilla.org/image/cache;1",
     imgLoaderConstructor, },
   { "image container",
     NS_IMGCONTAINER_CID,
-    "@mozilla.org/image/container;2",
+    "@mozilla.org/image/container;3",
     imgContainerConstructor, },
   { "image loader",
     NS_IMGLOADER_CID,
     "@mozilla.org/image/loader;1",
     imgLoaderConstructor,
     ImageRegisterProc, /* register the decoder mime types here */
     ImageUnregisterProc, },
   { "image request proxy",
@@ -206,72 +206,72 @@ static const nsModuleComponentInfo compo
     NS_IMGTOOLS_CID,
     "@mozilla.org/image/tools;1",
     imgToolsConstructor, },
 
 #ifdef IMG_BUILD_DECODER_gif
   // gif
   { "GIF Decoder",
      NS_GIFDECODER2_CID,
-     "@mozilla.org/image/decoder;2?type=image/gif",
+     "@mozilla.org/image/decoder;3?type=image/gif",
      nsGIFDecoder2Constructor, },
 #endif
 
 #ifdef IMG_BUILD_DECODER_jpeg
   // jpeg
   { "JPEG decoder",
     NS_JPEGDECODER_CID,
-    "@mozilla.org/image/decoder;2?type=image/jpeg",
+    "@mozilla.org/image/decoder;3?type=image/jpeg",
     nsJPEGDecoderConstructor, },
   { "JPEG decoder",
     NS_JPEGDECODER_CID,
-    "@mozilla.org/image/decoder;2?type=image/pjpeg",
+    "@mozilla.org/image/decoder;3?type=image/pjpeg",
     nsJPEGDecoderConstructor, },
   { "JPEG decoder",
     NS_JPEGDECODER_CID,
-    "@mozilla.org/image/decoder;2?type=image/jpg",
+    "@mozilla.org/image/decoder;3?type=image/jpg",
     nsJPEGDecoderConstructor, },
 #endif
 #ifdef IMG_BUILD_ENCODER_jpeg
   // jpeg (encoder)
   { "JPEG Encoder",
     NS_JPEGENCODER_CID,
     "@mozilla.org/image/encoder;2?type=image/jpeg",
     nsJPEGEncoderConstructor, },
 #endif
 
 #ifdef IMG_BUILD_DECODER_bmp
   // bmp
   { "ICO Decoder",
      NS_ICODECODER_CID,
-     "@mozilla.org/image/decoder;2?type=image/x-icon",
+     "@mozilla.org/image/decoder;3?type=image/x-icon",
      nsICODecoderConstructor, },
   { "ICO Decoder",
      NS_ICODECODER_CID,
-     "@mozilla.org/image/decoder;2?type=image/vnd.microsoft.icon",
+     "@mozilla.org/image/decoder;3?type=image/vnd.microsoft.icon",
      nsICODecoderConstructor, },
   { "BMP Decoder",
      NS_BMPDECODER_CID,
-     "@mozilla.org/image/decoder;2?type=image/bmp",
+     "@mozilla.org/image/decoder;3?type=image/bmp",
      nsBMPDecoderConstructor, },
   { "BMP Decoder",
      NS_BMPDECODER_CID,
-     "@mozilla.org/image/decoder;2?type=image/x-ms-bmp",
+     "@mozilla.org/image/decoder;3?type=image/x-ms-bmp",
      nsBMPDecoderConstructor, },
 #endif
 
 #ifdef IMG_BUILD_DECODER_png
   // png
   { "PNG Decoder",
     NS_PNGDECODER_CID,
-    "@mozilla.org/image/decoder;2?type=image/png",
+    "@mozilla.org/image/decoder;3?type=image/png",
     nsPNGDecoderConstructor, },
   { "PNG Decoder",
     NS_PNGDECODER_CID,
-    "@mozilla.org/image/decoder;2?type=image/x-png",
+    "@mozilla.org/image/decoder;3?type=image/x-png",
     nsPNGDecoderConstructor, },
 #endif
 #ifdef IMG_BUILD_ENCODER_png
   // png
   { "PNG Encoder",
     NS_PNGENCODER_CID,
     "@mozilla.org/image/encoder;2?type=image/png",
     nsPNGEncoderConstructor, },
--- a/modules/libpr0n/decoders/bmp/nsBMPDecoder.cpp
+++ b/modules/libpr0n/decoders/bmp/nsBMPDecoder.cpp
@@ -16,16 +16,17 @@
  *
  * The Initial Developer of the Original Code is
  * Christian Biesinger <cbiesinger@web.de>.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Neil Rashbrook <neil@parkwaycc.co.uk>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -45,18 +46,16 @@
 #include "nsBMPDecoder.h"
 
 #include "nsIInputStream.h"
 #include "nsIComponentManager.h"
 #include "imgIContainerObserver.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 
-#include "imgILoad.h"
-
 #include "prlog.h"
 
 #ifdef PR_LOGGING
 PRLogModuleInfo *gBMPLog = PR_NewLogModule("BMPDecoder");
 #endif
 
 // Convert from row (1..height) to absolute line (0..height-1)
 #define LINE(row) ((mBIH.height < 0) ? (-mBIH.height - (row)) : ((row) - 1))
@@ -68,86 +67,95 @@ nsBMPDecoder::nsBMPDecoder()
 {
     mColors = nsnull;
     mRow = nsnull;
     mCurPos = mPos = mNumColors = mRowBytes = 0;
     mOldLine = mCurLine = 1; // Otherwise decoder will never start
     mState = eRLEStateInitial;
     mStateData = 0;
     mLOH = WIN_HEADER_LENGTH;
+    mError = PR_FALSE;
 }
 
 nsBMPDecoder::~nsBMPDecoder()
 {
-    delete[] mColors;
-    if (mRow)
-        free(mRow);
+  delete[] mColors;
+  if (mRow)
+      free(mRow);
 }
 
-NS_IMETHODIMP nsBMPDecoder::Init(imgILoad *aLoad)
+NS_IMETHODIMP nsBMPDecoder::Init(imgIContainer *aImage,
+                                 imgIDecoderObserver *aObserver,
+                                 PRUint32 aFlags)
 {
-    PR_LOG(gBMPLog, PR_LOG_DEBUG, ("nsBMPDecoder::Init(%p)\n", aLoad));
-    mObserver = do_QueryInterface(aLoad);
+    PR_LOG(gBMPLog, PR_LOG_DEBUG, ("nsBMPDecoder::Init(%p)\n", aImage));
+    mImage = aImage;
+    mObserver = aObserver;
+    mFlags = aFlags;
 
-    nsresult rv;
-    mImage = do_CreateInstance("@mozilla.org/image/container;2", &rv);
-    if (NS_FAILED(rv))
-        return rv;
+    // Fire OnStartDecode at init time to support bug 512435
+    if (!(mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) && mObserver)
+        mObserver->OnStartDecode(nsnull);
 
-    return aLoad->SetImage(mImage);
+    return NS_OK;
 }
 
-NS_IMETHODIMP nsBMPDecoder::Close()
+NS_IMETHODIMP nsBMPDecoder::Close(PRUint32 aFlags)
 {
     PR_LOG(gBMPLog, PR_LOG_DEBUG, ("nsBMPDecoder::Close()\n"));
 
-    mImage->DecodingComplete();
-    if (mObserver) {
-        mObserver->OnStopFrame(nsnull, 0);
-        mObserver->OnStopContainer(nsnull, mImage);
-        mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
-        mObserver = nsnull;
+    // Send notifications if appropriate
+    if (!(mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) &&
+        !mError && !(aFlags & CLOSE_FLAG_DONTNOTIFY)) {
+        if (mObserver)
+            mObserver->OnStopFrame(nsnull, 0);
+        mImage->DecodingComplete();
+        if (mObserver) {
+            mObserver->OnStopContainer(nsnull, mImage);
+            mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
+        }
     }
-    mImage = nsnull;
     return NS_OK;
 }
 
 NS_IMETHODIMP nsBMPDecoder::Flush()
 {
     return NS_OK;
 }
 
 NS_METHOD nsBMPDecoder::ReadSegCb(nsIInputStream* aIn, void* aClosure,
                              const char* aFromRawSegment, PRUint32 aToOffset,
                              PRUint32 aCount, PRUint32 *aWriteCount) 
 {
     nsBMPDecoder *decoder = reinterpret_cast<nsBMPDecoder*>(aClosure);
+
+    // Always read everything
     *aWriteCount = aCount;
-    
+
     nsresult rv = decoder->ProcessData(aFromRawSegment, aCount);
 
-    if (NS_FAILED(rv)) {
-        *aWriteCount = 0;
-    }
-
+    // Necko doesn't propagate rvs. Set a flag before returning.
+    if (NS_FAILED(rv))
+        decoder->mError = PR_TRUE;
     return rv;
 }
 
-NS_IMETHODIMP nsBMPDecoder::WriteFrom(nsIInputStream *aInStr, PRUint32 aCount, PRUint32 *aRetval)
+NS_IMETHODIMP nsBMPDecoder::WriteFrom(nsIInputStream *aInStr, PRUint32 aCount)
 {
-    PR_LOG(gBMPLog, PR_LOG_DEBUG, ("nsBMPDecoder::WriteFrom(%p, %lu, %p)\n", aInStr, aCount, aRetval));
+    PR_LOG(gBMPLog, PR_LOG_DEBUG, ("nsBMPDecoder::WriteFrom(%p, %lu, %p)\n", aInStr, aCount));
 
-    nsresult rv = aInStr->ReadSegments(ReadSegCb, this, aCount, aRetval);
-    
-    if (aCount != *aRetval) { 
-        *aRetval = aCount; 
-        return NS_ERROR_FAILURE; 
+    // Decode, watching for errors.
+    nsresult rv = NS_OK;
+    PRUint32 ignored;
+    if (!mError)
+        rv = aInStr->ReadSegments(ReadSegCb, this, aCount, &ignored);
+    if (mError || NS_FAILED(rv)) {
+        return NS_ERROR_FAILURE;
     }
-    
-    return rv;    
+    return NS_OK;
 }
 
 // ----------------------------------------
 // Actual Data Processing
 // ----------------------------------------
 
 static void calcBitmask(PRUint32 aMask, PRUint8& aBegin, PRUint8& aLength)
 {
@@ -197,18 +205,16 @@ NS_METHOD nsBMPDecoder::ProcessData(cons
         if (toCopy > aCount)
             toCopy = aCount;
         memcpy(mRawBuf + mPos, aBuffer, toCopy);
         mPos += toCopy;
         aCount -= toCopy;
         aBuffer += toCopy;
     }
     if (mPos == BFH_LENGTH) {
-        rv = mObserver->OnStartDecode(nsnull);
-        NS_ENSURE_SUCCESS(rv, rv);
         ProcessFileHeader();
         if (mBFH.signature[0] != 'B' || mBFH.signature[1] != 'M')
             return NS_ERROR_FAILURE;
         if (mBFH.bihsize == OS2_BIH_LENGTH)
             mLOH = OS2_HEADER_LENGTH;
     }
     if (mPos >= BFH_LENGTH && mPos < mLOH) { /* In BITMAPINFOHEADER */
         PRUint32 toCopy = mLOH - mPos;
@@ -223,16 +229,40 @@ NS_METHOD nsBMPDecoder::ProcessData(cons
         ProcessInfoHeader();
         PR_LOG(gBMPLog, PR_LOG_DEBUG, ("BMP image is %lix%lix%lu. compression=%lu\n",
             mBIH.width, mBIH.height, mBIH.bpp, mBIH.compression));
         // Verify we support this bit depth
         if (mBIH.bpp != 1 && mBIH.bpp != 4 && mBIH.bpp != 8 &&
             mBIH.bpp != 16 && mBIH.bpp != 24 && mBIH.bpp != 32)
           return NS_ERROR_UNEXPECTED;
 
+        // BMPs with negative width are invalid
+        // Reject extremely wide images to keep the math sane
+        const PRInt32 k64KWidth = 0x0000FFFF;
+        if (mBIH.width < 0 || mBIH.width > k64KWidth)
+            return NS_ERROR_FAILURE;
+
+        PRUint32 real_height = (mBIH.height > 0) ? mBIH.height : -mBIH.height;
+
+        // Set the size and notify
+        rv = mImage->SetSize(mBIH.width, real_height);
+        NS_ENSURE_SUCCESS(rv, rv);
+        if (mObserver) {
+            rv = mObserver->OnStartContainer(nsnull, mImage);
+            NS_ENSURE_SUCCESS(rv, rv);
+        }
+
+        // We have the size. If we're doing a header-only decode, we got what
+        // we came for.
+        if (mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY)
+            return NS_OK;
+
+        // We're doing a real decode.
+        mOldLine = mCurLine = real_height;
+
         if (mBIH.bpp <= 8) {
             mNumColors = 1 << mBIH.bpp;
             if (mBIH.colors && mBIH.colors < mNumColors)
                 mNumColors = mBIH.colors;
 
             // Always allocate 256 even though mNumColors might be smaller
             mColors = new colorTable[256];
             if (!mColors)
@@ -242,28 +272,16 @@ NS_METHOD nsBMPDecoder::ProcessData(cons
         }
         else if (mBIH.compression != BI_BITFIELDS && mBIH.bpp == 16) {
             // Use default 5-5-5 format
             mBitFields.red   = 0x7C00;
             mBitFields.green = 0x03E0;
             mBitFields.blue  = 0x001F;
             CalcBitShift();
         }
-        // BMPs with negative width are invalid
-        // Reject extremely wide images to keep the math sane
-        const PRInt32 k64KWidth = 0x0000FFFF;
-        if (mBIH.width < 0 || mBIH.width > k64KWidth)
-            return NS_ERROR_FAILURE;
-
-        PRUint32 real_height = (mBIH.height > 0) ? mBIH.height : -mBIH.height;
-        rv = mImage->Init(mBIH.width, real_height, mObserver);
-        NS_ENSURE_SUCCESS(rv, rv);
-        rv = mObserver->OnStartContainer(nsnull, mImage);
-        NS_ENSURE_SUCCESS(rv, rv);
-        mOldLine = mCurLine = real_height;
 
         PRUint32 imageLength;
         if ((mBIH.compression == BI_RLE8) || (mBIH.compression == BI_RLE4)) {
             rv = mImage->AppendFrame(0, 0, mBIH.width, real_height, gfxASurface::ImageFormatARGB32,
                                      (PRUint8**)&mImageData, &imageLength);
         } else {
             // mRow is not used for RLE encoded images
             mRow = (PRUint8*)malloc((mBIH.width * mBIH.bpp)/8 + 4);
@@ -286,18 +304,20 @@ NS_METHOD nsBMPDecoder::ProcessData(cons
              || ((mBIH.compression == BI_RLE4) && (mBIH.bpp != 4) && (mBIH.bpp != 1))) {
                 PR_LOG(gBMPLog, PR_LOG_DEBUG, ("BMP RLE8/RLE4 compression only supports 8/4 bits per pixel\n"));
                 return NS_ERROR_FAILURE;
             }
             // Clear the image, as the RLE may jump over areas
             memset(mImageData, 0, imageLength);
         }
 
-        mObserver->OnStartFrame(nsnull, 0);
-        NS_ENSURE_SUCCESS(rv, rv);
+        if (mObserver) {
+            mObserver->OnStartFrame(nsnull, 0);
+            NS_ENSURE_SUCCESS(rv, rv);
+        }
     }
     PRUint8 bpc; // bytes per color
     bpc = (mBFH.bihsize == OS2_BIH_LENGTH) ? 3 : 4; // OS/2 Bitmaps have no padding byte
     if (mColors && (mPos >= mLOH && (mPos < (mLOH + mNumColors * bpc)))) {
         // We will receive (mNumColors * bpc) bytes of color data
         PRUint32 colorBytes = mPos - mLOH; // Number of bytes already received
         PRUint8 colorNum = colorBytes / bpc; // Color which is currently received
         PRUint8 at = colorBytes % bpc;
@@ -594,17 +614,18 @@ NS_METHOD nsBMPDecoder::ProcessData(cons
     if (rows) {
         nsIntRect r(0, mBIH.height < 0 ? -mBIH.height - mOldLine : mCurLine,
                     mBIH.width, rows);
 
         // Tell the image that its data has been updated
         rv = mImage->FrameUpdated(0, r); 
         NS_ENSURE_SUCCESS(rv, rv);
 
-        mObserver->OnDataAvailable(nsnull, PR_TRUE, &r);
+        if (mObserver)
+            mObserver->OnDataAvailable(nsnull, PR_TRUE, &r);
         mOldLine = mCurLine;
     }
 
     return NS_OK;
 }
 
 void nsBMPDecoder::ProcessFileHeader()
 {
--- a/modules/libpr0n/decoders/bmp/nsBMPDecoder.h
+++ b/modules/libpr0n/decoders/bmp/nsBMPDecoder.h
@@ -15,16 +15,17 @@
  * The Original Code is the Mozilla BMP Decoder.
  *
  * The Initial Developer of the Original Code is
  * Christian Biesinger <cbiesinger@web.de>.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -164,16 +165,17 @@ private:
 
     /** Calculates the red-, green- and blueshift in mBitFields using
      * the bitmasks from mBitFields */
     NS_METHOD CalcBitShift();
 
     nsCOMPtr<imgIDecoderObserver> mObserver;
 
     nsCOMPtr<imgIContainer> mImage;
+    PRUint32 mFlags;
 
     PRUint32 mPos;
 
     BMPFILEHEADER mBFH;
     BMPINFOHEADER mBIH;
     char mRawBuf[36];
 
     PRUint32 mLOH; ///< Length of the header
@@ -187,16 +189,17 @@ private:
     PRUint8 *mRow;      ///< Holds one raw line of the image
     PRUint32 mRowBytes; ///< How many bytes of the row were already received
     PRInt32 mCurLine;   ///< Index of the line of the image that's currently being decoded
     PRInt32 mOldLine;   ///< Previous index of the line 
     PRInt32 mCurPos;    ///< Index in the current line of the image
 
     ERLEState mState;   ///< Maintains the current state of the RLE decoding
     PRUint32 mStateData;///< Decoding information that is needed depending on mState
+    PRBool mError;      ///< Did we hit an error?
 
     /** Set mBFH from the raw data in mRawBuf, converting from little-endian
      * data to native data as necessary */
     void ProcessFileHeader();
     /** Set mBIH from the raw data in mRawBuf, converting from little-endian
      * data to native data as necessary */
     void ProcessInfoHeader();
 };
--- a/modules/libpr0n/decoders/bmp/nsICODecoder.cpp
+++ b/modules/libpr0n/decoders/bmp/nsICODecoder.cpp
@@ -17,16 +17,17 @@
  * The Initial Developer of the Original Code is
  * Netscape.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   David Hyatt <hyatt@netscape.com> (Original Author)
  *   Christian Biesinger <cbiesinger@web.de>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -43,17 +44,16 @@
 #include <stdlib.h>
 
 #include "nsICODecoder.h"
 
 #include "nsIInputStream.h"
 #include "nsIComponentManager.h"
 #include "imgIContainerObserver.h"
 
-#include "imgILoad.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 
 #include "nsIProperties.h"
 #include "nsISupportsPrimitives.h"
 
 #include "nsAutoPtr.h"
 
@@ -77,55 +77,65 @@ PRUint32 nsICODecoder::CalcAlphaRowSize(
 
 nsICODecoder::nsICODecoder()
 {
   mPos = mNumColors = mRowBytes = mImageOffset = mCurrIcon = mNumIcons = 0;
   mCurLine = 1; // Otherwise decoder will never start
   mColors = nsnull;
   mRow = nsnull;
   mHaveAlphaData = mDecodingAndMask = PR_FALSE;
+  mError = PR_FALSE;
 }
 
 nsICODecoder::~nsICODecoder()
 {
 }
 
-NS_IMETHODIMP nsICODecoder::Init(imgILoad *aLoad)
-{ 
-  mObserver = do_QueryInterface(aLoad);
-    
-  mImage = do_CreateInstance("@mozilla.org/image/container;2");
-  if (!mImage)
-    return NS_ERROR_OUT_OF_MEMORY;
+NS_IMETHODIMP nsICODecoder::Init(imgIContainer *aImage,
+                                 imgIDecoderObserver *aObserver,
+                                 PRUint32 aFlags)
+{
+  // Grab parameters
+  mImage = aImage;
+  mObserver = aObserver;
+  mFlags = aFlags;
 
-  return aLoad->SetImage(mImage);
+  // Fire OnStartDecode at init time to support bug 512435
+  if (!(mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) && mObserver)
+    mObserver->OnStartDecode(nsnull);
+
+  return NS_OK;
 }
 
-NS_IMETHODIMP nsICODecoder::Close()
+NS_IMETHODIMP nsICODecoder::Close(PRUint32 aFlags)
 {
-  // Tell the image that it's data has been updated 
-  nsIntRect r(0, 0, mDirEntry.mWidth, mDirEntry.mHeight);
-  nsresult rv = mImage->FrameUpdated(0, r);
-    
-  mImage->DecodingComplete();
+  nsresult rv = NS_OK;
+
+  // Send notifications if appropriate
+  if (!(mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) &&
+      !mError && !(aFlags & CLOSE_FLAG_DONTNOTIFY)) {
+    // Tell the image that it's data has been updated 
+    nsIntRect r(0, 0, mDirEntry.mWidth, mDirEntry.mHeight);
+    rv = mImage->FrameUpdated(0, r);
+
 
-  if (mObserver) {
-    mObserver->OnDataAvailable(nsnull, PR_TRUE, &r);
-    mObserver->OnStopFrame(nsnull, 0);
-    mObserver->OnStopContainer(nsnull, 0);
-    mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
-    mObserver = nsnull;
+    if (mObserver) {
+      mObserver->OnDataAvailable(nsnull, PR_TRUE, &r);
+      mObserver->OnStopFrame(nsnull, 0);
+    }
+    mImage->DecodingComplete();
+    if (mObserver) {
+      mObserver->OnStopContainer(nsnull, 0);
+      mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
+    }
   }
 
-  mImage = nsnull;
- 
   mPos = 0;
 
   delete[] mColors;
-  mColors = nsnull;
 
   mCurLine = 0;
   mRowBytes = 0;
   mImageOffset = 0;
   mCurrIcon = 0;
   mNumIcons = 0;
 
   if (mRow) {
@@ -142,23 +152,39 @@ NS_IMETHODIMP nsICODecoder::Flush()
   return NS_OK;
 }
 
 
 NS_METHOD nsICODecoder::ReadSegCb(nsIInputStream* aIn, void* aClosure,
                              const char* aFromRawSegment, PRUint32 aToOffset,
                              PRUint32 aCount, PRUint32 *aWriteCount) {
   nsICODecoder *decoder = reinterpret_cast<nsICODecoder*>(aClosure);
+
+  // Always read everything
   *aWriteCount = aCount;
-  return decoder->ProcessData(aFromRawSegment, aCount);
+
+  // Process
+  nsresult rv = decoder->ProcessData(aFromRawSegment, aCount);
+
+  // rvs might not propagate correctly. Set a flag before returning.
+  if (NS_FAILED(rv))
+    decoder->mError = PR_TRUE;
+  return rv;
 }
 
-NS_IMETHODIMP nsICODecoder::WriteFrom(nsIInputStream *aInStr, PRUint32 aCount, PRUint32 *aRetval)
+NS_IMETHODIMP nsICODecoder::WriteFrom(nsIInputStream *aInStr, PRUint32 aCount)
 {
-  return aInStr->ReadSegments(ReadSegCb, this, aCount, aRetval);
+  // Decode, watching for errors
+  nsresult rv = NS_OK;
+  PRUint32 ignored;
+  if (!mError)
+    rv = aInStr->ReadSegments(ReadSegCb, this, aCount, &ignored);
+  if (mError || NS_FAILED(rv))
+    return NS_ERROR_FAILURE;
+  return NS_OK;
 }
 
 nsresult nsICODecoder::ProcessData(const char* aBuffer, PRUint32 aCount) {
   if (!aCount) // aCount=0 means EOF
     return NS_OK;
 
   while (aCount && (mPos < ICONCOUNTOFFSET)) { // Skip to the # of icons.
     if (mPos == 2) { // if the third byte is 1: This is an icon, 2: a cursor
@@ -235,20 +261,27 @@ nsresult nsICODecoder::ProcessData(const
     mPos += toCopy;
     aCount -= toCopy;
     aBuffer += toCopy;
   }
 
   nsresult rv;
 
   if (mPos == mImageOffset + BITMAPINFOSIZE) {
-    rv = mObserver->OnStartDecode(nsnull);
-    NS_ENSURE_SUCCESS(rv, rv);
 
     ProcessInfoHeader();
+    rv = mImage->SetSize(mDirEntry.mWidth, mDirEntry.mHeight);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (mObserver) {
+      rv = mObserver->OnStartContainer(nsnull, mImage);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    if (mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY)
+      return NS_OK;
+
     if (mBIH.bpp <= 8) {
       switch (mBIH.bpp) {
         case 1:
           mNumColors = 2;
           break;
         case 4:
           mNumColors = 16;
           break;
@@ -259,53 +292,49 @@ nsresult nsICODecoder::ProcessData(const
           return NS_ERROR_FAILURE;
       }
 
       mColors = new colorTable[mNumColors];
       if (!mColors)
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    rv = mImage->Init(mDirEntry.mWidth, mDirEntry.mHeight, mObserver);
-    NS_ENSURE_SUCCESS(rv, rv);
-
     if (mIsCursor) {
       nsCOMPtr<nsIProperties> props(do_QueryInterface(mImage));
       if (props) {
         nsCOMPtr<nsISupportsPRUint32> intwrapx = do_CreateInstance("@mozilla.org/supports-PRUint32;1");
         nsCOMPtr<nsISupportsPRUint32> intwrapy = do_CreateInstance("@mozilla.org/supports-PRUint32;1");
 
         if (intwrapx && intwrapy) {
           intwrapx->SetData(mDirEntry.mXHotspot);
           intwrapy->SetData(mDirEntry.mYHotspot);
 
           props->Set("hotspotX", intwrapx);
           props->Set("hotspotY", intwrapy);
         }
       }
     }
 
-    rv = mObserver->OnStartContainer(nsnull, mImage);
-    NS_ENSURE_SUCCESS(rv, rv);
-
     mCurLine = mDirEntry.mHeight;
     mRow = (PRUint8*)malloc((mDirEntry.mWidth * mBIH.bpp)/8 + 4);
     // +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)
       return NS_ERROR_OUT_OF_MEMORY;
     
     PRUint32 imageLength;
     rv = mImage->AppendFrame(0, 0, mDirEntry.mWidth, mDirEntry.mHeight,
                              gfxASurface::ImageFormatARGB32, (PRUint8**)&mImageData, &imageLength);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    mObserver->OnStartFrame(nsnull, 0);
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (mObserver) {
+      mObserver->OnStartFrame(nsnull, 0);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
   }
 
   if (mColors && (mPos >= mImageOffset + BITMAPINFOSIZE) && 
                  (mPos < (mImageOffset + BITMAPINFOSIZE + mNumColors * 4))) {
     // We will receive (mNumColors * 4) bytes of color data
     PRUint32 colorBytes = mPos - (mImageOffset + 40); // Number of bytes already received
     PRUint8 colorNum = colorBytes / 4; // Color which is currently received
     PRUint8 at = colorBytes % 4;
--- a/modules/libpr0n/decoders/bmp/nsICODecoder.h
+++ b/modules/libpr0n/decoders/bmp/nsICODecoder.h
@@ -16,16 +16,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   David Hyatt <hyatt@netscape.com> (Original Author)
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -88,18 +89,19 @@ private:
   void ProcessDirEntry(IconDirEntry& aTarget);
   void ProcessInfoHeader();
 
   nsresult SetImageData();
 
   PRUint32 CalcAlphaRowSize();
 
 private:
+  nsCOMPtr<imgIContainer> mImage;
   nsCOMPtr<imgIDecoderObserver> mObserver;
-  nsCOMPtr<imgIContainer> mImage;
+  PRUint32 mFlags;
   
   PRUint32 mPos;
   PRUint16 mNumIcons;
   PRUint16 mCurrIcon;
   PRUint32 mImageOffset;
 
   char mDirEntryArray[16];
   IconDirEntry mDirEntry;
@@ -114,12 +116,13 @@ private:
   PRUint32 mRowBytes; // How many bytes of the row were already received
   PRInt32 mCurLine;
 
   PRUint32* mImageData;
 
   PRPackedBool mHaveAlphaData;
   PRPackedBool mIsCursor;
   PRPackedBool mDecodingAndMask;
+  PRPackedBool mError;
 };
 
 
 #endif
--- a/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp
+++ b/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Chris Saari <saari@netscape.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -76,18 +77,16 @@ mailing address.
 
 #include "nsIInterfaceRequestorUtils.h"
 
 #include "nsGIFDecoder2.h"
 #include "nsIInputStream.h"
 #include "nsIComponentManager.h"
 #include "imgIContainerObserver.h"
 
-#include "imgILoad.h"
-
 #include "gfxColor.h"
 #include "gfxPlatform.h"
 #include "qcms.h"
 
 /*
  * GETN(n, s) requests at least 'n' bytes available from 'q', at start of state 's'
  *
  * Note, the hold will never need to be bigger than 256 bytes to gather up in the hold,
@@ -117,87 +116,125 @@ nsGIFDecoder2::nsGIFDecoder2()
   , mLastFlushedRow(-1)
   , mImageData(nsnull)
   , mOldColor(0)
   , mCurrentFrame(-1)
   , mCurrentPass(0)
   , mLastFlushedPass(0)
   , mGIFOpen(PR_FALSE)
   , mSawTransparency(PR_FALSE)
+  , mError(PR_FALSE)
 {
   // Clear out the structure, excluding the arrays
   memset(&mGIFStruct, 0, sizeof(mGIFStruct));
 }
 
 nsGIFDecoder2::~nsGIFDecoder2()
 {
-  Close();
 }
 
 //******************************************************************************
 /** imgIDecoder methods **/
 //******************************************************************************
 
 //******************************************************************************
-/* void init (in imgILoad aLoad); */
-NS_IMETHODIMP nsGIFDecoder2::Init(imgILoad *aLoad)
+/* void init (in imgIContainer aImage,
+              in imgIDecoderObserver aObsever,
+              in unsigned long aFlags); */
+NS_IMETHODIMP nsGIFDecoder2::Init(imgIContainer *aImage,
+                                  imgIDecoderObserver *aObserver,
+                                  PRUint32 aFlags)
 {
-  mObserver = do_QueryInterface(aLoad);
+  // Store parameters
+  mImageContainer = aImage;
+  mObserver = aObserver;
+  mFlags = aFlags;
 
-  mImageContainer = do_CreateInstance("@mozilla.org/image/container;2");
-  aLoad->SetImage(mImageContainer);
-  
+  // Fire OnStartDecode at init time to support bug 512435
+  if (!(mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) && mObserver)
+    mObserver->OnStartDecode(nsnull);
+
   // Start with the version (GIF89a|GIF87a)
   mGIFStruct.state = gif_type;
   mGIFStruct.bytes_to_consume = 6;
 
   return NS_OK;
 }
 
 
 //******************************************************************************
 /** nsIOutputStream methods **/
 //******************************************************************************
 
 //******************************************************************************
 /* void close (); */
-NS_IMETHODIMP nsGIFDecoder2::Close()
+NS_IMETHODIMP nsGIFDecoder2::Close(PRUint32 aFlags)
 {
-  if (mCurrentFrame == mGIFStruct.images_decoded)
-    EndImageFrame();
-  EndGIF();
+  // Send notifications if appropriate
+  if (!(mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) &&
+      !mError && !(aFlags & CLOSE_FLAG_DONTNOTIFY)) {
+    if (mCurrentFrame == mGIFStruct.images_decoded)
+      EndImageFrame();
+    EndGIF(/* aSuccess = */ PR_TRUE);
+  }
 
   PR_FREEIF(mGIFStruct.local_colormap);
 
+  mImageContainer = nsnull;
+
   return NS_OK;
 }
 
 //******************************************************************************
 /* void flush (); */
 NS_IMETHODIMP nsGIFDecoder2::Flush()
 {
     return NS_OK;
 }
 
 //******************************************************************************
 /* static callback from nsIInputStream::ReadSegments */
-static NS_METHOD ReadDataOut(nsIInputStream* in,
-                             void* closure,
-                             const char* fromRawSegment,
-                             PRUint32 toOffset,
-                             PRUint32 count,
-                             PRUint32 *writeCount)
+NS_METHOD nsGIFDecoder2::ReadDataOut(nsIInputStream* in,
+                                     void* closure,
+                                     const char* fromRawSegment,
+                                     PRUint32 toOffset,
+                                     PRUint32 count,
+                                     PRUint32 *writeCount)
 {
   nsGIFDecoder2 *decoder = static_cast<nsGIFDecoder2*>(closure);
-  nsresult rv = decoder->ProcessData((unsigned char*)fromRawSegment, count, writeCount);
+
+  // Always read everything
+  *writeCount = count;
+
+  // Process
+  nsresult rv = decoder->ProcessData((unsigned char*)fromRawSegment, count);
+
+  // We do some fine-grained error control here. If we have at least one frame
+  // of an animated gif, we still want to display it (mostly for legacy reasons).
+  // libpr0n code is strict, so we have to lie and tell it we were successful. So
+  // if we have something to salvage, we send off final decode notifications, and
+  // pretend that we're decoded. Otherwise, we set mError.
   if (NS_FAILED(rv)) {
-    *writeCount = 0;
-    return rv;
+
+    // Determine if we want to salvage the situation
+    PRUint32 numFrames = 0;
+    if (decoder->mImageContainer)
+      decoder->mImageContainer->GetNumFrames(&numFrames);
+
+    // If we're salvaging, send off notifications
+    if (numFrames > 1) { // XXXbholley - this is from the old code, but why not > 0?
+      decoder->EndGIF(/* aSuccess = */ PR_TRUE);
+    }
+
+    // Otherwise, set mError
+    else
+      decoder->mError = PR_TRUE;
   }
 
+  // Necko is dubious with callbacks, so we don't use rvs to propagate errors.
   return NS_OK;
 }
 
 // Push any new rows according to mCurrentPass/mLastFlushedPass and
 // mCurrentRow/mLastFlushedRow.  Note: caller is responsible for
 // updating mlastFlushed{Row,Pass}.
 nsresult
 nsGIFDecoder2::FlushImageData(PRUint32 fromRow, PRUint32 rows)
@@ -239,90 +276,86 @@ nsGIFDecoder2::FlushImageData()
 
     default:   // more than one pass on - push the whole frame
       rv = FlushImageData(0, mGIFStruct.height);
   }
   return rv;
 }
 
 //******************************************************************************
-nsresult nsGIFDecoder2::ProcessData(unsigned char *data, PRUint32 count, PRUint32 *_retval)
+nsresult nsGIFDecoder2::ProcessData(unsigned char *data, PRUint32 count)
 {
   // Push the data to the GIF decoder
-  
   nsresult rv = GifWrite(data, count);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Flushing is only needed for first frame
   if (!mGIFStruct.images_decoded) {
     rv = FlushImageData();
+    NS_ENSURE_SUCCESS(rv, rv);
     mLastFlushedRow = mCurrentRow;
     mLastFlushedPass = mCurrentPass;
   }
 
-  *_retval = count;
-
-  return rv;
+  return NS_OK;
 }
 
 //******************************************************************************
-/* unsigned long writeFrom (in nsIInputStream inStr, in unsigned long count); */
-NS_IMETHODIMP nsGIFDecoder2::WriteFrom(nsIInputStream *inStr, PRUint32 count, PRUint32 *_retval)
+/* void  writeFrom (in nsIInputStream inStr, in unsigned long count); */
+NS_IMETHODIMP nsGIFDecoder2::WriteFrom(nsIInputStream *inStr, PRUint32 count)
 {
-  nsresult rv = inStr->ReadSegments(ReadDataOut, this,  count, _retval);
-
-  /* necko doesn't propagate the errors from ReadDataOut - take matters
-     into our own hands.  if we have at least one frame of an animated
-     gif, then return success so we keep displaying as much as possible. */
-  if (mGIFStruct.state == gif_error || mGIFStruct.state == gif_oom) {
-    PRUint32 numFrames = 0;
-    if (mImageContainer)
-      mImageContainer->GetNumFrames(&numFrames);
-    if (numFrames <= 1)
-      rv = NS_ERROR_FAILURE;
-  }
-
-  return rv;
+  // Decode, watching for errors
+  nsresult rv = NS_OK;
+  PRUint32 ignored;
+  if (!mError)
+    rv = inStr->ReadSegments(nsGIFDecoder2::ReadDataOut, this,
+                             count, &ignored);
+  if (mError || NS_FAILED(rv))
+    return NS_ERROR_FAILURE;
+  return NS_OK;
 }
 
 
 //******************************************************************************
 // GIF decoder callback methods. Part of public API for GIF2
 //******************************************************************************
 
 //******************************************************************************
 void nsGIFDecoder2::BeginGIF()
 {
   if (mGIFOpen)
     return;
-    
-  if (mObserver)
-    mObserver->OnStartDecode(nsnull);
 
-  mImageContainer->Init(mGIFStruct.screen_width, mGIFStruct.screen_height, mObserver);
+  mGIFOpen = PR_TRUE;
 
+  mImageContainer->SetSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
   if (mObserver)
     mObserver->OnStartContainer(nsnull, mImageContainer);
 
-  mGIFOpen = PR_TRUE;
+  // If we're doing a header-only decode, we have what we came for
+  if (mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY)
+    return;
 }
 
 //******************************************************************************
-void nsGIFDecoder2::EndGIF()
+void nsGIFDecoder2::EndGIF(PRBool aSuccess)
 {
   if (!mGIFOpen)
     return;
 
+  if (aSuccess)
+    mImageContainer->DecodingComplete();
+
   if (mObserver) {
     mObserver->OnStopContainer(nsnull, mImageContainer);
-    mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
+    mObserver->OnStopDecode(nsnull, aSuccess ? NS_OK : NS_ERROR_FAILURE,
+                            nsnull);
   }
-  
+
   mImageContainer->SetLoopCount(mGIFStruct.loop_count);
-  mImageContainer->DecodingComplete();
 
   mGIFOpen = PR_FALSE;
 }
 
 //******************************************************************************
 nsresult nsGIFDecoder2::BeginImageFrame(gfx_depth aDepth)
 {
   if (!mGIFStruct.images_decoded) {
@@ -330,17 +363,20 @@ nsresult nsGIFDecoder2::BeginImageFrame(
     // if it has a y-axis offset.  Otherwise, the area may never be refreshed
     // and the placeholder will remain on the screen. (Bug 37589)
     if (mGIFStruct.y_offset > 0) {
       PRInt32 imgWidth;
       mImageContainer->GetWidth(&imgWidth);
       PRUint32 imgCurFrame;
       mImageContainer->GetCurrentFrameIndex(&imgCurFrame);
       nsIntRect r(0, 0, imgWidth, mGIFStruct.y_offset);
-      mObserver->OnDataAvailable(nsnull, imgCurFrame == PRUint32(mGIFStruct.images_decoded), &r);
+      if (mObserver)
+        mObserver->OnDataAvailable(nsnull,
+                                   imgCurFrame == PRUint32(mGIFStruct.images_decoded),
+                                   &r);
     }
   }
 
   PRUint32 imageDataLength;
   nsresult rv;
   gfxASurface::gfxImageFormat format;
   if (mGIFStruct.is_transparent)
     format = gfxASurface::ImageFormatARGB32;
@@ -386,20 +422,23 @@ void nsGIFDecoder2::EndImageFrame()
 
     // If the first frame is smaller in height than the entire image, send a
     // OnDataAvailable (Display Refresh) for the area it does not have data for.
     // This will clear the remaining bits of the placeholder. (Bug 37589)
     const PRUint32 realFrameHeight = mGIFStruct.height + mGIFStruct.y_offset;
     if (realFrameHeight < mGIFStruct.screen_height) {
       PRUint32 imgCurFrame;
       mImageContainer->GetCurrentFrameIndex(&imgCurFrame);
-      nsIntRect r(0, realFrameHeight, 
-                  mGIFStruct.screen_width, 
-				  mGIFStruct.screen_height - realFrameHeight);
-      mObserver->OnDataAvailable(nsnull, imgCurFrame == PRUint32(mGIFStruct.images_decoded), &r);
+      nsIntRect r(0, realFrameHeight,
+                  mGIFStruct.screen_width,
+                  mGIFStruct.screen_height - realFrameHeight);
+      if (mObserver)
+        mObserver->OnDataAvailable(nsnull,
+                                  imgCurFrame == PRUint32(mGIFStruct.images_decoded),
+                                  &r);
     }
     // This transparency check is only valid for first frame
     if (mGIFStruct.is_transparent && !mSawTransparency) {
       mImageContainer->SetFrameHasNoAlpha(mGIFStruct.images_decoded);
     }
   }
   mCurrentRow = mLastFlushedRow = -1;
   mCurrentPass = mLastFlushedPass = 0;
@@ -1046,16 +1085,20 @@ nsresult nsGIFDecoder2::GifWrite(const P
             (mGIFStruct.version == 87)) {
           mGIFStruct.screen_height = mGIFStruct.height;
           mGIFStruct.screen_width = mGIFStruct.width;
           mGIFStruct.x_offset = 0;
           mGIFStruct.y_offset = 0;
         }    
         // Create the image container with the right size.
         BeginGIF();
+
+        // If we were doing header-only, we're done
+        if (mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY)
+          return NS_OK;
       }
 
       /* Work around more broken GIF files that have zero image
          width or height */
       if (!mGIFStruct.height || !mGIFStruct.width) {
         mGIFStruct.height = mGIFStruct.screen_height;
         mGIFStruct.width = mGIFStruct.screen_width;
         if (!mGIFStruct.height || !mGIFStruct.width) {
@@ -1177,38 +1220,38 @@ nsresult nsGIFDecoder2::GifWrite(const P
       } else {
         /* See if there are any more images in this sequence. */
         EndImageFrame();
         GETN(1, gif_image_start);
       }
       break;
 
     case gif_done:
-      EndGIF();
+      EndGIF(/* aSuccess = */ PR_TRUE);
       return NS_OK;
       break;
 
     case gif_error:
-      EndGIF();
+      EndGIF(/* aSuccess = */ PR_FALSE);
       return NS_ERROR_FAILURE;
       break;
 
     // Handle out of memory errors
     case gif_oom:
       return NS_ERROR_OUT_OF_MEMORY;
 
     // 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) {
-      EndGIF();
+      EndGIF(/* aSuccess = */ PR_FALSE);
       return NS_ERROR_FAILURE;
   }
   
   // 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/gif/nsGIFDecoder2.h
+++ b/modules/libpr0n/decoders/gif/nsGIFDecoder2.h
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Chris Saari <saari@netscape.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -62,37 +63,44 @@ class nsGIFDecoder2 : public imgIDecoder
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_IMGIDECODER
 
   nsGIFDecoder2();
   ~nsGIFDecoder2();
   
-  nsresult ProcessData(unsigned char *data, PRUint32 count, PRUint32 *_retval);
+  nsresult ProcessData(unsigned char *data, PRUint32 count);
+  static NS_METHOD ReadDataOut(nsIInputStream* in,
+                               void* closure,
+                               const char* fromRawSegment,
+                               PRUint32 toOffset,
+                               PRUint32 count,
+                               PRUint32 *writeCount);
 
 private:
   /* These functions will be called when the decoder has a decoded row,
    * frame size information, etc. */
 
   void      BeginGIF();
-  void      EndGIF();
+  void      EndGIF(PRBool aSuccess);
   nsresult  BeginImageFrame(gfx_depth aDepth);
   void      EndImageFrame();
   nsresult  FlushImageData();
   nsresult  FlushImageData(PRUint32 fromRow, PRUint32 rows);
 
   nsresult  GifWrite(const PRUint8 * buf, PRUint32 numbytes);
   PRUint32  OutputRow();
   PRBool    DoLzw(const PRUint8 *q);
 
   inline int ClearCode() const { return 1 << mGIFStruct.datasize; }
 
   nsCOMPtr<imgIContainer> mImageContainer;
-  nsCOMPtr<imgIDecoderObserver> mObserver; // this is just qi'd from mRequest for speed
+  nsCOMPtr<imgIDecoderObserver> mObserver;
+  PRUint32 mFlags;
   PRInt32 mCurrentRow;
   PRInt32 mLastFlushedRow;
 
   PRUint8 *mImageData;       // Pointer to image data in either Cairo or 8bit format
   PRUint32 *mColormap;       // Current colormap to be used in Cairo format
   PRUint32 mColormapSize;
   PRUint32 mOldColor;        // The old value of the transparent pixel
 
@@ -100,13 +108,14 @@ private:
   // of decoding it, and -1 otherwise.
   PRInt32 mCurrentFrame;
 
   PRUint8 mCurrentPass;
   PRUint8 mLastFlushedPass;
   PRUint8 mColorMask;        // Apply this to the pixel to keep within colormap
   PRPackedBool mGIFOpen;
   PRPackedBool mSawTransparency;
+  PRPackedBool mError;
 
   gif_struct mGIFStruct;
 };
 
 #endif
--- a/modules/libpr0n/decoders/icon/nsIconDecoder.cpp
+++ b/modules/libpr0n/decoders/icon/nsIconDecoder.cpp
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Scott MacGregor <mscott@netscape.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -36,112 +37,232 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsIconDecoder.h"
 #include "nsIInputStream.h"
 #include "imgIContainer.h"
 #include "imgIContainerObserver.h"
-#include "imgILoad.h"
 #include "nspr.h"
 #include "nsIComponentManager.h"
 #include "nsRect.h"
 #include "nsComponentManagerUtils.h"
 
 #include "nsIInterfaceRequestorUtils.h"
+#include "ImageErrors.h"
 
 NS_IMPL_THREADSAFE_ADDREF(nsIconDecoder)
 NS_IMPL_THREADSAFE_RELEASE(nsIconDecoder)
 
 NS_INTERFACE_MAP_BEGIN(nsIconDecoder)
    NS_INTERFACE_MAP_ENTRY(imgIDecoder)
 NS_INTERFACE_MAP_END_THREADSAFE
 
-nsIconDecoder::nsIconDecoder()
+
+nsIconDecoder::nsIconDecoder() :
+  mImage(nsnull),
+  mObserver(nsnull),
+  mFlags(imgIDecoder::DECODER_FLAG_NONE),
+  mWidth(-1),
+  mHeight(-1),
+  mPixBytesRead(0),
+  mPixBytesTotal(0),
+  mImageData(nsnull),
+  mState(iconStateStart),
+  mNotifiedDone(PR_FALSE)
 {
+  // Nothing to do
 }
 
 nsIconDecoder::~nsIconDecoder()
 { }
 
 
 /** imgIDecoder methods **/
 
-NS_IMETHODIMP nsIconDecoder::Init(imgILoad *aLoad)
+NS_IMETHODIMP nsIconDecoder::Init(imgIContainer *aImage,
+                                  imgIDecoderObserver *aObserver,
+                                  PRUint32 aFlags)
 {
-  mObserver = do_QueryInterface(aLoad);  // we're holding 2 strong refs to the request.
 
-  mImage = do_CreateInstance("@mozilla.org/image/container;2");
-  if (!mImage) return NS_ERROR_OUT_OF_MEMORY;
+  // Grab parameters
+  mImage = aImage;
+  mObserver = aObserver;
+  mFlags = aFlags;
 
-  aLoad->SetImage(mImage);                                                   
+  // Fire OnStartDecode at init time to support bug 512435
+  if (!(mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) && mObserver)
+    mObserver->OnStartDecode(nsnull);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP nsIconDecoder::Close()
+NS_IMETHODIMP nsIconDecoder::Close(PRUint32 aFlags)
 {
-  mImage->DecodingComplete();
+  // If we haven't notified of completion yet for a full/success decode, we
+  // didn't finish. Notify in error mode
+  if (!(aFlags & CLOSE_FLAG_DONTNOTIFY) &&
+      !(mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) &&
+      !mNotifiedDone)
+    NotifyDone(/* aSuccess = */ PR_FALSE);
 
-  if (mObserver) 
-  {
-    mObserver->OnStopFrame(nsnull, 0);
-    mObserver->OnStopContainer(nsnull, mImage);
-    mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
-  }
-  
+  mImage = nsnull;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsIconDecoder::Flush()
 {
   return NS_OK;
 }
 
-NS_IMETHODIMP nsIconDecoder::WriteFrom(nsIInputStream *inStr, PRUint32 count, PRUint32 *_retval)
+static nsresult
+WriteIconData(nsIInputStream *aInStream, void *aClosure, const char *aFromSegment,
+              PRUint32 aToOffset, PRUint32 aCount, PRUint32 *aWriteCount)
 {
-  // read the header from the input stram...
-  PRUint32 readLen;
-  PRUint8 header[2];
-  nsresult rv = inStr->Read((char*)header, 2, &readLen);
-  NS_ENSURE_TRUE(readLen == 2, NS_ERROR_UNEXPECTED); // w, h
-  count -= 2;
+  nsresult rv;
+
+  // We always read everything
+  *aWriteCount = aCount;
+
+  // We put this here to avoid errors about crossing initialization with case
+  // jumps on linux.
+  PRUint32 bytesToRead = 0;
+
+  // Grab the parameters
+  nsIconDecoder *decoder = static_cast<nsIconDecoder*>(aClosure);
+
+  // Performance isn't critical here, so our update rectangle is 
+  // always the full icon
+  nsIntRect r(0, 0, decoder->mWidth, decoder->mHeight);
+
+  // Loop until the input data is gone
+  while (aCount > 0) {
+    switch (decoder->mState) {
+      case iconStateStart:
 
-  PRInt32 w = header[0];
-  PRInt32 h = header[1];
-  NS_ENSURE_TRUE(w > 0 && h > 0, NS_ERROR_UNEXPECTED);
+        // Grab the width
+        decoder->mWidth = (PRUint8)*aFromSegment;
+
+        // Book Keeping
+        aFromSegment++;
+        aCount--;
+        decoder->mState = iconStateHaveHeight;
+        break;
+
+      case iconStateHaveHeight:
 
-  if (mObserver)
-    mObserver->OnStartDecode(nsnull);
-  mImage->Init(w, h, mObserver);
-  if (mObserver)
-    mObserver->OnStartContainer(nsnull, mImage);
+        // Grab the Height
+        decoder->mHeight = (PRUint8)*aFromSegment;
 
-  PRUint32 imageLen;
-  PRUint8 *imageData;
+        // Set up the container and signal
+        decoder->mImage->SetSize(decoder->mWidth,
+                                 decoder->mHeight);
+        if (decoder->mObserver)
+          decoder->mObserver->OnStartContainer(nsnull, decoder->mImage);
+
+        // If We're doing a header-only decode, we're done
+        if (decoder->mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) {
+          decoder->mState = iconStateFinished;
+          break;
+        }
 
-  rv = mImage->AppendFrame(0, 0, w, h, gfxASurface::ImageFormatARGB32, &imageData, &imageLen);
-  if (NS_FAILED(rv))
-    return rv;
+        // Add the frame and signal
+        rv = decoder->mImage->AppendFrame(0, 0,
+                                          decoder->mWidth,
+                                          decoder->mHeight,
+                                          gfxASurface::ImageFormatARGB32,
+                                          &decoder->mImageData, 
+                                          &decoder->mPixBytesTotal);
+        if (NS_FAILED(rv)) {
+          decoder->mState = iconStateError;
+          return rv;
+        }
+        if (decoder->mObserver)
+          decoder->mObserver->OnStartFrame(nsnull, 0);
 
-  if (mObserver)
-    mObserver->OnStartFrame(nsnull, 0);
+        // Book Keeping
+        aFromSegment++;
+        aCount--;
+        decoder->mState = iconStateReadPixels;
+        break;
+
+      case iconStateReadPixels:
 
-  // Ensure that there enough in the inputStream
-  NS_ENSURE_TRUE(count >= imageLen, NS_ERROR_UNEXPECTED);
+        // How many bytes are we reading?
+        bytesToRead = PR_MAX(aCount,
+                             decoder->mPixBytesTotal - decoder->mPixBytesRead);
+
+        // Copy the bytes
+        memcpy(decoder->mImageData + decoder->mPixBytesRead,
+               aFromSegment, bytesToRead);
 
-  // Read the image data direct into the frame data
-  rv = inStr->Read((char*)imageData, imageLen, &readLen);
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(readLen == imageLen, NS_ERROR_UNEXPECTED);
+        // Notify
+        rv = decoder->mImage->FrameUpdated(0, r);
+        if (NS_FAILED(rv)) {
+          decoder->mState = iconStateError;
+          return rv;
+        }
+        if (decoder->mObserver)
+          decoder->mObserver->OnDataAvailable(nsnull, PR_TRUE, &r);
+
+        // Book Keeping
+        aFromSegment += bytesToRead;
+        aCount -= bytesToRead;
+        decoder->mPixBytesRead += bytesToRead;
 
-  // Notify the image...
-  nsIntRect r(0, 0, w, h);
-  rv = mImage->FrameUpdated(0, r);
-  if (NS_FAILED(rv))
-    return rv;
+        // If we've got all the pixel bytes, we're finished
+        if (decoder->mPixBytesRead == decoder->mPixBytesTotal) {
+          decoder->NotifyDone(/* aSuccess = */ PR_TRUE);
+          decoder->mState = iconStateFinished;
+        }
+        break;
+
+      case iconStateFinished:
 
-  mObserver->OnDataAvailable(nsnull, PR_TRUE, &r);
+        // Consume all excess data silently
+        aCount = 0;
+
+        break;
+
+      case iconStateError:
+        return NS_IMAGELIB_ERROR_FAILURE;
+        break;
+    }
+  }
 
   return NS_OK;
 }
 
+void
+nsIconDecoder::NotifyDone(PRBool aSuccess)
+{
+  // We should only call this once
+  NS_ABORT_IF_FALSE(!mNotifiedDone, "Calling NotifyDone twice");
+
+  // Notify
+  if (mObserver)
+    mObserver->OnStopFrame(nsnull, 0);
+  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;
+}
+
+
+NS_IMETHODIMP nsIconDecoder::WriteFrom(nsIInputStream *inStr, PRUint32 count)
+{
+  // Decode, watching for errors.
+  nsresult rv = NS_OK;
+  PRUint32 ignored;
+  if (mState != iconStateError)
+    rv = inStr->ReadSegments(WriteIconData, this, count, &ignored);
+  if ((mState == iconStateError) || NS_FAILED(rv))
+    return NS_ERROR_FAILURE;
+  return NS_OK;
+}
+
--- a/modules/libpr0n/decoders/icon/nsIconDecoder.h
+++ b/modules/libpr0n/decoders/icon/nsIconDecoder.h
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Scott MacGregor <mscott@netscape.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -79,14 +80,32 @@ class nsIconDecoder : public imgIDecoder
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_IMGIDECODER
 
   nsIconDecoder();
   virtual ~nsIconDecoder();
 
-private:
   nsCOMPtr<imgIContainer> mImage;
-  nsCOMPtr<imgIDecoderObserver> mObserver; // this is just qi'd from mRequest for speed
+  nsCOMPtr<imgIDecoderObserver> mObserver;
+  PRUint32 mFlags;
+  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,
+  iconStateError      = 4
+};
+
+
 #endif // nsIconDecoder_h__
--- a/modules/libpr0n/decoders/icon/nsIconModule.cpp
+++ b/modules/libpr0n/decoders/icon/nsIconModule.cpp
@@ -92,17 +92,17 @@ static NS_METHOD IconDecoderUnregisterPr
 
 #endif
 
 static const nsModuleComponentInfo components[] =
 {
 #ifdef USE_ICON_DECODER
   { "icon decoder",
     NS_ICONDECODER_CID,
-    "@mozilla.org/image/decoder;2?type=image/icon",
+    "@mozilla.org/image/decoder;3?type=image/icon",
     nsIconDecoderConstructor,
     IconDecoderRegisterProc,
     IconDecoderUnregisterProc, },
 #endif
 
    { "Icon Protocol Handler",      
       NS_ICONPROTOCOL_CID,
       NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-icon",
--- a/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.cpp
+++ b/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.cpp
@@ -18,16 +18,17 @@
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Stuart Parmenter <stuart@mozilla.com>
  *   Federico Mena-Quintero <federico@novell.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -94,17 +95,18 @@ static void cmyk_convert_rgb(JSAMPROW ro
 /* 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;
-  mError = NS_OK;
+  mNotifiedDone = PR_FALSE;
+  mError = 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;
@@ -132,21 +134,32 @@ nsJPEGDecoder::~nsJPEGDecoder()
   PR_LOG(gJPEGDecoderAccountingLog, PR_LOG_DEBUG,
          ("nsJPEGDecoder::~nsJPEGDecoder: Destroying JPEG decoder %p",
           this));
 }
 
 
 /** imgIDecoder methods **/
 
-/* void init (in imgILoad aLoad); */
-NS_IMETHODIMP nsJPEGDecoder::Init(imgILoad *aLoad)
+/* void init (in imgIContainer aImage, 
+              in imgIDecoderObserver aObserver,
+              in unsigned long aFlags); */
+NS_IMETHODIMP nsJPEGDecoder::Init(imgIContainer *aImage, 
+                                  imgIDecoderObserver *aObserver,
+                                  PRUint32 aFlags)
 {
-  mImageLoad = aLoad;
-  mObserver = do_QueryInterface(aLoad);
+
+  /* Grab the parameters. */
+  mImage = aImage;
+  mObserver = aObserver;
+  mFlags = aFlags;
+
+  /* Fire OnStartDecode at init time to support bug 512435 */
+  if (!(mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) && mObserver)
+    mObserver->OnStartDecode(nsnull);
 
   /* We set up the normal JPEG error routines, then override error_exit. */
   mInfo.err = jpeg_std_error(&mErr.pub);
   /*   mInfo.err = jpeg_std_error(&mErr.pub); */
   mErr.pub.error_exit = my_error_exit;
   /* Establish the setjmp return context for my_error_exit to use. */
   if (setjmp(mErr.setjmp_buffer)) {
     /* If we get here, the JPEG code has signaled an error.
@@ -168,139 +181,102 @@ NS_IMETHODIMP nsJPEGDecoder::Init(imgILo
   mSourceMgr.skip_input_data = skip_input_data;
   mSourceMgr.resync_to_restart = jpeg_resync_to_restart;
   mSourceMgr.term_source = term_source;
 
   /* Record app markers for ICC data */
   for (PRUint32 m = 0; m < 16; m++)
     jpeg_save_markers(&mInfo, JPEG_APP0 + m, 0xFFFF);
 
-
-
-  /* Check if the request already has an image container.
-   * this is the case when multipart/x-mixed-replace is being downloaded
-   * if we already have one and it has the same width and height, reuse it.
-   * This is also the case when an existing container is reloading itself from
-   * us.
-   *
-   * If we have a mismatch in width/height for the container later on we will
-   * generate an error.
-   */
-  mImageLoad->GetImage(getter_AddRefs(mImage));
-
-  if (!mImage) {
-    mImage = do_CreateInstance("@mozilla.org/image/container;2");
-    if (!mImage)
-      return NS_ERROR_OUT_OF_MEMORY;
-      
-    mImageLoad->SetImage(mImage);
-    nsresult result = mImage->SetDiscardable("image/jpeg");
-    if (NS_FAILED(result)) {
-      mState = JPEG_ERROR;
-      PR_LOG(gJPEGDecoderAccountingLog, PR_LOG_DEBUG,
-             (" (could not set image container to discardable)"));
-      return result;
-    }
-  }
-
   return NS_OK;
 }
 
 
 /* void close (); */
-NS_IMETHODIMP nsJPEGDecoder::Close()
+NS_IMETHODIMP nsJPEGDecoder::Close(PRUint32 aFlags)
 {
   PR_LOG(gJPEGlog, PR_LOG_DEBUG,
          ("[this=%p] nsJPEGDecoder::Close\n", this));
 
   /* Step 8: Release JPEG decompression object */
   mInfo.src = nsnull;
 
   jpeg_destroy_decompress(&mInfo);
 
-  if (mState != JPEG_DONE && mState != JPEG_SINK_NON_JPEG_TRAILER) {
-    NS_WARNING("Never finished decoding the JPEG.");
-    /* Tell imgLoader that image decoding has failed */
-    return NS_ERROR_FAILURE;
-  }
+  /* If we already know we're in an error state, don't
+     bother flagging another one here. */
+  if (mError)
+    return NS_OK;
 
+  /* 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 (!(aFlags & CLOSE_FLAG_DONTNOTIFY) &&
+      !(mFlags && imgIDecoder::DECODER_FLAG_HEADERONLY) &&
+      !mNotifiedDone)
+    NotifyDone(/* aSuccess = */ PR_FALSE);
+
+  /* Otherwise, no problems. */
   return NS_OK;
 }
 
 /* void flush (); */
 NS_IMETHODIMP nsJPEGDecoder::Flush()
 {
   LOG_SCOPE(gJPEGlog, "nsJPEGDecoder::Flush");
 
-  PRUint32 ret;
   if (mState != JPEG_DONE && mState != JPEG_SINK_NON_JPEG_TRAILER && mState != JPEG_ERROR)
-    return this->ProcessData(nsnull, 0, &ret);
+    return this->ProcessData(nsnull, 0);
 
   return NS_OK;
 }
 
 static NS_METHOD ReadDataOut(nsIInputStream* in,
                              void* closure,
                              const char* fromRawSegment,
                              PRUint32 toOffset,
                              PRUint32 count,
                              PRUint32 *writeCount)
 {
   nsJPEGDecoder *decoder = static_cast<nsJPEGDecoder*>(closure);
-  nsresult rv = decoder->ProcessData(fromRawSegment, count, writeCount);
-  if (NS_FAILED(rv)) {
-    /* Tell imgLoader that image decoding has failed */
+
+  // We always read everything
+  *writeCount = count;
+
+  // Process some data
+  nsresult rv = decoder->ProcessData(fromRawSegment, count);
+
+  // Necko's error propagation is dubious - use an explicit flag
+  if (NS_FAILED(rv))
     decoder->mError = rv;
-    *writeCount = 0;
-  }
-
-  return NS_OK;
+  return rv;
 }
 
 
-/* unsigned long writeFrom (in nsIInputStream inStr, in unsigned long count); */
-NS_IMETHODIMP nsJPEGDecoder::WriteFrom(nsIInputStream *inStr, PRUint32 count, PRUint32 *writeCount)
+/* void writeFrom (in nsIInputStream inStr, in unsigned long count); */
+NS_IMETHODIMP nsJPEGDecoder::WriteFrom(nsIInputStream *inStr, PRUint32 count)
 {
   NS_ENSURE_ARG_POINTER(inStr);
-  NS_ENSURE_ARG_POINTER(writeCount);
 
-  /* necko doesn't propagate the errors from ReadDataOut */
-  nsresult rv = inStr->ReadSegments(ReadDataOut, this, count, writeCount);
-  if (NS_FAILED(mError)) {
-    /* Tell imgLoader that image decoding has failed */
-    rv = NS_ERROR_FAILURE;
-  }
-
-  return rv;
+  // Decode, watching for errors
+  nsresult rv = NS_OK;
+  PRUint32 ignored;
+  if (!mError)
+    rv = inStr->ReadSegments(ReadDataOut, this, count, &ignored);
+  if (mError || NS_FAILED(rv))
+    return NS_ERROR_FAILURE;
+  return NS_OK;
 }
 
 //******************************************************************************
-nsresult nsJPEGDecoder::ProcessData(const char *data, PRUint32 count, PRUint32 *writeCount)
+nsresult nsJPEGDecoder::ProcessData(const char *data, PRUint32 count)
 {
   LOG_SCOPE_WITH_PARAM(gJPEGlog, "nsJPEGDecoder::ProcessData", "count", count);
 
   mSegment = (const JOCTET *)data;
   mSegmentLen = count;
-  *writeCount = count;
-
-  if (data && count) {
-    nsresult result = mImage->AddRestoreData((char *) data, count);
-
-    if (NS_FAILED(result)) {
-      mState = JPEG_ERROR;
-      PR_LOG(gJPEGDecoderAccountingLog, PR_LOG_DEBUG,
-             ("} (could not add restore data)"));
-      return result;
-    }
-
-    PR_LOG(gJPEGDecoderAccountingLog, PR_LOG_DEBUG,
-           ("        added %u bytes to restore data",
-            count));
-  }
-  // else no input stream.. Flush() ?
 
   /* Return here if there is a fatal error. */
   nsresult error_code;
   if ((error_code = setjmp(mErr.setjmp_buffer)) != 0) {
     mState = JPEG_SINK_NON_JPEG_TRAILER;
     if (error_code == NS_ERROR_FAILURE) {
       /* Error due to corrupt stream - return NS_OK so that libpr0n
          doesn't throw away a partial image load */
@@ -327,16 +303,26 @@ nsresult nsJPEGDecoder::ProcessData(cons
 
     /* Step 3: read file parameters with jpeg_read_header() */
     if (jpeg_read_header(&mInfo, TRUE) == JPEG_SUSPENDED) {
       PR_LOG(gJPEGDecoderAccountingLog, PR_LOG_DEBUG,
              ("} (JPEG_SUSPENDED)"));
       return NS_OK; /* I/O suspension */
     }
 
+    /* Set Width and height, and notify that the container is ready to go. */
+    mImage->SetSize(mInfo.image_width, mInfo.image_height);
+    if (mObserver)
+      mObserver->OnStartContainer(nsnull, mImage);
+
+    /* If we're doing a header-only decode, we're done. */
+    if (mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY)
+      return NS_OK;
+
+    /* We're doing a full decode. */
     JOCTET  *profile;
     PRUint32 profileLength;
     eCMSMode cmsMode = gfxPlatform::GetCMSMode();
 
     if ((cmsMode != eCMSMode_Off) &&
         read_icc_profile(&mInfo, &profile, &profileLength) &&
         (mInProfile = qcms_profile_from_memory(profile, profileLength)) != NULL) {
       free(profile);
@@ -448,35 +434,16 @@ nsresult nsJPEGDecoder::ProcessData(cons
      * Don't allocate a giant and superfluous memory buffer
      * when the image is a sequential JPEG.
      */
     mInfo.buffered_image = jpeg_has_multiple_scans(&mInfo);
 
     /* Used to set up image size so arrays can be allocated */
     jpeg_calc_output_dimensions(&mInfo);
 
-    mObserver->OnStartDecode(nsnull);
-
-    /* verify that the width and height of the image are the same as
-     * the container we're about to put things in to.
-     * XXX it might not matter maybe we should just resize the image.
-     */
-    PRInt32 width, height;
-    mImage->GetWidth(&width);
-    mImage->GetHeight(&height);
-    if (width == 0 && height == 0) {
-      mImage->Init(mInfo.image_width, mInfo.image_height, mObserver);
-    } else if ((width != (PRInt32)mInfo.image_width) || (height != (PRInt32)mInfo.image_height)) {
-      mState = JPEG_ERROR;
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    mImage->Init(mInfo.image_width, mInfo.image_height, mObserver);
-
-    mObserver->OnStartContainer(nsnull, mImage);
 
     // Use EnsureCleanFrame so we don't create a new frame if we're being
     // reused for e.g. multipart/x-replace
     PRUint32 imagelength;
     if (NS_FAILED(mImage->EnsureCleanFrame(0, 0, 0, mInfo.image_width, mInfo.image_height,
                                            gfxASurface::ImageFormatRGB24,
                                            &mImageData, &imagelength))) {
       mState = JPEG_ERROR;
@@ -484,17 +451,18 @@ nsresult nsJPEGDecoder::ProcessData(cons
              ("} (could not initialize image frame)"));
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     PR_LOG(gJPEGDecoderAccountingLog, PR_LOG_DEBUG,
            ("        JPEGDecoderAccounting: nsJPEGDecoder::ProcessData -- created image frame with %ux%u pixels",
             mInfo.image_width, mInfo.image_height));
 
-    mObserver->OnStartFrame(nsnull, 0);
+    if (mObserver)
+      mObserver->OnStartFrame(nsnull, 0);
     mState = JPEG_START_DECOMPRESS;
   }
 
   case JPEG_START_DECOMPRESS:
   {
     LOG_SCOPE(gJPEGlog, "nsJPEGDecoder::ProcessData -- entering JPEG_START_DECOMPRESS case");
     /* Step 4: set parameters for decompression */
 
@@ -616,38 +584,28 @@ nsresult nsJPEGDecoder::ProcessData(cons
       }
 
       mState = JPEG_DONE;
     }
   }
 
   case JPEG_DONE:
   {
-    nsresult result;
-
     LOG_SCOPE(gJPEGlog, "nsJPEGDecoder::ProcessData -- entering JPEG_DONE case");
 
     /* Step 7: Finish decompression */
 
     if (jpeg_finish_decompress(&mInfo) == FALSE) {
       PR_LOG(gJPEGDecoderAccountingLog, PR_LOG_DEBUG,
              ("} (I/O suspension after jpeg_finish_decompress() - DONE)"));
       return NS_OK; /* I/O suspension */
     }
 
     mState = JPEG_SINK_NON_JPEG_TRAILER;
 
-    result = mImage->RestoreDataDone();
-    if (NS_FAILED (result)) {
-      mState = JPEG_ERROR;
-      PR_LOG(gJPEGDecoderAccountingLog, PR_LOG_DEBUG,
-             ("} (could not mark image container with RestoreDataDone)"));
-      return result;
-    }
-
     /* we're done dude */
     break;
   }
   case JPEG_SINK_NON_JPEG_TRAILER:
     PR_LOG(gJPEGlog, PR_LOG_DEBUG,
            ("[this=%p] nsJPEGDecoder::ProcessData -- entering JPEG_SINK_NON_JPEG_TRAILER case\n", this));
 
     break;
@@ -659,16 +617,36 @@ nsresult nsJPEGDecoder::ProcessData(cons
     break;
   }
 
   PR_LOG(gJPEGDecoderAccountingLog, PR_LOG_DEBUG,
          ("} (end of function)"));
   return NS_OK;
 }
 
+void
+nsJPEGDecoder::NotifyDone(PRBool aSuccess)
+{
+  // We should only be called once
+  NS_ABORT_IF_FALSE(!mNotifiedDone, "calling NotifyDone twice!");
+
+  // Notify
+  if (mObserver)
+    mObserver->OnStopFrame(nsnull, 0);
+  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;
+}
 
 nsresult
 nsJPEGDecoder::OutputScanlines(PRBool* suspend)
 {
   *suspend = PR_FALSE;
 
   const PRUint32 top = mInfo.output_scanline;
   nsresult rv = NS_OK;
@@ -754,17 +732,18 @@ nsJPEGDecoder::OutputScanlines(PRBool* s
         *imageRow++ = GFX_PACKED_PIXEL(0xFF, sampleRow[0], sampleRow[1], sampleRow[2]);
         sampleRow += 3;
       }
   }
 
   if (top != mInfo.output_scanline) {
       nsIntRect r(0, top, mInfo.output_width, mInfo.output_scanline-top);
       rv = mImage->FrameUpdated(0, r);
-      mObserver->OnDataAvailable(nsnull, PR_TRUE, &r);
+      if (mObserver)
+        mObserver->OnDataAvailable(nsnull, PR_TRUE, &r);
   }
 
   return rv;
 }
 
 
 /* Override the standard error method in the IJG JPEG decoder code. */
 METHODDEF(void)
@@ -971,27 +950,22 @@ fill_input_buffer (j_decompress_ptr jd)
  * data has been read to clean up JPEG source manager. NOT called by 
  * jpeg_abort() or jpeg_destroy().
  */
 METHODDEF(void)
 term_source (j_decompress_ptr jd)
 {
   nsJPEGDecoder *decoder = (nsJPEGDecoder *)(jd->client_data);
 
-  if (decoder->mObserver) {
-    decoder->mObserver->OnStopFrame(nsnull, 0);
-    decoder->mObserver->OnStopContainer(nsnull, decoder->mImage);
-    decoder->mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
-  }
+  // This function shouldn't be called if we ran into an error
+  NS_ABORT_IF_FALSE(!decoder->mError,
+                    "Calling term_source on a JPEG with mError=true!");
 
-  PRBool multipart = PR_FALSE;
-  if (decoder->mImageLoad) 
-      decoder->mImageLoad->GetIsMultiPartChannel(&multipart);
-  if (!multipart)
-    decoder->mImage->DecodingComplete();
+  // Notify
+  decoder->NotifyDone(/* aSuccess = */ PR_TRUE);
 }
 
 
 /**************** YCbCr -> Cairo's RGB24/ARGB32 conversion: most common case **************/
 
 /*
  * YCbCr is defined per CCIR 601-1, except that Cb and Cr are
  * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5.
--- a/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.h
+++ b/modules/libpr0n/decoders/jpeg/nsJPEGDecoder.h
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Stuart Parmenter <pavlov@netscape.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -41,17 +42,16 @@
 #define nsJPEGDecoder_h__
 
 #include "imgIDecoder.h"
 
 #include "nsCOMPtr.h"
 
 #include "imgIContainer.h"
 #include "imgIDecoderObserver.h"
-#include "imgILoad.h"
 #include "nsIInputStream.h"
 #include "nsIPipe.h"
 #include "qcms.h"
 
 extern "C" {
 #include "jpeglib.h"
 }
 
@@ -85,33 +85,34 @@ class nsJPEGDecoder : public imgIDecoder
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_IMGIDECODER
 
   nsJPEGDecoder();
   virtual ~nsJPEGDecoder();
 
-  nsresult  ProcessData(const char *data, PRUint32 count, PRUint32 *writeCount);
+  nsresult  ProcessData(const char *data, PRUint32 count);
+  void NotifyDone(PRBool aSuccess);
 
 protected:
   nsresult OutputScanlines(PRBool* suspend);
 
 public:
   nsCOMPtr<imgIContainer> mImage;
-  nsCOMPtr<imgILoad> mImageLoad;
+  nsCOMPtr<imgIDecoderObserver> mObserver;
 
-  nsCOMPtr<imgIDecoderObserver> mObserver;
+  PRUint32 mFlags;
   PRUint8 *mImageData;
 
   struct jpeg_decompress_struct mInfo;
   struct jpeg_source_mgr mSourceMgr;
   decoder_error_mgr mErr;
   jstate mState;
-  nsresult mError;
+  PRBool mError;
 
   PRUint32 mBytesToSkip;
 
   const JOCTET *mSegment;   // The current segment we are decoding from
   PRUint32 mSegmentLen;     // amount of data in mSegment
 
   JOCTET *mBackBuffer;
   PRUint32 mBackBufferLen; // Offset of end of active backtrack data
@@ -120,11 +121,12 @@ public:
 
   JOCTET  *mProfile;
   PRUint32 mProfileLength;
 
   qcms_profile *mInProfile;
   qcms_transform *mTransform;
 
   PRPackedBool mReading;
+  PRPackedBool mNotifiedDone;
 };
 
 #endif // nsJPEGDecoder_h__
--- a/modules/libpr0n/decoders/png/nsPNGDecoder.cpp
+++ b/modules/libpr0n/decoders/png/nsPNGDecoder.cpp
@@ -19,16 +19,17 @@
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Stuart Parmenter <stuart@mozilla.com>
  *   Andrew Smith
  *   Federico Mena-Quintero <federico@novell.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -66,39 +67,57 @@ static void PNGAPI end_callback(png_stru
 static void PNGAPI error_callback(png_structp png_ptr, png_const_charp error_msg);
 static void PNGAPI warning_callback(png_structp png_ptr, png_const_charp warning_msg);
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo *gPNGLog = PR_NewLogModule("PNGDecoder");
 static PRLogModuleInfo *gPNGDecoderAccountingLog = PR_NewLogModule("PNGDecoderAccounting");
 #endif
 
+/* limit image dimensions (bug #251381) */
+#define MOZ_PNG_MAX_DIMENSION 1000000L
+
+// For header-only decodes
+#define WIDTH_OFFSET 16
+#define HEIGHT_OFFSET (WIDTH_OFFSET + 4)
+#define BYTES_NEEDED_FOR_DIMENSIONS (HEIGHT_OFFSET + 4)
+
+// This is defined in the PNG spec as an invariant. We use it to
+// do manual validation without libpng.
+static const PRUint8 pngSignatureBytes[] =
+               { 137, 80, 78, 71, 13, 10, 26, 10 };
+
+
 NS_IMPL_ISUPPORTS1(nsPNGDecoder, imgIDecoder)
 
 nsPNGDecoder::nsPNGDecoder() :
   mPNG(nsnull), mInfo(nsnull),
   mCMSLine(nsnull), interlacebuf(nsnull),
   mInProfile(nsnull), mTransform(nsnull),
-  mChannels(0), mError(PR_FALSE), mFrameIsHidden(PR_FALSE)
+  mHeaderBuf(nsnull), mHeaderBytesRead(0),
+  mChannels(0), mError(PR_FALSE), mFrameIsHidden(PR_FALSE),
+  mNotifiedDone(PR_FALSE)
 {
 }
 
 nsPNGDecoder::~nsPNGDecoder()
 {
   if (mCMSLine)
     nsMemory::Free(mCMSLine);
   if (interlacebuf)
     nsMemory::Free(interlacebuf);
   if (mInProfile) {
     qcms_profile_release(mInProfile);
 
     /* mTransform belongs to us only if mInProfile is non-null */
     if (mTransform)
       qcms_transform_release(mTransform);
   }
+  if (mHeaderBuf)
+    nsMemory::Free(mHeaderBuf);
 }
 
 // CreateFrame() is used for both simple and animated images
 void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset, 
                                PRInt32 width, PRInt32 height,
                                gfxASurface::gfxImageFormat format)
 {
   PRUint32 imageDataLength;
@@ -186,29 +205,34 @@ void nsPNGDecoder::EndImageFrame()
       mImage->SetFrameHasNoAlpha(numFrames - 1);
 
     if (NS_FAILED(mImage->FrameUpdated(numFrames - 1, mFrameRect))) {
       mError = PR_TRUE;
       // allow the call out to the observers.
     }
     PRUint32 curFrame;
     mImage->GetCurrentFrameIndex(&curFrame);
-    mObserver->OnDataAvailable(nsnull, curFrame == numFrames - 1, &mFrameRect);
+    if (mObserver)
+      mObserver->OnDataAvailable(nsnull, curFrame == numFrames - 1, &mFrameRect);
   }
 
   mImage->EndFrameDecode(numFrames - 1);
   if (mObserver)
     mObserver->OnStopFrame(nsnull, numFrames - 1);
 }
 
 
 /** imgIDecoder methods **/
 
-/* void init (in imgILoad aLoad); */
-NS_IMETHODIMP nsPNGDecoder::Init(imgILoad *aLoad)
+/* void init (in imgIContainer aImage, 
+              imgIDecoderObserver aObserver,
+              unsigned long aFlags); */
+NS_IMETHODIMP nsPNGDecoder::Init(imgIContainer *aImage,
+                                 imgIDecoderObserver *aObserver,
+                                 PRUint32 aFlags)
 {
 #if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED)
   static png_byte color_chunks[]=
        { 99,  72,  82,  77, '\0',   /* cHRM */
         105,  67,  67,  80, '\0'};  /* iCCP */
   static png_byte unused_chunks[]=
        { 98,  75,  71,  68, '\0',   /* bKGD */
         104,  73,  83,  84, '\0',   /* hIST */
@@ -219,20 +243,33 @@ NS_IMETHODIMP nsPNGDecoder::Init(imgILoa
         112,  72,  89, 115, '\0',   /* pHYs */
         115,  66,  73,  84, '\0',   /* sBIT */
         115,  80,  76,  84, '\0',   /* sPLT */
         116,  69,  88, 116, '\0',   /* tEXt */
         116,  73,  77,  69, '\0',   /* tIME */
         122,  84,  88, 116, '\0'};  /* zTXt */
 #endif
 
-  mImageLoad = aLoad;
-  mObserver = do_QueryInterface(aLoad);  // we're holding 2 strong refs to the request.
+  mImage = aImage;
+  mObserver = aObserver;
+  mFlags = aFlags;
+
+  // Fire OnStartDecode at init time to support bug 512435
+  if (!(mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) && mObserver)
+    mObserver->OnStartDecode(nsnull);
 
-  /* do png init stuff */
+  // For header-only decodes, we only need a small buffer
+  if (mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) {
+    mHeaderBuf = (PRUint8 *)nsMemory::Alloc(BYTES_NEEDED_FOR_DIMENSIONS);
+    if (!mHeaderBuf)
+      return NS_ERROR_OUT_OF_MEMORY;
+    return NS_OK;
+  }
+
+  /* For full decodes, do png init stuff */
 
   /* Initialize the container's source image header. */
   /* Always decode to 24 bit pixdepth */
 
   mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING, 
                                 NULL, error_callback, warning_callback);
   if (!mPNG) {
     return NS_ERROR_OUT_OF_MEMORY;
@@ -253,129 +290,169 @@ NS_IMETHODIMP nsPNGDecoder::Init(imgILoa
      (int)sizeof(unused_chunks)/5);   
 #endif
 
   /* use this as libpng "progressive pointer" (retrieve in callbacks) */
   png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this),
                               info_callback, row_callback, end_callback);
 
 
-  /* The image container may already exist if it is reloading itself from us.
-   * Check that it has the same width/height; otherwise create a new container.
-   */
-  mImageLoad->GetImage(getter_AddRefs(mImage));
-  if (!mImage) {
-    mImage = do_CreateInstance("@mozilla.org/image/container;2");
-    if (!mImage)
-      return NS_ERROR_OUT_OF_MEMORY;
-      
-    mImageLoad->SetImage(mImage);
-    if (NS_FAILED(mImage->SetDiscardable("image/png"))) {
-      PR_LOG(gPNGDecoderAccountingLog, PR_LOG_DEBUG,
-             ("PNGDecoderAccounting: info_callback(): failed to set image container %p as discardable",
-              mImage.get()));
-      return NS_ERROR_FAILURE;
-    }
-  }
-
   return NS_OK;
 }
 
 /* void close (); */
-NS_IMETHODIMP nsPNGDecoder::Close()
+NS_IMETHODIMP nsPNGDecoder::Close(PRUint32 aFlags)
 {
   if (mPNG)
     png_destroy_read_struct(&mPNG, mInfo ? &mInfo : NULL, NULL);
 
-  if (mImage) { // mImage could be null in the case of an error
-    nsresult result = mImage->RestoreDataDone();
-    if (NS_FAILED(result)) {
-        PR_LOG(gPNGDecoderAccountingLog, PR_LOG_DEBUG,
-            ("PNGDecoderAccounting: nsPNGDecoder::Close(): failure in RestoreDataDone() for image container %p",
-                mImage.get()));
+  // 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 (!(aFlags & CLOSE_FLAG_DONTNOTIFY) &&
+      !(mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) &&
+      !mNotifiedDone)
+    NotifyDone(/* aSuccess = */ PR_FALSE);
 
-        mError = PR_TRUE;
-        return result;
-    }
-
-    PR_LOG(gPNGDecoderAccountingLog, PR_LOG_DEBUG,
-            ("PNGDecoderAccounting: nsPNGDecoder::Close(): image container %p is now with RestoreDataDone",
-            mImage.get()));
-  }
+  mImage = nsnull;
   return NS_OK;
 }
 
 /* void flush (); */
 NS_IMETHODIMP nsPNGDecoder::Flush()
 {
   return NS_OK;
 }
 
+// We make this a method to get the benefit of the 'this' parameter
+NS_METHOD
+nsPNGDecoder::ProcessData(unsigned char* aBuffer, PRUint32 aCount)
+{
+  // We use gotos, so we need to declare variables here
+  nsresult rv;
+  PRUint32 width = 0;
+  PRUint32 height = 0;
+
+  // No forgiveness if we previously hit an error
+  if (mError)
+    goto error;
+
+  // If we only want width/height, we don't need to go through libpng
+  if (mFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) {
+
+    // Are we done?
+    if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS)
+      return NS_OK;
+
+    // Read data into our header buffer
+    PRUint32 bytesToRead = PR_MIN(aCount, BYTES_NEEDED_FOR_DIMENSIONS - mHeaderBytesRead);
+    memcpy(mHeaderBuf + mHeaderBytesRead, aBuffer, bytesToRead);
+    mHeaderBytesRead += bytesToRead;
+
+    // If we're done now, verify the data and set up the container
+    if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) {
+
+      // Check that the signature bytes are right
+      if (memcmp(mHeaderBuf, pngSignatureBytes, sizeof(pngSignatureBytes)))
+        goto error;
+
+      // Grab the width and height, accounting for endianness (thanks libpng!)
+      width = png_get_uint_32(mHeaderBuf + WIDTH_OFFSET);
+      height = png_get_uint_32(mHeaderBuf + HEIGHT_OFFSET);
+
+      // Too big?
+      if ((width > MOZ_PNG_MAX_DIMENSION) || (height > MOZ_PNG_MAX_DIMENSION))
+        goto error;
+
+      // Set the size
+      rv = mImage->SetSize(width, height);
+      if (NS_FAILED(rv))
+        goto error;
+
+      // Notify the observer that the container is up
+      if (mObserver)
+        mObserver->OnStartContainer(nsnull, mImage);
+    }
+  }
+
+  // Otherwise, we're doing a standard decode
+  else {
+
+    // libpng uses setjmp/longjmp for error handling - set the buffer
+    if (setjmp(mPNG->jmpbuf)) {
+      png_destroy_read_struct(&mPNG, &mInfo, NULL);
+      goto error;
+    }
+
+    // Pass the data off to libpng
+    png_process_data(mPNG, mInfo, aBuffer, aCount);
+
+  }
+
+  return NS_OK;
+
+  // Consolidate error handling
+  error:
+  mError = PR_TRUE;
+  return NS_ERROR_FAILURE;
+}
+
+void
+nsPNGDecoder::NotifyDone(PRBool aSuccess)
+{
+  // We should only be called once
+  NS_ABORT_IF_FALSE(!mNotifiedDone, "Calling NotifyDone twice!");
+
+  // Notify
+  if (!mFrameIsHidden)
+    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;
+}
 
 static NS_METHOD ReadDataOut(nsIInputStream* in,
                              void* closure,
                              const char* fromRawSegment,
                              PRUint32 toOffset,
                              PRUint32 count,
                              PRUint32 *writeCount)
 {
+  // Grab the decoder
+  NS_ENSURE_ARG_POINTER(closure);
   nsPNGDecoder *decoder = static_cast<nsPNGDecoder*>(closure);
 
-  if (decoder->mError) {
-    *writeCount = 0;
-    return NS_ERROR_FAILURE;
-  }
-
-  // we force to add even erroneous data to restore halfway frame information
-  // later - bug 441563
-  nsresult result = decoder->mImage->AddRestoreData(const_cast<char *>(fromRawSegment), count);
-  if (NS_FAILED (result)) {
-    PR_LOG(gPNGDecoderAccountingLog, PR_LOG_DEBUG,
-           ("PNGDecoderAccounting: ReadDataOut(): failed to add restore data to image container %p",
-            decoder->mImage.get()));
-
-    decoder->mError = PR_TRUE;
-    *writeCount = 0;
-    return result;
-  }
+  // We always read everything
+  *writeCount = count;
 
-  // we need to do the setjmp here otherwise bad things will happen
-  if (setjmp(decoder->mPNG->jmpbuf)) {
-    png_destroy_read_struct(&decoder->mPNG, &decoder->mInfo, NULL);
-
-    decoder->mError = PR_TRUE;
-    *writeCount = 0;
-    return NS_ERROR_FAILURE;
-  }
-  png_process_data(decoder->mPNG, decoder->mInfo,
-                   reinterpret_cast<unsigned char *>(const_cast<char *>(fromRawSegment)), count);
-
-  PR_LOG(gPNGDecoderAccountingLog, PR_LOG_DEBUG,
-         ("PNGDecoderAccounting: ReadDataOut(): Added restore data to image container %p",
-          decoder->mImage.get()));
-
-  *writeCount = count;
-  return NS_OK;
+  // Twiddle the types, then process the data
+  char *unConst = const_cast<char *>(fromRawSegment);
+  unsigned char *buffer = reinterpret_cast<unsigned char *>(unConst);
+  return decoder->ProcessData(buffer, count);
 }
 
 
-/* unsigned long writeFrom (in nsIInputStream inStr, in unsigned long count); */
-NS_IMETHODIMP nsPNGDecoder::WriteFrom(nsIInputStream *inStr, PRUint32 count, PRUint32 *_retval)
+/* writeFrom (in nsIInputStream inStr, in unsigned long count); */
+NS_IMETHODIMP nsPNGDecoder::WriteFrom(nsIInputStream *inStr, PRUint32 count)
 {
   NS_ASSERTION(inStr, "Got a null input stream!");
 
-  nsresult rv;
-
+  // Decode, watching for errors
+  nsresult rv = NS_OK;
+  PRUint32 ignored;
   if (!mError)
-    rv = inStr->ReadSegments(ReadDataOut, this, count, _retval);
-
-  if (mError) {
-    *_retval = 0;
+    rv = inStr->ReadSegments(ReadDataOut, this, count, &ignored);
+  if (mError || NS_FAILED(rv))
     rv = NS_ERROR_FAILURE;
-  }
 
   return rv;
 }
 
 // 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)
@@ -500,26 +577,33 @@ info_callback(png_structp png_ptr, png_i
   png_uint_32 width, height;
   int bit_depth, color_type, interlace_type, compression_type, filter_type;
   unsigned int channels;
 
   png_bytep trans = NULL;
   int num_trans = 0;
 
   nsPNGDecoder *decoder = static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
+  nsresult rv;
 
   /* always decode to 24-bit RGB or 32-bit RGBA  */
   png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
                &interlace_type, &compression_type, &filter_type);
   
-  /* limit image dimensions (bug #251381) */
-#define MOZ_PNG_MAX_DIMENSION 1000000L
+  /* Are we too big? */
   if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION)
     longjmp(decoder->mPNG->jmpbuf, 1);
-#undef MOZ_PNG_MAX_DIMENSION
+
+  // Set the size and notify that the container is set up
+  rv = decoder->mImage->SetSize(width, height);
+  if (NS_FAILED(rv)) {
+    longjmp(decoder->mPNG->jmpbuf, 5); // NS_ERROR_UNEXPECTED
+  }
+  if (decoder->mObserver)
+    decoder->mObserver->OnStartContainer(nsnull, decoder->mImage);
 
   if (color_type == PNG_COLOR_TYPE_PALETTE)
     png_set_expand(png_ptr);
 
   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
     png_set_expand(png_ptr);
 
   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
@@ -611,35 +695,16 @@ info_callback(png_structp png_ptr, png_i
           }
         }
       }
     } else {
       alpha_bits = 8;
     }
   }
 
-  if (decoder->mObserver)
-    decoder->mObserver->OnStartDecode(nsnull);
-
-  /* The image container may already exist if it is reloading itself from us.
-   * Check that it has the same width/height; otherwise create a new container.
-   */
-  PRInt32 containerWidth, containerHeight;
-  decoder->mImage->GetWidth(&containerWidth);
-  decoder->mImage->GetHeight(&containerHeight);
-  if (containerWidth == 0 && containerHeight == 0) {
-    // the image hasn't been inited yet
-    decoder->mImage->Init(width, height, decoder->mObserver);
-  } else if (containerWidth != PRInt32(width) || containerHeight != PRInt32(height)) {
-    longjmp(decoder->mPNG->jmpbuf, 5); // NS_ERROR_UNEXPECTED
-  }
-
-  if (decoder->mObserver)
-    decoder->mObserver->OnStartContainer(nsnull, decoder->mImage);
-
   if (channels == 1 || channels == 3)
     decoder->format = gfxASurface::ImageFormatRGB24;
   else if (channels == 2 || channels == 4)
     decoder->format = gfxASurface::ImageFormatARGB32;
 
   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL))
     png_set_progressive_frame_fn(png_ptr, frame_info_callback, NULL);
   
@@ -649,18 +714,19 @@ info_callback(png_structp png_ptr, png_i
     decoder->CreateFrame(0, 0, width, height, decoder->format);
   }
   
   if (decoder->mTransform &&
       (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) {
     PRUint32 bpp[] = { 0, 3, 4, 3, 4 };
     decoder->mCMSLine =
       (PRUint8 *)nsMemory::Alloc(bpp[channels] * width);
-    if (!decoder->mCMSLine)
+    if (!decoder->mCMSLine) {
       longjmp(decoder->mPNG->jmpbuf, 5); // NS_ERROR_OUT_OF_MEMORY
+    }
   }
 
   if (interlace_type == PNG_INTERLACE_ADAM7) {
     if (height < PR_INT32_MAX / (width * channels))
       decoder->interlacebuf = (PRUint8 *)nsMemory::Alloc(channels * width * height);
     if (!decoder->interlacebuf) {
       longjmp(decoder->mPNG->jmpbuf, 5); // NS_ERROR_OUT_OF_MEMORY
     }
@@ -795,17 +861,18 @@ row_callback(png_structp png_ptr, png_by
       // Only do incremental image display for the first frame
       nsIntRect r(0, row_num, width, 1);
       if (NS_FAILED(decoder->mImage->FrameUpdated(numFrames - 1, r))) {
         decoder->mError = PR_TRUE;  /* bail */
         return;
       }
       PRUint32 curFrame;
       decoder->mImage->GetCurrentFrameIndex(&curFrame);
-      decoder->mObserver->OnDataAvailable(nsnull, curFrame == numFrames - 1, &r);
+      if (decoder->mObserver)
+        decoder->mObserver->OnDataAvailable(nsnull, curFrame == numFrames - 1, &r);
     }
   }
 }
 
 // got the header of a new frame that's coming
 void
 frame_info_callback(png_structp png_ptr, png_uint_32 frame_num)
 {
@@ -839,31 +906,27 @@ end_callback(png_structp png_ptr, png_in
    * had in the header, although some data may have been added
    * to the comments and time fields.
    *
    * Most people won't do much here, perhaps setting a flag that
    * marks the image as finished.
    */
 
   nsPNGDecoder *decoder = static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
+
+  // We shouldn't get here if we've hit an error
+  NS_ABORT_IF_FALSE(!decoder->mError, "Finishing up PNG but hit error!");
   
   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);
   }
-  
-  if (!decoder->mFrameIsHidden)
-    decoder->EndImageFrame();
-  
-  decoder->mImage->DecodingComplete();
 
-  if (decoder->mObserver) {
-    decoder->mObserver->OnStopContainer(nsnull, decoder->mImage);
-    decoder->mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
-  }
+  // Send final notifications
+  decoder->NotifyDone(/* aSuccess = */ PR_TRUE);
 }
 
 
 void
 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_ptr->jmpbuf, 1);
--- a/modules/libpr0n/decoders/png/nsPNGDecoder.h
+++ b/modules/libpr0n/decoders/png/nsPNGDecoder.h
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Stuart Parmenter <pavlov@netscape.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -39,17 +40,16 @@
 
 #ifndef nsPNGDecoder_h__
 #define nsPNGDecoder_h__
 
 #include "imgIDecoder.h"
 
 #include "imgIContainer.h"
 #include "imgIDecoderObserver.h"
-#include "imgILoad.h"
 #include "gfxASurface.h"
 
 #include "nsCOMPtr.h"
 
 #include "png.h"
 
 #include "qcms.h"
 
@@ -71,31 +71,39 @@ public:
   virtual ~nsPNGDecoder();
 
   void CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset, 
                    PRInt32 width, PRInt32 height, 
                    gfxASurface::gfxImageFormat format);
   void SetAnimFrameInfo();
   
   void EndImageFrame();
+  NS_METHOD ProcessData(unsigned char* aBuffer, PRUint32 aCount);
+  void NotifyDone(PRBool aSuccess);
 
 public:
   nsCOMPtr<imgIContainer> mImage;
-  nsCOMPtr<imgILoad> mImageLoad;
-  nsCOMPtr<imgIDecoderObserver> mObserver; // this is just qi'd from mRequest for speed
+  nsCOMPtr<imgIDecoderObserver> mObserver;
+  PRUint32 mFlags;
 
   png_structp mPNG;
   png_infop mInfo;
   nsIntRect mFrameRect;
   PRUint8 *mCMSLine;
   PRUint8 *interlacebuf;
   PRUint8 *mImageData;
   qcms_profile *mInProfile;
   qcms_transform *mTransform;
 
   gfxASurface::gfxImageFormat format;
+
+  // For header-only decodes
+  PRUint8 *mHeaderBuf;
+  PRUint32 mHeaderBytesRead;
+
   PRUint8 mChannels;
   PRPackedBool mError;
   PRPackedBool mFrameHasNoAlpha;
   PRPackedBool mFrameIsHidden;
+  PRPackedBool mNotifiedDone;
 };
 
 #endif // nsPNGDecoder_h__
--- a/modules/libpr0n/public/Makefile.in
+++ b/modules/libpr0n/public/Makefile.in
@@ -49,16 +49,15 @@ EXPORTS		= ImageErrors.h ImageLogging.h
 
 XPIDLSRCS	= \
 		imgICache.idl             \
 		imgIContainer.idl         \
 		imgIContainerObserver.idl \
 		imgIDecoder.idl           \
 		imgIDecoderObserver.idl   \
 		imgIEncoder.idl           \
-		imgILoad.idl              \
 		imgILoader.idl            \
 		imgIRequest.idl           \
 		imgITools.idl             \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
--- a/modules/libpr0n/public/imgIContainer.idl
+++ b/modules/libpr0n/public/imgIContainer.idl
@@ -19,16 +19,17 @@
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Stuart Parmenter <pavlov@netscape.com>
  *   Federico Mena-Quintero <federico@novell.com>
  *   Joe Drew <joe@drew.ca>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -36,17 +37,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
-interface imgIContainerObserver;
+interface imgIDecoderObserver;
 
 %{C++
 #include "gfxImageSurface.h"
 #include "gfxContext.h"
 #include "gfxMatrix.h"
 #include "gfxRect.h"
 #include "gfxPattern.h"
 #include "gfxASurface.h"
@@ -65,17 +66,17 @@ native gfxGraphicsFilter(gfxPattern::Gra
 /**
  * imgIContainer is the interface that represents an image. It allows
  * access to frames as Thebes surfaces, and permits users to extract subregions
  * as other imgIContainers. It also allows drawing of images on to Thebes
  * contexts.
  *
  * Internally, imgIContainer also manages animation of images.
  */
-[scriptable, uuid(1bcf7a25-1356-47a8-bf80-e284989ea38f)]
+[scriptable, uuid(74c63935-b54f-43a4-9449-2e9c815e3bef)]
 interface imgIContainer : nsISupports
 {
   /**
    * The width of the container rectangle.
    */
   readonly attribute PRInt32 width;
 
   /**
@@ -90,67 +91,156 @@ interface imgIContainer : nsISupports
 
   /**
    * Whether the current frame is opaque; that is, needs the background painted
    * behind it.
    */
   readonly attribute boolean currentFrameIsOpaque;
 
   /**
-   * Get a surface for the current frame. This may be a platform-native,
-   * optimized frame, so you cannot inspect its pixel data.
+   * Flags for imgIContainer operations.
+   *
+   * Meanings:
+   *
+   * FLAG_NONE: Lack of flags
+   *
+   * FLAG_SYNC_DECODE: Forces synchronous/non-progressive decode of all
+   * available data before the call returns.
    */
-  [noscript] readonly attribute gfxASurface currentFrame;
+
+  const long FLAG_NONE            = 0x0;
+  const long FLAG_SYNC_DECODE     = 0x1;
 
   /**
-   * Create and return a new copy of the current frame that you can write to
+    * Constants for specifying various "special" frames.
+    *
+    * FRAME_FIRST: The first frame
+    * FRAME_CURRENT: The current frame
+    *
+    * FRAME_MAX_VALUE should be set to the value of the maximum constant above,
+    * as it is used for ensuring that a valid value was passed in.
+    */
+  const unsigned long FRAME_FIRST = 0;
+  const unsigned long FRAME_CURRENT = 1;
+  const unsigned long FRAME_MAX_VALUE = 1;
+
+  /**
+   * Get a surface for the given frame. This may be a platform-native,
+   * optimized surface, so you cannot inspect its pixel data.
+   *
+   * @param aWhichFrame Frame specifier of the FRAME_* variety.
+   * @param aFlags Flags of the FLAG_* variety
+   */
+  [noscript] gfxASurface getFrame(in PRUint32 aWhichFrame,
+                                  in PRUint32 aFlags);
+
+  /**
+   * Create and return a new copy of the given frame that you can write to
    * and otherwise inspect the pixels of.
+   *
+   * @param aWhichFrame Frame specifier of the FRAME_* variety.
+   * @param aFlags Flags of the FLAG_* variety
    */
-  [noscript] gfxImageSurface copyCurrentFrame();
+  [noscript] gfxImageSurface copyFrame(in PRUint32 aWhichFrame,
+                                       in PRUint32 aFlags);
 
   /**
    * Create a new imgContainer that contains only a single frame, which itself
-   * contains a subregion of the current frame.
+   * contains a subregion of the given frame.
    *
+   * @param aWhichFrame Frame specifier of the FRAME_* variety.
    * @param aRect the area of the current frame to be duplicated in the
    *              returned imgContainer's frame.
+   * @param aFlags Flags of the FLAG_* variety
    */
-  [noscript] imgIContainer extractCurrentFrame([const] in nsIntRect aRect);
+  [noscript] imgIContainer extractFrame(in PRUint32 aWhichFrame,
+                                        [const] in nsIntRect aRect,
+                                        in PRUint32 aFlags);
 
   /**
    * Draw the current frame on to the context specified.
    *
    * @param aContext The Thebex context to draw the image to.
    * @param aFilter The filter to be used if we're scaling the image.
    * @param aUserSpaceToImageSpace The transformation from user space (e.g.,
    *                               appunits) to image space.
    * @param aFill The area in the context to draw pixels to. Image will be
    *              automatically tiled as necessary.
    * @param aSubimage The area of the image, in pixels, that we are allowed to
    *                  sample from.
+   * @param aFlags Flags of the FLAG_* variety
    */
   [noscript] void draw(in gfxContext aContext, in gfxGraphicsFilter aFilter,
                        in gfxMatrix aUserSpaceToImageSpace, in gfxRect aFill,
-                       in nsIntRect aSubimage);
+                       in nsIntRect aSubimage, in PRUint32 aFlags);
+
+  /*
+   * Ensures that an image is decoding. Calling this function guarantees that
+   * the image will at some point fire off decode notifications. Calling draw(),
+   * getFrame(), copyFrame(), or extractCurrentFrame() triggers the same
+   * mechanism internally. Thus, if you want to be sure that the image will be
+   * decoded but don't want to access it until then, you must call
+   * requestDecode().
+   */
+  void requestDecode();
 
+  /**
+    * Increments the lock count on the image. An image will not be discarded
+    * as long as the lock count is nonzero. Note that it is still possible for
+    * the image to be undecoded if decode-on-draw is enabled and the image
+    * was never drawn.
+    *
+    * Upon instantiation images have a lock count of zero.
+    */
+  void lockImage();
+
+  /**
+    * Decreases the lock count on the image. If the lock count drops to zero,
+    * the image is allowed to discard its frame data to save memory.
+    *
+    * Upon instantiation images have a lock count of zero. It is an error to
+    * call this method without first having made a matching lockImage() call.
+    * In other words, the lock count is not allowed to be negative.
+    */
+  void unlockImage();
 
   /************ Internal libpr0n use only below here. *****************/
 
   /**
-   * Create a new \a aWidth x \a aHeight sized image container.
+   * Flags for imgIContainer initialization.
+   *
+   * Meanings:
+   *
+   * INIT_FLAG_NONE: Lack of flags
+   *
+   * INIT_FLAG_DISCARDABLE: The container should be discardable
+   *
+   * INIT_FLAG_DECODE_ON_DRAW: The container should decode on draw rather than
+   * decoding on load.
    *
-   * @param aWidth The width of the container in which all the
-   *               frames will fit.
-   * @param aHeight The height of the container in which all the
-   *                frames will fit.
-   * @param aObserver Observer to send animation notifications to.
+   * INIT_FLAG_MULTIPART: The container will be used to display a stream of
+   * images in a multipart channel. If this flag is set, INIT_FLAG_DISCARDABLE
+   * and INIT_FLAG_DECODE_ON_DRAW must not be set.
    */
-  void init(in PRInt32 aWidth,
-            in PRInt32 aHeight,
-            in imgIContainerObserver aObserver);
+
+  const long INIT_FLAG_NONE           = 0x0;
+  const long INIT_FLAG_DISCARDABLE    = 0x1;
+  const long INIT_FLAG_DECODE_ON_DRAW = 0x2;
+  const long INIT_FLAG_MULTIPART      = 0x4;
+
+  /**
+   * Creates a new image container.
+   *
+   * @param aObserver Observer to send decoder and animation notifications to.
+   * @param aMimeType The mimetype of the image.
+   * @param aFlags Initialization flags of the INIT_FLAG_* variety.
+   */
+  void init(in imgIDecoderObserver aObserver,
+            in string aMimeType,
+            in PRUint32 aFlags);
 
   /** 
    * "Disposal" method indicates how the image should be handled before the
    *  subsequent image is displayed.
    *  Don't change these without looking at the implementations using them,
    *  struct gif_struct::disposal_method and gif_write() in particular.
    */
   const long kDisposeClearAll         = -1; // Clear the whole image, revealing
@@ -196,30 +286,33 @@ interface imgIContainer : nsISupports
   readonly attribute unsigned long currentFrameIndex;
 
   /**
    * The total number of frames in this image.
    */
   readonly attribute unsigned long numFrames;
 
   /**
-   * Get the size, in bytes, of a particular frame's image data.
+   * The size, in bytes, occupied by the significant data portions of the image.
+   * This includes both compressed source data and decoded frames.
    */
-  unsigned long getFrameImageDataLength(in unsigned long framenumber);
-
-  void getFrameColormap(in unsigned long framenumber, 
-                        [array, size_is(paletteLength)] out PRUint32 paletteData,
-                        out unsigned long paletteLength);
+  readonly attribute unsigned long dataSize;
 
   void setFrameDisposalMethod(in unsigned long framenumber, in PRInt32 aDisposalMethod);
   void setFrameBlendMethod(in unsigned long framenumber, in PRInt32 aBlendMethod);
   void setFrameTimeout(in unsigned long framenumber, in PRInt32 aTimeout);
   void setFrameHasNoAlpha(in unsigned long framenumber);
 
   /**
+   * Sets the size of the container. This should only be called by the decoder. This function may be called multiple
+   * times, but will throw an error if subsequent calls do not match the first.
+   */
+  [noscript] void setSize(in long aWidth, in long aHeight);
+
+  /**
    * Create or re-use a frame at index aFrameNum. It is an error to call this with aFrameNum not in the range [0, numFrames].
    */
   [noscript] void ensureCleanFrame(in unsigned long aFramenum, in PRInt32 aX, in PRInt32 aY, in PRInt32 aWidth, in PRInt32 aHeight, in gfxImageFormat aFormat,
                                    [array, size_is(imageLength)] out PRUint8 imageData, out unsigned long imageLength);
 
   /**
    * Adds to the end of the list of frames.
    */
@@ -244,14 +337,23 @@ interface imgIContainer : nsISupports
   void resetAnimation();
 
   /**
    * number of times to loop the image.
    * @note -1 means forever.
    */
   attribute long loopCount;
 
-  /* Methods to discard uncompressed images and restore them again */
-  [noscript] void setDiscardable(in string aMimeType);
-  [noscript] void addRestoreData([array, size_is(aCount), const] in char data,
+  /* Add compressed source data to the imgContainer.
+   *
+   * The decoder will use this data, either immediately or at draw time, do
+   * decode the image.
+   */
+  [noscript] void addSourceData([array, size_is(aCount), const] in char data,
                                  in unsigned long aCount);
-  [noscript] void restoreDataDone();
+
+  /* Called after the all the source data has been added with addSourceData. */
+  [noscript] void sourceDataComplete();
+
+  /* Called for multipart images when there's a new source image to add. */
+  [noscript] void newSourceData();
+
 };
--- a/modules/libpr0n/public/imgIDecoder.idl
+++ b/modules/libpr0n/public/imgIDecoder.idl
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Stuart Parmenter <pavlov@netscape.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -35,58 +36,88 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface nsIInputStream;
-interface imgILoad;
+interface imgIContainer;
+interface imgIDecoderObserver;
 
 /**
  * imgIDecoder interface
  *
  * @author Stuart Parmenter <pavlov@netscape.com>
  * @version 0.2
  * @see imagelib2
  */
-[scriptable, uuid(9eebf43a-1dd1-11b2-953e-f1782f4cbad3)]
+[scriptable, uuid(139c6f97-cd2f-4a4f-890d-8d9f4a693682)]
 interface imgIDecoder : nsISupports
 {
   /**
-   * Initialize an image decoder.
-   * @param aRequest the request that owns the decoder.
+   * Bits that can be passed to the decoder to affect decoding.
+   * @name decodeflags
+   *
+   * Meanings:
+   *
+   * DECODER_FLAG_NONE: No flags
    *
-   * @note The decode should QI \a aLoad to an imgIDecoderObserver
-   * and should send decoder notifications to the request.
-   * The decoder should always pass NULL as the first two parameters to
-   * all of the imgIDecoderObserver APIs.
+   * DECODER_FLAG_HEADERONLY: Read basic data from the image in order to 
+   * set up the image container,  but don't read any actual image data.
    */
-  void init(in imgILoad aLoad);
+  const long DECODER_FLAG_NONE       = 0x0;
+  const long DECODER_FLAG_HEADERONLY = 0x1;
+
+  /**
+   * Initialize an image decoder.
+   * @param aContainer The image container to decode to.
+   * @param aObserver The observer for decode notification events.
+   * @param aFlags Flags for the decoder
+   *
+   * @note The decoder should always pass NULL as the 
+   * first two parameters to all of the imgIDecoderObserver APIs.
+   */
+  void init(in imgIContainer aImage, in imgIDecoderObserver aObserver,
+            in unsigned long aFlags);
 
   /** 
-   * Closes the stream. 
+   * Closes the stream.
+   * @param aFlags Close flags of the CLOSE_FLAG_* Variety
+   *
+   * Resources are always freed with this call. If notifications are sent,
+   * OnStopDecode is guaranteed to be called if it hasn't been called already.
+   *
+   * CLOSE_FLAG_DONTNOTIFY - Don't send any observer notifications, and don't
+   * call imgIContainer::decodingComplete().
    */
-  void close();
+  const long CLOSE_FLAG_DONTNOTIFY = 0x01;
+  void close(in PRUint32 aFlags);
 
   /**
    * Flushes the stream.
    */
   void flush();
 
   /**
    * Writes data into the stream from an input stream.
+   *
+   * For Header-Only decodes, OnStartContainer is the only notification
+   * fired.
+   *
+   * If a decoding error occurs, an internal flag is set and an error is
+   * returned. Each subsequent call to writeFrom will fail immediately
+   * for the lifetime of the decoder. Shutdown notifications of the OnStopX
+   * variety, as well as DecodingComplete(), are guaranteed not to be called
+   * if a decoding error occurs.
+   *
    * Implementer's note: This method is defined by this interface in order
    * to allow the output stream to efficiently copy the data from the input
    * stream into its internal buffer (if any). If this method was provide
    * as an external facility, a separate char* buffer would need to be used
    * in order to call the output stream's other Write method.
    * @param fromStream the stream from which the data is read
    * @param count the maximun number of bytes to write
-   * @return aWriteCount out parameter to hold the number of
-   *         bytes written. if an error occurs, the writecount
-   *         is undefined
    */
-  unsigned long writeFrom(in nsIInputStream inStr,
-                          in unsigned long count);
+  void writeFrom(in nsIInputStream inStr, in unsigned long count);
 
 };
--- a/modules/libpr0n/public/imgIDecoderObserver.idl
+++ b/modules/libpr0n/public/imgIDecoderObserver.idl
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Stuart Parmenter <pavlov@netscape.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -47,74 +48,127 @@ interface imgIContainer;
 %}
 
 /**
  * imgIDecoderObserver interface
  *
  * This interface is used both for observing imgIDecoder objects and for
  * observing imgIRequest objects.  In the former case, aRequest is
  * always null.
- * XXXldb The two functions should probably be split.
+ *
+ * We make the distinction here between "load" and "decode" notifications. Load
+ * notifications are fired as the image is loaded from the network or
+ * filesystem. Decode notifications are fired as the image is decoded. If an
+ * image is decoded on load and not visibly discarded, decode notifications are
+ * nested logically inside load notifications as one might expect. However, with
+ * decode-on-draw, the set of decode notifications can come completely _after_
+ * the load notifications, and can come multiple times if the image is
+ * discardable. Moreover, they can be interleaved in various ways. In general,
+ * any presumed ordering between load and decode notifications should not be
+ * relied upon.
+ *
+ * Decode notifications may or may not be synchronous, depending on the
+ * situation. If imgIDecoder::FLAG_SYNC_DECODE is passed to a function that
+ * triggers a decode, all notifications that can be generated from the currently
+ * loaded data fire before the call returns. If FLAG_SYNC_DECODE is not passed,
+ * all, some, or none of the notifications may fire before the call returns.
+ *
+ * This interface will be cleaned up in bug 505385.
  *
  * @author Stuart Parmenter <pavlov@netscape.com>
  * @version 0.1
  * @see imagelib2
  */
-[scriptable, uuid(1dfc9189-6421-4281-83b2-d9c1c9ba4d1b)]
+[scriptable, uuid(9f6bfbee-9e04-43a0-b8f6-2159973efec8)]
 interface imgIDecoderObserver : imgIContainerObserver
 {
   /**
+   * Load notification.
+   *
    * called at the same time that nsIRequestObserver::onStartRequest would be
    * (used only for observers of imgIRequest objects, which are nsIRequests,
    * not imgIDecoder objects)
    *
    * Unlike nsIRequestObserver::onStartRequest, this can be called
    * synchronously.
    */
   void onStartRequest(in imgIRequest aRequest);
 
   /**
-   * called as soon as the image begins getting decoded
+   * Decode notification.
+   *
+   * Called as soon as the image begins getting decoded. This does not include
+   * "header-only" decodes used by decode-on-draw to parse the width/height
+   * out of the image. Thus, it is a decode notification only.
    */
   void onStartDecode(in imgIRequest aRequest);
 
   /**
-   * called once the image has been inited and therefore has a width and height
+   * Load notification.
+   *
+   * Called once enough data has been loaded from the network that we were able
+   * to parse the width/height from the image. By the time this callback is been
+   * called, the size has been set on the container and STATUS_SIZE_AVAILABLE
+   * has been set on the associated imgRequest.
    */
   void onStartContainer(in imgIRequest aRequest, in imgIContainer aContainer);
 
   /**
-   * called when each frame is created
+   * Decode notification.
+   *
+   * called when each frame is created.
    */
   void onStartFrame(in imgIRequest aRequest, in unsigned long aFrame);
 
   /**
-   * called when some part of the frame has new data in it
+   * Decode notification.
+   *
+   * called when there is more to paint.
    */
   [noscript] void onDataAvailable(in imgIRequest aRequest, in boolean aCurrentFrame, [const] in nsIntRect aRect);
 
   /**
-   * called when a frame is finished decoding
+   * Decode notification.
+   *
+   * called when a frame is finished decoding.
    */
   void onStopFrame(in imgIRequest aRequest, in unsigned long aFrame);
 
   /**
-   * probably not needed.  called right before onStopDecode
+   * Do not implement this. It is useless and going away.
    */
   void onStopContainer(in imgIRequest aRequest, in imgIContainer aContainer);
 
   /**
-   * called when the decoder is dying off
+   * In theory a decode notification, but currently a load notification.
+   *
+   * Ideally this would be called when the decode is complete. Unfortunately,
+   * this is currently the only way to signal decoding errors to consumers,
+   * and the only decoding errors that consumers care about (indeed, the only
+   * ones that they're prepared to hear about) are failures to instantiate the
+   * decoder (<img src="foo.html"> for example). Thus, currently this is just
+   * a companion to onStopDecode to signal success or failure. This will be
+   * revisited in bug 505385. If you're thinking of doing something new with
+   * this, please talk to bholley first.
    */
   void onStopDecode(in imgIRequest aRequest, in nsresult status,
                     in wstring statusArg);
 
   /**
+   * Load notification.
+   *
    * called at the same time that nsIRequestObserver::onStopRequest would be
    * (used only for observers of imgIRequest objects, which are nsIRequests,
    * not imgIDecoder objects)
    *
    * Unlike nsIRequestObserver::onStartRequest, this can be called
    * synchronously.
    */
   void onStopRequest(in imgIRequest aRequest, in boolean aIsLastPart);
 
+  /**
+   * Called when the decoded image data is discarded. This means that the frames
+   * no longer exist in decoded form, and any attempt to access or draw the
+   * image will initiate a new series of progressive decode notifications.
+   */
+  void onDiscard(in imgIRequest aRequest);
+
 };
deleted file mode 100644
--- a/modules/libpr0n/public/imgILoad.idl
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 2001
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Stuart Parmenter <pavlov@netscape.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#include "nsISupports.idl"
-
-interface imgIContainer;
-
-/**
- * imgILoad interface
- *
- * @author Stuart Parmenter <pavlov@netscape.com>
- * @version 0.1
- * @see imagelib2
- */
-[scriptable, uuid(e6273acc-1dd1-11b2-a08b-824ad1b1628d)]
-interface imgILoad : nsISupports
-{
-  /**
-   * the image container...
-   * @return the image object associated with the request.
-   * @attention NEED DOCS
-   */
-  attribute imgIContainer image;
-  readonly attribute PRBool isMultiPartChannel;
-};
--- a/modules/libpr0n/public/imgIRequest.idl
+++ b/modules/libpr0n/public/imgIRequest.idl
@@ -96,18 +96,17 @@ interface imgIRequest : nsIRequest
   const long STATUS_SIZE_AVAILABLE   = 0x1;
   const long STATUS_LOAD_PARTIAL     = 0x2;
   const long STATUS_LOAD_COMPLETE    = 0x4;
   const long STATUS_ERROR            = 0x8;
   const long STATUS_FRAME_COMPLETE   = 0x10;
   //@}
 
   /**
-   * something
-   * @attention NEED DOCS
+   * Status flags of the STATUS_* variety.
    */
   readonly attribute unsigned long imageStatus;
 
   /**
    * The URI the image load was started with.  Note that this might not be the
    * actual URI for the image (e.g. if HTTP redirects happened during the
    * load).
    */
@@ -135,10 +134,46 @@ interface imgIRequest : nsIRequest
    * decoderObserver so it gets no further notifications from us.
    *
    * NOTE: You should not use this in any new code; instead, use cancel(). Note
    * that cancel() is asynchronous, which means that some time after you call
    * it, the listener/observer will get an OnStopRequest(). This means that, if
    * you're the observer, you can't call cancel() from your destructor.
    */
   void cancelAndForgetObserver(in nsresult aStatus);
+
+  /**
+   * Requests a decode for the image.
+   *
+   * imgIContainer has a requestDecode() method, but callers may want to request
+   * a decode before the container has necessarily been instantiated. Calling
+   * requestDecode() on the imgIRequest simply forwards along the request if the
+   * container already exists, or calls it once it gets OnStartContainer if the
+   * container does not yet exist.
+   */
+  void requestDecode();
+
+  /**
+   * Locks an image. If the image does not exist yet, locks it once it becomes
+   * available. The lock persists for the lifetime of the imgIRequest (until
+   * unlockImage is called) even if the underlying image changes.
+   *
+   * If you don't call unlockImage() by the time this imgIRequest goes away, it
+   * will be called for you automatically.
+   *
+   * Only one lock at a time per imgIRequest is supported. That is to say, you
+   * can do foo->LockImage(); foo->UnlockImage(); foo->LockImage();
+   * foo->UnlockImage(); but not foo->LockImage(); foo->LockImage();
+   * foo->UnlockImage(); foo->UnlockImage();
+   *
+   * @see imgIContainer::lockImage for documentation of the underlying call.
+   */
+  void lockImage();
+
+  /**
+   * Unlocks an image.
+   *
+   * @see imgIContainer::unlockImage for documentation of the underlying call.
+   */
+  void unlockImage();
+
 };
 
--- a/modules/libpr0n/public/imgITools.idl
+++ b/modules/libpr0n/public/imgITools.idl
@@ -53,17 +53,18 @@ interface imgITools : nsISupports
      *
      * @param aStream
      *        An input stream for an encoded image file.
      * @param aMimeType
      *        Type of image in the stream.
      * @param aContainer
      *        An imgIContainer holding the decoded image. Specify |null| when
      *        calling to have one created, otherwise specify a container to
-     *        be reused.
+     *        be used. It is an error to pass an already-initialized container
+     *        as aContainer.
      */
     void decodeImageData(in nsIInputStream aStream,
                          in ACString aMimeType,
                          inout imgIContainer aContainer);
 
     /**
      * encodeImage
      * Caller provides an image container, and the mime type it should be
--- a/modules/libpr0n/src/imgContainer.cpp
+++ b/modules/libpr0n/src/imgContainer.cpp
@@ -21,16 +21,17 @@
  *
  * Contributor(s):
  *   Stuart Parmenter <pavlov@netscape.com>
  *   Chris Saari <saari@netscape.com>
  *   Asko Tontti <atontti@cc.hut.fi>
  *   Arron Mogge <paper@animecity.nu>
  *   Andrew Smith
  *   Federico Mena-Quintero <federico@novell.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -39,176 +40,349 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsComponentManagerUtils.h"
 #include "imgIContainerObserver.h"
 #include "ImageErrors.h"
-#include "imgILoad.h"
 #include "imgIDecoder.h"
 #include "imgIDecoderObserver.h"
 #include "imgContainer.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsAutoPtr.h"
 #include "nsStringStream.h"
 #include "prmem.h"
 #include "prlog.h"
 #include "prenv.h"
+#include "nsTime.h"
+#include "ImageLogging.h"
 
 #include "gfxContext.h"
 
 /* Accounting for compressed data */
 #if defined(PR_LOGGING)
 static PRLogModuleInfo *gCompressedImageAccountingLog = PR_NewLogModule ("CompressedImageAccounting");
 #else
 #define gCompressedImageAccountingLog
 #endif
 
-static int num_containers_with_discardable_data;
-static PRInt64 num_compressed_image_bytes;
-
-
-NS_IMPL_ISUPPORTS3(imgContainer, imgIContainer, nsITimerCallback, nsIProperties)
+/* 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
+ * failures. As such, we don't want to clutter up debug consoles
+ * with spurious messages about NS_ENSURE_SUCCESS failures.
+ *
+ * 2) We want to set the internal error flag, shutdown properly,
+ * and end up in an error state.
+ *
+ * So this macro should be called when the desired failure behavior
+ * is to put the container into an error state and return failure.
+ * It goes without saying that macro won't compile outside of a
+ * non-static imgContainer method.
+ */
+#define LOG_CONTAINER_ERROR                      \
+  PR_BEGIN_MACRO                                 \
+  PR_LOG (gImgLog, PR_LOG_ERROR,                 \
+          ("ImgContainer: [this=%p] Error "      \
+           "detected at line %u for image of "   \
+           "type %s\n", this, __LINE__,          \
+           mSourceDataMimeType.get()));          \
+  PR_END_MACRO
+
+#define CONTAINER_ENSURE_SUCCESS(status)      \
+  PR_BEGIN_MACRO                              \
+  nsresult _status = status; /* eval once */  \
+  if (_status) {                              \
+    LOG_CONTAINER_ERROR;                      \
+    DoError();                                \
+    return _status;                           \
+  }                                           \
+ PR_END_MACRO
+
+#define CONTAINER_ENSURE_TRUE(arg, rv)  \
+  PR_BEGIN_MACRO                        \
+  if (!(arg)) {                         \
+    LOG_CONTAINER_ERROR;                \
+    DoError();                          \
+    return rv;                          \
+  }                                     \
+  PR_END_MACRO
+
+
+
+static int num_containers;
+static int num_discardable_containers;
+static PRInt64 total_source_bytes;
+static PRInt64 discardable_source_bytes;
+
+/* Are we globally disabling image discarding? */
+static PRBool
+DiscardingEnabled()
+{
+  static PRBool inited;
+  static PRBool enabled;
+
+  if (!inited) {
+    inited = PR_TRUE;
+
+    enabled = (PR_GetEnv("MOZ_DISABLE_IMAGE_DISCARD") == nsnull);
+  }
+
+  return enabled;
+}
+
+NS_IMPL_ISUPPORTS4(imgContainer, imgIContainer, nsITimerCallback, nsIProperties,
+                   nsISupportsWeakReference)
 
 //******************************************************************************
 imgContainer::imgContainer() :
   mSize(0,0),
-  mNumFrames(0),
+  mHasSize(PR_FALSE),
   mAnim(nsnull),
   mAnimationMode(kNormalAnimMode),
   mLoopCount(-1),
   mObserver(nsnull),
+  mDecodeOnDraw(PR_FALSE),
+  mMultipart(PR_FALSE),
+  mInitialized(PR_FALSE),
   mDiscardable(PR_FALSE),
-  mDiscarded(PR_FALSE),
-  mRestoreDataDone(PR_FALSE),
-  mDiscardTimer(nsnull)
+  mLockCount(0),
+  mDiscardTimer(nsnull),
+  mHasSourceData(PR_FALSE),
+  mDecoded(PR_FALSE),
+  mDecoder(nsnull),
+  mWorker(nsnull),
+  mBytesDecoded(0),
+  mDecoderInput(nsnull),
+  mDecoderFlags(imgIDecoder::DECODER_FLAG_NONE),
+  mWorkerPending(PR_FALSE),
+  mInDecoder(PR_FALSE),
+  mError(PR_FALSE)
 {
+  // Statistics
+  num_containers++;
 }
 
 //******************************************************************************
 imgContainer::~imgContainer()
 {
   if (mAnim)
     delete mAnim;
 
   for (unsigned int i = 0; i < mFrames.Length(); ++i)
     delete mFrames[i];
 
-  if (!mRestoreData.IsEmpty()) {
-    num_containers_with_discardable_data--;
-    num_compressed_image_bytes -= mRestoreData.Length();
+  // Discardable statistics
+  if (mDiscardable) {
+    num_discardable_containers--;
+    discardable_source_bytes -= mSourceData.Length();
 
     PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
             ("CompressedImageAccounting: destroying imgContainer %p.  "
-             "Compressed containers: %d, Compressed data bytes: %lld",
+             "Total Containers: %d, Discardable containers: %d, "
+             "Total source bytes: %lld, Source bytes for discardable containers %lld",
              this,
-             num_containers_with_discardable_data,
-             num_compressed_image_bytes));
+             num_containers,
+             num_discardable_containers,
+             total_source_bytes,
+             discardable_source_bytes));
   }
 
   if (mDiscardTimer) {
-    mDiscardTimer->Cancel ();
+    mDiscardTimer->Cancel();
     mDiscardTimer = nsnull;
   }
+
+  // If we have a decoder open, shut it down
+  if (mDecoder) {
+    nsresult rv = ShutdownDecoder(eShutdownIntent_Interrupted);
+    if (NS_FAILED(rv))
+      NS_WARNING("Failed to shut down decoder in destructor!");
+  }
+
+  // Total statistics
+  num_containers--;
+  total_source_bytes -= mSourceData.Length();
 }
 
 //******************************************************************************
-/* void init (in PRInt32 aWidth, in PRInt32 aHeight, 
-              in imgIContainerObserver aObserver); */
-NS_IMETHODIMP imgContainer::Init(PRInt32 aWidth, PRInt32 aHeight,
-                                 imgIContainerObserver *aObserver)
+/* void init(in imgIDecoderObserver aObserver, in string aMimeType,
+             in PRUint32 aFlags); */
+NS_IMETHODIMP imgContainer::Init(imgIDecoderObserver *aObserver,
+                                 const char* aMimeType,
+                                 PRUint32 aFlags)
 {
-  if (aWidth <= 0 || aHeight <= 0) {
-    NS_WARNING("error - negative image size\n");
+  // We don't support re-initialization
+  if (mInitialized)
+    return NS_ERROR_ILLEGAL_VALUE;
+
+  // Not sure an error can happen before init, but be safe
+  if (mError)
     return NS_ERROR_FAILURE;
+
+  NS_ENSURE_ARG_POINTER(aMimeType);
+
+  // We must be non-discardable and non-decode-on-draw for
+  // multipart channels
+  NS_ABORT_IF_FALSE(!(aFlags & INIT_FLAG_MULTIPART) ||
+                    (!(aFlags & INIT_FLAG_DISCARDABLE) &&
+                     !(aFlags & INIT_FLAG_DECODE_ON_DRAW)),
+                    "Can't be discardable or decode-on-draw for multipart");
+
+  // Store initialization data
+  mObserver = do_GetWeakReference(aObserver);
+  mSourceDataMimeType.Assign(aMimeType);
+  mDiscardable = aFlags & INIT_FLAG_DISCARDABLE;
+  mDecodeOnDraw = aFlags & INIT_FLAG_DECODE_ON_DRAW;;
+  mMultipart = aFlags & INIT_FLAG_MULTIPART;
+
+  // Statistics
+  if (mDiscardable) {
+    num_discardable_containers++;
+    discardable_source_bytes += mSourceData.Length();
   }
 
-  mSize.SizeTo(aWidth, aHeight);
-  
-  // As we are reloading it means we are no longer in 'discarded' state
-  mDiscarded = PR_FALSE;
-
-  mObserver = do_GetWeakReference(aObserver);
-  
+  // If we're being called from ExtractFrame (used by borderimage),
+  // we don't actually do any decoding. Bail early.
+  // XXX - This should be removed when we fix borderimage
+  if (mSourceDataMimeType.Length() == 0) {
+    mInitialized = PR_TRUE;
+    return NS_OK;
+  }
+
+  // Determine our decoder flags. If we're doing decode-on-draw,
+  // we want to do a quick first pass to get the size but nothing
+  // else. We instantiate another decoder later to do the full
+  // decoding.
+  PRUint32 dFlags = imgIDecoder::DECODER_FLAG_NONE;
+  if (mDecodeOnDraw)
+    dFlags |= imgIDecoder::DECODER_FLAG_HEADERONLY;
+
+  // Instantiate the decoder
+  nsresult rv = InitDecoder(dFlags);
+  CONTAINER_ENSURE_SUCCESS(rv);
+
+  // Mark us as initialized
+  mInitialized = PR_TRUE;
+
   return NS_OK;
 }
 
 //******************************************************************************
-/* [noscript] imgIContainer extractCurrentFrame([const] in nsIntRect aRegion); */
-NS_IMETHODIMP imgContainer::ExtractCurrentFrame(const nsIntRect &aRegion, imgIContainer **_retval)
+/* [noscript] imgIContainer extractFrame(PRUint32 aWhichFrame,
+ *                                       [const] in nsIntRect aRegion,
+ *                                       in PRUint32 aFlags); */
+NS_IMETHODIMP imgContainer::ExtractFrame(PRUint32 aWhichFrame,
+                                         const nsIntRect &aRegion,
+                                         PRUint32 aFlags,
+                                         imgIContainer **_retval)
 {
   NS_ENSURE_ARG_POINTER(_retval);
 
+  nsresult rv;
+
+  if (aWhichFrame > FRAME_MAX_VALUE)
+    return NS_ERROR_INVALID_ARG;
+
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  // Make a new container. This should switch to another class with bug 505959.
   nsRefPtr<imgContainer> img(new imgContainer());
   NS_ENSURE_TRUE(img, NS_ERROR_OUT_OF_MEMORY);
 
-  img->Init(aRegion.width, aRegion.height, nsnull);
-
-  imgFrame *frame = GetCurrentImgFrame();
-  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+  // We don't actually have a mimetype in this case. The empty string tells the
+  // init routine not to try to instantiate a decoder. This should be fixed in
+  // bug 505959.
+  img->Init(nsnull, "", INIT_FLAG_NONE);
+  img->SetSize(aRegion.width, aRegion.height);
+  img->mDecoded = PR_TRUE; // Also, we need to mark the image as decoded
+
+  // If a synchronous decode was requested, do it
+  if (aFlags & FLAG_SYNC_DECODE) {
+    rv = SyncDecode();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // Get the frame. If it's not there, it's probably the caller's fault for
+  // not waiting for the data to be loaded from the network or not passing
+  // FLAG_SYNC_DECODE
+  PRUint32 frameIndex = (aWhichFrame == FRAME_FIRST) ?
+                        0 : GetCurrentImgFrameIndex();
+  imgFrame *frame = GetImgFrame(frameIndex);
+  if (!frame) {
+    *_retval = nsnull;
+    return NS_ERROR_FAILURE;
+  }
 
   // The frame can be smaller than the image. We want to extract only the part
   // of the frame that actually exists.
   nsIntRect framerect = frame->GetRect();
   framerect.IntersectRect(framerect, aRegion);
 
   if (framerect.IsEmpty())
     return NS_ERROR_NOT_AVAILABLE;
 
   nsAutoPtr<imgFrame> subframe;
-  nsresult rv = frame->Extract(framerect, getter_Transfers(subframe));
+  rv = frame->Extract(framerect, getter_Transfers(subframe));
   if (NS_FAILED(rv))
     return rv;
 
   img->mFrames.AppendElement(subframe.forget());
-  img->mNumFrames++;
 
   *_retval = img.forget().get();
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* readonly attribute PRInt32 width; */
 NS_IMETHODIMP imgContainer::GetWidth(PRInt32 *aWidth)
 {
   NS_ENSURE_ARG_POINTER(aWidth);
 
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   *aWidth = mSize.width;
   return NS_OK;
 }
 
 //******************************************************************************
 /* readonly attribute PRInt32 height; */
 NS_IMETHODIMP imgContainer::GetHeight(PRInt32 *aHeight)
 {
   NS_ENSURE_ARG_POINTER(aHeight);
 
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   *aHeight = mSize.height;
   return NS_OK;
 }
 
 imgFrame *imgContainer::GetImgFrame(PRUint32 framenum)
 {
-  nsresult rv = RestoreDiscardedData();
-  NS_ENSURE_SUCCESS(rv, nsnull);
+  nsresult rv = WantDecodedFrames();
+  CONTAINER_ENSURE_TRUE(NS_SUCCEEDED(rv), nsnull);
 
   if (!mAnim) {
     NS_ASSERTION(framenum == 0, "Don't ask for a frame > 0 if we're not animated!");
     return mFrames.SafeElementAt(0, nsnull);
   }
   if (mAnim->lastCompositedFrameIndex == PRInt32(framenum))
     return mAnim->compositingFrame;
   return mFrames.SafeElementAt(framenum, nsnull);
 }
 
-PRInt32 imgContainer::GetCurrentImgFrameIndex() const
+PRUint32 imgContainer::GetCurrentImgFrameIndex() const
 {
   if (mAnim)
     return mAnim->currentAnimationFrameIndex;
 
   return 0;
 }
 
 imgFrame *imgContainer::GetCurrentImgFrame()
@@ -217,83 +391,142 @@ imgFrame *imgContainer::GetCurrentImgFra
 }
 
 //******************************************************************************
 /* readonly attribute boolean currentFrameIsOpaque; */
 NS_IMETHODIMP imgContainer::GetCurrentFrameIsOpaque(PRBool *aIsOpaque)
 {
   NS_ENSURE_ARG_POINTER(aIsOpaque);
 
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  // See if we can get an image frame
   imgFrame *curframe = GetCurrentImgFrame();
-  NS_ENSURE_TRUE(curframe, NS_ERROR_FAILURE);
-
-  *aIsOpaque = !curframe->GetNeedsBackground();
-
-  // We are also transparent if the current frame's size doesn't cover our
-  // entire area.
-  nsIntRect framerect = curframe->GetRect();
-  *aIsOpaque = *aIsOpaque && (framerect != nsIntRect(0, 0, mSize.width, mSize.height));
+
+  // If we don't get a frame, the safe answer is "not opaque"
+  if (!curframe)
+    *aIsOpaque = PR_FALSE;
+
+  // Otherwise, we can make a more intelligent decision
+  else {
+    *aIsOpaque = !curframe->GetNeedsBackground();
+
+    // We are also transparent if the current frame's size doesn't cover our
+    // entire area.
+    nsIntRect framerect = curframe->GetRect();
+    *aIsOpaque = *aIsOpaque && (framerect != nsIntRect(0, 0, mSize.width, mSize.height));
+  }
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* [noscript] void getCurrentFrameRect(nsIntRect rect); */
 NS_IMETHODIMP imgContainer::GetCurrentFrameRect(nsIntRect &aRect)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  // Get the current frame
   imgFrame *curframe = GetCurrentImgFrame();
-  NS_ENSURE_TRUE(curframe, NS_ERROR_FAILURE);
-
-  aRect = curframe->GetRect();
+
+  // If we have the frame, use that rectangle
+  if (curframe)
+    aRect = curframe->GetRect();
+
+  // If the frame doesn't exist, we pass the empty rectangle. It's not clear
+  // whether this is appropriate in general, but at the moment the only
+  // consumer of this method is imgRequest (when it wants to figure out dirty
+  // rectangles to send out batched observer updates). This should probably be
+  // revisited when we fix bug 503973.
+  else {
+    aRect.MoveTo(0, 0);
+    aRect.SizeTo(0, 0);
+  }
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* readonly attribute unsigned long currentFrameIndex; */
 NS_IMETHODIMP imgContainer::GetCurrentFrameIndex(PRUint32 *aCurrentFrameIdx)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   NS_ENSURE_ARG_POINTER(aCurrentFrameIdx);
   
   *aCurrentFrameIdx = GetCurrentImgFrameIndex();
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* readonly attribute unsigned long numFrames; */
 NS_IMETHODIMP imgContainer::GetNumFrames(PRUint32 *aNumFrames)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   NS_ENSURE_ARG_POINTER(aNumFrames);
 
-  *aNumFrames = mNumFrames;
+  *aNumFrames = mFrames.Length();
   
   return NS_OK;
 }
 
 //******************************************************************************
 /* readonly attribute boolean animated; */
 NS_IMETHODIMP imgContainer::GetAnimated(PRBool *aAnimated)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   NS_ENSURE_ARG_POINTER(aAnimated);
 
-  *aAnimated = (mNumFrames > 1);
+  *aAnimated = (mAnim != nsnull);
   
   return NS_OK;
 }
 
 
 //******************************************************************************
-/* [noscript] gfxImageSurface copyCurrentFrame(); */
-NS_IMETHODIMP imgContainer::CopyCurrentFrame(gfxImageSurface **_retval)
+/* [noscript] gfxImageSurface copyFrame(in PRUint32 aWhichFrame,
+ *                                      in PRUint32 aFlags); */
+NS_IMETHODIMP imgContainer::CopyFrame(PRUint32 aWhichFrame,
+                                      PRUint32 aFlags,
+                                      gfxImageSurface **_retval)
 {
+  if (aWhichFrame > FRAME_MAX_VALUE)
+    return NS_ERROR_INVALID_ARG;
+
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  nsresult rv;
+
+  // If requested, synchronously flush any data we have lying around to the decoder
+  if (aFlags & FLAG_SYNC_DECODE) {
+    rv = SyncDecode();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
   NS_ENSURE_ARG_POINTER(_retval);
 
-  imgFrame *frame = GetImgFrame(GetCurrentImgFrameIndex());
-  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+  // Get the frame. If it's not there, it's probably the caller's fault for
+  // not waiting for the data to be loaded from the network or not passing
+  // FLAG_SYNC_DECODE
+  PRUint32 frameIndex = (aWhichFrame == FRAME_FIRST) ?
+                        0 : GetCurrentImgFrameIndex();
+  imgFrame *frame = GetImgFrame(frameIndex);
+  if (!frame) {
+    *_retval = nsnull;
+    return NS_ERROR_FAILURE;
+  }
 
   nsRefPtr<gfxPattern> pattern;
   frame->GetPattern(getter_AddRefs(pattern));
   nsIntRect intframerect = frame->GetRect();
   gfxRect framerect(intframerect.x, intframerect.y, intframerect.width, intframerect.height);
 
   // Create a 32-bit image surface of our size, but draw using the frame's
   // rect, implicitly padding the frame out to the image's size.
@@ -305,118 +538,129 @@ NS_IMETHODIMP imgContainer::CopyCurrentF
   ctx.Rectangle(framerect);
   ctx.Fill();
 
   *_retval = imgsurface.forget().get();
   return NS_OK;
 }
 
 //******************************************************************************
-/* [noscript] readonly attribute gfxASurface currentFrame; */
-NS_IMETHODIMP imgContainer::GetCurrentFrame(gfxASurface **_retval)
+/* [noscript] gfxASurface getFrame(in PRUint32 aWhichFrame,
+ *                                 in PRUint32 aFlags); */
+NS_IMETHODIMP imgContainer::GetFrame(PRUint32 aWhichFrame,
+                                     PRUint32 aFlags,
+                                     gfxASurface **_retval)
 {
-  imgFrame *frame = GetImgFrame(GetCurrentImgFrameIndex());
-  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+  if (aWhichFrame > FRAME_MAX_VALUE)
+    return NS_ERROR_INVALID_ARG;
+
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  nsresult rv = NS_OK;
+
+  // If the caller requested a synchronous decode, do it
+  if (aFlags & FLAG_SYNC_DECODE) {
+    rv = SyncDecode();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // Get the frame. If it's not there, it's probably the caller's fault for
+  // not waiting for the data to be loaded from the network or not passing
+  // FLAG_SYNC_DECODE
+  PRUint32 frameIndex = (aWhichFrame == FRAME_FIRST) ?
+                          0 : GetCurrentImgFrameIndex();
+  imgFrame *frame = GetImgFrame(frameIndex);
+  if (!frame) {
+    *_retval = nsnull;
+    return NS_ERROR_FAILURE;
+  }
 
   nsRefPtr<gfxASurface> framesurf;
-  nsresult rv = NS_OK;
 
   // If this frame covers the entire image, we can just reuse its existing
   // surface.
   nsIntRect framerect = frame->GetRect();
   if (framerect.x == 0 && framerect.y == 0 &&
       framerect.width == mSize.width &&
       framerect.height == mSize.height)
     rv = frame->GetSurface(getter_AddRefs(framesurf));
 
   // The image doesn't have a surface because it's been optimized away. Create
   // one.
   if (!framesurf) {
     nsRefPtr<gfxImageSurface> imgsurf;
-    rv = CopyCurrentFrame(getter_AddRefs(imgsurf));
+    rv = CopyFrame(aWhichFrame, aFlags, getter_AddRefs(imgsurf));
     framesurf = imgsurf;
   }
 
   *_retval = framesurf.forget().get();
 
   return rv;
 }
 
 //******************************************************************************
-/* unsigned long getFrameDataLength(in unsigned long framenum); */
-NS_IMETHODIMP imgContainer::GetFrameImageDataLength(PRUint32 framenum, PRUint32 *_retval)
+/* readonly attribute unsigned long dataSize; */
+NS_IMETHODIMP imgContainer::GetDataSize(PRUint32 *_retval)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   NS_ENSURE_ARG_POINTER(_retval);
 
-  if (framenum >= PRUint32(mNumFrames))
-    return NS_ERROR_INVALID_ARG;
-
-  imgFrame *frame = GetImgFrame(framenum);
-  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
-
-  *_retval = frame->GetImageDataLength();
-
-  return NS_OK;
-}
-
-//******************************************************************************
-/* unsigned long getFrameColormap(unsigned long framenumber, 
- *                         [array, size_is(paletteLength)] out PRUint32 paletteData,
- *                         out unsigned long paletteLength); */
-NS_IMETHODIMP imgContainer::GetFrameColormap(PRUint32 framenum, PRUint32 **aPaletteData,
-                                             PRUint32 *aPaletteLength)
-{
-  NS_ENSURE_ARG_POINTER(aPaletteData);
-  NS_ENSURE_ARG_POINTER(aPaletteLength);
-
-  if (framenum >= PRUint32(mNumFrames))
-    return NS_ERROR_INVALID_ARG;
-
-  imgFrame *frame = GetImgFrame(framenum);
-  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
-
-  if (!frame->GetIsPaletted())
-    return NS_ERROR_FAILURE;
-
-  frame->GetPaletteData(aPaletteData, aPaletteLength);
+  // Start with 0
+  *_retval = 0;
+
+  // Account for any compressed source data
+  *_retval += mSourceData.Length();
+  NS_ABORT_IF_FALSE(StoringSourceData() || (*_retval == 0),
+                    "Non-zero source data size when we aren't storing it?");
+
+  // Account for any uncompressed frames
+  for (PRUint32 i = 0; i < mFrames.Length(); ++i) {
+    imgFrame *frame = mFrames.SafeElementAt(i, nsnull);
+    NS_ABORT_IF_FALSE(frame, "Null frame in frame array!");
+    *_retval += frame->GetImageDataLength();
+  }
 
   return NS_OK;
 }
 
 nsresult imgContainer::InternalAddFrameHelper(PRUint32 framenum, imgFrame *aFrame,
                                               PRUint8 **imageData, PRUint32 *imageLength,
                                               PRUint32 **paletteData, PRUint32 *paletteLength)
 {
-  if (framenum > PRUint32(mNumFrames))
+  NS_ABORT_IF_FALSE(framenum <= mFrames.Length(), "Invalid frame index!");
+  if (framenum > mFrames.Length())
     return NS_ERROR_INVALID_ARG;
 
   nsAutoPtr<imgFrame> frame(aFrame);
 
   if (paletteData && paletteLength)
     frame->GetPaletteData(paletteData, paletteLength);
 
   frame->GetImageData(imageData, imageLength);
 
   mFrames.InsertElementAt(framenum, frame.forget());
-  mNumFrames++;
 
   return NS_OK;
 }
                                   
 nsresult imgContainer::InternalAddFrame(PRUint32 framenum,
                                         PRInt32 aX, PRInt32 aY,
                                         PRInt32 aWidth, PRInt32 aHeight,
                                         gfxASurface::gfxImageFormat aFormat,
                                         PRUint8 aPaletteDepth,
                                         PRUint8 **imageData,
                                         PRUint32 *imageLength,
                                         PRUint32 **paletteData,
                                         PRUint32 *paletteLength)
 {
-  if (framenum > PRUint32(mNumFrames))
+  NS_ABORT_IF_FALSE(framenum <= mFrames.Length(), "Invalid frame index!");
+  if (framenum > mFrames.Length())
     return NS_ERROR_INVALID_ARG;
 
   nsAutoPtr<imgFrame> frame(new imgFrame());
   NS_ENSURE_TRUE(frame, NS_ERROR_OUT_OF_MEMORY);
 
   nsresult rv = frame->Init(aX, aY, aWidth, aHeight, aFormat, aPaletteDepth);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -460,58 +704,105 @@ nsresult imgContainer::InternalAddFrame(
 
 /* [noscript] void appendFrame (in PRInt32 aX, in PRInt32 aY, in PRInt32 aWidth, in PRInt32 aHeight, in gfxImageFormat aFormat, [array, size_is (imageLength)] out PRUint8 imageData, out unsigned long imageLength); */
 NS_IMETHODIMP imgContainer::AppendFrame(PRInt32 aX, PRInt32 aY, PRInt32 aWidth,
                                         PRInt32 aHeight, 
                                         gfxASurface::gfxImageFormat aFormat,
                                         PRUint8 **imageData,
                                         PRUint32 *imageLength)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   NS_ENSURE_ARG_POINTER(imageData);
   NS_ENSURE_ARG_POINTER(imageLength);
 
-  return InternalAddFrame(mNumFrames, aX, aY, aWidth, aHeight, aFormat, 
+  return InternalAddFrame(mFrames.Length(), aX, aY, aWidth, aHeight, aFormat, 
                           /* aPaletteDepth = */ 0, imageData, imageLength,
                           /* aPaletteData = */ nsnull, 
                           /* aPaletteLength = */ nsnull);
 }
 
 /* [noscript] void appendPalettedFrame (in PRInt32 aX, in PRInt32 aY, in PRInt32 aWidth, in PRInt32 aHeight, in gfxImageFormat aFormat, in PRUint8 aPaletteDepth, [array, size_is (imageLength)] out PRUint8 imageData, out unsigned long imageLength, [array, size_is (paletteLength)] out PRUint32 paletteData, out unsigned long paletteLength); */
 NS_IMETHODIMP imgContainer::AppendPalettedFrame(PRInt32 aX, PRInt32 aY,
                                                 PRInt32 aWidth, PRInt32 aHeight,
                                                 gfxASurface::gfxImageFormat aFormat,
                                                 PRUint8 aPaletteDepth,
                                                 PRUint8 **imageData,
                                                 PRUint32 *imageLength,
                                                 PRUint32 **paletteData,
                                                 PRUint32 *paletteLength)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   NS_ENSURE_ARG_POINTER(imageData);
   NS_ENSURE_ARG_POINTER(imageLength);
   NS_ENSURE_ARG_POINTER(paletteData);
   NS_ENSURE_ARG_POINTER(paletteLength);
 
-  return InternalAddFrame(mNumFrames, aX, aY, aWidth, aHeight, aFormat, 
+  return InternalAddFrame(mFrames.Length(), aX, aY, aWidth, aHeight, aFormat, 
                           aPaletteDepth, imageData, imageLength,
                           paletteData, paletteLength);
 }
 
-/*  [noscript] void ensureCleanFrame(in unsigned long aFramenum, in PRInt32 aX, in PRInt32 aY, in PRInt32 aWidth, in PRInt32 aHeight, in gfxImageFormat aFormat, [array, size_is(imageLength)] out PRUint8 imageData, out unsigned long imageLength); */
+/*  [noscript] void setSize(in long aWidth, in long aHeight); */
+NS_IMETHODIMP imgContainer::SetSize(PRInt32 aWidth, PRInt32 aHeight)
+{
+  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))) {
+
+    // Alter the warning depending on whether the channel is multipart
+    if (!mMultipart)
+      NS_WARNING("Image changed size on redecode! This should not happen!");
+    else
+      NS_WARNING("Multipart channel sent an image of a different size");
+
+    DoError();
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // Set the size and flag that we have it
+  mSize.SizeTo(aWidth, aHeight);
+  mHasSize = PR_TRUE;
+
+  return NS_OK;
+}
+
+/*  [noscript] void ensureCleanFrame(in unsigned long aFramenum, in PRInt32 aX, 
+                                     in PRInt32 aY, in PRInt32 aWidth, 
+                                     in PRInt32 aHeight, in gfxImageFormat aFormat, 
+                                     [array, size_is(imageLength)]
+                                       out PRUint8 imageData,
+                                     out unsigned long imageLength); */
 NS_IMETHODIMP imgContainer::EnsureCleanFrame(PRUint32 aFrameNum, PRInt32 aX, PRInt32 aY,
                                              PRInt32 aWidth, PRInt32 aHeight, 
                                              gfxASurface::gfxImageFormat aFormat,
                                              PRUint8 **imageData, PRUint32 *imageLength)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   NS_ENSURE_ARG_POINTER(imageData);
   NS_ENSURE_ARG_POINTER(imageLength);
-  if (aFrameNum > PRUint32(mNumFrames))
+  NS_ABORT_IF_FALSE(aFrameNum <= mFrames.Length(), "Invalid frame index!");
+  if (aFrameNum > mFrames.Length())
     return NS_ERROR_INVALID_ARG;
 
   // Adding a frame that doesn't already exist.
-  if (aFrameNum == PRUint32(mNumFrames))
+  if (aFrameNum == mFrames.Length())
     return InternalAddFrame(aFrameNum, aX, aY, aWidth, aHeight, aFormat, 
                             /* aPaletteDepth = */ 0, imageData, imageLength,
                             /* aPaletteData = */ nsnull, 
                             /* aPaletteLength = */ nsnull);
 
   imgFrame *frame = GetImgFrame(aFrameNum);
   if (!frame)
     return InternalAddFrame(aFrameNum, aX, aY, aWidth, aHeight, aFormat, 
@@ -536,161 +827,218 @@ NS_IMETHODIMP imgContainer::EnsureCleanF
   return NS_OK;
 }
 
 
 //******************************************************************************
 /* void frameUpdated (in unsigned long framenumber, in nsIntRect rect); */
 NS_IMETHODIMP imgContainer::FrameUpdated(PRUint32 aFrameNum, nsIntRect &aUpdatedRect)
 {
-  if (aFrameNum >= PRUint32(mNumFrames))
+  NS_ABORT_IF_FALSE(aFrameNum < mFrames.Length(), "Invalid frame index!");
+  if (aFrameNum >= mFrames.Length())
     return NS_ERROR_INVALID_ARG;
 
   imgFrame *frame = GetImgFrame(aFrameNum);
+  NS_ABORT_IF_FALSE(frame, "Calling FrameUpdated on frame that doesn't exist!");
   NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
 
   frame->ImageUpdated(aUpdatedRect);
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* void setFrameDisposalMethod (in unsigned long framenumber, in PRInt32 aDisposalMethod); */
 NS_IMETHODIMP imgContainer::SetFrameDisposalMethod(PRUint32 aFrameNum, PRInt32 aDisposalMethod)
 {
-  if (aFrameNum >= PRUint32(mNumFrames))
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  NS_ABORT_IF_FALSE(aFrameNum < mFrames.Length(), "Invalid frame index!");
+  if (aFrameNum >= mFrames.Length())
     return NS_ERROR_INVALID_ARG;
 
   imgFrame *frame = GetImgFrame(aFrameNum);
+  NS_ABORT_IF_FALSE(frame,
+                    "Calling SetFrameDisposalMethod on frame that doesn't exist!");
   NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
 
   frame->SetFrameDisposalMethod(aDisposalMethod);
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* void setFrameTimeout (in unsigned long framenumber, in PRInt32 aTimeout); */
 NS_IMETHODIMP imgContainer::SetFrameTimeout(PRUint32 aFrameNum, PRInt32 aTimeout)
 {
-  if (aFrameNum >= PRUint32(mNumFrames))
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  NS_ABORT_IF_FALSE(aFrameNum < mFrames.Length(), "Invalid frame index!");
+  if (aFrameNum >= mFrames.Length())
     return NS_ERROR_INVALID_ARG;
 
   imgFrame *frame = GetImgFrame(aFrameNum);
+  NS_ABORT_IF_FALSE(frame, "Calling SetFrameTimeout on frame that doesn't exist!");
   NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
 
   frame->SetTimeout(aTimeout);
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* void setFrameBlendMethod (in unsigned long framenumber, in PRInt32 aBlendMethod); */
 NS_IMETHODIMP imgContainer::SetFrameBlendMethod(PRUint32 aFrameNum, PRInt32 aBlendMethod)
 {
-  if (aFrameNum >= PRUint32(mNumFrames))
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  NS_ABORT_IF_FALSE(aFrameNum < mFrames.Length(), "Invalid frame index!");
+  if (aFrameNum >= mFrames.Length())
     return NS_ERROR_INVALID_ARG;
 
   imgFrame *frame = GetImgFrame(aFrameNum);
+  NS_ABORT_IF_FALSE(frame, "Calling SetFrameBlendMethod on frame that doesn't exist!");
   NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
 
   frame->SetBlendMethod(aBlendMethod);
 
   return NS_OK;
 }
 
 
 //******************************************************************************
 /* void setFrameHasNoAlpha (in unsigned long framenumber); */
 NS_IMETHODIMP imgContainer::SetFrameHasNoAlpha(PRUint32 aFrameNum)
 {
-  if (aFrameNum >= PRUint32(mNumFrames))
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  NS_ABORT_IF_FALSE(aFrameNum < mFrames.Length(), "Invalid frame index!");
+  if (aFrameNum >= mFrames.Length())
     return NS_ERROR_INVALID_ARG;
 
   imgFrame *frame = GetImgFrame(aFrameNum);
+  NS_ABORT_IF_FALSE(frame, "Calling SetFrameHasNoAlpha on frame that doesn't exist!");
   NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
 
   frame->SetHasNoAlpha();
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* void endFrameDecode (in unsigned long framenumber); */
 NS_IMETHODIMP imgContainer::EndFrameDecode(PRUint32 aFrameNum)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   // Assume there's another frame.
   // currentDecodingFrameIndex is 0 based, aFrameNum is 1 based
   if (mAnim)
     mAnim->currentDecodingFrameIndex = aFrameNum;
   
   return NS_OK;
 }
 
 //******************************************************************************
 /* void decodingComplete (); */
 NS_IMETHODIMP imgContainer::DecodingComplete(void)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  // Flag that we're done decoding.
+  // XXX - these should probably be combined when we fix animated image
+  // discarding with bug 500402.
+  mDecoded = PR_TRUE;
   if (mAnim)
     mAnim->doneDecoding = PR_TRUE;
 
-  // If there's only 1 frame, optimize it.
-  // Optimizing animated images is not supported.
-  if (mNumFrames == 1)
-    return mFrames[0]->Optimize();
+  nsresult rv;
+
+  // We now have one of the qualifications for discarding. Re-evaluate.
+  if (CanDiscard()) {
+    NS_ABORT_IF_FALSE(!mDiscardTimer,
+                      "We shouldn't have been discardable before this");
+    rv = ResetDiscardTimer();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // If there's only 1 frame, optimize it. Optimizing animated images
+  // is not supported.
+  //
+  // We don't optimize the frame for multipart images because we reuse
+  // the frame.
+  if ((mFrames.Length() == 1) && !mMultipart) {
+    rv = mFrames[0]->Optimize();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* attribute unsigned short animationMode; */
 NS_IMETHODIMP imgContainer::GetAnimationMode(PRUint16 *aAnimationMode)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   NS_ENSURE_ARG_POINTER(aAnimationMode);
   
   *aAnimationMode = mAnimationMode;
   return NS_OK;
 }
 
 //******************************************************************************
 /* attribute unsigned short animationMode; */
 NS_IMETHODIMP imgContainer::SetAnimationMode(PRUint16 aAnimationMode)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   NS_ASSERTION(aAnimationMode == imgIContainer::kNormalAnimMode ||
                aAnimationMode == imgIContainer::kDontAnimMode ||
                aAnimationMode == imgIContainer::kLoopOnceAnimMode,
                "Wrong Animation Mode is being set!");
   
   switch (mAnimationMode = aAnimationMode) {
     case kDontAnimMode:
       StopAnimation();
       break;
     case kNormalAnimMode:
       if (mLoopCount != 0 || 
-          (mAnim && (mAnim->currentAnimationFrameIndex + 1 < mNumFrames)))
+          (mAnim && (mAnim->currentAnimationFrameIndex + 1 < mFrames.Length())))
         StartAnimation();
       break;
     case kLoopOnceAnimMode:
-      if (mAnim && (mAnim->currentAnimationFrameIndex + 1 < mNumFrames))
+      if (mAnim && (mAnim->currentAnimationFrameIndex + 1 < mFrames.Length()))
         StartAnimation();
       break;
   }
   
   return NS_OK;
 }
 
 //******************************************************************************
 /* void startAnimation () */
 NS_IMETHODIMP imgContainer::StartAnimation()
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   if (mAnimationMode == kDontAnimMode || 
       (mAnim && (mAnim->timer || mAnim->animating)))
     return NS_OK;
   
-  if (mNumFrames > 1) {
+  if (mFrames.Length() > 1) {
     if (!ensureAnimExists())
       return NS_ERROR_OUT_OF_MEMORY;
     
     // Default timeout to 100: the timer notify code will do the right
     // thing, so just get that started.
     PRInt32 timeout = 100;
     imgFrame *currentFrame = GetCurrentImgFrame();
     if (currentFrame) {
@@ -710,16 +1058,19 @@ NS_IMETHODIMP imgContainer::StartAnimati
   
   return NS_OK;
 }
 
 //******************************************************************************
 /* void stopAnimation (); */
 NS_IMETHODIMP imgContainer::StopAnimation()
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   if (mAnim) {
     mAnim->animating = PR_FALSE;
 
     if (!mAnim->timer)
       return NS_OK;
 
     mAnim->timer->Cancel();
     mAnim->timer = nsnull;
@@ -727,140 +1078,134 @@ NS_IMETHODIMP imgContainer::StopAnimatio
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* void resetAnimation (); */
 NS_IMETHODIMP imgContainer::ResetAnimation()
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   if (mAnimationMode == kDontAnimMode || 
       !mAnim || mAnim->currentAnimationFrameIndex == 0)
     return NS_OK;
 
   PRBool oldAnimating = mAnim->animating;
 
   if (mAnim->animating) {
     nsresult rv = StopAnimation();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   mAnim->lastCompositedFrameIndex = -1;
   mAnim->currentAnimationFrameIndex = 0;
-  // Update display
+
+  // Note - We probably want to kick off a redecode somewhere around here when
+  // we fix bug 500402.
+
+  // Update display if we were animating before
   nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(mObserver));
-  if (observer) {
-    nsresult rv = RestoreDiscardedData();
-    NS_ENSURE_SUCCESS(rv, rv);
+  if (oldAnimating && observer)
     observer->FrameChanged(this, &(mAnim->firstFrameRefreshArea));
-  }
 
   if (oldAnimating)
     return StartAnimation();
   return NS_OK;
 }
 
 //******************************************************************************
 /* attribute long loopCount; */
 NS_IMETHODIMP imgContainer::GetLoopCount(PRInt32 *aLoopCount)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   NS_ENSURE_ARG_POINTER(aLoopCount);
   
   *aLoopCount = mLoopCount;
   
   return NS_OK;
 }
 
 //******************************************************************************
 /* attribute long loopCount; */
 NS_IMETHODIMP imgContainer::SetLoopCount(PRInt32 aLoopCount)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   // -1  infinite
   //  0  no looping, one iteration
   //  1  one loop, two iterations
   //  ...
   mLoopCount = aLoopCount;
 
   return NS_OK;
 }
 
-static PRBool
-DiscardingEnabled(void)
+//******************************************************************************
+/* void addSourceData(in nsIInputStream aInputStream, in unsigned long aCount); */
+NS_IMETHODIMP imgContainer::AddSourceData(const char *aBuffer, PRUint32 aCount)
 {
-  static PRBool inited;
-  static PRBool enabled;
-
-  if (!inited) {
-    inited = PR_TRUE;
-
-    enabled = (PR_GetEnv("MOZ_DISABLE_IMAGE_DISCARD") == nsnull);
+  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 "
+                                  "imgContainer!");
+
+  // We should not call this if we're already finished adding source data
+  NS_ABORT_IF_FALSE(!mHasSourceData, "Calling AddSourceData() after calling "
+                                     "sourceDataComplete()!");
+
+  // This call should come straight from necko - no reentrancy allowed
+  NS_ABORT_IF_FALSE(!mInDecoder, "Re-entrant call to AddSourceData!");
+
+  // If we're not storing source data, write it directly to the decoder
+  if (!StoringSourceData()) {
+    rv = WriteToDecoder(aBuffer, aCount);
+    CONTAINER_ENSURE_SUCCESS(rv);
   }
 
-  return enabled;
-}
-
-//******************************************************************************
-/* void setDiscardable(in string mime_type); */
-NS_IMETHODIMP imgContainer::SetDiscardable(const char* aMimeType)
-{
-  NS_ENSURE_ARG_POINTER(aMimeType);
-
-  if (!DiscardingEnabled())
-    return NS_OK;
-
-  if (mDiscardable) {
-    NS_WARNING ("imgContainer::SetDiscardable(): cannot change an imgContainer which is already discardable");
-    return NS_ERROR_FAILURE;
+  // 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 there's a decoder open, that means we want to do more decoding.
+    // Wake up the worker if it's not up already
+    if (mDecoder && !mWorkerPending) {
+      NS_ABORT_IF_FALSE(mWorker, "We should have a worker here!");
+      rv = mWorker->Run();
+      CONTAINER_ENSURE_SUCCESS(rv);
+    }
   }
 
-  mDiscardableMimeType.Assign(aMimeType);
-  mDiscardable = PR_TRUE;
-
-  num_containers_with_discardable_data++;
+  // Statistics
+  total_source_bytes += aCount;
+  if (mDiscardable)
+    discardable_source_bytes += aCount;
   PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
-          ("CompressedImageAccounting: Making imgContainer %p (%s) discardable.  "
-           "Compressed containers: %d, Compressed data bytes: %lld",
+          ("CompressedImageAccounting: Added compressed data to imgContainer %p (%s). "
+           "Total Containers: %d, Discardable containers: %d, "
+           "Total source bytes: %lld, Source bytes for discardable containers %lld",
            this,
-           aMimeType,
-           num_containers_with_discardable_data,
-           num_compressed_image_bytes));
-
-  return NS_OK;
-}
-
-//******************************************************************************
-/* void addRestoreData(in nsIInputStream aInputStream, in unsigned long aCount); */
-NS_IMETHODIMP imgContainer::AddRestoreData(const char *aBuffer, PRUint32 aCount)
-{
-  NS_ENSURE_ARG_POINTER(aBuffer);
-
-  if (!mDiscardable)
-    return NS_OK;
-
-  if (mRestoreDataDone) {
-    /* We are being called from the decoder while the data is being restored
-     * (i.e. we were fully loaded once, then we discarded the image data, then
-     * we are being restored).  We don't want to save the compressed data again,
-     * since we already have it.
-     */
-    return NS_OK;
-  }
-
-  if (!mRestoreData.AppendElements(aBuffer, aCount))
-    return NS_ERROR_OUT_OF_MEMORY;
-
-  num_compressed_image_bytes += aCount;
-
-  PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
-          ("CompressedImageAccounting: Added compressed data to imgContainer %p (%s).  "
-           "Compressed containers: %d, Compressed data bytes: %lld",
-           this,
-           mDiscardableMimeType.get(),
-           num_containers_with_discardable_data,
-           num_compressed_image_bytes));
+           mSourceDataMimeType.get(),
+           num_containers,
+           num_discardable_containers,
+           total_source_bytes,
+           discardable_source_bytes));
 
   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, PRSize data_len)
@@ -875,87 +1220,156 @@ get_header_str (char *buf, char *data, P
     buf[i * 2]     = hex[(data[i] >> 4) & 0x0f];
     buf[i * 2 + 1] = hex[data[i] & 0x0f];
   }
 
   buf[i * 2] = 0;
 }
 
 //******************************************************************************
-/* void restoreDataDone(); */
-NS_IMETHODIMP imgContainer::RestoreDataDone (void)
+/* void sourceDataComplete(); */
+NS_IMETHODIMP imgContainer::SourceDataComplete()
 {
-  // If image is not discardable, don't start discard timer
-  if (!mDiscardable)
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  // If we've been called before, ignore. Otherwise, flag that we have everything
+  if (mHasSourceData)
     return NS_OK;
-
-  if (mRestoreDataDone)
-    return NS_OK;
-
-  mRestoreData.Compact();
-
-  mRestoreDataDone = PR_TRUE;
-
+  mHasSourceData = PR_TRUE;
+
+  // This call should come straight from necko - no reentrancy allowed
+  NS_ABORT_IF_FALSE(!mInDecoder, "Re-entrant call to AddSourceData!");
+
+  // If we're not storing any source data, then all the data was written
+  // directly to the decoder in the AddSourceData() calls. This means we're
+  // done, so we can shut down the decoder.
+  if (!StoringSourceData()) {
+    nsresult rv = ShutdownDecoder(eShutdownIntent_Done);
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // If there's a decoder open, we need to wake up the worker if it's not
+  // already. This is so the worker can account for the fact that the source
+  // data is complete. For some decoders, DecodingComplete() is only called
+  // when the decoder is Close()-ed, and thus the SourceDataComplete() call
+  // is the only way we can transition to a 'decoded' state. Furthermore,
+  // it's always possible for any image type to have the data stream stop
+  // abruptly at any point, in which case we need to trigger an error.
+  if (mDecoder && !mWorkerPending) {
+    NS_ABORT_IF_FALSE(mWorker, "We should have a worker here!");
+    nsresult rv = mWorker->Run();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // Free up any extra space in the backing buffer
+  mSourceData.Compact();
+
+  // Log header information
   if (PR_LOG_TEST(gCompressedImageAccountingLog, PR_LOG_DEBUG)) {
     char buf[9];
-    get_header_str(buf, mRestoreData.Elements(), mRestoreData.Length());
+    get_header_str(buf, mSourceData.Elements(), mSourceData.Length());
     PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
-            ("CompressedImageAccounting: imgContainer::RestoreDataDone() - data is done for container %p (%s), %d real frames (cached as %d frames) - header %p is 0x%s (length %d)",
+            ("CompressedImageAccounting: imgContainer::SourceDataComplete() - data "
+             "is done for container %p (%s) - header %p is 0x%s (length %d)",
              this,
-             mDiscardableMimeType.get(),
-             mFrames.Length (),
-             mNumFrames,
-             mRestoreData.Elements(),
+             mSourceDataMimeType.get(),
+             mSourceData.Elements(),
              buf,
-             mRestoreData.Length()));
+             mSourceData.Length()));
+  }
+
+  // We now have one of the qualifications for discarding. Re-evaluate.
+  if (CanDiscard()) {
+    nsresult rv = ResetDiscardTimer();
+    CONTAINER_ENSURE_SUCCESS(rv);
   }
-
-  return ResetDiscardTimer();
+  return NS_OK;
+}
+
+//******************************************************************************
+/* void newSourceData(); */
+NS_IMETHODIMP imgContainer::NewSourceData()
+{
+  nsresult rv;
+
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  // The source data should be complete before calling this
+  NS_ABORT_IF_FALSE(mHasSourceData,
+                    "Calling NewSourceData before SourceDataComplete!");
+  if (!mHasSourceData)
+    return NS_ERROR_ILLEGAL_VALUE;
+
+  // Only supported for multipart channels. It wouldn't be too hard to change this,
+  // but it would involve making sure that things worked for decode-on-draw and
+  // discarding. Presently there's no need for this, so we don't.
+  NS_ABORT_IF_FALSE(mMultipart, "NewSourceData not supported for multipart");
+  if (!mMultipart)
+    return NS_ERROR_ILLEGAL_VALUE;
+
+  // We're multipart, so we shouldn't be storing source data
+  NS_ABORT_IF_FALSE(!StoringSourceData(),
+                    "Shouldn't be storing source data for multipart");
+
+  // We're not storing the source data and we got SourceDataComplete. We should
+  // have shut down the previous decoder
+  NS_ABORT_IF_FALSE(!mDecoder, "Shouldn't have a decoder in NewSourceData");
+
+  // The decoder was shut down and we didn't flag an error, so we should be decoded
+  NS_ABORT_IF_FALSE(mDecoded, "Should be decoded in NewSourceData");
+
+  // Reset some flags
+  mDecoded = PR_FALSE;
+  mHasSourceData = PR_FALSE;
+
+  // We're decode-on-load here. Open up a new decoder just like what happens when
+  // we call Init() for decode-on-load images.
+  rv = InitDecoder(imgIDecoder::DECODER_FLAG_NONE);
+  CONTAINER_ENSURE_SUCCESS(rv);
+
+  return NS_OK;
 }
 
 //******************************************************************************
 /* void notify(in nsITimer timer); */
 NS_IMETHODIMP imgContainer::Notify(nsITimer *timer)
 {
-  // Note that as long as the image is animated, it will not be discarded, 
-  // so this should never happen...
-  nsresult rv = RestoreDiscardedData();
-  NS_ENSURE_SUCCESS(rv, rv);
-
   // This should never happen since the timer is only set up in StartAnimation()
   // after mAnim is checked to exist.
   NS_ENSURE_TRUE(mAnim, NS_ERROR_UNEXPECTED);
   NS_ASSERTION(mAnim->timer == timer,
                "imgContainer::Notify() called with incorrect timer");
 
   if (!mAnim->animating || !mAnim->timer)
     return NS_OK;
 
   nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(mObserver));
   if (!observer) {
     // the imgRequest that owns us is dead, we should die now too.
     StopAnimation();
     return NS_OK;
   }
 
-  if (mNumFrames == 0)
+  if (mFrames.Length() == 0)
     return NS_OK;
   
   imgFrame *nextFrame = nsnull;
   PRInt32 previousFrameIndex = mAnim->currentAnimationFrameIndex;
-  PRInt32 nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
+  PRUint32 nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
   PRInt32 timeout = 0;
 
   // If we're done decoding the next frame, go ahead and display it now and
   // reinit the timer with the next frame's delay time.
   // currentDecodingFrameIndex is not set until the second frame has
   // finished decoding (see EndFrameDecode)
   if (mAnim->doneDecoding || 
       (nextFrameIndex < mAnim->currentDecodingFrameIndex)) {
-    if (mNumFrames == nextFrameIndex) {
+    if (mFrames.Length() == nextFrameIndex) {
       // End of Animation
 
       // If animation mode is "loop once", it's time to stop animating
       if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
         StopAnimation();
         return NS_OK;
       } else {
         // We may have used compositingFrame to build a frame, and then copied
@@ -1516,323 +1930,670 @@ get_discard_timer_ms (void)
 {
   /* FIXME: don't hardcode this */
   return 15000; /* 15 seconds */
 }
 
 void
 imgContainer::sDiscardTimerCallback(nsITimer *aTimer, void *aClosure)
 {
+  // Retrieve self pointer and null out the expired timer
   imgContainer *self = (imgContainer *) aClosure;
-
-  NS_ASSERTION(aTimer == self->mDiscardTimer,
-               "imgContainer::DiscardTimerCallback() got a callback for an unknown timer");
-
+  NS_ABORT_IF_FALSE(aTimer == self->mDiscardTimer, 
+                    "imgContainer::DiscardTimerCallback() got a callback "
+                    "for an unknown timer");
   self->mDiscardTimer = nsnull;
 
+  // We should be ok for discard
+  NS_ABORT_IF_FALSE(self->CanDiscard(), "Hit discard callback but can't discard!");
+
+  // We should never discard when we have an active decoder
+  NS_ABORT_IF_FALSE(!self->mDecoder, "Discard callback fired with open decoder!");
+
+  // As soon as an image becomes animated, it becomes non-discardable and any
+  // timers are cancelled.
+  NS_ABORT_IF_FALSE(!self->mAnim, "Discard callback fired for animated image!");
+
+  // For post-operation logging
   int old_frame_count = self->mFrames.Length();
 
-  // Don't discard animated images, because we don't handle that very well. (See bug 414259.)
-  if (self->mAnim) {
-    return;
-  }
-
+  // Delete all the decoded frames, then clear the array.
   for (int i = 0; i < old_frame_count; ++i)
     delete self->mFrames[i];
   self->mFrames.Clear();
 
-  self->mDiscarded = PR_TRUE;
-
+  // Flag that we no longer have decoded frames for this image
+  self->mDecoded = PR_FALSE;
+
+  // Notify that we discarded
+  nsCOMPtr<imgIDecoderObserver> observer(do_QueryReferent(self->mObserver));
+  if (observer)
+    observer->OnDiscard(nsnull);
+
+  // Log
   PR_LOG(gCompressedImageAccountingLog, PR_LOG_DEBUG,
-         ("CompressedImageAccounting: discarded uncompressed image data from imgContainer %p (%s) - %d frames (cached count: %d); "
-          "Compressed containers: %d, Compressed data bytes: %lld",
+         ("CompressedImageAccounting: discarded uncompressed image "
+          "data from imgContainer %p (%s) - %d frames (cached count: %d); "
+          "Total Containers: %d, Discardable containers: %d, "
+          "Total source bytes: %lld, Source bytes for discardable containers %lld",
           self,
-          self->mDiscardableMimeType.get(),
+          self->mSourceDataMimeType.get(),
           old_frame_count,
-          self->mNumFrames,
-          num_containers_with_discardable_data,
-          num_compressed_image_bytes));
+          self->mFrames.Length(),
+          num_containers,
+          num_discardable_containers,
+          total_source_bytes,
+          discardable_source_bytes));
 }
 
 nsresult
-imgContainer::ResetDiscardTimer (void)
+imgContainer::ResetDiscardTimer()
 {
-  if (!mRestoreDataDone)
-    return NS_OK;
-
+  // We should not call this function if we can't discard
+  NS_ABORT_IF_FALSE(CanDiscard(), "Calling ResetDiscardTimer but can't discard!");
+
+  // As soon as an image becomes animated it is set non-discardable
+  NS_ABORT_IF_FALSE(!mAnim, "Trying to reset discard timer on animated image!");
+
+  // If we have a timer already ticking, cancel it
   if (mDiscardTimer) {
-    /* Cancel current timer */
     nsresult rv = mDiscardTimer->Cancel();
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+    CONTAINER_ENSURE_SUCCESS(rv);
     mDiscardTimer = nsnull;
   }
 
-  /* Don't activate timer when we are animating... */
-  if (mAnim && mAnim->animating)
-    return NS_OK;
-
-  if (!mDiscardTimer) {
-    mDiscardTimer = do_CreateInstance("@mozilla.org/timer;1");
-    NS_ENSURE_TRUE(mDiscardTimer, NS_ERROR_OUT_OF_MEMORY);
-  }
-
+  // Create a new timer
+  mDiscardTimer = do_CreateInstance("@mozilla.org/timer;1");
+  CONTAINER_ENSURE_TRUE(mDiscardTimer, NS_ERROR_OUT_OF_MEMORY);
+
+  // Activate the timer
   return mDiscardTimer->InitWithFuncCallback(sDiscardTimerCallback,
                                              (void *) this,
                                              get_discard_timer_ms (),
                                              nsITimer::TYPE_ONE_SHOT);
 }
 
+// Helper method to determine if we can discard an image
+PRBool
+imgContainer::CanDiscard() {
+  return (DiscardingEnabled() && // Globally enabled...
+          mDiscardable &&        // ...Enabled at creation time...
+          (mLockCount == 0) &&   // ...not temporarily disabled...
+          mHasSourceData &&      // ...have the source data...
+          mDecoded);             // ...and have something to discard.
+}
+
+// Helper method to determine if we're storing the source data in a buffer
+// or just writing it directly to the decoder
+PRBool
+imgContainer::StoringSourceData() {
+  return (mDecodeOnDraw || mDiscardable);
+}
+
+
+// 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
-imgContainer::RestoreDiscardedData(void)
+imgContainer::InitDecoder (PRUint32 dFlags)
+{
+  // 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!");
+
+  // Since we're not decoded, we should not have a discard timer active
+  NS_ABORT_IF_FALSE(!mDiscardTimer, "Discard Timer active in InitDecoder()!");
+
+  // Find and instantiate the decoder
+  nsCAutoString decoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/decoder;3?type=") +
+                                              mSourceDataMimeType);
+  mDecoder = do_CreateInstance(decoderCID.get());
+  CONTAINER_ENSURE_TRUE(mDecoder, NS_IMAGELIB_ERROR_NO_DECODER);
+
+  // Store the flags for this decoder
+  mDecoderFlags = dFlags;
+
+  // Initialize the decoder
+  nsCOMPtr<imgIDecoderObserver> observer(do_QueryReferent(mObserver));
+  nsresult result = mDecoder->Init(this, observer, dFlags);
+  CONTAINER_ENSURE_SUCCESS(result);
+
+  // Create an nsIInputStream for the data. Because nsIStringInputStreams don't
+  // like their dependent data to grow dynamically, we reset the stream to the
+  // proper buffer each time we write data to the decoder. Nevertheless, it's
+  // worth keeping the structure around to avoid needless construction and
+  // destruction.
+  mDecoderInput = do_CreateInstance("@mozilla.org/io/string-input-stream;1");
+  CONTAINER_ENSURE_TRUE(mDecoderInput, NS_ERROR_OUT_OF_MEMORY);
+
+  // Create a decode worker
+  mWorker = new imgDecodeWorker(this);
+  CONTAINER_ENSURE_TRUE(mWorker, NS_ERROR_OUT_OF_MEMORY);
+
+  return NS_OK;
+}
+
+// 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.
+// 
+// aIntent specifies the intent of the shutdown. If aIntent is
+// eShutdownIntent_Done, an error is flagged if we didn't get what we should
+// have out of the decode. If aIntent is eShutdownIntent_Interrupted, we don't
+// check this. If aIntent is eShutdownIntent_Error, we shut down in error mode.
+nsresult
+imgContainer::ShutdownDecoder(eShutdownIntent aIntent)
 {
-  // mRestoreDataDone = PR_TRUE means that we want to timeout and then discard the image frames
-  // So, we only need to restore, if mRestoreDataDone is true, and then only when the frames are discarded...
-  if (!mRestoreDataDone) 
-    return NS_OK;
-
-  // Reset timer, as the frames are accessed
-  nsresult rv = ResetDiscardTimer();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!mDiscarded)
+  // Ensure that our intent is valid
+  NS_ABORT_IF_FALSE((aIntent >= 0) || (aIntent < eShutdownIntent_AllCount),
+                    "Invalid shutdown intent");
+
+  // Ensure that the decoder is initialized
+  NS_ABORT_IF_FALSE(mDecoder, "Calling ShutdownDecoder() with no active decoder!");
+
+  nsresult rv;
+
+  // If we're "done" _and_ it's a full decode, flush
+  if ((aIntent == eShutdownIntent_Done) &&
+      !(mDecoderFlags && imgIDecoder::DECODER_FLAG_HEADERONLY)) {
+    mInDecoder = PR_TRUE;
+    rv = mDecoder->Flush();
+    mInDecoder = PR_FALSE;
+
+    // The error case here is a bit tricky. We flag an error, which takes us
+    // back into this function, and then we return.
+    if (NS_FAILED(rv)) {
+      DoError();
+      return rv;
+    }
+  }
+
+  // Close the decoder with the appropriate flags
+  mInDecoder = PR_TRUE;
+  PRUint32 closeFlags = (aIntent == eShutdownIntent_Error)
+                          ? (PRUint32) imgIDecoder::CLOSE_FLAG_DONTNOTIFY
+                          : 0;
+  rv = mDecoder->Close(closeFlags);
+  mInDecoder = PR_FALSE;
+
+  // null out the decoder, _then_ check for errors on the close (otherwise the
+  // error routine might re-invoke ShutdownDecoder)
+  mDecoder = nsnull;
+  if (NS_FAILED(rv)) {
+    DoError();
+    return rv;
+  }
+
+  // Get rid of the stream
+  mDecoderInput = nsnull;
+
+  // Kill off the worker
+  mWorker = nsnull;
+
+  // We just shut down the decoder. If we didn't get what we want, but expected
+  // to, flag an error
+  PRBool failed = PR_FALSE;
+  if ((mDecoderFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) && !mHasSize)
+    failed = PR_TRUE;
+  if (!(mDecoderFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) && !mDecoded)
+    failed = PR_TRUE;
+  if ((aIntent == eShutdownIntent_Done) && failed) {
+    DoError();
+    return NS_ERROR_FAILURE;
+  }
+
+  // Clear the flags
+  mDecoderFlags = imgIDecoder::DECODER_FLAG_NONE;
+
+  // Reset number of decoded bytes
+  mBytesDecoded = 0;
+
+  return NS_OK;
+}
+
+// Wraps a shared stream around the data and passes the stream to the decoder,
+// updating the total number of bytes written.
+nsresult
+imgContainer::WriteToDecoder(const char *aBuffer, PRUint32 aCount)
+{
+  // We should have a decoder
+  NS_ABORT_IF_FALSE(mDecoder, "Trying to write to null decoder!");
+
+  // Wrap a shared stream around the data
+  nsresult rv = mDecoderInput->ShareData(aBuffer, aCount);
+  CONTAINER_ENSURE_SUCCESS(rv);
+
+  // Write
+  mInDecoder = PR_TRUE;
+  rv = mDecoder->WriteFrom(mDecoderInput, aCount);
+  mInDecoder = PR_FALSE;
+  CONTAINER_ENSURE_SUCCESS(rv);
+
+  // Keep track of the total number of bytes written over the lifetime of the
+  // decoder
+  mBytesDecoded += aCount;
+
+  return NS_OK;
+}
+
+// This function is called in situations where it's clear that we want the
+// frames in decoded form (Draw, GetFrame, CopyFrame, ExtractFrame, 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
+imgContainer::WantDecodedFrames()
+{
+  nsresult rv;
+
+  // If we can discard, we should have a timer already. reset it.
+  if (CanDiscard()) {
+    NS_ABORT_IF_FALSE(mDiscardTimer, "Decoded and discardable but timer not set!");
+    rv = ResetDiscardTimer();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // Request a decode (no-op if we're decoded)
+  return RequestDecode();
+}
+
+//******************************************************************************
+/* void requestDecode() */
+NS_IMETHODIMP
+imgContainer::RequestDecode()
+{
+  nsresult rv;
+
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  // If we're not storing source data, we have nothing to do
+  if (!StoringSourceData())
     return NS_OK;
 
-  int num_expected_frames = mNumFrames;
-
-  // To prevent that ReloadImages is called multiple times, reset the flag before reloading
-  mDiscarded = PR_FALSE;
-
-  rv = ReloadImages();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  NS_ASSERTION (mNumFrames == PRInt32(mFrames.Length()),
-                "number of restored image frames doesn't match");
-  NS_ASSERTION (num_expected_frames == mNumFrames,
-                "number of restored image frames doesn't match the original number of frames!");
-  
-  PR_LOG (gCompressedImageAccountingLog, PR_LOG_DEBUG,
-          ("CompressedImageAccounting: imgContainer::RestoreDiscardedData() restored discarded data "
-           "for imgContainer %p (%s) - %d image frames.  "
-           "Compressed containers: %d, Compressed data bytes: %lld",
-           this,
-           mDiscardableMimeType.get(),
-           mNumFrames,
-           num_containers_with_discardable_data,
-           num_compressed_image_bytes));
-
+  // If we're fully decoded, we have nothing to do
+  if (mDecoded)
+    return NS_OK;
+
+  // If we're within the decoder, request asynchronously
+  if (mInDecoder) {
+    nsRefPtr<imgDecodeRequestor> requestor = new imgDecodeRequestor(this);
+    if (!requestor)
+      return NS_ERROR_OUT_OF_MEMORY;
+    return NS_DispatchToCurrentThread(requestor);
+  }
+
+
+  // If we have a header-only decoder open, interrupt it and shut it down
+  if (mDecoder && (mDecoderFlags & imgIDecoder::DECODER_FLAG_HEADERONLY)) {
+    rv = ShutdownDecoder(eShutdownIntent_Interrupted);
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // If we don't have a decoder, create one 
+  if (!mDecoder) {
+    NS_ABORT_IF_FALSE(mFrames.IsEmpty(), "Trying to decode to non-empty frame-array");
+    rv = InitDecoder(imgIDecoder::DECODER_FLAG_NONE);
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // If we already have a pending worker, we're done
+  if (mWorkerPending)
+    return NS_OK;
+
+  // If we've read all the data we have, we're done
+  if (mBytesDecoded == mSourceData.Length())
+    return NS_OK;
+
+  // If we get this far, dispatch the worker. We do this instead of starting
+  // any immediate decoding so that actions like tabbing-over to a tab with
+  // large undecoded images don't incur an annoying lag.
+  return mWorker->Dispatch();
+}
+
+// Synchronously decodes as much data as possible
+nsresult
+imgContainer::SyncDecode()
+{
+  nsresult rv;
+
+  // If we're decoded already, no worries
+  if (mDecoded)
+    return NS_OK;
+
+  // If we're not storing source data, there isn't much to do here
+  if (!StoringSourceData())
+    return NS_OK;
+
+  // We really have no good way of forcing a synchronous decode if we're being
+  // called in a re-entrant manner (ie, from an event listener fired by a
+  // decoder), because the decoding machinery is already tied up. The best we
+  // can do is assert that it doesn't happen for debug builds (if it does, we
+  // need to rethink the layout code that does it), and fail for release builds.
+  NS_ABORT_IF_FALSE(!mInDecoder, "Yikes, forcing sync in reentrant call!");
+  if (mInDecoder)
+    return NS_ERROR_FAILURE;
+
+  // If we have a header-only decoder open, shut it down
+  if (mDecoder && (mDecoderFlags & imgIDecoder::DECODER_FLAG_HEADERONLY)) {
+    rv = ShutdownDecoder(eShutdownIntent_Interrupted);
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // If we don't have a decoder, create one 
+  if (!mDecoder) {
+    NS_ABORT_IF_FALSE(mFrames.IsEmpty(), "Trying to decode to non-empty frame-array");
+    rv = InitDecoder(imgIDecoder::DECODER_FLAG_NONE);
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // Write everything we have
+  rv = WriteToDecoder(mSourceData.Elements() + mBytesDecoded,
+                      mSourceData.Length() - mBytesDecoded);
+  CONTAINER_ENSURE_SUCCESS(rv);
+
+  // If we finished the decode, shutdown the decoder
+  if (IsDecodeFinished()) {
+    rv = ShutdownDecoder(eShutdownIntent_Done);
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // All good!
   return NS_OK;
 }
 
 //******************************************************************************
-/* [noscript] void draw(in gfxContext aContext, in gfxGraphicsFilter aFilter, in gfxMatrix aUserSpaceToImageSpace, in gfxRect aFill, in nsIntRect aSubimage); */ 
+/* [noscript] void draw(in gfxContext aContext, in gfxGraphicsFilter aFilter,
+ * in gfxMatrix aUserSpaceToImageSpace, in gfxRect aFill, in nsIntRect aSubimage,
+ * in PRUint32 aFlags); */ 
 NS_IMETHODIMP imgContainer::Draw(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter, 
                                  gfxMatrix &aUserSpaceToImageSpace, gfxRect &aFill,
-                                 nsIntRect &aSubimage)
+                                 nsIntRect &aSubimage, PRUint32 aFlags)
 {
+  if (mError)
+    return NS_ERROR_FAILURE;
+
   NS_ENSURE_ARG_POINTER(aContext);
 
+  // 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);
+  }
+
   imgFrame *frame = GetCurrentImgFrame();
-  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+  if (!frame) {
+    NS_ABORT_IF_FALSE(!mDecoded, "Decoded but frame not available?");
+    return NS_OK; // Getting the frame (above) touches the image and kicks off decoding
+  }
 
   nsIntRect framerect = frame->GetRect();
   nsIntMargin padding(framerect.x, framerect.y, 
                       mSize.width - framerect.XMost(),
                       mSize.height - framerect.YMost());
 
   frame->Draw(aContext, aFilter, aUserSpaceToImageSpace, aFill, padding, aSubimage);
 
   return NS_OK;
 }
 
-class ContainerLoader : public imgILoad,
-                        public imgIDecoderObserver,
-                        public nsSupportsWeakReference
-{
-public:
-
-  NS_DECL_ISUPPORTS
-  NS_DECL_IMGILOAD
-  NS_DECL_IMGIDECODEROBSERVER
-  NS_DECL_IMGICONTAINEROBSERVER
-
-  ContainerLoader(void);
-
-private:
-
-  nsCOMPtr<imgIContainer> mContainer;
-};
-
-NS_IMPL_ISUPPORTS4 (ContainerLoader, imgILoad, imgIDecoderObserver, imgIContainerObserver, nsISupportsWeakReference)
-
-ContainerLoader::ContainerLoader (void)
+//******************************************************************************
+/* void lockImage() */
+NS_IMETHODIMP
+imgContainer::LockImage()
 {
-}
-
-/* Implement imgILoad::image getter */
-NS_IMETHODIMP
-ContainerLoader::GetImage(imgIContainer **aImage)
-{
-  *aImage = mContainer;
-  NS_IF_ADDREF (*aImage);
-  return NS_OK;
-}
-
-/* Implement imgILoad::image setter */
-NS_IMETHODIMP
-ContainerLoader::SetImage(imgIContainer *aImage)
-{
-  mContainer = aImage;
-  return NS_OK;
-}
-
-/* Implement imgILoad::isMultiPartChannel getter */
-NS_IMETHODIMP
-ContainerLoader::GetIsMultiPartChannel(PRBool *aIsMultiPartChannel)
-{
-  *aIsMultiPartChannel = PR_FALSE; /* FIXME: is this always right? */
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  // Cancel the discard timer if it's there
+  if (mDiscardTimer) {
+    mDiscardTimer->Cancel();
+    mDiscardTimer = nsnull; // It's wasteful to null out the discard timers each
+                            // time, but we'll wait to fix that until bug 502694.
+  }
+
+  // Increment the lock count
+  mLockCount++;
+
   return NS_OK;
 }
 
-/* Implement imgIDecoderObserver::onStartRequest() */
-NS_IMETHODIMP
-ContainerLoader::OnStartRequest(imgIRequest *aRequest)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStartDecode() */
-NS_IMETHODIMP
-ContainerLoader::OnStartDecode(imgIRequest *aRequest)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStartContainer() */
-NS_IMETHODIMP
-ContainerLoader::OnStartContainer(imgIRequest *aRequest, imgIContainer *aContainer)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStartFrame() */
+//******************************************************************************
+/* void unlockImage() */
 NS_IMETHODIMP
-ContainerLoader::OnStartFrame(imgIRequest *aRequest, PRUint32 aFrame)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onDataAvailable() */
-NS_IMETHODIMP
-ContainerLoader::OnDataAvailable(imgIRequest *aRequest, PRBool aCurrentFrame, const nsIntRect * aRect)
+imgContainer::UnlockImage()
 {
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStopFrame() */
-NS_IMETHODIMP
-ContainerLoader::OnStopFrame(imgIRequest *aRequest, PRUint32 aFrame)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStopContainer() */
-NS_IMETHODIMP
-ContainerLoader::OnStopContainer(imgIRequest *aRequest, imgIContainer *aContainer)
-{
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  // It's an error to call this function if the lock count is 0
+  NS_ABORT_IF_FALSE(mLockCount > 0,
+                    "Calling UnlockImage with mLockCount == 0!");
+  if (mLockCount == 0)
+    return NS_ERROR_ABORT;
+
+  // We're locked, so we shouldn't have a discard timer set
+  NS_ABORT_IF_FALSE(!mDiscardTimer, "Locked, but discard timer set!");
+
+  // Decrement our lock count
+  mLockCount--;
+
+  // We now _might_ have one of the qualifications for discarding. Re-evaluate.
+  if (CanDiscard()) {
+    nsresult rv = ResetDiscardTimer();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
   return NS_OK;
 }
 
-/* Implement imgIDecoderObserver::onStopDecode() */
-NS_IMETHODIMP
-ContainerLoader::OnStopDecode(imgIRequest *aRequest, nsresult status, const PRUnichar *statusArg)
+// Flushes up to aMaxBytes to the decoder.
+nsresult
+imgContainer::DecodeSomeData (PRUint32 aMaxBytes)
+{
+  // We should have a decoder if we get here
+  NS_ABORT_IF_FALSE(mDecoder, "trying to decode without decoder!");
+
+  // If we have nothing to decode, return
+  if (mBytesDecoded == mSourceData.Length())
+    return NS_OK;
+
+
+  // write the proper amount of data
+  PRUint32 bytesToDecode = PR_MIN(aMaxBytes,
+                                  mSourceData.Length() - mBytesDecoded);
+  nsresult rv = WriteToDecoder(mSourceData.Elements() + mBytesDecoded,
+                               bytesToDecode);
+
+  return rv;
+}
+
+// There are various indicators that tell us we're finished with the decode
+// task at hand and can shut down the decoder.
+PRBool imgContainer::IsDecodeFinished()
 {
-  return NS_OK;
+  // Assume it's not finished
+  PRBool decodeFinished = PR_FALSE;
+
+  // There shouldn't be any reason to call this if we're not storing
+  // source data
+  NS_ABORT_IF_FALSE(StoringSourceData(),
+                    "just shut down on SourceDataComplete!");
+
+  // The decode is complete if we got what we wanted...
+  if (mDecoderFlags & imgIDecoder::DECODER_FLAG_HEADERONLY) {
+    if (mHasSize)
+      decodeFinished = PR_TRUE;
+  }
+  else {
+    if (mDecoded)
+      decodeFinished = PR_TRUE;
+  }
+
+  // ...or if we have all the source data and wrote all the source data.
+  //
+  // (NB - This can be distinct from the above case even for non-erroneous
+  // images because the decoder might not call DecodingComplete() until we
+  // call Close() in ShutdownDecoder())
+  if (mHasSourceData && (mBytesDecoded == mSourceData.Length()))
+    decodeFinished = PR_TRUE;
+
+  return decodeFinished;
+}
+
+// Indempotent error flagging routine. If a decoder is open,
+// sends OnStopContainer and OnStopDecode and shuts down the decoder
+void imgContainer::DoError()
+{
+  // If we've flagged an error before, we have nothing to do
+  if (mError)
+    return;
+
+  // If we're mid-decode
+  if (mDecoder) {
+
+    // grab the observer and give an OnStopContainer and an OnStopDecode
+    nsCOMPtr<imgIDecoderObserver> observer = do_QueryReferent(mObserver);
+    if (observer) {
+      observer->OnStopContainer(nsnull, this);
+      observer->OnStopDecode(nsnull, NS_ERROR_FAILURE, nsnull);
+    }
+
+    // Shutdown the decoder in error mode. We don't care if this flags other
+    // errors.
+    (void) ShutdownDecoder(eShutdownIntent_Error);
+  }
+
+  // Put the container in an error state
+  mError = PR_TRUE;
+
+  // Log our error
+  LOG_CONTAINER_ERROR;
 }
 
-/* Implement imgIDecoderObserver::onStopRequest() */
-NS_IMETHODIMP
-ContainerLoader::OnStopRequest(imgIRequest *aRequest, PRBool aIsLastPart)
+// Tweakable progressive decoding parameters
+#define DECODE_BYTES_AT_A_TIME 4096
+#define MAX_USEC_BEFORE_YIELD (1000 * 5)
+
+// Decodes some data, then re-posts itself to the end of the event queue if
+// there's more processing to be done
+NS_IMETHODIMP imgDecodeWorker::Run()
 {
-  return NS_OK;
-}
-
-/* implement imgIContainerObserver::frameChanged() */
-NS_IMETHODIMP
-ContainerLoader::FrameChanged(imgIContainer *aContainer, nsIntRect * aDirtyRect)
-{
+  nsresult rv;
+
+  // If we shutdown the decoder in this function, we could lose ourselves
+  nsCOMPtr<nsIRunnable> kungFuDeathGrip(this);
+
+  // The container holds a strong reference to us. Cycles are bad.
+  nsCOMPtr<imgIContainer> iContainer(do_QueryReferent(mContainer));
+  if (!iContainer)
+    return NS_OK;
+  imgContainer* container = static_cast<imgContainer*>(iContainer.get());
+
+  NS_ABORT_IF_FALSE(container->mInitialized,
+                    "Worker active for uninitialized container!");
+
+  // If we were pending, we're not anymore
+  container->mWorkerPending = PR_FALSE;
+
+  // If an error is flagged, it probably happened while we were waiting
+  // in the event queue. Bail early, but no need to bother the run queue
+  // by returning an error.
+  if (container->mError)
+    return NS_OK;
+
+  // If we don't have a decoder, we must have finished already (for example,
+  // a synchronous decode request came while the worker was pending).
+  if (!container->mDecoder)
+    return NS_OK;
+
+  // Header-only decodes are cheap and we more or less want them to be
+  // synchronous. Write all the data in that case, otherwise write a
+  // chunk
+  PRUint32 maxBytes =
+    (container->mDecoderFlags & imgIDecoder::DECODER_FLAG_HEADERONLY)
+    ? container->mSourceData.Length() : DECODE_BYTES_AT_A_TIME;
+
+  // Loop control
+  PRBool haveMoreData = PR_TRUE;
+  nsTime deadline(PR_Now() + MAX_USEC_BEFORE_YIELD);
+
+  // We keep decoding chunks until one of three possible events occur:
+  // 1) We don't have any data left to decode
+  // 2) The decode completes
+  // 3) We hit the deadline and need to yield to keep the UI snappy
+  while (haveMoreData && !container->IsDecodeFinished() &&
+         (nsTime(PR_Now()) < deadline)) {
+
+    // Decode a chunk of data
+    rv = container->DecodeSomeData(maxBytes);
+    if (NS_FAILED(rv)) {
+      container->DoError();
+      return rv;
+    }
+
+    // Figure out if we still have more data
+    haveMoreData =
+      container->mSourceData.Length() > container->mBytesDecoded;
+  }
+
+  // If the decode finished, shutdown the decoder
+  if (container->IsDecodeFinished()) {
+    rv = container->ShutdownDecoder(imgContainer::eShutdownIntent_Done);
+    if (NS_FAILED(rv)) {
+      container->DoError();
+      return rv;
+    }
+  }
+
+  // If Conditions 1 & 2 are still true, then the only reason we bailed was
+  // because we hit the deadline. Repost ourselves to the end of the event
+  // queue.
+  if (!container->IsDecodeFinished() && haveMoreData)
+    return this->Dispatch();
+
+  // Otherwise, return success
   return NS_OK;
 }
 
-nsresult
-imgContainer::ReloadImages(void)
+// Queues the worker up at the end of the event queue
+NS_METHOD imgDecodeWorker::Dispatch()
 {
-  NS_ASSERTION(!mRestoreData.IsEmpty(),
-               "imgContainer::ReloadImages(): mRestoreData should not be empty");
-  NS_ASSERTION(mRestoreDataDone,
-               "imgContainer::ReloadImages(): mRestoreDataDone shoudl be true!");
-
-  mNumFrames = 0;
-  NS_ASSERTION(mFrames.Length() == 0,
-               "imgContainer::ReloadImages(): mFrames should be empty");
-
-  nsCAutoString decoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/decoder;2?type=") + mDiscardableMimeType);
-
-  nsCOMPtr<imgIDecoder> decoder = do_CreateInstance(decoderCID.get());
-  if (!decoder) {
-    PR_LOG(gCompressedImageAccountingLog, PR_LOG_WARNING,
-           ("CompressedImageAccounting: imgContainer::ReloadImages() could not create decoder for %s",
-            mDiscardableMimeType.get()));
-    return NS_IMAGELIB_ERROR_NO_DECODER;
-  }
-
-  nsCOMPtr<imgILoad> loader = new ContainerLoader();
-  if (!loader) {
-    PR_LOG(gCompressedImageAccountingLog, PR_LOG_WARNING,
-           ("CompressedImageAccounting: imgContainer::ReloadImages() could not allocate ContainerLoader "
-            "when reloading the images for container %p",
-            this));
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  loader->SetImage(this);
-
-  nsresult result = decoder->Init(loader);
-  if (NS_FAILED(result)) {
-    PR_LOG(gCompressedImageAccountingLog, PR_LOG_WARNING,
-           ("CompressedImageAccounting: imgContainer::ReloadImages() image container %p "
-            "failed to initialize the decoder (%s)",
-            this,
-            mDiscardableMimeType.get()));
-    return result;
-  }
-
-  nsCOMPtr<nsIInputStream> stream;
-  result = NS_NewByteInputStream(getter_AddRefs(stream), mRestoreData.Elements(), mRestoreData.Length(), NS_ASSIGNMENT_DEPEND);
-  NS_ENSURE_SUCCESS(result, result);
-
-  if (PR_LOG_TEST(gCompressedImageAccountingLog, PR_LOG_DEBUG)) {
-    char buf[9];
-    get_header_str(buf, mRestoreData.Elements(), mRestoreData.Length());
-    PR_LOG(gCompressedImageAccountingLog, PR_LOG_WARNING,
-           ("CompressedImageAccounting: imgContainer::ReloadImages() starting to restore images for container %p (%s) - "
-            "header %p is 0x%s (length %d)",
-            this,
-            mDiscardableMimeType.get(),
-            mRestoreData.Elements(),
-            buf,
-            mRestoreData.Length()));
-  }
-
-  // |WriteFrom()| may fail if the original data is broken.
-  PRUint32 written;
-  (void)decoder->WriteFrom(stream, mRestoreData.Length(), &written);
-
-  result = decoder->Flush();
-  NS_ENSURE_SUCCESS(result, result);
-
-  result = decoder->Close();
-  NS_ENSURE_SUCCESS(result, result);
-
-  NS_ASSERTION(PRInt32(mFrames.Length()) == mNumFrames,
-               "imgContainer::ReloadImages(): the restored mFrames.Length() doesn't match mNumFrames!");
-
-  return result;
+  // The container holds a strong reference to us. Cycles are bad.
+  nsCOMPtr<imgIContainer> iContainer(do_QueryReferent(mContainer));
+  if (!iContainer)
+    return NS_OK;
+  imgContainer* container = static_cast<imgContainer*>(iContainer.get());
+
+  // We should not be called if there's already a pending worker
+  NS_ABORT_IF_FALSE(!container->mWorkerPending,
+                    "Trying to queue up worker with one already pending!");
+
+  // Flag that we're pending
+  container->mWorkerPending = PR_TRUE;
+
+  // Dispatch
+  return NS_DispatchToCurrentThread(this);
 }
+
+// nsIInputStream callback to copy the incoming image data directly to the 
+// container without processing. The imgContainer is passed as the closure.
+// Always reads everything it gets, even if the data is erroneous.
+NS_METHOD
+imgContainer::WriteToContainer(nsIInputStream* in, void* closure,
+                               const char* fromRawSegment, PRUint32 toOffset,
+                               PRUint32 count, PRUint32 *writeCount)
+{
+  // Retrieve the imgContainer
+  imgIContainer *container = static_cast<imgIContainer*>(closure);
+
+  // Copy the source data. 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.
+  (void) container->AddSourceData(fromRawSegment, count);
+
+  // We wrote everything we got
+  *writeCount = count;
+
+  return NS_OK;
+}
--- a/modules/libpr0n/src/imgContainer.h
+++ b/modules/libpr0n/src/imgContainer.h
@@ -19,16 +19,17 @@
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Stuart Parmenter <pavlov@netscape.com>
  *   Chris Saari <saari@netscape.com>
  *   Federico Mena-Quintero <federico@novell.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -50,21 +51,24 @@
  */
 
 #ifndef __imgContainer_h__
 #define __imgContainer_h__
 
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "imgIContainer.h"
+#include "imgIDecoder.h"
 #include "nsIProperties.h"
 #include "nsITimer.h"
 #include "nsWeakReference.h"
 #include "nsTArray.h"
+#include "nsIStringStream.h"
 #include "imgFrame.h"
+#include "nsThreadUtils.h"
 
 #define NS_IMGCONTAINER_CID \
 { /* c76ff2c1-9bf6-418a-b143-3340c00112f7 */         \
      0x376ff2c1,                                     \
      0x9bf6,                                         \
      0x418a,                                         \
     {0xb1, 0x43, 0x33, 0x40, 0xc0, 0x01, 0x12, 0xf7} \
 }
@@ -126,37 +130,44 @@
  * The mAnim structure has members only needed for animated images, so
  * it's not allocated until the second frame is added.
  *
  * @note
  * mAnimationMode, mLoopCount and mObserver are not in the mAnim structure
  * because the first two have public setters and the observer we only get
  * in Init().
  */
+class imgDecodeWorker;
 class imgContainer : public imgIContainer, 
-                     public nsITimerCallback, 
-                     public nsIProperties
+                     public nsITimerCallback,
+                     public nsIProperties,
+                     public nsSupportsWeakReference
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_IMGICONTAINER
   NS_DECL_NSITIMERCALLBACK
   NS_DECL_NSIPROPERTIES
 
   imgContainer();
   virtual ~imgContainer();
 
+  static NS_METHOD WriteToContainer(nsIInputStream* in, void* closure,
+                                    const char* fromRawSegment,
+                                    PRUint32 toOffset, PRUint32 count,
+                                    PRUint32 *writeCount);
+
 private:
   struct Anim
   {
     //! Area of the first frame that needs to be redrawn on subsequent loops.
     nsIntRect                  firstFrameRefreshArea;
     // Note this doesn't hold a proper value until frame 2 finished decoding.
-    PRInt32                    currentDecodingFrameIndex; // 0 to numFrames-1
-    PRInt32                    currentAnimationFrameIndex; // 0 to numFrames-1
+    PRUint32                   currentDecodingFrameIndex; // 0 to numFrames-1
+    PRUint32                   currentAnimationFrameIndex; // 0 to numFrames-1
     //! Track the last composited frame for Optimizations (See DoComposite code)
     PRInt32                    lastCompositedFrameIndex;
     //! Whether we can assume there will be no more frames
     //! (and thus loop the animation)
     PRBool                     doneDecoding;
     //! Are we currently animating the image?
     PRBool                     animating;
     /** For managing blending of frames
@@ -192,21 +203,36 @@ private:
     {
       if (timer)
         timer->Cancel();
     }
   };
 
   imgFrame* GetImgFrame(PRUint32 framenum);
   imgFrame* GetCurrentImgFrame();
-  PRInt32 GetCurrentImgFrameIndex() const;
+  PRUint32 GetCurrentImgFrameIndex() const;
   
-  inline Anim* ensureAnimExists() {
-    if (!mAnim)
+  inline Anim* ensureAnimExists()
+  {
+    if (!mAnim) {
+
+      // Create the animation context
       mAnim = new Anim();
+
+      // We don't support discarding animated images (See bug 414259)
+      // Flag that we are no longer discardable (if we were before) 
+      // and cancel any discard timer.
+      mDiscardable = PR_FALSE;
+      if (mDiscardTimer) {
+        nsresult rv = mDiscardTimer->Cancel();
+        if (!NS_SUCCEEDED(rv))
+          NS_WARNING("Discard Timer failed to cancel!");
+        mDiscardTimer = nsnull;
+      }
+    }
     return mAnim;
   }
   
   /** Function for doing the frame compositing of animations
    *
    * @param aFrameToUse Set by DoComposite
    *                   (aNextFrame, compositingFrame, or compositingPrevFrame)
    * @param aDirtyRect  Area that the display will need to update
@@ -251,45 +277,143 @@ private:
   nsresult InternalAddFrame(PRUint32 framenum, PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight,
                             gfxASurface::gfxImageFormat aFormat, PRUint8 aPaletteDepth,
                             PRUint8 **imageData, PRUint32 *imageLength,
                             PRUint32 **paletteData, PRUint32 *paletteLength);
 
 private: // data
 
   nsIntSize                  mSize;
+  PRBool                     mHasSize;
   
   //! All the frames of the image
-  // *** IMPORTANT: if you use mFrames in a method, call RestoreDiscardedData() first to ensure
-  //     that the frames actually exist (they may have been discarded to save memory).
+  // IMPORTANT: if you use mFrames in a method, call EnsureImageIsDecoded() first 
+  // to ensure that the frames actually exist (they may have been discarded to save
+  // memory, or we may be decoding on draw).
   nsTArray<imgFrame *>       mFrames;
-  int                        mNumFrames; /* stored separately from mFrames.Count() to support discarded images */
   
   nsCOMPtr<nsIProperties>    mProperties;
 
-  // *** IMPORTANT: if you use mAnim in a method, call RestoreDiscardedData() first to ensure
-  //     that the frames actually exist (they may have been discarded to save memory).
+  // 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).
   imgContainer::Anim*        mAnim;
   
   //! See imgIContainer for mode constants
   PRUint16                   mAnimationMode;
   
   //! # loops remaining before animation stops (-1 no stop)
   PRInt32                    mLoopCount;
   
-  //! imgIContainerObserver
+  //! imgIDecoderObserver
   nsWeakPtr                  mObserver;
 
+  // Decoding on draw?
+  PRBool                     mDecodeOnDraw;
+
+  // Multipart?
+  PRBool                     mMultipart;
+
+  // Have we been initalized?
+  PRBool                     mInitialized;
+
+  // Discard members
   PRBool                     mDiscardable;
-  PRBool                     mDiscarded;
-  nsCString                  mDiscardableMimeType;
-
-  nsTArray<char>             mRestoreData;
-  PRBool                     mRestoreDataDone;
+  PRUint32                   mLockCount;
   nsCOMPtr<nsITimer>         mDiscardTimer;
 
-  nsresult ResetDiscardTimer (void);
-  nsresult RestoreDiscardedData (void);
-  nsresult ReloadImages (void);
-  static void sDiscardTimerCallback (nsITimer *aTimer, void *aClosure);
+  // Source data members
+  nsTArray<char>             mSourceData;
+  PRBool                     mHasSourceData;
+  nsCString                  mSourceDataMimeType;
+
+  // Do we have the frames in decoded form?
+  PRBool                     mDecoded;
+
+  friend class imgDecodeWorker;
+
+  // Decoder and friends
+  nsCOMPtr<imgIDecoder>          mDecoder;
+  nsRefPtr<imgDecodeWorker>      mWorker;
+  PRUint32                       mBytesDecoded;
+  nsCOMPtr<nsIStringInputStream> mDecoderInput;
+  PRUint32                       mDecoderFlags;
+  PRBool                         mWorkerPending;
+  PRBool                         mInDecoder;
+
+  // Error handling
+  PRBool                         mError;
+
+  // Discard code
+  nsresult ResetDiscardTimer();
+  static void sDiscardTimerCallback(nsITimer *aTimer, void *aClosure);
+
+  // Decoding
+  nsresult WantDecodedFrames();
+  nsresult SyncDecode();
+  nsresult InitDecoder(PRUint32 dFlags);
+  nsresult WriteToDecoder(const char *aBuffer, PRUint32 aCount);
+  nsresult DecodeSomeData(PRUint32 aMaxBytes);
+  PRBool   IsDecodeFinished();
+  nsresult EnableDiscarding();
+
+  // Decoder shutdown
+  enum eShutdownIntent {
+    eShutdownIntent_Done        = 0,
+    eShutdownIntent_Interrupted = 1,
+    eShutdownIntent_Error       = 2,
+    eShutdownIntent_AllCount    = 3
+  };
+  nsresult ShutdownDecoder(eShutdownIntent aIntent);
+
+  // Helpers
+  void DoError();
+  PRBool CanDiscard();
+  PRBool StoringSourceData();
+
 };
 
+// Decoding Helper Class
+//
+// We use this class to mimic the interactivity benefits of threading
+// in a single-threaded event loop. We want to progressively decode
+// and keep a responsive UI while we're at it, so we have a runnable
+// class that does a bit of decoding, and then "yields" by dispatching
+// itself to the end of the event queue.
+class imgDecodeWorker : public nsRunnable
+{
+  public:
+    imgDecodeWorker(imgIContainer* aContainer) {
+      mContainer = do_GetWeakReference(aContainer);
+    }
+    NS_IMETHOD Run();
+    NS_METHOD  Dispatch();
+
+  private:
+    nsWeakPtr mContainer;
+};
+
+// Asynchronous Decode Requestor
+//
+// We use this class when someone calls requestDecode() from within a decode
+// notification. Since requestDecode() involves modifying the decoder's state
+// (for example, possibly shutting down a header-only decode and starting a
+// full decode), we don't want to do this from inside a decoder.
+class imgDecodeRequestor : public nsRunnable
+{
+  public:
+    imgDecodeRequestor(imgIContainer *aContainer) {
+      mContainer = do_GetWeakReference(aContainer);
+    }
+    NS_IMETHOD Run() {
+      nsCOMPtr<imgIContainer> con = do_QueryReferent(mContainer);
+      if (con)
+        con->RequestDecode();
+      return NS_OK;
+    }
+
+  private:
+    nsWeakPtr mContainer;
+};
+
+
+
 #endif /* __imgContainer_h__ */
--- a/modules/libpr0n/src/imgLoader.cpp
+++ b/modules/libpr0n/src/imgLoader.cpp
@@ -97,17 +97,17 @@ static void PrintImageDecoders()
   while (NS_SUCCEEDED(enumer->HasMoreElements(&more)) && more) {
     enumer->GetNext(getter_AddRefs(s));
     if (s) {
       nsCOMPtr<nsISupportsCString> ss(do_QueryInterface(s));
 
       nsCAutoString xcs;
       ss->GetData(xcs);
 
-      NS_NAMED_LITERAL_CSTRING(decoderContract, "@mozilla.org/image/decoder;2?type=");
+      NS_NAMED_LITERAL_CSTRING(decoderContract, "@mozilla.org/image/decoder;3?type=");
 
       if (StringBeginsWith(xcs, decoderContract)) {
         printf("Have decoder for mime type: %s\n", xcs.get()+decoderContract.Length());
       }
     }
   }
 }
 #endif
@@ -367,44 +367,34 @@ imgCacheEntry::imgCacheEntry(imgRequest 
    mHasNoProxies(PR_TRUE)
 {}
 
 imgCacheEntry::~imgCacheEntry()
 {
   LOG_FUNC(gImgLog, "imgCacheEntry::~imgCacheEntry()");
 }
 
-void imgCacheEntry::TouchWithSize(PRInt32 diff)
-{
-  LOG_SCOPE(gImgLog, "imgCacheEntry::TouchWithSize");
-
-  mTouchedTime = SecondsFromPRTime(PR_Now());
-
-  // Don't update the cache if we've been removed from it or it doesn't care
-  // about our size or usage.
-  if (!Evicted() && HasNoProxies()) {
-    nsCOMPtr<nsIURI> uri;
-    mRequest->GetKeyURI(getter_AddRefs(uri));
-    imgLoader::CacheEntriesChanged(uri, diff);
-  }
-}
-
 void imgCacheEntry::Touch(PRBool updateTime /* = PR_TRUE */)
 {
   LOG_SCOPE(gImgLog, "imgCacheEntry::Touch");
 
   if (updateTime)
     mTouchedTime = SecondsFromPRTime(PR_Now());
 
+  UpdateCache();
+}
+
+void imgCacheEntry::UpdateCache(PRInt32 diff /* = 0 */)
+{
   // Don't update the cache if we've been removed from it or it doesn't care
   // about our size or usage.
   if (!Evicted() && HasNoProxies()) {
     nsCOMPtr<nsIURI> uri;
     mRequest->GetKeyURI(getter_AddRefs(uri));
-    imgLoader::CacheEntriesChanged(uri);
+    imgLoader::CacheEntriesChanged(uri, diff);
   }
 }
 
 void imgCacheEntry::SetHasNoProxies(PRBool hasNoProxies)
 {
 #if defined(PR_LOGGING)
   nsCOMPtr<nsIURI> uri;
   mRequest->GetKeyURI(getter_AddRefs(uri));
@@ -1636,17 +1626,17 @@ NS_IMETHODIMP imgLoader::SupportImageWit
 {
   *_retval = PR_FALSE;
   nsCOMPtr<nsIComponentRegistrar> reg;
   nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(reg));
   if (NS_FAILED(rv))
     return rv;
   nsCAutoString mimeType(aMimeType);
   ToLowerCase(mimeType);
-  nsCAutoString decoderId(NS_LITERAL_CSTRING("@mozilla.org/image/decoder;2?type=") + mimeType);
+  nsCAutoString decoderId(NS_LITERAL_CSTRING("@mozilla.org/image/decoder;3?type=") + mimeType);
   return reg->IsContractIDRegistered(decoderId.get(),  _retval);
 }
 
 NS_IMETHODIMP imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest,
                                                 const PRUint8* aContents,
                                                 PRUint32 aLength,
                                                 nsACString& aContentType)
 {
--- a/modules/libpr0n/src/imgLoader.h
+++ b/modules/libpr0n/src/imgLoader.h
@@ -92,17 +92,17 @@ public:
   PRUint32 GetDataSize() const
   {
     return mDataSize;
   }
   void SetDataSize(PRUint32 aDataSize)
   {
     PRInt32 oldsize = mDataSize;
     mDataSize = aDataSize;
-    TouchWithSize(mDataSize - oldsize);
+    UpdateCache(mDataSize - oldsize);
   }
 
   PRInt32 GetTouchedTime() const
   {
     return mTouchedTime;
   }
   void SetTouchedTime(PRInt32 time)
   {
@@ -151,17 +151,17 @@ public:
   {
     return mHasNoProxies;
   }
 
 private: // methods
   friend class imgLoader;
   friend class imgCacheQueue;
   void Touch(PRBool updateTime = PR_TRUE);
-  void TouchWithSize(PRInt32 diff);
+  void UpdateCache(PRInt32 diff = 0);
   void SetEvicted(PRBool evict)
   {
     mEvicted = evict;
   }
   void SetHasNoProxies(PRBool hasNoProxies);
 
   // Private, unimplemented copy constructor.
   imgCacheEntry(const imgCacheEntry &);
--- a/modules/libpr0n/src/imgRequest.cpp
+++ b/modules/libpr0n/src/imgRequest.cpp
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Stuart Parmenter <stuart@mozilla.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -36,16 +37,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "imgRequest.h"
 
 #include "imgLoader.h"
 #include "imgRequestProxy.h"
+#include "imgContainer.h"
 
 #include "imgILoader.h"
 #include "ImageErrors.h"
 #include "ImageLogging.h"
 
 #include "netCore.h"
 
 #include "nsIChannel.h"
@@ -63,31 +65,34 @@
 #include "nsIScriptSecurityManager.h"
 
 #include "nsICacheVisitor.h"
 
 #include "nsString.h"
 #include "nsXPIDLString.h"
 #include "plstr.h" // PL_strcasestr(...)
 
+static PRBool gDecodeOnDraw = PR_TRUE;
+static PRBool gDiscardable = PR_TRUE;
+
 #if defined(PR_LOGGING)
 PRLogModuleInfo *gImgLog = PR_NewLogModule("imgRequest");
 #endif
 
-NS_IMPL_ISUPPORTS8(imgRequest, imgILoad,
+NS_IMPL_ISUPPORTS7(imgRequest,
                    imgIDecoderObserver, imgIContainerObserver,
                    nsIStreamListener, nsIRequestObserver,
                    nsISupportsWeakReference,
                    nsIChannelEventSink,
                    nsIInterfaceRequestor)
 
 imgRequest::imgRequest() : 
   mImageStatus(imgIRequest::STATUS_NONE), mState(0), mCacheId(0), 
   mValidator(nsnull), mImageSniffers("image-sniffing-services"), 
-  mIsMultiPartChannel(PR_FALSE), mLoading(PR_FALSE), mProcessing(PR_FALSE),
+  mIsMultiPartChannel(PR_FALSE), mLoading(PR_FALSE),
   mHadLastPart(PR_FALSE), mGotData(PR_FALSE), mIsInCache(PR_FALSE)
 {
   /* member initializers and constructor code */
 }
 
 imgRequest::~imgRequest()
 {
   if (mKeyURI) {
@@ -181,24 +186,24 @@ nsresult imgRequest::RemoveProxy(imgRequ
   /* Check mState below before we potentially call Cancel() below. Since
      Cancel() may result in OnStopRequest being called back before Cancel()
      returns, leaving mState in a different state then the one it was in at
      this point.
    */
 
   if (aNotify) {
     // make sure that observer gets an OnStopDecode message sent to it
-    if (!(mState & onStopDecode)) {
+    if (!(mState & stateRequestStopped)) {
       proxy->OnStopDecode(aStatus, nsnull);
     }
 
   }
 
   // make sure that observer gets an OnStopRequest message sent to it
-  if (!(mState & onStopRequest)) {
+  if (!(mState & stateRequestStopped)) {
     proxy->OnStopRequest(nsnull, nsnull, NS_BINDING_ABORTED, PR_TRUE);
   }
 
   if (mImage && !HaveProxyWithObserver(nsnull)) {
     LOG_MSG(gImgLog, "imgRequest::RemoveProxy", "stopping animation");
 
     mImage->StopAnimation();
   }
@@ -245,68 +250,57 @@ nsresult imgRequest::RemoveProxy(imgRequ
   return NS_OK;
 }
 
 nsresult imgRequest::NotifyProxyListener(imgRequestProxy *proxy)
 {
   nsCOMPtr<imgIRequest> kungFuDeathGrip(proxy);
 
   // OnStartRequest
-  if (mState & onStartRequest)
+  if (mState & stateRequestStarted)
     proxy->OnStartRequest(nsnull, nsnull);
 
+  // OnStartContainer
+  if (mState & stateHasSize)
+    proxy->OnStartContainer(mImage);
+
   // OnStartDecode
-  if (mState & onStartDecode)
+  if (mState & stateDecodeStarted)
     proxy->OnStartDecode();
 
-  // OnStartContainer
-  if (mState & onStartContainer)
-    proxy->OnStartContainer(mImage);
-
   // Send frame messages (OnStartFrame, OnDataAvailable, OnStopFrame)
   PRUint32 nframes = 0;
   if (mImage)
     mImage->GetNumFrames(&nframes);
 
   if (nframes > 0) {
     PRUint32 frame;
     mImage->GetCurrentFrameIndex(&frame);
     proxy->OnStartFrame(frame);
 
-    if (!(mState & onStopContainer)) {
-      // OnDataAvailable
-      nsIntRect r;
-      mImage->GetCurrentFrameRect(r); // XXX we should only send the currently decoded rectangle here.
-      proxy->OnDataAvailable(frame, &r);
-    } else {
-      // OnDataAvailable
-      nsIntRect r;
-      mImage->GetCurrentFrameRect(r); // We're done loading this image, send the the whole rect
-      proxy->OnDataAvailable(frame, &r);
+    // OnDataAvailable
+    // XXX - Should only send partial rects here, but that needs to
+    // wait until we fix up the observer interface
+    nsIntRect r;
+    mImage->GetCurrentFrameRect(r);
+    proxy->OnDataAvailable(frame, &r);
 
-      // OnStopFrame
+    if (mState & stateRequestStopped)
       proxy->OnStopFrame(frame);
-    }
   }
 
-  // OnStopContainer
-  if (mState & onStopContainer)
-    proxy->OnStopContainer(mImage);
-
-  // OnStopDecode
-  if (mState & onStopDecode)
-    proxy->OnStopDecode(GetResultFromImageStatus(mImageStatus), nsnull);
-
   if (mImage && !HaveProxyWithObserver(proxy) && proxy->HasObserver()) {
     LOG_MSG(gImgLog, "imgRequest::NotifyProxyListener", "resetting animation");
 
     mImage->ResetAnimation();
   }
 
-  if (mState & onStopRequest) {
+  if (mState & stateRequestStopped) {
+    proxy->OnStopContainer(mImage);
+    proxy->OnStopDecode(GetResultFromImageStatus(mImageStatus), nsnull);
     proxy->OnStopRequest(nsnull, nsnull,
                          GetResultFromImageStatus(mImageStatus),
                          mHadLastPart);
   }
 
   return NS_OK;
 }
 
@@ -332,19 +326,17 @@ void imgRequest::Cancel(nsresult aStatus
     LOG_MSG(gImgLog, "imgRequest::Cancel", "stopping animation");
 
     mImage->StopAnimation();
   }
 
   if (!(mImageStatus & imgIRequest::STATUS_LOAD_PARTIAL))
     mImageStatus |= imgIRequest::STATUS_ERROR;
 
-  if (aStatus != NS_IMAGELIB_ERROR_NO_DECODER) {
-    RemoveFromCache();
-  }
+  RemoveFromCache();
 
   if (mRequest && mLoading)
     mRequest->Cancel(aStatus);
 }
 
 void imgRequest::CancelAndAbort(nsresult aStatus)
 {
   LOG_SCOPE(gImgLog, "imgRequest::CancelAndAbort");
@@ -468,45 +460,43 @@ void imgRequest::AdjustPriority(imgReque
 }
 
 void imgRequest::SetIsInCache(PRBool incache)
 {
   LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::SetIsCacheable", "incache", incache);
   mIsInCache = incache;
 }
 
-/** imgILoad methods **/
-
-NS_IMETHODIMP imgRequest::SetImage(imgIContainer *aImage)
+void imgRequest::UpdateCacheEntrySize()
 {
-  LOG_FUNC(gImgLog, "imgRequest::SetImage");
+  if (mCacheEntry) {
+    PRUint32 imageSize = 0;
+    if (mImage)
+      mImage->GetDataSize(&imageSize);
+    mCacheEntry->SetDataSize(imageSize);
 
-  mImage = aImage;
+#ifdef DEBUG_joe
+    nsCAutoString url;
+    mURI->GetSpec(url);
+    printf("CACHEPUT: %d %s %d\n", time(NULL), url.get(), imageSize);
+#endif
+  }
 
-  return NS_OK;
 }
 
-NS_IMETHODIMP imgRequest::GetImage(imgIContainer **aImage)
+nsresult
+imgRequest::GetImage(imgIContainer **aImage)
 {
   LOG_FUNC(gImgLog, "imgRequest::GetImage");
 
   *aImage = mImage;
   NS_IF_ADDREF(*aImage);
   return NS_OK;
 }
 
-NS_IMETHODIMP imgRequest::GetIsMultiPartChannel(PRBool *aIsMultiPartChannel)
-{
-  LOG_FUNC(gImgLog, "imgRequest::GetIsMultiPartChannel");
-
-  *aIsMultiPartChannel = mIsMultiPartChannel;
-
-  return NS_OK;
-}
-
 /** imgIContainerObserver methods **/
 
 /* [noscript] void frameChanged (in imgIContainer container, in nsIntRect dirtyRect); */
 NS_IMETHODIMP imgRequest::FrameChanged(imgIContainer *container,
                                        nsIntRect * dirtyRect)
 {
   LOG_SCOPE(gImgLog, "imgRequest::FrameChanged");
 
@@ -520,17 +510,17 @@ NS_IMETHODIMP imgRequest::FrameChanged(i
 
 /** imgIDecoderObserver methods **/
 
 /* void onStartDecode (in imgIRequest request); */
 NS_IMETHODIMP imgRequest::OnStartDecode(imgIRequest *request)
 {
   LOG_SCOPE(gImgLog, "imgRequest::OnStartDecode");
 
-  mState |= onStartDecode;
+  mState |= stateDecodeStarted;
 
   nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
   while (iter.HasMore()) {
     iter.GetNext()->OnStartDecode();
   }
 
   /* In the case of streaming jpegs, it is possible to get multiple OnStartDecodes which
      indicates the beginning of a new decode.
@@ -552,23 +542,28 @@ NS_IMETHODIMP imgRequest::OnStartRequest
 /* void onStartContainer (in imgIRequest request, in imgIContainer image); */
 NS_IMETHODIMP imgRequest::OnStartContainer(imgIRequest *request, imgIContainer *image)
 {
   LOG_SCOPE(gImgLog, "imgRequest::OnStartContainer");
 
   NS_ASSERTION(image, "imgRequest::OnStartContainer called with a null image!");
   if (!image) return NS_ERROR_UNEXPECTED;
 
-  mState |= onStartContainer;
+  // We only want to send onStartContainer once
+  PRBool alreadySent = (mState & stateHasSize) != 0;
+
+  mState |= stateHasSize;
 
   mImageStatus |= imgIRequest::STATUS_SIZE_AVAILABLE;
 
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
-  while (iter.HasMore()) {
-    iter.GetNext()->OnStartContainer(image);
+  if (!alreadySent) {
+    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+    while (iter.HasMore()) {
+      iter.GetNext()->OnStartContainer(image);
+    }
   }
 
   return NS_OK;
 }
 
 /* void onStartFrame (in imgIRequest request, in unsigned long frame); */
 NS_IMETHODIMP imgRequest::OnStartFrame(imgIRequest *request,
                                        PRUint32 frame)
@@ -601,121 +596,144 @@ NS_IMETHODIMP imgRequest::OnDataAvailabl
 /* void onStopFrame (in imgIRequest request, in unsigned long frame); */
 NS_IMETHODIMP imgRequest::OnStopFrame(imgIRequest *request,
                                       PRUint32 frame)
 {
   LOG_SCOPE(gImgLog, "imgRequest::OnStopFrame");
 
   mImageStatus |= imgIRequest::STATUS_FRAME_COMPLETE;
 
-  if (mCacheEntry) {
-    PRUint32 cacheSize = mCacheEntry->GetDataSize();
-
-    PRUint32 imageSize = 0;
-    if (mImage)
-      mImage->GetFrameImageDataLength(frame, &imageSize);
-
-    mCacheEntry->SetDataSize(cacheSize + imageSize);
-
-#ifdef DEBUG_joe
-    nsCAutoString url;
-    mURI->GetSpec(url);
-
-    printf("CACHEPUT: %d %s %d\n", time(NULL), url.get(), cacheSize + imageSize);
-#endif
-  }
-
   nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
   while (iter.HasMore()) {
     iter.GetNext()->OnStopFrame(frame);
   }
 
   return NS_OK;
 }
 
 /* void onStopContainer (in imgIRequest request, in imgIContainer image); */
 NS_IMETHODIMP imgRequest::OnStopContainer(imgIRequest *request,
                                           imgIContainer *image)
 {
   LOG_SCOPE(gImgLog, "imgRequest::OnStopContainer");
 
-  mState |= onStopContainer;
-
   nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
   while (iter.HasMore()) {
     iter.GetNext()->OnStopContainer(image);
   }
 
   return NS_OK;
 }
 
 /* void onStopDecode (in imgIRequest request, in nsresult status, in wstring statusArg); */
 NS_IMETHODIMP imgRequest::OnStopDecode(imgIRequest *aRequest,
                                        nsresult aStatus,
                                        const PRUnichar *aStatusArg)
 {
   LOG_SCOPE(gImgLog, "imgRequest::OnStopDecode");
 
-  NS_ASSERTION(!(mState & onStopDecode), "OnStopDecode called multiple times.");
-
-  mState |= onStopDecode;
+  // We finished the decode, and thus have the decoded frames. Update the cache
+  // entry size to take this into account.
+  UpdateCacheEntrySize();
 
-  if (NS_FAILED(aStatus) && !(mImageStatus & imgIRequest::STATUS_LOAD_PARTIAL)) {
-    mImageStatus |= imgIRequest::STATUS_ERROR;
-  }
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
-  while (iter.HasMore()) {
-    iter.GetNext()->OnStopDecode(GetResultFromImageStatus(mImageStatus), aStatusArg);
-  }
+  // ImgContainer and everything below it is completely correct and
+  // bulletproof about its handling of decoder notifications.
+  // Unfortunately, here and above we have to make some gross and
+  // inappropriate use of things to get things to work without
+  // completely overhauling the decoder observer interface (this will,
+  // thankfully, happen in bug 505385). From imgRequest and above (for
+  // the time being), OnStopDecode is just a companion to OnStopRequest
+  // that signals success or failure of the _load_ (not the _decode_).
+  // As such, we ignore OnStopDecode notifications from the decoder and
+  // container and generate our own every time we send OnStopRequest.
+  // For more information, see bug 435296.
 
   return NS_OK;
 }
 
 NS_IMETHODIMP imgRequest::OnStopRequest(imgIRequest *aRequest,
                                         PRBool aLastPart)
 {
   NS_NOTREACHED("imgRequest(imgIDecoderObserver)::OnStopRequest");
   return NS_OK;
 }
 
+/* void onDiscard (in imgIRequest request); */
+NS_IMETHODIMP imgRequest::OnDiscard(imgIRequest *aRequest)
+{
+  // Clear the state bits we no longer deserve.
+  PRUint32 stateBitsToClear = stateDecodeStarted;
+  mState &= ~stateBitsToClear;
+
+  // Clear the status bits we no longer deserve.
+  PRUint32 statusBitsToClear = imgIRequest::STATUS_FRAME_COMPLETE;
+  mImageStatus &= ~statusBitsToClear;
+
+  // Update the cache entry size, since we just got rid of frame data
+  UpdateCacheEntrySize();
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  while (iter.HasMore()) {
+    iter.GetNext()->OnDiscard();
+  }
+
+  return NS_OK;
+
+}
+
 /** nsIRequestObserver methods **/
 
 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
 NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
 {
   nsresult rv;
 
   LOG_SCOPE(gImgLog, "imgRequest::OnStartRequest");
 
-  NS_ASSERTION(!mDecoder, "imgRequest::OnStartRequest -- we already have a decoder");
-
+  // Figure out if we're multipart
   nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
   if (mpchan)
       mIsMultiPartChannel = PR_TRUE;
 
+  // If we're not multipart, we shouldn't have an image yet
+  NS_ABORT_IF_FALSE(mIsMultiPartChannel || !mImage,
+                    "Already have an image for non-multipart request");
+
+  // If we're multipart and have an image, fix things up for another round
+  if (mIsMultiPartChannel && mImage) {
+
+    // Inform the container that we have new source data
+    mImage->NewSourceData();
+
+    // Clear any status and state bits indicating load/decode
+    mImageStatus &= ~imgIRequest::STATUS_LOAD_PARTIAL;
+    mImageStatus &= ~imgIRequest::STATUS_LOAD_COMPLETE;
+    mImageStatus &= ~imgIRequest::STATUS_FRAME_COMPLETE;
+    mState &= ~stateRequestStarted;
+    mState &= ~stateDecodeStarted;
+    mState &= ~stateRequestStopped;
+  }
+
   /*
    * If mRequest is null here, then we need to set it so that we'll be able to
    * cancel it if our Cancel() method is called.  Note that this can only
    * happen for multipart channels.  We could simply not null out mRequest for
    * non-last parts, if GetIsLastPart() were reliable, but it's not.  See
    * https://bugzilla.mozilla.org/show_bug.cgi?id=339610
    */
   if (!mRequest) {
     NS_ASSERTION(mpchan,
                  "We should have an mRequest here unless we're multipart");
     nsCOMPtr<nsIChannel> chan;
     mpchan->GetBaseChannel(getter_AddRefs(chan));
     mRequest = chan;
   }
 
-  /* set our state variables to their initial values, but advance mState
-     to onStartRequest. */
-  mImageStatus = imgIRequest::STATUS_NONE;
-  mState = onStartRequest;
+  // The request has started
+  mState |= stateRequestStarted;
 
   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
   if (channel)
     channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
 
   /* set our loading flag to true */
   mLoading = PR_TRUE;
 
@@ -797,24 +815,21 @@ NS_IMETHODIMP imgRequest::OnStartRequest
   return NS_OK;
 }
 
 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
 NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
 {
   LOG_FUNC(gImgLog, "imgRequest::OnStopRequest");
 
-  mState |= onStopRequest;
+  mState |= stateRequestStopped;
 
   /* set our loading flag to false */
   mLoading = PR_FALSE;
 
-  /* set our processing flag to false */
-  mProcessing = PR_FALSE;
-
   mHadLastPart = PR_TRUE;
   nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
   if (mpchan) {
     PRBool lastPart;
     nsresult rv = mpchan->GetIsLastPart(&lastPart);
     if (NS_SUCCEEDED(rv))
       mHadLastPart = lastPart;
   }
@@ -829,82 +844,96 @@ NS_IMETHODIMP imgRequest::OnStopRequest(
 
   // stop holding a ref to the channel, since we don't need it anymore
   if (mChannel) {
     mChannel->SetNotificationCallbacks(mPrevChannelSink);
     mPrevChannelSink = nsnull;
     mChannel = nsnull;
   }
 
-  // If mImage is still null, we didn't properly load the image.
-  if (NS_FAILED(status) || !mImage) {
-    this->Cancel(status); // sets status, stops animations, removes from cache
-  } else {
-    mImageStatus |= imgIRequest::STATUS_LOAD_COMPLETE;
+  // Tell the image that it has all of the source data. Note that this can
+  // trigger a failure, since the image might be waiting for more non-optional
+  // data and this is the point where we break the news that it's not coming.
+  if (mImage) {
+
+    // Notify the image
+    nsresult rv = mImage->SourceDataComplete();
+
+    // If we got an error in the SourceDataComplete() call, we don't want to
+    // proceed as if nothing bad happened. However, we also want to give
+    // precedence to failure status codes from necko, since presumably
+    // they're more meaningful.
+    if (NS_FAILED(rv) && NS_SUCCEEDED(status))
+      status = rv;
   }
 
-  if (mDecoder) {
-    mDecoder->Flush();
-    mDecoder->Close();
-    mDecoder = nsnull; // release the decoder so that it can rest peacefully ;)
+  // If the request went through, say we loaded the image, and update the
+  // cache entry size. Otherwise, cancel the request, which adds an error
+  // flag to mImageStatus.
+  if (NS_SUCCEEDED(status)) {
+
+    // Flag that we loaded the image
+    mImageStatus |= imgIRequest::STATUS_LOAD_COMPLETE;
+
+    // We update the cache entry size here because this is where we finish
+    // loading compressed source data, which is part of our size calculus.
+    UpdateCacheEntrySize();
+  }
+  else
+    this->Cancel(status); // sets status, stops animations, removes from cache
+
+  /* notify the kids */
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator sdIter(mObservers);
+  while (sdIter.HasMore()) {
+    sdIter.GetNext()->OnStopDecode(GetResultFromImageStatus(mImageStatus), nsnull);
   }
 
-  // if there was an error loading the image, (mState & onStopDecode) won't be true.
-  // Send an onStopDecode message
-  if (!(mState & onStopDecode)) {
-    this->OnStopDecode(nsnull, status, nsnull);
-  }
-
-  /* notify the kids */
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
-  while (iter.HasMore()) {
-    iter.GetNext()->OnStopRequest(aRequest, ctxt, status, mHadLastPart);
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator srIter(mObservers);
+  while (srIter.HasMore()) {
+    srIter.GetNext()->OnStopRequest(aRequest, ctxt, status, mHadLastPart);
   }
 
   return NS_OK;
 }
 
-/* prototype for this defined below */
+/* prototype for these defined below */
 static NS_METHOD sniff_mimetype_callback(nsIInputStream* in, void* closure, const char* fromRawSegment,
                                          PRUint32 toOffset, PRUint32 count, PRUint32 *writeCount);
 
-
 /** nsIStreamListener methods **/
 
 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long sourceOffset, in unsigned long count); */
 NS_IMETHODIMP imgRequest::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count)
 {
   LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::OnDataAvailable", "count", count);
 
   NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!");
 
   mGotData = PR_TRUE;
+  nsresult rv;
 
-  if (!mProcessing) {
+  if (!mImage) {
     LOG_SCOPE(gImgLog, "imgRequest::OnDataAvailable |First time through... finding mimetype|");
 
-    /* set our processing flag to true if this is the first OnDataAvailable() */
-    mProcessing = PR_TRUE;
-
     /* look at the first few bytes and see if we can tell what the data is from that
      * since servers tend to lie. :(
      */
     PRUint32 out;
     inStr->ReadSegments(sniff_mimetype_callback, this, count, &out);
 
 #ifdef NS_DEBUG
     /* NS_WARNING if the content type from the channel isn't the same if the sniffing */
 #endif
 
     if (mContentType.IsEmpty()) {
       LOG_SCOPE(gImgLog, "imgRequest::OnDataAvailable |sniffing of mimetype failed|");
 
       nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
 
-      nsresult rv = NS_ERROR_FAILURE;
+      rv = NS_ERROR_FAILURE;
       if (chan) {
         rv = chan->GetContentType(mContentType);
       }
 
       if (NS_FAILED(rv)) {
         PR_LOG(gImgLog, PR_LOG_ERROR,
                ("[this=%p] imgRequest::OnDataAvailable -- Content type unavailable from the channel\n",
                 this));
@@ -940,61 +969,89 @@ NS_IMETHODIMP imgRequest::OnDataAvailabl
       if (contentDisposition) {
         contentDisposition->SetData(disposition);
         mProperties->Set("content-disposition", contentDisposition);
       }
     }
 
     LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::OnDataAvailable", "content type", mContentType.get());
 
-    nsCAutoString conid(NS_LITERAL_CSTRING("@mozilla.org/image/decoder;2?type=") + mContentType);
+    //
+    // Figure out if our container initialization flags
+    //
 
-    mDecoder = do_CreateInstance(conid.get());
+    // We default to the static globals
+    PRBool isDiscardable = gDiscardable;
+    PRBool doDecodeOnDraw = gDecodeOnDraw;
 
-    if (!mDecoder) {
-      PR_LOG(gImgLog, PR_LOG_WARNING,
-             ("[this=%p] imgRequest::OnDataAvailable -- Decoder not available\n", this));
+    // We want UI to be as snappy as possible and not to flicker. Disable discarding
+    // and decode-on-draw for chrome URLS
+    PRBool isChrome = PR_FALSE;
+    rv = mURI->SchemeIs("chrome", &isChrome);
+    if (NS_SUCCEEDED(rv) && isChrome)
+      isDiscardable = doDecodeOnDraw = PR_FALSE;
 
-      // no image decoder for this mimetype :(
-      this->Cancel(NS_IMAGELIB_ERROR_NO_DECODER);
+    // We don't want resources like the "loading" icon to be discardable or
+    // decode-on-draw either.
+    PRBool isResource = PR_FALSE;
+    rv = mURI->SchemeIs("resource", &isResource);
+    if (NS_SUCCEEDED(rv) && isResource)
+      isDiscardable = doDecodeOnDraw = PR_FALSE;
+
+    // For multipart/x-mixed-replace, we basically want a direct channel to the
+    // decoder. Disable both for this case as well.
+    if (mIsMultiPartChannel)
+      isDiscardable = doDecodeOnDraw = PR_FALSE;
 
-      return NS_IMAGELIB_ERROR_NO_DECODER;
-    }
+    // We have all the information we need
+    PRUint32 containerFlags = imgIContainer::INIT_FLAG_NONE;
+    if (isDiscardable)
+      containerFlags |= imgIContainer::INIT_FLAG_DISCARDABLE;
+    if (doDecodeOnDraw)
+      containerFlags |= imgIContainer::INIT_FLAG_DECODE_ON_DRAW;
+    if (mIsMultiPartChannel)
+      containerFlags |= imgIContainer::INIT_FLAG_MULTIPART;
 
-    nsresult rv = mDecoder->Init(static_cast<imgILoad*>(this));
-    if (NS_FAILED(rv)) {
-      PR_LOG(gImgLog, PR_LOG_WARNING,
-             ("[this=%p] imgRequest::OnDataAvailable -- mDecoder->Init failed\n", this));
+    // Create and initialize the imgContainer. This instantiates a decoder behind
+    // the scenes, so if we don't have a decoder for this mimetype we'll find out
+    // about it here.
+    mImage = do_CreateInstance("@mozilla.org/image/container;3");
+    if (!mImage) {
+      this->Cancel(NS_ERROR_OUT_OF_MEMORY);
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+    rv = mImage->Init(this, mContentType.get(), containerFlags);
+    if (NS_FAILED(rv)) { // Probably bad mimetype
 
-      this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
+      // There's no reason to keep the image around. Save memory.
+      //
+      // XXXbholley - This is also here because I'm not sure we've found
+      // all the consumers who (incorrectly) check whether the container
+      // is null to determine things like size availability (they should
+      // be checking the image status instead).
+      mImage = nsnull;
 
+      this->Cancel(rv);
       return NS_BINDING_ABORTED;
     }
   }
 
-  if (!mDecoder) {
+  // WriteToContainer always consumes everything it gets
+  PRUint32 bytesRead;
+  rv = inStr->ReadSegments(imgContainer::WriteToContainer,
+                           static_cast<void*>(mImage),
+                           count, &bytesRead);
+  if (NS_FAILED(rv)) {
     PR_LOG(gImgLog, PR_LOG_WARNING,
-           ("[this=%p] imgRequest::OnDataAvailable -- no decoder\n", this));
-
-    this->Cancel(NS_IMAGELIB_ERROR_NO_DECODER);
-
+           ("[this=%p] imgRequest::OnDataAvailable -- "
+            "copy to container failed\n", this));
+    this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
     return NS_BINDING_ABORTED;
   }
-
-  PRUint32 wrote;
-  nsresult rv = mDecoder->WriteFrom(inStr, count, &wrote);
-
-  if (NS_FAILED(rv)) {
-    PR_LOG(gImgLog, PR_LOG_WARNING,
-           ("[this=%p] imgRequest::OnDataAvailable -- mDecoder->WriteFrom failed\n", this));
-
-    this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
-
-    return NS_BINDING_ABORTED;
-  }
+  NS_ABORT_IF_FALSE(bytesRead == count, "WriteToContainer should consume everything!");
 
   return NS_OK;
 }
 
 static NS_METHOD sniff_mimetype_callback(nsIInputStream* in,
                                          void* closure,
                                          const char* fromRawSegment,
                                          PRUint32 toOffset,
@@ -1031,16 +1088,17 @@ imgRequest::SniffMimeType(const char *bu
     nsresult rv =
       sniffers[i]->GetMIMETypeFromContent(nsnull, (const PRUint8 *) buf, len, mContentType);
     if (NS_SUCCEEDED(rv) && !mContentType.IsEmpty()) {
       return;
     }
   }
 }
 
+
 /** nsIInterfaceRequestor methods **/
 
 NS_IMETHODIMP
 imgRequest::GetInterface(const nsIID & aIID, void **aResult)
 {
   if (!mPrevChannelSink || aIID.Equals(NS_GET_IID(nsIChannelEventSink)))
     return QueryInterface(aIID, aResult);
 
--- a/modules/libpr0n/src/imgRequest.h
+++ b/modules/libpr0n/src/imgRequest.h
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2001
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Stuart Parmenter <pavlov@netscape.com>
+ *   Bobby Holley <bobbyholley@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -35,18 +36,16 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef imgRequest_h__
 #define imgRequest_h__
 
-#include "imgILoad.h"
-
 #include "imgIContainer.h"
 #include "imgIDecoder.h"
 #include "imgIDecoderObserver.h"
 
 #include "nsIChannelEventSink.h"
 #include "nsIContentSniffer.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIRequest.h"
@@ -62,26 +61,23 @@
 #include "nsWeakReference.h"
 
 class imgCacheValidator;
 
 class imgRequestProxy;
 class imgCacheEntry;
 
 enum {
-  onStartRequest   = PR_BIT(0),
-  onStartDecode    = PR_BIT(1),
-  onStartContainer = PR_BIT(2),
-  onStopContainer  = PR_BIT(3),
-  onStopDecode     = PR_BIT(4),
-  onStopRequest    = PR_BIT(5)
+  stateRequestStarted    = PR_BIT(0),
+  stateHasSize           = PR_BIT(1),
+  stateDecodeStarted     = PR_BIT(2),
+  stateRequestStopped    = PR_BIT(4)
 };
 
-class imgRequest : public imgILoad,
-                   public imgIDecoderObserver,
+class imgRequest : public imgIDecoderObserver,
                    public nsIStreamListener,
                    public nsSupportsWeakReference,
                    public nsIChannelEventSink,
                    public nsIInterfaceRequestor
 {
 public:
   imgRequest();
   virtual ~imgRequest();
@@ -110,16 +106,18 @@ public:
   // being made...
   PRBool IsReusable(void *aCacheId) { return !mLoading || (aCacheId == mCacheId); }
 
   // Cancel, but also ensure that all work done in Init() is undone. Call this
   // only when the channel has failed to open, and so calling Cancel() on it
   // won't be sufficient.
   void CancelAndAbort(nsresult aStatus);
 
+  nsresult GetImage(imgIContainer **aImage);
+
 private:
   friend class imgCacheEntry;
   friend class imgRequestProxy;
   friend class imgLoader;
   friend class imgCacheValidator;
   friend class imgCacheExpirationTracker;
 
   inline void SetLoadId(void *aLoadId) {
@@ -164,34 +162,35 @@ private:
   // Return whether we've seen some data at this point
   PRBool HasTransferredData() const { return mGotData; }
 
   // Set whether this request is stored in the cache. If it isn't, regardless
   // of whether this request has a non-null mCacheEntry, this imgRequest won't
   // try to update or modify the image cache.
   void SetIsInCache(PRBool cacheable);
 
+  // Update the cache entry size based on the image container
+  void UpdateCacheEntrySize();
+
 public:
-  NS_DECL_IMGILOAD
   NS_DECL_IMGIDECODEROBSERVER
   NS_DECL_IMGICONTAINEROBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIINTERFACEREQUESTOR
 
 private:
   nsCOMPtr<nsIRequest> mRequest;
   // The original URI we were loaded with.
   nsCOMPtr<nsIURI> mURI;
   // The URI we are keyed on in the cache.
   nsCOMPtr<nsIURI> mKeyURI;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<imgIContainer> mImage;
-  nsCOMPtr<imgIDecoder> mDecoder;
   nsCOMPtr<nsIProperties> mProperties;
   nsCOMPtr<nsISupports> mSecurityInfo;
   nsCOMPtr<nsIChannel> mChannel;
   nsCOMPtr<nsIInterfaceRequestor> mPrevChannelSink;
 
   nsTObserverArray<imgRequestProxy*> mObservers;
 
   PRUint32 mImageStatus;
@@ -205,15 +204,14 @@ private:
   void *mLoadId;
   PRTime mLoadTime;
 
   imgCacheValidator *mValidator;
   nsCategoryCache<nsIContentSniffer> mImageSniffers;
 
   PRPackedBool mIsMultiPartChannel : 1;
   PRPackedBool mLoading : 1;
-  PRPackedBool mProcessing : 1;
   PRPackedBool mHadLastPart : 1;
   PRPackedBool mGotData : 1;
   PRPackedBool mIsInCache : 1;
 };
 
 #endif
--- a/modules/libpr0n/src/imgRequestProxy.cpp
+++ b/modules/libpr0n/src/imgRequestProxy.cpp
@@ -59,27 +59,33 @@ NS_IMPL_ISUPPORTS4(imgRequestProxy, imgI
                    nsISupportsPriority, nsISecurityInfoProvider)
 
 imgRequestProxy::imgRequestProxy() :
   mOwner(nsnull),
   mListener(nsnull),
   mLoadFlags(nsIRequest::LOAD_NORMAL),
   mCanceled(PR_FALSE),
   mIsInLoadGroup(PR_FALSE),
-  mListenerIsStrongRef(PR_FALSE)
+  mListenerIsStrongRef(PR_FALSE),
+  mShouldRequestDecode(PR_FALSE),
+  mLockHeld(PR_FALSE)
 {
   /* member initializers and constructor code */
 
 }
 
 imgRequestProxy::~imgRequestProxy()
 {
   /* destructor code */
   NS_PRECONDITION(!mListener, "Someone forgot to properly cancel this request!");
 
+  // Unlock the image if we're holding a lock on it
+  if (mLockHeld && mOwner)
+    UnlockImage();
+
   // Explicitly set mListener to null to ensure that the RemoveProxy
   // call below can't send |this| to an arbitrary listener while |this|
   // is being destroyed.  This is all belt-and-suspenders in view of the
   // above assert.
   NullOutListener();
 
   if (mOwner) {
     if (!mCanceled) {
@@ -124,24 +130,42 @@ nsresult imgRequestProxy::Init(imgReques
   return NS_OK;
 }
 
 nsresult imgRequestProxy::ChangeOwner(imgRequest *aNewOwner)
 {
   if (mCanceled)
     return NS_OK;
 
+  // Were we decoded before?
+  PRBool wasDecoded = PR_FALSE;
+  if (mOwner->GetImageStatus() & imgIRequest::STATUS_FRAME_COMPLETE)
+    wasDecoded = PR_TRUE;
+
+  // If we're holding a lock, unlock the old image
+  PRBool wasLocked = mLockHeld;
+  if (mLockHeld)
+    UnlockImage();
+
   // Passing false to aNotify means that mListener will still get
   // OnStopRequest, if needed.
   mOwner->RemoveProxy(this, NS_IMAGELIB_CHANGING_OWNER, PR_FALSE);
 
   mOwner = aNewOwner;
 
   mOwner->AddProxy(this);
 
+  // If we were decoded, request a decode on the new image
+  if (wasDecoded)
+    RequestDecode();
+
+  // If we were locked, apply the lock here
+  if (wasLocked)
+    LockImage();
+
   return NS_OK;
 }
 
 void imgRequestProxy::AddToLoadGroup()
 {
   NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
 
   if (!mIsInLoadGroup && mLoadGroup) {
@@ -237,16 +261,84 @@ NS_IMETHODIMP imgRequestProxy::CancelAnd
   // OnStopRequest, if needed.
   mOwner->RemoveProxy(this, aStatus, PR_FALSE);
 
   NullOutListener();
 
   return NS_OK;
 }
 
+/* void requestDecode (); */
+NS_IMETHODIMP
+imgRequestProxy::RequestDecode()
+{
+  if (!mOwner)
+    return NS_ERROR_FAILURE;
+
+  // See if we can get the image
+  nsCOMPtr<imgIContainer> container;
+  nsresult rv = mOwner->GetImage(getter_AddRefs(container));
+  if (NS_FAILED(rv))
+    return rv;
+
+  // If we've got the container, just forward along the request
+  if (container)
+    return container->RequestDecode();
+
+  // Otherwise, flag that we should do it when the container is ready
+  mShouldRequestDecode = PR_TRUE;
+  return NS_OK;
+}
+
+/* void lockImage (); */
+NS_IMETHODIMP
+imgRequestProxy::LockImage()
+{
+  if (!mOwner)
+    return NS_ERROR_FAILURE;
+
+  // Flag that we're holding a lock
+  NS_ABORT_IF_FALSE(!mLockHeld, "Only call lockImage once per imgIRequest!");
+  mLockHeld = PR_TRUE;
+
+  // If we've got the container, forward along the request. If we don't, well
+  // do it in OnStartContainer.
+  nsCOMPtr<imgIContainer> container;
+  nsresult rv = mOwner->GetImage(getter_AddRefs(container));
+  if (NS_FAILED(rv))
+    return rv;
+  if (container)
+    return container->LockImage();
+
+  return NS_OK;
+}
+
+/* void unlockImage (); */
+NS_IMETHODIMP
+imgRequestProxy::UnlockImage()
+{
+  if (!mOwner)
+    return NS_ERROR_FAILURE;
+
+  // Flag that we're not holding a lock
+  NS_ABORT_IF_FALSE(mLockHeld, "calling unlock but not locked!");
+  mLockHeld = PR_FALSE;
+
+  // If we've got the container, forward along the request. If we don't, it
+  // doesn't matter.
+  nsCOMPtr<imgIContainer> container;
+  nsresult rv = mOwner->GetImage(getter_AddRefs(container));
+  if (NS_FAILED(rv))
+    return rv;
+  if (container)
+    return container->UnlockImage();
+
+  return NS_OK;
+}
+
 /* void suspend (); */
 NS_IMETHODIMP imgRequestProxy::Suspend()
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 /* void resume (); */
 NS_IMETHODIMP imgRequestProxy::Resume()
@@ -453,16 +545,26 @@ void imgRequestProxy::OnStartContainer(i
 {
   LOG_FUNC(gImgLog, "imgRequestProxy::OnStartContainer");
 
   if (mListener && !mCanceled) {
     // Hold a ref to the listener while we call it, just in case.
     nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
     mListener->OnStartContainer(this, image);
   }
+
+  // Request a decode if we said we should
+  if (mShouldRequestDecode) {
+    image->RequestDecode();
+    mShouldRequestDecode = PR_FALSE;
+  }
+
+  // Lock if we said we should
+  if (mLockHeld)
+    image->LockImage();
 }
 
 void imgRequestProxy::OnStartFrame(PRUint32 frame)
 {
   LOG_FUNC(gImgLog, "imgRequestProxy::OnStartFrame");
 
   if (mListener && !mCanceled) {
     // Hold a ref to the listener while we call it, just in case.
@@ -510,16 +612,28 @@ void imgRequestProxy::OnStopDecode(nsres
 
   if (mListener && !mCanceled) {
     // Hold a ref to the listener while we call it, just in case.
     nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
     mListener->OnStopDecode(this, status, statusArg);
   }
 }
 
+void imgRequestProxy::OnDiscard()
+{
+  LOG_FUNC(gImgLog, "imgRequestProxy::OnDiscard");
+
+  if (mListener && !mCanceled) {
+    // Hold a ref to the listener while we call it, just in case.
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->OnDiscard(this);
+  }
+}
+
+
 
 
 void imgRequestProxy::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
 {
 #ifdef PR_LOGGING
   nsCAutoString name;
   GetName(name);
   LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnStartRequest", "name", name.get());
--- a/modules/libpr0n/src/imgRequestProxy.h
+++ b/modules/libpr0n/src/imgRequestProxy.h
@@ -111,16 +111,17 @@ protected:
   /* non-virtual imgIDecoderObserver methods */
   void OnStartDecode   ();
   void OnStartContainer(imgIContainer *aContainer);
   void OnStartFrame    (PRUint32 aFrame);
   void OnDataAvailable (PRBool aCurrentFrame, const nsIntRect * aRect);
   void OnStopFrame     (PRUint32 aFrame);
   void OnStopContainer (imgIContainer *aContainer);
   void OnStopDecode    (nsresult status, const PRUnichar *statusArg); 
+  void OnDiscard       ();
 
   /* non-virtual imgIContainerObserver methods */
   void FrameChanged(imgIContainer *aContainer, nsIntRect * aDirtyRect);
 
   /* non-virtual nsIRequestObserver (plus some) methods */
   void OnStartRequest(nsIRequest *request, nsISupports *ctxt);
   void OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult statusCode, PRBool aLastPart); 
 
@@ -150,9 +151,11 @@ private:
   // first OnStopRequest.
   imgIDecoderObserver* mListener;
   nsCOMPtr<nsILoadGroup> mLoadGroup;
 
   nsLoadFlags mLoadFlags;
   PRPackedBool mCanceled;
   PRPackedBool mIsInLoadGroup;
   PRPackedBool mListenerIsStrongRef;
+  PRPackedBool mShouldRequestDecode;
+  PRPackedBool mLockHeld;
 };
--- a/modules/libpr0n/src/imgTools.cpp
+++ b/modules/libpr0n/src/imgTools.cpp
@@ -36,153 +36,28 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "imgTools.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "ImageErrors.h"
 #include "imgIContainer.h"
-#include "imgILoad.h"
 #include "imgIDecoder.h"
 #include "imgIEncoder.h"
 #include "imgIDecoderObserver.h"
 #include "imgIContainerObserver.h"
 #include "gfxContext.h"
 #include "nsStringStream.h"
 #include "nsComponentManagerUtils.h"
 #include "nsWeakReference.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsStreamUtils.h"
 #include "nsNetUtil.h"
-
-
-/* ========== Utility classes ========== */
-
-
-class HelperLoader : public imgILoad,
-                     public imgIDecoderObserver,
-                     public nsSupportsWeakReference
-{
-  public:
-    NS_DECL_ISUPPORTS
-    NS_DECL_IMGILOAD
-    NS_DECL_IMGIDECODEROBSERVER
-    NS_DECL_IMGICONTAINEROBSERVER
-    HelperLoader(void);
-
-  private:
-    nsCOMPtr<imgIContainer> mContainer;
-};
-
-NS_IMPL_ISUPPORTS4 (HelperLoader, imgILoad, imgIDecoderObserver, imgIContainerObserver, nsISupportsWeakReference)
-
-HelperLoader::HelperLoader (void)
-{
-}
-
-/* Implement imgILoad::image getter */
-NS_IMETHODIMP
-HelperLoader::GetImage(imgIContainer **aImage)
-{
-  *aImage = mContainer;
-  NS_IF_ADDREF (*aImage);
-  return NS_OK;
-}
-
-/* Implement imgILoad::image setter */
-NS_IMETHODIMP
-HelperLoader::SetImage(imgIContainer *aImage)
-{
-  mContainer = aImage;
-  return NS_OK;
-}
-
-/* Implement imgILoad::isMultiPartChannel getter */
-NS_IMETHODIMP
-HelperLoader::GetIsMultiPartChannel(PRBool *aIsMultiPartChannel)
-{
-  *aIsMultiPartChannel = PR_FALSE;
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStartRequest() */
-NS_IMETHODIMP
-HelperLoader::OnStartRequest(imgIRequest *aRequest)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStartDecode() */
-NS_IMETHODIMP
-HelperLoader::OnStartDecode(imgIRequest *aRequest)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStartContainer() */
-NS_IMETHODIMP
-HelperLoader::OnStartContainer(imgIRequest *aRequest, imgIContainer
-*aContainer)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStartFrame() */
-NS_IMETHODIMP
-HelperLoader::OnStartFrame(imgIRequest *aRequest, PRUint32 aFrame)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onDataAvailable() */
-NS_IMETHODIMP
-HelperLoader::OnDataAvailable(imgIRequest *aRequest, PRBool aCurrentFrame, const nsIntRect * aRect)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStopFrame() */
-NS_IMETHODIMP
-HelperLoader::OnStopFrame(imgIRequest *aRequest, PRUint32 aFrame)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStopContainer() */
-NS_IMETHODIMP
-HelperLoader::OnStopContainer(imgIRequest *aRequest, imgIContainer
-*aContainer)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStopDecode() */
-NS_IMETHODIMP
-HelperLoader::OnStopDecode(imgIRequest *aRequest, nsresult status, const
-PRUnichar *statusArg)
-{
-  return NS_OK;
-}
-
-/* Implement imgIDecoderObserver::onStopRequest() */
-NS_IMETHODIMP
-HelperLoader::OnStopRequest(imgIRequest *aRequest, PRBool aIsLastPart)
-{
-  return NS_OK;
-}
-  
-/* implement imgIContainerObserver::frameChanged() */
-NS_IMETHODIMP
-HelperLoader::FrameChanged(imgIContainer *aContainer, nsIntRect * aDirtyRect)
-{
-  return NS_OK;
-}
-
-
+#include "imgContainer.h"
 
 /* ========== imgITools implementation ========== */
 
 
 
 NS_IMPL_ISUPPORTS1(imgTools, imgITools)
 
 imgTools::imgTools()
@@ -197,62 +72,58 @@ imgTools::~imgTools()
 
 
 NS_IMETHODIMP imgTools::DecodeImageData(nsIInputStream* aInStr,
                                         const nsACString& aMimeType,
                                         imgIContainer **aContainer)
 {
   nsresult rv;
 
-  // Get an image decoder for our media type
-  nsCAutoString decoderCID(
-    NS_LITERAL_CSTRING("@mozilla.org/image/decoder;2?type=") + aMimeType);
-
-  nsCOMPtr<imgIDecoder> decoder = do_CreateInstance(decoderCID.get());
-  if (!decoder)
-    return NS_IMAGELIB_ERROR_NO_DECODER;
+  NS_ENSURE_ARG_POINTER(aInStr);
+  // If the caller didn't provide a container, create one
+  if (!*aContainer) {
+    NS_NEWXPCOM(*aContainer, imgContainer);
+    if (!*aContainer)
+      return NS_ERROR_OUT_OF_MEMORY;
+    NS_ADDREF(*aContainer);
+  }
 
-  // Init the decoder, we use a small utility class here.
-  nsCOMPtr<imgILoad> loader = new HelperLoader();
-  if (!loader)
-    return NS_ERROR_OUT_OF_MEMORY;
-
-  // If caller provided an existing container, use it.
-  if (*aContainer)
-    loader->SetImage(*aContainer);
-
-  rv = decoder->Init(loader);
+  // Initialize the container. If we're using the one from the caller, we
+  // require that it not be initialized
+  nsCString mimeType(aMimeType);
+  rv = (*aContainer)->Init(nsnull, mimeType.get(), imgIContainer::INIT_FLAG_NONE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIInputStream> inStream = aInStr;
   if (!NS_InputStreamIsBuffered(aInStr)) {
     nsCOMPtr<nsIInputStream> bufStream;
     rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), aInStr, 1024);
     if (NS_SUCCEEDED(rv))
       inStream = bufStream;
   }
 
+  // Figure out how much data we've been passed
   PRUint32 length;
   rv = inStream->Available(&length);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  PRUint32 written;
-  rv = decoder->WriteFrom(inStream, length, &written);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (written != length)
-    NS_WARNING("decoder didn't eat all of its vegetables");
-  rv = decoder->Flush();
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = decoder->Close();
+  // Send the source data to the container. WriteToContainer always
+  // consumes everything it gets.
+  PRUint32 bytesRead;
+  rv = inStream->ReadSegments(imgContainer::WriteToContainer,
+                              static_cast<void*>(*aContainer),
+                              length, &bytesRead);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // If caller didn't provide an existing container, return the new one.
-  if (!*aContainer)
-    loader->GetImage(aContainer);
 
+  // Let the container know we've sent all the data
+  rv = (*aContainer)->SourceDataComplete();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // All done
   return NS_OK;
 }
 
 
 NS_IMETHODIMP imgTools::EncodeImage(imgIContainer *aContainer,
                                           const nsACString& aMimeType,
                                           nsIInputStream **aStream)
 {
@@ -285,17 +156,18 @@ NS_IMETHODIMP imgTools::EncodeScaledImag
     NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType);
 
   nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
   if (!encoder)
     return NS_IMAGELIB_ERROR_NO_ENCODER;
 
   // Use frame 0 from the image container.
   nsRefPtr<gfxImageSurface> frame;
-  rv = aContainer->CopyCurrentFrame(getter_AddRefs(frame));
+  rv = aContainer->CopyFrame(imgIContainer::FRAME_CURRENT, PR_TRUE,
+                             getter_AddRefs(frame));
   NS_ENSURE_SUCCESS(rv, rv);
   if (!frame)
     return NS_ERROR_NOT_AVAILABLE;
 
   PRInt32 w = frame->Width(), h = frame->Height();
   if (!w || !h)
     return NS_ERROR_FAILURE;
 
--- a/modules/libpr0n/test/mochitest/Makefile.in
+++ b/modules/libpr0n/test/mochitest/Makefile.in
@@ -39,18 +39,19 @@ DEPTH		= ../../../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = modules/libpr0n/test/mochitest
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
-_TEST_FILES = 	test_bug399925.html \
-		bug399925.gif \
+_TEST_FILES =   imgutils.js \
+                test_bug399925.html \
+                bug399925.gif \
                 bug468160.sjs \
                 test_bug468160.html \
                 red.png \
                 test_bug466586.html \
                 big.png \
                 blue.png \
                 test_bug490949.html \
                 bug490949-iframe.html \
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/test/mochitest/imgutils.js
@@ -0,0 +1,92 @@
+// Helper file for shared image functionality
+// 
+// Note that this is use by tests elsewhere in the source tree. When in doubt,
+// check mxr before removing or changing functionality.
+
+// Helper function to determine if the frame is decoded for a given image id
+function isFrameDecoded(id)
+{
+  return (getImageStatus(id) &
+          Components.interfaces.imgIRequest.STATUS_FRAME_COMPLETE)
+         ? true : false;
+}
+
+// Helper function to determine if the image is loaded for a given image id
+function isImageLoaded(id)
+{
+  return (getImageStatus(id) &
+          Components.interfaces.imgIRequest.STATUS_LOAD_COMPLETE)
+         ? true : false;
+}
+
+// Helper function to get the status flags of an image
+function getImageStatus(id)
+{
+  // Escalate
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+  // Get the image
+  var img = document.getElementById(id);
+
+  // QI the image to nsImageLoadingContent
+  img.QueryInterface(Components.interfaces.nsIImageLoadingContent);
+
+  // Get the request
+  var request = img.getRequest(Components.interfaces
+                                         .nsIImageLoadingContent
+                                         .CURRENT_REQUEST);
+
+  // Return the status
+  return request.imageStatus;
+}
+
+// Forces a synchronous decode of an image by drawing it to a canvas. Only
+// really meaningful if the image is fully loaded first
+function forceDecode(id)
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+  // Get the image
+  var img = document.getElementById(id);
+
+  // Make a new canvas
+  var canvas = document.createElement("canvas");
+
+  // Draw the image to the canvas. This forces a synchronous decode
+  var ctx = canvas.getContext("2d");
+  ctx.drawImage(img, 0, 0);
+}
+
+
+// Functions to facilitate getting/setting the discard timer pref
+//
+// Don't forget to reset the pref to the original value!
+//
+// Null indicates no pref set
+
+const DISCARD_BRANCH_NAME = "image.cache.";
+const DISCARD_PREF_NAME = "discard_timer_ms";
+
+function setDiscardTimerPref(timeMS)
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                              .getService(Components.interfaces.nsIPrefService);
+  var branch = prefService.getBranch(DISCARD_BRANCH_NAME);
+  if (timeMS != null)
+    branch.setIntPref(DISCARD_PREF_NAME, timeMS);
+  else if (branch.prefHasUserValue(DISCARD_PREF_NAME))
+    branch.clearUserPref(DISCARD_PREF_NAME);
+}
+
+function getDiscardTimerPref()
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                              .getService(Components.interfaces.nsIPrefService);
+  var branch = prefService.getBranch(DISCARD_BRANCH_NAME);
+  if (branch.prefHasUserValue(DISCARD_PREF_NAME))
+    return branch.getIntPref("discard_timeout_ms");
+  else
+    return null;
+}
--- a/modules/libpr0n/test/reftest/apng/delaytest.html
+++ b/modules/libpr0n/test/reftest/apng/delaytest.html
@@ -1,24 +1,41 @@
 <!DOCTYPE HTML>
 <html class="reftest-wait">
 <head>
 <title>Delayed image reftest wrapper</title>
 </head>
 <body>
 <img id="image1">
 <script>
-// This loads a externally specified image, waits 100ms, and then triggers the
-// reftest snapshot. This allows the animation on the page to complete.
+// This loads a externally specified image, forces a draw (in case of
+// decode-on-draw), waits 100ms, and then triggers the reftest snapshot.
+// This allows the animation on the page to complete.
 //
 // Use as "delaytest.html?animation.png"
 //
+
+// Get the image URL from our URL
+var imgURL = document.location.search.substr(1);
+
+// Load the image
+var img = document.images[0];
+img.src = imgURL;
+img.onload = forceDecode;
+
+function forceDecode() {
+
+  // We need to force drawing of the image in an invisible context
+  var canvas = document.createElement("canvas");
+  var ctx = canvas.getContext("2d");
+  ctx.drawImage(img, 0, 0);
+
+  // We've force the decode. start the timer to trigger the reftest
+  startTimer();
+}
+
 function startTimer() {
   const delay = 100;
   setTimeout("document.documentElement.className = '';", delay);
 }
-var imgURL = document.location.search.substr(1);
-var img = document.images[0];
-img.src = imgURL;
-img.onload = startTimer;
 </script>
 </body>
 </html>
--- a/modules/libpr0n/test/unit/test_imgtools.js
+++ b/modules/libpr0n/test/unit/test_imgtools.js
@@ -377,34 +377,38 @@ testnum = 413512;
 testdesc = "test decoding bad favicon (bug 413512)";
 
 imgName = "bug413512.ico";
 inMimeType = "image/x-icon";
 imgFile = do_get_file(imgName);
 
 istream = getFileInputStream(imgFile);
 do_check_eq(istream.available(), 17759);
+var errsrc = "none";
 
 try {
   outParam = { value: null };
   imgTools.decodeImageData(istream, inMimeType, outParam);
   container = outParam.value;
 
   // We should never hit this - decodeImageData throws an assertion because the
   // image decoded doesn't have enough frames.
   try {
       istream = imgTools.encodeImage(container, "image/png");
   } catch (e) {
       err = e;
+      errsrc = "encode";
   }
 } catch (e) {
   err = e;
+  errsrc = "decode";
 }
 
-checkExpectedError(/NS_ERROR_ILLEGAL_VALUE/, err);
+do_check_eq(errsrc, "decode");
+checkExpectedError(/NS_ERROR_FAILURE/, err);
 
 
 /* ========== end ========== */
 
 } catch (e) {
     throw "FAILED in test #" + testnum + " -- " + testdesc + ": " + e;
 }
 };
--- a/toolkit/system/gnome/nsAlertsIconListener.cpp
+++ b/toolkit/system/gnome/nsAlertsIconListener.cpp
@@ -157,16 +157,22 @@ nsAlertsIconListener::OnStopRequest(imgI
   if (mIconRequest) {
     mIconRequest->Cancel(NS_BINDING_ABORTED);
     mIconRequest = nsnull;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsAlertsIconListener::OnDiscard(imgIRequest *aRequest)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsAlertsIconListener::OnStopFrame(imgIRequest* aRequest,
                                   PRUint32 aFrame)
 {
   if (aRequest != mIconRequest)
     return NS_ERROR_FAILURE;
 
   if (mLoadedFrame)
     return NS_OK; // only use one frame
--- a/widget/src/cocoa/nsClipboard.mm
+++ b/widget/src/cocoa/nsClipboard.mm
@@ -427,17 +427,19 @@ nsClipboard::PasteboardDictFromTransfera
 
       nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData));
       if (!image) {
         NS_WARNING("Image isn't an imgIContainer in transferable");
         continue;
       }
 
       nsRefPtr<gfxImageSurface> currentFrame;
-      if (NS_FAILED(image->CopyCurrentFrame(getter_AddRefs(currentFrame))))
+      if (NS_FAILED(image->CopyFrame(imgIContainer::FRAME_CURRENT,
+                                     imgIContainer::FLAG_SYNC_DECODE,
+                                     getter_AddRefs(currentFrame))))
         continue;
 
       PRInt32 height = currentFrame->Height();
       PRInt32 stride = currentFrame->Stride();
       PRInt32 width = currentFrame->Width();
       if ((stride % 4 != 0) || (height < 1) || (width < 1))
         continue;
 
--- a/widget/src/cocoa/nsMenuItemIconX.mm
+++ b/widget/src/cocoa/nsMenuItemIconX.mm
@@ -301,16 +301,20 @@ nsMenuItemIconX::OnStartDecode(imgIReque
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMenuItemIconX::OnStartContainer(imgIRequest*   aRequest,
                                   imgIContainer* aContainer)
 {
+  // Request a decode
+  NS_ABORT_IF_FALSE(aContainer, "who sent the notification then?");
+  aContainer->RequestDecode();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMenuItemIconX::OnStartFrame(imgIRequest* aRequest, PRUint32 aFrame)
 {
   return NS_OK;
 }
@@ -339,17 +343,19 @@ nsMenuItemIconX::OnStopFrame(imgIRequest
   if (!mNativeMenuItem) return NS_ERROR_FAILURE;
 
   nsCOMPtr<imgIContainer> imageContainer;
   aRequest->GetImage(getter_AddRefs(imageContainer));
   if (!imageContainer)
     return NS_ERROR_FAILURE;
 
   nsRefPtr<gfxImageSurface> image;
-  imageContainer->CopyCurrentFrame(getter_AddRefs(image));
+  imageContainer->CopyFrame(imgIContainer::FRAME_CURRENT,
+                            imgIContainer::FLAG_NONE,
+                            getter_AddRefs(image));
 
   PRInt32 height = image->Height();
   PRInt32 stride = image->Stride();
   PRInt32 width = image->Width();
   PRUint32 imageLength = ((stride * height) / 4);
   if ((stride % 4 != 0) || (height < 1) || (width < 1))
     return NS_ERROR_FAILURE;
 
@@ -473,8 +479,14 @@ nsMenuItemIconX::OnStopRequest(imgIReque
 {
   NS_ASSERTION(mIconRequest, "NULL mIconRequest!  Multiple calls to OnStopRequest()?");
   if (mIconRequest) {
     mIconRequest->Cancel(NS_BINDING_ABORTED);
     mIconRequest = nsnull;
   }
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsMenuItemIconX::OnDiscard(imgIRequest* aRequest)
+{
+  return NS_OK;
+}
--- a/widget/src/gtk2/nsImageToPixbuf.cpp
+++ b/widget/src/gtk2/nsImageToPixbuf.cpp
@@ -64,17 +64,19 @@ nsImageToPixbuf::ConvertImageToPixbuf(im
 {
     return ImageToPixbuf(aImage);
 }
 
 GdkPixbuf*
 nsImageToPixbuf::ImageToPixbuf(imgIContainer* aImage)
 {
     nsRefPtr<gfxImageSurface> frame;
-    aImage->CopyCurrentFrame(getter_AddRefs(frame));
+    aImage->CopyFrame(imgIContainer::FRAME_CURRENT,
+                      imgIContainer::FLAG_SYNC_DECODE,
+                      getter_AddRefs(frame));
 
     return ImgSurfaceToPixbuf(frame, frame->Width(), frame->Height());
 }
 
 GdkPixbuf*
 nsImageToPixbuf::ImgSurfaceToPixbuf(gfxImageSurface* aImgSurface, PRInt32 aWidth, PRInt32 aHeight)
 {
     GdkPixbuf* pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, PR_TRUE, 8,
--- a/widget/src/os2/nsWindow.cpp
+++ b/widget/src/os2/nsWindow.cpp
@@ -1614,17 +1614,19 @@ NS_IMETHODIMP nsWindow::SetCursor(imgICo
   // it will be destroyed when we create a new one or when the
   // current window is destroyed
   if (mCssCursorImg == aCursor && mCssCursorHPtr) {
     WinSetPointer(HWND_DESKTOP, mCssCursorHPtr);
     return NS_OK;
   }
 
   nsRefPtr<gfxImageSurface> frame;
-  aCursor->CopyCurrentFrame(getter_AddRefs(frame));
+  aCursor->CopyFrame(imgIContainer::FRAME_CURRENT,
+                     imgIContainer::FLAG_SYNC_DECODE,
+                     getter_AddRefs(frame));
   if (!frame)
     return NS_ERROR_NOT_AVAILABLE;
 
   // if the image is ridiculously large, exit because
   // it will be unrecognizable when shrunk to 32x32
   PRInt32 width = frame->Width();
   PRInt32 height = frame->Height();
   if (width > 128 || height > 128)
--- a/widget/src/windows/nsImageClipboard.cpp
+++ b/widget/src/windows/nsImageClipboard.cpp
@@ -144,17 +144,19 @@ nsImageToClipboard::CalcSpanLength(PRUin
 //
 nsresult
 nsImageToClipboard::CreateFromImage ( imgIContainer* inImage, HANDLE* outBitmap )
 {
     nsresult result = NS_OK;
     *outBitmap = nsnull;
 
     nsRefPtr<gfxImageSurface> frame;
-    nsresult rv = inImage->CopyCurrentFrame(getter_AddRefs(frame));
+    nsresult rv = inImage->CopyFrame(imgIContainer::FRAME_CURRENT,
+                                     imgIContainer::FLAG_SYNC_DECODE,
+                                     getter_AddRefs(frame));
     if (NS_FAILED(rv))
       return rv;
 
     const PRUint32 imageSize = frame->GetDataSize();
     const PRInt32 bitmapSize = sizeof(BITMAPINFOHEADER) + imageSize;
 
     HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, bitmapSize);
     if (!glob) {
--- a/widget/src/windows/nsWindowGfx.cpp
+++ b/widget/src/windows/nsWindowGfx.cpp
@@ -687,17 +687,19 @@ nsresult nsWindowGfx::CreateIcon(imgICon
   rv = aContainer->GetNumFrames(&nFrames);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!nFrames)
     return NS_ERROR_INVALID_ARG;
 
   // Get the image data
   nsRefPtr<gfxImageSurface> frame;
-  aContainer->CopyCurrentFrame(getter_AddRefs(frame));
+  aContainer->CopyFrame(imgIContainer::FRAME_CURRENT,
+                        imgIContainer::FLAG_SYNC_DECODE,
+                        getter_AddRefs(frame));
   if (!frame)
     return NS_ERROR_NOT_AVAILABLE;
 
   PRUint8 *data = frame->Data();
 
   PRInt32 width = frame->Width();
   PRInt32 height = frame->Height();
 
--- a/widget/src/xpwidgets/nsBaseDragService.cpp
+++ b/widget/src/xpwidgets/nsBaseDragService.cpp
@@ -596,17 +596,18 @@ nsBaseDragService::DrawDragForImage(nsPr
   *aSurface = surface;
   NS_ADDREF(*aSurface);
 
   if (aImageLoader) {
     gfxRect outRect(0, 0, destSize.width, destSize.height);
     gfxMatrix scale =
       gfxMatrix().Scale(srcSize.width/outRect.Width(), srcSize.height/outRect.Height());
     nsIntRect imgSize(0, 0, srcSize.width, srcSize.height);
-    imgContainer->Draw(ctx, gfxPattern::FILTER_GOOD, scale, outRect, imgSize);
+    imgContainer->Draw(ctx, gfxPattern::FILTER_GOOD, scale, outRect, imgSize,
+                       imgIContainer::FLAG_SYNC_DECODE);
     return NS_OK;
   } else {
     return aCanvas->RenderContexts(ctx, gfxPattern::FILTER_GOOD);
   }
 }
 
 void
 nsBaseDragService::ConvertToUnscaledDevPixels(nsPresContext* aPresContext,