Back out bug 585385 for build bustage.
authorJosh Matthews <josh@joshmatthews.net>
Thu, 11 Oct 2012 21:58:24 -0400
changeset 110037 2adc0ce03dba4ba6548c1a0673a4c9e4d18b473a
parent 110031 d35c748b03b0f222767442747c7eab9fa911e9e4
child 110038 ea7bf4553d66eb343c0f6e9a7cc071d1eb54c40c
push id23667
push useremorley@mozilla.com
push dateFri, 12 Oct 2012 11:04:11 +0000
treeherdermozilla-central@83e8792a147a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs585385
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Back out bug 585385 for build bustage.
content/base/public/nsContentUtils.h
content/base/public/nsIImageLoadingContent.idl
content/base/src/Makefile.in
content/base/src/nsContentUtils.cpp
content/base/src/nsGenConImageContent.cpp
content/base/src/nsImageLoadingContent.cpp
content/base/src/nsImageLoadingContent.h
content/base/src/nsObjectLoadingContent.h
content/base/src/nsStubImageDecoderObserver.cpp
content/base/src/nsStubImageDecoderObserver.h
content/html/content/src/nsHTMLImageElement.cpp
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLObjectElement.cpp
content/html/content/src/nsHTMLSharedObjectElement.cpp
content/html/content/test/test_bug389797.html
content/html/document/src/ImageDocument.cpp
content/svg/content/src/nsSVGFilters.cpp
content/svg/content/src/nsSVGFilters.h
content/svg/content/src/nsSVGImageElement.cpp
image/decoders/nsICODecoder.cpp
image/encoders/ico/nsICOEncoder.cpp
image/public/Makefile.in
image/public/imgIContainerObserver.idl
image/public/imgIDecoderObserver.idl
image/public/imgILoader.idl
image/public/imgINotificationObserver.idl
image/public/imgIRequest.idl
image/public/imgIScriptedNotificationObserver.idl
image/public/imgITools.idl
image/src/Decoder.cpp
image/src/Image.cpp
image/src/Makefile.in
image/src/RasterImage.cpp
image/src/ScriptedNotificationObserver.cpp
image/src/ScriptedNotificationObserver.h
image/src/VectorImage.cpp
image/src/imgLoader.cpp
image/src/imgLoader.h
image/src/imgRequest.cpp
image/src/imgRequest.h
image/src/imgRequestProxy.cpp
image/src/imgRequestProxy.h
image/src/imgStatusTracker.cpp
image/src/imgStatusTracker.h
image/src/imgTools.cpp
image/test/mochitest/imgutils.js
image/test/mochitest/test_animSVGImage.html
image/test/unit/async_load_tests.js
image/test/unit/image_load_helpers.js
image/test/unit/test_private_channel.js
layout/generic/nsBulletFrame.cpp
layout/generic/nsBulletFrame.h
layout/generic/nsImageFrame.cpp
layout/generic/nsImageFrame.h
layout/style/ImageLoader.cpp
layout/style/ImageLoader.h
layout/svg/nsSVGImageFrame.cpp
layout/xul/base/src/nsImageBoxFrame.cpp
layout/xul/base/src/nsImageBoxFrame.h
layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
layout/xul/base/src/tree/src/nsTreeBodyFrame.h
layout/xul/base/src/tree/src/nsTreeImageListener.cpp
layout/xul/base/src/tree/src/nsTreeImageListener.h
toolkit/system/gnome/nsAlertsIconListener.cpp
toolkit/system/gnome/nsAlertsIconListener.h
widget/cocoa/nsMenuItemIconX.h
widget/cocoa/nsMenuItemIconX.mm
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -59,17 +59,17 @@ class nsIScriptSecurityManager;
 class nsTextFragment;
 class nsIJSContextStack;
 class nsIThreadJSContextStack;
 class nsIParser;
 class nsIParserService;
 class nsIIOService;
 class nsIURI;
 class imgIContainer;
-class imgINotificationObserver;
+class imgIDecoderObserver;
 class imgIRequest;
 class imgILoader;
 class imgICache;
 class nsIImageLoadingContent;
 class nsIDOMHTMLFormElement;
 class nsIDOMDocument;
 class nsIConsoleService;
 class nsIStringBundleService;
@@ -656,17 +656,17 @@ public:
    * @param aObserver the observer for the image load
    * @param aLoadFlags the load flags to use.  See nsIRequest
    * @return the imgIRequest for the image load
    */
   static nsresult LoadImage(nsIURI* aURI,
                             nsIDocument* aLoadingDocument,
                             nsIPrincipal* aLoadingPrincipal,
                             nsIURI* aReferrer,
-                            imgINotificationObserver* aObserver,
+                            imgIDecoderObserver* aObserver,
                             int32_t aLoadFlags,
                             imgIRequest** aRequest);
 
   /**
    * Obtain an image loader that respects the given document/channel's privacy status.
    * Null document/channel arguments return the public image loader.
    */
   static imgILoader* GetImgLoaderForDocument(nsIDocument* aDoc);
--- a/content/base/public/nsIImageLoadingContent.idl
+++ b/content/base/public/nsIImageLoadingContent.idl
@@ -1,14 +1,14 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "imgINotificationObserver.idl"
+#include "imgIDecoderObserver.idl"
 
 interface imgIRequest;
 interface nsIChannel;
 interface nsIStreamListener;
 interface nsIURI;
 interface nsIDocument;
 interface nsIFrame;
 
@@ -29,18 +29,18 @@ interface nsIFrame;
  * observers to check which request they are getting notifications for.
  *
  * Observers added in mid-load will not get any notifications they
  * missed.  We should NOT freeze this interface without considering
  * this issue.  (It could be that the image status on imgIRequest is
  * sufficient, when combined with the imageBlockingStatus information.)
  */
 
-[scriptable, builtinclass, uuid(497bfb9b-d996-4d1e-a647-8137b0cfc876)]
-interface nsIImageLoadingContent : imgINotificationObserver
+[scriptable, uuid(4bf1a7c5-6edb-4191-a257-e31a90f6aa85)]
+interface nsIImageLoadingContent : imgIDecoderObserver
 {
   /**
    * Request types.  Image loading content nodes attempt to do atomic
    * image changes when the image url is changed.  This means that
    * when the url changes the new image load will start, but the old
    * image will remain the "current" request until the new image is
    * fully loaded.  At that point, the old "current" request will be
    * discarded and the "pending" request will become "current".
@@ -70,24 +70,24 @@ interface nsIImageLoadingContent : imgIN
    * Notifications from ongoing image loads will be passed to all
    * registered observers.  Notifications for all request types,
    * current and pending, will be passed through.
    *
    * @param aObserver the observer to register
    *
    * @throws NS_ERROR_OUT_OF_MEMORY
    */
-  void addObserver(in imgINotificationObserver aObserver);
+  void addObserver(in imgIDecoderObserver aObserver);
 
   /**
    * Used to unregister an image decoder observer.
    *
    * @param aObserver the observer to unregister
    */
-  void removeObserver(in imgINotificationObserver aObserver);
+  void removeObserver(in imgIDecoderObserver aObserver);
   
   /**
    * Accessor to get the image requests
    *
    * @param aRequestType a value saying which request is wanted
    *
    * @return the imgIRequest object (may be null, even when no error
    * is thrown)
--- a/content/base/src/Makefile.in
+++ b/content/base/src/Makefile.in
@@ -23,16 +23,17 @@ EXPORTS		= \
 		nsGkAtomList.h \
 		nsGkAtoms.h \
 		nsNodeInfoManager.h \
 		nsNodeUtils.h \
 		nsPropertyTable.h \
 		nsRange.h \
 		nsScriptLoader.h \
 		nsStubDocumentObserver.h \
+		nsStubImageDecoderObserver.h \
 		nsStubMutationObserver.h \
 		nsTextFragment.h \
 		mozAutoDocUpdate.h \
 		nsFrameMessageManager.h \
 		nsAttrAndChildArray.h \
 		nsAttrValue.h \
 		nsAttrValueInlines.h \
 		nsCrossSiteListenerProxy.h \
@@ -103,16 +104,17 @@ CPPSRCS		= \
 		nsObjectLoadingContent.cpp \
 		nsPlainTextSerializer.cpp \
 		nsPropertyTable.cpp \
 		nsRange.cpp \
 		nsReferencedElement.cpp \
 		nsScriptElement.cpp \
 		nsScriptLoader.cpp \
 		nsStubDocumentObserver.cpp \
+		nsStubImageDecoderObserver.cpp \
 		nsStubMutationObserver.cpp \
 		nsStyledElement.cpp \
 		nsStyleLinkElement.cpp \
 		nsSyncLoadService.cpp \
 		nsTextFragment.cpp \
 		nsTextNode.cpp \
 		nsTraversal.cpp \
 		nsTreeSanitizer.cpp \
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -46,17 +46,17 @@
 #include "nsIContentSink.h"
 #include "nsContentList.h"
 #include "nsIHTMLDocument.h"
 #include "nsIDOMHTMLFormElement.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIForm.h"
 #include "nsIFormControl.h"
 #include "nsGkAtoms.h"
-#include "imgINotificationObserver.h"
+#include "imgIDecoderObserver.h"
 #include "imgIRequest.h"
 #include "imgIContainer.h"
 #include "imgILoader.h"
 #include "nsDocShellCID.h"
 #include "nsIImageLoadingContent.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsILoadGroup.h"
@@ -2729,17 +2729,17 @@ nsContentUtils::IsImageInCache(nsIURI* a
     nsresult rv = cache->FindEntryProperties(aURI, getter_AddRefs(props));
     return (NS_SUCCEEDED(rv) && props);
 }
 
 // static
 nsresult
 nsContentUtils::LoadImage(nsIURI* aURI, nsIDocument* aLoadingDocument,
                           nsIPrincipal* aLoadingPrincipal, nsIURI* aReferrer,
-                          imgINotificationObserver* aObserver, int32_t aLoadFlags,
+                          imgIDecoderObserver* aObserver, int32_t aLoadFlags,
                           imgIRequest** aRequest)
 {
   NS_PRECONDITION(aURI, "Must have a URI");
   NS_PRECONDITION(aLoadingDocument, "Must have a document");
   NS_PRECONDITION(aLoadingPrincipal, "Must have a principal");
   NS_PRECONDITION(aRequest, "Null out param");
 
   imgILoader* imgLoader = GetImgLoaderForDocument(aLoadingDocument);
@@ -2772,17 +2772,17 @@ nsContentUtils::LoadImage(nsIURI* aURI, 
 
   // XXXbz using "documentURI" for the initialDocumentURI is not quite
   // right, but the best we can do here...
   return imgLoader->LoadImage(aURI,                 /* uri to load */
                               documentURI,          /* initialDocumentURI */
                               aReferrer,            /* referrer */
                               aLoadingPrincipal,    /* loading principal */
                               loadGroup,            /* loadgroup */
-                              aObserver,            /* imgINotificationObserver */
+                              aObserver,            /* imgIDecoderObserver */
                               aLoadingDocument,     /* uniquification key */
                               aLoadFlags,           /* load flags */
                               nullptr,               /* cache key */
                               nullptr,               /* existing request*/
                               channelPolicy,        /* CSP info */
                               aRequest);
 }
 
--- a/content/base/src/nsGenConImageContent.cpp
+++ b/content/base/src/nsGenConImageContent.cpp
@@ -43,20 +43,21 @@ public:
   
 private:
   virtual ~nsGenConImageContent();
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
 };
 
-NS_IMPL_ISUPPORTS_INHERITED3(nsGenConImageContent,
+NS_IMPL_ISUPPORTS_INHERITED4(nsGenConImageContent,
                              nsXMLElement,
                              nsIImageLoadingContent,
-                             imgINotificationObserver,
+                             imgIContainerObserver,
+                             imgIDecoderObserver,
                              imgIOnloadBlocker)
 
 nsresult
 NS_NewGenConImageContent(nsIContent** aResult, already_AddRefed<nsINodeInfo> aNodeInfo,
                          imgIRequest* aImageRequest)
 {
   NS_PRECONDITION(aImageRequest, "Must have request!");
   nsGenConImageContent *it = new nsGenConImageContent(aNodeInfo);
--- a/content/base/src/nsImageLoadingContent.cpp
+++ b/content/base/src/nsImageLoadingContent.cpp
@@ -117,73 +117,124 @@ nsImageLoadingContent::~nsImageLoadingCo
          observer = next) {                                              \
       next = observer->mNext;                                            \
       if (observer->mObserver) {                                         \
         observer->mObserver->func_;                                      \
       }                                                                  \
     }                                                                    \
   PR_END_MACRO
 
+
 /*
- * imgINotificationObserver impl
+ * imgIContainerObserver impl
  */
 NS_IMETHODIMP
-nsImageLoadingContent::Notify(imgIRequest* aRequest,
-                              int32_t aType,
-                              const nsIntRect* aData)
+nsImageLoadingContent::FrameChanged(imgIRequest* aRequest,
+                                    imgIContainer* aContainer,
+                                    const nsIntRect* aDirtyRect)
 {
-  if (aType == imgINotificationObserver::IS_ANIMATED) {
-    return OnImageIsAnimated(aRequest);
-  }
-
-  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
-    // We should definitely have a request here
-    NS_ABORT_IF_FALSE(aRequest, "no request?");
-
-    NS_PRECONDITION(aRequest == mCurrentRequest || aRequest == mPendingRequest,
-                    "Unknown request");
-  }
-
+  LOOP_OVER_OBSERVERS(FrameChanged(aRequest, aContainer, aDirtyRect));
+  return NS_OK;
+}
+            
+/*
+ * imgIDecoderObserver impl
+ */
+NS_IMETHODIMP
+nsImageLoadingContent::OnStartRequest(imgIRequest* aRequest)
+{
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
 
-  LOOP_OVER_OBSERVERS(Notify(aRequest, aType, aData));
-
-  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
-    // Have to check for state changes here, since we might have been in
-    // the LOADING state before.
-    UpdateImageState(true);
-  }
+  LOOP_OVER_OBSERVERS(OnStartRequest(aRequest));
+  return NS_OK;
+}
 
-  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
-    uint32_t reqStatus;
-    aRequest->GetImageStatus(&reqStatus);
-    nsresult status =
-        reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
-    return OnStopRequest(aRequest, status);
-  }
+NS_IMETHODIMP
+nsImageLoadingContent::OnStartDecode(imgIRequest* aRequest)
+{
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
 
+  LOOP_OVER_OBSERVERS(OnStartDecode(aRequest));
   return NS_OK;
 }
 
-nsresult
-nsImageLoadingContent::OnStopRequest(imgIRequest* aRequest,
-                                     nsresult aStatus)
+NS_IMETHODIMP
+nsImageLoadingContent::OnStartContainer(imgIRequest* aRequest,
+                                        imgIContainer* aContainer)
 {
-  uint32_t oldStatus;
-  aRequest->GetImageStatus(&oldStatus);
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+
+  LOOP_OVER_OBSERVERS(OnStartContainer(aRequest, aContainer));
+
+  // Have to check for state changes here, since we might have been in
+  // the LOADING state before.
+  UpdateImageState(true);
+  return NS_OK;    
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::OnStartFrame(imgIRequest* aRequest,
+                                    uint32_t aFrame)
+{
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+
+  LOOP_OVER_OBSERVERS(OnStartFrame(aRequest, aFrame));
+  return NS_OK;    
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::OnDataAvailable(imgIRequest* aRequest,
+                                       bool aCurrentFrame,
+                                       const nsIntRect* aRect)
+{
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+
+  LOOP_OVER_OBSERVERS(OnDataAvailable(aRequest, aCurrentFrame, aRect));
+  return NS_OK;
+}
 
-  //XXXjdm This occurs when we have a pending request created, then another
-  //       pending request replaces it before the first one is finished.
-  //       This begs the question of what the correct behaviour is; we used
-  //       to not have to care because we ran this code in OnStopDecode which
-  //       wasn't called when the first request was cancelled. For now, I choose
-  //       to punt when the given request doesn't appear to have terminated in
-  //       an expected state.
-  if (!(oldStatus & (imgIRequest::STATUS_ERROR | imgIRequest::STATUS_LOAD_COMPLETE)))
-    return NS_OK;
+NS_IMETHODIMP
+nsImageLoadingContent::OnStopFrame(imgIRequest* aRequest,
+                                   uint32_t aFrame)
+{
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+
+  LOOP_OVER_OBSERVERS(OnStopFrame(aRequest, aFrame));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::OnStopContainer(imgIRequest* aRequest,
+                                       imgIContainer* aContainer)
+{
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+
+  LOOP_OVER_OBSERVERS(OnStopContainer(aRequest, aContainer));
+  return NS_OK;
+}
+
+// Warning - This isn't actually fired when decode is complete. Rather, it's
+// fired when load is complete. See bug 505385, and in the mean time use
+// OnStopContainer.
+NS_IMETHODIMP
+nsImageLoadingContent::OnStopDecode(imgIRequest* aRequest,
+                                    nsresult aStatus,
+                                    const PRUnichar* aStatusArg)
+{
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+
+  // We should definitely have a request here
+  NS_ABORT_IF_FALSE(aRequest, "no request?");
+
+  NS_PRECONDITION(aRequest == mCurrentRequest || aRequest == mPendingRequest,
+                  "Unknown request");
+  LOOP_OVER_OBSERVERS(OnStopDecode(aRequest, aStatus, aStatusArg));
+
+  // XXXbholley - When we fix bug 505385,  everything here should go in
+  // OnStopRequest.
 
   // Our state may change. Watch it.
   AutoStateChanger changer(this, true);
 
   // If the pending request is loaded, switch to it.
   if (aRequest == mPendingRequest) {
     MakePendingRequestCurrent();
   }
@@ -228,28 +279,48 @@ nsImageLoadingContent::OnStopRequest(img
   }
 
   nsCOMPtr<nsINode> thisNode = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   nsSVGEffects::InvalidateDirectRenderingObservers(thisNode->AsElement());
 
   return NS_OK;
 }
 
-nsresult
+NS_IMETHODIMP
+nsImageLoadingContent::OnStopRequest(imgIRequest* aRequest, bool aLastPart)
+{
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+
+  LOOP_OVER_OBSERVERS(OnStopRequest(aRequest, aLastPart));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsImageLoadingContent::OnImageIsAnimated(imgIRequest *aRequest)
 {
   bool* requestFlag = GetRegisteredFlagForRequest(aRequest);
   if (requestFlag) {
     nsLayoutUtils::RegisterImageRequest(GetFramePresContext(),
                                         aRequest, requestFlag);
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsImageLoadingContent::OnDiscard(imgIRequest *aRequest)
+{
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+
+  LOOP_OVER_OBSERVERS(OnDiscard(aRequest));
+
+  return NS_OK;
+}
+
 /*
  * nsIImageLoadingContent impl
  */
 
 NS_IMETHODIMP
 nsImageLoadingContent::GetLoadingEnabled(bool *aLoadingEnabled)
 {
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
@@ -275,17 +346,17 @@ nsImageLoadingContent::GetImageBlockingS
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
 
   NS_PRECONDITION(aStatus, "Null out param");
   *aStatus = mImageBlockingStatus;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver)
+nsImageLoadingContent::AddObserver(imgIDecoderObserver* aObserver)
 {
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
 
   NS_ENSURE_ARG_POINTER(aObserver);
 
   if (!mObserverList.mObserver) {
     mObserverList.mObserver = aObserver;
     // Don't touch the linking of the list!
@@ -303,17 +374,17 @@ nsImageLoadingContent::AddObserver(imgIN
   if (! observer->mNext) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsImageLoadingContent::RemoveObserver(imgINotificationObserver* aObserver)
+nsImageLoadingContent::RemoveObserver(imgIDecoderObserver* aObserver)
 {
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
 
   NS_ENSURE_ARG_POINTER(aObserver);
 
   if (mObserverList.mObserver == aObserver) {
     mObserverList.mObserver = nullptr;
     // Don't touch the linking of the list!
--- a/content/base/src/nsImageLoadingContent.h
+++ b/content/base/src/nsImageLoadingContent.h
@@ -8,17 +8,18 @@
  * A base class which implements nsIImageLoadingContent and can be
  * subclassed by various content nodes that want to provide image
  * loading functionality (eg <img>, <object>, etc).
  */
 
 #ifndef nsImageLoadingContent_h__
 #define nsImageLoadingContent_h__
 
-#include "imgINotificationObserver.h"
+#include "imgIContainerObserver.h"
+#include "imgIDecoderObserver.h"
 #include "imgIOnloadBlocker.h"
 #include "mozilla/CORSMode.h"
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h" // NS_CONTENT_DELETE_LIST_MEMBER
 #include "nsEventStates.h"
 #include "nsIImageLoadingContent.h"
 #include "nsIRequest.h"
 
@@ -30,17 +31,18 @@ class nsIIOService;
 class nsImageLoadingContent : public nsIImageLoadingContent,
                               public imgIOnloadBlocker
 {
   /* METHODS */
 public:
   nsImageLoadingContent();
   virtual ~nsImageLoadingContent();
 
-  NS_DECL_IMGINOTIFICATIONOBSERVER
+  NS_DECL_IMGICONTAINEROBSERVER
+  NS_DECL_IMGIDECODEROBSERVER
   NS_DECL_NSIIMAGELOADINGCONTENT
   NS_DECL_IMGIONLOADBLOCKER
 
 protected:
   /**
    * LoadImage is called by subclasses when the appropriate
    * attributes (eg 'src' for <img> tags) change.  The string passed
    * in is the new uri string; this consolidates the code for getting
@@ -154,37 +156,34 @@ protected:
    */
   virtual mozilla::CORSMode GetCORSMode();
 
   // Subclasses are *required* to call BindToTree/UnbindFromTree.
   void BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                   nsIContent* aBindingParent, bool aCompileEventHandlers);
   void UnbindFromTree(bool aDeep, bool aNullParent);
 
-  nsresult OnStopRequest(imgIRequest* aRequest, nsresult aStatus);
-  nsresult OnImageIsAnimated(imgIRequest *aRequest);
-
 private:
   /**
    * Struct used to manage the image observers.
    */
   struct ImageObserver {
-    ImageObserver(imgINotificationObserver* aObserver) :
+    ImageObserver(imgIDecoderObserver* aObserver) :
       mObserver(aObserver),
       mNext(nullptr)
     {
       MOZ_COUNT_CTOR(ImageObserver);
     }
     ~ImageObserver()
     {
       MOZ_COUNT_DTOR(ImageObserver);
       NS_CONTENT_DELETE_LIST_MEMBER(ImageObserver, this, mNext);
     }
 
-    nsCOMPtr<imgINotificationObserver> mObserver;
+    nsCOMPtr<imgIDecoderObserver> mObserver;
     ImageObserver* mNext;
   };
 
   /**
    * Struct to report state changes
    */
   struct AutoStateChanger {
     AutoStateChanger(nsImageLoadingContent* aImageContent,
--- a/content/base/src/nsObjectLoadingContent.h
+++ b/content/base/src/nsObjectLoadingContent.h
@@ -93,16 +93,23 @@ class nsObjectLoadingContent : public ns
 
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSIFRAMELOADEROWNER
     NS_DECL_NSIOBJECTLOADINGCONTENT
     NS_DECL_NSIINTERFACEREQUESTOR
     NS_DECL_NSICHANNELEVENTSINK
 
+#ifdef HAVE_CPP_AMBIGUITY_RESOLVING_USING
+    // Fix gcc compile warnings
+    using nsImageLoadingContent::OnStartRequest;
+    using nsImageLoadingContent::OnDataAvailable;
+    using nsImageLoadingContent::OnStopRequest;
+#endif
+
     /**
      * Object state. This is a bitmask of NS_EVENT_STATEs epresenting the
      * current state of the object.
      */
     nsEventStates ObjectState() const;
 
     ObjectType Type() { return mType; }
 
new file mode 100644
--- /dev/null
+++ b/content/base/src/nsStubImageDecoderObserver.cpp
@@ -0,0 +1,89 @@
+/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStubImageDecoderObserver.h"
+
+NS_IMETHODIMP
+nsStubImageDecoderObserver::OnStartRequest(imgIRequest *aRequest)
+{
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStubImageDecoderObserver::OnStartDecode(imgIRequest *aRequest)
+{
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStubImageDecoderObserver::OnStartContainer(imgIRequest *aRequest,
+                                             imgIContainer *aContainer)
+{
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStubImageDecoderObserver::OnStartFrame(imgIRequest *aRequest,
+                                         uint32_t aFrame)
+{
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStubImageDecoderObserver::OnDataAvailable(imgIRequest *aRequest,
+                                            bool aCurrentFrame,
+                                            const nsIntRect * aRect)
+{
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStubImageDecoderObserver::OnStopFrame(imgIRequest *aRequest,
+                                        uint32_t aFrame)
+{
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStubImageDecoderObserver::OnStopContainer(imgIRequest *aRequest,
+                                            imgIContainer *aContainer)
+{
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStubImageDecoderObserver::OnStopDecode(imgIRequest *aRequest,
+                                         nsresult status,
+                                         const PRUnichar *statusArg)
+{
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStubImageDecoderObserver::OnStopRequest(imgIRequest *aRequest, 
+                                          bool aIsLastPart)
+{
+    return NS_OK;
+}
+
+NS_IMETHODIMP 
+nsStubImageDecoderObserver::OnDiscard(imgIRequest *aRequest)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStubImageDecoderObserver::OnImageIsAnimated(imgIRequest *aRequest)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStubImageDecoderObserver::FrameChanged(imgIRequest* aRequest,
+                                         imgIContainer *aContainer,
+                                         const nsIntRect *aDirtyRect)
+{
+    return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/content/base/src/nsStubImageDecoderObserver.h
@@ -0,0 +1,35 @@
+/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsStubImageDecoderObserver is an implementation of the imgIDecoderObserver
+ * interface (except for the methods on nsISupports) that is intended to be
+ * used as a base class within the content/layout library.  All methods do
+ * nothing.
+ */
+
+#ifndef nsStubImageDecoderObserver_h_
+#define nsStubImageDecoderObserver_h_
+
+#include "imgIDecoderObserver.h"
+
+/**
+ * There are two advantages to inheriting from nsStubImageDecoderObserver
+ * rather than directly from imgIDecoderObserver:
+ *  1. smaller compiled code size (since there's no need for the code
+ *     for the empty virtual function implementations for every
+ *     imgIDecoderObserver implementation)
+ *  2. the performance of document's loop over observers benefits from
+ *     the fact that more of the functions called are the same (which
+ *     can reduce instruction cache misses and perhaps improve branch
+ *     prediction)
+ */
+class nsStubImageDecoderObserver : public imgIDecoderObserver {
+public:
+    NS_DECL_IMGICONTAINEROBSERVER
+    NS_DECL_IMGIDECODEROBSERVER
+};
+
+#endif /* !defined(nsStubImageDecoderObserver_h_) */
--- a/content/html/content/src/nsHTMLImageElement.cpp
+++ b/content/html/content/src/nsHTMLImageElement.cpp
@@ -24,17 +24,17 @@
 #include "nsGUIEvent.h"
 #include "nsContentPolicyUtils.h"
 #include "nsIDOMWindow.h"
 #include "nsFocusManager.h"
 
 #include "imgIContainer.h"
 #include "imgILoader.h"
 #include "imgIRequest.h"
-#include "imgINotificationObserver.h"
+#include "imgIDecoderObserver.h"
 
 #include "nsILoadGroup.h"
 
 #include "nsRuleData.h"
 
 #include "nsIJSContextStack.h"
 #include "nsIDOMHTMLMapElement.h"
 #include "nsEventDispatcher.h"
@@ -84,22 +84,23 @@ nsHTMLImageElement::~nsHTMLImageElement(
 NS_IMPL_ADDREF_INHERITED(nsHTMLImageElement, nsGenericElement)
 NS_IMPL_RELEASE_INHERITED(nsHTMLImageElement, nsGenericElement)
 
 
 DOMCI_NODE_DATA(HTMLImageElement, nsHTMLImageElement)
 
 // QueryInterface implementation for nsHTMLImageElement
 NS_INTERFACE_TABLE_HEAD(nsHTMLImageElement)
-  NS_HTML_CONTENT_INTERFACE_TABLE5(nsHTMLImageElement,
+  NS_HTML_CONTENT_INTERFACE_TABLE6(nsHTMLImageElement,
                                    nsIDOMHTMLImageElement,
                                    nsIJSNativeInitializer,
+                                   imgIDecoderObserver,
                                    nsIImageLoadingContent,
-                                   imgIOnloadBlocker,
-                                   imgINotificationObserver)
+                                   imgIContainerObserver,
+                                   imgIOnloadBlocker)
   NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLImageElement,
                                                nsGenericHTMLElement)
 NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLImageElement)
 
 
 NS_IMPL_ELEMENT_CLONE(nsHTMLImageElement)
 
 
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -637,22 +637,23 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_ADDREF_INHERITED(nsHTMLInputElement, nsGenericElement) 
 NS_IMPL_RELEASE_INHERITED(nsHTMLInputElement, nsGenericElement) 
 
 
 DOMCI_NODE_DATA(HTMLInputElement, nsHTMLInputElement)
 
 // QueryInterface implementation for nsHTMLInputElement
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHTMLInputElement)
-  NS_HTML_CONTENT_INTERFACE_TABLE8(nsHTMLInputElement,
+  NS_HTML_CONTENT_INTERFACE_TABLE9(nsHTMLInputElement,
                                    nsIDOMHTMLInputElement,
                                    nsITextControlElement,
                                    nsIPhonetic,
-                                   imgINotificationObserver,
+                                   imgIDecoderObserver,
                                    nsIImageLoadingContent,
+                                   imgIContainerObserver,
                                    imgIOnloadBlocker,
                                    nsIDOMNSEditableElement,
                                    nsIConstraintValidation)
   NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLInputElement,
                                                nsGenericHTMLFormElement)
 NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLInputElement)
 
 // nsIConstraintValidation
--- a/content/html/content/src/nsHTMLObjectElement.cpp
+++ b/content/html/content/src/nsHTMLObjectElement.cpp
@@ -180,23 +180,24 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_ADDREF_INHERITED(nsHTMLObjectElement, nsGenericElement) 
 NS_IMPL_RELEASE_INHERITED(nsHTMLObjectElement, nsGenericElement) 
 
 DOMCI_NODE_DATA(HTMLObjectElement, nsHTMLObjectElement)
 
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHTMLObjectElement)
   NS_HTML_CONTENT_INTERFACE_TABLE_BEGIN(nsHTMLObjectElement)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, nsIDOMHTMLObjectElement)
-    NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, imgINotificationObserver)
+    NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, imgIDecoderObserver)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, nsIRequestObserver)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, nsIStreamListener)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, nsIFrameLoaderOwner)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, nsIObjectLoadingContent)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, nsIImageLoadingContent)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, imgIOnloadBlocker)
+    NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, imgIContainerObserver)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, nsIInterfaceRequestor)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, nsIChannelEventSink)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, nsIConstraintValidation)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, nsIDOMGetSVGDocument)
   NS_OFFSET_AND_INTERFACE_TABLE_END
   NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLObjectElement,
                                                nsGenericHTMLFormElement)
 NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLObjectElement)
--- a/content/html/content/src/nsHTMLSharedObjectElement.cpp
+++ b/content/html/content/src/nsHTMLSharedObjectElement.cpp
@@ -229,18 +229,19 @@ nsHTMLSharedObjectElement::GetClassInfoI
 }
 
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHTMLSharedObjectElement)
   NS_HTML_CONTENT_INTERFACE_TABLE_AMBIGUOUS_BEGIN(nsHTMLSharedObjectElement,
                                                   nsIDOMHTMLAppletElement)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLSharedObjectElement, nsIRequestObserver)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLSharedObjectElement, nsIStreamListener)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLSharedObjectElement, nsIFrameLoaderOwner)
+    NS_INTERFACE_TABLE_ENTRY(nsHTMLSharedObjectElement, imgIContainerObserver)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLSharedObjectElement, nsIObjectLoadingContent)
-    NS_INTERFACE_TABLE_ENTRY(nsHTMLSharedObjectElement, imgINotificationObserver)
+    NS_INTERFACE_TABLE_ENTRY(nsHTMLSharedObjectElement, imgIDecoderObserver)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLSharedObjectElement, nsIImageLoadingContent)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLSharedObjectElement, imgIOnloadBlocker)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLSharedObjectElement, nsIInterfaceRequestor)
     NS_INTERFACE_TABLE_ENTRY(nsHTMLSharedObjectElement, nsIChannelEventSink)
   NS_OFFSET_AND_INTERFACE_TABLE_END
   NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE_AMBIGUOUS(nsHTMLSharedObjectElement,
                                                          nsGenericHTMLElement,
                                                          nsIDOMHTMLAppletElement)
--- a/content/html/content/test/test_bug389797.html
+++ b/content/html/content/test/test_bug389797.html
@@ -67,17 +67,17 @@ function HTML_TAG(aTagName, aImplClass) 
   if (arguments.length > 3) {
     for (i = 0; i < arguments[3].length; ++i) {
       interfacesNonClassinfo[aTagName].push(arguments[3][i]);
     }
   }
 }
 
 const objectIfaces = [
-    "imgINotificationObserver", "nsIRequestObserver", "nsIStreamListener",
+    "imgIDecoderObserver", "nsIRequestObserver", "nsIStreamListener",
     "nsIFrameLoaderOwner", "nsIObjectLoadingContent", "nsIInterfaceRequestor",
     "nsIChannelEventSink"
   ];
 
 var objectIfaces2 = [];
 for each (var iface in objectIfaces) {
   objectIfaces2.push(iface);
 }
@@ -155,19 +155,19 @@ HTML_TAG("head", "Head");
 HTML_TAG("header", "")
 HTML_TAG("hgroup", "")
 HTML_TAG("hr", "HR");
 HTML_TAG("html", "Html");
 HTML_TAG("i", "");
 HTML_TAG("iframe", "IFrame", [ "nsIDOMGetSVGDocument", "nsIDOMMozBrowserFrame" ],
                              [ "nsIFrameLoaderOwner" ]);
 HTML_TAG("image", "Span");
-HTML_TAG("img", "Image", [], [ "imgINotificationObserver",
+HTML_TAG("img", "Image", [], [ "imgIDecoderObserver",
                                "nsIImageLoadingContent" ]);
-HTML_TAG("input", "Input", [], [ "imgINotificationObserver",
+HTML_TAG("input", "Input", [], [ "imgIDecoderObserver",
                                  "nsIImageLoadingContent",
                                  "nsIDOMNSEditableElement" ]);
 HTML_TAG("ins", "Mod");
 HTML_TAG("kbd", "");
 HTML_TAG("keygen", "Span");
 HTML_TAG("label", "Label");
 HTML_TAG("legend", "Legend");
 HTML_TAG("li", "LI");
--- a/content/html/document/src/ImageDocument.cpp
+++ b/content/html/document/src/ImageDocument.cpp
@@ -13,17 +13,17 @@
 #include "nsIDOMEvent.h"
 #include "nsIDOMKeyEvent.h"
 #include "nsIDOMMouseEvent.h"
 #include "nsIDOMEventListener.h"
 #include "nsGkAtoms.h"
 #include "imgIRequest.h"
 #include "imgILoader.h"
 #include "imgIContainer.h"
-#include "imgINotificationObserver.h"
+#include "nsStubImageDecoderObserver.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 #include "nsAutoPtr.h"
 #include "nsStyleSet.h"
 #include "nsIChannel.h"
 #include "nsIContentPolicy.h"
 #include "nsContentPolicyUtils.h"
@@ -59,17 +59,17 @@ public:
   virtual ~ImageListener();
 
   /* nsIRequestObserver */
   NS_IMETHOD OnStartRequest(nsIRequest* request, nsISupports *ctxt);
 };
 
 class ImageDocument : public MediaDocument
                     , public nsIImageDocument
-                    , public imgINotificationObserver
+                    , public nsStubImageDecoderObserver
                     , public nsIDOMEventListener
 {
 public:
   ImageDocument();
   virtual ~ImageDocument();
 
   NS_DECL_ISUPPORTS_INHERITED
 
@@ -84,17 +84,22 @@ public:
                                      nsIContentSink*     aSink = nullptr);
 
   virtual void SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject);
   virtual void Destroy();
   virtual void OnPageShow(bool aPersisted,
                           nsIDOMEventTarget* aDispatchStartTarget);
 
   NS_DECL_NSIIMAGEDOCUMENT
-  NS_DECL_IMGINOTIFICATIONOBSERVER
+
+  // imgIDecoderObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage);
+  NS_IMETHOD OnStopContainer(imgIRequest* aRequest, imgIContainer* aImage);
+  NS_IMETHOD OnStopDecode(imgIRequest *aRequest, nsresult aStatus, const PRUnichar *aStatusArg);
+  NS_IMETHOD OnDiscard(imgIRequest *aRequest);
 
   // nsIDOMEventListener
   NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ImageDocument, MediaDocument)
 
   friend class ImageListener;
 
@@ -113,19 +118,16 @@ protected:
   float GetRatio() {
     return NS_MIN((float)mVisibleWidth / mImageWidth,
                   (float)mVisibleHeight / mImageHeight);
   }
 
   void ResetZoomLevel();
   float GetZoomLevel();
 
-  nsresult OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage);
-  nsresult OnStopRequest(imgIRequest *aRequest, nsresult aStatus);
-
   nsCOMPtr<nsIContent>          mImageContent;
 
   int32_t                       mVisibleWidth;
   int32_t                       mVisibleHeight;
   int32_t                       mImageWidth;
   int32_t                       mImageHeight;
 
   bool                          mResizeImageByDefault;
@@ -231,17 +233,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_ADDREF_INHERITED(ImageDocument, MediaDocument)
 NS_IMPL_RELEASE_INHERITED(ImageDocument, MediaDocument)
 
 DOMCI_NODE_DATA(ImageDocument, ImageDocument)
 
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(ImageDocument)
   NS_HTML_DOCUMENT_INTERFACE_TABLE_BEGIN(ImageDocument)
     NS_INTERFACE_TABLE_ENTRY(ImageDocument, nsIImageDocument)
-    NS_INTERFACE_TABLE_ENTRY(ImageDocument, imgINotificationObserver)
+    NS_INTERFACE_TABLE_ENTRY(ImageDocument, imgIDecoderObserver)
+    NS_INTERFACE_TABLE_ENTRY(ImageDocument, imgIContainerObserver)
     NS_INTERFACE_TABLE_ENTRY(ImageDocument, nsIDOMEventListener)
   NS_OFFSET_AND_INTERFACE_TABLE_END
   NS_OFFSET_AND_INTERFACE_TABLE_TO_MAP_SEGUE
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(ImageDocument)
 NS_INTERFACE_MAP_END_INHERITING(MediaDocument)
 
 
 nsresult
@@ -501,70 +504,46 @@ ImageDocument::ToggleImageSize()
     ResetZoomLevel();
     ShrinkToFit();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-ImageDocument::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData)
-{
-  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
-    nsCOMPtr<imgIContainer> image;
-    aRequest->GetImage(getter_AddRefs(image));
-    return OnStartContainer(aRequest, image);
-  }
-
-  if (aType == imgINotificationObserver::DECODE_COMPLETE) {
-    if (mImageContent) {
-      // Update the background-color of the image only after the
-      // image has been decoded to prevent flashes of just the
-      // background-color.
-      mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
-                             NS_LITERAL_STRING("decoded"), true);
-    }
-  }
-
-  if (aType == imgINotificationObserver::DISCARD) {
-    // mImageContent can be null if the document is already destroyed
-    if (mImageContent) {
-      // Remove any decoded-related styling when the image is unloaded.
-      mImageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_class,
-                               true);
-    }
-  }
-
-  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
-    uint32_t reqStatus;
-    aRequest->GetImageStatus(&reqStatus);
-    nsresult status =
-        reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
-    return OnStopRequest(aRequest, status);
-  }
-
-  return NS_OK;
-}
-
-nsresult
 ImageDocument::OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage)
 {
   aImage->GetWidth(&mImageWidth);
   aImage->GetHeight(&mImageHeight);
   nsCOMPtr<nsIRunnable> runnable =
     NS_NewRunnableMethod(this, &ImageDocument::DefaultCheckOverflowing);
   nsContentUtils::AddScriptRunner(runnable);
   UpdateTitleAndCharset();
 
   return NS_OK;
 }
 
-nsresult
-ImageDocument::OnStopRequest(imgIRequest *aRequest,
-                             nsresult aStatus)
+NS_IMETHODIMP
+ImageDocument::OnStopContainer(imgIRequest* aRequest, imgIContainer* aImage)
+{
+  if (mImageContent) {
+    // Update the background-color of the image only after the
+    // image has been decoded to prevent flashes of just the
+    // background-color.
+    mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+                           NS_LITERAL_STRING("decoded"), true);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ImageDocument::OnStopDecode(imgIRequest *aRequest,
+                            nsresult aStatus,
+                            const PRUnichar *aStatusArg)
 {
   UpdateTitleAndCharset();
 
   // mImageContent can be null if the document is already destroyed
   if (NS_FAILED(aStatus) && mStringBundle && mImageContent) {
     nsAutoCString src;
     mDocumentURI->GetSpec(src);
     NS_ConvertUTF8toUTF16 srcString(src);
@@ -576,16 +555,28 @@ ImageDocument::OnStopRequest(imgIRequest
 
     mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, errorMsg, false);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+ImageDocument::OnDiscard(imgIRequest *aRequest)
+{
+  // mImageContent can be null if the document is already destroyed
+  if (mImageContent) {
+    // Remove any decoded-related styling when the image is unloaded.
+    mImageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+                             true);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 ImageDocument::HandleEvent(nsIDOMEvent* aEvent)
 {
   nsAutoString eventType;
   aEvent->GetType(eventType);
   if (eventType.EqualsLiteral("resize")) {
     CheckOverflowing(false);
   }
   else if (eventType.EqualsLiteral("click") && mClickResizingEnabled) {
--- a/content/svg/content/src/nsSVGFilters.cpp
+++ b/content/svg/content/src/nsSVGFilters.cpp
@@ -5500,17 +5500,17 @@ NS_IMPL_RELEASE_INHERITED(nsSVGFEImageEl
 
 DOMCI_NODE_DATA(SVGFEImageElement, nsSVGFEImageElement)
 
 NS_INTERFACE_TABLE_HEAD(nsSVGFEImageElement)
   NS_NODE_INTERFACE_TABLE9(nsSVGFEImageElement, nsIDOMNode, nsIDOMElement,
                            nsIDOMSVGElement,
                            nsIDOMSVGFilterPrimitiveStandardAttributes,
                            nsIDOMSVGFEImageElement, nsIDOMSVGURIReference,
-                           imgINotificationObserver, nsIImageLoadingContent,
+                           imgIDecoderObserver, nsIImageLoadingContent,
                            imgIOnloadBlocker)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGFEImageElement)
 NS_INTERFACE_MAP_END_INHERITING(nsSVGFEImageElementBase)
 
 //----------------------------------------------------------------------
 // Implementation
 
 nsSVGFEImageElement::nsSVGFEImageElement(already_AddRefed<nsINodeInfo> aNodeInfo)
@@ -5751,37 +5751,53 @@ nsSVGFEImageElement::GetPreserveAspectRa
 nsSVGElement::StringAttributesInfo
 nsSVGFEImageElement::GetStringInfo()
 {
   return StringAttributesInfo(mStringAttributes, sStringInfo,
                               ArrayLength(sStringInfo));
 }
 
 //----------------------------------------------------------------------
-// imgINotificationObserver methods
+// imgIDecoderObserver methods
+
+NS_IMETHODIMP
+nsSVGFEImageElement::OnStopDecode(imgIRequest *aRequest,
+                                  nsresult status,
+                                  const PRUnichar *statusArg)
+{
+  nsresult rv =
+    nsImageLoadingContent::OnStopDecode(aRequest, status, statusArg);
+  Invalidate();
+  return rv;
+}
 
 NS_IMETHODIMP
-nsSVGFEImageElement::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData)
-{
-  nsresult rv = nsImageLoadingContent::Notify(aRequest, aType, aData);
-
-  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
-    // Request a decode
-    nsCOMPtr<imgIContainer> container;
-    aRequest->GetImage(getter_AddRefs(container));
-    NS_ABORT_IF_FALSE(container, "who sent the notification then?");
-    container->StartDecoding();
-  }
-
-  if (aType == imgINotificationObserver::LOAD_COMPLETE ||
-      aType == imgINotificationObserver::FRAME_UPDATE ||
-      aType == imgINotificationObserver::SIZE_AVAILABLE) {
-    Invalidate();
-  }
-
+nsSVGFEImageElement::FrameChanged(imgIRequest* aRequest,
+                                  imgIContainer *aContainer,
+                                  const nsIntRect *aDirtyRect)
+{
+  nsresult rv =
+    nsImageLoadingContent::FrameChanged(aRequest, aContainer, aDirtyRect);
+  Invalidate();
+  return rv;
+}
+
+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->StartDecoding();
+
+  // We have a size - invalidate
+  Invalidate();
   return rv;
 }
 
 //----------------------------------------------------------------------
 // helper methods
 
 void
 nsSVGFEImageElement::Invalidate()
--- a/content/svg/content/src/nsSVGFilters.h
+++ b/content/svg/content/src/nsSVGFilters.h
@@ -262,17 +262,26 @@ public:
   virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify);
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers);
   virtual void UnbindFromTree(bool aDeep, bool aNullParent);
   virtual nsEventStates IntrinsicState() const;
 
-  NS_IMETHODIMP Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData);
+  // imgIDecoderObserver
+  NS_IMETHOD OnStopDecode(imgIRequest *aRequest, nsresult status,
+                          const PRUnichar *statusArg);
+  // imgIContainerObserver
+  NS_IMETHOD FrameChanged(imgIRequest* aRequest,
+                          imgIContainer *aContainer,
+                          const nsIntRect *aDirtyRect);
+  // imgIContainerObserver
+  NS_IMETHOD OnStartContainer(imgIRequest *aRequest,
+                              imgIContainer *aContainer);
 
   void MaybeLoadSVGImage();
 
   virtual nsXPCClassInfo* GetClassInfo();
 
   virtual nsIDOMNode* AsDOMNode() { return this; }
 private:
   // Invalidate users of the filter containing this element.
--- a/content/svg/content/src/nsSVGImageElement.cpp
+++ b/content/svg/content/src/nsSVGImageElement.cpp
@@ -5,17 +5,17 @@
 
 #include "mozilla/Util.h"
 
 #include "nsSVGImageElement.h"
 #include "nsCOMPtr.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "imgIContainer.h"
-#include "imgINotificationObserver.h"
+#include "imgIDecoderObserver.h"
 #include "gfxContext.h"
 
 using namespace mozilla;
 
 nsSVGElement::LengthInfo nsSVGImageElement::sLengthInfo[4] =
 {
   { &nsGkAtoms::x, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::X },
   { &nsGkAtoms::y, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::Y },
@@ -37,17 +37,17 @@ NS_IMPL_ADDREF_INHERITED(nsSVGImageEleme
 NS_IMPL_RELEASE_INHERITED(nsSVGImageElement,nsSVGImageElementBase)
 
 DOMCI_NODE_DATA(SVGImageElement, nsSVGImageElement)
 
 NS_INTERFACE_TABLE_HEAD(nsSVGImageElement)
   NS_NODE_INTERFACE_TABLE9(nsSVGImageElement, nsIDOMNode, nsIDOMElement,
                            nsIDOMSVGElement, nsIDOMSVGTests,
                            nsIDOMSVGImageElement,
-                           nsIDOMSVGURIReference, imgINotificationObserver,
+                           nsIDOMSVGURIReference, imgIDecoderObserver,
                            nsIImageLoadingContent, imgIOnloadBlocker)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGImageElement)
 NS_INTERFACE_MAP_END_INHERITING(nsSVGImageElementBase)
 
 //----------------------------------------------------------------------
 // Implementation
 
 nsSVGImageElement::nsSVGImageElement(already_AddRefed<nsINodeInfo> aNodeInfo)
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -453,20 +453,20 @@ nsICODecoder::WriteInternal(const char* 
   // If we have a BMP
   if (!mIsPNG && mContainedDecoder && mPos >= mImageOffset + BITMAPINFOSIZE) {
     uint16_t numColors = GetNumColors();
     if (numColors == (uint16_t)-1) {
       PostDataError();
       return;
     }
     // Feed the actual image data (not including headers) into the BMP decoder
-    uint32_t bmpDataOffset = mDirEntry.mImageOffset + BITMAPINFOSIZE;
-    uint32_t bmpDataEnd = mDirEntry.mImageOffset + BITMAPINFOSIZE + 
-                          static_cast<nsBMPDecoder*>(mContainedDecoder.get())->GetCompressedImageSize() +
-                          4 * numColors;
+    int32_t bmpDataOffset = mDirEntry.mImageOffset + BITMAPINFOSIZE;
+    int32_t bmpDataEnd = mDirEntry.mImageOffset + BITMAPINFOSIZE + 
+                         static_cast<nsBMPDecoder*>(mContainedDecoder.get())->GetCompressedImageSize() +
+                         4 * numColors;
 
     // If we are feeding in the core image data, but we have not yet
     // reached the ICO's 'AND buffer mask'
     if (mPos >= bmpDataOffset && mPos < bmpDataEnd) {
 
       // Figure out how much data the BMP decoder wants
       uint32_t toFeed = bmpDataEnd - mPos;
       if (toFeed > aCount) {
--- a/image/encoders/ico/nsICOEncoder.cpp
+++ b/image/encoders/ico/nsICOEncoder.cpp
@@ -273,17 +273,17 @@ nsICOEncoder::ParseOptions(const nsAStri
   // From format: format=<png|bmp>;bpp=<bpp_value>
   // to format: [0] = format=<png|bmp>, [1] = bpp=<bpp_value>
   nsTArray<nsCString> nameValuePairs;
   if (!ParseString(NS_ConvertUTF16toUTF8(aOptions), ';', nameValuePairs)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   // For each name/value pair in the set
-  for (unsigned i = 0; i < nameValuePairs.Length(); ++i) {
+  for (int i = 0; i < nameValuePairs.Length(); ++i) {
 
     // Split the name value pair [0] = name, [1] = value
     nsTArray<nsCString> nameValuePair;
     if (!ParseString(nameValuePairs[i], '=', nameValuePair)) {
       return NS_ERROR_INVALID_ARG;
     }
     if (nameValuePair.Length() != 2) {
       return NS_ERROR_INVALID_ARG;
--- a/image/public/Makefile.in
+++ b/image/public/Makefile.in
@@ -18,17 +18,15 @@ EXPORTS		= ImageLogging.h
 XPIDLSRCS	= \
 		imgICache.idl             \
 		imgIContainer.idl         \
 		imgIContainerDebug.idl    \
 		imgIContainerObserver.idl \
 		imgIDecoderObserver.idl   \
 		imgIEncoder.idl           \
 		imgILoader.idl            \
-		imgINotificationObserver.idl \
 		imgIOnloadBlocker.idl     \
 		imgIRequest.idl           \
-		imgIScriptedNotificationObserver.idl \
 		imgITools.idl             \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
--- a/image/public/imgIContainerObserver.idl
+++ b/image/public/imgIContainerObserver.idl
@@ -5,22 +5,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 %{C++
 #include "nsRect.h"
 %}
 
+interface imgIContainer;
 interface imgIRequest;
 [ptr] native nsIntRect(nsIntRect);
 
 /**
  * imgIContainerObserver interface
  *
  * @author Stuart Parmenter <pavlov@netscape.com>
  * @version 0.1
  */
-[scriptable, uuid(f4aaf410-e88f-4036-b91c-610c9c11d825)]
+[scriptable, uuid(f01efdb3-4b20-4313-a636-a2aa01a4ef5d)]
 interface imgIContainerObserver : nsISupports
 {
-  [noscript] void frameChanged([const] in nsIntRect aDirtyRect);
+  [noscript] void frameChanged(in imgIRequest aRequest,
+                               in imgIContainer aContainer,
+                               [const] in nsIntRect aDirtyRect);
 };
--- a/image/public/imgIDecoderObserver.idl
+++ b/image/public/imgIDecoderObserver.idl
@@ -38,83 +38,103 @@ interface imgIContainer;
  * 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(0089cf0c-210c-4b44-9ab0-8099b32bcf64)]
+[scriptable, uuid(5ca71b89-1a2f-475f-881d-d76c1531c4c8)]
 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)
    */
-  void onStartRequest();
+  void onStartRequest(in imgIRequest aRequest);
 
   /**
    * 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();
+  void onStartDecode(in imgIRequest aRequest);
 
   /**
    * 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();
+  void onStartContainer(in imgIRequest aRequest, in imgIContainer aContainer);
+
+  /**
+   * Decode notification.
+   *
+   * called when each frame is created.
+   */
+  void onStartFrame(in imgIRequest aRequest, in unsigned long aFrame);
 
   /**
    * Decode notification.
    *
    * called when there is more to paint.
    */
-  [noscript] void onDataAvailable([const] in nsIntRect aRect);
+  [noscript] void onDataAvailable(in imgIRequest aRequest, in boolean aCurrentFrame, [const] in nsIntRect aRect);
 
   /**
    * Decode notification.
    *
    * called when a frame is finished decoding.
    */
-  void onStopFrame();
+  void onStopFrame(in imgIRequest aRequest, in unsigned long aFrame);
+
+  /**
+   * Do not implement this. It is useless and going away.
+   */
+  void onStopContainer(in imgIRequest aRequest, in imgIContainer aContainer);
 
   /**
    * Notification for when an image is known to be animated. This should be
    * fired at the earliest possible time.
    */
-  void onImageIsAnimated();
+  void onImageIsAnimated(in imgIRequest aRequest);
 
   /**
-   * Decode notification.
+   * In theory a decode notification, but currently a load notification.
    *
-   * Called when all decoding has terminated.
+   * 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 nsresult status);
+  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)
    */
-  void onStopRequest(in boolean aIsLastPart);
+  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();
+  void onDiscard(in imgIRequest aRequest);
 
 };
--- a/image/public/imgILoader.idl
+++ b/image/public/imgILoader.idl
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
-interface imgINotificationObserver;
+interface imgIDecoderObserver;
 interface imgIRequest;
 
 interface nsIChannel;
 interface nsILoadGroup;
 interface nsIPrincipal;
 interface nsIStreamListener;
 interface nsIURI;
 
@@ -56,17 +56,17 @@ interface imgILoader : nsISupports
    * make sure to Cancel() the resulting request before the observer
    * goes away.
    */
   imgIRequest loadImage(in nsIURI aURI,
                         in nsIURI aInitialDocumentURL,
                         in nsIURI aReferrerURI,
                         in nsIPrincipal aLoadingPrincipal,
                         in nsILoadGroup aLoadGroup,
-                        in imgINotificationObserver aObserver,
+                        in imgIDecoderObserver aObserver,
                         in nsISupports aCX,
                         in nsLoadFlags aLoadFlags,
                         in nsISupports cacheKey,
                         in imgIRequest aRequest,
                         in nsIChannelPolicy channelPolicy);
 
   /**
    * Start the load and decode of an image.
@@ -81,12 +81,12 @@ interface imgILoader : nsISupports
    *        not interested in the data. @aChannel will be canceled for you in
    *        this case.
    *
    * libpr0n does NOT keep a strong ref to the observer; this prevents
    * reference cycles.  This means that callers of loadImageWithChannel should
    * make sure to Cancel() the resulting request before the observer goes away.
    */
   imgIRequest loadImageWithChannel(in nsIChannel aChannel,
-                        in imgINotificationObserver aObserver,
+                        in imgIDecoderObserver aObserver,
                         in nsISupports cx,
                         out nsIStreamListener aListener);
 };
deleted file mode 100644
--- a/image/public/imgINotificationObserver.idl
+++ /dev/null
@@ -1,29 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-interface imgIRequest;
-
-%{C++
-#include "nsRect.h"
-%}
-
-[ptr] native nsIntRect(nsIntRect);
-
-[scriptable, builtinclass, uuid(90b3d21c-317d-4d96-93c0-12add64a26bf)]
-interface imgINotificationObserver : nsISupports
-{
-  const long SIZE_AVAILABLE = 1;
-  const long FRAME_UPDATE = 2;
-  const long FRAME_COMPLETE = 3;
-  const long LOAD_COMPLETE = 4;
-  const long DECODE_COMPLETE = 5;
-  const long DISCARD = 6;
-  const long IS_ANIMATED = 7;
-
-  [noscript] void notify(in imgIRequest aProxy, in long aType, [const] in nsIntRect aRect);
-};
--- a/image/public/imgIRequest.idl
+++ b/image/public/imgIRequest.idl
@@ -3,28 +3,28 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 #include "nsIRequest.idl"
 
 interface imgIContainer;
-interface imgINotificationObserver;
+interface imgIDecoderObserver;
 interface nsIURI;
 interface nsIPrincipal;
 
 /**
  * imgIRequest interface
  *
  * @author Stuart Parmenter <stuart@mozilla.com>
  * @version 0.1
  * @see imagelib2
  */
-[scriptable, uuid(3ea9fc87-2e97-45bf-b373-d1dd253a0b5e)]
+[scriptable, uuid(a5a785a8-9881-11e1-aaff-001fbc092072)]
 interface imgIRequest : nsIRequest
 {
   /**
    * the image container...
    * @return the image object associated with the request.
    * @attention NEED DOCS
    */
   readonly attribute imgIContainer image;
@@ -77,27 +77,27 @@ interface imgIRequest : nsIRequest
 
   /**
    * 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).
    */
   readonly attribute nsIURI URI;
 
-  readonly attribute imgINotificationObserver notificationObserver;
+  readonly attribute imgIDecoderObserver decoderObserver;
 
   readonly attribute string mimeType;
 
   /**
    * Clone this request; the returned request will have aObserver as the
    * observer.  aObserver will be notified synchronously (before the clone()
    * call returns) with all the notifications that have already been dispatched
    * for this image load.
    */
-  imgIRequest clone(in imgINotificationObserver aObserver);
+  imgIRequest clone(in imgIDecoderObserver aObserver);
 
   /**
    * The principal gotten from the channel the image was loaded from.
    */
   readonly attribute nsIPrincipal imagePrincipal;
 
   /**
    * Whether the request is multipart (ie, multipart/x-mixed-replace)
deleted file mode 100644
--- a/image/public/imgIScriptedNotificationObserver.idl
+++ /dev/null
@@ -1,21 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-interface imgIRequest;
-
-[scriptable, uuid(10be55b3-2029-41a7-a975-538efed250ed)]
-interface imgIScriptedNotificationObserver : nsISupports
-{
-  void sizeAvailable(in imgIRequest aRequest);
-  void frameUpdate(in imgIRequest aRequest);
-  void frameComplete(in imgIRequest aRequest);
-  void loadComplete(in imgIRequest aRequest);
-  void decodeComplete(in imgIRequest aRequest);
-  void discard(in imgIRequest aRequest);
-  void isAnimated(in imgIRequest aRequest);
-};
--- a/image/public/imgITools.idl
+++ b/image/public/imgITools.idl
@@ -6,20 +6,18 @@
 
 #include "nsISupports.idl"
 
 interface nsIInputStream;
 interface imgIContainer;
 interface imgILoader;
 interface imgICache;
 interface nsIDOMDocument;
-interface imgIScriptedNotificationObserver;
-interface imgINotificationObserver;
 
-[scriptable, uuid(98bd5bf9-87eb-4d92-81b1-4cd10c64f7b2)]
+[scriptable, uuid(53dd1cbe-cb9f-4d9e-8104-1ab72851c88e)]
 interface imgITools : nsISupports
 {
     /**
      * decodeImageData
      * Caller provides an input stream and mimetype. We read from the stream
      * and decompress it (according to the specified mime type) and return
      * the resulting imgIContainer. (If the caller already has a container,
      * it can be provided as input to be reused).
@@ -120,17 +118,9 @@ interface imgITools : nsISupports
      */
     nsIInputStream encodeCroppedImage(in imgIContainer aContainer,
                                       in ACString aMimeType,
                                       in long aOffsetX,
                                       in long aOffsetY,
                                       in long aWidth,
                                       in long aHeight,
                                       [optional] in AString outputOptions);
-
-    /**
-     * Create a wrapper around a scripted notification observer (ordinarily
-     * imgINotificationObserver cannot be implemented from scripts).
-     *
-     * @param aObserver The scripted observer to wrap 
-     */
-    imgINotificationObserver createScriptedObserver(in imgIScriptedNotificationObserver aObserver);
 };
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -40,17 +40,17 @@ Decoder::~Decoder()
 void
 Decoder::Init()
 {
   // No re-initializing
   NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
 
   // Fire OnStartDecode at init time to support bug 512435
   if (!IsSizeDecode() && mObserver)
-      mObserver->OnStartDecode();
+      mObserver->OnStartDecode(nullptr);
 
   // Implementation-specific initialization
   InitInternal();
   mInitialized = true;
 }
 
 // Initializes a decoder whose aImage and aObserver is already being used by a
 // parent decoder
@@ -119,17 +119,18 @@ Decoder::Finish()
     bool salvage = !HasDecoderError() && mImage.GetNumFrames();
 
     // If we're salvaging, say we finished decoding
     if (salvage)
       mImage.DecodingComplete();
 
     // Fire teardown notifications
     if (mObserver) {
-      mObserver->OnStopDecode(salvage ? NS_OK : NS_ERROR_FAILURE);
+      mObserver->OnStopContainer(nullptr, &mImage);
+      mObserver->OnStopDecode(nullptr, salvage ? NS_OK : NS_ERROR_FAILURE, nullptr);
     }
   }
 }
 
 void
 Decoder::FinishSharedDecoder()
 {
   if (!HasError()) {
@@ -161,17 +162,18 @@ Decoder::FlushInvalidations()
 
     mImage.GetWidth(&width);
     mImage.GetHeight(&height);
     nsIntRect mImageBound(0, 0, width, height);
 
     mInvalidRect.Inflate(1);
     mInvalidRect = mInvalidRect.Intersect(mImageBound);
 #endif
-    mObserver->OnDataAvailable(&mInvalidRect);
+    bool isCurrentFrame = mImage.GetCurrentFrameIndex() == (mFrameCount - 1);
+    mObserver->OnDataAvailable(nullptr, isCurrentFrame, &mInvalidRect);
   }
 
   // Clear the invalidation rectangle
   mInvalidRect.SetEmpty();
 }
 
 /*
  * Hook stubs. Override these as necessary in decoder implementations.
@@ -192,17 +194,17 @@ Decoder::PostSize(int32_t aWidth, int32_
   NS_ABORT_IF_FALSE(aWidth >= 0, "Width can't be negative!");
   NS_ABORT_IF_FALSE(aHeight >= 0, "Height can't be negative!");
 
   // Tell the image
   mImage.SetSize(aWidth, aHeight);
 
   // Notify the observer
   if (mObserver)
-    mObserver->OnStartContainer();
+    mObserver->OnStartContainer(nullptr, &mImage);
 }
 
 void
 Decoder::PostFrameStart()
 {
   // We shouldn't already be mid-frame
   NS_ABORT_IF_FALSE(!mInFrame, "Starting new frame but not done with old one!");
 
@@ -215,36 +217,40 @@ Decoder::PostFrameStart()
   mFrameCount++;
   mInFrame = true;
 
   // Decoder implementations should only call this method if they successfully
   // appended the frame to the image. So mFrameCount should always match that
   // reported by the Image.
   NS_ABORT_IF_FALSE(mFrameCount == mImage.GetNumFrames(),
                     "Decoder frame count doesn't match image's!");
+
+  // Fire notification
+  if (mObserver)
+    mObserver->OnStartFrame(nullptr, mFrameCount - 1); // frame # is zero-indexed
 }
 
 void
 Decoder::PostFrameStop()
 {
   // We should be mid-frame
   NS_ABORT_IF_FALSE(mInFrame, "Stopping frame when we didn't start one!");
 
   // Update our state
   mInFrame = false;
 
   // Flush any invalidations before we finish the frame
   FlushInvalidations();
 
   // Fire notifications
   if (mObserver) {
-    mObserver->OnStopFrame();
+    mObserver->OnStopFrame(nullptr, mFrameCount - 1); // frame # is zero-indexed
     if (mFrameCount > 1 && !mIsAnimated) {
       mIsAnimated = true;
-      mObserver->OnImageIsAnimated();
+      mObserver->OnImageIsAnimated(nullptr);
     }
   }
 }
 
 void
 Decoder::PostInvalidation(nsIntRect& aRect)
 {
   // We should be mid-frame
@@ -267,17 +273,18 @@ Decoder::PostDecodeDone()
   bool isNonPremult = GetDecodeFlags() & DECODER_NO_PREMULTIPLY_ALPHA;
   for (int i = 0; i < frames; i++) {
     mImage.SetFrameAsNonPremult(i, isNonPremult);
   }
 
   // Notify
   mImage.DecodingComplete();
   if (mObserver) {
-    mObserver->OnStopDecode(NS_OK);
+    mObserver->OnStopContainer(nullptr, &mImage);
+    mObserver->OnStopDecode(nullptr, NS_OK, nullptr);
   }
 }
 
 void
 Decoder::PostDataError()
 {
   mDataError = true;
 }
--- a/image/src/Image.cpp
+++ b/image/src/Image.cpp
@@ -16,17 +16,17 @@ Image::Image(imgStatusTracker* aStatusTr
   mInitialized(false),
   mAnimating(false),
   mError(false)
 {
   if (aStatusTracker) {
     mStatusTracker = aStatusTracker;
     mStatusTracker->SetImage(this);
   } else {
-    mStatusTracker = new imgStatusTracker(this, nullptr);
+    mStatusTracker = new imgStatusTracker(this);
   }
 }
 
 uint32_t
 Image::SizeOfData()
 {
   if (mError)
     return 0;
--- a/image/src/Makefile.in
+++ b/image/src/Makefile.in
@@ -22,17 +22,16 @@ EXPORTS		=  imgLoader.h \
 		   imgRequest.h \
 		   $(NULL)
 
 CPPSRCS		= \
 			Image.cpp \
 			Decoder.cpp \
 			DiscardTracker.cpp \
 			RasterImage.cpp \
-			ScriptedNotificationObserver.cpp \
 			SVGDocumentWrapper.cpp \
 			VectorImage.cpp \
 			imgFrame.cpp \
 			imgLoader.cpp    \
 			imgRequest.cpp   \
 			imgRequestProxy.cpp \
 			imgTools.cpp \
 			imgStatusTracker.cpp \
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -509,17 +509,17 @@ RasterImage::RequestRefresh(const mozill
     // Notify listeners that our frame has actually changed, but do this only
     // once for all frames that we've now passed (if AdvanceFrame() was called
     // more than once).
     #ifdef DEBUG
       mFramesNotified++;
     #endif
 
     UpdateImageContainer();
-    observer->FrameChanged(&dirtyRect);
+    observer->FrameChanged(nullptr, this, &dirtyRect);
   }
 }
 
 //******************************************************************************
 /* [noscript] imgIContainer extractFrame(uint32_t aWhichFrame,
  *                                       [const] in nsIntRect aRegion,
  *                                       in uint32_t aFlags); */
 NS_IMETHODIMP
@@ -1495,17 +1495,17 @@ RasterImage::ResetAnimation()
   UpdateImageContainer();
 
   // 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 (mAnimating && observer)
-    observer->FrameChanged(&(mAnim->firstFrameRefreshArea));
+    observer->FrameChanged(nullptr, this, &(mAnim->firstFrameRefreshArea));
 
   if (ShouldAnimate()) {
     StartAnimation();
     // The animation may not have been running before, if mAnimationFinished
     // was false (before we changed it to true in this function). So, mark the
     // animation as running.
     mAnimating = true;
   }
@@ -2279,17 +2279,17 @@ RasterImage::Discard(bool force)
   mFrames.Clear();
 
   // Flag that we no longer have decoded frames for this image
   mDecoded = false;
 
   // Notify that we discarded
   nsCOMPtr<imgIDecoderObserver> observer(do_QueryReferent(mObserver));
   if (observer)
-    observer->OnDiscard();
+    observer->OnDiscard(nullptr);
 
   if (force)
     DiscardTracker::Remove(&mDiscardTrackerNode);
 
   // Log
   PR_LOG(gCompressedImageAccountingLog, PR_LOG_DEBUG,
          ("CompressedImageAccounting: discarded uncompressed image "
           "data from RasterImage %p (%s) - %d frames (cached count: %d); "
@@ -2769,17 +2769,17 @@ RasterImage::DrawWorker::Run()
     if (request->stopped) {
       ScaleRequest::Stop(request->image);
     }
     nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(request->image->mObserver));
     if (request->done && observer) {
       imgFrame *scaledFrame = request->dstFrame.get();
       scaledFrame->ImageUpdated(scaledFrame->GetRect());
       nsIntRect frameRect = request->srcFrame->GetRect();
-      observer->FrameChanged(&frameRect);
+      observer->FrameChanged(nullptr, request->image, &frameRect);
     }
   }
 
   return NS_OK;
 }
 
 void
 RasterImage::DrawWorker::RequestDraw(RasterImage* aImg)
deleted file mode 100644
--- a/image/src/ScriptedNotificationObserver.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "ScriptedNotificationObserver.h"
-#include "imgIScriptedNotificationObserver.h"
-#include "nsCycleCollectionParticipant.h"
-
-using namespace mozilla::image;
-
-NS_IMPL_CYCLE_COLLECTION_1(ScriptedNotificationObserver, mInner)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptedNotificationObserver)
-  NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptedNotificationObserver)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptedNotificationObserver)
-
-ScriptedNotificationObserver::ScriptedNotificationObserver(
-    imgIScriptedNotificationObserver* aInner)
-: mInner(aInner)
-{
-}
-
-NS_IMETHODIMP
-ScriptedNotificationObserver::Notify(imgIRequest* aRequest,
-                                     int32_t aType,
-                                     const nsIntRect* /*aUnused*/)
-{
-  if (aType == imgINotificationObserver::SIZE_AVAILABLE)
-    return mInner->SizeAvailable(aRequest);
-  if (aType == imgINotificationObserver::FRAME_UPDATE)
-    return mInner->FrameUpdate(aRequest);
-  if (aType == imgINotificationObserver::FRAME_COMPLETE)
-    return mInner->FrameComplete(aRequest);
-  if (aType == imgINotificationObserver::DECODE_COMPLETE)
-    return mInner->DecodeComplete(aRequest);
-  if (aType == imgINotificationObserver::LOAD_COMPLETE)
-    return mInner->LoadComplete(aRequest);
-  if (aType == imgINotificationObserver::DISCARD)
-    return mInner->Discard(aRequest);
-  if (aType == imgINotificationObserver::IS_ANIMATED)
-    return mInner->IsAnimated(aRequest);
-  return NS_OK;
-}
deleted file mode 100644
--- a/image/src/ScriptedNotificationObserver.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "imgINotificationObserver.h"
-#include "nsCOMPtr.h"
-#include "nsCycleCollectionParticipant.h"
-
-class imgIScriptedNotificationObserver;
-
-namespace mozilla {
-namespace image {
-
-class ScriptedNotificationObserver : public imgINotificationObserver
-{
-public:
-  ScriptedNotificationObserver(imgIScriptedNotificationObserver* aInner);
-  virtual ~ScriptedNotificationObserver() {}
-
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_IMGINOTIFICATIONOBSERVER
-  NS_DECL_CYCLE_COLLECTION_CLASS(ScriptedNotificationObserver)
-
-private:
-  nsCOMPtr<imgIScriptedNotificationObserver> mInner;
-};
-
-}}
--- a/image/src/VectorImage.cpp
+++ b/image/src/VectorImage.cpp
@@ -676,21 +676,21 @@ VectorImage::OnStopRequest(nsIRequest* a
 
   // Start listening to our image for rendering updates
   mRenderingObserver = new SVGRootRenderingObserver(mSVGDocumentWrapper, this);
 
   // Tell *our* observers that we're done loading
   nsCOMPtr<imgIDecoderObserver> observer = do_QueryReferent(mObserver);
   if (observer) {
     // NOTE: This signals that width/height are available.
-    observer->OnStartContainer();
+    observer->OnStartContainer(nullptr, this);
 
-    observer->FrameChanged(&nsIntRect::GetMaxSizedIntRect());
-    observer->OnStopFrame();
-    observer->OnStopDecode(NS_OK);
+    observer->FrameChanged(nullptr, this, &nsIntRect::GetMaxSizedIntRect());
+    observer->OnStopFrame(nullptr, 0);
+    observer->OnStopDecode(nullptr, NS_OK, nullptr);
   }
   EvaluateAnimation();
 
   return rv;
 }
 
 //------------------------------------------------------------------------------
 // nsIStreamListener method
@@ -717,19 +717,19 @@ VectorImage::OnDataAvailable(nsIRequest*
 void
 VectorImage::InvalidateObserver()
 {
   if (!mObserver)
     return;
 
   nsCOMPtr<imgIContainerObserver> containerObs(do_QueryReferent(mObserver));
   if (containerObs) {
-    containerObs->FrameChanged(&nsIntRect::GetMaxSizedIntRect());
+    containerObs->FrameChanged(nullptr, this, &nsIntRect::GetMaxSizedIntRect());
   }
 
   nsCOMPtr<imgIDecoderObserver> decoderObs(do_QueryReferent(mObserver));
   if (decoderObs) {
-    decoderObs->OnStopFrame();
+    decoderObs->OnStopFrame(nullptr, imgIContainer::FRAME_CURRENT);
   }
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/imgLoader.cpp
+++ b/image/src/imgLoader.cpp
@@ -675,17 +675,17 @@ imgCacheQueue::iterator imgCacheQueue::e
   return mQueue.end();
 }
 imgCacheQueue::const_iterator imgCacheQueue::end() const
 {
   return mQueue.end();
 }
 
 nsresult imgLoader::CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup,
-                                             imgINotificationObserver *aObserver,
+                                             imgIDecoderObserver *aObserver,
                                              nsLoadFlags aLoadFlags, imgIRequest *aProxyRequest,
                                              imgIRequest **_retval)
 {
   LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::CreateNewProxyForRequest", "imgRequest", aRequest);
 
   /* XXX If we move decoding onto separate threads, we should save off the
      calling thread here and pass it off to |proxyRequest| so that it call
      proxy calls to |aObserver|.
@@ -703,17 +703,17 @@ nsresult imgLoader::CreateNewProxyForReq
      |Init()| adds the request to the loadgroup.
    */
   proxyRequest->SetLoadFlags(aLoadFlags);
 
   nsCOMPtr<nsIURI> uri;
   aRequest->GetURI(getter_AddRefs(uri));
 
   // init adds itself to imgRequest's list of observers
-  nsresult rv = proxyRequest->Init(&aRequest->GetStatusTracker(), aLoadGroup, uri, aObserver);
+  nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, aRequest->mImage, uri, aObserver);
   if (NS_FAILED(rv)) {
     NS_RELEASE(proxyRequest);
     return rv;
   }
 
   // transfer reference to caller
   *_retval = static_cast<imgIRequest*>(proxyRequest);
 
@@ -1157,17 +1157,17 @@ void imgLoader::CheckCacheLimits(imgCach
   }
 }
 
 bool imgLoader::ValidateRequestWithNewChannel(imgRequest *request,
                                                 nsIURI *aURI,
                                                 nsIURI *aInitialDocumentURI,
                                                 nsIURI *aReferrerURI,
                                                 nsILoadGroup *aLoadGroup,
-                                                imgINotificationObserver *aObserver,
+                                                imgIDecoderObserver *aObserver,
                                                 nsISupports *aCX,
                                                 nsLoadFlags aLoadFlags,
                                                 imgIRequest *aExistingRequest,
                                                 imgIRequest **aProxyRequest,
                                                 nsIChannelPolicy *aPolicy,
                                                 nsIPrincipal* aLoadingPrincipal,
                                                 int32_t aCORSMode)
 {
@@ -1279,17 +1279,17 @@ bool imgLoader::ValidateRequestWithNewCh
   }
 }
 
 bool imgLoader::ValidateEntry(imgCacheEntry *aEntry,
                                 nsIURI *aURI,
                                 nsIURI *aInitialDocumentURI,
                                 nsIURI *aReferrerURI,
                                 nsILoadGroup *aLoadGroup,
-                                imgINotificationObserver *aObserver,
+                                imgIDecoderObserver *aObserver,
                                 nsISupports *aCX,
                                 nsLoadFlags aLoadFlags,
                                 bool aCanMakeNewChannel,
                                 imgIRequest *aExistingRequest,
                                 imgIRequest **aProxyRequest,
                                 nsIChannelPolicy *aPolicy,
                                 nsIPrincipal* aLoadingPrincipal,
                                 int32_t aCORSMode)
@@ -1516,24 +1516,24 @@ nsresult imgLoader::EvictEntries(imgCach
 #define LOAD_FLAGS_CACHE_MASK    (nsIRequest::LOAD_BYPASS_CACHE | \
                                   nsIRequest::LOAD_FROM_CACHE)
 
 #define LOAD_FLAGS_VALIDATE_MASK (nsIRequest::VALIDATE_ALWAYS |   \
                                   nsIRequest::VALIDATE_NEVER |    \
                                   nsIRequest::VALIDATE_ONCE_PER_SESSION)
 
 
-/* imgIRequest loadImage (in nsIURI aURI, in nsIURI initialDocumentURI, in nsIPrincipal loadingPrincipal, in nsILoadGroup aLoadGroup, in imgINotificationObserver aObserver, in nsISupports aCX, in nsLoadFlags aLoadFlags, in nsISupports cacheKey, in imgIRequest aRequest); */
+/* imgIRequest loadImage (in nsIURI aURI, in nsIURI initialDocumentURI, in nsIPrincipal loadingPrincipal, in nsILoadGroup aLoadGroup, in imgIDecoderObserver aObserver, in nsISupports aCX, in nsLoadFlags aLoadFlags, in nsISupports cacheKey, in imgIRequest aRequest); */
 
 NS_IMETHODIMP imgLoader::LoadImage(nsIURI *aURI, 
                                    nsIURI *aInitialDocumentURI,
                                    nsIURI *aReferrerURI,
                                    nsIPrincipal* aLoadingPrincipal,
                                    nsILoadGroup *aLoadGroup,
-                                   imgINotificationObserver *aObserver,
+                                   imgIDecoderObserver *aObserver,
                                    nsISupports *aCX,
                                    nsLoadFlags aLoadFlags,
                                    nsISupports *aCacheKey,
                                    imgIRequest *aRequest,
                                    nsIChannelPolicy *aPolicy,
                                    imgIRequest **_retval)
 {
   VerifyCacheSizes();
@@ -1779,18 +1779,18 @@ NS_IMETHODIMP imgLoader::LoadImage(nsIUR
     return rv;
   }
 
   NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value");
 
   return NS_OK;
 }
 
-/* imgIRequest loadImageWithChannel(in nsIChannel channel, in imgINotificationObserver aObserver, in nsISupports cx, out nsIStreamListener); */
-NS_IMETHODIMP imgLoader::LoadImageWithChannel(nsIChannel *channel, imgINotificationObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgIRequest **_retval)
+/* imgIRequest loadImageWithChannel(in nsIChannel channel, in imgIDecoderObserver aObserver, in nsISupports cx, out nsIStreamListener); */
+NS_IMETHODIMP imgLoader::LoadImageWithChannel(nsIChannel *channel, imgIDecoderObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgIRequest **_retval)
 {
   NS_ASSERTION(channel, "imgLoader::LoadImageWithChannel -- NULL channel pointer");
 
   MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy);
 
   nsRefPtr<imgRequest> request;
 
   nsCOMPtr<nsIURI> uri;
--- a/image/src/imgLoader.h
+++ b/image/src/imgLoader.h
@@ -25,17 +25,17 @@
 #ifdef LOADER_THREADSAFE
 #include "prlock.h"
 #endif
 
 class imgLoader;
 class imgRequest;
 class imgRequestProxy;
 class imgIRequest;
-class imgINotificationObserver;
+class imgIDecoderObserver;
 class nsILoadGroup;
 class imgCacheExpirationTracker;
 class imgMemoryReporter;
 
 class imgCacheEntry
 {
 public:
   imgCacheEntry(imgLoader* loader, imgRequest *request, bool aForcePrincipalCheck);
@@ -284,37 +284,37 @@ public:
   bool SetHasProxies(nsIURI *key);
 
 private: // methods
 
 
   bool ValidateEntry(imgCacheEntry *aEntry, nsIURI *aKey,
                        nsIURI *aInitialDocumentURI, nsIURI *aReferrerURI, 
                        nsILoadGroup *aLoadGroup,
-                       imgINotificationObserver *aObserver, nsISupports *aCX,
+                       imgIDecoderObserver *aObserver, nsISupports *aCX,
                        nsLoadFlags aLoadFlags, bool aCanMakeNewChannel,
                        imgIRequest *aExistingRequest,
                        imgIRequest **aProxyRequest,
                        nsIChannelPolicy *aPolicy,
                        nsIPrincipal* aLoadingPrincipal,
                        int32_t aCORSMode);
   bool ValidateRequestWithNewChannel(imgRequest *request, nsIURI *aURI,
                                        nsIURI *aInitialDocumentURI,
                                        nsIURI *aReferrerURI,
                                        nsILoadGroup *aLoadGroup,
-                                       imgINotificationObserver *aObserver,
+                                       imgIDecoderObserver *aObserver,
                                        nsISupports *aCX, nsLoadFlags aLoadFlags,
                                        imgIRequest *aExistingRequest,
                                        imgIRequest **aProxyRequest,
                                        nsIChannelPolicy *aPolicy,
                                        nsIPrincipal* aLoadingPrincipal,
                                        int32_t aCORSMode);
 
   nsresult CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup,
-                                    imgINotificationObserver *aObserver,
+                                    imgIDecoderObserver *aObserver,
                                     nsLoadFlags aLoadFlags, imgIRequest *aRequestProxy,
                                     imgIRequest **_retval);
 
   void ReadAcceptHeaderPref();
 
 
   typedef nsRefPtrHashtable<nsCStringHashKey, imgCacheEntry> imgCacheTable;
 
--- a/image/src/imgRequest.cpp
+++ b/image/src/imgRequest.cpp
@@ -66,33 +66,35 @@ InitPrefCaches()
   Preferences::AddBoolVarCache(&gDecodeOnDraw, "image.mem.decodeondraw");
   gInitializedPrefCaches = true;
 }
 
 #if defined(PR_LOGGING)
 PRLogModuleInfo *gImgLog = PR_NewLogModule("imgRequest");
 #endif
 
-NS_IMPL_ISUPPORTS5(imgRequest,
+NS_IMPL_ISUPPORTS8(imgRequest,
+                   imgIDecoderObserver, imgIContainerObserver,
                    nsIStreamListener, nsIRequestObserver,
+                   nsISupportsWeakReference,
                    nsIChannelEventSink,
                    nsIInterfaceRequestor,
                    nsIAsyncVerifyRedirectCallback)
 
 imgRequest::imgRequest(imgLoader* aLoader)
  : mLoader(aLoader)
- , mStatusTracker(new imgStatusTracker(nullptr, this))
  , mValidator(nullptr)
  , mImageSniffers("image-sniffing-services")
  , mInnerWindowId(0)
  , mCORSMode(imgIRequest::CORS_NONE)
  , mDecodeRequested(false)
  , mIsMultiPartChannel(false)
  , mGotData(false)
  , mIsInCache(false)
+ , mBlockingOnload(false)
  , mResniffMimeType(false)
 {
   // Register our pref observers if we haven't yet.
   if (NS_UNLIKELY(!gInitializedPrefCaches)) {
     InitPrefCaches();
   }
 }
 
@@ -120,16 +122,18 @@ nsresult imgRequest::Init(nsIURI *aURI,
   NS_ABORT_IF_FALSE(!mImage, "Multiple calls to init");
   NS_ABORT_IF_FALSE(aURI, "No uri");
   NS_ABORT_IF_FALSE(aCurrentURI, "No current uri");
   NS_ABORT_IF_FALSE(aRequest, "No request");
   NS_ABORT_IF_FALSE(aChannel, "No channel");
 
   mProperties = do_CreateInstance("@mozilla.org/properties;1");
 
+  mStatusTracker = new imgStatusTracker(nullptr);
+
   mURI = aURI;
   mCurrentURI = aCurrentURI;
   mRequest = aRequest;
   mChannel = aChannel;
   mTimedChannel = do_QueryInterface(mChannel);
 
   mLoadingPrincipal = aLoadingPrincipal;
   mCORSMode = aCORSMode;
@@ -167,56 +171,63 @@ void imgRequest::SetCacheEntry(imgCacheE
   mCacheEntry = entry;
 }
 
 bool imgRequest::HasCacheEntry() const
 {
   return mCacheEntry != nullptr;
 }
 
-void imgRequest::ResetCacheEntry()
-{
-  if (HasCacheEntry()) {
-    mCacheEntry->SetDataSize(0);
-  }
-}
-
 void imgRequest::AddProxy(imgRequestProxy *proxy)
 {
   NS_PRECONDITION(proxy, "null imgRequestProxy passed in");
   LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::AddProxy", "proxy", proxy);
 
   // If we're empty before adding, we have to tell the loader we now have
   // proxies.
-  if (GetStatusTracker().ConsumerCount() == 0) {
+  if (mObservers.IsEmpty()) {
     NS_ABORT_IF_FALSE(mURI, "Trying to SetHasProxies without key uri.");
     mLoader->SetHasProxies(mURI);
   }
 
-  GetStatusTracker().AddConsumer(proxy);
+  // If we don't have any current observers, we should restart any animation.
+  if (mImage && !HaveProxyWithObserver(proxy) && proxy->HasObserver()) {
+    LOG_MSG(gImgLog, "imgRequest::AddProxy", "resetting animation");
+
+    mImage->ResetAnimation();
+  }
+
+  proxy->SetPrincipal(mPrincipal);
+
+  mObservers.AppendElementUnlessExists(proxy);
 }
 
 nsresult imgRequest::RemoveProxy(imgRequestProxy *proxy, nsresult aStatus)
 {
   LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy", "proxy", proxy);
 
   // This will remove our animation consumers, so after removing
   // this proxy, we don't end up without proxies with observers, but still
   // have animation consumers.
   proxy->ClearAnimationConsumers();
 
+  if (!mObservers.RemoveElement(proxy)) {
+    // Not one of our proxies; we're done
+    return NS_OK;
+  }
+
   // Let the status tracker do its thing before we potentially call Cancel()
   // below, because Cancel() may result in OnStopRequest being called back
   // before Cancel() returns, leaving the image in a different state then the
   // one it was in at this point.
+
   imgStatusTracker& statusTracker = GetStatusTracker();
-  if (!statusTracker.RemoveConsumer(proxy, aStatus, !aNotify))
-    return NS_OK;
+  statusTracker.EmulateRequestFinished(proxy, aStatus);
 
-  if (statusTracker.ConsumerCount() == 0) {
+  if (mObservers.IsEmpty()) {
     // If we have no observers, there's nothing holding us alive. If we haven't
     // been cancelled and thus removed from the cache, tell the image loader so
     // we can be evicted from the cache.
     if (mCacheEntry) {
       NS_ABORT_IF_FALSE(mURI, "Removing last observer without key uri.");
 
       mLoader->SetHasNoProxies(mURI, mCacheEntry);
     } 
@@ -226,17 +237,17 @@ nsresult imgRequest::RemoveProxy(imgRequ
       mURI->GetSpec(spec);
       LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy no cache entry", "uri", spec.get());
     }
 #endif
 
     /* If |aStatus| is a failure code, then cancel the load if it is still in progress.
        Otherwise, let the load continue, keeping 'this' in the cache with no observers.
        This way, if a proxy is destroyed without calling cancel on it, it won't leak
-       and won't leave a bad pointer in the observer list.
+       and won't leave a bad pointer in mObservers.
      */
     if (statusTracker.IsLoading() && NS_FAILED(aStatus)) {
       LOG_MSG(gImgLog, "imgRequest::RemoveProxy", "load in progress.  canceling");
 
       this->Cancel(NS_BINDING_ABORTED);
     }
 
     /* break the cycle from the cache entry. */
@@ -269,17 +280,26 @@ void imgRequest::CancelAndAbort(nsresult
 void imgRequest::Cancel(nsresult aStatus)
 {
   /* The Cancel() method here should only be called by this class. */
 
   LOG_SCOPE(gImgLog, "imgRequest::Cancel");
 
   imgStatusTracker& statusTracker = GetStatusTracker();
 
-  statusTracker.MaybeUnblockOnload();
+  if (mBlockingOnload) {
+    mBlockingOnload = false;
+
+    statusTracker.RecordUnblockOnload();
+
+    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+    while (iter.HasMore()) {
+      statusTracker.SendUnblockOnload(iter.GetNext());
+    }
+  }
 
   statusTracker.RecordCancel();
 
   RemoveFromCache();
 
   if (mRequest && statusTracker.IsLoading())
     mRequest->Cancel(aStatus);
 }
@@ -317,16 +337,34 @@ void imgRequest::RemoveFromCache()
       mLoader->RemoveFromCache(mCacheEntry);
     else
       mLoader->RemoveFromCache(mURI);
   }
 
   mCacheEntry = nullptr;
 }
 
+bool imgRequest::HaveProxyWithObserver(imgRequestProxy* aProxyToIgnore) const
+{
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  imgRequestProxy* proxy;
+  while (iter.HasMore()) {
+    proxy = iter.GetNext();
+    if (proxy == aProxyToIgnore) {
+      continue;
+    }
+    
+    if (proxy->HasObserver()) {
+      return true;
+    }
+  }
+  
+  return false;
+}
+
 int32_t imgRequest::Priority() const
 {
   int32_t priority = nsISupportsPriority::PRIORITY_NORMAL;
   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest);
   if (p)
     p->GetPriority(&priority);
   return priority;
 }
@@ -335,17 +373,17 @@ void imgRequest::AdjustPriority(imgReque
 {
   // only the first proxy is allowed to modify the priority of this image load.
   //
   // XXX(darin): this is probably not the most optimal algorithm as we may want
   // to increase the priority of requests that have a lot of proxies.  the key
   // concern though is that image loads remain lower priority than other pieces
   // of content such as link clicks, CSS, and JS.
   //
-  if (!GetStatusTracker().FirstConsumerIs(proxy))
+  if (mObservers.SafeElementAt(0, nullptr) != proxy)
     return;
 
   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest);
   if (p)
     p->AdjustPriority(delta);
 }
 
 void imgRequest::SetIsInCache(bool incache)
@@ -467,16 +505,299 @@ imgRequest::StartDecoding()
   }
 
   // Otherwise, flag to do it when we get the image
   mDecodeRequested = true;
 
   return NS_OK;
 }
 
+
+
+/** imgIContainerObserver methods **/
+
+/* [noscript] void frameChanged (in imgIRequest request,
+                                 in imgIContainer container,
+                                 in nsIntRect dirtyRect); */
+NS_IMETHODIMP imgRequest::FrameChanged(imgIRequest *request,
+                                       imgIContainer *container,
+                                       const nsIntRect *dirtyRect)
+{
+  LOG_SCOPE(gImgLog, "imgRequest::FrameChanged");
+  NS_ABORT_IF_FALSE(mImage,
+                    "FrameChanged callback before we've created our image");
+
+  mImage->GetStatusTracker().RecordFrameChanged(container, dirtyRect);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  while (iter.HasMore()) {
+    mImage->GetStatusTracker().SendFrameChanged(iter.GetNext(), container, dirtyRect);
+  }
+
+  return NS_OK;
+}
+
+/** imgIDecoderObserver methods **/
+
+/* void onStartDecode (in imgIRequest request); */
+NS_IMETHODIMP imgRequest::OnStartDecode(imgIRequest *request)
+{
+  LOG_SCOPE(gImgLog, "imgRequest::OnStartDecode");
+  NS_ABORT_IF_FALSE(mImage,
+                    "OnStartDecode callback before we've created our image");
+
+
+  imgStatusTracker& tracker = mImage->GetStatusTracker();
+  tracker.RecordStartDecode();
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  while (iter.HasMore()) {
+    tracker.SendStartDecode(iter.GetNext());
+  }
+
+  if (!mIsMultiPartChannel) {
+    MOZ_ASSERT(!mBlockingOnload);
+    mBlockingOnload = true;
+
+    tracker.RecordBlockOnload();
+
+    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+    while (iter.HasMore()) {
+      tracker.SendBlockOnload(iter.GetNext());
+    }
+  }
+
+  /* In the case of streaming jpegs, it is possible to get multiple OnStartDecodes which
+     indicates the beginning of a new decode.
+     The cache entry's size therefore needs to be reset to 0 here.  If we do not do this,
+     the code in imgRequest::OnStopFrame will continue to increase the data size cumulatively.
+   */
+  if (mCacheEntry)
+    mCacheEntry->SetDataSize(0);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP imgRequest::OnStartRequest(imgIRequest *aRequest)
+{
+  NS_NOTREACHED("imgRequest(imgIDecoderObserver)::OnStartRequest");
+  return NS_OK;
+}
+
+/* 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;
+
+  NS_ABORT_IF_FALSE(mImage,
+                    "OnStartContainer callback before we've created our image");
+  NS_ABORT_IF_FALSE(image == mImage,
+                    "OnStartContainer callback from an image we don't own");
+  mImage->GetStatusTracker().RecordStartContainer(image);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  while (iter.HasMore()) {
+    mImage->GetStatusTracker().SendStartContainer(iter.GetNext(), image);
+  }
+
+  return NS_OK;
+}
+
+/* void onStartFrame (in imgIRequest request, in unsigned long frame); */
+NS_IMETHODIMP imgRequest::OnStartFrame(imgIRequest *request,
+                                       uint32_t frame)
+{
+  LOG_SCOPE(gImgLog, "imgRequest::OnStartFrame");
+  NS_ABORT_IF_FALSE(mImage,
+                    "OnStartFrame callback before we've created our image");
+
+  mImage->GetStatusTracker().RecordStartFrame(frame);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  while (iter.HasMore()) {
+    mImage->GetStatusTracker().SendStartFrame(iter.GetNext(), frame);
+  }
+
+  return NS_OK;
+}
+
+/* [noscript] void onDataAvailable (in imgIRequest request, in boolean aCurrentFrame, [const] in nsIntRect rect); */
+NS_IMETHODIMP imgRequest::OnDataAvailable(imgIRequest *request,
+                                          bool aCurrentFrame,
+                                          const nsIntRect * rect)
+{
+  LOG_SCOPE(gImgLog, "imgRequest::OnDataAvailable");
+  NS_ABORT_IF_FALSE(mImage,
+                    "OnDataAvailable callback before we've created our image");
+
+  mImage->GetStatusTracker().RecordDataAvailable(aCurrentFrame, rect);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  while (iter.HasMore()) {
+    mImage->GetStatusTracker().SendDataAvailable(iter.GetNext(), aCurrentFrame, rect);
+  }
+
+  return NS_OK;
+}
+
+/* void onStopFrame (in imgIRequest request, in unsigned long frame); */
+NS_IMETHODIMP imgRequest::OnStopFrame(imgIRequest *request,
+                                      uint32_t frame)
+{
+  LOG_SCOPE(gImgLog, "imgRequest::OnStopFrame");
+  NS_ABORT_IF_FALSE(mImage,
+                    "OnStopFrame callback before we've created our image");
+
+  imgStatusTracker& tracker = mImage->GetStatusTracker();
+  tracker.RecordStopFrame(frame);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  while (iter.HasMore()) {
+    tracker.SendStopFrame(iter.GetNext(), frame);
+  }
+
+  if (mBlockingOnload) {
+    mBlockingOnload = false;
+
+    tracker.RecordUnblockOnload();
+
+    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+    while (iter.HasMore()) {
+      tracker.SendUnblockOnload(iter.GetNext());
+    }
+  }
+
+  return NS_OK;
+}
+
+/* void onStopContainer (in imgIRequest request, in imgIContainer image); */
+NS_IMETHODIMP imgRequest::OnStopContainer(imgIRequest *request,
+                                          imgIContainer *image)
+{
+  LOG_SCOPE(gImgLog, "imgRequest::OnStopContainer");
+  NS_ABORT_IF_FALSE(mImage,
+                    "OnDataContainer callback before we've created our image");
+
+  imgStatusTracker& tracker = mImage->GetStatusTracker();
+  tracker.RecordStopContainer(image);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  while (iter.HasMore()) {
+    tracker.SendStopContainer(iter.GetNext(), image);
+  }
+
+  // This is really hacky. We need to handle the case where we start decoding,
+  // block onload, but then hit an error before we get to our first frame. In
+  // theory we would just hook in at OnStopDecode, but OnStopDecode is broken
+  // until we fix bug 505385. OnStopContainer is actually going away at that
+  // point. So for now we take advantage of the fact that OnStopContainer is
+  // always fired in the decoders at the same time as OnStopDecode.
+  if (mBlockingOnload) {
+    mBlockingOnload = false;
+
+    tracker.RecordUnblockOnload();
+
+    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+    while (iter.HasMore()) {
+      tracker.SendUnblockOnload(iter.GetNext());
+    }
+  }
+
+  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_ABORT_IF_FALSE(mImage,
+                    "OnDataDecode callback before we've created our image");
+
+  // We finished the decode, and thus have the decoded frames. Update the cache
+  // entry size to take this into account.
+  UpdateCacheEntrySize();
+
+  mImage->GetStatusTracker().RecordStopDecode(aStatus, aStatusArg);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  while (iter.HasMore()) {
+    mImage->GetStatusTracker().SendStopDecode(iter.GetNext(), aStatus,
+                                              aStatusArg);
+  }
+
+  if (NS_FAILED(aStatus)) {
+    // Some kind of problem has happened with image decoding.
+    // Report the URI to net:failed-to-process-uri-conent observers.
+
+    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+    if (os)
+      os->NotifyObservers(mURI, "net:failed-to-process-uri-content", nullptr);
+  }
+
+  // RasterImage 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_).
+  // Within imgStatusTracker, we ignore OnStopDecode notifications from the
+  // decoder and RasterImage and generate our own every time we send
+  // OnStopRequest. From within SendStopDecode, we actually send
+  // OnStopContainer.  For more information, see bug 435296.
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP imgRequest::OnStopRequest(imgIRequest *aRequest,
+                                        bool aLastPart)
+{
+  NS_NOTREACHED("imgRequest(imgIDecoderObserver)::OnStopRequest");
+  return NS_OK;
+}
+
+/* void onDiscard (in imgIRequest request); */
+NS_IMETHODIMP imgRequest::OnDiscard(imgIRequest *aRequest)
+{
+  NS_ABORT_IF_FALSE(mImage,
+                    "OnDiscard callback before we've created our image");
+
+  mImage->GetStatusTracker().RecordDiscard();
+
+  // Update the cache entry size, since we just got rid of frame data
+  UpdateCacheEntrySize();
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  while (iter.HasMore()) {
+    mImage->GetStatusTracker().SendDiscard(iter.GetNext());
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP imgRequest::OnImageIsAnimated(imgIRequest *aRequest)
+{
+  NS_ABORT_IF_FALSE(mImage,
+                    "OnImageIsAnimated callback before we've created our image");
+  mImage->GetStatusTracker().RecordImageIsAnimated();
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  while (iter.HasMore()) {
+    mImage->GetStatusTracker().SendImageIsAnimated(iter.GetNext());
+  }
+
+  return NS_OK;
+}
+
 /** nsIRequestObserver methods **/
 
 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
 NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
 {
   LOG_SCOPE(gImgLog, "imgRequest::OnStartRequest");
 
   // Figure out if we're multipart
@@ -512,40 +833,52 @@ NS_IMETHODIMP imgRequest::OnStartRequest
   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;
   }
 
-  GetStatusTracker().OnStartRequest();
+  imgStatusTracker& statusTracker = GetStatusTracker();
+  statusTracker.RecordStartRequest();
 
   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
   if (channel)
     channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
 
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  while (iter.HasMore()) {
+    statusTracker.SendStartRequest(iter.GetNext());
+  }
+
   /* Get our principal */
   nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
   if (chan) {
     nsCOMPtr<nsIScriptSecurityManager> secMan =
       do_GetService("@mozilla.org/scriptsecuritymanager;1");
     if (secMan) {
       nsresult rv = secMan->GetChannelPrincipal(chan,
                                                 getter_AddRefs(mPrincipal));
       if (NS_FAILED(rv)) {
         return rv;
       }
+
+      // Tell all of our proxies that we have a principal.
+      nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+      while (iter.HasMore()) {
+        iter.GetNext()->SetPrincipal(mPrincipal);
+      }
     }
   }
 
   SetCacheValidation(mCacheEntry, aRequest);
 
   // Shouldn't we be dead already if this gets hit?  Probably multipart/x-mixed-replace...
-  if (GetStatusTracker().ConsumerCount() == 0) {
+  if (mObservers.IsEmpty()) {
     this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
   }
 
   return NS_OK;
 }
 
 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
 NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
@@ -605,17 +938,21 @@ NS_IMETHODIMP imgRequest::OnStopRequest(
     // loading compressed source data, which is part of our size calculus.
     UpdateCacheEntrySize();
   }
   else {
     // stops animations, removes from cache
     this->Cancel(status);
   }
 
-  GetStatusTracker().OnStopRequest(lastPart, status);
+  /* notify the kids */
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator srIter(mObservers);
+  while (srIter.HasMore()) {
+    statusTracker.SendStopRequest(srIter.GetNext(), lastPart, status);
+  }
 
   mTimedChannel = nullptr;
   return NS_OK;
 }
 
 struct mimetype_closure
 {
   imgRequest* request;
@@ -691,32 +1028,34 @@ imgRequest::OnDataAvailable(nsIRequest *
     if (mContentType != newType || newType.EqualsLiteral(SVG_MIMETYPE)) {
       mContentType = newType;
 
       // If we've resniffed our MIME type and it changed, we need to create a
       // new status tracker to give to the image, because we don't have one of
       // our own any more.
       if (mResniffMimeType) {
         NS_ABORT_IF_FALSE(mIsMultiPartChannel, "Resniffing a non-multipart image");
-        imgStatusTracker* freshTracker = new imgStatusTracker(nullptr, this);
-        freshTracker->AdoptConsumers(&GetStatusTracker());
-        mStatusTracker = freshTracker;
+        mStatusTracker = new imgStatusTracker(nullptr);
       }
 
       mResniffMimeType = false;
 
       /* now we have mimetype, so we can infer the image type that we want */
       if (mContentType.EqualsLiteral(SVG_MIMETYPE)) {
         mImage = new VectorImage(mStatusTracker.forget());
       } else {
         mImage = new RasterImage(mStatusTracker.forget());
       }
       mImage->SetInnerWindowID(mInnerWindowId);
 
-      GetStatusTracker().OnDataAvailable();
+      // Notify any imgRequestProxys that are observing us that we have an Image.
+      nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+      while (iter.HasMore()) {
+        iter.GetNext()->SetImage(mImage);
+      }
 
       /* set our mimetype as a property */
       nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1"));
       if (contentType) {
         contentType->SetData(mContentType);
         mProperties->Set("type", contentType);
       }
 
@@ -775,18 +1114,17 @@ imgRequest::OnDataAvailable(nsIRequest *
       nsAutoCString uriString;
       rv = mURI->GetSpec(uriString);
       if (NS_FAILED(rv))
         uriString.Assign("<unknown image URI>");
 
       // Initialize the image that we created above. For RasterImages, 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.
-      rv = mImage->Init(GetStatusTracker().GetDecoderObserver(),
-                        mContentType.get(), uriString.get(), imageFlags);
+      rv = mImage->Init(this, mContentType.get(), uriString.get(), imageFlags);
 
       // We allow multipart images to fail to initialize without cancelling the
       // load because subsequent images might be fine.
       if (NS_FAILED(rv) && !mIsMultiPartChannel) { // Probably bad mimetype
 
         this->Cancel(rv);
         return NS_BINDING_ABORTED;
       }
--- a/image/src/imgRequest.h
+++ b/image/src/imgRequest.h
@@ -2,29 +2,33 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef imgRequest_h__
 #define imgRequest_h__
 
+#include "imgIDecoderObserver.h"
+
 #include "nsIChannelEventSink.h"
 #include "nsIContentSniffer.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIRequest.h"
 #include "nsIProperties.h"
 #include "nsIStreamListener.h"
 #include "nsIURI.h"
 #include "nsIPrincipal.h"
 #include "nsITimedChannel.h"
 
 #include "nsCategoryCache.h"
 #include "nsCOMPtr.h"
 #include "nsStringGlue.h"
+#include "nsTObserverArray.h"
+#include "nsWeakReference.h"
 #include "nsError.h"
 #include "imgIRequest.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 
 class imgCacheValidator;
 class imgStatusTracker;
 class imgLoader;
 class imgRequestProxy;
@@ -33,17 +37,19 @@ class imgMemoryReporter;
 class imgRequestNotifyRunnable;
 
 namespace mozilla {
 namespace image {
 class Image;
 } // namespace image
 } // namespace mozilla
 
-class imgRequest : public nsIStreamListener,
+class imgRequest : public imgIDecoderObserver,
+                   public nsIStreamListener,
+                   public nsSupportsWeakReference,
                    public nsIChannelEventSink,
                    public nsIInterfaceRequestor,
                    public nsIAsyncVerifyRedirectCallback
 {
 public:
   imgRequest(imgLoader* aLoader);
   virtual ~imgRequest();
 
@@ -99,84 +105,80 @@ public:
   // The principal for the document that loaded this image. Used when trying to
   // validate a CORS image load.
   already_AddRefed<nsIPrincipal> GetLoadingPrincipal() const
   {
     nsCOMPtr<nsIPrincipal> principal = mLoadingPrincipal;
     return principal.forget();
   }
 
-  // Return the imgStatusTracker associated with this imgRequest. It may live
-  // in |mStatusTracker| or in |mImage.mStatusTracker|, depending on whether
-  // mImage has been instantiated yet.
-  imgStatusTracker& GetStatusTracker();
-
-  // Get the current principal of the image. No AddRefing.
-  inline nsIPrincipal* GetPrincipal() const { return mPrincipal.get(); };
-
-  // Resize the cache entry to 0 if it exists
-  void ResetCacheEntry();
-
-  // Update the cache entry size based on the image container
-  void UpdateCacheEntrySize();
-
-  nsresult GetURI(nsIURI **aURI);
-
 private:
   friend class imgCacheEntry;
   friend class imgRequestProxy;
   friend class imgLoader;
   friend class imgCacheValidator;
   friend class imgStatusTracker;
   friend class imgCacheExpirationTracker;
   friend class imgRequestNotifyRunnable;
 
   inline void SetLoadId(void *aLoadId) {
     mLoadId = aLoadId;
   }
   void Cancel(nsresult aStatus);
   void RemoveFromCache();
 
+  nsresult GetURI(nsIURI **aURI);
   nsresult GetSecurityInfo(nsISupports **aSecurityInfo);
 
   inline const char *GetMimeType() const {
     return mContentType.get();
   }
   inline nsIProperties *Properties() {
     return mProperties;
   }
+
+  // Return the imgStatusTracker associated with this imgRequest.  It may live
+  // in |mStatusTracker| or in |mImage.mStatusTracker|, depending on whether
+  // mImage has been instantiated yet..
+  imgStatusTracker& GetStatusTracker();
     
   // Reset the cache entry after we've dropped our reference to it. Used by the
   // imgLoader when our cache entry is re-requested after we've dropped our
   // reference to it.
   void SetCacheEntry(imgCacheEntry *entry);
 
   // Returns whether we've got a reference to the cache entry.
   bool HasCacheEntry() const;
 
+  // Return true if at least one of our proxies, excluding
+  // aProxyToIgnore, has an observer.  aProxyToIgnore may be null.
+  bool HaveProxyWithObserver(imgRequestProxy* aProxyToIgnore) const;
+
   // Return the priority of the underlying network request, or return
   // PRIORITY_NORMAL if it doesn't support nsISupportsPriority.
   int32_t Priority() const;
 
   // Adjust the priority of the underlying network request by the given delta
   // on behalf of the given proxy.
   void AdjustPriority(imgRequestProxy *aProxy, int32_t aDelta);
 
   // Return whether we've seen some data at this point
   bool 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(bool cacheable);
 
-  bool IsBlockingOnload() const;
-  void SetBlockingOnload(bool block) const;
+  // Update the cache entry size based on the image container
+  void UpdateCacheEntrySize();
 
 public:
+  NS_DECL_IMGIDECODEROBSERVER
+  NS_DECL_IMGICONTAINEROBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
 
 private:
   friend class imgMemoryReporter;
@@ -196,16 +198,18 @@ private:
   // Status-tracker -- transferred to mImage, when it gets instantiated
   nsAutoPtr<imgStatusTracker> mStatusTracker;
   nsRefPtr<mozilla::image::Image> mImage;
   nsCOMPtr<nsIProperties> mProperties;
   nsCOMPtr<nsISupports> mSecurityInfo;
   nsCOMPtr<nsIChannel> mChannel;
   nsCOMPtr<nsIInterfaceRequestor> mPrevChannelSink;
 
+  nsTObserverArray<imgRequestProxy*> mObservers;
+
   nsCOMPtr<nsITimedChannel> mTimedChannel;
 
   nsCString mContentType;
 
   nsRefPtr<imgCacheEntry> mCacheEntry; /* we hold on to this to this so long as we have observers */
 
   void *mLoadId;
 
--- a/image/src/imgRequestProxy.cpp
+++ b/image/src/imgRequestProxy.cpp
@@ -35,27 +35,28 @@ NS_INTERFACE_MAP_BEGIN(imgRequestProxy)
   NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
   NS_INTERFACE_MAP_ENTRY(nsISecurityInfoProvider)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel, TimedChannel() != nullptr)
 NS_INTERFACE_MAP_END
 
 imgRequestProxy::imgRequestProxy() :
   mOwner(nullptr),
   mURI(nullptr),
+  mImage(nullptr),
+  mPrincipal(nullptr),
   mListener(nullptr),
   mLoadFlags(nsIRequest::LOAD_NORMAL),
   mLockCount(0),
   mAnimationConsumers(0),
   mCanceled(false),
   mIsInLoadGroup(false),
   mListenerIsStrongRef(false),
   mDecodeRequested(false),
   mDeferNotifications(false),
-  mSentStartContainer(false),
-  mOwnerHasImage(false)
+  mSentStartContainer(false)
 {
   /* member initializers and constructor code */
 
 }
 
 imgRequestProxy::~imgRequestProxy()
 {
   /* destructor code */
@@ -83,37 +84,36 @@ imgRequestProxy::~imgRequestProxy()
          the last observer.  This allows the image to continue to download and
          be cached even if no one is using it currently.
        */
       mOwner->RemoveProxy(this, NS_OK);
     }
   }
 }
 
-nsresult imgRequestProxy::Init(imgStatusTracker* aStatusTracker,
-                               nsILoadGroup* aLoadGroup,
-                               nsIURI* aURI, imgINotificationObserver* aObserver)
+nsresult imgRequestProxy::Init(imgRequest* request, nsILoadGroup* aLoadGroup, Image* aImage,
+                               nsIURI* aURI, imgIDecoderObserver* aObserver)
 {
   NS_PRECONDITION(!mOwner && !mListener, "imgRequestProxy is already initialized");
 
-  LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request", aStatusTracker->GetRequest());
+  LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request", request);
 
   NS_ABORT_IF_FALSE(mAnimationConsumers == 0, "Cannot have animation before Init");
 
-  mOwner = aStatusTracker->GetRequest();
-  mOwnerHasImage = !!aStatusTracker->GetImage();
+  mOwner = request;
   mListener = aObserver;
   // Make sure to addref mListener before the AddProxy call below, since
   // that call might well want to release it if the imgRequest has
   // already seen OnStopRequest.
   if (mListener) {
     mListenerIsStrongRef = true;
     NS_ADDREF(mListener);
   }
   mLoadGroup = aLoadGroup;
+  mImage = aImage;
   mURI = aURI;
 
   // Note: AddProxy won't send all the On* notifications immediately
   if (mOwner)
     mOwner->AddProxy(this);
 
   return NS_OK;
 }
@@ -127,48 +127,51 @@ nsresult imgRequestProxy::ChangeOwner(im
   uint32_t oldLockCount = mLockCount;
   while (mLockCount)
     UnlockImage();
 
   // If we're holding animation requests, undo them.
   uint32_t oldAnimationConsumers = mAnimationConsumers;
   ClearAnimationConsumers();
 
-  nsRefPtr<imgRequest> oldOwner = mOwner;
-  mOwner = aNewOwner;
-  mOwnerHasImage = !!GetStatusTracker().GetImage();
+  // Even if we are cancelled, we MUST change our image, because the image
+  // holds our status, and the status must always be correct.
+  mImage = aNewOwner->mImage;
 
   // If we were locked, apply the locks here
   for (uint32_t i = 0; i < oldLockCount; i++)
     LockImage();
 
   if (mCanceled) {
     // If we had animation requests, restore them before exiting
     // (otherwise we restore them later below)
     for (uint32_t i = 0; i < oldAnimationConsumers; i++)
       IncrementAnimationConsumers();
 
     return NS_OK;
   }
 
   // Were we decoded before?
   bool wasDecoded = false;
-  if (GetImage() &&
-      (GetStatusTracker().GetImageStatus() & imgIRequest::STATUS_FRAME_COMPLETE)) {
+  if (mImage &&
+      (mImage->GetStatusTracker().GetImageStatus() &
+       imgIRequest::STATUS_FRAME_COMPLETE)) {
     wasDecoded = true;
   }
 
-  oldOwner->RemoveProxy(this, NS_IMAGELIB_CHANGING_OWNER, false);
+  mOwner->RemoveProxy(this, NS_IMAGELIB_CHANGING_OWNER);
 
   // If we had animation requests, restore them here. Note that we
   // do this *after* RemoveProxy, which clears out animation consumers
   // (see bug 601723).
   for (uint32_t i = 0; i < oldAnimationConsumers; i++)
     IncrementAnimationConsumers();
 
+  mOwner = aNewOwner;
+
   mOwner->AddProxy(this);
 
   // If we were decoded, or if we'd previously requested a decode, request a
   // decode on the new image
   if (wasDecoded || mDecodeRequested)
     mOwner->StartDecoding();
 
   return NS_OK;
@@ -321,64 +324,65 @@ imgRequestProxy::RequestDecode()
 }
 
 
 /* void lockImage (); */
 NS_IMETHODIMP
 imgRequestProxy::LockImage()
 {
   mLockCount++;
-  if (GetImage())
-    return GetImage()->LockImage();
+  if (mImage)
+    return mImage->LockImage();
   return NS_OK;
 }
 
 /* void unlockImage (); */
 NS_IMETHODIMP
 imgRequestProxy::UnlockImage()
 {
   NS_ABORT_IF_FALSE(mLockCount > 0, "calling unlock but no locks!");
 
   mLockCount--;
-  if (GetImage())
-    return GetImage()->UnlockImage();
+  if (mImage)
+    return mImage->UnlockImage();
   return NS_OK;
 }
 
 /* void requestDiscard (); */
 NS_IMETHODIMP
 imgRequestProxy::RequestDiscard()
 {
-  if (GetImage())
-    return GetImage()->RequestDiscard();
+  if (mImage) {
+    return mImage->RequestDiscard();
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 imgRequestProxy::IncrementAnimationConsumers()
 {
   mAnimationConsumers++;
-  if (GetImage())
-    GetImage()->IncrementAnimationConsumers();
+  if (mImage)
+    mImage->IncrementAnimationConsumers();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 imgRequestProxy::DecrementAnimationConsumers()
 {
   // We may get here if some responsible code called Increment,
   // then called us, but we have meanwhile called ClearAnimationConsumers
   // because we needed to get rid of them earlier (see
   // imgRequest::RemoveProxy), and hence have nothing left to
   // decrement. (In such a case we got rid of the animation consumers
   // early, but not the observer.)
   if (mAnimationConsumers > 0) {
     mAnimationConsumers--;
-    if (GetImage())
-      GetImage()->DecrementAnimationConsumers();
+    if (mImage)
+      mImage->DecrementAnimationConsumers();
   }
   return NS_OK;
 }
 
 void
 imgRequestProxy::ClearAnimationConsumers()
 {
   while (mAnimationConsumers > 0)
@@ -425,17 +429,17 @@ NS_IMETHODIMP imgRequestProxy::SetLoadFl
 
 /* attribute imgIContainer image; */
 NS_IMETHODIMP imgRequestProxy::GetImage(imgIContainer * *aImage)
 {
   // It's possible that our owner has an image but hasn't notified us of it -
   // that'll happen if we get Canceled before the owner instantiates its image
   // (because Canceling unregisters us as a listener on mOwner). If we're
   // in that situation, just grab the image off of mOwner.
-  imgIContainer* imageToReturn = GetImage() ? GetImage() : mOwner->mImage.get();
+  imgIContainer* imageToReturn = mImage ? mImage : mOwner->mImage;
 
   if (!imageToReturn)
     return NS_ERROR_FAILURE;
 
   NS_ADDREF(*aImage = imageToReturn);
 
   return NS_OK;
 }
@@ -454,21 +458,21 @@ NS_IMETHODIMP imgRequestProxy::GetURI(ns
   if (!mURI)
     return NS_ERROR_FAILURE;
 
   NS_ADDREF(*aURI = mURI);
 
   return NS_OK;
 }
 
-/* readonly attribute imgINotificationObserver notificationObserver; */
-NS_IMETHODIMP imgRequestProxy::GetNotificationObserver(imgINotificationObserver **aObserver)
+/* readonly attribute imgIDecoderObserver decoderObserver; */
+NS_IMETHODIMP imgRequestProxy::GetDecoderObserver(imgIDecoderObserver **aDecoderObserver)
 {
-  *aObserver = mListener;
-  NS_IF_ADDREF(*aObserver);
+  *aDecoderObserver = mListener;
+  NS_IF_ADDREF(*aDecoderObserver);
   return NS_OK;
 }
 
 /* readonly attribute string mimeType; */
 NS_IMETHODIMP imgRequestProxy::GetMimeType(char **aMimeType)
 {
   if (!mOwner)
     return NS_ERROR_FAILURE;
@@ -477,76 +481,61 @@ NS_IMETHODIMP imgRequestProxy::GetMimeTy
   if (!type)
     return NS_ERROR_FAILURE;
 
   *aMimeType = NS_strdup(type);
 
   return NS_OK;
 }
 
-static imgRequestProxy* NewProxy(imgRequestProxy* /*aThis*/)
-{
-  return new imgRequestProxy();
-}
-
-imgRequestProxy* NewStaticProxy(imgRequestProxy* aThis)
-{
-  nsCOMPtr<nsIPrincipal> currentPrincipal;
-  aThis->GetImagePrincipal(getter_AddRefs(currentPrincipal));
-  return new imgRequestProxyStatic(
-      static_cast<imgRequestProxyStatic*>(aThis)->mImage, currentPrincipal);
-}
-
-NS_IMETHODIMP imgRequestProxy::Clone(imgINotificationObserver* aObserver,
+NS_IMETHODIMP imgRequestProxy::Clone(imgIDecoderObserver* aObserver,
                                      imgIRequest** aClone)
 {
-  return PerformClone(aObserver, NewProxy, aClone);
-}
-
-nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
-                                       imgRequestProxy* (aAllocFn)(imgRequestProxy*),
-                                       imgIRequest** aClone)
-{
   NS_PRECONDITION(aClone, "Null out param");
 
   LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
 
   *aClone = nullptr;
-  nsRefPtr<imgRequestProxy> clone = aAllocFn(this);
+  nsRefPtr<imgRequestProxy> clone = new imgRequestProxy();
 
   // It is important to call |SetLoadFlags()| before calling |Init()| because
   // |Init()| adds the request to the loadgroup.
   // When a request is added to a loadgroup, its load flags are merged
   // with the load flags of the loadgroup.
   // XXXldb That's not true anymore.  Stuff from imgLoader adds the
   // request to the loadgroup.
   clone->SetLoadFlags(mLoadFlags);
-  nsresult rv = clone->Init(&GetStatusTracker(), mLoadGroup, mURI, aObserver);
+  nsresult rv = clone->Init(mOwner, mLoadGroup,
+                            mImage ? mImage : mOwner->mImage,
+                            mURI, aObserver);
   if (NS_FAILED(rv))
     return rv;
 
+  clone->SetPrincipal(mPrincipal);
+
   // Assign to *aClone before calling Notify so that if the caller expects to
   // only be notified for requests it's already holding pointers to it won't be
   // surprised.
   NS_ADDREF(*aClone = clone);
 
   // This is wrong!!! We need to notify asynchronously, but there's code that
   // assumes that we don't. This will be fixed in bug 580466.
   clone->SyncNotifyListener();
 
   return NS_OK;
 }
 
 /* readonly attribute nsIPrincipal imagePrincipal; */
 NS_IMETHODIMP imgRequestProxy::GetImagePrincipal(nsIPrincipal **aPrincipal)
 {
-  if (!mOwner)
+  if (!mPrincipal)
     return NS_ERROR_FAILURE;
 
-  NS_ADDREF(*aPrincipal = mOwner->GetPrincipal());
+  NS_ADDREF(*aPrincipal = mPrincipal);
+
   return NS_OK;
 }
 
 /* readonly attribute bool multipart; */
 NS_IMETHODIMP imgRequestProxy::GetMultipart(bool *aMultipart)
 {
   if (!mOwner)
     return NS_ERROR_FAILURE;
@@ -607,113 +596,168 @@ NS_IMETHODIMP imgRequestProxy::GetHasTra
     *hasData = mOwner->HasTransferredData();
   } else {
     // The safe thing to do is to claim we have data
     *hasData = true;
   }
   return NS_OK;
 }
 
+/** imgIContainerObserver methods **/
+
+void imgRequestProxy::FrameChanged(imgIContainer *container,
+                                   const nsIntRect *dirtyRect)
+{
+  LOG_FUNC(gImgLog, "imgRequestProxy::FrameChanged");
+
+  if (mListener && !mCanceled) {
+    // Hold a ref to the listener while we call it, just in case.
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->FrameChanged(this, container, dirtyRect);
+  }
+}
+
 /** imgIDecoderObserver methods **/
 
-void imgRequestProxy::OnStartContainer()
+void imgRequestProxy::OnStartDecode()
+{
+  LOG_FUNC(gImgLog, "imgRequestProxy::OnStartDecode");
+
+  if (mListener && !mCanceled) {
+    // Hold a ref to the listener while we call it, just in case.
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->OnStartDecode(this);
+  }
+}
+
+void imgRequestProxy::OnStartContainer(imgIContainer *image)
 {
   LOG_FUNC(gImgLog, "imgRequestProxy::OnStartContainer");
 
   if (mListener && !mCanceled && !mSentStartContainer) {
     // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::SIZE_AVAILABLE, nullptr);
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->OnStartContainer(this, image);
     mSentStartContainer = true;
   }
 }
 
-void imgRequestProxy::OnFrameUpdate(const nsIntRect * rect)
+void imgRequestProxy::OnStartFrame(uint32_t frame)
+{
+  LOG_FUNC(gImgLog, "imgRequestProxy::OnStartFrame");
+
+  if (mListener && !mCanceled) {
+    // Hold a ref to the listener while we call it, just in case.
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->OnStartFrame(this, frame);
+  }
+}
+
+void imgRequestProxy::OnDataAvailable(bool aCurrentFrame, const nsIntRect * rect)
 {
   LOG_FUNC(gImgLog, "imgRequestProxy::OnDataAvailable");
 
   if (mListener && !mCanceled) {
     // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::FRAME_UPDATE, rect);
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->OnDataAvailable(this, aCurrentFrame, rect);
   }
 }
 
-void imgRequestProxy::OnStopFrame()
+void imgRequestProxy::OnStopFrame(uint32_t frame)
 {
   LOG_FUNC(gImgLog, "imgRequestProxy::OnStopFrame");
 
   if (mListener && !mCanceled) {
     // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::FRAME_COMPLETE, nullptr);
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->OnStopFrame(this, frame);
   }
 }
 
-void imgRequestProxy::OnStopDecode()
+void imgRequestProxy::OnStopContainer(imgIContainer *image)
+{
+  LOG_FUNC(gImgLog, "imgRequestProxy::OnStopContainer");
+
+  if (mListener && !mCanceled) {
+    // Hold a ref to the listener while we call it, just in case.
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->OnStopContainer(this, image);
+  }
+
+  // Multipart needs reset for next OnStartContainer
+  if (mOwner && mOwner->GetMultipart())
+    mSentStartContainer = false;
+}
+
+void imgRequestProxy::OnStopDecode(nsresult status, const PRUnichar *statusArg)
 {
   LOG_FUNC(gImgLog, "imgRequestProxy::OnStopDecode");
 
   if (mListener && !mCanceled) {
     // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::DECODE_COMPLETE, nullptr);
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->OnStopDecode(this, status, statusArg);
   }
-
-  // Multipart needs reset for next OnStartContainer
-  if (mOwner && mOwner->GetMultipart())
-    mSentStartContainer = false;
 }
 
 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<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::DISCARD, nullptr);
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->OnDiscard(this);
   }
 }
 
 void imgRequestProxy::OnImageIsAnimated()
 {
   LOG_FUNC(gImgLog, "imgRequestProxy::OnImageIsAnimated");
   if (mListener && !mCanceled) {
     // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::IS_ANIMATED, nullptr);
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->OnImageIsAnimated(this);
   }
 }
 
 void imgRequestProxy::OnStartRequest()
 {
 #ifdef PR_LOGGING
   nsAutoCString name;
   GetName(name);
   LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnStartRequest", "name", name.get());
 #endif
+
+  // Notify even if mCanceled, since OnStartRequest is guaranteed by the
+  // nsIStreamListener contract so it makes sense to do the same here.
+  if (mListener) {
+    // Hold a ref to the listener while we call it, just in case.
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->OnStartRequest(this);
+  }
 }
 
 void imgRequestProxy::OnStopRequest(bool lastPart)
 {
 #ifdef PR_LOGGING
   nsAutoCString name;
   GetName(name);
   LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnStopRequest", "name", name.get());
 #endif
   // There's all sorts of stuff here that could kill us (the OnStopRequest call
   // on the listener, the removal from the loadgroup, the release of the
   // listener, etc).  Don't let them do it.
   nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
 
   if (mListener) {
     // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
+    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
+    mListener->OnStopRequest(this, lastPart);
   }
 
   // If we're expecting more data from a multipart channel, re-add ourself
   // to the loadgroup so that the document doesn't lose track of the load.
   // If the request is already a background request and there's more data
   // coming, we can just leave the request in the loadgroup as-is.
   if (lastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
     RemoveFromLoadGroup(lastPart);
@@ -725,17 +769,17 @@ void imgRequestProxy::OnStopRequest(bool
     }
   }
 
   if (mListenerIsStrongRef) {
     NS_PRECONDITION(mListener, "How did that happen?");
     // Drop our strong ref to the listener now that we're done with
     // everything.  Note that this can cancel us and other fun things
     // like that.  Don't add anything in this method after this point.
-    imgINotificationObserver* obs = mListener;
+    imgIDecoderObserver* obs = mListener;
     mListenerIsStrongRef = false;
     NS_RELEASE(obs);
   }
 }
 
 void imgRequestProxy::BlockOnload()
 {
 #ifdef PR_LOGGING
@@ -767,150 +811,116 @@ void imgRequestProxy::UnblockOnload()
 void imgRequestProxy::NullOutListener()
 {
   // If we have animation consumers, then they don't matter anymore
   if (mListener)
     ClearAnimationConsumers();
 
   if (mListenerIsStrongRef) {
     // Releasing could do weird reentery stuff, so just play it super-safe
-    nsCOMPtr<imgINotificationObserver> obs;
+    nsCOMPtr<imgIDecoderObserver> obs;
     obs.swap(mListener);
     mListenerIsStrongRef = false;
   } else {
     mListener = nullptr;
   }
 }
 
 NS_IMETHODIMP
 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn)
 {
   *aReturn = nullptr;
-  mozilla::image::Image* image = GetImage();
 
   bool animated;
-  if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
+  if (!mImage || (NS_SUCCEEDED(mImage->GetAnimated(&animated)) && !animated)) {
     // Early exit - we're not animated, so we don't have to do anything.
     NS_ADDREF(*aReturn = this);
     return NS_OK;
   }
 
   // We are animated. We need to extract the current frame from this image.
   int32_t w = 0;
   int32_t h = 0;
-  image->GetWidth(&w);
-  image->GetHeight(&h);
+  mImage->GetWidth(&w);
+  mImage->GetHeight(&h);
   nsIntRect rect(0, 0, w, h);
   nsCOMPtr<imgIContainer> currentFrame;
-  nsresult rv = image->ExtractFrame(imgIContainer::FRAME_CURRENT, rect,
-                                    imgIContainer::FLAG_SYNC_DECODE,
-                                    getter_AddRefs(currentFrame));
+  nsresult rv = mImage->ExtractFrame(imgIContainer::FRAME_CURRENT, rect,
+                                     imgIContainer::FLAG_SYNC_DECODE,
+                                     getter_AddRefs(currentFrame));
   if (NS_FAILED(rv))
     return rv;
 
   nsRefPtr<Image> frame = static_cast<Image*>(currentFrame.get());
 
   // Create a static imgRequestProxy with our new extracted frame.
-  nsCOMPtr<nsIPrincipal> currentPrincipal;
-  GetImagePrincipal(getter_AddRefs(currentPrincipal));
-  nsRefPtr<imgRequestProxy> req = new imgRequestProxyStatic(frame, currentPrincipal);
-  req->Init(&frame->GetStatusTracker(), nullptr, mURI, nullptr);
+  nsRefPtr<imgRequestProxy> req = new imgRequestProxy();
+  req->Init(nullptr, nullptr, frame, mURI, nullptr);
+  req->SetPrincipal(mPrincipal);
 
   NS_ADDREF(*aReturn = req);
 
   return NS_OK;
 }
 
+void imgRequestProxy::SetPrincipal(nsIPrincipal *aPrincipal)
+{
+  mPrincipal = aPrincipal;
+}
+
 void imgRequestProxy::NotifyListener()
 {
   // It would be nice to notify the observer directly in the status tracker
   // instead of through the proxy, but there are several places we do extra
   // processing when we receive notifications (like OnStopRequest()), and we
   // need to check mCanceled everywhere too.
 
   if (mOwner) {
     // Send the notifications to our listener asynchronously.
     GetStatusTracker().Notify(mOwner, this);
   } else {
     // We don't have an imgRequest, so we can only notify the clone of our
     // current state, but we still have to do that asynchronously.
-    NS_ABORT_IF_FALSE(GetImage(),
+    NS_ABORT_IF_FALSE(mImage,
                       "if we have no imgRequest, we should have an Image");
-    GetStatusTracker().NotifyCurrentState(this);
+    mImage->GetStatusTracker().NotifyCurrentState(this);
   }
 }
 
 void imgRequestProxy::SyncNotifyListener()
 {
   // It would be nice to notify the observer directly in the status tracker
   // instead of through the proxy, but there are several places we do extra
   // processing when we receive notifications (like OnStopRequest()), and we
   // need to check mCanceled everywhere too.
 
   GetStatusTracker().SyncNotify(this);
 }
 
 void
-imgRequestProxy::SetHasImage()
+imgRequestProxy::SetImage(Image* aImage)
 {
-  Image* image = GetStatusTracker().GetImage();
+  NS_ABORT_IF_FALSE(aImage,  "Setting null image");
+  NS_ABORT_IF_FALSE(!mImage || mOwner->GetMultipart(),
+                    "Setting image when we already have one");
 
-  mOwnerHasImage = true;
+  mImage = aImage;
 
   // Apply any locks we have
   for (uint32_t i = 0; i < mLockCount; ++i)
-    image->LockImage();
+    mImage->LockImage();
 
   // Apply any animation consumers we have
   for (uint32_t i = 0; i < mAnimationConsumers; i++)
-    image->IncrementAnimationConsumers();
+    mImage->IncrementAnimationConsumers();
 }
 
 imgStatusTracker&
-imgRequestProxy::GetStatusTracker() const
+imgRequestProxy::GetStatusTracker()
 {
   // NOTE: It's possible that our mOwner has an Image that it didn't notify
   // us about, if we were Canceled before its Image was constructed.
   // (Canceling removes us as an observer, so mOwner has no way to notify us).
   // That's why this method uses mOwner->GetStatusTracker() instead of just
   // mOwner->mStatusTracker -- we might have a null mImage and yet have an
   // mOwner with a non-null mImage (and a null mStatusTracker pointer).
-  return mOwner->GetStatusTracker();
-}
-
-mozilla::image::Image*
-imgRequestProxy::GetImage() const
-{
-  if (!mOwnerHasImage)
-    return nullptr;
-  return GetStatusTracker().GetImage();
+  return mImage ? mImage->GetStatusTracker() : mOwner->GetStatusTracker();
 }
-
-////////////////// imgRequestProxyStatic methods
-
-NS_IMETHODIMP imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal **aPrincipal)
-{
-  if (!mPrincipal)
-    return NS_ERROR_FAILURE;
-
-  NS_ADDREF(*aPrincipal = mPrincipal);
-
-  return NS_OK;
-}
-
-mozilla::image::Image*
-imgRequestProxyStatic::GetImage() const
-{
-  return mImage;
-}
-
-imgStatusTracker&
-imgRequestProxyStatic::GetStatusTracker() const
-{
-  return mImage->GetStatusTracker();
-}
-
-NS_IMETHODIMP
-imgRequestProxyStatic::Clone(imgINotificationObserver* aObserver,
-                             imgIRequest** aClone)
-{
-  return PerformClone(aObserver, NewStaticProxy, aClone);
-}
--- a/image/src/imgRequestProxy.h
+++ b/image/src/imgRequestProxy.h
@@ -3,17 +3,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef imgRequestProxy_h__
 #define imgRequestProxy_h__
 
 #include "imgIRequest.h"
-#include "imgINotificationObserver.h"
+#include "imgIDecoderObserver.h"
 #include "nsISecurityInfoProvider.h"
 
 #include "nsIRequestObserver.h"
 #include "nsIChannel.h"
 #include "nsILoadGroup.h"
 #include "nsISupportsPriority.h"
 #include "nsITimedChannel.h"
 #include "nsCOMPtr.h"
@@ -52,30 +52,32 @@ public:
   NS_DECL_NSISECURITYINFOPROVIDER
   // nsITimedChannel declared below
 
   imgRequestProxy();
   virtual ~imgRequestProxy();
 
   // Callers to Init or ChangeOwner are required to call NotifyListener after
   // (although not immediately after) doing so.
-  nsresult Init(imgStatusTracker* aStatusTracker,
-                nsILoadGroup *aLoadGroup,
-                nsIURI* aURI, imgINotificationObserver *aObserver);
+  nsresult Init(imgRequest *request, nsILoadGroup *aLoadGroup,
+                mozilla::image::Image* aImage,
+                nsIURI* aURI, imgIDecoderObserver *aObserver);
 
   nsresult ChangeOwner(imgRequest *aNewOwner); // this will change mOwner.  Do not call this if the previous
                                                // owner has already sent notifications out!
 
   void AddToLoadGroup();
   void RemoveFromLoadGroup(bool releaseLoadGroup);
 
   inline bool HasObserver() const {
     return mListener != nullptr;
   }
 
+  void SetPrincipal(nsIPrincipal *aPrincipal);
+
   // Asynchronously notify this proxy's listener of the current state of the
   // image, and, if we have an imgRequest mOwner, any status changes that
   // happen between the time this function is called and the time the
   // notification is scheduled.
   void NotifyListener();
 
   // Synchronously notify this proxy's listener of the current state of the
   // image. Only use this function if you are currently servicing an
@@ -88,18 +90,19 @@ public:
   {
     return mDeferNotifications;
   }
   void SetNotificationsDeferred(bool aDeferNotifications)
   {
     mDeferNotifications = aDeferNotifications;
   }
 
-  // XXXbholley - This eventually gets folded into the new notification API.
-  void SetHasImage();
+  // Setter for our |mImage| pointer, for imgRequest to use, once it
+  // instantiates an Image.
+  void SetImage(mozilla::image::Image* aImage);
 
   // Removes all animation consumers that were created with
   // IncrementAnimationConsumers. This is necessary since we need
   // to do it before the proxy itself is destroyed. See
   // imgRequest::RemoveProxy
   void ClearAnimationConsumers();
 
 protected:
@@ -127,23 +130,30 @@ protected:
       nsresult mStatus;
   };
 
   // The following notification functions are protected to ensure that (friend
   // class) imgStatusTracker is the only class allowed to send us
   // notifications.
 
   /* non-virtual imgIDecoderObserver methods */
-  void OnStartContainer  ();
-  void OnFrameUpdate     (const nsIntRect * aRect);
-  void OnStopFrame       ();
-  void OnStopDecode      ();
+  void OnStartDecode     ();
+  void OnStartContainer  (imgIContainer *aContainer);
+  void OnStartFrame      (uint32_t aFrame);
+  void OnDataAvailable   (bool aCurrentFrame, const nsIntRect * aRect);
+  void OnStopFrame       (uint32_t aFrame);
+  void OnStopContainer   (imgIContainer *aContainer);
+  void OnStopDecode      (nsresult status, const PRUnichar *statusArg);
   void OnDiscard         ();
   void OnImageIsAnimated ();
 
+  /* non-virtual imgIContainerObserver methods */
+  void FrameChanged(imgIContainer *aContainer,
+                    const nsIntRect *aDirtyRect);
+
   /* non-virtual sort-of-nsIRequestObserver methods */
   void OnStartRequest();
   void OnStopRequest(bool aLastPart);
 
   /* non-virtual imgIOnloadBlocker methods */
   void BlockOnload();
   void UnblockOnload();
 
@@ -156,31 +166,25 @@ protected:
   void DoRemoveFromLoadGroup() {
     RemoveFromLoadGroup(true);
   }
 
   // Return the imgStatusTracker associated with mOwner and/or mImage. It may
   // live either on mOwner or mImage, depending on whether
   //   (a) we have an mOwner at all
   //   (b) whether mOwner has instantiated its image yet
-  virtual imgStatusTracker& GetStatusTracker() const;
+  imgStatusTracker& GetStatusTracker();
 
   nsITimedChannel* TimedChannel()
   {
     if (!mOwner)
       return nullptr;
     return mOwner->mTimedChannel;
   }
 
-  virtual mozilla::image::Image* GetImage() const;
-
-  nsresult PerformClone(imgINotificationObserver* aObserver,
-                        imgRequestProxy* (aAllocFn)(imgRequestProxy*),
-                        imgIRequest** aClone);
-
 public:
   NS_FORWARD_SAFE_NSITIMEDCHANNEL(TimedChannel())
 
 private:
   friend class imgCacheValidator;
 
   // We maintain the following invariant:
   // The proxy is registered at most with a single imgRequest as an observer,
@@ -188,20 +192,28 @@ private:
   // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
   // from whatever request it was registered with (if any). This, in turn,
   // means that imgRequest::mObservers will not have any stale pointers in it.
   nsRefPtr<imgRequest> mOwner;
 
   // The URI of our request.
   nsCOMPtr<nsIURI> mURI;
 
+  // The image we represent. Is null until data has been received, and is then
+  // set by imgRequest.
+  nsRefPtr<mozilla::image::Image> mImage;
+
+  // Our principal. Is null until data has been received from the channel, and
+  // is then set by imgRequest.
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+
   // mListener is only promised to be a weak ref (see imgILoader.idl),
   // but we actually keep a strong ref to it until we've seen our
   // first OnStopRequest.
-  imgINotificationObserver* mListener;
+  imgIDecoderObserver* mListener;
   nsCOMPtr<nsILoadGroup> mLoadGroup;
 
   nsLoadFlags mLoadFlags;
   uint32_t    mLockCount;
   uint32_t    mAnimationConsumers;
   bool mCanceled;
   bool mIsInLoadGroup;
   bool mListenerIsStrongRef;
@@ -209,49 +221,11 @@ private:
 
   // Whether we want to defer our notifications by the non-virtual Observer
   // interfaces as image loads proceed.
   bool mDeferNotifications;
 
   // We only want to send OnStartContainer once for each proxy, but we might
   // get multiple OnStartContainer calls.
   bool mSentStartContainer;
-
-  protected:
-    bool mOwnerHasImage;
-};
-
-// Used for static image proxies for which no requests are available, so
-// certain behaviours must be overridden to compensate.
-class imgRequestProxyStatic : public imgRequestProxy
-{
-
-public:
-  imgRequestProxyStatic(mozilla::image::Image* aImage,
-                        nsIPrincipal* aPrincipal)
-                       : mImage(aImage)
-                       , mPrincipal(aPrincipal)
-  {
-    mOwnerHasImage = true;
-  };
-
-  NS_IMETHOD GetImagePrincipal(nsIPrincipal** aPrincipal) MOZ_OVERRIDE;
-  virtual imgStatusTracker& GetStatusTracker() const MOZ_OVERRIDE;
-
-  NS_IMETHOD Clone(imgINotificationObserver* aObserver,
-                   imgIRequest** aClone) MOZ_OVERRIDE;
-
-protected:
-  friend imgRequestProxy* NewStaticProxy(imgRequestProxy*);
-
-  // Our image. We have to hold a strong reference here, because that's normally
-  // the job of the underlying request.
-  nsRefPtr<mozilla::image::Image> mImage;
-
-  // Our principal. We have to cache it, rather than accessing the underlying
-  // request on-demand, because static proxies don't have an underlying request.
-  nsCOMPtr<nsIPrincipal> mPrincipal;
-
-  mozilla::image::Image* GetImage() const MOZ_OVERRIDE;
-  using imgRequestProxy::GetImage;
 };
 
 #endif // imgRequestProxy_h__
--- a/image/src/imgStatusTracker.cpp
+++ b/image/src/imgStatusTracker.cpp
@@ -7,240 +7,41 @@
 #include "imgStatusTracker.h"
 
 #include "imgRequest.h"
 #include "imgIContainer.h"
 #include "imgRequestProxy.h"
 #include "Image.h"
 #include "ImageLogging.h"
 #include "RasterImage.h"
-#include "nsIObserverService.h"
-
-#include "mozilla/Util.h"
-#include "mozilla/Assertions.h"
-#include "mozilla/Services.h"
 
 using namespace mozilla::image;
 
-NS_IMPL_ISUPPORTS3(imgStatusTrackerObserver,
-                   imgIDecoderObserver,
-                   imgIContainerObserver,
-                   nsISupportsWeakReference)
-
-/** imgIContainerObserver methods **/
-
-/* [noscript] void frameChanged (in nsIntRect dirtyRect); */
-NS_IMETHODIMP imgStatusTrackerObserver::FrameChanged(const nsIntRect *dirtyRect)
-{
-  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::FrameChanged");
-  NS_ABORT_IF_FALSE(mTracker->GetImage(),
-                    "FrameChanged callback before we've created our image");
-
-  mTracker->RecordFrameChanged(dirtyRect);
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->mConsumers);
-  while (iter.HasMore()) {
-    mTracker->SendFrameChanged(iter.GetNext(), dirtyRect);
-  }
-
-  return NS_OK;
-}
-
-/** imgIDecoderObserver methods **/
-
-NS_IMETHODIMP imgStatusTrackerObserver::OnStartDecode()
+static nsresult
+GetResultFromImageStatus(uint32_t aStatus)
 {
-  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::OnStartDecode");
-  NS_ABORT_IF_FALSE(mTracker->GetImage(),
-                    "OnStartDecode callback before we've created our image");
-
-  if (!mTracker->GetRequest()->GetMultipart()) {
-    MOZ_ASSERT(!mTracker->mBlockingOnload);
-    mTracker->mBlockingOnload = true;
-
-    mTracker->RecordBlockOnload();
-
-    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->mConsumers);
-    while (iter.HasMore()) {
-      mTracker->SendBlockOnload(iter.GetNext());
-    }
-  }
-
-  /* In the case of streaming jpegs, it is possible to get multiple OnStartDecodes which
-     indicates the beginning of a new decode.
-     The cache entry's size therefore needs to be reset to 0 here.  If we do not do this,
-     the code in imgStatusTrackerObserver::OnStopFrame will continue to increase the data size cumulatively.
-  */
-  mTracker->GetRequest()->ResetCacheEntry();
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP imgStatusTrackerObserver::OnStartRequest()
-{
-  NS_NOTREACHED("imgRequest(imgIDecoderObserver)::OnStartRequest");
-  return NS_OK;
-}
-
-/* void onStartContainer (); */
-NS_IMETHODIMP imgStatusTrackerObserver::OnStartContainer()
-{
-  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::OnStartContainer");
-
-  NS_ABORT_IF_FALSE(mTracker->GetImage(),
-                    "OnStartContainer callback before we've created our image");
-  mTracker->RecordStartContainer(mTracker->GetImage());
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->mConsumers);
-  while (iter.HasMore()) {
-    mTracker->SendStartContainer(iter.GetNext());
-  }
-
-  return NS_OK;
-}
-
-/* [noscript] void onDataAvailable ([const] in nsIntRect rect); */
-NS_IMETHODIMP imgStatusTrackerObserver::OnDataAvailable(const nsIntRect * rect)
-{
-  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::OnDataAvailable");
-  NS_ABORT_IF_FALSE(mTracker->GetImage(),
-                    "OnDataAvailable callback before we've created our image");
-
-  mTracker->RecordDataAvailable();
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->mConsumers);
-  while (iter.HasMore()) {
-    mTracker->SendDataAvailable(iter.GetNext(), rect);
-  }
-
+  if (aStatus & imgIRequest::STATUS_ERROR)
+    return NS_IMAGELIB_ERROR_FAILURE;
+  if (aStatus & imgIRequest::STATUS_LOAD_COMPLETE)
+    return NS_IMAGELIB_SUCCESS_LOAD_FINISHED;
   return NS_OK;
 }
 
-/* void onStopFrame (); */
-NS_IMETHODIMP imgStatusTrackerObserver::OnStopFrame()
-{
-  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::OnStopFrame");
-  NS_ABORT_IF_FALSE(mTracker->GetImage(),
-                    "OnStopFrame callback before we've created our image");
-
-  mTracker->RecordStopFrame();
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->mConsumers);
-  while (iter.HasMore()) {
-    mTracker->SendStopFrame(iter.GetNext());
-  }
-
-  mTracker->MaybeUnblockOnload();
-
-  return NS_OK;
-}
-
-static void
-FireFailureNotification(imgRequest* aRequest)
-{
-  // Some kind of problem has happened with image decoding.
-  // Report the URI to net:failed-to-process-uri-conent observers.
-
-  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-  if (os) {
-    nsCOMPtr<nsIURI> uri;
-    aRequest->GetURI(getter_AddRefs(uri));
-    os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr);
-  }
-}
-
-/* void onStopDecode (in nsresult status); */
-NS_IMETHODIMP imgStatusTrackerObserver::OnStopDecode(nsresult aStatus)
-{
-  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::OnStopDecode");
-  NS_ABORT_IF_FALSE(mTracker->GetImage(),
-                    "OnStopDecode callback before we've created our image");
-
-  // We finished the decode, and thus have the decoded frames. Update the cache
-  // entry size to take this into account.
-  mTracker->GetRequest()->UpdateCacheEntrySize();
-
-  bool preexistingError = mTracker->GetImageStatus() == imgIRequest::STATUS_ERROR;
-
-  mTracker->RecordStopDecode(aStatus);
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->mConsumers);
-  while (iter.HasMore()) {
-    mTracker->SendStopDecode(iter.GetNext(), aStatus);
-  }
-
-  // This is really hacky. We need to handle the case where we start decoding,
-  // block onload, but then hit an error before we get to our first frame.
-  mTracker->MaybeUnblockOnload();
-
-  if (NS_FAILED(aStatus) && !preexistingError) {
-    FireFailureNotification(mTracker->GetRequest());
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP imgStatusTrackerObserver::OnStopRequest(bool aLastPart)
-{
-  NS_NOTREACHED("imgRequest(imgIDecoderObserver)::OnStopRequest");
-  return NS_OK;
-}
-
-/* void onDiscard (); */
-NS_IMETHODIMP imgStatusTrackerObserver::OnDiscard()
-{
-  NS_ABORT_IF_FALSE(mTracker->GetImage(),
-                    "OnDiscard callback before we've created our image");
-
-  mTracker->RecordDiscard();
-
-  // Update the cache entry size, since we just got rid of frame data
-  mTracker->GetRequest()->UpdateCacheEntrySize();
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->mConsumers);
-  while (iter.HasMore()) {
-    mTracker->SendDiscard(iter.GetNext());
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP imgStatusTrackerObserver::OnImageIsAnimated()
-{
-  NS_ABORT_IF_FALSE(mTracker->GetImage(),
-                    "OnImageIsAnimated callback before we've created our image");
-  mTracker->RecordImageIsAnimated();
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->mConsumers);
-  while (iter.HasMore()) {
-    mTracker->SendImageIsAnimated(iter.GetNext());
-  }
-
-  return NS_OK;
-}
-
-// imgStatusTracker methods
-
-imgStatusTracker::imgStatusTracker(Image* aImage, imgRequest* aRequest)
+imgStatusTracker::imgStatusTracker(Image* aImage)
   : mImage(aImage),
-    mRequest(aRequest),
     mState(0),
     mImageStatus(imgIRequest::STATUS_NONE),
-    mHadLastPart(false),
-    mBlockingOnload(false),
-    mTrackerObserver(new imgStatusTrackerObserver(this))
+    mHadLastPart(false)
 {}
 
 imgStatusTracker::imgStatusTracker(const imgStatusTracker& aOther)
   : mImage(aOther.mImage),
-    mRequest(aOther.mRequest),
     mState(aOther.mState),
     mImageStatus(aOther.mImageStatus),
-    mHadLastPart(aOther.mHadLastPart),
-    mBlockingOnload(aOther.mBlockingOnload)
+    mHadLastPart(aOther.mHadLastPart)
     // Note: we explicitly don't copy mRequestRunnable, because it won't be
     // nulled out when the mRequestRunnable's Run function eventually gets
     // called.
 {}
 
 void
 imgStatusTracker::SetImage(Image* aImage)
 {
@@ -388,102 +189,87 @@ imgStatusTracker::SyncNotify(imgRequestP
   nsCOMPtr<imgIRequest> kungFuDeathGrip(proxy);
 
   // OnStartRequest
   if (mState & stateRequestStarted)
     proxy->OnStartRequest();
 
   // OnStartContainer
   if (mState & stateHasSize)
-    proxy->OnStartContainer();
+    proxy->OnStartContainer(mImage);
+
+  // OnStartDecode
+  if (mState & stateDecodeStarted)
+    proxy->OnStartDecode();
 
   // BlockOnload
   if (mState & stateBlockingOnload)
     proxy->BlockOnload();
 
   if (mImage) {
     int16_t imageType = mImage->GetType();
-    // Send frame messages (OnDataAvailable, OnStopFrame)
+    // Send frame messages (OnStartFrame, OnDataAvailable, OnStopFrame)
     if (imageType == imgIContainer::TYPE_VECTOR ||
         static_cast<RasterImage*>(mImage)->GetNumFrames() > 0) {
 
+      uint32_t frame = (imageType == imgIContainer::TYPE_VECTOR) ?
+        0 : static_cast<RasterImage*>(mImage)->GetCurrentFrameIndex();
+
+      proxy->OnStartFrame(frame);
+
       // 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->OnFrameUpdate(&r);
+      proxy->OnDataAvailable(frame, &r);
 
       if (mState & stateFrameStopped)
-        proxy->OnStopFrame();
+        proxy->OnStopFrame(frame);
     }
 
     // OnImageIsAnimated
     bool isAnimated = false;
 
     nsresult rv = mImage->GetAnimated(&isAnimated);
     if (NS_SUCCEEDED(rv) && isAnimated) {
       proxy->OnImageIsAnimated();
     }
   }
 
+  // See bug 505385 and imgRequest::OnStopDecode for more information on why we
+  // call OnStopContainer based on stateDecodeStopped, and why OnStopDecode is
+  // called with OnStopRequest.
   if (mState & stateDecodeStopped) {
     NS_ABORT_IF_FALSE(mImage, "stopped decoding without ever having an image?");
-    proxy->OnStopDecode();
+    proxy->OnStopContainer(mImage);
   }
 
   if (mState & stateRequestStopped) {
+    proxy->OnStopDecode(GetResultFromImageStatus(mImageStatus), nullptr);
     proxy->OnStopRequest(mHadLastPart);
   }
 }
 
 void
 imgStatusTracker::EmulateRequestFinished(imgRequestProxy* aProxy,
                                          nsresult aStatus)
 {
   nsCOMPtr<imgIRequest> kungFuDeathGrip(aProxy);
 
-  // In certain cases the request might not have started yet.
-  // We still need to fulfill the contract.
-  if (!(mState & stateRequestStarted)) {
-    aProxy->OnStartRequest();
-  }
-
   if (mState & stateBlockingOnload) {
     aProxy->UnblockOnload();
   }
 
   if (!(mState & stateRequestStopped)) {
     aProxy->OnStopRequest(true);
   }
 }
 
 void
-imgStatusTracker::AddConsumer(imgRequestProxy* aConsumer)
-{
-  mConsumers.AppendElementUnlessExists(aConsumer);
-}
-
-// XXX - The last two arguments should go away.
-bool
-imgStatusTracker::RemoveConsumer(imgRequestProxy* aConsumer,
-                                 nsresult aStatus,
-                                 bool aOnlySendStopRequest)
-{
-  // Remove the proxy from the list.
-  bool removed = mConsumers.RemoveElement(aConsumer);
-
-  // Consumers can get confused if they don't get all the proper teardown
-  // notifications. Part ways on good terms.
-  if (removed)
-    EmulateRequestFinished(aConsumer, aStatus, aOnlySendStopRequest);
-  return removed;
-}
-
-void
 imgStatusTracker::RecordCancel()
 {
   if (!(mImageStatus & imgIRequest::STATUS_LOAD_PARTIAL))
     mImageStatus |= imgIRequest::STATUS_ERROR;
 }
 
 void
 imgStatusTracker::RecordLoaded()
@@ -493,100 +279,147 @@ imgStatusTracker::RecordLoaded()
   mImageStatus |= imgIRequest::STATUS_SIZE_AVAILABLE | imgIRequest::STATUS_LOAD_COMPLETE;
   mHadLastPart = true;
 }
 
 void
 imgStatusTracker::RecordDecoded()
 {
   NS_ABORT_IF_FALSE(mImage, "RecordDecoded called before we have an Image");
-  mState |= stateDecodeStopped | stateFrameStopped;
+  mState |= stateDecodeStarted | stateDecodeStopped | stateFrameStopped;
   mImageStatus |= imgIRequest::STATUS_FRAME_COMPLETE | imgIRequest::STATUS_DECODE_COMPLETE;
 }
 
+/* non-virtual imgIDecoderObserver methods */
+void
+imgStatusTracker::RecordStartDecode()
+{
+  NS_ABORT_IF_FALSE(mImage, "RecordStartDecode without an Image");
+  mState |= stateDecodeStarted;
+}
+
+void
+imgStatusTracker::SendStartDecode(imgRequestProxy* aProxy)
+{
+  if (!aProxy->NotificationsDeferred())
+    aProxy->OnStartDecode();
+}
+
 void
 imgStatusTracker::RecordStartContainer(imgIContainer* aContainer)
 {
   NS_ABORT_IF_FALSE(mImage,
                     "RecordStartContainer called before we have an Image");
   NS_ABORT_IF_FALSE(mImage == aContainer,
                     "RecordStartContainer called with wrong Image");
   mState |= stateHasSize;
   mImageStatus |= imgIRequest::STATUS_SIZE_AVAILABLE;
 }
 
 void
-imgStatusTracker::SendStartContainer(imgRequestProxy* aProxy)
+imgStatusTracker::SendStartContainer(imgRequestProxy* aProxy, imgIContainer* aContainer)
 {
   if (!aProxy->NotificationsDeferred())
-    aProxy->OnStartContainer();
+    aProxy->OnStartContainer(aContainer);
 }
 
 void
-imgStatusTracker::RecordDataAvailable()
+imgStatusTracker::RecordStartFrame(uint32_t aFrame)
+{
+  NS_ABORT_IF_FALSE(mImage, "RecordStartFrame called before we have an Image");
+  // no bookkeeping necessary here - this is implied by imgIContainer's number
+  // of frames
+}
+
+void
+imgStatusTracker::SendStartFrame(imgRequestProxy* aProxy, uint32_t aFrame)
+{
+  if (!aProxy->NotificationsDeferred())
+    aProxy->OnStartFrame(aFrame);
+}
+
+void
+imgStatusTracker::RecordDataAvailable(bool aCurrentFrame, const nsIntRect* aRect)
 {
   NS_ABORT_IF_FALSE(mImage,
                     "RecordDataAvailable called before we have an Image");
   // no bookkeeping necessary here - this is implied by imgIContainer's
   // number of frames and frame rect
 }
 
 void
-imgStatusTracker::SendDataAvailable(imgRequestProxy* aProxy,
-                                    const nsIntRect* aRect)
+imgStatusTracker::SendDataAvailable(imgRequestProxy* aProxy, bool aCurrentFrame,
+                                         const nsIntRect* aRect)
 {
   if (!aProxy->NotificationsDeferred())
-    aProxy->OnFrameUpdate(aRect);
+    aProxy->OnDataAvailable(aCurrentFrame, aRect);
 }
 
 
 void
-imgStatusTracker::RecordStopFrame()
+imgStatusTracker::RecordStopFrame(uint32_t aFrame)
 {
   NS_ABORT_IF_FALSE(mImage, "RecordStopFrame called before we have an Image");
   mState |= stateFrameStopped;
   mImageStatus |= imgIRequest::STATUS_FRAME_COMPLETE;
 }
 
 void
-imgStatusTracker::SendStopFrame(imgRequestProxy* aProxy)
+imgStatusTracker::SendStopFrame(imgRequestProxy* aProxy, uint32_t aFrame)
 {
   if (!aProxy->NotificationsDeferred())
-    aProxy->OnStopFrame();
+    aProxy->OnStopFrame(aFrame);
 }
 
 void
-imgStatusTracker::RecordStopDecode(nsresult aStatus)
+imgStatusTracker::RecordStopContainer(imgIContainer* aContainer)
+{
+  NS_ABORT_IF_FALSE(mImage,
+                    "RecordStopContainer called before we have an Image");
+  // No-op: see imgRequest::OnStopDecode for more information
+}
+
+void
+imgStatusTracker::SendStopContainer(imgRequestProxy* aProxy, imgIContainer* aContainer)
+{
+  // No-op: see imgRequest::OnStopDecode for more information
+}
+
+void
+imgStatusTracker::RecordStopDecode(nsresult aStatus, const PRUnichar* statusArg)
 {
   NS_ABORT_IF_FALSE(mImage,
                     "RecordStopDecode called before we have an Image");
   mState |= stateDecodeStopped;
 
-  if (NS_SUCCEEDED(aStatus) && mImageStatus != imgIRequest::STATUS_ERROR)
+  if (NS_SUCCEEDED(aStatus))
     mImageStatus |= imgIRequest::STATUS_DECODE_COMPLETE;
   // If we weren't successful, clear all success status bits and set error.
   else
     mImageStatus = imgIRequest::STATUS_ERROR;
 }
 
 void
-imgStatusTracker::SendStopDecode(imgRequestProxy* aProxy,
-                                 nsresult aStatus)
+imgStatusTracker::SendStopDecode(imgRequestProxy* aProxy, nsresult aStatus,
+                                 const PRUnichar* statusArg)
 {
+  // See imgRequest::OnStopDecode for more information on why we call
+  // OnStopContainer from here this, and why imgRequestProxy::OnStopDecode() is
+  // called from OnStopRequest() and SyncNotify().
   if (!aProxy->NotificationsDeferred())
-    aProxy->OnStopDecode();
+    aProxy->OnStopContainer(mImage);
 }
 
 void
 imgStatusTracker::RecordDiscard()
 {
   NS_ABORT_IF_FALSE(mImage,
                     "RecordDiscard called before we have an Image");
   // Clear the state bits we no longer deserve.
-  uint32_t stateBitsToClear = stateDecodeStopped;
+  uint32_t stateBitsToClear = stateDecodeStarted | stateDecodeStopped;
   mState &= ~stateBitsToClear;
 
   // Clear the status bits we no longer deserve.
   uint32_t statusBitsToClear = imgIRequest::STATUS_FRAME_COMPLETE
                                | imgIRequest::STATUS_DECODE_COMPLETE;
   mImageStatus &= ~statusBitsToClear;
 }
 
@@ -612,115 +445,77 @@ void
 imgStatusTracker::SendDiscard(imgRequestProxy* aProxy)
 {
   if (!aProxy->NotificationsDeferred())
     aProxy->OnDiscard();
 }
 
 /* non-virtual imgIContainerObserver methods */
 void
-imgStatusTracker::RecordFrameChanged(const nsIntRect* aDirtyRect)
+imgStatusTracker::RecordFrameChanged(imgIContainer* aContainer,
+                                     const nsIntRect* aDirtyRect)
 {
   NS_ABORT_IF_FALSE(mImage,
                     "RecordFrameChanged called before we have an Image");
   // no bookkeeping necessary here - this is only for in-frame updates, which we
   // don't fire while we're recording
 }
 
 void
-imgStatusTracker::SendFrameChanged(imgRequestProxy* aProxy,
+imgStatusTracker::SendFrameChanged(imgRequestProxy* aProxy, imgIContainer* aContainer,
                                    const nsIntRect* aDirtyRect)
 {
   if (!aProxy->NotificationsDeferred())
-    aProxy->OnFrameUpdate(aDirtyRect);
+    aProxy->FrameChanged(aContainer, aDirtyRect);
 }
 
 /* non-virtual sort-of-nsIRequestObserver methods */
 void
 imgStatusTracker::RecordStartRequest()
 {
   // We're starting a new load, so 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 &= ~stateDecodeStopped;
   mState &= ~stateRequestStopped;
   mState &= ~stateBlockingOnload;
 
   mState |= stateRequestStarted;
 }
 
 void
 imgStatusTracker::SendStartRequest(imgRequestProxy* aProxy)
 {
   if (!aProxy->NotificationsDeferred())
     aProxy->OnStartRequest();
 }
 
 void
-imgStatusTracker::OnStartRequest()
-{
-  RecordStartRequest();
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
-  while (iter.HasMore()) {
-    SendStartRequest(iter.GetNext());
-  }
-}
-
-void
-imgStatusTracker::RecordStopRequest(bool aLastPart,
-                                    nsresult aStatus)
+imgStatusTracker::RecordStopRequest(bool aLastPart, nsresult aStatus)
 {
   mHadLastPart = aLastPart;
   mState |= stateRequestStopped;
 
   // If we were successful in loading, note that the image is complete.
-  if (NS_SUCCEEDED(aStatus) && mImageStatus != imgIRequest::STATUS_ERROR)
+  if (NS_SUCCEEDED(aStatus))
     mImageStatus |= imgIRequest::STATUS_LOAD_COMPLETE;
-  else
-    mImageStatus = imgIRequest::STATUS_ERROR;
-}
-
-void
-imgStatusTracker::SendStopRequest(imgRequestProxy* aProxy,
-                                  bool aLastPart,
-                                  nsresult aStatus)
-{
-  if (!aProxy->NotificationsDeferred()) {
-    aProxy->OnStopRequest(aLastPart);
-  }
 }
 
 void
-imgStatusTracker::OnStopRequest(bool aLastPart,
-                                nsresult aStatus)
+imgStatusTracker::SendStopRequest(imgRequestProxy* aProxy, bool aLastPart, nsresult aStatus)
 {
-  bool preexistingError = mImageStatus == imgIRequest::STATUS_ERROR;
-
-  RecordStopRequest(aLastPart, aStatus);
-  /* notify the kids */
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator srIter(mConsumers);
-  while (srIter.HasMore()) {
-    SendStopRequest(srIter.GetNext(), aLastPart, aStatus);
-  }
-
-  if (NS_FAILED(aStatus) && !preexistingError) {
-    FireFailureNotification(GetRequest());
-  }
-}
-
-void
-imgStatusTracker::OnDataAvailable()
-{
-  // Notify any imgRequestProxys that are observing us that we have an Image.
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
-  while (iter.HasMore()) {
-    iter.GetNext()->SetHasImage();
+  // See bug 505385 and imgRequest::OnStopDecode for more information on why
+  // OnStopDecode is called with OnStopRequest.
+  if (!aProxy->NotificationsDeferred()) {
+    aProxy->OnStopDecode(GetResultFromImageStatus(mImageStatus), nullptr);
+    aProxy->OnStopRequest(aLastPart);
   }
 }
 
 void
 imgStatusTracker::RecordBlockOnload()
 {
   MOZ_ASSERT(!(mState & stateBlockingOnload));
   mState |= stateBlockingOnload;
@@ -743,25 +538,8 @@ imgStatusTracker::RecordUnblockOnload()
 
 void
 imgStatusTracker::SendUnblockOnload(imgRequestProxy* aProxy)
 {
   if (!aProxy->NotificationsDeferred()) {
     aProxy->UnblockOnload();
   }
 }
-
-void
-imgStatusTracker::MaybeUnblockOnload()
-{
-  if (!mBlockingOnload) {
-    return;
-  }
-
-  mBlockingOnload = false;
-
-  RecordUnblockOnload();
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
-  while (iter.HasMore()) {
-    SendUnblockOnload(iter.GetNext());
-  }
-}
--- a/image/src/imgStatusTracker.h
+++ b/image/src/imgStatusTracker.h
@@ -7,63 +7,38 @@
 #ifndef imgStatusTracker_h__
 #define imgStatusTracker_h__
 
 class imgIContainer;
 class imgRequest;
 class imgRequestProxy;
 class imgStatusNotifyRunnable;
 class imgRequestNotifyRunnable;
-class imgStatusTracker;
 struct nsIntRect;
 namespace mozilla {
 namespace image {
 class Image;
 } // namespace image
 } // namespace mozilla
 
 
 #include "nsCOMPtr.h"
-#include "nsAutoPtr.h"
-#include "nsTObserverArray.h"
 #include "nsIRunnable.h"
 #include "nscore.h"
-#include "nsWeakReference.h"
-#include "imgIDecoderObserver.h"
 
 enum {
   stateRequestStarted    = PR_BIT(0),
   stateHasSize           = PR_BIT(1),
+  stateDecodeStarted     = PR_BIT(2),
   stateDecodeStopped     = PR_BIT(3),
   stateFrameStopped      = PR_BIT(4),
   stateRequestStopped    = PR_BIT(5),
   stateBlockingOnload    = PR_BIT(6)
 };
 
-class imgStatusTrackerObserver : public imgIDecoderObserver,
-                                 public nsSupportsWeakReference
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_IMGIDECODEROBSERVER
-  NS_DECL_IMGICONTAINEROBSERVER
-
-  imgStatusTrackerObserver(imgStatusTracker* aTracker)
-  : mTracker(aTracker) {}
-
-  virtual ~imgStatusTrackerObserver() {}
-
-  void SetTracker(imgStatusTracker* aTracker) {
-    mTracker = aTracker;
-  }
-
-private:
-  imgStatusTracker* mTracker;
-};
-
 /*
  * The image status tracker is a class that encapsulates all the loading and
  * decoding status about an Image, and makes it possible to send notifications
  * to imgRequestProxys, both synchronously (i.e., the status now) and
  * asynchronously (the status later).
  *
  * When a new proxy needs to be notified of the current state of an image, call
  * the Notify() method on this class with the relevant proxy as its argument,
@@ -71,17 +46,17 @@ private:
  */
 
 class imgStatusTracker
 {
 public:
   // aImage is the image that this status tracker will pass to the
   // imgRequestProxys in SyncNotify() and EmulateRequestFinished(), and must be
   // alive as long as this instance is, because we hold a weak reference to it.
-  imgStatusTracker(mozilla::image::Image* aImage, imgRequest* aRequest);
+  imgStatusTracker(mozilla::image::Image* aImage);
   imgStatusTracker(const imgStatusTracker& aOther);
 
   // Image-setter, for imgStatusTrackers created by imgRequest::Init, which
   // are created before their Image is created.  This method should only
   // be called once, and only on an imgStatusTracker that was initialized
   // without an image.
   void SetImage(mozilla::image::Image* aImage);
 
@@ -105,31 +80,16 @@ public:
   // OnStartRequest).
   void SyncNotify(imgRequestProxy* proxy);
 
   // Send some notifications that would be necessary to make |proxy| believe
   // the request is finished downloading and decoding.  We only send
   // OnStopRequest and UnblockOnload, and only if necessary.
   void EmulateRequestFinished(imgRequestProxy* proxy, nsresult aStatus);
 
-  // We manage a set of consumers that are using an image and thus concerned
-  // with its status. Weak pointers.
-  void AddConsumer(imgRequestProxy* aConsumer);
-  bool RemoveConsumer(imgRequestProxy* aConsumer, nsresult aStatus, bool aOnlySendStopRequest);
-  size_t ConsumerCount() const { return mConsumers.Length(); };
-
-  // This is intentionally non-general because its sole purpose is to support an
-  // some obscure network priority logic in imgRequest. That stuff could probably
-  // be improved, but it's too scary to mess with at the moment.
-  bool FirstConsumerIs(imgRequestProxy* aConsumer) {
-    return mConsumers.SafeElementAt(0, nullptr) == aConsumer;
-  }
-
-  void AdoptConsumers(imgStatusTracker* aTracker) { mConsumers = aTracker->mConsumers; }
-
   // Returns whether we are in the process of loading; that is, whether we have
   // not received OnStopRequest.
   bool IsLoading() const;
 
   // Get the current image status (as in imgIRequest).
   uint32_t GetImageStatus() const;
 
   // Following are all the notification methods. You must call the Record
@@ -139,80 +99,67 @@ public:
   // Call when the request is being cancelled.
   void RecordCancel();
 
   // Shorthand for recording all the load notifications: StartRequest,
   // StartContainer, StopRequest.
   void RecordLoaded();
 
   // Shorthand for recording all the decode notifications: StartDecode,
-  // StartFrame, DataAvailable, StopFrame, StopDecode.
+  // StartFrame, DataAvailable, StopFrame, StopContainer, StopDecode.
   void RecordDecoded();
 
   /* non-virtual imgIDecoderObserver methods */
+  void RecordStartDecode();
+  void SendStartDecode(imgRequestProxy* aProxy);
   void RecordStartContainer(imgIContainer* aContainer);
-  void SendStartContainer(imgRequestProxy* aProxy);
-  void RecordDataAvailable();
-  void SendDataAvailable(imgRequestProxy* aProxy, const nsIntRect* aRect);
-  void RecordStopFrame();
-  void SendStopFrame(imgRequestProxy* aProxy);
-  void RecordStopDecode(nsresult statusg);
-  void SendStopDecode(imgRequestProxy* aProxy, nsresult aStatus);
+  void SendStartContainer(imgRequestProxy* aProxy, imgIContainer* aContainer);
+  void RecordStartFrame(uint32_t aFrame);
+  void SendStartFrame(imgRequestProxy* aProxy, uint32_t aFrame);
+  void RecordDataAvailable(bool aCurrentFrame, const nsIntRect* aRect);
+  void SendDataAvailable(imgRequestProxy* aProxy, bool aCurrentFrame, const nsIntRect* aRect);
+  void RecordStopFrame(uint32_t aFrame);
+  void SendStopFrame(imgRequestProxy* aProxy, uint32_t aFrame);
+  void RecordStopContainer(imgIContainer* aContainer);
+  void SendStopContainer(imgRequestProxy* aProxy, imgIContainer* aContainer);
+  void RecordStopDecode(nsresult status, const PRUnichar* statusArg);
+  void SendStopDecode(imgRequestProxy* aProxy, nsresult aStatus, const PRUnichar* statusArg);
   void RecordDiscard();
   void SendDiscard(imgRequestProxy* aProxy);
   void RecordImageIsAnimated();
   void SendImageIsAnimated(imgRequestProxy *aProxy);
 
   /* non-virtual imgIContainerObserver methods */
-  void RecordFrameChanged(const nsIntRect* aDirtyRect);
-  void SendFrameChanged(imgRequestProxy* aProxy, const nsIntRect* aDirtyRect);
+  void RecordFrameChanged(imgIContainer* aContainer,
+                          const nsIntRect* aDirtyRect);
+  void SendFrameChanged(imgRequestProxy* aProxy, imgIContainer* aContainer,
+                        const nsIntRect* aDirtyRect);
 
   /* non-virtual sort-of-nsIRequestObserver methods */
   void RecordStartRequest();
   void SendStartRequest(imgRequestProxy* aProxy);
   void RecordStopRequest(bool aLastPart, nsresult aStatus);
   void SendStopRequest(imgRequestProxy* aProxy, bool aLastPart, nsresult aStatus);
 
-  void OnStartRequest();
-  void OnDataAvailable();
-  void OnStopRequest(bool aLastPart, nsresult aStatus);
-
   /* non-virtual imgIOnloadBlocker methods */
   // NB: If UnblockOnload is sent, and then we are asked to replay the
   // notifications, we will not send a BlockOnload/UnblockOnload pair.  This
   // is different from all the other notifications.
   void RecordBlockOnload();
   void SendBlockOnload(imgRequestProxy* aProxy);
   void RecordUnblockOnload();
   void SendUnblockOnload(imgRequestProxy* aProxy);
 
-  void MaybeUnblockOnload();
-
-  // Weak pointer getters - no AddRefs.
-  inline mozilla::image::Image* GetImage() const { return mImage; };
-  inline imgRequest* GetRequest() const { return mRequest; };
-
-  inline imgIDecoderObserver* GetDecoderObserver() { return mTrackerObserver.get(); }
-
 private:
   friend class imgStatusNotifyRunnable;
   friend class imgRequestNotifyRunnable;
-  friend class imgStatusTrackerObserver;
 
   nsCOMPtr<nsIRunnable> mRequestRunnable;
 
-  // Weak pointers to the image and request. The request owns the image, and
-  // the image (or the request, if there's no image) owns the status tracker.
+  // A weak pointer to the Image, because it owns us, and we
+  // can't create a cycle.
   mozilla::image::Image* mImage;
-  imgRequest* mRequest;
   uint32_t mState;
   uint32_t mImageStatus;
   bool mHadLastPart;
-  bool mBlockingOnload;
-
-  // List of proxies attached to the image. Each proxy represents a consumer
-  // using the image.
-  nsTObserverArray<imgRequestProxy*> mConsumers;
-
-  nsRefPtr<imgStatusTrackerObserver> mTrackerObserver;
 };
 
 #endif
--- a/image/src/imgTools.cpp
+++ b/image/src/imgTools.cpp
@@ -18,18 +18,16 @@
 #include "nsStringStream.h"
 #include "nsComponentManagerUtils.h"
 #include "nsWeakReference.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsStreamUtils.h"
 #include "nsNetUtil.h"
 #include "nsContentUtils.h"
 #include "RasterImage.h"
-#include "ScriptedNotificationObserver.h"
-#include "imgIScriptedNotificationObserver.h"
 
 using namespace mozilla::image;
 
 class nsIDOMDocument;
 class nsIDocument;
 
 /* ========== imgITools implementation ========== */
 
@@ -273,23 +271,16 @@ NS_IMETHODIMP imgTools::GetFirstImageFra
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(frame, NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_TRUE(frame->Width() && frame->Height(), NS_ERROR_FAILURE);
 
   frame.forget(aSurface);
   return NS_OK;
 }
 
-NS_IMETHODIMP imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver* aInner,
-                                               imgINotificationObserver** aObserver)
-{
-  NS_ADDREF(*aObserver = new ScriptedNotificationObserver(aInner));
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 imgTools::GetImgLoaderForDocument(nsIDOMDocument* aDoc, imgILoader** aLoader)
 {
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
   NS_IF_ADDREF(*aLoader = nsContentUtils::GetImgLoaderForDocument(doc));
   return NS_OK;
 }
 
--- a/image/test/mochitest/imgutils.js
+++ b/image/test/mochitest/imgutils.js
@@ -116,19 +116,20 @@ function getImagePref(pref)
       default:
         throw new Error("Unknown pref type");
     }
   }
   else
     return null;
 }
 
-// JS implementation of imgIScriptedNotificationObserver with stubs for all of its methods.
+// JS implementation of imgIDecoderObserver with stubs for all of its methods.
 function ImageDecoderObserverStub()
 {
-  this.sizeAvailable = function sizeAvailable(aRequest)   {}
-  this.frameComplete = function frameComplete(aRequest)   {}
-  this.decodeComplete = function decodeComplete(aRequest) {}
-  this.loadComplete = function loadComplete(aRequest)     {}
-  this.frameUpdate = function frameUpdate(aRequest)       {}
-  this.discard = function discard(aRequest)               {}
-  this.isAnimated = function isAnimated(aRequest)         {}
+  this.onStartRequest = function onStartRequest(aRequest)                 {}
+  this.onStartDecode = function onStartDecode(aRequest)                   {}
+  this.onStartContainer = function onStartContainer(aRequest, aContainer) {}
+  this.onStartFrame = function onStartFrame(aRequest, aFrame)             {}
+  this.onStopFrame = function onStopFrame(aRequest, aFrame)               {}
+  this.onStopContainer = function onStopContainer(aRequest, aContainer)   {}
+  this.onStopDecode = function onStopDecode(aRequest, status, statusArg)  {}
+  this.onStopRequest = function onStopRequest(aRequest, aIsLastPart)      {}
 }
--- a/image/test/mochitest/test_animSVGImage.html
+++ b/image/test/mochitest/test_animSVGImage.html
@@ -21,17 +21,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 <pre id="test">
 <script type="application/javascript;version=1.8">
 /** Test for Bug 610419**/
 
 SimpleTest.waitForExplicitFinish();
 
 const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
 
-const Cc = Components.classes;
 const Ci = Components.interfaces;
 const gImg = document.getElementsByTagName("img")[0];
 
 var gMyDecoderObserver; // value will be set in main()
 var gReferenceSnapshot; // value will be set in takeReferenceSnapshot()
 var gOnStopFrameCounter = 0;
 var gIsTestFinished = false;
 
@@ -49,17 +48,17 @@ function takeReferenceSnapshot() {
 
   // Re-hide reference div, and take another snapshot to be sure it's gone
   referenceDiv.style.display = "none";
   let blankSnapshot2 = snapshotWindow(window, false);
   ok(compareSnapshots(blankSnapshot, blankSnapshot2, true)[0],
      "reference div should disappear when it becomes display:none");
 }
 
-function myOnStopFrame(aRequest) {
+function myOnStopFrame(aRequest, aFrame) {
   gOnStopFrameCounter++;
   ok(true, "myOnStopFrame called");
   let currentSnapshot = snapshotWindow(window, false);
   if (compareSnapshots(currentSnapshot, gReferenceSnapshot, true)[0]) {
     // SUCCESS!
     ok(true, "Animated image looks correct, " +
              "at call #" + gOnStopFrameCounter + " to onStopFrame");
     cleanUpAndFinish();
@@ -85,21 +84,18 @@ function cleanUpAndFinish() {
   SimpleTest.finish();
   gIsTestFinished = true;
 }
 
 function main() {
   takeReferenceSnapshot();
 
   // Create, customize & attach decoder observer
-  observer = new ImageDecoderObserverStub();
-  observer.frameComplete = myOnStopFrame;
-  gMyDecoderObserver =
-    Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-      .createScriptedObserver(observer);
+  gMyDecoderObserver = new ImageDecoderObserverStub();
+  gMyDecoderObserver.onStopFrame = myOnStopFrame;
   let imgLoadingContent = gImg.QueryInterface(Ci.nsIImageLoadingContent);
   imgLoadingContent.addObserver(gMyDecoderObserver);
 
   // We want to test the cold loading behavior, so clear cache in case an
   // earlier test got our image in there already.
   clearImageCache();
 
   // kick off image-loading! myOnStopFrame handles the rest.
--- a/image/test/unit/async_load_tests.js
+++ b/image/test/unit/async_load_tests.js
@@ -40,47 +40,39 @@ function getCloneStopCallback(original_l
 // but they aren't synchronous right now.
 function checkClone(other_listener, aRequest)
 {
   do_test_pending();
 
   // For as long as clone notification is synchronous, we can't test the clone state reliably.
   var listener = new ImageListener(null, function(foo, bar) { do_test_finished(); } /*getCloneStopCallback(other_listener)*/);
   listener.synchronous = false;
-  var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-                .createScriptedObserver(listener);
-  var clone = aRequest.clone(outer);
+  var clone = aRequest.clone(listener);
 }
 
 // Ensure that all the callbacks were called on aRequest.
 function checkAllCallbacks(listener, aRequest)
 {
-  do_check_neq(listener.state & SIZE_AVAILABLE, 0);
-  do_check_neq(listener.state & FRAME_COMPLETE, 0);
-  do_check_neq(listener.state & DECODE_COMPLETE, 0);
-  do_check_neq(listener.state & LOAD_COMPLETE, 0);
   do_check_eq(listener.state, ALL_BITS);
 
   do_test_finished();
 }
 
 function secondLoadDone(oldlistener, aRequest)
 {
   do_test_pending();
 
   try {
     var staticrequest = aRequest.getStaticRequest();
 
     // For as long as clone notification is synchronous, we can't test the
     // clone state reliably.
     var listener = new ImageListener(null, checkAllCallbacks);
     listener.synchronous = false;
-    var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-                  .createScriptedObserver(listener);
-    var staticrequestclone = staticrequest.clone(outer);
+    var staticrequestclone = staticrequest.clone(listener);
   } catch(e) {
     // We can't create a static request. Most likely the request we started
     // with didn't load successfully.
     do_test_finished();
   }
 
   run_loadImageWithChannel_tests();
 
@@ -89,38 +81,36 @@ function secondLoadDone(oldlistener, aRe
 
 // Load the request a second time. This should come from the image cache, and
 // therefore would be at most risk of being served synchronously.
 function checkSecondLoad()
 {
   do_test_pending();
 
   var listener = new ImageListener(checkClone, secondLoadDone);
-  var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-                .createScriptedObserver(listener);
-  requests.push(gCurrentLoader.loadImage(uri, null, null, null, null, outer, null, 0, null, null, null));
+  requests.push(gCurrentLoader.loadImage(uri, null, null, null, null, listener, null, 0, null, null, null));
   listener.synchronous = false;
 }
 
 function firstLoadDone(oldlistener, aRequest)
 {
   checkSecondLoad(uri);
 
   do_test_finished();
 }
 
 // Return a closure that allows us to check the stream listener's status when the
 // image starts loading.
 function getChannelLoadImageStartCallback(streamlistener)
 {
   return function channelLoadStart(imglistener, aRequest) {
-    // We must not have received all status before we get this start callback.
+    // We must not have received any status before we get this start callback.
     // If we have, we've broken people's expectations by delaying events from a
     // channel we were given.
-    do_check_eq(streamlistener.requestStatus & STOP_REQUEST, 0);
+    do_check_eq(streamlistener.requestStatus, 0);
 
     checkClone(imglistener, aRequest);
   }
 }
 
 // Return a closure that allows us to check the stream listener's status when the
 // image finishes loading.
 function getChannelLoadImageStopCallback(streamlistener, next)
@@ -146,20 +136,18 @@ function checkSecondChannelLoad()
   var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);  
   var channel = ioService.newChannelFromURI(uri);
   var channellistener = new ChannelListener();
   channel.asyncOpen(channellistener, null);
 
   var listener = new ImageListener(getChannelLoadImageStartCallback(channellistener),
                                    getChannelLoadImageStopCallback(channellistener,
                                                                    all_done_callback));
-  var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-                .createScriptedObserver(listener);
   var outlistener = {};
-  requests.push(gCurrentLoader.loadImageWithChannel(channel, outer, null, outlistener));
+  requests.push(gCurrentLoader.loadImageWithChannel(channel, listener, null, outlistener));
   channellistener.outputListener = outlistener.value;
 
   listener.synchronous = false;
 }
 
 function run_loadImageWithChannel_tests()
 {
   // To ensure we're testing what we expect to, create a new loader and cache.
@@ -170,20 +158,18 @@ function run_loadImageWithChannel_tests(
   var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);  
   var channel = ioService.newChannelFromURI(uri);
   var channellistener = new ChannelListener();
   channel.asyncOpen(channellistener, null);
 
   var listener = new ImageListener(getChannelLoadImageStartCallback(channellistener),
                                    getChannelLoadImageStopCallback(channellistener,
                                                                    checkSecondChannelLoad));
-  var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-                .createScriptedObserver(listener);
   var outlistener = {};
-  requests.push(gCurrentLoader.loadImageWithChannel(channel, outer, null, outlistener));
+  requests.push(gCurrentLoader.loadImageWithChannel(channel, listener, null, outlistener));
   channellistener.outputListener = outlistener.value;
 
   listener.synchronous = false;
 }
 
 function all_done_callback()
 {
   server.stop(function() { do_test_finished(); });
@@ -191,36 +177,32 @@ function all_done_callback()
 
 function startImageCallback(otherCb)
 {
   return function(listener, request)
   {
     // Make sure we can load the same image immediately out of the cache.
     do_test_pending();
     var listener2 = new ImageListener(null, function(foo, bar) { do_test_finished(); });
-    var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-                  .createScriptedObserver(listener2);
-    requests.push(gCurrentLoader.loadImage(uri, null, null, null, null, outer, null, 0, null, null, null));
+    requests.push(gCurrentLoader.loadImage(uri, null, null, null, null, listener2, null, 0, null, null, null));
     listener2.synchronous = false;
 
     // Now that we've started another load, chain to the callback.
     otherCb(listener, request);
   }
 }
 
 var gCurrentLoader;
 
 function run_test()
 {
   gCurrentLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader);
 
   do_test_pending();
   var listener = new ImageListener(startImageCallback(checkClone), firstLoadDone);
-  var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-                .createScriptedObserver(listener);
-  var req = gCurrentLoader.loadImage(uri, null, null, null, null, outer, null, 0, null, null, null);
+  var req = gCurrentLoader.loadImage(uri, null, null, null, null, listener, null, 0, null, null, null);
   requests.push(req);
 
   // Ensure that we don't cause any mayhem when we lock an image.
   req.lockImage();
 
   listener.synchronous = false;
 }
--- a/image/test/unit/image_load_helpers.js
+++ b/image/test/unit/image_load_helpers.js
@@ -1,71 +1,92 @@
 /*
  * Helper structures to track callbacks from image and channel loads.
  */
 
-// START_REQUEST and STOP_REQUEST are used by ChannelListener, and
+// One bit per callback that imageListener below implements. Stored in
+// ImageListener.state.
+// START_REQUEST and STOP_REQUEST are also reused by ChannelListener, and
 // stored in ChannelListener.requestStatus.
 const START_REQUEST = 0x01;
-const STOP_REQUEST = 0x02;
-const DATA_AVAILABLE = 0x04;
-
-// One bit per callback that imageListener below implements. Stored in
-// ImageListener.state.
-const SIZE_AVAILABLE = 0x01;
-const FRAME_UPDATE = 0x02;
-const FRAME_COMPLETE = 0x04;
-const LOAD_COMPLETE = 0x08;
-const DECODE_COMPLETE = 0x10;
-const ALL_BITS = SIZE_AVAILABLE | FRAME_COMPLETE | DECODE_COMPLETE | LOAD_COMPLETE;
+const START_DECODE = 0x02;
+const START_CONTAINER = 0x04;
+const START_FRAME = 0x08;
+const STOP_FRAME = 0x10;
+const STOP_CONTAINER = 0x20;
+const STOP_DECODE = 0x40;
+const STOP_REQUEST = 0x80;
+const ALL_BITS = 0xFF;
 
 // An implementation of imgIDecoderObserver with the ability to call specified
 // functions on onStartRequest and onStopRequest.
 function ImageListener(start_callback, stop_callback)
 {
-  this.sizeAvailable = function onSizeAvailable(aRequest)
+  this.onStartRequest = function onStartRequest(aRequest)
   {
     do_check_false(this.synchronous);
 
-    this.state |= SIZE_AVAILABLE;
+    this.state |= START_REQUEST;
 
     if (this.start_callback)
       this.start_callback(this, aRequest);
   }
-  this.frameComplete = function onFrameComplete(aRequest)
+  this.onStartDecode = function onStartDecode(aRequest)
+  {
+    do_check_false(this.synchronous);
+
+    this.state |= START_DECODE;
+  }
+  this.onStartContainer = function onStartContainer(aRequest, aContainer)
+  {
+    do_check_false(this.synchronous);
+
+    this.state |= START_CONTAINER;
+  }
+  this.onStartFrame = function onStartFrame(aRequest, aFrame)
+  {
+    do_check_false(this.synchronous);
+
+    this.state |= START_FRAME;
+  }
+  this.onStopFrame = function onStopFrame(aRequest, aFrame)
   {
     do_check_false(this.synchronous);
 
-    this.state |= FRAME_COMPLETE;
+    this.state |= STOP_FRAME;
   }
-  this.decodeComplete = function onDecodeComplete(aRequest)
+  this.onStopContainer = function onStopContainer(aRequest, aContainer)
   {
     do_check_false(this.synchronous);
 
-    this.state |= DECODE_COMPLETE;
+    this.state |= STOP_CONTAINER;
   }
-  this.loadComplete = function onLoadcomplete(aRequest)
+  this.onStopDecode = function onStopDecode(aRequest, status, statusArg)
   {
     do_check_false(this.synchronous);
 
+    this.state |= STOP_DECODE;
+  }
+  this.onStopRequest = function onStopRequest(aRequest, aIsLastPart)
+  {
+    do_check_false(this.synchronous);
+
+    // onStopDecode must always be called before, and with, onStopRequest. See
+    // imgRequest::OnStopDecode for more information.
+    do_check_true(!!(this.state & STOP_DECODE));
+
     // We have to cancel the request when we're done with it to break any
     // reference loops!
     aRequest.cancelAndForgetObserver(0);
 
-    this.state |= LOAD_COMPLETE;
+    this.state |= STOP_REQUEST;
 
     if (this.stop_callback)
       this.stop_callback(this, aRequest);
   }
-  this.frameUpdate = function onFrameUpdate(aRequest)
-  {
-  }
-  this.isAnimated = function onIsAnimated()
-  {
-  }
 
   // Initialize the synchronous flag to true to start. This must be set to
   // false before exiting to the event loop!
   this.synchronous = true;
 
   // A function to call when onStartRequest is called.
   this.start_callback = start_callback;
 
@@ -92,18 +113,16 @@ function ChannelListener()
 
     this.requestStatus |= START_REQUEST;
   }
 
   this.onDataAvailable = function onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount)
   {
     if (this.outputListener)
       this.outputListener.onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
-
-    this.requestStatus |= DATA_AVAILABLE;
   }
 
   this.onStopRequest = function onStopRequest(aRequest, aContext, aStatusCode)
   {
     if (this.outputListener)
       this.outputListener.onStopRequest(aRequest, aContext, aStatusCode);
 
     // If we failed (or were canceled - failure is implied if canceled),
--- a/image/test/unit/test_private_channel.js
+++ b/image/test/unit/test_private_channel.js
@@ -50,35 +50,31 @@ var gImgPath = 'http://localhost:8088/im
 function setup_chan(path, isPrivate, callback) {
   var uri = gIoService.newURI(gImgPath, null, null);
   var chan = gIoService.newChannelFromURI(uri);
   chan.notificationCallbacks = new NotificationCallbacks(isPrivate);
   var channelListener = new ChannelListener();
   chan.asyncOpen(channelListener, null);
   
   var listener = new ImageListener(null, callback);
+  listeners.push(listener);
   var outlistener = {};
   var loader = isPrivate ? gPrivateLoader : gPublicLoader;
-  var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-                .createScriptedObserver(listener);
-  listeners.push(outer);
-  requests.push(loader.loadImageWithChannel(chan, outer, null, outlistener));
+  requests.push(loader.loadImageWithChannel(chan, listener, null, outlistener));
   channelListener.outputListener = outlistener.value;
   listener.synchronous = false;
 }
 
 function loadImage(isPrivate, callback) {
   var listener = new ImageListener(null, callback);
-  var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-                .createScriptedObserver(listener);
   var uri = gIoService.newURI(gImgPath, null, null);
   var loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
   loadGroup.notificationCallbacks = new NotificationCallbacks(isPrivate);
   var loader = isPrivate ? gPrivateLoader : gPublicLoader;
-  requests.push(loader.loadImage(uri, null, null, null, loadGroup, outer, null, 0, null, null, null));
+  requests.push(loader.loadImage(uri, null, null, null, loadGroup, listener, null, 0, null, null, null));
   listener.synchronous = false;  
 }
 
 function run_loadImage_tests() {
   let cs = Cc["@mozilla.org/network/cache-service;1"].getService(Ci.nsICacheService);
   cs.evictEntries(Ci.nsICache.STORE_ANYWHERE);
 
   gHits = 0;
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -1426,46 +1426,19 @@ nsBulletFrame::GetMinWidth(nsRenderingCo
 nsBulletFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
 {
   nsHTMLReflowMetrics metrics;
   DISPLAY_PREF_WIDTH(this, metrics.width);
   GetDesiredSize(PresContext(), aRenderingContext, metrics, 1.0f);
   return metrics.width;
 }
 
-NS_IMETHODIMP
-nsBulletFrame::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
-{
-  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
-    nsCOMPtr<imgIContainer> image;
-    aRequest->GetImage(getter_AddRefs(image));
-    return OnStartContainer(aRequest, image);
-  }
 
-  if (aType == imgINotificationObserver::FRAME_UPDATE) {
-    // The image has changed.
-    // Invalidate the entire content area. Maybe it's not optimal but it's simple and
-    // always correct, and I'll be a stunned mullet if it ever matters for performance
-    InvalidateFrame();
-  }
-
-  if (aType == imgINotificationObserver::IS_ANIMATED) {
-    // Register the image request with the refresh driver now that we know it's
-    // animated.
-    if (aRequest == mImageRequest) {
-      nsLayoutUtils::RegisterImageRequest(PresContext(), mImageRequest,
-                                          &mRequestRegistered);
-    }
-  }
-
-  return NS_OK;
-}
-
-nsresult nsBulletFrame::OnStartContainer(imgIRequest *aRequest,
-                                         imgIContainer *aImage)
+NS_IMETHODIMP nsBulletFrame::OnStartContainer(imgIRequest *aRequest,
+                                              imgIContainer *aImage)
 {
   if (!aImage) return NS_ERROR_INVALID_ARG;
   if (!aRequest) return NS_ERROR_INVALID_ARG;
 
   uint32_t status;
   aRequest->GetImageStatus(&status);
   if (status & imgIRequest::STATUS_ERROR) {
     return NS_OK;
@@ -1497,16 +1470,70 @@ nsresult nsBulletFrame::OnStartContainer
   // Ensure the animation (if any) is started. Note: There is no
   // corresponding call to Decrement for this. This Increment will be
   // 'cleaned up' by the Request when it is destroyed, but only then.
   aRequest->IncrementAnimationConsumers();
   
   return NS_OK;
 }
 
+NS_IMETHODIMP nsBulletFrame::OnDataAvailable(imgIRequest *aRequest,
+                                             bool aCurrentFrame,
+                                             const nsIntRect *aRect)
+{
+  // The image has changed.
+  // Invalidate the entire content area. Maybe it's not optimal but it's simple and
+  // always correct, and I'll be a stunned mullet if it ever matters for performance
+  InvalidateFrame();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsBulletFrame::OnStopDecode(imgIRequest *aRequest,
+                                          nsresult aStatus,
+                                          const PRUnichar *aStatusArg)
+{
+  // XXX should the bulletframe do anything if the image failed to load?
+  //     it didn't in the old code...
+
+#if 0
+  if (NS_FAILED(aStatus)) {
+    // We failed to load the image. Notify the pres shell
+    if (NS_FAILED(aStatus) && (mImageRequest == aRequest || !mImageRequest)) {
+      imageFailed = true;
+    }
+  }
+#endif
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsBulletFrame::OnImageIsAnimated(imgIRequest* aRequest)
+{
+  // Register the image request with the refresh driver now that we know it's
+  // animated.
+  if (aRequest == mImageRequest) {
+    nsLayoutUtils::RegisterImageRequest(PresContext(), mImageRequest,
+                                        &mRequestRegistered);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsBulletFrame::FrameChanged(imgIRequest *aRequest,
+                                          imgIContainer *aContainer,
+                                          const nsIntRect *aDirtyRect)
+{
+  // Invalidate the entire content area. Maybe it's not optimal but it's simple and
+  // always correct.
+  InvalidateFrame();
+
+  return NS_OK;
+}
+
 void
 nsBulletFrame::GetLoadGroup(nsPresContext *aPresContext, nsILoadGroup **aLoadGroup)
 {
   if (!aPresContext)
     return;
 
   NS_PRECONDITION(nullptr != aLoadGroup, "null OUT parameter pointer");
 
@@ -1592,26 +1619,65 @@ nsBulletFrame::GetBaseline() const
 
 
 
 
 
 
 
 
-NS_IMPL_ISUPPORTS1(nsBulletListener, imgINotificationObserver)
+NS_IMPL_ISUPPORTS2(nsBulletListener, imgIDecoderObserver, imgIContainerObserver)
 
 nsBulletListener::nsBulletListener() :
   mFrame(nullptr)
 {
 }
 
 nsBulletListener::~nsBulletListener()
 {
 }
 
-NS_IMETHODIMP
-nsBulletListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
+NS_IMETHODIMP nsBulletListener::OnStartContainer(imgIRequest *aRequest,
+                                                 imgIContainer *aImage)
 {
   if (!mFrame)
     return NS_ERROR_FAILURE;
-  return mFrame->Notify(aRequest, aType, aData);
+
+  return mFrame->OnStartContainer(aRequest, aImage);
+}
+
+NS_IMETHODIMP nsBulletListener::OnDataAvailable(imgIRequest *aRequest,
+                                                bool aCurrentFrame,
+                                                const nsIntRect *aRect)
+{
+  if (!mFrame)
+    return NS_OK;
+
+  return mFrame->OnDataAvailable(aRequest, aCurrentFrame, aRect);
 }
+
+NS_IMETHODIMP nsBulletListener::OnStopDecode(imgIRequest *aRequest,
+                                             nsresult status,
+                                             const PRUnichar *statusArg)
+{
+  if (!mFrame)
+    return NS_OK;
+  
+  return mFrame->OnStopDecode(aRequest, status, statusArg);
+}
+
+NS_IMETHODIMP nsBulletListener::OnImageIsAnimated(imgIRequest *aRequest)
+{
+  if (!mFrame)
+    return NS_OK;
+
+  return mFrame->OnImageIsAnimated(aRequest);
+}
+
+NS_IMETHODIMP nsBulletListener::FrameChanged(imgIRequest *aRequest,
+                                             imgIContainer *aContainer,
+                                             const nsIntRect *aDirtyRect)
+{
+  if (!mFrame)
+    return NS_OK;
+
+  return mFrame->FrameChanged(aRequest, aContainer, aDirtyRect);
+}
--- a/layout/generic/nsBulletFrame.h
+++ b/layout/generic/nsBulletFrame.h
@@ -9,31 +9,42 @@
 #define nsBulletFrame_h___
 
 #include "mozilla/Attributes.h"
 #include "nsFrame.h"
 #include "nsStyleContext.h"
 
 #include "imgIRequest.h"
 #include "imgIDecoderObserver.h"
-#include "imgINotificationObserver.h"
+#include "nsStubImageDecoderObserver.h"
 
 #define BULLET_FRAME_IMAGE_LOADING NS_FRAME_STATE_BIT(63)
 #define BULLET_FRAME_HAS_FONT_INFLATION NS_FRAME_STATE_BIT(62)
 
 class nsBulletFrame;
 
-class nsBulletListener : public imgINotificationObserver
+class nsBulletListener : public nsStubImageDecoderObserver
 {
 public:
   nsBulletListener();
   virtual ~nsBulletListener();
 
   NS_DECL_ISUPPORTS
-  NS_DECL_IMGINOTIFICATIONOBSERVER
+  // imgIDecoderObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
+  NS_IMETHOD OnDataAvailable(imgIRequest *aRequest, bool aCurrentFrame,
+                             const nsIntRect *aRect);
+  NS_IMETHOD OnStopDecode(imgIRequest *aRequest, nsresult status,
+                          const PRUnichar *statusArg);
+  NS_IMETHOD OnImageIsAnimated(imgIRequest *aRequest);
+
+  // imgIContainerObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD FrameChanged(imgIRequest *aRequest,
+                          imgIContainer *aContainer,
+                          const nsIntRect *dirtyRect);
 
   void SetFrame(nsBulletFrame *frame) { mFrame = frame; }
 
 private:
   nsBulletFrame *mFrame;
 };
 
 /**
@@ -45,18 +56,16 @@ public:
   NS_DECL_FRAMEARENA_HELPERS
 
   nsBulletFrame(nsStyleContext* aContext)
     : nsFrame(aContext)
   {
   }
   virtual ~nsBulletFrame();
 
-  NS_IMETHOD Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData);
-
   // nsIFrame
   virtual void DestroyFrom(nsIFrame* aDestructRoot);
   NS_IMETHOD BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                               const nsRect&           aDirtyRect,
                               const nsDisplayListSet& aLists);
   virtual nsIAtom* GetType() const MOZ_OVERRIDE;
   virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) MOZ_OVERRIDE;
 #ifdef DEBUG
@@ -71,16 +80,28 @@ public:
   virtual nscoord GetMinWidth(nsRenderingContext *aRenderingContext) MOZ_OVERRIDE;
   virtual nscoord GetPrefWidth(nsRenderingContext *aRenderingContext) MOZ_OVERRIDE;
 
   // nsBulletFrame
   int32_t SetListItemOrdinal(int32_t aNextOrdinal, bool* aChanged,
                              int32_t aIncrement);
 
 
+  NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
+  NS_IMETHOD OnDataAvailable(imgIRequest *aRequest,
+                             bool aCurrentFrame,
+                             const nsIntRect *aRect);
+  NS_IMETHOD OnStopDecode(imgIRequest *aRequest,
+                          nsresult aStatus,
+                          const PRUnichar *aStatusArg);
+  NS_IMETHOD OnImageIsAnimated(imgIRequest *aRequest);
+  NS_IMETHOD FrameChanged(imgIRequest *aRequest,
+                          imgIContainer *aContainer,
+                          const nsIntRect *aDirtyRect);
+
   /* get list item text, without '.' */
   static bool AppendCounterText(int32_t aListStyleType,
                                   int32_t aOrdinal,
                                   nsString& aResult);
 
   /* get list item text, with '.' */
   bool GetListItemText(const nsStyleList& aStyleList,
                          nsString& aResult);
@@ -94,18 +115,16 @@ public:
 
   float GetFontSizeInflation() const;
   bool HasFontSizeInflation() const {
     return (GetStateBits() & BULLET_FRAME_HAS_FONT_INFLATION) != 0;
   }
   void SetFontSizeInflation(float aInflation);
 
 protected:
-  nsresult OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
-
   void GetDesiredSize(nsPresContext* aPresContext,
                       nsRenderingContext *aRenderingContext,
                       nsHTMLReflowMetrics& aMetrics,
                       float aFontSizeInflation);
 
   void GetLoadGroup(nsPresContext *aPresContext, nsILoadGroup **aLoadGroup);
 
   nsMargin mPadding;
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -135,18 +135,17 @@ NS_NewImageFrame(nsIPresShell* aPresShel
 
 NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)
 
 
 nsImageFrame::nsImageFrame(nsStyleContext* aContext) :
   ImageFrameSuper(aContext),
   mComputedSize(0, 0),
   mIntrinsicRatio(0, 0),
-  mDisplayingIcon(false),
-  mFirstFrameComplete(false)
+  mDisplayingIcon(false)
 {
   // We assume our size is not constrained and we haven't gotten an
   // initial reflow yet, so don't touch those flags.
   mIntrinsicSize.width.SetCoordValue(0);
   mIntrinsicSize.height.SetCoordValue(0);
 }
 
 nsImageFrame::~nsImageFrame()
@@ -522,44 +521,16 @@ nsImageFrame::ShouldCreateImageFrameFor(
       }
     }
   }
   
   return useSizedBox;
 }
 
 nsresult
-nsImageFrame::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData)
-{
-  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
-    nsCOMPtr<imgIContainer> image;
-    aRequest->GetImage(getter_AddRefs(image));
-    return OnStartContainer(aRequest, image);
-  }
-
-  if (aType == imgINotificationObserver::FRAME_UPDATE) {
-    return OnDataAvailable(aRequest, aData);
-  }
-
-  if (aType == imgINotificationObserver::FRAME_COMPLETE) {
-    mFirstFrameComplete = true;
-  }
-
-  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
-    uint32_t imgStatus;
-    aRequest->GetImageStatus(&imgStatus);
-    nsresult status =
-        imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
-    return OnStopRequest(aRequest, status);
-  }
-
-  return NS_OK;
-}
-
-nsresult
 nsImageFrame::OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage)
 {
   if (!aImage) return NS_ERROR_INVALID_ARG;
 
   /* Get requested animation policy from the pres context:
    *   normal = 0
    *   one frame = 1
    *   one loop = 2
@@ -588,60 +559,61 @@ nsImageFrame::OnStartContainer(imgIReque
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 nsImageFrame::OnDataAvailable(imgIRequest *aRequest,
+                              bool aCurrentFrame,
                               const nsIntRect *aRect)
 {
-  if (mFirstFrameComplete) {
-    nsCOMPtr<imgIContainer> container;
-    aRequest->GetImage(getter_AddRefs(container));
-    return FrameChanged(aRequest, container);
-  }
-
   // XXX do we need to make sure that the reflow from the
   // OnStartContainer has been processed before we start calling
   // invalidate?
 
   NS_ENSURE_ARG_POINTER(aRect);
 
   if (!(mState & IMAGE_GOTINITIALREFLOW)) {
     // Don't bother to do anything; we have a reflow coming up!
     return NS_OK;
   }
   
   if (IsPendingLoad(aRequest)) {
     // We don't care
     return NS_OK;
   }
 
+  // Don't invalidate if the current visible frame isn't the one the data is
+  // from
+  if (!aCurrentFrame)
+    return NS_OK;
+  
 #ifdef DEBUG_decode
   printf("Source rect (%d,%d,%d,%d)\n",
          aRect->x, aRect->y, aRect->width, aRect->height);
 #endif
 
   if (aRect->IsEqualInterior(nsIntRect::GetMaxSizedIntRect())) {
     InvalidateFrame(nsDisplayItem::TYPE_IMAGE);
     InvalidateFrame(nsDisplayItem::TYPE_ALT_FEEDBACK);
   } else {
     nsRect invalid = SourceRectToDest(*aRect);
     InvalidateFrameWithRect(invalid, nsDisplayItem::TYPE_IMAGE);
     InvalidateFrameWithRect(invalid, nsDisplayItem::TYPE_ALT_FEEDBACK);
   }
-
+  
   return NS_OK;
 }
 
 nsresult
-nsImageFrame::OnStopRequest(imgIRequest *aRequest,
-                            nsresult aStatus)
+nsImageFrame::OnStopDecode(imgIRequest *aRequest,
+                           nsresult aStatus,
+                           const PRUnichar *aStatusArg)
 {
   // Check what request type we're dealing with
   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
   NS_ASSERTION(imageLoader, "Who's notifying us??");
   int32_t loadType = nsIImageLoadingContent::UNKNOWN_REQUEST;
   imageLoader->GetRequestType(aRequest, &loadType);
   if (loadType != nsIImageLoadingContent::CURRENT_REQUEST &&
       loadType != nsIImageLoadingContent::PENDING_REQUEST) {
@@ -690,17 +662,18 @@ nsImageFrame::NotifyNewCurrentRequest(im
       // Update border+content to account for image change
       InvalidateFrame();
     }
   }
 }
 
 nsresult
 nsImageFrame::FrameChanged(imgIRequest *aRequest,
-                           imgIContainer *aContainer)
+                           imgIContainer *aContainer,
+                           const nsIntRect *aDirtyRect)
 {
   if (!GetStyleVisibility()->IsVisible()) {
     return NS_OK;
   }
 
   if (IsPendingLoad(aContainer)) {
     // We don't care about it
     return NS_OK;
@@ -1916,17 +1889,17 @@ nsresult nsImageFrame::LoadIcons(nsPresC
 
   rv = LoadIcon(brokenSrc,
                 aPresContext,
                 getter_AddRefs(gIconLoad->mBrokenImage));
   return rv;
 }
 
 NS_IMPL_ISUPPORTS2(nsImageFrame::IconLoad, nsIObserver,
-                   imgINotificationObserver)
+                   imgIDecoderObserver)
 
 static const char* kIconLoadPrefs[] = {
   "browser.display.force_inline_alttext",
   "browser.display.show_image_placeholders",
   nullptr
 };
 
 nsImageFrame::IconLoad::IconLoad()
@@ -1973,52 +1946,165 @@ void nsImageFrame::IconLoad::GetPrefs()
 {
   mPrefForceInlineAltText =
     Preferences::GetBool("browser.display.force_inline_alttext");
 
   mPrefShowPlaceholders =
     Preferences::GetBool("browser.display.show_image_placeholders", true);
 }
 
+
+
 NS_IMETHODIMP
-nsImageFrame::IconLoad::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
+nsImageFrame::IconLoad::OnStartRequest(imgIRequest *aRequest)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageFrame::IconLoad::OnStartDecode(imgIRequest *aRequest)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageFrame::IconLoad::OnStartContainer(imgIRequest *aRequest,
+                                         imgIContainer *aContainer)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageFrame::IconLoad::OnStartFrame(imgIRequest *aRequest,
+                                     uint32_t aFrame)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageFrame::IconLoad::OnDataAvailable(imgIRequest *aRequest,
+                                        bool aCurrentFrame,
+                                        const nsIntRect * aRect)
 {
-  if (aType != imgINotificationObserver::LOAD_COMPLETE &&
-      aType != imgINotificationObserver::FRAME_UPDATE) {
-    return NS_OK;
-  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageFrame::IconLoad::OnStopFrame(imgIRequest *aRequest,
+                                    uint32_t aFrame)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageFrame::IconLoad::OnStopContainer(imgIRequest *aRequest,
+                                        imgIContainer *aContainer)
+{
+  return NS_OK;
+}
 
+NS_IMETHODIMP
+nsImageFrame::IconLoad::OnStopDecode(imgIRequest *aRequest,
+                                     nsresult status,
+                                     const PRUnichar *statusArg)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageFrame::IconLoad::OnImageIsAnimated(imgIRequest *aRequest)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageFrame::IconLoad::OnStopRequest(imgIRequest *aRequest,
+                                      bool aIsLastPart)
+{
   nsTObserverArray<nsImageFrame*>::ForwardIterator iter(mIconObservers);
   nsImageFrame *frame;
   while (iter.HasMore()) {
     frame = iter.GetNext();
     frame->InvalidateFrame();
   }
 
   return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS1(nsImageListener, imgINotificationObserver)
+NS_IMETHODIMP
+nsImageFrame::IconLoad::OnDiscard(imgIRequest *aRequest)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageFrame::IconLoad::FrameChanged(imgIRequest *aRequest,
+                                     imgIContainer *aContainer,
+                                     const nsIntRect *aDirtyRect)
+{
+  nsTObserverArray<nsImageFrame*>::ForwardIterator iter(mIconObservers);
+  nsImageFrame *frame;
+  while (iter.HasMore()) {
+    frame = iter.GetNext();
+    frame->InvalidateFrame();
+  }
+
+  return NS_OK;
+}
+
+
+
+NS_IMPL_ISUPPORTS2(nsImageListener, imgIDecoderObserver, imgIContainerObserver)
 
 nsImageListener::nsImageListener(nsImageFrame *aFrame) :
   mFrame(aFrame)
 {
 }
 
 nsImageListener::~nsImageListener()
 {
 }
 
-NS_IMETHODIMP
-nsImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
+NS_IMETHODIMP nsImageListener::OnStartContainer(imgIRequest *aRequest,
+                                                imgIContainer *aImage)
+{
+  if (!mFrame)
+    return NS_ERROR_FAILURE;
+
+  return mFrame->OnStartContainer(aRequest, aImage);
+}
+
+NS_IMETHODIMP nsImageListener::OnDataAvailable(imgIRequest *aRequest,
+                                               bool aCurrentFrame,
+                                               const nsIntRect *aRect)
 {
   if (!mFrame)
     return NS_ERROR_FAILURE;
 
-  return mFrame->Notify(aRequest, aType, aData);
+  return mFrame->OnDataAvailable(aRequest, aCurrentFrame, aRect);
+}
+
+NS_IMETHODIMP nsImageListener::OnStopDecode(imgIRequest *aRequest,
+                                            nsresult status,
+                                            const PRUnichar *statusArg)
+{
+  if (!mFrame)
+    return NS_ERROR_FAILURE;
+
+  return mFrame->OnStopDecode(aRequest, status, statusArg);
+}
+
+NS_IMETHODIMP nsImageListener::FrameChanged(imgIRequest *aRequest,
+                                            imgIContainer *aContainer,
+                                            const nsIntRect *aDirtyRect)
+{
+  if (!mFrame)
+    return NS_ERROR_FAILURE;
+
+  return mFrame->FrameChanged(aRequest, aContainer, aDirtyRect);
 }
 
 static bool
 IsInAutoWidthTableCellForQuirk(nsIFrame *aFrame)
 {
   if (eCompatibility_NavQuirks != aFrame->PresContext()->CompatibilityMode())
     return false;
   // Check if the parent of the closest nsBlockFrame has auto width.
--- a/layout/generic/nsImageFrame.h
+++ b/layout/generic/nsImageFrame.h
@@ -7,17 +7,18 @@
 
 #ifndef nsImageFrame_h___
 #define nsImageFrame_h___
 
 #include "nsSplittableFrame.h"
 #include "nsIIOService.h"
 #include "nsIObserver.h"
 
-#include "imgINotificationObserver.h"
+#include "nsStubImageDecoderObserver.h"
+#include "imgIDecoderObserver.h"
 
 #include "nsDisplayList.h"
 #include "imgIContainer.h"
 #include "mozilla/Attributes.h"
 
 class nsIFrame;
 class nsImageMap;
 class nsIURI;
@@ -34,24 +35,33 @@ class nsImageLoadingContent;
 namespace mozilla {
 namespace layers {
   class ImageContainer;
   class ImageLayer;
   class LayerManager;
 }
 }
 
-class nsImageListener : public imgINotificationObserver
+class nsImageListener : public nsStubImageDecoderObserver
 {
 public:
   nsImageListener(nsImageFrame *aFrame);
   virtual ~nsImageListener();
 
   NS_DECL_ISUPPORTS
-  NS_DECL_IMGINOTIFICATIONOBSERVER
+  // imgIDecoderObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
+  NS_IMETHOD OnDataAvailable(imgIRequest *aRequest, bool aCurrentFrame,
+                             const nsIntRect *aRect);
+  NS_IMETHOD OnStopDecode(imgIRequest *aRequest, nsresult status,
+                          const PRUnichar *statusArg);
+  // imgIContainerObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD FrameChanged(imgIRequest *aRequest,
+                          imgIContainer *aContainer,
+                          const nsIntRect *dirtyRect);
 
   void SetFrame(nsImageFrame *frame) { mFrame = frame; }
 
 private:
   nsImageFrame *mFrame;
 };
 
 #define IMAGE_SIZECONSTRAINED       NS_FRAME_STATE_BIT(20)
@@ -122,18 +132,16 @@ public:
   static void ReleaseGlobals() {
     if (gIconLoad) {
       gIconLoad->Shutdown();
       NS_RELEASE(gIconLoad);
     }
     NS_IF_RELEASE(sIOService);
   }
 
-  nsresult Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData);
-
   /**
    * Function to test whether aContent, which has aStyleContext as its style,
    * should get an image frame.  Note that this method is only used by the
    * frame constructor; it's only here because it uses gIconLoad for now.
    */
   static bool ShouldCreateImageFrameFor(mozilla::dom::Element* aElement,
                                           nsStyleContext* aStyleContext);
   
@@ -208,21 +216,24 @@ protected:
   void PaintImage(nsRenderingContext& aRenderingContext, nsPoint aPt,
                   const nsRect& aDirtyRect, imgIContainer* aImage,
                   uint32_t aFlags);
 
 protected:
   friend class nsImageListener;
   friend class nsImageLoadingContent;
   nsresult OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
-  nsresult OnDataAvailable(imgIRequest *aRequest, const nsIntRect *rect);
-  nsresult OnStopRequest(imgIRequest *aRequest,
-                         nsresult aStatus);
+  nsresult OnDataAvailable(imgIRequest *aRequest, bool aCurrentFrame,
+                           const nsIntRect *rect);
+  nsresult OnStopDecode(imgIRequest *aRequest,
+                        nsresult aStatus,
+                        const PRUnichar *aStatusArg);
   nsresult FrameChanged(imgIRequest *aRequest,
-                        imgIContainer *aContainer);
+                        imgIContainer *aContainer,
+                        const nsIntRect *aDirtyRect);
   /**
    * Notification that aRequest will now be the current request.
    */
   void NotifyNewCurrentRequest(imgIRequest *aRequest, nsresult aStatus);
 
 private:
   // random helpers
   inline void SpecToURI(const nsAString& aSpec, nsIIOService *aIOService,
@@ -272,51 +283,51 @@ private:
   /**
    * Function to convert a dirty rect in the source image to a dirty
    * rect for the image frame.
    */
   nsRect SourceRectToDest(const nsIntRect & aRect);
 
   nsImageMap*         mImageMap;
 
-  nsCOMPtr<imgINotificationObserver> mListener;
+  nsCOMPtr<imgIDecoderObserver> mListener;
 
   nsSize mComputedSize;
   nsIFrame::IntrinsicSize mIntrinsicSize;
   nsSize mIntrinsicRatio;
 
   bool mDisplayingIcon;
-  bool mFirstFrameComplete;
 
   static nsIIOService* sIOService;
   
   /* loading / broken image icon support */
 
   // XXXbz this should be handled by the prescontext, I think; that
   // way we would have a single iconload per mozilla session instead
   // of one per document...
 
   // LoadIcons: initiate the loading of the static icons used to show
   // loading / broken images
   nsresult LoadIcons(nsPresContext *aPresContext);
   nsresult LoadIcon(const nsAString& aSpec, nsPresContext *aPresContext,
                     imgIRequest **aRequest);
 
   class IconLoad MOZ_FINAL : public nsIObserver,
-                             public imgINotificationObserver {
+                             public imgIDecoderObserver {
     // private class that wraps the data and logic needed for
     // broken image and loading image icons
   public:
     IconLoad();
 
     void Shutdown();
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOBSERVER
-    NS_DECL_IMGINOTIFICATIONOBSERVER
+    NS_DECL_IMGICONTAINEROBSERVER
+    NS_DECL_IMGIDECODEROBSERVER
 
     void AddIconObserver(nsImageFrame *frame) {
         NS_ABORT_IF_FALSE(!mIconObservers.Contains(frame),
                           "Observer shouldn't aleady be in array");
         mIconObservers.AppendElement(frame);
     }
 
     void RemoveIconObserver(nsImageFrame *frame) {
--- a/layout/style/ImageLoader.cpp
+++ b/layout/style/ImageLoader.cpp
@@ -51,18 +51,18 @@ ImageLoader::DropDocumentReference()
 void
 ImageLoader::AssociateRequestToFrame(imgIRequest* aRequest,
                                      nsIFrame* aFrame)
 {
   MOZ_ASSERT(mRequestToFrameMap.IsInitialized() &&
              mFrameToRequestMap.IsInitialized() &&
              mImages.IsInitialized());
 
-  nsCOMPtr<imgINotificationObserver> observer;
-  aRequest->GetNotificationObserver(getter_AddRefs(observer));
+  nsCOMPtr<imgIDecoderObserver> observer;
+  aRequest->GetDecoderObserver(getter_AddRefs(observer));
   if (!observer) {
     // The request has already been canceled, so ignore it.  This is ok because
     // we're not going to get any more notifications from a canceled request.
     return;
   }
 
   MOZ_ASSERT(observer == this);
 
@@ -152,18 +152,18 @@ ImageLoader::DisassociateRequestFromFram
   RequestSet* requestSet = nullptr;
 
   MOZ_ASSERT(mRequestToFrameMap.IsInitialized() &&
              mFrameToRequestMap.IsInitialized() &&
              mImages.IsInitialized());
 
 #ifdef DEBUG
   {
-    nsCOMPtr<imgINotificationObserver> observer;
-    aRequest->GetNotificationObserver(getter_AddRefs(observer));
+    nsCOMPtr<imgIDecoderObserver> observer;
+    aRequest->GetDecoderObserver(getter_AddRefs(observer));
     MOZ_ASSERT(!observer || observer == this);
   }
 #endif
 
   mRequestToFrameMap.Get(aRequest, &frameSet);
   mFrameToRequestMap.Get(aFrame, &requestSet);
 
   if (frameSet) {
@@ -329,58 +329,35 @@ ImageLoader::DoRedraw(FrameSet* aFrameSe
     }
   }
 }
 
 NS_IMPL_ADDREF(ImageLoader)
 NS_IMPL_RELEASE(ImageLoader)
 
 NS_INTERFACE_MAP_BEGIN(ImageLoader)
-  NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
+  NS_INTERFACE_MAP_ENTRY(imgIDecoderObserver)
+  NS_INTERFACE_MAP_ENTRY(imgIContainerObserver)
   NS_INTERFACE_MAP_ENTRY(imgIOnloadBlocker)
 NS_INTERFACE_MAP_END
 
 NS_IMETHODIMP
-ImageLoader::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
-{
-  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
-    nsCOMPtr<imgIContainer> image;
-    aRequest->GetImage(getter_AddRefs(image));
-    return OnStartContainer(aRequest, image);
-  }
-
-  if (aType == imgINotificationObserver::IS_ANIMATED) {
-    return OnImageIsAnimated(aRequest);
-  }
-
-  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
-    return OnStopFrame(aRequest);
-  }
-
-  if (aType == imgINotificationObserver::FRAME_UPDATE) {
-    return FrameChanged(aRequest);
-  }
-
-  return NS_OK;
-}
-
-nsresult
 ImageLoader::OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage)
 { 
   nsPresContext* presContext = GetPresContext();
   if (!presContext) {
     return NS_OK;
   }
 
   aImage->SetAnimationMode(presContext->ImageAnimationMode());
 
   return NS_OK;
 }
 
-nsresult
+NS_IMETHODIMP
 ImageLoader::OnImageIsAnimated(imgIRequest* aRequest)
 {
   if (!mDocument) {
     return NS_OK;
   }
 
   FrameSet* frameSet = nullptr;
   if (!mRequestToFrameMap.Get(aRequest, &frameSet)) {
@@ -394,18 +371,18 @@ ImageLoader::OnImageIsAnimated(imgIReque
     nsLayoutUtils::RegisterImageRequest(presContext,
                                         aRequest,
                                         nullptr);
   }
 
   return NS_OK;
 }
 
-nsresult
-ImageLoader::OnStopFrame(imgIRequest *aRequest)
+NS_IMETHODIMP
+ImageLoader::OnStopFrame(imgIRequest *aRequest, uint32_t aFrame)
 {
   if (!mDocument || mInClone) {
     return NS_OK;
   }
 
   FrameSet* frameSet = nullptr;
   if (!mRequestToFrameMap.Get(aRequest, &frameSet)) {
     return NS_OK;
@@ -413,18 +390,20 @@ ImageLoader::OnStopFrame(imgIRequest *aR
 
   NS_ASSERTION(frameSet, "This should never be null!");
 
   DoRedraw(frameSet);
 
   return NS_OK;
 }
 
-nsresult
-ImageLoader::FrameChanged(imgIRequest *aRequest)
+NS_IMETHODIMP
+ImageLoader::FrameChanged(imgIRequest *aRequest,
+                          imgIContainer *aContainer,
+                          const nsIntRect *aDirtyRect)
 {
   if (!mDocument || mInClone) {
     return NS_OK;
   }
 
   FrameSet* frameSet = nullptr;
   if (!mRequestToFrameMap.Get(aRequest, &frameSet)) {
     return NS_OK;
--- a/layout/style/ImageLoader.h
+++ b/layout/style/ImageLoader.h
@@ -7,29 +7,29 @@
 
 #include "nsAutoPtr.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "nsInterfaceHashtable.h"
 #include "nsCSSValue.h"
 #include "imgIRequest.h"
 #include "imgIOnloadBlocker.h"
-#include "imgINotificationObserver.h"
+#include "nsStubImageDecoderObserver.h"
 #include "mozilla/Attributes.h"
 
 class nsIFrame;
 class nsIDocument;
 class nsPresContext;
 class nsIURI;
 class nsIPrincipal;
 
 namespace mozilla {
 namespace css {
 
-class ImageLoader MOZ_FINAL : public imgINotificationObserver,
+class ImageLoader MOZ_FINAL : public nsStubImageDecoderObserver,
                               public imgIOnloadBlocker {
 public:
   typedef mozilla::css::ImageValue Image;
 
   ImageLoader(nsIDocument* aDocument)
   : mDocument(aDocument),
     mInClone(false)
   {
@@ -37,17 +37,29 @@ public:
 
     mRequestToFrameMap.Init();
     mFrameToRequestMap.Init();
     mImages.Init();
   }
 
   NS_DECL_ISUPPORTS
   NS_DECL_IMGIONLOADBLOCKER
-  NS_DECL_IMGINOTIFICATIONOBSERVER
+
+  // imgIDecoderObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
+  NS_IMETHOD OnStopFrame(imgIRequest *aRequest, uint32_t aFrame);
+  NS_IMETHOD OnImageIsAnimated(imgIRequest *aRequest);
+  // Do not override OnDataAvailable since background images are not
+  // displayed incrementally; they are displayed after the entire image
+  // has been loaded.
+
+  // imgIContainerObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD FrameChanged(imgIRequest* aRequest,
+                          imgIContainer *aContainer,
+                          const nsIntRect *aDirtyRect);
 
   void DropDocumentReference();
 
   void MaybeRegisterCSSImage(Image* aImage);
   void DeregisterCSSImage(Image* aImage);
 
   void AssociateRequestToFrame(imgIRequest* aRequest,
                                nsIFrame* aFrame);
@@ -86,24 +98,16 @@ private:
   nsPresContext* GetPresContext();
 
   void DoRedraw(FrameSet* aFrameSet);
 
   static PLDHashOperator
   SetAnimationModeEnumerator(nsISupports* aKey, FrameSet* aValue,
                              void* aClosure);
 
-  nsresult OnStartContainer(imgIRequest *aRequest, imgIContainer* aImage);
-  nsresult OnStopFrame(imgIRequest *aRequest);
-  nsresult OnImageIsAnimated(imgIRequest *aRequest);
-  nsresult FrameChanged(imgIRequest* aRequest);
-  // Do not override OnDataAvailable since background images are not
-  // displayed incrementally; they are displayed after the entire image
-  // has been loaded.
-
   // A map of imgIRequests to the nsIFrames that are using them.
   RequestToFrameMap mRequestToFrameMap;
 
   // A map of nsIFrames to the imgIRequests they use.
   FrameToRequestMap mFrameToRequestMap;
 
   // A weak pointer to our document. Nulled out by DropDocumentReference.
   nsIDocument* mDocument;
--- a/layout/svg/nsSVGImageFrame.cpp
+++ b/layout/svg/nsSVGImageFrame.cpp
@@ -6,35 +6,44 @@
 // Keep in (case-insensitive) order:
 #include "gfxContext.h"
 #include "gfxMatrix.h"
 #include "gfxPlatform.h"
 #include "imgIContainer.h"
 #include "nsIDOMSVGImageElement.h"
 #include "nsLayoutUtils.h"
 #include "nsRenderingContext.h"
-#include "imgINotificationObserver.h"
+#include "nsStubImageDecoderObserver.h"
 #include "nsSVGEffects.h"
 #include "nsSVGImageElement.h"
 #include "nsSVGPathGeometryFrame.h"
 #include "nsSVGSVGElement.h"
 #include "nsSVGUtils.h"
 #include "SVGContentUtils.h"
 
 using namespace mozilla;
 
 class nsSVGImageFrame;
 
-class nsSVGImageListener MOZ_FINAL : public imgINotificationObserver
+class nsSVGImageListener MOZ_FINAL : public nsStubImageDecoderObserver
 {
 public:
   nsSVGImageListener(nsSVGImageFrame *aFrame);
 
   NS_DECL_ISUPPORTS
-  NS_DECL_IMGINOTIFICATIONOBSERVER
+  // imgIDecoderObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD OnStopDecode(imgIRequest *aRequest, nsresult status,
+                          const PRUnichar *statusArg);
+  // imgIContainerObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD FrameChanged(imgIRequest *aRequest,
+                          imgIContainer *aContainer,
+                          const nsIntRect *aDirtyRect);
+  // imgIContainerObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD OnStartContainer(imgIRequest *aRequest,
+                              imgIContainer *aContainer);
 
   void SetFrame(nsSVGImageFrame *frame) { mFrame = frame; }
 
 private:
   nsSVGImageFrame *mFrame;
 };
 
 typedef nsSVGPathGeometryFrame nsSVGImageFrameBase;
@@ -84,17 +93,17 @@ public:
 
 private:
   gfxMatrix GetRasterImageTransform(int32_t aNativeWidth,
                                     int32_t aNativeHeight,
                                     uint32_t aFor);
   gfxMatrix GetVectorImageTransform(uint32_t aFor);
   bool      TransformContextForPainting(gfxContext* aGfxContext);
 
-  nsCOMPtr<imgINotificationObserver> mListener;
+  nsCOMPtr<imgIDecoderObserver> mListener;
 
   nsCOMPtr<imgIContainer> mImageContainer;
 
   friend class nsSVGImageListener;
 };
 
 //----------------------------------------------------------------------
 // Implementation
@@ -546,40 +555,55 @@ nsSVGImageFrame::GetHitTestFlags()
   }
 
   return flags;
 }
 
 //----------------------------------------------------------------------
 // nsSVGImageListener implementation
 
-NS_IMPL_ISUPPORTS1(nsSVGImageListener, imgINotificationObserver)
+NS_IMPL_ISUPPORTS2(nsSVGImageListener,
+                   imgIDecoderObserver,
+                   imgIContainerObserver)
 
 nsSVGImageListener::nsSVGImageListener(nsSVGImageFrame *aFrame) :  mFrame(aFrame)
 {
 }
 
-NS_IMETHODIMP
-nsSVGImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
+NS_IMETHODIMP nsSVGImageListener::OnStopDecode(imgIRequest *aRequest,
+                                               nsresult status,
+                                               const PRUnichar *statusArg)
+{
+  if (!mFrame)
+    return NS_ERROR_FAILURE;
+
+  nsSVGUtils::InvalidateAndScheduleReflowSVG(mFrame);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsSVGImageListener::FrameChanged(imgIRequest *aRequest,
+                                               imgIContainer *aContainer,
+                                               const nsIntRect *aDirtyRect)
 {
   if (!mFrame)
     return NS_ERROR_FAILURE;
 
-  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
-    nsSVGUtils::InvalidateAndScheduleReflowSVG(mFrame);
-  }
+  // No new dimensions, so we don't need to call
+  // nsSVGUtils::InvalidateAndScheduleBoundsUpdate.
+  nsSVGEffects::InvalidateRenderingObservers(mFrame);
+  nsSVGUtils::InvalidateBounds(mFrame);
+  return NS_OK;
+}
 
-  if (aType == imgINotificationObserver::FRAME_UPDATE) {
-    // No new dimensions, so we don't need to call
-    // nsSVGUtils::InvalidateAndScheduleBoundsUpdate.
-    nsSVGEffects::InvalidateRenderingObservers(mFrame);
-    nsSVGUtils::InvalidateBounds(mFrame);
-  }
+NS_IMETHODIMP nsSVGImageListener::OnStartContainer(imgIRequest *aRequest,
+                                                   imgIContainer *aContainer)
+{
+  // Called once the resource's dimensions have been obtained.
 
-  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
-    // Called once the resource's dimensions have been obtained.
-    aRequest->GetImage(getter_AddRefs(mFrame->mImageContainer));
-    nsSVGUtils::InvalidateAndScheduleReflowSVG(mFrame);
-  }
+  if (!mFrame)
+    return NS_ERROR_FAILURE;
+
+  mFrame->mImageContainer = aContainer;
+  nsSVGUtils::InvalidateAndScheduleReflowSVG(mFrame);
 
   return NS_OK;
 }
 
--- a/layout/xul/base/src/nsImageBoxFrame.cpp
+++ b/layout/xul/base/src/nsImageBoxFrame.cpp
@@ -183,17 +183,17 @@ NS_IMETHODIMP
 nsImageBoxFrame::Init(nsIContent*      aContent,
                       nsIFrame*        aParent,
                       nsIFrame*        aPrevInFlow)
 {
   if (!mListener) {
     nsImageBoxListener *listener = new nsImageBoxListener();
     NS_ADDREF(listener);
     listener->SetFrame(this);
-    listener->QueryInterface(NS_GET_IID(imgINotificationObserver), getter_AddRefs(mListener));
+    listener->QueryInterface(NS_GET_IID(imgIDecoderObserver), getter_AddRefs(mListener));
     NS_RELEASE(listener);
   }
 
   mSuppressStyleCheck = true;
   nsresult rv = nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
   mSuppressStyleCheck = false;
 
   UpdateLoadFlags();
@@ -572,50 +572,19 @@ nsImageBoxFrame::GetType() const
 #ifdef DEBUG
 NS_IMETHODIMP
 nsImageBoxFrame::GetFrameName(nsAString& aResult) const
 {
   return MakeFrameName(NS_LITERAL_STRING("ImageBox"), aResult);
 }
 #endif
 
-nsresult
-nsImageBoxFrame::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
-{
-  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
-    nsCOMPtr<imgIContainer> image;
-    aRequest->GetImage(getter_AddRefs(image));
-    return OnStartContainer(aRequest, image);
-  }
 
-  if (aType == imgINotificationObserver::DECODE_COMPLETE) {
-    return OnStopDecode(aRequest);
-  }
-
-  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
-    uint32_t imgStatus;
-    aRequest->GetImageStatus(&imgStatus);
-    nsresult status =
-        imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
-    return OnStopRequest(aRequest, status);
-  }
-
-  if (aType == imgINotificationObserver::IS_ANIMATED) {
-    return OnImageIsAnimated(aRequest);
-  }
-
-  if (aType == imgINotificationObserver::FRAME_UPDATE) {
-    return FrameChanged(aRequest);
-  }
-
-  return NS_OK;
-}
-
-nsresult nsImageBoxFrame::OnStartContainer(imgIRequest *request,
-                                           imgIContainer *image)
+NS_IMETHODIMP nsImageBoxFrame::OnStartContainer(imgIRequest *request,
+                                                imgIContainer *image)
 {
   NS_ENSURE_ARG_POINTER(image);
 
   // Ensure the animation (if any) is started. Note: There is no
   // corresponding call to Decrement for this. This Increment will be
   // 'cleaned up' by the Request when it is destroyed, but only then.
   request->IncrementAnimationConsumers();
 
@@ -629,71 +598,113 @@ nsresult nsImageBoxFrame::OnStartContain
   if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
     PresContext()->PresShell()->
       FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
   }
 
   return NS_OK;
 }
 
-nsresult nsImageBoxFrame::OnStopDecode(imgIRequest *request)
+NS_IMETHODIMP nsImageBoxFrame::OnStopContainer(imgIRequest *request,
+                                               imgIContainer *image)
 {
   nsBoxLayoutState state(PresContext());
   this->Redraw(state);
 
   return NS_OK;
 }
 
-nsresult nsImageBoxFrame::OnStopRequest(imgIRequest *request,
-                                        nsresult aStatus)
+NS_IMETHODIMP nsImageBoxFrame::OnStopDecode(imgIRequest *request,
+                                            nsresult aStatus,
+                                            const PRUnichar *statusArg)
 {
   if (NS_SUCCEEDED(aStatus))
     // Fire an onload DOM event.
     FireImageDOMEvent(mContent, NS_LOAD);
   else {
     // Fire an onerror DOM event.
     mIntrinsicSize.SizeTo(0, 0);
     PresContext()->PresShell()->
       FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
     FireImageDOMEvent(mContent, NS_LOAD_ERROR);
   }
 
   return NS_OK;
 }
 
-nsresult nsImageBoxFrame::OnImageIsAnimated(imgIRequest *aRequest)
+NS_IMETHODIMP nsImageBoxFrame::OnImageIsAnimated(imgIRequest *aRequest)
 {
   // Register with our refresh driver, if we're animated.
   nsLayoutUtils::RegisterImageRequest(PresContext(), aRequest,
                                       &mRequestRegistered);
 
   return NS_OK;
 }
 
-nsresult nsImageBoxFrame::FrameChanged(imgIRequest *aRequest)
+NS_IMETHODIMP nsImageBoxFrame::FrameChanged(imgIRequest *aRequest,
+                                            imgIContainer *aContainer,
+                                            const nsIntRect *aDirtyRect)
 {
   if ((0 == mRect.width) || (0 == mRect.height)) {
     return NS_OK;
   }
  
   InvalidateLayer(nsDisplayItem::TYPE_XUL_IMAGE);
 
   return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS1(nsImageBoxListener, imgINotificationObserver)
+NS_IMPL_ISUPPORTS2(nsImageBoxListener, imgIDecoderObserver, imgIContainerObserver)
 
 nsImageBoxListener::nsImageBoxListener()
 {
 }
 
 nsImageBoxListener::~nsImageBoxListener()
 {
 }
 
-NS_IMETHODIMP
-nsImageBoxListener::Notify(imgIRequest *request, int32_t aType, const nsIntRect* aData)
+NS_IMETHODIMP nsImageBoxListener::OnStartContainer(imgIRequest *request,
+                                                   imgIContainer *image)
+{
+  if (!mFrame)
+    return NS_OK;
+
+  return mFrame->OnStartContainer(request, image);
+}
+
+NS_IMETHODIMP nsImageBoxListener::OnStopContainer(imgIRequest *request,
+                                                  imgIContainer *image)
 {
   if (!mFrame)
     return NS_OK;
 
-  return mFrame->Notify(request, aType, aData);
+  return mFrame->OnStopContainer(request, image);
+}
+
+NS_IMETHODIMP nsImageBoxListener::OnStopDecode(imgIRequest *request,
+                                               nsresult status,
+                                               const PRUnichar *statusArg)
+{
+  if (!mFrame)
+    return NS_OK;
+
+  return mFrame->OnStopDecode(request, status, statusArg);
 }
+
+NS_IMETHODIMP nsImageBoxListener::OnImageIsAnimated(imgIRequest* aRequest)
+{
+  if (!mFrame)
+    return NS_OK;
+
+  return mFrame->OnImageIsAnimated(aRequest);
+}
+
+NS_IMETHODIMP nsImageBoxListener::FrameChanged(imgIRequest *aRequest,
+                                               imgIContainer *aContainer,
+                                               const nsIntRect *aDirtyRect)
+{
+  if (!mFrame)
+    return NS_ERROR_FAILURE;
+
+  return mFrame->FrameChanged(aRequest, aContainer, aDirtyRect);
+}
+
--- a/layout/xul/base/src/nsImageBoxFrame.h
+++ b/layout/xul/base/src/nsImageBoxFrame.h
@@ -6,30 +6,40 @@
 #define nsImageBoxFrame_h___
 
 #include "mozilla/Attributes.h"
 #include "nsLeafBoxFrame.h"
 
 #include "imgILoader.h"
 #include "imgIRequest.h"
 #include "imgIContainer.h"
-#include "imgINotificationObserver.h"
+#include "nsStubImageDecoderObserver.h"
 
 class nsImageBoxFrame;
 
 class nsDisplayXULImage;
 
-class nsImageBoxListener : public imgINotificationObserver
+class nsImageBoxListener : public nsStubImageDecoderObserver
 {
 public:
   nsImageBoxListener();
   virtual ~nsImageBoxListener();
 
   NS_DECL_ISUPPORTS
-  NS_DECL_IMGINOTIFICATIONOBSERVER
+  // imgIDecoderObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD OnStartContainer(imgIRequest *request, imgIContainer *image);
+  NS_IMETHOD OnStopContainer(imgIRequest *request, imgIContainer *image);
+  NS_IMETHOD OnStopDecode(imgIRequest *request, nsresult status,
+                          const PRUnichar *statusArg);
+  NS_IMETHOD OnImageIsAnimated(imgIRequest* aRequest);
+
+  // imgIContainerObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD FrameChanged(imgIRequest *aRequest,
+                          imgIContainer *aContainer,
+                          const nsIntRect *aDirtyRect);
 
   void SetFrame(nsImageBoxFrame *frame) { mFrame = frame; }
 
 private:
   nsImageBoxFrame *mFrame;
 };
 
 class nsImageBoxFrame : public nsLeafBoxFrame
@@ -38,18 +48,16 @@ public:
   friend class nsDisplayXULImage;
   NS_DECL_FRAMEARENA_HELPERS
 
   virtual nsSize GetPrefSize(nsBoxLayoutState& aBoxLayoutState);
   virtual nsSize GetMinSize(nsBoxLayoutState& aBoxLayoutState);
   virtual nscoord GetBoxAscent(nsBoxLayoutState& aBoxLayoutState) MOZ_OVERRIDE;
   virtual void MarkIntrinsicWidthsDirty() MOZ_OVERRIDE;
 
-  nsresult Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData);
-
   friend nsIFrame* NS_NewImageBoxFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 
   NS_IMETHOD  Init(nsIContent*      aContent,
                    nsIFrame*        aParent,
                    nsIFrame*        asPrevInFlow) MOZ_OVERRIDE;
 
   NS_IMETHOD AttributeChanged(int32_t aNameSpaceID,
                               nsIAtom* aAttribute,
@@ -76,45 +84,51 @@ public:
    * image using the new load flags.
    */
   void UpdateLoadFlags();
 
   NS_IMETHOD BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                               const nsRect&           aDirtyRect,
                               const nsDisplayListSet& aLists) MOZ_OVERRIDE;
 
+  NS_IMETHOD OnStartContainer(imgIRequest *request, imgIContainer *image);
+  NS_IMETHOD OnStopContainer(imgIRequest *request, imgIContainer *image);
+  NS_IMETHOD OnStopDecode(imgIRequest *request,
+                          nsresult status,
+                          const PRUnichar *statusArg);
+  NS_IMETHOD OnImageIsAnimated(imgIRequest* aRequest);
+
+  NS_IMETHOD FrameChanged(imgIRequest *aRequest,
+                          imgIContainer *aContainer,
+                          const nsIntRect *aDirtyRect);
+
   virtual ~nsImageBoxFrame();
 
   void  PaintImage(nsRenderingContext& aRenderingContext,
                    const nsRect& aDirtyRect,
                    nsPoint aPt, uint32_t aFlags);
 
   already_AddRefed<mozilla::layers::ImageContainer> GetContainer();
 protected:
   nsImageBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext);
 
   virtual void GetImageSize();
 
 private:
-  nsresult OnStartContainer(imgIRequest *request, imgIContainer *image);
-  nsresult OnStopDecode(imgIRequest *request);
-  nsresult OnStopRequest(imgIRequest *request, nsresult status);
-  nsresult OnImageIsAnimated(imgIRequest* aRequest);
-  nsresult FrameChanged(imgIRequest *aRequest);
 
   nsRect mSubRect; ///< If set, indicates that only the portion of the image specified by the rect should be used.
   nsSize mIntrinsicSize;
   nsSize mImageSize;
 
   // Boolean variable to determine if the current image request has been
   // registered with the refresh driver.
   bool mRequestRegistered;
 
   nsCOMPtr<imgIRequest> mImageRequest;
-  nsCOMPtr<imgINotificationObserver> mListener;
+  nsCOMPtr<imgIDecoderObserver> mListener;
 
   int32_t mLoadFlags;
 
   bool mUseSrcAttr; ///< Whether or not the image src comes from an attribute.
   bool mSuppressStyleCheck;
 }; // class nsImageBoxFrame
 
 class nsDisplayXULImage : public nsDisplayImageContainer {
--- a/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
+++ b/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
@@ -2112,18 +2112,18 @@ nsTreeBodyFrame::GetImage(int32_t aRowIn
     bool animated = true; // Assuming animated is the safe option
 
     // We can only call GetAnimated if we're decoded
     if (*aResult && (status & imgIRequest::STATUS_DECODE_COMPLETE))
       (*aResult)->GetAnimated(&animated);
 
     if ((!(status & imgIRequest::STATUS_LOAD_COMPLETE)) || animated) {
       // We either aren't done loading, or we're animating. Add our row as a listener for invalidations.
-      nsCOMPtr<imgINotificationObserver> obs;
-      imgReq->GetNotificationObserver(getter_AddRefs(obs));
+      nsCOMPtr<imgIDecoderObserver> obs;
+      imgReq->GetDecoderObserver(getter_AddRefs(obs));
 
       if (obs) {
         static_cast<nsTreeImageListener*> (obs.get())->AddCell(aRowIndex, aCol);
       }
 
       return NS_OK;
     }
   }
@@ -2135,21 +2135,21 @@ nsTreeBodyFrame::GetImage(int32_t aRowIn
     if (!listener)
       return NS_ERROR_OUT_OF_MEMORY;
 
     if (!mCreatedListeners.PutEntry(listener)) {
       return NS_ERROR_FAILURE;
     }
 
     listener->AddCell(aRowIndex, aCol);
-    nsCOMPtr<imgINotificationObserver> imgNotificationObserver = listener;
+    nsCOMPtr<imgIDecoderObserver> imgDecoderObserver = listener;
 
     nsCOMPtr<imgIRequest> imageRequest;
     if (styleRequest) {
-      styleRequest->Clone(imgNotificationObserver, getter_AddRefs(imageRequest));
+      styleRequest->Clone(imgDecoderObserver, getter_AddRefs(imageRequest));
     } else {
       nsIDocument* doc = mContent->GetDocument();
       if (!doc)
         // The page is currently being torn down.  Why bother.
         return NS_ERROR_FAILURE;
 
       nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI();
 
@@ -2164,17 +2164,17 @@ nsTreeBodyFrame::GetImage(int32_t aRowIn
       // XXXbz what's the origin principal for this stuff that comes from our
       // view?  I guess we should assume that it's the node's principal...
       if (nsContentUtils::CanLoadImage(srcURI, mContent, doc,
                                        mContent->NodePrincipal())) {
         nsresult rv = nsContentUtils::LoadImage(srcURI,
                                                 doc,
                                                 mContent->NodePrincipal(),
                                                 doc->GetDocumentURI(),
-                                                imgNotificationObserver,
+                                                imgDecoderObserver,
                                                 nsIRequest::LOAD_NORMAL,
                                                 getter_AddRefs(imageRequest));
         NS_ENSURE_SUCCESS(rv, rv);
                                   
       }
     }
     listener->UnsuppressInvalidation();
 
@@ -2182,17 +2182,17 @@ nsTreeBodyFrame::GetImage(int32_t aRowIn
       return NS_ERROR_FAILURE;
 
     // We don't want discarding/decode-on-draw for xul images
     imageRequest->StartDecoding();
     imageRequest->LockImage();
 
     // In a case it was already cached.
     imageRequest->GetImage(aResult);
-    nsTreeImageCacheEntry cacheEntry(imageRequest, imgNotificationObserver);
+    nsTreeImageCacheEntry cacheEntry(imageRequest, imgDecoderObserver);
     mImageCache.Put(imageSrc, cacheEntry);
   }
   return NS_OK;
 }
 
 nsRect nsTreeBodyFrame::GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
                                      nsStyleContext* aStyleContext)
 {
--- a/layout/xul/base/src/tree/src/nsTreeBodyFrame.h
+++ b/layout/xul/base/src/tree/src/nsTreeBodyFrame.h
@@ -15,33 +15,33 @@
 #include "nsITimer.h"
 #include "nsIReflowCallback.h"
 #include "nsTArray.h"
 #include "nsTreeStyleCache.h"
 #include "nsTreeColumns.h"
 #include "nsAutoPtr.h"
 #include "nsDataHashtable.h"
 #include "imgIRequest.h"
-#include "imgINotificationObserver.h"
+#include "imgIDecoderObserver.h"
 #include "nsScrollbarFrame.h"
 #include "nsThreadUtils.h"
 #include "mozilla/LookAndFeel.h"
 
 class nsOverflowChecker;
 class nsTreeImageListener;
 
 // An entry in the tree's image cache
 struct nsTreeImageCacheEntry
 {
   nsTreeImageCacheEntry() {}
-  nsTreeImageCacheEntry(imgIRequest *aRequest, imgINotificationObserver *aListener)
+  nsTreeImageCacheEntry(imgIRequest *aRequest, imgIDecoderObserver *aListener)
     : request(aRequest), listener(aListener) {}
 
   nsCOMPtr<imgIRequest> request;
-  nsCOMPtr<imgINotificationObserver> listener;
+  nsCOMPtr<imgIDecoderObserver> listener;
 };
 
 // The actual frame that paints the cells and rows.
 class nsTreeBodyFrame MOZ_FINAL
   : public nsLeafBoxFrame
   , public nsICSSPseudoComparator
   , public nsIScrollbarMediator
   , public nsIReflowCallback
--- a/layout/xul/base/src/tree/src/nsTreeImageListener.cpp
+++ b/layout/xul/base/src/tree/src/nsTreeImageListener.cpp
@@ -3,51 +3,67 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsTreeImageListener.h"
 #include "nsITreeBoxObject.h"
 #include "imgIRequest.h"
 #include "imgIContainer.h"
 
-NS_IMPL_ISUPPORTS1(nsTreeImageListener, imgINotificationObserver)
+NS_IMPL_ISUPPORTS2(nsTreeImageListener, imgIDecoderObserver, imgIContainerObserver)
 
 nsTreeImageListener::nsTreeImageListener(nsTreeBodyFrame* aTreeFrame)
   : mTreeFrame(aTreeFrame),
     mInvalidationSuppressed(true),
     mInvalidationArea(nullptr)
 {
 }
 
 nsTreeImageListener::~nsTreeImageListener()
 {
   delete mInvalidationArea;
 }
 
 NS_IMETHODIMP
-nsTreeImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
+nsTreeImageListener::OnImageIsAnimated(imgIRequest *aRequest)
 {
-  if (aType == imgINotificationObserver::IS_ANIMATED) {
-    return mTreeFrame ? mTreeFrame->OnImageIsAnimated(aRequest) : NS_OK;
+  if (!mTreeFrame) {
+    return NS_OK;
   }
 
-  if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
-    // Ensure the animation (if any) is started. Note: There is no
-    // corresponding call to Decrement for this. This Increment will be
-    // 'cleaned up' by the Request when it is destroyed, but only then.
-    aRequest->IncrementAnimationConsumers();
-  }
+  return mTreeFrame->OnImageIsAnimated(aRequest);
+}
 
-  if (aType == imgINotificationObserver::FRAME_UPDATE) {
-    Invalidate();
-  }
-
+NS_IMETHODIMP nsTreeImageListener::OnStartContainer(imgIRequest *aRequest,
+                                                    imgIContainer *aImage)
+{
+  // Ensure the animation (if any) is started. Note: There is no
+  // corresponding call to Decrement for this. This Increment will be
+  // 'cleaned up' by the Request when it is destroyed, but only then.
+  aRequest->IncrementAnimationConsumers();
   return NS_OK;
 }
 
+NS_IMETHODIMP nsTreeImageListener::OnDataAvailable(imgIRequest *aRequest,
+                                                   bool aCurrentFrame,
+                                                   const nsIntRect *aRect)
+{
+  Invalidate();
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsTreeImageListener::FrameChanged(imgIRequest *aRequest,
+                                                imgIContainer *aContainer,
+                                                const nsIntRect *aDirtyRect)
+{
+  Invalidate();
+  return NS_OK;
+}
+
+
 void
 nsTreeImageListener::AddCell(int32_t aIndex, nsITreeColumn* aCol)
 {
   if (!mInvalidationArea) {
     mInvalidationArea = new InvalidationArea(aCol);
     mInvalidationArea->AddRow(aIndex);
   }
   else {
--- a/layout/xul/base/src/tree/src/nsTreeImageListener.h
+++ b/layout/xul/base/src/tree/src/nsTreeImageListener.h
@@ -4,28 +4,37 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsTreeImageListener_h__
 #define nsTreeImageListener_h__
 
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsITreeColumns.h"
+#include "nsStubImageDecoderObserver.h"
 #include "nsTreeBodyFrame.h"
 #include "mozilla/Attributes.h"
 
 // This class handles image load observation.
-class nsTreeImageListener MOZ_FINAL : public imgINotificationObserver
+class nsTreeImageListener MOZ_FINAL : public nsStubImageDecoderObserver
 {
 public:
   nsTreeImageListener(nsTreeBodyFrame *aTreeFrame);
   ~nsTreeImageListener();
 
   NS_DECL_ISUPPORTS
-  NS_DECL_IMGINOTIFICATIONOBSERVER
+  // imgIDecoderObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
+  NS_IMETHOD OnImageIsAnimated(imgIRequest* aRequest);
+  NS_IMETHOD OnDataAvailable(imgIRequest *aRequest, bool aCurrentFrame,
+                             const nsIntRect *aRect);
+  // imgIContainerObserver (override nsStubImageDecoderObserver)
+  NS_IMETHOD FrameChanged(imgIRequest *aRequest,
+                          imgIContainer *aContainer,
+                          const nsIntRect *aDirtyRect);
 
   NS_IMETHOD ClearFrame();
 
   friend class nsTreeBodyFrame;
 
 protected:
   void UnsuppressInvalidation() { mInvalidationSuppressed = false; }
   void Invalidate();
--- a/toolkit/system/gnome/nsAlertsIconListener.cpp
+++ b/toolkit/system/gnome/nsAlertsIconListener.cpp
@@ -46,18 +46,18 @@ static void notify_closed_marshal(GClosu
   NS_ABORT_IF_FALSE(n_param_values >= 1, "No object in params");
 
   nsAlertsIconListener* alert =
     static_cast<nsAlertsIconListener*>(closure->data);
   alert->SendClosed();
   NS_RELEASE(alert);
 }
 
-NS_IMPL_ISUPPORTS3(nsAlertsIconListener, imgINotificationObserver,
-                   nsIObserver, nsISupportsWeakReference)
+NS_IMPL_ISUPPORTS4(nsAlertsIconListener, imgIContainerObserver,
+                   imgIDecoderObserver, nsIObserver, nsISupportsWeakReference)
 
 nsAlertsIconListener::nsAlertsIconListener()
 : mLoadedFrame(false),
   mNotification(NULL)
 {
   if (!libNotifyHandle && !libNotifyNotAvail) {
     libNotifyHandle = dlopen("libnotify.so.4", RTLD_LAZY);
     if (!libNotifyHandle) {
@@ -85,49 +85,110 @@ nsAlertsIconListener::nsAlertsIconListen
 nsAlertsIconListener::~nsAlertsIconListener()
 {
   if (mIconRequest)
     mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
   // Don't dlclose libnotify as it uses atexit().
 }
 
 NS_IMETHODIMP
-nsAlertsIconListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
+nsAlertsIconListener::OnStartRequest(imgIRequest* aRequest)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnStartDecode(imgIRequest* aRequest)
 {
-  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
-    return OnStopRequest(aRequest);
-  }
+  return NS_OK;
+}
+
 
-  if (aType == imgINotificationObserver::FRAME_COMPLETE) {
-    return OnStopFrame(aRequest);
-  }
+NS_IMETHODIMP
+nsAlertsIconListener::OnStartContainer(imgIRequest* aRequest,
+                                       imgIContainer* aContainer)
+{
+  return NS_OK;
+}
 
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnStartFrame(imgIRequest* aRequest,
+                                   uint32_t aFrame)
+{
   return NS_OK;
 }
 
-nsresult
-nsAlertsIconListener::OnStopRequest(imgIRequest* aRequest)
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnDataAvailable(imgIRequest* aRequest,
+                                      bool aCurrentFrame,
+                                      const nsIntRect* aRect)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnStopContainer(imgIRequest* aRequest,
+                                      imgIContainer* aContainer)
+{
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnStopDecode(imgIRequest* aRequest,
+                                   nsresult status,
+                                   const PRUnichar* statusArg)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::FrameChanged(imgIRequest* aRequest, 
+                                   imgIContainer* aContainer,
+                                   const nsIntRect* aDirtyRect)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnStopRequest(imgIRequest* aRequest,
+                                    bool aIsLastPart)
 {
   uint32_t imgStatus = imgIRequest::STATUS_ERROR;
   nsresult rv = aRequest->GetImageStatus(&imgStatus);
   NS_ENSURE_SUCCESS(rv, rv);
   if (imgStatus == imgIRequest::STATUS_ERROR && !mLoadedFrame) {
     // We have an error getting the image. Display the notification with no icon.
     ShowAlert(NULL);
   }
 
   if (mIconRequest) {
     mIconRequest->Cancel(NS_BINDING_ABORTED);
     mIconRequest = nullptr;
   }
   return NS_OK;
 }
 
-nsresult
-nsAlertsIconListener::OnStopFrame(imgIRequest* aRequest)
+NS_IMETHODIMP
+nsAlertsIconListener::OnDiscard(imgIRequest *aRequest)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnImageIsAnimated(imgIRequest *aRequest)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnStopFrame(imgIRequest* aRequest,
+                                  uint32_t aFrame)
 {
   if (aRequest != mIconRequest)
     return NS_ERROR_FAILURE;
 
   if (mLoadedFrame)
     return NS_OK; // only use one frame
 
   nsCOMPtr<imgIContainer> image;
--- a/toolkit/system/gnome/nsAlertsIconListener.h
+++ b/toolkit/system/gnome/nsAlertsIconListener.h
@@ -2,53 +2,51 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsAlertsIconListener_h__
 #define nsAlertsIconListener_h__
 
 #include "nsCOMPtr.h"
-#include "imgINotificationObserver.h"
+#include "imgIDecoderObserver.h"
 #include "nsStringAPI.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 
 #include <gdk-pixbuf/gdk-pixbuf.h>
 
 class imgIRequest;
 
 struct NotifyNotification;
 
-class nsAlertsIconListener : public imgINotificationObserver,
+class nsAlertsIconListener : public imgIDecoderObserver,
                              public nsIObserver,
                              public nsSupportsWeakReference
 {
 public:
   NS_DECL_ISUPPORTS
-  NS_DECL_IMGINOTIFICATIONOBSERVER
+  NS_DECL_IMGICONTAINEROBSERVER
+  NS_DECL_IMGIDECODEROBSERVER
   NS_DECL_NSIOBSERVER
 
   nsAlertsIconListener();
   virtual ~nsAlertsIconListener();
 
   nsresult InitAlertAsync(const nsAString & aImageUrl,
                           const nsAString & aAlertTitle, 
                           const nsAString & aAlertText,
                           bool aAlertTextClickable,
                           const nsAString & aAlertCookie,
                           nsIObserver * aAlertListener);
 
   void SendCallback();
   void SendClosed();
 
 protected:
-  nsresult OnStopRequest(imgIRequest* aRequest);
-  nsresult OnStopFrame(imgIRequest* aRequest);
-
   /**
    * The only difference between libnotify.so.4 and libnotify.so.1 for these symbols
    * is that notify_notification_new takes three arguments in libnotify.so.4 and
    * four in libnotify.so.1.
    * Passing the fourth argument as NULL is binary compatible.
    */
   typedef void (*NotifyActionCallback)(NotifyNotification*, char*, gpointer);
   typedef bool (*notify_is_initted_t)(void);
--- a/widget/cocoa/nsMenuItemIconX.h
+++ b/widget/cocoa/nsMenuItemIconX.h
@@ -6,37 +6,38 @@
 /*
  * Retrieves and displays icons in native menu items on Mac OS X.
  */
 
 #ifndef nsMenuItemIconX_h_
 #define nsMenuItemIconX_h_
 
 #include "nsCOMPtr.h"
-#include "imgINotificationObserver.h"
+#include "imgIDecoderObserver.h"
 
 class nsIURI;
 class nsIContent;
 class imgIRequest;
 class nsMenuObjectX;
 
 #import <Cocoa/Cocoa.h>
 
-class nsMenuItemIconX : public imgINotificationObserver
+class nsMenuItemIconX : public imgIDecoderObserver
 {
 public:
   nsMenuItemIconX(nsMenuObjectX* aMenuItem,
                   nsIContent*    aContent,
                   NSMenuItem*    aNativeMenuItem);
 private:
   virtual ~nsMenuItemIconX();
 
 public:
   NS_DECL_ISUPPORTS
-  NS_DECL_IMGINOTIFICATIONOBSERVER
+  NS_DECL_IMGICONTAINEROBSERVER
+  NS_DECL_IMGIDECODEROBSERVER
 
   // SetupIcon succeeds if it was able to set up the icon, or if there should
   // be no icon, in which case it clears any existing icon but still succeeds.
   nsresult SetupIcon();
 
   // GetIconURI fails if the item should not have any icon.
   nsresult GetIconURI(nsIURI** aIconURI);
 
@@ -46,18 +47,16 @@ public:
 
   // Unless we take precautions, we may outlive the object that created us
   // (mMenuObject, which owns our native menu item (mNativeMenuItem)).
   // Destroy() should be called from mMenuObject's destructor to prevent
   // this from happening.  See bug 499600.
   void Destroy();
 
 protected:
-  nsresult OnStopFrame(imgIRequest* aRequest);
-
   nsCOMPtr<nsIContent>  mContent;
   nsCOMPtr<imgIRequest> mIconRequest;
   nsMenuObjectX*        mMenuObject; // [weak]
   nsIntRect             mImageRegionRect;
   bool                  mLoadedIcon;
   bool                  mSetIcon;
   NSMenuItem*           mNativeMenuItem; // [weak]
 };
--- a/widget/cocoa/nsMenuItemIconX.mm
+++ b/widget/cocoa/nsMenuItemIconX.mm
@@ -37,17 +37,17 @@ static const uint32_t kIconComponents = 
 static const uint32_t kIconBitsPerPixel = kIconBitsPerComponent *
                                           kIconComponents;
 static const uint32_t kIconBytesPerRow = kIconWidth * kIconBitsPerPixel / 8;
 static const uint32_t kIconBytes = kIconBytesPerRow * kIconHeight;
 
 typedef NS_STDCALL_FUNCPROTO(nsresult, GetRectSideMethod, nsIDOMRect,
                              GetBottom, (nsIDOMCSSPrimitiveValue**));
 
-NS_IMPL_ISUPPORTS1(nsMenuItemIconX, imgINotificationObserver)
+NS_IMPL_ISUPPORTS2(nsMenuItemIconX, imgIContainerObserver, imgIDecoderObserver)
 
 nsMenuItemIconX::nsMenuItemIconX(nsMenuObjectX* aMenuItem,
                                  nsIContent*    aContent,
                                  NSMenuItem*    aNativeMenuItem)
 : mContent(aContent)
 , mMenuObject(aMenuItem)
 , mLoadedIcon(false)
 , mSetIcon(false)
@@ -313,38 +313,67 @@ nsMenuItemIconX::LoadIcon(nsIURI* aIconU
   mIconRequest->StartDecoding();
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 //
-// imgINotificationObserver
+// imgIContainerObserver
+//
+
+NS_IMETHODIMP
+nsMenuItemIconX::FrameChanged(imgIRequest* aRequest,
+                              imgIContainer*   aContainer,
+                              const nsIntRect* aDirtyRect)
+{
+  return NS_OK;
+}
+
+//
+// imgIDecoderObserver
 //
 
 NS_IMETHODIMP
-nsMenuItemIconX::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
+nsMenuItemIconX::OnStartRequest(imgIRequest* aRequest)
 {
-  if (aType == imgINotificationObserver::FRAME_COMPLETE) {
-    return OnStopFrame(aRequest);
-  }
+  return NS_OK;
+}
 
-  if (aType == imgINotificationObserver::LOAD_COMPLETE) {
-    if (mIconRequest && mIconRequest == aRequest) {
-      mIconRequest->Cancel(NS_BINDING_ABORTED);
-      mIconRequest = nullptr;
-    }
-  }
+NS_IMETHODIMP
+nsMenuItemIconX::OnStartDecode(imgIRequest* aRequest)
+{
+  return NS_OK;
+}
 
+NS_IMETHODIMP
+nsMenuItemIconX::OnStartContainer(imgIRequest*   aRequest,
+                                  imgIContainer* aContainer)
+{
   return NS_OK;
 }
 
-nsresult
-nsMenuItemIconX::OnStopFrame(imgIRequest*    aRequest)
+NS_IMETHODIMP
+nsMenuItemIconX::OnStartFrame(imgIRequest* aRequest, uint32_t aFrame)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMenuItemIconX::OnDataAvailable(imgIRequest*     aRequest,
+                                 bool             aCurrentFrame,
+                                 const nsIntRect* aRect)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMenuItemIconX::OnStopFrame(imgIRequest*    aRequest,
+                             uint32_t        aFrame)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   if (aRequest != mIconRequest)
     return NS_ERROR_FAILURE;
 
   // Only support one frame.
   if (mLoadedIcon)
@@ -457,8 +486,46 @@ nsMenuItemIconX::OnStopFrame(imgIRequest
 
   mLoadedIcon = true;
   mSetIcon = true;
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
+
+NS_IMETHODIMP
+nsMenuItemIconX::OnStopContainer(imgIRequest*   aRequest,
+                                imgIContainer* aContainer)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMenuItemIconX::OnStopDecode(imgIRequest*     aRequest,
+                             nsresult         status,
+                             const PRUnichar* statusArg)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMenuItemIconX::OnStopRequest(imgIRequest* aRequest,
+                              bool         aIsLastPart)
+{
+  if (mIconRequest && mIconRequest == aRequest) {
+    mIconRequest->Cancel(NS_BINDING_ABORTED);
+    mIconRequest = nullptr;
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMenuItemIconX::OnDiscard(imgIRequest* aRequest)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMenuItemIconX::OnImageIsAnimated(imgIRequest* aRequest)
+{
+  return NS_OK;
+}