Bug 505385 - Part 11: Create a new imgINotificationObserver interface to replace all uses of imgIContainerObserver and imgIDecoderObserver outside of image/. r=joe
authorJosh Matthews <josh@joshmatthews.net>
Thu, 11 Oct 2012 21:34:23 -0400
changeset 110155 307acd23def9b82406f153ad8a5690aaa55963cb
parent 110154 0645314aa7c2c327c42adad5ec2e5cfea93a1b3d
child 110156 2d223dd76a6270141031cb467a81da8d56c27e9e
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersjoe
bugs505385
milestone19.0a1
Bug 505385 - Part 11: Create a new imgINotificationObserver interface to replace all uses of imgIContainerObserver and imgIDecoderObserver outside of image/. r=joe
content/base/public/nsContentUtils.h
content/base/public/nsIImageLoadingContent.idl
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/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/public/Makefile.in
image/public/imgILoader.idl
image/public/imgINotificationObserver.idl
image/public/imgIRequest.idl
image/public/imgIScriptedNotificationObserver.idl
image/public/imgITools.idl
image/src/Makefile.in
image/src/ScriptedNotificationObserver.cpp
image/src/ScriptedNotificationObserver.h
image/src/imgLoader.cpp
image/src/imgLoader.h
image/src/imgRequestProxy.cpp
image/src/imgRequestProxy.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 imgIDecoderObserver;
+class imgINotificationObserver;
 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,
-                            imgIDecoderObserver* aObserver,
+                            imgINotificationObserver* 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 "imgIDecoderObserver.idl"
+#include "imgINotificationObserver.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, uuid(4bf1a7c5-6edb-4191-a257-e31a90f6aa85)]
-interface nsIImageLoadingContent : imgIDecoderObserver
+[scriptable, builtinclass, uuid(497bfb9b-d996-4d1e-a647-8137b0cfc876)]
+interface nsIImageLoadingContent : imgINotificationObserver
 {
   /**
    * 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 : imgID
    * 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 imgIDecoderObserver aObserver);
+  void addObserver(in imgINotificationObserver aObserver);
 
   /**
    * Used to unregister an image decoder observer.
    *
    * @param aObserver the observer to unregister
    */
-  void removeObserver(in imgIDecoderObserver aObserver);
+  void removeObserver(in imgINotificationObserver 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/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 "imgIDecoderObserver.h"
+#include "imgINotificationObserver.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,
-                          imgIDecoderObserver* aObserver, int32_t aLoadFlags,
+                          imgINotificationObserver* 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,            /* imgIDecoderObserver */
+                              aObserver,            /* imgINotificationObserver */
                               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,21 +43,20 @@ public:
   
 private:
   virtual ~nsGenConImageContent();
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
 };
 
-NS_IMPL_ISUPPORTS_INHERITED4(nsGenConImageContent,
+NS_IMPL_ISUPPORTS_INHERITED3(nsGenConImageContent,
                              nsXMLElement,
                              nsIImageLoadingContent,
-                             imgIContainerObserver,
-                             imgIDecoderObserver,
+                             imgINotificationObserver,
                              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,122 +117,66 @@ nsImageLoadingContent::~nsImageLoadingCo
          observer = next) {                                              \
       next = observer->mNext;                                            \
       if (observer->mObserver) {                                         \
         observer->mObserver->func_;                                      \
       }                                                                  \
     }                                                                    \
   PR_END_MACRO
 
-
 /*
- * imgIContainerObserver impl
- */
-NS_IMETHODIMP
-nsImageLoadingContent::FrameChanged(imgIRequest* aRequest,
-                                    imgIContainer* aContainer,
-                                    const nsIntRect* aDirtyRect)
-{
-  LOOP_OVER_OBSERVERS(FrameChanged(aRequest, aContainer, aDirtyRect));
-  return NS_OK;
-}
-            
-/*
- * imgIDecoderObserver impl
+ * imgINotificationObserver impl
  */
 NS_IMETHODIMP
-nsImageLoadingContent::OnStartRequest(imgIRequest* aRequest)
-{
-  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
-
-  LOOP_OVER_OBSERVERS(OnStartRequest(aRequest));
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsImageLoadingContent::OnStartDecode(imgIRequest* aRequest)
+nsImageLoadingContent::Notify(imgIRequest* aRequest,
+                              int32_t aType,
+                              const nsIntRect* aData)
 {
-  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+  if (aType == imgINotificationObserver::IS_ANIMATED) {
+    return OnImageIsAnimated(aRequest);
+  }
 
-  LOOP_OVER_OBSERVERS(OnStartDecode(aRequest));
-  return NS_OK;
-}
+  if (aType == imgINotificationObserver::STOP_DECODE) {
+    // We should definitely have a request here
+    NS_ABORT_IF_FALSE(aRequest, "no request?");
 
-NS_IMETHODIMP
-nsImageLoadingContent::OnStartContainer(imgIRequest* aRequest,
-                                        imgIContainer* aContainer)
-{
+    NS_PRECONDITION(aRequest == mCurrentRequest || aRequest == mPendingRequest,
+                    "Unknown request");
+  }
+
   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;    
-}
+  if (aType != imgINotificationObserver::FRAME_CHANGED) {
+    LOOP_OVER_OBSERVERS(Notify(aRequest, aType, aData));
+  }
 
-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;
-}
+  if (aType == imgINotificationObserver::START_CONTAINER) {
+    // Have to check for state changes here, since we might have been in
+    // the LOADING state before.
+    UpdateImageState(true);
+  }
 
-NS_IMETHODIMP
-nsImageLoadingContent::OnStopFrame(imgIRequest* aRequest,
-                                   uint32_t aFrame)
-{
-  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+  if (aType == imgINotificationObserver::STOP_DECODE) {
+    uint32_t reqStatus;
+    aRequest->GetImageStatus(&reqStatus);
+    nsresult status =
+        reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+    return OnStopDecode(aRequest, status);
+  }
 
-  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
+nsresult
 nsImageLoadingContent::OnStopDecode(imgIRequest* aRequest,
-                                    nsresult aStatus,
-                                    const PRUnichar* aStatusArg)
+                                    nsresult aStatus)
 {
-  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) {
@@ -279,48 +223,28 @@ nsImageLoadingContent::OnStopDecode(imgI
   }
 
   nsCOMPtr<nsINode> thisNode = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   nsSVGEffects::InvalidateDirectRenderingObservers(thisNode->AsElement());
 
   return NS_OK;
 }
 
-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
+nsresult
 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);
@@ -346,17 +270,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(imgIDecoderObserver* aObserver)
+nsImageLoadingContent::AddObserver(imgINotificationObserver* 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!
@@ -374,17 +298,17 @@ nsImageLoadingContent::AddObserver(imgID
   if (! observer->mNext) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsImageLoadingContent::RemoveObserver(imgIDecoderObserver* aObserver)
+nsImageLoadingContent::RemoveObserver(imgINotificationObserver* 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,18 +8,17 @@
  * 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 "imgIContainerObserver.h"
-#include "imgIDecoderObserver.h"
+#include "imgINotificationObserver.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"
 
@@ -31,18 +30,17 @@ class nsIIOService;
 class nsImageLoadingContent : public nsIImageLoadingContent,
                               public imgIOnloadBlocker
 {
   /* METHODS */
 public:
   nsImageLoadingContent();
   virtual ~nsImageLoadingContent();
 
-  NS_DECL_IMGICONTAINEROBSERVER
-  NS_DECL_IMGIDECODEROBSERVER
+  NS_DECL_IMGINOTIFICATIONOBSERVER
   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
@@ -156,34 +154,37 @@ 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 OnStopDecode(imgIRequest* aRequest, nsresult aStatus);
+  nsresult OnImageIsAnimated(imgIRequest *aRequest);
+
 private:
   /**
    * Struct used to manage the image observers.
    */
   struct ImageObserver {
-    ImageObserver(imgIDecoderObserver* aObserver) :
+    ImageObserver(imgINotificationObserver* aObserver) :
       mObserver(aObserver),
       mNext(nullptr)
     {
       MOZ_COUNT_CTOR(ImageObserver);
     }
     ~ImageObserver()
     {
       MOZ_COUNT_DTOR(ImageObserver);
       NS_CONTENT_DELETE_LIST_MEMBER(ImageObserver, this, mNext);
     }
 
-    nsCOMPtr<imgIDecoderObserver> mObserver;
+    nsCOMPtr<imgINotificationObserver> 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,23 +93,16 @@ 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; }
 
--- 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 "imgIDecoderObserver.h"
+#include "imgINotificationObserver.h"
 
 #include "nsILoadGroup.h"
 
 #include "nsRuleData.h"
 
 #include "nsIJSContextStack.h"
 #include "nsIDOMHTMLMapElement.h"
 #include "nsEventDispatcher.h"
@@ -84,23 +84,22 @@ 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_TABLE6(nsHTMLImageElement,
+  NS_HTML_CONTENT_INTERFACE_TABLE5(nsHTMLImageElement,
                                    nsIDOMHTMLImageElement,
                                    nsIJSNativeInitializer,
-                                   imgIDecoderObserver,
                                    nsIImageLoadingContent,
-                                   imgIContainerObserver,
-                                   imgIOnloadBlocker)
+                                   imgIOnloadBlocker,
+                                   imgINotificationObserver)
   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,23 +637,22 @@ 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_TABLE9(nsHTMLInputElement,
+  NS_HTML_CONTENT_INTERFACE_TABLE8(nsHTMLInputElement,
                                    nsIDOMHTMLInputElement,
                                    nsITextControlElement,
                                    nsIPhonetic,
-                                   imgIDecoderObserver,
+                                   imgINotificationObserver,
                                    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,24 +180,23 @@ 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, imgIDecoderObserver)
+    NS_INTERFACE_TABLE_ENTRY(nsHTMLObjectElement, imgINotificationObserver)
     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,19 +229,18 @@ 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, imgIDecoderObserver)
+    NS_INTERFACE_TABLE_ENTRY(nsHTMLSharedObjectElement, imgINotificationObserver)
     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 = [
-    "imgIDecoderObserver", "nsIRequestObserver", "nsIStreamListener",
+    "imgINotificationObserver", "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", [], [ "imgIDecoderObserver",
+HTML_TAG("img", "Image", [], [ "imgINotificationObserver",
                                "nsIImageLoadingContent" ]);
-HTML_TAG("input", "Input", [], [ "imgIDecoderObserver",
+HTML_TAG("input", "Input", [], [ "imgINotificationObserver",
                                  "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 "nsStubImageDecoderObserver.h"
+#include "imgINotificationObserver.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 nsStubImageDecoderObserver
+                    , public imgINotificationObserver
                     , public nsIDOMEventListener
 {
 public:
   ImageDocument();
   virtual ~ImageDocument();
 
   NS_DECL_ISUPPORTS_INHERITED
 
@@ -84,22 +84,17 @@ public:
                                      nsIContentSink*     aSink = nullptr);
 
   virtual void SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject);
   virtual void Destroy();
   virtual void OnPageShow(bool aPersisted,
                           nsIDOMEventTarget* aDispatchStartTarget);
 
   NS_DECL_NSIIMAGEDOCUMENT
-
-  // 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);
+  NS_DECL_IMGINOTIFICATIONOBSERVER
 
   // nsIDOMEventListener
   NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ImageDocument, MediaDocument)
 
   friend class ImageListener;
 
@@ -118,16 +113,19 @@ protected:
   float GetRatio() {
     return NS_MIN((float)mVisibleWidth / mImageWidth,
                   (float)mVisibleHeight / mImageHeight);
   }
 
   void ResetZoomLevel();
   float GetZoomLevel();
 
+  nsresult OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage);
+  nsresult OnStopDecode(imgIRequest *aRequest, nsresult aStatus);
+
   nsCOMPtr<nsIContent>          mImageContent;
 
   int32_t                       mVisibleWidth;
   int32_t                       mVisibleHeight;
   int32_t                       mImageWidth;
   int32_t                       mImageHeight;
 
   bool                          mResizeImageByDefault;
@@ -233,18 +231,17 @@ 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, imgIDecoderObserver)
-    NS_INTERFACE_TABLE_ENTRY(ImageDocument, imgIContainerObserver)
+    NS_INTERFACE_TABLE_ENTRY(ImageDocument, imgINotificationObserver)
     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
@@ -504,46 +501,70 @@ ImageDocument::ToggleImageSize()
     ResetZoomLevel();
     ShrinkToFit();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+ImageDocument::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData)
+{
+  if (aType == imgINotificationObserver::START_CONTAINER) {
+    nsCOMPtr<imgIContainer> image;
+    aRequest->GetImage(getter_AddRefs(image));
+    return OnStartContainer(aRequest, image);
+  }
+
+  if (aType == imgINotificationObserver::STOP_CONTAINER) {
+    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::STOP_DECODE) {
+    uint32_t reqStatus;
+    aRequest->GetImageStatus(&reqStatus);
+    nsresult status =
+        reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+    return OnStopDecode(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;
 }
 
-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
+nsresult
 ImageDocument::OnStopDecode(imgIRequest *aRequest,
-                            nsresult aStatus,
-                            const PRUnichar *aStatusArg)
+                            nsresult aStatus)
 {
   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);
@@ -555,28 +576,16 @@ ImageDocument::OnStopDecode(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,
-                           imgIDecoderObserver, nsIImageLoadingContent,
+                           imgINotificationObserver, nsIImageLoadingContent,
                            imgIOnloadBlocker)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGFEImageElement)
 NS_INTERFACE_MAP_END_INHERITING(nsSVGFEImageElementBase)
 
 //----------------------------------------------------------------------
 // Implementation
 
 nsSVGFEImageElement::nsSVGFEImageElement(already_AddRefed<nsINodeInfo> aNodeInfo)
@@ -5751,53 +5751,37 @@ nsSVGFEImageElement::GetPreserveAspectRa
 nsSVGElement::StringAttributesInfo
 nsSVGFEImageElement::GetStringInfo()
 {
   return StringAttributesInfo(mStringAttributes, sStringInfo,
                               ArrayLength(sStringInfo));
 }
 
 //----------------------------------------------------------------------
-// imgIDecoderObserver methods
-
-NS_IMETHODIMP
-nsSVGFEImageElement::OnStopDecode(imgIRequest *aRequest,
-                                  nsresult status,
-                                  const PRUnichar *statusArg)
-{
-  nsresult rv =
-    nsImageLoadingContent::OnStopDecode(aRequest, status, statusArg);
-  Invalidate();
-  return rv;
-}
+// imgINotificationObserver methods
 
 NS_IMETHODIMP
-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();
+nsSVGFEImageElement::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData)
+{
+  nsresult rv = nsImageLoadingContent::Notify(aRequest, aType, aData);
+
+  if (aType == imgINotificationObserver::START_CONTAINER) {
+    // 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::STOP_DECODE ||
+      aType == imgINotificationObserver::FRAME_CHANGED ||
+      aType == imgINotificationObserver::START_CONTAINER) {
+    Invalidate();
+  }
+
   return rv;
 }
 
 //----------------------------------------------------------------------
 // helper methods
 
 void
 nsSVGFEImageElement::Invalidate()
--- a/content/svg/content/src/nsSVGFilters.h
+++ b/content/svg/content/src/nsSVGFilters.h
@@ -262,26 +262,17 @@ 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;
 
-  // 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);
+  NS_IMETHODIMP Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData);
 
   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 "imgIDecoderObserver.h"
+#include "imgINotificationObserver.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, imgIDecoderObserver,
+                           nsIDOMSVGURIReference, imgINotificationObserver,
                            nsIImageLoadingContent, imgIOnloadBlocker)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGImageElement)
 NS_INTERFACE_MAP_END_INHERITING(nsSVGImageElementBase)
 
 //----------------------------------------------------------------------
 // Implementation
 
 nsSVGImageElement::nsSVGImageElement(already_AddRefed<nsINodeInfo> aNodeInfo)
--- a/image/public/Makefile.in
+++ b/image/public/Makefile.in
@@ -18,15 +18,17 @@ 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/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 imgIDecoderObserver;
+interface imgINotificationObserver;
 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 imgIDecoderObserver aObserver,
+                        in imgINotificationObserver 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 imgIDecoderObserver aObserver,
+                        in imgINotificationObserver aObserver,
                         in nsISupports cx,
                         out nsIStreamListener aListener);
 };
new file mode 100644
--- /dev/null
+++ b/image/public/imgINotificationObserver.idl
@@ -0,0 +1,34 @@
+/* -*- 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(bf9ed307-02a5-4732-b3eb-659bde5de84f)]
+interface imgINotificationObserver : nsISupports
+{
+  const long START_REQUEST = 1;
+  const long START_CONTAINER = 2;
+  const long START_FRAME = 3;
+  const long DATA_AVAILABLE = 4;
+  const long STOP_FRAME = 5;
+  const long STOP_CONTAINER = 6;
+  const long STOP_DECODE = 7;
+  const long DISCARD = 8;
+  const long IS_ANIMATED = 9;
+  const long FRAME_CHANGED = 10;
+  const long STOP_REQUEST = 11;
+  const long START_DECODE = 12;
+
+  [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 imgIDecoderObserver;
+interface imgINotificationObserver;
 interface nsIURI;
 interface nsIPrincipal;
 
 /**
  * imgIRequest interface
  *
  * @author Stuart Parmenter <stuart@mozilla.com>
  * @version 0.1
  * @see imagelib2
  */
-[scriptable, uuid(a5a785a8-9881-11e1-aaff-001fbc092072)]
+[scriptable, uuid(3ea9fc87-2e97-45bf-b373-d1dd253a0b5e)]
 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 imgIDecoderObserver decoderObserver;
+  readonly attribute imgINotificationObserver notificationObserver;
 
   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 imgIDecoderObserver aObserver);
+  imgIRequest clone(in imgINotificationObserver 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)
new file mode 100644
--- /dev/null
+++ b/image/public/imgIScriptedNotificationObserver.idl
@@ -0,0 +1,26 @@
+/* -*- 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(1296bf6c-6067-424b-ba8e-389ec89ee48b)]
+interface imgIScriptedNotificationObserver : nsISupports
+{
+  void startRequest(in imgIRequest aRequest);
+  void startContainer(in imgIRequest aRequest);
+  void startFrame(in imgIRequest aRequest);
+  void startDecode(in imgIRequest aRequest);
+  void dataAvailable(in imgIRequest aRequest);
+  void stopFrame(in imgIRequest aRequest);
+  void stopContainer(in imgIRequest aRequest);
+  void stopDecode(in imgIRequest aRequest);
+  void stopRequest(in imgIRequest aRequest);
+  void discard(in imgIRequest aRequest);
+  void isAnimated(in imgIRequest aRequest);
+  void frameChanged(in imgIRequest aRequest);
+};
--- a/image/public/imgITools.idl
+++ b/image/public/imgITools.idl
@@ -6,18 +6,20 @@
 
 #include "nsISupports.idl"
 
 interface nsIInputStream;
 interface imgIContainer;
 interface imgILoader;
 interface imgICache;
 interface nsIDOMDocument;
+interface imgIScriptedNotificationObserver;
+interface imgINotificationObserver;
 
-[scriptable, uuid(53dd1cbe-cb9f-4d9e-8104-1ab72851c88e)]
+[scriptable, uuid(98bd5bf9-87eb-4d92-81b1-4cd10c64f7b2)]
 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).
@@ -118,9 +120,17 @@ 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/Makefile.in
+++ b/image/src/Makefile.in
@@ -22,16 +22,17 @@ 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 \
new file mode 100644
--- /dev/null
+++ b/image/src/ScriptedNotificationObserver.cpp
@@ -0,0 +1,59 @@
+/* -*- 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::START_REQUEST)
+    return mInner->StartRequest(aRequest);
+  if (aType == imgINotificationObserver::START_CONTAINER)
+    return mInner->StartContainer(aRequest);
+  if (aType == imgINotificationObserver::START_FRAME)
+    return mInner->StartFrame(aRequest);
+  if (aType == imgINotificationObserver::START_DECODE)
+    return mInner->StartDecode(aRequest);
+  if (aType == imgINotificationObserver::DATA_AVAILABLE)
+    return mInner->DataAvailable(aRequest);
+  if (aType == imgINotificationObserver::STOP_FRAME)
+    return mInner->StopFrame(aRequest);
+  if (aType == imgINotificationObserver::STOP_CONTAINER)
+    return mInner->StopContainer(aRequest);
+  if (aType == imgINotificationObserver::STOP_DECODE)
+    return mInner->StopDecode(aRequest);
+  if (aType == imgINotificationObserver::STOP_REQUEST)
+    return mInner->StopRequest(aRequest);
+  if (aType == imgINotificationObserver::DISCARD)
+    return mInner->StopRequest(aRequest);
+  if (aType == imgINotificationObserver::IS_ANIMATED)
+    return mInner->IsAnimated(aRequest);
+  if (aType == imgINotificationObserver::FRAME_CHANGED)
+    return mInner->FrameChanged(aRequest);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/image/src/ScriptedNotificationObserver.h
@@ -0,0 +1,30 @@
+/* -*- 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/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,
-                                             imgIDecoderObserver *aObserver,
+                                             imgINotificationObserver *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|.
@@ -1157,17 +1157,17 @@ void imgLoader::CheckCacheLimits(imgCach
   }
 }
 
 bool imgLoader::ValidateRequestWithNewChannel(imgRequest *request,
                                                 nsIURI *aURI,
                                                 nsIURI *aInitialDocumentURI,
                                                 nsIURI *aReferrerURI,
                                                 nsILoadGroup *aLoadGroup,
-                                                imgIDecoderObserver *aObserver,
+                                                imgINotificationObserver *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,
-                                imgIDecoderObserver *aObserver,
+                                imgINotificationObserver *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 imgIDecoderObserver 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 imgINotificationObserver 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,
-                                   imgIDecoderObserver *aObserver,
+                                   imgINotificationObserver *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 imgIDecoderObserver aObserver, in nsISupports cx, out nsIStreamListener); */
-NS_IMETHODIMP imgLoader::LoadImageWithChannel(nsIChannel *channel, imgIDecoderObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgIRequest **_retval)
+/* 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)
 {
   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 imgIDecoderObserver;
+class imgINotificationObserver;
 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,
-                       imgIDecoderObserver *aObserver, nsISupports *aCX,
+                       imgINotificationObserver *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,
-                                       imgIDecoderObserver *aObserver,
+                                       imgINotificationObserver *aObserver,
                                        nsISupports *aCX, nsLoadFlags aLoadFlags,
                                        imgIRequest *aExistingRequest,
                                        imgIRequest **aProxyRequest,
                                        nsIChannelPolicy *aPolicy,
                                        nsIPrincipal* aLoadingPrincipal,
                                        int32_t aCORSMode);
 
   nsresult CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup,
-                                    imgIDecoderObserver *aObserver,
+                                    imgINotificationObserver *aObserver,
                                     nsLoadFlags aLoadFlags, imgIRequest *aRequestProxy,
                                     imgIRequest **_retval);
 
   void ReadAcceptHeaderPref();
 
 
   typedef nsRefPtrHashtable<nsCStringHashKey, imgCacheEntry> imgCacheTable;
 
--- a/image/src/imgRequestProxy.cpp
+++ b/image/src/imgRequestProxy.cpp
@@ -85,17 +85,17 @@ imgRequestProxy::~imgRequestProxy()
        */
       mOwner->RemoveProxy(this, NS_OK);
     }
   }
 }
 
 nsresult imgRequestProxy::Init(imgStatusTracker* aStatusTracker,
                                nsILoadGroup* aLoadGroup,
-                               nsIURI* aURI, imgIDecoderObserver* aObserver)
+                               nsIURI* aURI, imgINotificationObserver* aObserver)
 {
   NS_PRECONDITION(!mOwner && !mListener, "imgRequestProxy is already initialized");
 
   LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request", aStatusTracker->GetRequest());
 
   NS_ABORT_IF_FALSE(mAnimationConsumers == 0, "Cannot have animation before Init");
 
   mOwner = aStatusTracker->GetRequest();
@@ -453,21 +453,21 @@ NS_IMETHODIMP imgRequestProxy::GetURI(ns
   if (!mURI)
     return NS_ERROR_FAILURE;
 
   NS_ADDREF(*aURI = mURI);
 
   return NS_OK;
 }
 
-/* readonly attribute imgIDecoderObserver decoderObserver; */
-NS_IMETHODIMP imgRequestProxy::GetDecoderObserver(imgIDecoderObserver **aDecoderObserver)
+/* readonly attribute imgINotificationObserver notificationObserver; */
+NS_IMETHODIMP imgRequestProxy::GetNotificationObserver(imgINotificationObserver **aObserver)
 {
-  *aDecoderObserver = mListener;
-  NS_IF_ADDREF(*aDecoderObserver);
+  *aObserver = mListener;
+  NS_IF_ADDREF(*aObserver);
   return NS_OK;
 }
 
 /* readonly attribute string mimeType; */
 NS_IMETHODIMP imgRequestProxy::GetMimeType(char **aMimeType)
 {
   if (!mOwner)
     return NS_ERROR_FAILURE;
@@ -476,17 +476,17 @@ NS_IMETHODIMP imgRequestProxy::GetMimeTy
   if (!type)
     return NS_ERROR_FAILURE;
 
   *aMimeType = NS_strdup(type);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP imgRequestProxy::Clone(imgIDecoderObserver* aObserver,
+NS_IMETHODIMP imgRequestProxy::Clone(imgINotificationObserver* aObserver,
                                      imgIRequest** aClone)
 {
   NS_PRECONDITION(aClone, "Null out param");
 
   LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
 
   *aClone = nullptr;
   nsRefPtr<imgRequestProxy> clone = new imgRequestProxy();
@@ -586,149 +586,147 @@ 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);
+    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
+    mListener->Notify(this, imgINotificationObserver::FRAME_CHANGED, dirtyRect);
   }
 }
 
 /** imgIDecoderObserver methods **/
 
 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);
+    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
+    mListener->Notify(this, imgINotificationObserver::START_DECODE, nullptr);
   }
 }
 
 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<imgIDecoderObserver> kungFuDeathGrip(mListener);
-    mListener->OnStartContainer(this, image);
+    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
+    mListener->Notify(this, imgINotificationObserver::START_CONTAINER, nullptr);
     mSentStartContainer = true;
   }
 }
 
 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);
+    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
+    mListener->Notify(this, imgINotificationObserver::START_FRAME, nullptr);
   }
 }
 
 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<imgIDecoderObserver> kungFuDeathGrip(mListener);
-    mListener->OnDataAvailable(this, aCurrentFrame, rect);
+    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
+    mListener->Notify(this, imgINotificationObserver::DATA_AVAILABLE, rect);
   }
 }
 
 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<imgIDecoderObserver> kungFuDeathGrip(mListener);
-    mListener->OnStopFrame(this, frame);
+    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
+    mListener->Notify(this, imgINotificationObserver::STOP_FRAME, nullptr);
   }
 }
 
 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);
+    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
+    mListener->Notify(this, imgINotificationObserver::STOP_CONTAINER, nullptr);
   }
 
   // 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<imgIDecoderObserver> kungFuDeathGrip(mListener);
-    mListener->OnStopDecode(this, status, statusArg);
+    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
+    mListener->Notify(this, imgINotificationObserver::STOP_DECODE, nullptr);
   }
 }
 
 void imgRequestProxy::OnDiscard()
 {
   LOG_FUNC(gImgLog, "imgRequestProxy::OnDiscard");
 
   if (mListener && !mCanceled) {
     // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgIDecoderObserver> kungFuDeathGrip(mListener);
-    mListener->OnDiscard(this);
+    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
+    mListener->Notify(this, imgINotificationObserver::DISCARD, nullptr);
   }
 }
 
 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<imgIDecoderObserver> kungFuDeathGrip(mListener);
-    mListener->OnImageIsAnimated(this);
+    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
+    mListener->Notify(this, imgINotificationObserver::IS_ANIMATED, nullptr);
   }
 }
 
 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);
+    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
+    mListener->Notify(this, imgINotificationObserver::START_REQUEST, nullptr);
   }
 }
 
 void imgRequestProxy::OnStopRequest(bool lastPart)
 {
 #ifdef PR_LOGGING
   nsAutoCString name;
   GetName(name);
@@ -736,18 +734,18 @@ void imgRequestProxy::OnStopRequest(bool
 #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<imgIDecoderObserver> kungFuDeathGrip(mListener);
-    mListener->OnStopRequest(this, lastPart);
+    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
+    mListener->Notify(this, imgINotificationObserver::STOP_REQUEST, nullptr);
   }
 
   // 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);
@@ -759,17 +757,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.
-    imgIDecoderObserver* obs = mListener;
+    imgINotificationObserver* obs = mListener;
     mListenerIsStrongRef = false;
     NS_RELEASE(obs);
   }
 }
 
 void imgRequestProxy::BlockOnload()
 {
 #ifdef PR_LOGGING
@@ -801,17 +799,17 @@ 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<imgIDecoderObserver> obs;
+    nsCOMPtr<imgINotificationObserver> obs;
     obs.swap(mListener);
     mListenerIsStrongRef = false;
   } else {
     mListener = nullptr;
   }
 }
 
 NS_IMETHODIMP
--- 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 "imgIDecoderObserver.h"
+#include "imgINotificationObserver.h"
 #include "nsISecurityInfoProvider.h"
 
 #include "nsIRequestObserver.h"
 #include "nsIChannel.h"
 #include "nsILoadGroup.h"
 #include "nsISupportsPriority.h"
 #include "nsITimedChannel.h"
 #include "nsCOMPtr.h"
@@ -54,17 +54,17 @@ public:
 
   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, imgIDecoderObserver *aObserver);
+                nsIURI* aURI, imgINotificationObserver *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 {
@@ -194,17 +194,17 @@ private:
   nsRefPtr<imgRequest> mOwner;
 
   // The URI of our request.
   nsCOMPtr<nsIURI> mURI;
 
   // 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.
-  imgIDecoderObserver* mListener;
+  imgINotificationObserver* mListener;
   nsCOMPtr<nsILoadGroup> mLoadGroup;
 
   nsLoadFlags mLoadFlags;
   uint32_t    mLockCount;
   uint32_t    mAnimationConsumers;
   bool mCanceled;
   bool mIsInLoadGroup;
   bool mListenerIsStrongRef;
--- a/image/src/imgTools.cpp
+++ b/image/src/imgTools.cpp
@@ -18,16 +18,18 @@
 #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 ========== */
 
@@ -271,16 +273,23 @@ 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,20 +116,24 @@ function getImagePref(pref)
       default:
         throw new Error("Unknown pref type");
     }
   }
   else
     return null;
 }
 
-// JS implementation of imgIDecoderObserver with stubs for all of its methods.
+// JS implementation of imgIScriptedNotificationObserver with stubs for all of its methods.
 function ImageDecoderObserverStub()
 {
-  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)      {}
+  this.startRequest = function startRequest(aRequest)     {}
+  this.startDecode = function startDecode(aRequest)       {}
+  this.startContainer = function startContainer(aRequest) {}
+  this.startFrame = function startFrame(aRequest)         {}
+  this.stopFrame = function stopFrame(aRequest)           {}
+  this.stopContainer = function stopContainer(aRequest)   {}
+  this.stopDecode = function stopDecode(aRequest)         {}
+  this.stopRequest = function stopRequest(aRequest)       {}
+  this.dataAvailable = function dataAvailable(aRequest)   {}
+  this.discard = function discard(aRequest)               {}
+  this.isAnimated = function isAnimated(aRequest)         {}
+  this.frameChanged = function frameChanged(aRequest)     {}
 }
--- a/image/test/mochitest/test_animSVGImage.html
+++ b/image/test/mochitest/test_animSVGImage.html
@@ -21,16 +21,17 @@ 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;
 
@@ -48,17 +49,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, aFrame) {
+function myOnStopFrame(aRequest) {
   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();
@@ -84,18 +85,21 @@ function cleanUpAndFinish() {
   SimpleTest.finish();
   gIsTestFinished = true;
 }
 
 function main() {
   takeReferenceSnapshot();
 
   // Create, customize & attach decoder observer
-  gMyDecoderObserver = new ImageDecoderObserverStub();
-  gMyDecoderObserver.onStopFrame = myOnStopFrame;
+  observer = new ImageDecoderObserverStub();
+  observer.stopFrame = myOnStopFrame;
+  gMyDecoderObserver =
+    Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+      .createScriptedObserver(observer);
   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,39 +40,51 @@ 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 clone = aRequest.clone(listener);
+  var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+                .createScriptedObserver(listener);
+  var clone = aRequest.clone(outer);
 }
 
 // Ensure that all the callbacks were called on aRequest.
 function checkAllCallbacks(listener, aRequest)
 {
+  do_check_neq(listener.state & START_REQUEST, 0);
+  do_check_neq(listener.state & START_DECODE, 0);
+  do_check_neq(listener.state & START_CONTAINER, 0);
+  do_check_neq(listener.state & START_FRAME, 0);
+  do_check_neq(listener.state & STOP_FRAME, 0);
+  do_check_neq(listener.state & STOP_CONTAINER, 0);
+  do_check_neq(listener.state & STOP_DECODE, 0);
+  do_check_neq(listener.state & STOP_REQUEST, 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 staticrequestclone = staticrequest.clone(listener);
+    var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+                  .createScriptedObserver(listener);
+    var staticrequestclone = staticrequest.clone(outer);
   } 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();
 
@@ -81,17 +93,19 @@ 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);
-  requests.push(gCurrentLoader.loadImage(uri, null, null, null, null, listener, null, 0, null, null, null));
+  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));
   listener.synchronous = false;
 }
 
 function firstLoadDone(oldlistener, aRequest)
 {
   checkSecondLoad(uri);
 
   do_test_finished();
@@ -136,18 +150,20 @@ 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, listener, null, outlistener));
+  requests.push(gCurrentLoader.loadImageWithChannel(channel, outer, 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.
@@ -158,18 +174,20 @@ 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, listener, null, outlistener));
+  requests.push(gCurrentLoader.loadImageWithChannel(channel, outer, null, outlistener));
   channellistener.outputListener = outlistener.value;
 
   listener.synchronous = false;
 }
 
 function all_done_callback()
 {
   server.stop(function() { do_test_finished(); });
@@ -177,32 +195,36 @@ 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(); });
-    requests.push(gCurrentLoader.loadImage(uri, null, null, null, null, listener2, null, 0, null, null, null));
+    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));
     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 req = gCurrentLoader.loadImage(uri, null, null, null, null, listener, null, 0, null, null, null);
+  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);
   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
@@ -15,78 +15,81 @@ 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.onStartRequest = function onStartRequest(aRequest)
+  this.startRequest = function onStartRequest(aRequest)
   {
     do_check_false(this.synchronous);
 
     this.state |= START_REQUEST;
 
     if (this.start_callback)
       this.start_callback(this, aRequest);
   }
-  this.onStartDecode = function onStartDecode(aRequest)
+  this.startDecode = function onStartDecode(aRequest)
   {
     do_check_false(this.synchronous);
 
     this.state |= START_DECODE;
   }
-  this.onStartContainer = function onStartContainer(aRequest, aContainer)
+  this.startContainer = function onStartContainer(aRequest)
   {
     do_check_false(this.synchronous);
 
     this.state |= START_CONTAINER;
   }
-  this.onStartFrame = function onStartFrame(aRequest, aFrame)
+  this.startFrame = function onStartFrame(aRequest)
   {
     do_check_false(this.synchronous);
 
     this.state |= START_FRAME;
   }
-  this.onStopFrame = function onStopFrame(aRequest, aFrame)
+  this.stopFrame = function onStopFrame(aRequest)
   {
     do_check_false(this.synchronous);
 
     this.state |= STOP_FRAME;
   }
-  this.onStopContainer = function onStopContainer(aRequest, aContainer)
+  this.stopContainer = function onStopContainer(aRequest)
   {
     do_check_false(this.synchronous);
 
     this.state |= STOP_CONTAINER;
   }
-  this.onStopDecode = function onStopDecode(aRequest, status, statusArg)
+  this.stopDecode = function onStopDecode(aRequest)
   {
     do_check_false(this.synchronous);
 
     this.state |= STOP_DECODE;
   }
-  this.onStopRequest = function onStopRequest(aRequest, aIsLastPart)
+  this.stopRequest = function onStopRequest(aRequest)
   {
     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 |= STOP_REQUEST;
 
     if (this.stop_callback)
       this.stop_callback(this, aRequest);
   }
+  this.dataAvailable = function onDataAvailable(aRequest)
+  {
+  }
 
   // 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;
 
--- a/image/test/unit/test_private_channel.js
+++ b/image/test/unit/test_private_channel.js
@@ -50,31 +50,35 @@ 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;
-  requests.push(loader.loadImageWithChannel(chan, listener, null, outlistener));
+  var outer = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+                .createScriptedObserver(listener);
+  listeners.push(outer);
+  requests.push(loader.loadImageWithChannel(chan, outer, 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, listener, null, 0, null, null, null));
+  requests.push(loader.loadImage(uri, null, null, null, loadGroup, outer, 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,19 +1426,47 @@ 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::START_CONTAINER) {
+    nsCOMPtr<imgIContainer> image;
+    aRequest->GetImage(getter_AddRefs(image));
+    return OnStartContainer(aRequest, image);
+  }
 
-NS_IMETHODIMP nsBulletFrame::OnStartContainer(imgIRequest *aRequest,
-                                              imgIContainer *aImage)
+  if (aType == imgINotificationObserver::DATA_AVAILABLE ||
+      aType == imgINotificationObserver::FRAME_CHANGED) {
+    // 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)
 {
   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;
@@ -1470,70 +1498,16 @@ NS_IMETHODIMP nsBulletFrame::OnStartCont
   // 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");
 
@@ -1619,65 +1593,26 @@ nsBulletFrame::GetBaseline() const
 
 
 
 
 
 
 
 
-NS_IMPL_ISUPPORTS2(nsBulletListener, imgIDecoderObserver, imgIContainerObserver)
+NS_IMPL_ISUPPORTS1(nsBulletListener, imgINotificationObserver)
 
 nsBulletListener::nsBulletListener() :
   mFrame(nullptr)
 {
 }
 
 nsBulletListener::~nsBulletListener()
 {
 }
 
-NS_IMETHODIMP nsBulletListener::OnStartContainer(imgIRequest *aRequest,
-                                                 imgIContainer *aImage)
+NS_IMETHODIMP
+nsBulletListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
 {
   if (!mFrame)
     return NS_ERROR_FAILURE;
-
-  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);
+  return mFrame->Notify(aRequest, aType, aData);
 }
-
-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,42 +9,31 @@
 #define nsBulletFrame_h___
 
 #include "mozilla/Attributes.h"
 #include "nsFrame.h"
 #include "nsStyleContext.h"
 
 #include "imgIRequest.h"
 #include "imgIDecoderObserver.h"
-#include "nsStubImageDecoderObserver.h"
+#include "imgINotificationObserver.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 nsStubImageDecoderObserver
+class nsBulletListener : public imgINotificationObserver
 {
 public:
   nsBulletListener();
   virtual ~nsBulletListener();
 
   NS_DECL_ISUPPORTS
-  // 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);
+  NS_DECL_IMGINOTIFICATIONOBSERVER
 
   void SetFrame(nsBulletFrame *frame) { mFrame = frame; }
 
 private:
   nsBulletFrame *mFrame;
 };
 
 /**
@@ -56,16 +45,18 @@ 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
@@ -80,28 +71,16 @@ 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);
@@ -115,16 +94,18 @@ 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
@@ -521,16 +521,46 @@ nsImageFrame::ShouldCreateImageFrameFor(
       }
     }
   }
   
   return useSizedBox;
 }
 
 nsresult
+nsImageFrame::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData)
+{
+  if (aType == imgINotificationObserver::START_CONTAINER) {
+    nsCOMPtr<imgIContainer> image;
+    aRequest->GetImage(getter_AddRefs(image));
+    return OnStartContainer(aRequest, image);
+  }
+
+  if (aType == imgINotificationObserver::DATA_AVAILABLE) {
+    return OnDataAvailable(aRequest, aData);
+  }
+
+  if (aType == imgINotificationObserver::STOP_DECODE) {
+    uint32_t imgStatus;
+    aRequest->GetImageStatus(&imgStatus);
+    nsresult status =
+        imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+    return OnStopDecode(aRequest, status);
+  }
+
+  if (aType == imgINotificationObserver::FRAME_CHANGED) {
+    nsCOMPtr<imgIContainer> image;
+    aRequest->GetImage(getter_AddRefs(image));
+    return FrameChanged(aRequest, image);
+  }
+
+  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
@@ -559,17 +589,16 @@ nsImageFrame::OnStartContainer(imgIReque
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 nsImageFrame::OnDataAvailable(imgIRequest *aRequest,
-                              bool aCurrentFrame,
                               const nsIntRect *aRect)
 {
   // 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);
 
@@ -578,21 +607,16 @@ nsImageFrame::OnDataAvailable(imgIReques
     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);
@@ -602,18 +626,17 @@ nsImageFrame::OnDataAvailable(imgIReques
     InvalidateFrameWithRect(invalid, nsDisplayItem::TYPE_ALT_FEEDBACK);
   }
   
   return NS_OK;
 }
 
 nsresult
 nsImageFrame::OnStopDecode(imgIRequest *aRequest,
-                           nsresult aStatus,
-                           const PRUnichar *aStatusArg)
+                           nsresult aStatus)
 {
   // 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) {
@@ -662,18 +685,17 @@ nsImageFrame::NotifyNewCurrentRequest(im
       // Update border+content to account for image change
       InvalidateFrame();
     }
   }
 }
 
 nsresult
 nsImageFrame::FrameChanged(imgIRequest *aRequest,
-                           imgIContainer *aContainer,
-                           const nsIntRect *aDirtyRect)
+                           imgIContainer *aContainer)
 {
   if (!GetStyleVisibility()->IsVisible()) {
     return NS_OK;
   }
 
   if (IsPendingLoad(aContainer)) {
     // We don't care about it
     return NS_OK;
@@ -1889,17 +1911,17 @@ nsresult nsImageFrame::LoadIcons(nsPresC
 
   rv = LoadIcon(brokenSrc,
                 aPresContext,
                 getter_AddRefs(gIconLoad->mBrokenImage));
   return rv;
 }
 
 NS_IMPL_ISUPPORTS2(nsImageFrame::IconLoad, nsIObserver,
-                   imgIDecoderObserver)
+                   imgINotificationObserver)
 
 static const char* kIconLoadPrefs[] = {
   "browser.display.force_inline_alttext",
   "browser.display.show_image_placeholders",
   nullptr
 };
 
 nsImageFrame::IconLoad::IconLoad()
@@ -1946,165 +1968,52 @@ 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::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)
+nsImageFrame::IconLoad::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
 {
-  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;
-}
+  if (aType != imgINotificationObserver::STOP_REQUEST &&
+      aType != imgINotificationObserver::FRAME_CHANGED) {
+    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_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)
+NS_IMPL_ISUPPORTS1(nsImageListener, imgINotificationObserver)
 
 nsImageListener::nsImageListener(nsImageFrame *aFrame) :
   mFrame(aFrame)
 {
 }
 
 nsImageListener::~nsImageListener()
 {
 }
 
-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)
+NS_IMETHODIMP
+nsImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
 {
   if (!mFrame)
     return NS_ERROR_FAILURE;
 
-  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);
+  return mFrame->Notify(aRequest, aType, aData);
 }
 
 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,18 +7,17 @@
 
 #ifndef nsImageFrame_h___
 #define nsImageFrame_h___
 
 #include "nsSplittableFrame.h"
 #include "nsIIOService.h"
 #include "nsIObserver.h"
 
-#include "nsStubImageDecoderObserver.h"
-#include "imgIDecoderObserver.h"
+#include "imgINotificationObserver.h"
 
 #include "nsDisplayList.h"
 #include "imgIContainer.h"
 #include "mozilla/Attributes.h"
 
 class nsIFrame;
 class nsImageMap;
 class nsIURI;
@@ -35,33 +34,24 @@ class nsImageLoadingContent;
 namespace mozilla {
 namespace layers {
   class ImageContainer;
   class ImageLayer;
   class LayerManager;
 }
 }
 
-class nsImageListener : public nsStubImageDecoderObserver
+class nsImageListener : public imgINotificationObserver
 {
 public:
   nsImageListener(nsImageFrame *aFrame);
   virtual ~nsImageListener();
 
   NS_DECL_ISUPPORTS
-  // 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);
+  NS_DECL_IMGINOTIFICATIONOBSERVER
 
   void SetFrame(nsImageFrame *frame) { mFrame = frame; }
 
 private:
   nsImageFrame *mFrame;
 };
 
 #define IMAGE_SIZECONSTRAINED       NS_FRAME_STATE_BIT(20)
@@ -132,16 +122,18 @@ 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);
   
@@ -216,24 +208,21 @@ 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, bool aCurrentFrame,
-                           const nsIntRect *rect);
+  nsresult OnDataAvailable(imgIRequest *aRequest, const nsIntRect *rect);
   nsresult OnStopDecode(imgIRequest *aRequest,
-                        nsresult aStatus,
-                        const PRUnichar *aStatusArg);
+                        nsresult aStatus);
   nsresult FrameChanged(imgIRequest *aRequest,
-                        imgIContainer *aContainer,
-                        const nsIntRect *aDirtyRect);
+                        imgIContainer *aContainer);
   /**
    * 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,
@@ -283,17 +272,17 @@ 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<imgIDecoderObserver> mListener;
+  nsCOMPtr<imgINotificationObserver> mListener;
 
   nsSize mComputedSize;
   nsIFrame::IntrinsicSize mIntrinsicSize;
   nsSize mIntrinsicRatio;
 
   bool mDisplayingIcon;
 
   static nsIIOService* sIOService;
@@ -306,28 +295,27 @@ private:
 
   // 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 imgIDecoderObserver {
+                             public imgINotificationObserver {
     // 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_IMGICONTAINEROBSERVER
-    NS_DECL_IMGIDECODEROBSERVER
+    NS_DECL_IMGINOTIFICATIONOBSERVER
 
     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<imgIDecoderObserver> observer;
-  aRequest->GetDecoderObserver(getter_AddRefs(observer));
+  nsCOMPtr<imgINotificationObserver> observer;
+  aRequest->GetNotificationObserver(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<imgIDecoderObserver> observer;
-    aRequest->GetDecoderObserver(getter_AddRefs(observer));
+    nsCOMPtr<imgINotificationObserver> observer;
+    aRequest->GetNotificationObserver(getter_AddRefs(observer));
     MOZ_ASSERT(!observer || observer == this);
   }
 #endif
 
   mRequestToFrameMap.Get(aRequest, &frameSet);
   mFrameToRequestMap.Get(aFrame, &requestSet);
 
   if (frameSet) {
@@ -329,35 +329,58 @@ ImageLoader::DoRedraw(FrameSet* aFrameSe
     }
   }
 }
 
 NS_IMPL_ADDREF(ImageLoader)
 NS_IMPL_RELEASE(ImageLoader)
 
 NS_INTERFACE_MAP_BEGIN(ImageLoader)
-  NS_INTERFACE_MAP_ENTRY(imgIDecoderObserver)
-  NS_INTERFACE_MAP_ENTRY(imgIContainerObserver)
+  NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
   NS_INTERFACE_MAP_ENTRY(imgIOnloadBlocker)
 NS_INTERFACE_MAP_END
 
 NS_IMETHODIMP
+ImageLoader::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
+{
+  if (aType == imgINotificationObserver::START_CONTAINER) {
+    nsCOMPtr<imgIContainer> image;
+    aRequest->GetImage(getter_AddRefs(image));
+    return OnStartContainer(aRequest, image);
+  }
+
+  if (aType == imgINotificationObserver::IS_ANIMATED) {
+    return OnImageIsAnimated(aRequest);
+  }
+
+  if (aType == imgINotificationObserver::STOP_FRAME) {
+    return OnStopFrame(aRequest);
+  }
+
+  if (aType == imgINotificationObserver::FRAME_CHANGED) {
+    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;
 }
 
-NS_IMETHODIMP
+nsresult
 ImageLoader::OnImageIsAnimated(imgIRequest* aRequest)
 {
   if (!mDocument) {
     return NS_OK;
   }
 
   FrameSet* frameSet = nullptr;
   if (!mRequestToFrameMap.Get(aRequest, &frameSet)) {
@@ -371,18 +394,18 @@ ImageLoader::OnImageIsAnimated(imgIReque
     nsLayoutUtils::RegisterImageRequest(presContext,
                                         aRequest,
                                         nullptr);
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-ImageLoader::OnStopFrame(imgIRequest *aRequest, uint32_t aFrame)
+nsresult
+ImageLoader::OnStopFrame(imgIRequest *aRequest)
 {
   if (!mDocument || mInClone) {
     return NS_OK;
   }
 
   FrameSet* frameSet = nullptr;
   if (!mRequestToFrameMap.Get(aRequest, &frameSet)) {
     return NS_OK;
@@ -390,20 +413,18 @@ ImageLoader::OnStopFrame(imgIRequest *aR
 
   NS_ASSERTION(frameSet, "This should never be null!");
 
   DoRedraw(frameSet);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-ImageLoader::FrameChanged(imgIRequest *aRequest,
-                          imgIContainer *aContainer,
-                          const nsIntRect *aDirtyRect)
+nsresult
+ImageLoader::FrameChanged(imgIRequest *aRequest)
 {
   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 "nsStubImageDecoderObserver.h"
+#include "imgINotificationObserver.h"
 #include "mozilla/Attributes.h"
 
 class nsIFrame;
 class nsIDocument;
 class nsPresContext;
 class nsIURI;
 class nsIPrincipal;
 
 namespace mozilla {
 namespace css {
 
-class ImageLoader MOZ_FINAL : public nsStubImageDecoderObserver,
+class ImageLoader MOZ_FINAL : public imgINotificationObserver,
                               public imgIOnloadBlocker {
 public:
   typedef mozilla::css::ImageValue Image;
 
   ImageLoader(nsIDocument* aDocument)
   : mDocument(aDocument),
     mInClone(false)
   {
@@ -37,29 +37,17 @@ public:
 
     mRequestToFrameMap.Init();
     mFrameToRequestMap.Init();
     mImages.Init();
   }
 
   NS_DECL_ISUPPORTS
   NS_DECL_IMGIONLOADBLOCKER
-
-  // 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);
+  NS_DECL_IMGINOTIFICATIONOBSERVER
 
   void DropDocumentReference();
 
   void MaybeRegisterCSSImage(Image* aImage);
   void DeregisterCSSImage(Image* aImage);
 
   void AssociateRequestToFrame(imgIRequest* aRequest,
                                nsIFrame* aFrame);
@@ -98,16 +86,24 @@ 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,44 +6,35 @@
 // 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 "nsStubImageDecoderObserver.h"
+#include "imgINotificationObserver.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 nsStubImageDecoderObserver
+class nsSVGImageListener MOZ_FINAL : public imgINotificationObserver
 {
 public:
   nsSVGImageListener(nsSVGImageFrame *aFrame);
 
   NS_DECL_ISUPPORTS
-  // 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);
+  NS_DECL_IMGINOTIFICATIONOBSERVER
 
   void SetFrame(nsSVGImageFrame *frame) { mFrame = frame; }
 
 private:
   nsSVGImageFrame *mFrame;
 };
 
 typedef nsSVGPathGeometryFrame nsSVGImageFrameBase;
@@ -93,17 +84,17 @@ public:
 
 private:
   gfxMatrix GetRasterImageTransform(int32_t aNativeWidth,
                                     int32_t aNativeHeight,
                                     uint32_t aFor);
   gfxMatrix GetVectorImageTransform(uint32_t aFor);
   bool      TransformContextForPainting(gfxContext* aGfxContext);
 
-  nsCOMPtr<imgIDecoderObserver> mListener;
+  nsCOMPtr<imgINotificationObserver> mListener;
 
   nsCOMPtr<imgIContainer> mImageContainer;
 
   friend class nsSVGImageListener;
 };
 
 //----------------------------------------------------------------------
 // Implementation
@@ -555,55 +546,40 @@ nsSVGImageFrame::GetHitTestFlags()
   }
 
   return flags;
 }
 
 //----------------------------------------------------------------------
 // nsSVGImageListener implementation
 
-NS_IMPL_ISUPPORTS2(nsSVGImageListener,
-                   imgIDecoderObserver,
-                   imgIContainerObserver)
+NS_IMPL_ISUPPORTS1(nsSVGImageListener, imgINotificationObserver)
 
 nsSVGImageListener::nsSVGImageListener(nsSVGImageFrame *aFrame) :  mFrame(aFrame)
 {
 }
 
-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)
+NS_IMETHODIMP
+nsSVGImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
 {
   if (!mFrame)
     return NS_ERROR_FAILURE;
 
-  // No new dimensions, so we don't need to call
-  // nsSVGUtils::InvalidateAndScheduleBoundsUpdate.
-  nsSVGEffects::InvalidateRenderingObservers(mFrame);
-  nsSVGUtils::InvalidateBounds(mFrame);
-  return NS_OK;
-}
+  if (aType == imgINotificationObserver::STOP_DECODE) {
+    nsSVGUtils::InvalidateAndScheduleReflowSVG(mFrame);
+  }
 
-NS_IMETHODIMP nsSVGImageListener::OnStartContainer(imgIRequest *aRequest,
-                                                   imgIContainer *aContainer)
-{
-  // Called once the resource's dimensions have been obtained.
+  if (aType == imgINotificationObserver::FRAME_CHANGED) {
+    // No new dimensions, so we don't need to call
+    // nsSVGUtils::InvalidateAndScheduleBoundsUpdate.
+    nsSVGEffects::InvalidateRenderingObservers(mFrame);
+    nsSVGUtils::InvalidateBounds(mFrame);
+  }
 
-  if (!mFrame)
-    return NS_ERROR_FAILURE;
-
-  mFrame->mImageContainer = aContainer;
-  nsSVGUtils::InvalidateAndScheduleReflowSVG(mFrame);
+  if (aType == imgINotificationObserver::START_CONTAINER) {
+    // Called once the resource's dimensions have been obtained.
+    aRequest->GetImage(getter_AddRefs(mFrame->mImageContainer));
+    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(imgIDecoderObserver), getter_AddRefs(mListener));
+    listener->QueryInterface(NS_GET_IID(imgINotificationObserver), getter_AddRefs(mListener));
     NS_RELEASE(listener);
   }
 
   mSuppressStyleCheck = true;
   nsresult rv = nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
   mSuppressStyleCheck = false;
 
   UpdateLoadFlags();
@@ -572,19 +572,50 @@ 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::START_CONTAINER) {
+    nsCOMPtr<imgIContainer> image;
+    aRequest->GetImage(getter_AddRefs(image));
+    return OnStartContainer(aRequest, image);
+  }
 
-NS_IMETHODIMP nsImageBoxFrame::OnStartContainer(imgIRequest *request,
-                                                imgIContainer *image)
+  if (aType == imgINotificationObserver::STOP_CONTAINER) {
+    return OnStopContainer(aRequest);
+  }
+
+  if (aType == imgINotificationObserver::STOP_DECODE) {
+    uint32_t imgStatus;
+    aRequest->GetImageStatus(&imgStatus);
+    nsresult status =
+        imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+    return OnStopDecode(aRequest, status);
+  }
+
+  if (aType == imgINotificationObserver::IS_ANIMATED) {
+    return OnImageIsAnimated(aRequest);
+  }
+
+  if (aType == imgINotificationObserver::FRAME_CHANGED) {
+    return FrameChanged(aRequest);
+  }
+
+  return NS_OK;
+}
+
+nsresult 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();
 
@@ -598,113 +629,71 @@ NS_IMETHODIMP nsImageBoxFrame::OnStartCo
   if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
     PresContext()->PresShell()->
       FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP nsImageBoxFrame::OnStopContainer(imgIRequest *request,
-                                               imgIContainer *image)
+nsresult nsImageBoxFrame::OnStopContainer(imgIRequest *request)
 {
   nsBoxLayoutState state(PresContext());
   this->Redraw(state);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP nsImageBoxFrame::OnStopDecode(imgIRequest *request,
-                                            nsresult aStatus,
-                                            const PRUnichar *statusArg)
+nsresult nsImageBoxFrame::OnStopDecode(imgIRequest *request,
+                                       nsresult aStatus)
 {
   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;
 }
 
-NS_IMETHODIMP nsImageBoxFrame::OnImageIsAnimated(imgIRequest *aRequest)
+nsresult nsImageBoxFrame::OnImageIsAnimated(imgIRequest *aRequest)
 {
   // Register with our refresh driver, if we're animated.
   nsLayoutUtils::RegisterImageRequest(PresContext(), aRequest,
                                       &mRequestRegistered);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP nsImageBoxFrame::FrameChanged(imgIRequest *aRequest,
-                                            imgIContainer *aContainer,
-                                            const nsIntRect *aDirtyRect)
+nsresult nsImageBoxFrame::FrameChanged(imgIRequest *aRequest)
 {
   if ((0 == mRect.width) || (0 == mRect.height)) {
     return NS_OK;
   }
  
   InvalidateLayer(nsDisplayItem::TYPE_XUL_IMAGE);
 
   return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS2(nsImageBoxListener, imgIDecoderObserver, imgIContainerObserver)
+NS_IMPL_ISUPPORTS1(nsImageBoxListener, imgINotificationObserver)
 
 nsImageBoxListener::nsImageBoxListener()
 {
 }
 
 nsImageBoxListener::~nsImageBoxListener()
 {
 }
 
-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)
+NS_IMETHODIMP
+nsImageBoxListener::Notify(imgIRequest *request, int32_t aType, const nsIntRect* aData)
 {
   if (!mFrame)
     return NS_OK;
 
-  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);
+  return mFrame->Notify(request, aType, aData);
 }
-
-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,40 +6,30 @@
 #define nsImageBoxFrame_h___
 
 #include "mozilla/Attributes.h"
 #include "nsLeafBoxFrame.h"
 
 #include "imgILoader.h"
 #include "imgIRequest.h"
 #include "imgIContainer.h"
-#include "nsStubImageDecoderObserver.h"
+#include "imgINotificationObserver.h"
 
 class nsImageBoxFrame;
 
 class nsDisplayXULImage;
 
-class nsImageBoxListener : public nsStubImageDecoderObserver
+class nsImageBoxListener : public imgINotificationObserver
 {
 public:
   nsImageBoxListener();
   virtual ~nsImageBoxListener();
 
   NS_DECL_ISUPPORTS
-  // 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);
+  NS_DECL_IMGINOTIFICATIONOBSERVER
 
   void SetFrame(nsImageBoxFrame *frame) { mFrame = frame; }
 
 private:
   nsImageBoxFrame *mFrame;
 };
 
 class nsImageBoxFrame : public nsLeafBoxFrame
@@ -48,16 +38,18 @@ 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,
@@ -84,51 +76,45 @@ 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 OnStopContainer(imgIRequest *request);
+  nsresult OnStopDecode(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<imgIDecoderObserver> mListener;
+  nsCOMPtr<imgINotificationObserver> 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<imgIDecoderObserver> obs;
-      imgReq->GetDecoderObserver(getter_AddRefs(obs));
+      nsCOMPtr<imgINotificationObserver> obs;
+      imgReq->GetNotificationObserver(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<imgIDecoderObserver> imgDecoderObserver = listener;
+    nsCOMPtr<imgINotificationObserver> imgNotificationObserver = listener;
 
     nsCOMPtr<imgIRequest> imageRequest;
     if (styleRequest) {
-      styleRequest->Clone(imgDecoderObserver, getter_AddRefs(imageRequest));
+      styleRequest->Clone(imgNotificationObserver, 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(),
-                                                imgDecoderObserver,
+                                                imgNotificationObserver,
                                                 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, imgDecoderObserver);
+    nsTreeImageCacheEntry cacheEntry(imageRequest, imgNotificationObserver);
     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 "imgIDecoderObserver.h"
+#include "imgINotificationObserver.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, imgIDecoderObserver *aListener)
+  nsTreeImageCacheEntry(imgIRequest *aRequest, imgINotificationObserver *aListener)
     : request(aRequest), listener(aListener) {}
 
   nsCOMPtr<imgIRequest> request;
-  nsCOMPtr<imgIDecoderObserver> listener;
+  nsCOMPtr<imgINotificationObserver> 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,67 +3,52 @@
  * 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_ISUPPORTS2(nsTreeImageListener, imgIDecoderObserver, imgIContainerObserver)
+NS_IMPL_ISUPPORTS1(nsTreeImageListener, imgINotificationObserver)
 
 nsTreeImageListener::nsTreeImageListener(nsTreeBodyFrame* aTreeFrame)
   : mTreeFrame(aTreeFrame),
     mInvalidationSuppressed(true),
     mInvalidationArea(nullptr)
 {
 }
 
 nsTreeImageListener::~nsTreeImageListener()
 {
   delete mInvalidationArea;
 }
 
 NS_IMETHODIMP
-nsTreeImageListener::OnImageIsAnimated(imgIRequest *aRequest)
+nsTreeImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
 {
-  if (!mTreeFrame) {
-    return NS_OK;
+  if (aType == imgINotificationObserver::IS_ANIMATED) {
+    return mTreeFrame ? mTreeFrame->OnImageIsAnimated(aRequest) : NS_OK;
   }
 
-  return mTreeFrame->OnImageIsAnimated(aRequest);
-}
+  if (aType == imgINotificationObserver::START_CONTAINER) {
+    // 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();
+  }
 
-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();
+  if (aType == imgINotificationObserver::DATA_AVAILABLE ||
+      aType == imgINotificationObserver::FRAME_CHANGED) {
+    Invalidate();
+  }
+
   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,37 +4,28 @@
  * 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 nsStubImageDecoderObserver
+class nsTreeImageListener MOZ_FINAL : public imgINotificationObserver
 {
 public:
   nsTreeImageListener(nsTreeBodyFrame *aTreeFrame);
   ~nsTreeImageListener();
 
   NS_DECL_ISUPPORTS
-  // 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_DECL_IMGINOTIFICATIONOBSERVER
 
   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_ISUPPORTS4(nsAlertsIconListener, imgIContainerObserver,
-                   imgIDecoderObserver, nsIObserver, nsISupportsWeakReference)
+NS_IMPL_ISUPPORTS3(nsAlertsIconListener, imgINotificationObserver,
+                   nsIObserver, nsISupportsWeakReference)
 
 nsAlertsIconListener::nsAlertsIconListener()
 : mLoadedFrame(false),
   mNotification(NULL)
 {
   if (!libNotifyHandle && !libNotifyNotAvail) {
     libNotifyHandle = dlopen("libnotify.so.4", RTLD_LAZY);
     if (!libNotifyHandle) {
@@ -85,110 +85,49 @@ nsAlertsIconListener::nsAlertsIconListen
 nsAlertsIconListener::~nsAlertsIconListener()
 {
   if (mIconRequest)
     mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
   // Don't dlclose libnotify as it uses atexit().
 }
 
 NS_IMETHODIMP
-nsAlertsIconListener::OnStartRequest(imgIRequest* aRequest)
+nsAlertsIconListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
 {
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsAlertsIconListener::OnStartDecode(imgIRequest* aRequest)
-{
-  return NS_OK;
-}
-
+  if (aType == imgINotificationObserver::STOP_REQUEST) {
+    return OnStopRequest(aRequest);
+  }
 
-NS_IMETHODIMP
-nsAlertsIconListener::OnStartContainer(imgIRequest* aRequest,
-                                       imgIContainer* aContainer)
-{
-  return NS_OK;
-}
+  if (aType == imgINotificationObserver::STOP_FRAME) {
+    return OnStopFrame(aRequest);
+  }
 
-
-NS_IMETHODIMP
-nsAlertsIconListener::OnStartFrame(imgIRequest* aRequest,
-                                   uint32_t aFrame)
-{
   return NS_OK;
 }
 
-
-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)
+nsresult
+nsAlertsIconListener::OnStopRequest(imgIRequest* aRequest)
 {
   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;
 }
 
-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)
+nsresult
+nsAlertsIconListener::OnStopFrame(imgIRequest* aRequest)
 {
   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,51 +2,53 @@
 /* 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 "imgIDecoderObserver.h"
+#include "imgINotificationObserver.h"
 #include "nsStringAPI.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 
 #include <gdk-pixbuf/gdk-pixbuf.h>
 
 class imgIRequest;
 
 struct NotifyNotification;
 
-class nsAlertsIconListener : public imgIDecoderObserver,
+class nsAlertsIconListener : public imgINotificationObserver,
                              public nsIObserver,
                              public nsSupportsWeakReference
 {
 public:
   NS_DECL_ISUPPORTS
-  NS_DECL_IMGICONTAINEROBSERVER
-  NS_DECL_IMGIDECODEROBSERVER
+  NS_DECL_IMGINOTIFICATIONOBSERVER
   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,38 +6,37 @@
 /*
  * Retrieves and displays icons in native menu items on Mac OS X.
  */
 
 #ifndef nsMenuItemIconX_h_
 #define nsMenuItemIconX_h_
 
 #include "nsCOMPtr.h"
-#include "imgIDecoderObserver.h"
+#include "imgINotificationObserver.h"
 
 class nsIURI;
 class nsIContent;
 class imgIRequest;
 class nsMenuObjectX;
 
 #import <Cocoa/Cocoa.h>
 
-class nsMenuItemIconX : public imgIDecoderObserver
+class nsMenuItemIconX : public imgINotificationObserver
 {
 public:
   nsMenuItemIconX(nsMenuObjectX* aMenuItem,
                   nsIContent*    aContent,
                   NSMenuItem*    aNativeMenuItem);
 private:
   virtual ~nsMenuItemIconX();
 
 public:
   NS_DECL_ISUPPORTS
-  NS_DECL_IMGICONTAINEROBSERVER
-  NS_DECL_IMGIDECODEROBSERVER
+  NS_DECL_IMGINOTIFICATIONOBSERVER
 
   // 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);
 
@@ -47,16 +46,18 @@ 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_ISUPPORTS2(nsMenuItemIconX, imgIContainerObserver, imgIDecoderObserver)
+NS_IMPL_ISUPPORTS1(nsMenuItemIconX, imgINotificationObserver)
 
 nsMenuItemIconX::nsMenuItemIconX(nsMenuObjectX* aMenuItem,
                                  nsIContent*    aContent,
                                  NSMenuItem*    aNativeMenuItem)
 : mContent(aContent)
 , mMenuObject(aMenuItem)
 , mLoadedIcon(false)
 , mSetIcon(false)
@@ -313,67 +313,38 @@ nsMenuItemIconX::LoadIcon(nsIURI* aIconU
   mIconRequest->StartDecoding();
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 //
-// imgIContainerObserver
+// imgINotificationObserver
 //
 
 NS_IMETHODIMP
-nsMenuItemIconX::FrameChanged(imgIRequest* aRequest,
-                              imgIContainer*   aContainer,
-                              const nsIntRect* aDirtyRect)
+nsMenuItemIconX::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
 {
-  return NS_OK;
-}
+  if (aType == imgINotificationObserver::STOP_FRAME) {
+    return OnStopFrame(aRequest);
+  }
 
-//
-// imgIDecoderObserver
-//
+  if (aType == imgINotificationObserver::STOP_REQUEST) {
+    if (mIconRequest && mIconRequest == aRequest) {
+      mIconRequest->Cancel(NS_BINDING_ABORTED);
+      mIconRequest = nullptr;
+    }
+  }
 
-NS_IMETHODIMP
-nsMenuItemIconX::OnStartRequest(imgIRequest* aRequest)
-{
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsMenuItemIconX::OnStartDecode(imgIRequest* aRequest)
-{
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsMenuItemIconX::OnStartContainer(imgIRequest*   aRequest,
-                                  imgIContainer* aContainer)
-{
-  return NS_OK;
-}
-
-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)
+nsresult
+nsMenuItemIconX::OnStopFrame(imgIRequest*    aRequest)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   if (aRequest != mIconRequest)
     return NS_ERROR_FAILURE;
 
   // Only support one frame.
   if (mLoadedIcon)
@@ -486,46 +457,8 @@ 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;
-}