Bug 1112956 - Add IProgressObserver to permit more than one class to observe ProgressTracker. r=tn, a=sledru
authorSeth Fowler <seth@mozilla.com>
Wed, 07 Jan 2015 01:35:20 -0800
changeset 243670 842d25881e21
parent 243669 914dfaa20eef
child 243671 28f4806f60ee
push id4432
push userryanvm@gmail.com
push date2015-02-04 15:12 +0000
treeherdermozilla-beta@7422906b1a32 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn, sledru
bugs1112956
milestone36.0
Bug 1112956 - Add IProgressObserver to permit more than one class to observe ProgressTracker. r=tn, a=sledru
image/src/DecodePool.cpp
image/src/IProgressObserver.h
image/src/Image.h
image/src/ProgressTracker.cpp
image/src/ProgressTracker.h
image/src/imgRequest.cpp
image/src/imgRequestProxy.cpp
image/src/imgRequestProxy.h
image/src/moz.build
--- a/image/src/DecodePool.cpp
+++ b/image/src/DecodePool.cpp
@@ -7,16 +7,17 @@
 
 #include <algorithm>
 
 #include "mozilla/ClearOnShutdown.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIObserverService.h"
 #include "nsIThreadPool.h"
+#include "nsProxyRelease.h"
 #include "nsXPCOMCIDInternal.h"
 #include "prsystem.h"
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
 #include "gfxPrefs.h"
new file mode 100644
--- /dev/null
+++ b/image/src/IProgressObserver.h
@@ -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/. */
+
+#ifndef MOZILLA_IMAGELIB_PROGRESSOBSERVER_H_
+#define MOZILLA_IMAGELIB_PROGRESSOBSERVER_H_
+
+#include "mozilla/WeakPtr.h"
+#include "nsISupports.h"
+#include "nsRect.h"
+
+namespace mozilla {
+namespace image {
+
+/**
+ * An interface for observing changes to image state, as reported by
+ * ProgressTracker.
+ * 
+ * This is the ImageLib-internal version of imgINotificationObserver,
+ * essentially, with implementation details that code outside of ImageLib
+ * shouldn't see.
+ *
+ * XXX(seth): It's preferable to avoid adding anything to this interface if
+ * possible.  In the long term, it would be ideal to get to a place where we can
+ * just use the imgINotificationObserver interface internally as well.
+ */
+class IProgressObserver : public SupportsWeakPtr<IProgressObserver>
+{
+public:
+  MOZ_DECLARE_REFCOUNTED_TYPENAME(IProgressObserver)
+
+  // Subclasses may or may not be XPCOM classes, so we just require that they
+  // implement AddRef and Release.
+  NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
+  NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
+
+  // imgINotificationObserver methods:
+  virtual void Notify(int32_t aType, const nsIntRect* aRect = nullptr) = 0;
+  virtual void OnLoadComplete(bool aLastPart) = 0;
+
+  // imgIOnloadBlocker methods:
+  virtual void BlockOnload() = 0;
+  virtual void UnblockOnload() = 0;
+
+  // Other, internal-only methods:
+  virtual void SetHasImage() = 0;
+  virtual void OnStartDecode() = 0;
+  virtual bool NotificationsDeferred() const = 0;
+  virtual void SetNotificationsDeferred(bool aDeferNotifications) = 0;
+
+protected:
+  virtual ~IProgressObserver() { }
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // MOZILLA_IMAGELIB_PROGRESSOBSERVER_H_
--- a/image/src/Image.h
+++ b/image/src/Image.h
@@ -2,16 +2,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 MOZILLA_IMAGELIB_IMAGE_H_
 #define MOZILLA_IMAGELIB_IMAGE_H_
 
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/TimeStamp.h"
 #include "gfx2DGlue.h"                // for gfxMemoryLocation
 #include "imgIContainer.h"
 #include "ProgressTracker.h"
 #include "ImageURL.h"
 #include "nsStringFwd.h"
 
 class nsIRequest;
 class nsIInputStream;
--- a/image/src/ProgressTracker.cpp
+++ b/image/src/ProgressTracker.cpp
@@ -3,17 +3,18 @@
  * 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 "ImageLogging.h"
 #include "ProgressTracker.h"
 
 #include "imgIContainer.h"
-#include "imgRequestProxy.h"
+#include "imgINotificationObserver.h"
+#include "imgIRequest.h"
 #include "Image.h"
 #include "nsNetUtil.h"
 #include "nsIObserverService.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Services.h"
 
 using mozilla::WeakPtr;
@@ -166,333 +167,340 @@ ProgressTracker::GetImageStatus() const
   return status;
 }
 
 // A helper class to allow us to call SyncNotify asynchronously.
 class AsyncNotifyRunnable : public nsRunnable
 {
   public:
     AsyncNotifyRunnable(ProgressTracker* aTracker,
-                        imgRequestProxy* aRequestProxy)
+                        IProgressObserver* aObserver)
       : mTracker(aTracker)
     {
       MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
       MOZ_ASSERT(aTracker, "aTracker should not be null");
-      MOZ_ASSERT(aRequestProxy, "aRequestProxy should not be null");
-      mProxies.AppendElement(aRequestProxy);
+      MOZ_ASSERT(aObserver, "aObserver should not be null");
+      mObservers.AppendElement(aObserver);
     }
 
     NS_IMETHOD Run()
     {
       MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
       MOZ_ASSERT(mTracker, "mTracker should not be null");
-      for (uint32_t i = 0; i < mProxies.Length(); ++i) {
-        mProxies[i]->SetNotificationsDeferred(false);
-        mTracker->SyncNotify(mProxies[i]);
+      for (uint32_t i = 0; i < mObservers.Length(); ++i) {
+        mObservers[i]->SetNotificationsDeferred(false);
+        mTracker->SyncNotify(mObservers[i]);
       }
 
       mTracker->mRunnable = nullptr;
       return NS_OK;
     }
 
-    void AddProxy(imgRequestProxy* aRequestProxy)
+    void AddObserver(IProgressObserver* aObserver)
     {
-      mProxies.AppendElement(aRequestProxy);
+      mObservers.AppendElement(aObserver);
     }
 
-    void RemoveProxy(imgRequestProxy* aRequestProxy)
+    void RemoveObserver(IProgressObserver* aObserver)
     {
-      mProxies.RemoveElement(aRequestProxy);
+      mObservers.RemoveElement(aObserver);
     }
 
   private:
     friend class ProgressTracker;
 
     nsRefPtr<ProgressTracker> mTracker;
-    nsTArray<nsRefPtr<imgRequestProxy>> mProxies;
+    nsTArray<nsRefPtr<IProgressObserver>> mObservers;
 };
 
 void
-ProgressTracker::Notify(imgRequestProxy* proxy)
+ProgressTracker::Notify(IProgressObserver* aObserver)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "imgRequestProxy is not threadsafe");
+  MOZ_ASSERT(NS_IsMainThread());
+
 #ifdef PR_LOGGING
   if (mImage && mImage->GetURI()) {
     nsRefPtr<ImageURL> uri(mImage->GetURI());
     nsAutoCString spec;
     uri->GetSpec(spec);
     LOG_FUNC_WITH_PARAM(GetImgLog(),
                         "ProgressTracker::Notify async", "uri", spec.get());
   } else {
     LOG_FUNC_WITH_PARAM(GetImgLog(),
                         "ProgressTracker::Notify async", "uri", "<unknown>");
   }
 #endif
 
-  proxy->SetNotificationsDeferred(true);
+  aObserver->SetNotificationsDeferred(true);
 
-  // If we have an existing runnable that we can use, we just append this proxy
-  // to its list of proxies to be notified. This ensures we don't unnecessarily
-  // delay onload.
+  // If we have an existing runnable that we can use, we just append this
+  // observer to its list of observers to be notified. This ensures we don't
+  // unnecessarily delay onload.
   AsyncNotifyRunnable* runnable =
     static_cast<AsyncNotifyRunnable*>(mRunnable.get());
 
   if (runnable) {
-    runnable->AddProxy(proxy);
+    runnable->AddObserver(aObserver);
   } else {
-    mRunnable = new AsyncNotifyRunnable(this, proxy);
+    mRunnable = new AsyncNotifyRunnable(this, aObserver);
     NS_DispatchToCurrentThread(mRunnable);
   }
 }
 
 // A helper class to allow us to call SyncNotify asynchronously for a given,
 // fixed, state.
 class AsyncNotifyCurrentStateRunnable : public nsRunnable
 {
   public:
     AsyncNotifyCurrentStateRunnable(ProgressTracker* aProgressTracker,
-                                    imgRequestProxy* aProxy)
+                                    IProgressObserver* aObserver)
       : mProgressTracker(aProgressTracker)
-      , mProxy(aProxy)
+      , mObserver(aObserver)
     {
       MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
       MOZ_ASSERT(mProgressTracker, "mProgressTracker should not be null");
-      MOZ_ASSERT(mProxy, "mProxy should not be null");
+      MOZ_ASSERT(mObserver, "mObserver should not be null");
       mImage = mProgressTracker->GetImage();
     }
 
     NS_IMETHOD Run()
     {
       MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
-      mProxy->SetNotificationsDeferred(false);
+      mObserver->SetNotificationsDeferred(false);
 
-      mProgressTracker->SyncNotify(mProxy);
+      mProgressTracker->SyncNotify(mObserver);
       return NS_OK;
     }
 
   private:
     nsRefPtr<ProgressTracker> mProgressTracker;
-    nsRefPtr<imgRequestProxy> mProxy;
+    nsRefPtr<IProgressObserver> mObserver;
 
     // We have to hold on to a reference to the tracker's image, just in case
     // it goes away while we're in the event queue.
     nsRefPtr<Image> mImage;
 };
 
 void
-ProgressTracker::NotifyCurrentState(imgRequestProxy* proxy)
+ProgressTracker::NotifyCurrentState(IProgressObserver* aObserver)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "imgRequestProxy is not threadsafe");
+  MOZ_ASSERT(NS_IsMainThread());
+
 #ifdef PR_LOGGING
-  nsRefPtr<ImageURL> uri;
-  proxy->GetURI(getter_AddRefs(uri));
   nsAutoCString spec;
-  uri->GetSpec(spec);
+  if (mImage && mImage->GetURI()) {
+    mImage->GetURI()->GetSpec(spec);
+  }
   LOG_FUNC_WITH_PARAM(GetImgLog(),
                       "ProgressTracker::NotifyCurrentState", "uri", spec.get());
 #endif
 
-  proxy->SetNotificationsDeferred(true);
+  aObserver->SetNotificationsDeferred(true);
 
-  nsCOMPtr<nsIRunnable> ev = new AsyncNotifyCurrentStateRunnable(this, proxy);
+  nsCOMPtr<nsIRunnable> ev = new AsyncNotifyCurrentStateRunnable(this, aObserver);
   NS_DispatchToCurrentThread(ev);
 }
 
-#define NOTIFY_IMAGE_OBSERVERS(PROXIES, FUNC) \
+#define NOTIFY_IMAGE_OBSERVERS(OBSERVERS, FUNC) \
   do { \
-    ProxyArray::ForwardIterator iter(PROXIES); \
+    ObserverArray::ForwardIterator iter(OBSERVERS); \
     while (iter.HasMore()) { \
-      nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get(); \
-      if (proxy && !proxy->NotificationsDeferred()) { \
-        proxy->FUNC; \
+      nsRefPtr<IProgressObserver> observer = iter.GetNext().get(); \
+      if (observer && !observer->NotificationsDeferred()) { \
+        observer->FUNC; \
       } \
     } \
   } while (false);
 
 /* static */ void
-ProgressTracker::SyncNotifyInternal(ProxyArray& aProxies,
+ProgressTracker::SyncNotifyInternal(ObserverArray& aObservers,
                                     bool aHasImage,
                                     Progress aProgress,
                                     const nsIntRect& aDirtyRect)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  typedef imgINotificationObserver I;
+
   if (aProgress & FLAG_SIZE_AVAILABLE) {
-    NOTIFY_IMAGE_OBSERVERS(aProxies, OnSizeAvailable());
+    NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::SIZE_AVAILABLE));
   }
 
   if (aProgress & FLAG_DECODE_STARTED) {
-    NOTIFY_IMAGE_OBSERVERS(aProxies, OnStartDecode());
+    NOTIFY_IMAGE_OBSERVERS(aObservers, OnStartDecode());
   }
 
   if (aProgress & FLAG_ONLOAD_BLOCKED) {
-    NOTIFY_IMAGE_OBSERVERS(aProxies, BlockOnload());
+    NOTIFY_IMAGE_OBSERVERS(aObservers, BlockOnload());
   }
 
   if (aHasImage) {
     // OnFrameUpdate
     // If there's any content in this frame at all (always true for
     // vector images, true for raster images that have decoded at
     // least one frame) then send OnFrameUpdate.
     if (!aDirtyRect.IsEmpty()) {
-      NOTIFY_IMAGE_OBSERVERS(aProxies, OnFrameUpdate(&aDirtyRect));
+      NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::FRAME_UPDATE, &aDirtyRect));
     }
 
     if (aProgress & FLAG_FRAME_COMPLETE) {
-      NOTIFY_IMAGE_OBSERVERS(aProxies, OnFrameComplete());
+      NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::FRAME_COMPLETE));
     }
 
     if (aProgress & FLAG_HAS_TRANSPARENCY) {
-      NOTIFY_IMAGE_OBSERVERS(aProxies, OnImageHasTransparency());
+      NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::HAS_TRANSPARENCY));
     }
 
     if (aProgress & FLAG_IS_ANIMATED) {
-      NOTIFY_IMAGE_OBSERVERS(aProxies, OnImageIsAnimated());
+      NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::IS_ANIMATED));
     }
   }
 
   // Send UnblockOnload before OnStopDecode and OnStopRequest. This allows
   // observers that can fire events when they receive those notifications to do
   // so then, instead of being forced to wait for UnblockOnload.
   if (aProgress & FLAG_ONLOAD_UNBLOCKED) {
-    NOTIFY_IMAGE_OBSERVERS(aProxies, UnblockOnload());
+    NOTIFY_IMAGE_OBSERVERS(aObservers, UnblockOnload());
   }
 
   if (aProgress & FLAG_DECODE_COMPLETE) {
     MOZ_ASSERT(aHasImage, "Stopped decoding without ever having an image?");
-    NOTIFY_IMAGE_OBSERVERS(aProxies, OnDecodeComplete());
+    NOTIFY_IMAGE_OBSERVERS(aObservers, Notify(I::DECODE_COMPLETE));
   }
 
   if (aProgress & FLAG_LOAD_COMPLETE) {
-    NOTIFY_IMAGE_OBSERVERS(aProxies,
+    NOTIFY_IMAGE_OBSERVERS(aObservers,
                            OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE));
   }
 }
 
 void
 ProgressTracker::SyncNotifyProgress(Progress aProgress,
                                     const nsIntRect& aInvalidRect
                                                   /* = nsIntRect() */)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
+  MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
 
   // Don't unblock onload if we're not blocked.
   Progress progress = Difference(aProgress);
   if (!((mProgress | progress) & FLAG_ONLOAD_BLOCKED)) {
     progress &= ~FLAG_ONLOAD_UNBLOCKED;
   }
 
   // Apply the changes.
   mProgress |= progress;
 
   CheckProgressConsistency(mProgress);
 
   // Send notifications.
-  SyncNotifyInternal(mConsumers, !!mImage, progress, aInvalidRect);
+  SyncNotifyInternal(mObservers, !!mImage, progress, aInvalidRect);
 
   if (progress & FLAG_HAS_ERROR) {
     FireFailureNotification();
   }
 }
 
 void
-ProgressTracker::SyncNotify(imgRequestProxy* proxy)
+ProgressTracker::SyncNotify(IProgressObserver* aObserver)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "imgRequestProxy is not threadsafe");
+  MOZ_ASSERT(NS_IsMainThread());
+
 #ifdef PR_LOGGING
-  nsRefPtr<ImageURL> uri;
-  proxy->GetURI(getter_AddRefs(uri));
   nsAutoCString spec;
-  uri->GetSpec(spec);
+  if (mImage && mImage->GetURI()) {
+    mImage->GetURI()->GetSpec(spec);
+  }
   LOG_SCOPE_WITH_PARAM(GetImgLog(),
                        "ProgressTracker::SyncNotify", "uri", spec.get());
 #endif
 
   nsIntRect r;
   if (mImage) {
     // XXX - Should only send partial rects here, but that needs to
     // wait until we fix up the observer interface
     r = mImage->FrameRect(imgIContainer::FRAME_CURRENT);
   }
 
-  ProxyArray array;
-  array.AppendElement(proxy);
+  ObserverArray array;
+  array.AppendElement(aObserver);
   SyncNotifyInternal(array, !!mImage, mProgress, r);
 }
 
 void
-ProgressTracker::EmulateRequestFinished(imgRequestProxy* aProxy,
+ProgressTracker::EmulateRequestFinished(IProgressObserver* aObserver,
                                         nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread(),
-             "SyncNotifyState and mConsumers are not threadsafe");
-  nsCOMPtr<imgIRequest> kungFuDeathGrip(aProxy);
+             "SyncNotifyState and mObservers are not threadsafe");
+  nsRefPtr<IProgressObserver> kungFuDeathGrip(aObserver);
 
   if (mProgress & FLAG_ONLOAD_BLOCKED && !(mProgress & FLAG_ONLOAD_UNBLOCKED)) {
-    aProxy->UnblockOnload();
+    aObserver->UnblockOnload();
   }
 
   if (!(mProgress & FLAG_LOAD_COMPLETE)) {
-    aProxy->OnLoadComplete(true);
+    aObserver->OnLoadComplete(true);
   }
 }
 
 void
-ProgressTracker::AddConsumer(imgRequestProxy* aConsumer)
+ProgressTracker::AddObserver(IProgressObserver* aObserver)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mConsumers.AppendElementUnlessExists(aConsumer);
+  mObservers.AppendElementUnlessExists(aObserver);
 }
 
 // XXX - The last argument should go away.
 bool
-ProgressTracker::RemoveConsumer(imgRequestProxy* aConsumer, nsresult aStatus)
+ProgressTracker::RemoveObserver(IProgressObserver* aObserver, nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  // Remove the proxy from the list.
-  bool removed = mConsumers.RemoveElement(aConsumer);
+
+  // Remove the observer from the list.
+  bool removed = mObservers.RemoveElement(aObserver);
 
-  // Consumers can get confused if they don't get all the proper teardown
+  // Observers can get confused if they don't get all the proper teardown
   // notifications. Part ways on good terms.
-  if (removed && !aConsumer->NotificationsDeferred()) {
-    EmulateRequestFinished(aConsumer, aStatus);
+  if (removed && !aObserver->NotificationsDeferred()) {
+    EmulateRequestFinished(aObserver, aStatus);
   }
 
-  // Make sure we don't give callbacks to a consumer that isn't interested in
+  // Make sure we don't give callbacks to an observer that isn't interested in
   // them any more.
   AsyncNotifyRunnable* runnable =
     static_cast<AsyncNotifyRunnable*>(mRunnable.get());
 
-  if (aConsumer->NotificationsDeferred() && runnable) {
-    runnable->RemoveProxy(aConsumer);
-    aConsumer->SetNotificationsDeferred(false);
+  if (aObserver->NotificationsDeferred() && runnable) {
+    runnable->RemoveObserver(aObserver);
+    aObserver->SetNotificationsDeferred(false);
   }
 
   return removed;
 }
 
 bool
-ProgressTracker::FirstConsumerIs(imgRequestProxy* aConsumer)
+ProgressTracker::FirstObserverIs(IProgressObserver* aObserver)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
-  ProxyArray::ForwardIterator iter(mConsumers);
+  MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
+  ObserverArray::ForwardIterator iter(mObservers);
   while (iter.HasMore()) {
-    nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
-    if (proxy) {
-      return proxy.get() == aConsumer;
+    nsRefPtr<IProgressObserver> observer = iter.GetNext().get();
+    if (observer) {
+      return observer.get() == aObserver;
     }
   }
   return false;
 }
 
 void
 ProgressTracker::OnUnlockedDraw()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  NOTIFY_IMAGE_OBSERVERS(mConsumers, OnUnlockedDraw());
+  NOTIFY_IMAGE_OBSERVERS(mObservers,
+                         Notify(imgINotificationObserver::UNLOCKED_DRAW));
 }
 
 void
 ProgressTracker::ResetForNewRequest()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // We're starting a new load (and if this is called more than once, this is a
@@ -502,32 +510,33 @@ ProgressTracker::ResetForNewRequest()
 
   CheckProgressConsistency(mProgress);
 }
 
 void
 ProgressTracker::OnDiscard()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  NOTIFY_IMAGE_OBSERVERS(mConsumers, OnDiscard());
+  NOTIFY_IMAGE_OBSERVERS(mObservers,
+                         Notify(imgINotificationObserver::DISCARD));
 }
 
 void
 ProgressTracker::OnImageAvailable()
 {
   if (!NS_IsMainThread()) {
     // Note: SetHasImage calls Image::Lock and Image::IncrementAnimationCounter
     // so subsequent calls or dispatches which Unlock or Decrement~ should
     // be issued after this to avoid race conditions.
     NS_DispatchToMainThread(
       NS_NewRunnableMethod(this, &ProgressTracker::OnImageAvailable));
     return;
   }
 
-  NOTIFY_IMAGE_OBSERVERS(mConsumers, SetHasImage());
+  NOTIFY_IMAGE_OBSERVERS(mObservers, SetHasImage());
 }
 
 void
 ProgressTracker::FireFailureNotification()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Some kind of problem has happened with image decoding.
--- a/image/src/ProgressTracker.h
+++ b/image/src/ProgressTracker.h
@@ -8,17 +8,17 @@
 #define ProgressTracker_h__
 
 #include "mozilla/RefPtr.h"
 #include "mozilla/WeakPtr.h"
 #include "nsCOMPtr.h"
 #include "nsTObserverArray.h"
 #include "nsThreadUtils.h"
 #include "nsRect.h"
-#include "imgRequestProxy.h"
+#include "IProgressObserver.h"
 
 class imgIContainer;
 class nsIRunnable;
 
 namespace mozilla {
 namespace image {
 
 class AsyncNotifyRunnable;
@@ -57,21 +57,22 @@ inline Progress LoadCompleteProgress(boo
     progress |= FLAG_HAS_ERROR;
   }
   return progress;
 }
 
 /**
  * ProgressTracker is a class that records an Image's progress through the
  * loading and decoding process, and makes it possible to send notifications to
- * imgRequestProxys, both synchronously and asynchronously.
+ * IProgressObservers, both synchronously and asynchronously.
  *
- * When a new proxy needs to be notified of the current progress of an image,
- * call the Notify() method on this class with the relevant proxy as its
- * argument, and the notifications will be replayed to the proxy asynchronously.
+ * When a new observer needs to be notified of the current progress of an image,
+ * call the Notify() method on this class with the relevant observer as its
+ * argument, and the notifications will be replayed to the observer
+ * asynchronously.
  */
 class ProgressTracker : public mozilla::SupportsWeakPtr<ProgressTracker>
 {
   virtual ~ProgressTracker() { }
 
 public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(ProgressTracker)
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProgressTracker)
@@ -102,38 +103,38 @@ public:
   uint32_t GetImageStatus() const;
 
   // Get the current Progress.
   Progress GetProgress() const { return mProgress; }
 
   // Schedule an asynchronous "replaying" of all the notifications that would
   // have to happen to put us in the current state.
   // We will also take note of any notifications that happen between the time
-  // Notify() is called and when we call SyncNotify on |proxy|, and replay them
-  // as well.
-  // Should be called on the main thread only, since imgRequestProxy and GetURI
-  // are not threadsafe.
-  void Notify(imgRequestProxy* proxy);
+  // Notify() is called and when we call SyncNotify on |aObserver|, and replay
+  // them as well.
+  // Should be called on the main thread only, since observers and GetURI are
+  // not threadsafe.
+  void Notify(IProgressObserver* aObserver);
 
   // Schedule an asynchronous "replaying" of all the notifications that would
   // have to happen to put us in the state we are in right now.
   // Unlike Notify(), does *not* take into account future notifications.
   // This is only useful if you do not have an imgRequest, e.g., if you are a
   // static request returned from imgIRequest::GetStaticRequest().
-  // Should be called on the main thread only, since imgRequestProxy and GetURI
-  // are not threadsafe.
-  void NotifyCurrentState(imgRequestProxy* proxy);
+  // Should be called on the main thread only, since observers and GetURI are
+  // not threadsafe.
+  void NotifyCurrentState(IProgressObserver* aObserver);
 
   // "Replay" all of the notifications that would have to happen to put us in
   // the state we're currently in.
   // Only use this if you're already servicing an asynchronous call (e.g.
   // OnStartRequest).
-  // Should be called on the main thread only, since imgRequestProxy and GetURI
-  // are not threadsafe.
-  void SyncNotify(imgRequestProxy* proxy);
+  // Should be called on the main thread only, since observers and GetURI are
+  // not threadsafe.
+  void SyncNotify(IProgressObserver* aObserver);
 
   // Get this ProgressTracker ready for a new request. This resets all the
   // state that doesn't persist between requests.
   void ResetForNewRequest();
 
   // Stateless notifications. These are dispatched and immediately forgotten
   // about. All except OnImageAvailable are main thread only.
   void OnDiscard();
@@ -150,75 +151,75 @@ public:
   // Update our state to incorporate the changes in aProgress and synchronously
   // notify our observers.
   //
   // Because this may result in recursive notifications, no decoding locks may
   // be held.  Called on the main thread only.
   void SyncNotifyProgress(Progress aProgress,
                           const nsIntRect& aInvalidRect = nsIntRect());
 
-  // We manage a set of consumers that are using an image and thus concerned
+  // We manage a set of observers that are using an image and thus concerned
   // with its loading progress. Weak pointers.
-  void AddConsumer(imgRequestProxy* aConsumer);
-  bool RemoveConsumer(imgRequestProxy* aConsumer, nsresult aStatus);
-  size_t ConsumerCount() const {
-    MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
-    return mConsumers.Length();
+  void AddObserver(IProgressObserver* aObserver);
+  bool RemoveObserver(IProgressObserver* aObserver, nsresult aStatus);
+  size_t ObserverCount() const {
+    MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
+    return mObservers.Length();
   }
 
   // This is intentionally non-general because its sole purpose is to support
   // some obscure network priority logic in imgRequest. That stuff could
   // probably be improved, but it's too scary to mess with at the moment.
-  bool FirstConsumerIs(imgRequestProxy* aConsumer);
+  bool FirstObserverIs(IProgressObserver* aObserver);
 
-  void AdoptConsumers(ProgressTracker* aTracker) {
-    MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
+  void AdoptObservers(ProgressTracker* aTracker) {
+    MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
     MOZ_ASSERT(aTracker);
-    mConsumers = aTracker->mConsumers;
+    mObservers = aTracker->mObservers;
   }
 
 private:
-  typedef nsTObserverArray<mozilla::WeakPtr<imgRequestProxy>> ProxyArray;
+  typedef nsTObserverArray<mozilla::WeakPtr<IProgressObserver>> ObserverArray;
   friend class AsyncNotifyRunnable;
   friend class AsyncNotifyCurrentStateRunnable;
   friend class ProgressTrackerInit;
 
   ProgressTracker(const ProgressTracker& aOther) MOZ_DELETE;
 
   // This method should only be called once, and only on an ProgressTracker
   // that was initialized without an image. ProgressTrackerInit automates this.
   void SetImage(Image* aImage);
 
   // Resets our weak reference to our image, for when mImage is about to go out
   // of scope.  ProgressTrackerInit automates this.
   void ResetImage();
 
-  // Send some notifications that would be necessary to make |aProxy| believe
+  // Send some notifications that would be necessary to make |aObserver| believe
   // the request is finished downloading and decoding.  We only send
-  // FLAG_REQUEST_* and FLAG_ONLOAD_UNBLOCKED, and only if necessary.
-  void EmulateRequestFinished(imgRequestProxy* aProxy, nsresult aStatus);
+  // FLAG_LOAD_COMPLETE and FLAG_ONLOAD_UNBLOCKED, and only if necessary.
+  void EmulateRequestFinished(IProgressObserver* aObserver, nsresult aStatus);
 
   // Main thread only because it deals with the observer service.
   void FireFailureNotification();
 
-  // Main thread only, since imgRequestProxy calls are expected on the main
-  // thread, and mConsumers is not threadsafe.
-  static void SyncNotifyInternal(ProxyArray& aProxies,
+  // Main thread only, since notifications are expected on the main thread, and
+  // mObservers is not threadsafe.
+  static void SyncNotifyInternal(ObserverArray& aObservers,
                                  bool aHasImage, Progress aProgress,
                                  const nsIntRect& aInvalidRect);
 
   nsCOMPtr<nsIRunnable> mRunnable;
 
   // This weak ref should be set null when the image goes out of scope.
   Image* mImage;
 
-  // List of proxies attached to the image. Each proxy represents a consumer
-  // using the image. Array and/or individual elements should only be accessed
-  // on the main thread.
-  ProxyArray mConsumers;
+  // List of observers attached to the image. Each observer represents a
+  // consumer using the image. Array and/or individual elements should only be
+  // accessed on the main thread.
+  ObserverArray mObservers;
 
   Progress mProgress;
 };
 
 class ProgressTrackerInit
 {
 public:
   ProgressTrackerInit(Image* aImage, ProgressTracker* aTracker);
--- a/image/src/imgRequest.cpp
+++ b/image/src/imgRequest.cpp
@@ -175,44 +175,44 @@ void imgRequest::ResetCacheEntry()
 void imgRequest::AddProxy(imgRequestProxy *proxy)
 {
   NS_PRECONDITION(proxy, "null imgRequestProxy passed in");
   LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::AddProxy", "proxy", proxy);
 
   // If we're empty before adding, we have to tell the loader we now have
   // proxies.
   nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
-  if (progressTracker->ConsumerCount() == 0) {
+  if (progressTracker->ObserverCount() == 0) {
     NS_ABORT_IF_FALSE(mURI, "Trying to SetHasProxies without key uri.");
     if (mLoader) {
       mLoader->SetHasProxies(this);
     }
   }
 
-  progressTracker->AddConsumer(proxy);
+  progressTracker->AddObserver(proxy);
 }
 
 nsresult imgRequest::RemoveProxy(imgRequestProxy *proxy, nsresult aStatus)
 {
   LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::RemoveProxy", "proxy", proxy);
 
   // This will remove our animation consumers, so after removing
   // this proxy, we don't end up without proxies with observers, but still
   // have animation consumers.
   proxy->ClearAnimationConsumers();
 
   // Let the status tracker do its thing before we potentially call Cancel()
   // below, because Cancel() may result in OnStopRequest being called back
   // before Cancel() returns, leaving the image in a different state then the
   // one it was in at this point.
   nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
-  if (!progressTracker->RemoveConsumer(proxy, aStatus))
+  if (!progressTracker->RemoveObserver(proxy, aStatus))
     return NS_OK;
 
-  if (progressTracker->ConsumerCount() == 0) {
+  if (progressTracker->ObserverCount() == 0) {
     // If we have no observers, there's nothing holding us alive. If we haven't
     // been cancelled and thus removed from the cache, tell the image loader so
     // we can be evicted from the cache.
     if (mCacheEntry) {
       NS_ABORT_IF_FALSE(mURI, "Removing last observer without key uri.");
 
       if (mLoader) {
         mLoader->SetHasNoProxies(this, mCacheEntry);
@@ -412,17 +412,17 @@ void imgRequest::RemoveFromCache()
   }
 
   mCacheEntry = nullptr;
 }
 
 bool imgRequest::HasConsumers()
 {
   nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
-  return progressTracker && progressTracker->ConsumerCount() > 0;
+  return progressTracker && progressTracker->ObserverCount() > 0;
 }
 
 int32_t imgRequest::Priority() const
 {
   int32_t priority = nsISupportsPriority::PRIORITY_NORMAL;
   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest);
   if (p)
     p->GetPriority(&priority);
@@ -434,17 +434,17 @@ void imgRequest::AdjustPriority(imgReque
   // only the first proxy is allowed to modify the priority of this image load.
   //
   // XXX(darin): this is probably not the most optimal algorithm as we may want
   // to increase the priority of requests that have a lot of proxies.  the key
   // concern though is that image loads remain lower priority than other pieces
   // of content such as link clicks, CSS, and JS.
   //
   nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
-  if (!progressTracker->FirstConsumerIs(proxy))
+  if (!progressTracker->FirstObserverIs(proxy))
     return;
 
   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
   if (p)
     p->AdjustPriority(delta);
 }
 
 void imgRequest::SetIsInCache(bool incache)
@@ -696,17 +696,17 @@ NS_IMETHODIMP imgRequest::OnStartRequest
     }
   }
 
   SetCacheValidation(mCacheEntry, aRequest);
 
   mApplicationCache = GetApplicationCache(aRequest);
 
   // Shouldn't we be dead already if this gets hit?  Probably multipart/x-mixed-replace...
-  if (progressTracker->ConsumerCount() == 0) {
+  if (progressTracker->ObserverCount() == 0) {
     this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
   }
 
   // Try to retarget OnDataAvailable to a decode thread.
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
   nsCOMPtr<nsIThreadRetargetableRequest> retargetable =
     do_QueryInterface(aRequest);
   if (httpChannel && retargetable &&
@@ -897,17 +897,17 @@ imgRequest::OnDataAvailable(nsIRequest *
         MOZ_ASSERT(mIsMultiPartChannel, "Resniffing a non-multipart image");
 
         // Initialize a new status tracker.
         nsRefPtr<ProgressTracker> freshTracker = new ProgressTracker(nullptr);
         freshTracker->SetIsMultipart();
 
         // Replace the old status tracker with it.
         nsRefPtr<ProgressTracker> oldProgressTracker = GetProgressTracker();
-        freshTracker->AdoptConsumers(oldProgressTracker);
+        freshTracker->AdoptObservers(oldProgressTracker);
         mProgressTracker = freshTracker.forget();
       }
 
       SetProperties(chan);
 
       LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "content type", mContentType.get());
 
       // XXX If server lied about mimetype and it's SVG, we may need to copy
--- a/image/src/imgRequestProxy.cpp
+++ b/image/src/imgRequestProxy.cpp
@@ -729,108 +729,62 @@ void imgRequestProxy::OnStartDecode()
     // OnStartDecodes which indicates the beginning of a new decode.  The cache
     // entry's size therefore needs to be reset to 0 here.  If we do not do
     // this, the code in ProgressTrackerObserver::OnStopFrame will continue to
     // increase the data size cumulatively.
     GetOwner()->ResetCacheEntry();
   }
 }
 
-void imgRequestProxy::OnSizeAvailable()
-{
-  LOG_FUNC(GetImgLog(), "imgRequestProxy::OnStartContainer");
-
-  if (mListener && !mCanceled) {
-    // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::SIZE_AVAILABLE, nullptr);
-  }
-}
-
-void imgRequestProxy::OnFrameUpdate(const nsIntRect * rect)
+static const char*
+NotificationTypeToString(int32_t aType)
 {
-  LOG_FUNC(GetImgLog(), "imgRequestProxy::OnFrameUpdate");
-
-  if (mListener && !mCanceled) {
-    // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::FRAME_UPDATE, rect);
-  }
-}
-
-void imgRequestProxy::OnFrameComplete()
-{
-  LOG_FUNC(GetImgLog(), "imgRequestProxy::OnFrameComplete");
-
-  if (mListener && !mCanceled) {
-    // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::FRAME_COMPLETE, nullptr);
-  }
-}
-
-void imgRequestProxy::OnDecodeComplete()
-{
-  LOG_FUNC(GetImgLog(), "imgRequestProxy::OnDecodeComplete");
-
-  if (mListener && !mCanceled) {
-    // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::DECODE_COMPLETE, nullptr);
+  switch(aType)
+  {
+    case imgINotificationObserver::SIZE_AVAILABLE: return "SIZE_AVAILABLE";
+    case imgINotificationObserver::FRAME_UPDATE: return "FRAME_UPDATE";
+    case imgINotificationObserver::FRAME_COMPLETE: return "FRAME_COMPLETE";
+    case imgINotificationObserver::LOAD_COMPLETE: return "LOAD_COMPLETE";
+    case imgINotificationObserver::DECODE_COMPLETE: return "DECODE_COMPLETE";
+    case imgINotificationObserver::DISCARD: return "DISCARD";
+    case imgINotificationObserver::UNLOCKED_DRAW: return "UNLOCKED_DRAW";
+    case imgINotificationObserver::IS_ANIMATED: return "IS_ANIMATED";
+    case imgINotificationObserver::HAS_TRANSPARENCY: return "HAS_TRANSPARENCY";
+    default:
+      NS_NOTREACHED("Notification list should be exhaustive");
+      return "(unknown notification)";
   }
 }
 
-void imgRequestProxy::OnDiscard()
+void
+imgRequestProxy::Notify(int32_t aType, const nsIntRect* aRect)
 {
-  LOG_FUNC(GetImgLog(), "imgRequestProxy::OnDiscard");
+  MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE,
+             "Should call OnLoadComplete");
 
-  if (mListener && !mCanceled) {
-    // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::DISCARD, nullptr);
+  LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::Notify", "type",
+                      NotificationTypeToString(aType));
+
+  if (!mListener || mCanceled) {
+    return;
   }
-}
-
-void imgRequestProxy::OnUnlockedDraw()
-{
-  LOG_FUNC(GetImgLog(), "imgRequestProxy::OnUnlockedDraw");
 
-  if (mListener && !mCanceled) {
-    // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::UNLOCKED_DRAW, nullptr);
-  }
+  // Make sure the listener stays alive while we notify.
+  nsCOMPtr<imgINotificationObserver> listener(mListener);
+
+  mListener->Notify(this, aType, aRect);
 }
 
-void imgRequestProxy::OnImageHasTransparency()
-{
-  LOG_FUNC(GetImgLog(), "imgRequestProxy::OnImageHasTransparency");
-  if (mListener && !mCanceled) {
-    // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::HAS_TRANSPARENCY, nullptr);
-  }
-}
-
-void imgRequestProxy::OnImageIsAnimated()
-{
-  LOG_FUNC(GetImgLog(), "imgRequestProxy::OnImageIsAnimated");
-  if (mListener && !mCanceled) {
-    // Hold a ref to the listener while we call it, just in case.
-    nsCOMPtr<imgINotificationObserver> kungFuDeathGrip(mListener);
-    mListener->Notify(this, imgINotificationObserver::IS_ANIMATED, nullptr);
-  }
-}
-
-void imgRequestProxy::OnLoadComplete(bool aLastPart)
+void
+imgRequestProxy::OnLoadComplete(bool aLastPart)
 {
 #ifdef PR_LOGGING
   nsAutoCString name;
   GetName(name);
-  LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::OnStopRequest", "name", name.get());
+  LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::OnLoadComplete", "name", name.get());
 #endif
   // There's all sorts of stuff here that could kill us (the OnStopRequest call
   // on the listener, the removal from the loadgroup, the release of the
   // listener, etc).  Don't let them do it.
   nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
 
   if (mListener && !mCanceled) {
     // Hold a ref to the listener while we call it, just in case.
@@ -858,31 +812,33 @@ void imgRequestProxy::OnLoadComplete(boo
     // everything.  Note that this can cancel us and other fun things
     // like that.  Don't add anything in this method after this point.
     imgINotificationObserver* obs = mListener;
     mListenerIsStrongRef = false;
     NS_RELEASE(obs);
   }
 }
 
-void imgRequestProxy::BlockOnload()
+void
+imgRequestProxy::BlockOnload()
 {
 #ifdef PR_LOGGING
   nsAutoCString name;
   GetName(name);
   LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::BlockOnload", "name", name.get());
 #endif
 
   nsCOMPtr<imgIOnloadBlocker> blocker = do_QueryInterface(mListener);
   if (blocker) {
     blocker->BlockOnload(this);
   }
 }
 
-void imgRequestProxy::UnblockOnload()
+void
+imgRequestProxy::UnblockOnload()
 {
 #ifdef PR_LOGGING
   nsAutoCString name;
   GetName(name);
   LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::UnblockOnload", "name", name.get());
 #endif
 
   nsCOMPtr<imgIOnloadBlocker> blocker = do_QueryInterface(mListener);
--- a/image/src/imgRequestProxy.h
+++ b/image/src/imgRequestProxy.h
@@ -2,29 +2,29 @@
  *
  * 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 "mozilla/WeakPtr.h"
 #include "imgIRequest.h"
 #include "nsISecurityInfoProvider.h"
 
 #include "nsILoadGroup.h"
 #include "nsISupportsPriority.h"
 #include "nsITimedChannel.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
 #include "mozilla/TimeStamp.h"
 
 #include "imgRequest.h"
+#include "IProgressObserver.h"
 
 #define NS_IMGREQUESTPROXY_CID \
 { /* 20557898-1dd2-11b2-8f65-9c462ee2bc95 */         \
      0x20557898,                                     \
      0x1dd2,                                         \
      0x11b2,                                         \
     {0x8f, 0x65, 0x9c, 0x46, 0x2e, 0xe2, 0xbc, 0x95} \
 }
@@ -39,20 +39,20 @@ namespace mozilla {
 namespace image {
 class Image;
 class ImageURL;
 class ProgressTracker;
 } // namespace image
 } // namespace mozilla
 
 class imgRequestProxy : public imgIRequest,
+                        public mozilla::image::IProgressObserver,
                         public nsISupportsPriority,
                         public nsISecurityInfoProvider,
-                        public nsITimedChannel,
-                        public mozilla::SupportsWeakPtr<imgRequestProxy>
+                        public nsITimedChannel
 {
 protected:
   virtual ~imgRequestProxy();
 
 public:
   typedef mozilla::image::Image Image;
   typedef mozilla::image::ImageURL ImageURL;
   typedef mozilla::image::ProgressTracker ProgressTracker;
@@ -90,30 +90,40 @@ public:
   // notification is scheduled.
   void NotifyListener();
 
   // Synchronously notify this proxy's listener of the current state of the
   // image. Only use this function if you are currently servicing an
   // asynchronously-called function.
   void SyncNotifyListener();
 
+  // imgINotificationObserver methods:
+  virtual void Notify(int32_t aType,
+                      const nsIntRect* aRect = nullptr) MOZ_OVERRIDE;
+  virtual void OnLoadComplete(bool aLastPart) MOZ_OVERRIDE;
+
+  // imgIOnloadBlocker methods:
+  virtual void BlockOnload() MOZ_OVERRIDE;
+  virtual void UnblockOnload() MOZ_OVERRIDE;
+
+  // Other, internal-only methods:
+  virtual void SetHasImage() MOZ_OVERRIDE;
+  virtual void OnStartDecode() MOZ_OVERRIDE;
+
   // Whether we want notifications from ProgressTracker to be deferred until
   // an event it has scheduled has been fired.
-  bool NotificationsDeferred() const
+  virtual bool NotificationsDeferred() const MOZ_OVERRIDE
   {
     return mDeferNotifications;
   }
-  void SetNotificationsDeferred(bool aDeferNotifications)
+  virtual void SetNotificationsDeferred(bool aDeferNotifications) MOZ_OVERRIDE
   {
     mDeferNotifications = aDeferNotifications;
   }
 
-  // XXXbholley - This eventually gets folded into the new notification API.
-  void SetHasImage();
-
   // Removes all animation consumers that were created with
   // IncrementAnimationConsumers. This is necessary since we need
   // to do it before the proxy itself is destroyed. See
   // imgRequest::RemoveProxy
   void ClearAnimationConsumers();
 
   virtual nsresult Clone(imgINotificationObserver* aObserver, imgRequestProxy** aClone);
   nsresult GetStaticRequest(imgRequestProxy** aReturn);
@@ -140,37 +150,16 @@ protected:
         return NS_OK;
       }
 
     private:
       nsRefPtr<imgRequestProxy> mOwner;
       nsresult mStatus;
   };
 
-  // The following notification functions are protected to ensure that (friend
-  // class) ProgressTracker is the only class allowed to send us
-  // notifications.
-
-  void OnStartDecode();
-  void OnSizeAvailable();
-  void OnFrameUpdate(const nsIntRect* aRect);
-  void OnFrameComplete();
-  void OnDecodeComplete();
-  void OnDiscard();
-  void OnUnlockedDraw();
-  void OnImageHasTransparency();
-  void OnImageIsAnimated();
-
-  /* non-virtual sort-of-nsIRequestObserver methods */
-  void OnLoadComplete(bool aLastPart);
-
-  /* non-virtual imgIOnloadBlocker methods */
-  void BlockOnload();
-  void UnblockOnload();
-
   /* Finish up canceling ourselves */
   void DoCancel(nsresult status);
 
   /* Do the proper refcount management to null out mListener */
   void NullOutListener();
 
   void DoRemoveFromLoadGroup() {
     RemoveFromLoadGroup(true);
--- a/image/src/moz.build
+++ b/image/src/moz.build
@@ -5,18 +5,19 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     'ImageOps.h',
     'ImageRegion.h',
     'imgLoader.h',
     'imgRequest.h',
     'imgRequestProxy.h',
+    'IProgressObserver.h',
     'Orientation.h',
-    'SurfaceCache.h'
+    'SurfaceCache.h',
 ]
 
 UNIFIED_SOURCES += [
     'ClippedImage.cpp',
     'DecodePool.cpp',
     'Decoder.cpp',
     'DynamicImage.cpp',
     'FrameAnimator.cpp',