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 118670 307acd23def9b82406f153ad8a5690aaa55963cb
parent 118669 0645314aa7c2c327c42adad5ec2e5cfea93a1b3d
child 118671 2d223dd76a6270141031cb467a81da8d56c27e9e
push id273
push userlsblakk@mozilla.com
push dateThu, 14 Feb 2013 23:19:38 +0000
treeherdermozilla-release@c5e807a3f8b8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoe
bugs505385
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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;
-}