Back out bug 585385 for build bustage.
authorJosh Matthews <josh@joshmatthews.net>
Thu, 11 Oct 2012 21:58:24 -0400
changeset 110169 2adc0ce03dba4ba6548c1a0673a4c9e4d18b473a
parent 110163 d35c748b03b0f222767442747c7eab9fa911e9e4
child 110170 ea7bf4553d66eb343c0f6e9a7cc071d1eb54c40c
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
bugs585385
milestone19.0a1
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;
+}