Bug 505385 - Part 9: Hoist decoder and container observer out of imgRequest into imgStatusTracker. r=joe
authorJosh Matthews <josh@joshmatthews.net>
Thu, 11 Oct 2012 21:34:23 -0400
changeset 110153 1544cc1b035ddad74d55a2647dc0b20660d2531d
parent 110152 4c750723535fdf1f0e3e91ca5330ce7c46d57841
child 110154 0645314aa7c2c327c42adad5ec2e5cfea93a1b3d
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersjoe
bugs505385
milestone19.0a1
Bug 505385 - Part 9: Hoist decoder and container observer out of imgRequest into imgStatusTracker. r=joe
image/src/imgRequest.cpp
image/src/imgRequest.h
image/src/imgRequestProxy.cpp
image/src/imgRequestProxy.h
image/src/imgStatusTracker.cpp
image/src/imgStatusTracker.h
--- a/image/src/imgRequest.cpp
+++ b/image/src/imgRequest.cpp
@@ -66,36 +66,33 @@ InitPrefCaches()
   Preferences::AddBoolVarCache(&gDecodeOnDraw, "image.mem.decodeondraw");
   gInitializedPrefCaches = true;
 }
 
 #if defined(PR_LOGGING)
 PRLogModuleInfo *gImgLog = PR_NewLogModule("imgRequest");
 #endif
 
-NS_IMPL_ISUPPORTS8(imgRequest,
-                   imgIDecoderObserver, imgIContainerObserver,
+NS_IMPL_ISUPPORTS5(imgRequest,
                    nsIStreamListener, nsIRequestObserver,
-                   nsISupportsWeakReference,
                    nsIChannelEventSink,
                    nsIInterfaceRequestor,
                    nsIAsyncVerifyRedirectCallback)
 
 imgRequest::imgRequest(imgLoader* aLoader)
  : mLoader(aLoader)
  , mStatusTracker(new imgStatusTracker(nullptr, this))
  , mValidator(nullptr)
  , mImageSniffers("image-sniffing-services")
  , mInnerWindowId(0)
  , mCORSMode(imgIRequest::CORS_NONE)
  , mDecodeRequested(false)
  , mIsMultiPartChannel(false)
  , mGotData(false)
  , mIsInCache(false)
- , mBlockingOnload(false)
  , mResniffMimeType(false)
 {
   // Register our pref observers if we haven't yet.
   if (NS_UNLIKELY(!gInitializedPrefCaches)) {
     InitPrefCaches();
   }
 }
 
@@ -170,16 +167,23 @@ void imgRequest::SetCacheEntry(imgCacheE
   mCacheEntry = entry;
 }
 
 bool imgRequest::HasCacheEntry() const
 {
   return mCacheEntry != nullptr;
 }
 
+void imgRequest::ResetCacheEntry()
+{
+  if (HasCacheEntry()) {
+    mCacheEntry->SetDataSize(0);
+  }
+}
+
 void imgRequest::AddProxy(imgRequestProxy *proxy)
 {
   NS_PRECONDITION(proxy, "null imgRequestProxy passed in");
   LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::AddProxy", "proxy", proxy);
 
   // If we're empty before adding, we have to tell the loader we now have
   // proxies.
   if (GetStatusTracker().ConsumerCount() == 0) {
@@ -265,26 +269,17 @@ void imgRequest::CancelAndAbort(nsresult
 void imgRequest::Cancel(nsresult aStatus)
 {
   /* The Cancel() method here should only be called by this class. */
 
   LOG_SCOPE(gImgLog, "imgRequest::Cancel");
 
   imgStatusTracker& statusTracker = GetStatusTracker();
 
-  if (mBlockingOnload) {
-    mBlockingOnload = false;
-
-    statusTracker.RecordUnblockOnload();
-
-    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-    while (iter.HasMore()) {
-      statusTracker.SendUnblockOnload(iter.GetNext());
-    }
-  }
+  statusTracker.MaybeUnblockOnload();
 
   statusTracker.RecordCancel();
 
   RemoveFromCache();
 
   if (mRequest && statusTracker.IsLoading())
     mRequest->Cancel(aStatus);
 }
@@ -472,299 +467,16 @@ imgRequest::StartDecoding()
   }
 
   // Otherwise, flag to do it when we get the image
   mDecodeRequested = true;
 
   return NS_OK;
 }
 
-
-
-/** imgIContainerObserver methods **/
-
-/* [noscript] void frameChanged (in imgIRequest request,
-                                 in imgIContainer container,
-                                 in nsIntRect dirtyRect); */
-NS_IMETHODIMP imgRequest::FrameChanged(imgIRequest *request,
-                                       imgIContainer *container,
-                                       const nsIntRect *dirtyRect)
-{
-  LOG_SCOPE(gImgLog, "imgRequest::FrameChanged");
-  NS_ABORT_IF_FALSE(mImage,
-                    "FrameChanged callback before we've created our image");
-
-  mImage->GetStatusTracker().RecordFrameChanged(container, dirtyRect);
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-  while (iter.HasMore()) {
-    mImage->GetStatusTracker().SendFrameChanged(iter.GetNext(), container, dirtyRect);
-  }
-
-  return NS_OK;
-}
-
-/** imgIDecoderObserver methods **/
-
-/* void onStartDecode (in imgIRequest request); */
-NS_IMETHODIMP imgRequest::OnStartDecode(imgIRequest *request)
-{
-  LOG_SCOPE(gImgLog, "imgRequest::OnStartDecode");
-  NS_ABORT_IF_FALSE(mImage,
-                    "OnStartDecode callback before we've created our image");
-
-
-  imgStatusTracker& tracker = mImage->GetStatusTracker();
-  tracker.RecordStartDecode();
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-  while (iter.HasMore()) {
-    tracker.SendStartDecode(iter.GetNext());
-  }
-
-  if (!mIsMultiPartChannel) {
-    MOZ_ASSERT(!mBlockingOnload);
-    mBlockingOnload = true;
-
-    tracker.RecordBlockOnload();
-
-    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-    while (iter.HasMore()) {
-      tracker.SendBlockOnload(iter.GetNext());
-    }
-  }
-
-  /* In the case of streaming jpegs, it is possible to get multiple OnStartDecodes which
-     indicates the beginning of a new decode.
-     The cache entry's size therefore needs to be reset to 0 here.  If we do not do this,
-     the code in imgRequest::OnStopFrame will continue to increase the data size cumulatively.
-   */
-  if (mCacheEntry)
-    mCacheEntry->SetDataSize(0);
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP imgRequest::OnStartRequest(imgIRequest *aRequest)
-{
-  NS_NOTREACHED("imgRequest(imgIDecoderObserver)::OnStartRequest");
-  return NS_OK;
-}
-
-/* void onStartContainer (in imgIRequest request, in imgIContainer image); */
-NS_IMETHODIMP imgRequest::OnStartContainer(imgIRequest *request, imgIContainer *image)
-{
-  LOG_SCOPE(gImgLog, "imgRequest::OnStartContainer");
-
-  NS_ASSERTION(image, "imgRequest::OnStartContainer called with a null image!");
-  if (!image) return NS_ERROR_UNEXPECTED;
-
-  NS_ABORT_IF_FALSE(mImage,
-                    "OnStartContainer callback before we've created our image");
-  NS_ABORT_IF_FALSE(image == mImage,
-                    "OnStartContainer callback from an image we don't own");
-  mImage->GetStatusTracker().RecordStartContainer(image);
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-  while (iter.HasMore()) {
-    mImage->GetStatusTracker().SendStartContainer(iter.GetNext(), image);
-  }
-
-  return NS_OK;
-}
-
-/* void onStartFrame (in imgIRequest request, in unsigned long frame); */
-NS_IMETHODIMP imgRequest::OnStartFrame(imgIRequest *request,
-                                       uint32_t frame)
-{
-  LOG_SCOPE(gImgLog, "imgRequest::OnStartFrame");
-  NS_ABORT_IF_FALSE(mImage,
-                    "OnStartFrame callback before we've created our image");
-
-  mImage->GetStatusTracker().RecordStartFrame(frame);
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-  while (iter.HasMore()) {
-    mImage->GetStatusTracker().SendStartFrame(iter.GetNext(), frame);
-  }
-
-  return NS_OK;
-}
-
-/* [noscript] void onDataAvailable (in imgIRequest request, in boolean aCurrentFrame, [const] in nsIntRect rect); */
-NS_IMETHODIMP imgRequest::OnDataAvailable(imgIRequest *request,
-                                          bool aCurrentFrame,
-                                          const nsIntRect * rect)
-{
-  LOG_SCOPE(gImgLog, "imgRequest::OnDataAvailable");
-  NS_ABORT_IF_FALSE(mImage,
-                    "OnDataAvailable callback before we've created our image");
-
-  mImage->GetStatusTracker().RecordDataAvailable(aCurrentFrame, rect);
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-  while (iter.HasMore()) {
-    mImage->GetStatusTracker().SendDataAvailable(iter.GetNext(), aCurrentFrame, rect);
-  }
-
-  return NS_OK;
-}
-
-/* void onStopFrame (in imgIRequest request, in unsigned long frame); */
-NS_IMETHODIMP imgRequest::OnStopFrame(imgIRequest *request,
-                                      uint32_t frame)
-{
-  LOG_SCOPE(gImgLog, "imgRequest::OnStopFrame");
-  NS_ABORT_IF_FALSE(mImage,
-                    "OnStopFrame callback before we've created our image");
-
-  imgStatusTracker& tracker = mImage->GetStatusTracker();
-  tracker.RecordStopFrame(frame);
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-  while (iter.HasMore()) {
-    tracker.SendStopFrame(iter.GetNext(), frame);
-  }
-
-  if (mBlockingOnload) {
-    mBlockingOnload = false;
-
-    tracker.RecordUnblockOnload();
-
-    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-    while (iter.HasMore()) {
-      tracker.SendUnblockOnload(iter.GetNext());
-    }
-  }
-
-  return NS_OK;
-}
-
-/* void onStopContainer (in imgIRequest request, in imgIContainer image); */
-NS_IMETHODIMP imgRequest::OnStopContainer(imgIRequest *request,
-                                          imgIContainer *image)
-{
-  LOG_SCOPE(gImgLog, "imgRequest::OnStopContainer");
-  NS_ABORT_IF_FALSE(mImage,
-                    "OnDataContainer callback before we've created our image");
-
-  imgStatusTracker& tracker = mImage->GetStatusTracker();
-  tracker.RecordStopContainer(image);
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-  while (iter.HasMore()) {
-    tracker.SendStopContainer(iter.GetNext(), image);
-  }
-
-  // This is really hacky. We need to handle the case where we start decoding,
-  // block onload, but then hit an error before we get to our first frame. In
-  // theory we would just hook in at OnStopDecode, but OnStopDecode is broken
-  // until we fix bug 505385. OnStopContainer is actually going away at that
-  // point. So for now we take advantage of the fact that OnStopContainer is
-  // always fired in the decoders at the same time as OnStopDecode.
-  if (mBlockingOnload) {
-    mBlockingOnload = false;
-
-    tracker.RecordUnblockOnload();
-
-    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-    while (iter.HasMore()) {
-      tracker.SendUnblockOnload(iter.GetNext());
-    }
-  }
-
-  return NS_OK;
-}
-
-/* void onStopDecode (in imgIRequest request, in nsresult status, in wstring statusArg); */
-NS_IMETHODIMP imgRequest::OnStopDecode(imgIRequest *aRequest,
-                                       nsresult aStatus,
-                                       const PRUnichar *aStatusArg)
-{
-  LOG_SCOPE(gImgLog, "imgRequest::OnStopDecode");
-  NS_ABORT_IF_FALSE(mImage,
-                    "OnDataDecode callback before we've created our image");
-
-  // We finished the decode, and thus have the decoded frames. Update the cache
-  // entry size to take this into account.
-  UpdateCacheEntrySize();
-
-  mImage->GetStatusTracker().RecordStopDecode(aStatus, aStatusArg);
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-  while (iter.HasMore()) {
-    mImage->GetStatusTracker().SendStopDecode(iter.GetNext(), aStatus,
-                                              aStatusArg);
-  }
-
-  if (NS_FAILED(aStatus)) {
-    // Some kind of problem has happened with image decoding.
-    // Report the URI to net:failed-to-process-uri-conent observers.
-
-    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-    if (os)
-      os->NotifyObservers(mURI, "net:failed-to-process-uri-content", nullptr);
-  }
-
-  // RasterImage and everything below it is completely correct and
-  // bulletproof about its handling of decoder notifications.
-  // Unfortunately, here and above we have to make some gross and
-  // inappropriate use of things to get things to work without
-  // completely overhauling the decoder observer interface (this will,
-  // thankfully, happen in bug 505385). From imgRequest and above (for
-  // the time being), OnStopDecode is just a companion to OnStopRequest
-  // that signals success or failure of the _load_ (not the _decode_).
-  // Within imgStatusTracker, we ignore OnStopDecode notifications from the
-  // decoder and RasterImage and generate our own every time we send
-  // OnStopRequest. From within SendStopDecode, we actually send
-  // OnStopContainer.  For more information, see bug 435296.
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP imgRequest::OnStopRequest(imgIRequest *aRequest,
-                                        bool aLastPart)
-{
-  NS_NOTREACHED("imgRequest(imgIDecoderObserver)::OnStopRequest");
-  return NS_OK;
-}
-
-/* void onDiscard (in imgIRequest request); */
-NS_IMETHODIMP imgRequest::OnDiscard(imgIRequest *aRequest)
-{
-  NS_ABORT_IF_FALSE(mImage,
-                    "OnDiscard callback before we've created our image");
-
-  mImage->GetStatusTracker().RecordDiscard();
-
-  // Update the cache entry size, since we just got rid of frame data
-  UpdateCacheEntrySize();
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-  while (iter.HasMore()) {
-    mImage->GetStatusTracker().SendDiscard(iter.GetNext());
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP imgRequest::OnImageIsAnimated(imgIRequest *aRequest)
-{
-  NS_ABORT_IF_FALSE(mImage,
-                    "OnImageIsAnimated callback before we've created our image");
-  mImage->GetStatusTracker().RecordImageIsAnimated();
-
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
-  while (iter.HasMore()) {
-    mImage->GetStatusTracker().SendImageIsAnimated(iter.GetNext());
-  }
-
-  return NS_OK;
-}
-
 /** nsIRequestObserver methods **/
 
 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
 NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
 {
   LOG_SCOPE(gImgLog, "imgRequest::OnStartRequest");
 
   // Figure out if we're multipart
@@ -1077,17 +789,18 @@ imgRequest::OnDataAvailable(nsIRequest *
       nsAutoCString uriString;
       rv = mURI->GetSpec(uriString);
       if (NS_FAILED(rv))
         uriString.Assign("<unknown image URI>");
 
       // Initialize the image that we created above. For RasterImages, this
       // instantiates a decoder behind the scenes, so if we don't have a decoder
       // for this mimetype we'll find out about it here.
-      rv = mImage->Init(this, mContentType.get(), uriString.get(), imageFlags);
+      rv = mImage->Init(GetStatusTracker().GetDecoderObserver(),
+                        mContentType.get(), uriString.get(), imageFlags);
 
       // We allow multipart images to fail to initialize without cancelling the
       // load because subsequent images might be fine.
       if (NS_FAILED(rv) && !mIsMultiPartChannel) { // Probably bad mimetype
 
         this->Cancel(rv);
         return NS_BINDING_ABORTED;
       }
--- a/image/src/imgRequest.h
+++ b/image/src/imgRequest.h
@@ -2,32 +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 imgRequest_h__
 #define imgRequest_h__
 
-#include "imgIDecoderObserver.h"
-
 #include "nsIChannelEventSink.h"
 #include "nsIContentSniffer.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIRequest.h"
 #include "nsIProperties.h"
 #include "nsIStreamListener.h"
 #include "nsIURI.h"
 #include "nsIPrincipal.h"
 #include "nsITimedChannel.h"
 
 #include "nsCategoryCache.h"
 #include "nsCOMPtr.h"
 #include "nsStringGlue.h"
-#include "nsWeakReference.h"
 #include "nsError.h"
 #include "imgIRequest.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 
 class imgCacheValidator;
 class imgStatusTracker;
 class imgLoader;
 class imgRequestProxy;
@@ -36,19 +33,17 @@ class imgMemoryReporter;
 class imgRequestNotifyRunnable;
 
 namespace mozilla {
 namespace image {
 class Image;
 } // namespace image
 } // namespace mozilla
 
-class imgRequest : public imgIDecoderObserver,
-                   public nsIStreamListener,
-                   public nsSupportsWeakReference,
+class imgRequest : public nsIStreamListener,
                    public nsIChannelEventSink,
                    public nsIInterfaceRequestor,
                    public nsIAsyncVerifyRedirectCallback
 {
 public:
   imgRequest(imgLoader* aLoader);
   virtual ~imgRequest();
 
@@ -112,32 +107,39 @@ public:
   // Return the imgStatusTracker associated with this imgRequest. It may live
   // in |mStatusTracker| or in |mImage.mStatusTracker|, depending on whether
   // mImage has been instantiated yet.
   imgStatusTracker& GetStatusTracker();
 
   // Get the current principal of the image. No AddRefing.
   inline nsIPrincipal* GetPrincipal() const { return mPrincipal.get(); };
 
+  // Resize the cache entry to 0 if it exists
+  void ResetCacheEntry();
+
+  // Update the cache entry size based on the image container
+  void UpdateCacheEntrySize();
+
+  nsresult GetURI(nsIURI **aURI);
+
 private:
   friend class imgCacheEntry;
   friend class imgRequestProxy;
   friend class imgLoader;
   friend class imgCacheValidator;
   friend class imgStatusTracker;
   friend class imgCacheExpirationTracker;
   friend class imgRequestNotifyRunnable;
 
   inline void SetLoadId(void *aLoadId) {
     mLoadId = aLoadId;
   }
   void Cancel(nsresult aStatus);
   void RemoveFromCache();
 
-  nsresult GetURI(nsIURI **aURI);
   nsresult GetSecurityInfo(nsISupports **aSecurityInfo);
 
   inline const char *GetMimeType() const {
     return mContentType.get();
   }
   inline nsIProperties *Properties() {
     return mProperties;
   }
@@ -161,22 +163,20 @@ private:
   // Return whether we've seen some data at this point
   bool HasTransferredData() const { return mGotData; }
 
   // Set whether this request is stored in the cache. If it isn't, regardless
   // of whether this request has a non-null mCacheEntry, this imgRequest won't
   // try to update or modify the image cache.
   void SetIsInCache(bool cacheable);
 
-  // Update the cache entry size based on the image container
-  void UpdateCacheEntrySize();
+  bool IsBlockingOnload() const;
+  void SetBlockingOnload(bool block) const;
 
 public:
-  NS_DECL_IMGIDECODEROBSERVER
-  NS_DECL_IMGICONTAINEROBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
 
 private:
   friend class imgMemoryReporter;
--- a/image/src/imgRequestProxy.cpp
+++ b/image/src/imgRequestProxy.cpp
@@ -93,20 +93,18 @@ nsresult imgRequestProxy::Init(imgStatus
                                nsIURI* aURI, imgIDecoderObserver* 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");
 
-  mStatusTracker = aStatusTracker;
   mOwner = aStatusTracker->GetRequest();
   mOwnerHasImage = !!aStatusTracker->GetImage();
-  MOZ_ASSERT_IF(mOwner, mStatusTracker == &mOwner->GetStatusTracker());
   mListener = aObserver;
   // Make sure to addref mListener before the AddProxy call below, since
   // that call might well want to release it if the imgRequest has
   // already seen OnStopRequest.
   if (mListener) {
     mListenerIsStrongRef = true;
     NS_ADDREF(mListener);
   }
@@ -131,17 +129,16 @@ nsresult imgRequestProxy::ChangeOwner(im
     UnlockImage();
 
   // If we're holding animation requests, undo them.
   uint32_t oldAnimationConsumers = mAnimationConsumers;
   ClearAnimationConsumers();
 
   nsRefPtr<imgRequest> oldOwner = mOwner;
   mOwner = aNewOwner;
-  mStatusTracker = &aNewOwner->GetStatusTracker();
 
   // If we were locked, apply the locks here
   for (uint32_t i = 0; i < oldLockCount; i++)
     LockImage();
 
   if (mCanceled) {
     // If we had animation requests, restore them before exiting
     // (otherwise we restore them later below)
@@ -496,17 +493,17 @@ NS_IMETHODIMP imgRequestProxy::Clone(img
 
   // It is important to call |SetLoadFlags()| before calling |Init()| because
   // |Init()| adds the request to the loadgroup.
   // When a request is added to a loadgroup, its load flags are merged
   // with the load flags of the loadgroup.
   // XXXldb That's not true anymore.  Stuff from imgLoader adds the
   // request to the loadgroup.
   clone->SetLoadFlags(mLoadFlags);
-  nsresult rv = clone->Init(mStatusTracker, mLoadGroup, mURI, aObserver);
+  nsresult rv = clone->Init(&mOwner->GetStatusTracker(), mLoadGroup, mURI, aObserver);
   if (NS_FAILED(rv))
     return rv;
 
   // Assign to *aClone before calling Notify so that if the caller expects to
   // only be notified for requests it's already holding pointers to it won't be
   // surprised.
   NS_ADDREF(*aClone = clone);
 
@@ -905,17 +902,17 @@ imgStatusTracker&
 imgRequestProxy::GetStatusTracker() const
 {
   // NOTE: It's possible that our mOwner has an Image that it didn't notify
   // us about, if we were Canceled before its Image was constructed.
   // (Canceling removes us as an observer, so mOwner has no way to notify us).
   // That's why this method uses mOwner->GetStatusTracker() instead of just
   // mOwner->mStatusTracker -- we might have a null mImage and yet have an
   // mOwner with a non-null mImage (and a null mStatusTracker pointer).
-  return *mStatusTracker;
+  return mOwner->GetStatusTracker();
 }
 
 mozilla::image::Image*
 imgRequestProxy::GetImage() const
 {
   if (!mOwnerHasImage)
     return nullptr;
   return GetStatusTracker().GetImage();
@@ -933,8 +930,14 @@ NS_IMETHODIMP imgRequestProxyStatic::Get
   return NS_OK;
 }
 
 mozilla::image::Image*
 imgRequestProxyStatic::GetImage() const
 {
   return mImage;
 }
+
+imgStatusTracker&
+imgRequestProxyStatic::GetStatusTracker() const
+{
+  return mImage->GetStatusTracker();
+}
--- a/image/src/imgRequestProxy.h
+++ b/image/src/imgRequestProxy.h
@@ -163,17 +163,17 @@ protected:
   void DoRemoveFromLoadGroup() {
     RemoveFromLoadGroup(true);
   }
 
   // Return the imgStatusTracker associated with mOwner and/or mImage. It may
   // live either on mOwner or mImage, depending on whether
   //   (a) we have an mOwner at all
   //   (b) whether mOwner has instantiated its image yet
-  imgStatusTracker& GetStatusTracker() const;
+  virtual imgStatusTracker& GetStatusTracker() const;
 
   nsITimedChannel* TimedChannel()
   {
     if (!mOwner)
       return nullptr;
     return mOwner->mTimedChannel;
   }
 
@@ -188,19 +188,16 @@ private:
   // We maintain the following invariant:
   // The proxy is registered at most with a single imgRequest as an observer,
   // and whenever it is, mOwner points to that object. This helps ensure that
   // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
   // from whatever request it was registered with (if any). This, in turn,
   // means that imgRequest::mObservers will not have any stale pointers in it.
   nsRefPtr<imgRequest> mOwner;
 
-  // Weak pointer to the status tracker.
-  imgStatusTracker* mStatusTracker;
-
   // 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;
   nsCOMPtr<nsILoadGroup> mLoadGroup;
@@ -235,16 +232,17 @@ public:
                         nsIPrincipal* aPrincipal)
                        : mImage(aImage)
                        , mPrincipal(aPrincipal)
   {
     mOwnerHasImage = true;
   };
 
   NS_IMETHOD GetImagePrincipal(nsIPrincipal** aPrincipal);
+  virtual imgStatusTracker& GetStatusTracker() const MOZ_OVERRIDE;
 
 protected:
   // Our image. We have to hold a strong reference here, because that's normally
   // the job of the underlying request.
   nsRefPtr<mozilla::image::Image> mImage;
 
   // Our principal. We have to cache it, rather than accessing the underlying
   // request on-demand, because static proxies don't have an underlying request.
--- a/image/src/imgStatusTracker.cpp
+++ b/image/src/imgStatusTracker.cpp
@@ -7,46 +7,319 @@
 #include "imgStatusTracker.h"
 
 #include "imgRequest.h"
 #include "imgIContainer.h"
 #include "imgRequestProxy.h"
 #include "Image.h"
 #include "ImageLogging.h"
 #include "RasterImage.h"
+#include "nsIObserverService.h"
 
 #include "mozilla/Util.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/Services.h"
 
 using namespace mozilla::image;
 
+NS_IMPL_ISUPPORTS3(imgStatusTrackerObserver,
+                   imgIDecoderObserver,
+                   imgIContainerObserver,
+                   nsISupportsWeakReference)
+
+/** imgIContainerObserver methods **/
+
+/* [noscript] void frameChanged (in imgIRequest request,
+   in imgIContainer container,
+   in nsIntRect dirtyRect); */
+NS_IMETHODIMP imgStatusTrackerObserver::FrameChanged(imgIRequest *request,
+                                                     imgIContainer *container,
+                                                     const nsIntRect *dirtyRect)
+{
+  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::FrameChanged");
+  NS_ABORT_IF_FALSE(mTracker->GetImage(),
+                    "FrameChanged callback before we've created our image");
+
+  mTracker->RecordFrameChanged(container, dirtyRect);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->GetConsumers());
+  while (iter.HasMore()) {
+    mTracker->SendFrameChanged(iter.GetNext(), container, dirtyRect);
+  }
+
+  return NS_OK;
+}
+
+/** imgIDecoderObserver methods **/
+
+/* void onStartDecode (in imgIRequest request); */
+NS_IMETHODIMP imgStatusTrackerObserver::OnStartDecode(imgIRequest *request)
+{
+  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::OnStartDecode");
+  NS_ABORT_IF_FALSE(mTracker->GetImage(),
+                    "OnStartDecode callback before we've created our image");
+
+
+  mTracker->RecordStartDecode();
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->GetConsumers());
+  while (iter.HasMore()) {
+    mTracker->SendStartDecode(iter.GetNext());
+  }
+
+  if (!mTracker->GetRequest()->GetMultipart()) {
+    MOZ_ASSERT(!mTracker->mBlockingOnload);
+    mTracker->mBlockingOnload = true;
+
+    mTracker->RecordBlockOnload();
+
+    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->GetConsumers());
+    while (iter.HasMore()) {
+      mTracker->SendBlockOnload(iter.GetNext());
+    }
+  }
+
+  /* In the case of streaming jpegs, it is possible to get multiple OnStartDecodes which
+     indicates the beginning of a new decode.
+     The cache entry's size therefore needs to be reset to 0 here.  If we do not do this,
+     the code in imgStatusTrackerObserver::OnStopFrame will continue to increase the data size cumulatively.
+  */
+  mTracker->GetRequest()->ResetCacheEntry();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP imgStatusTrackerObserver::OnStartRequest(imgIRequest *aRequest)
+{
+  NS_NOTREACHED("imgRequest(imgIDecoderObserver)::OnStartRequest");
+  return NS_OK;
+}
+
+/* void onStartContainer (in imgIRequest request, in imgIContainer image); */
+NS_IMETHODIMP imgStatusTrackerObserver::OnStartContainer(imgIRequest *request, imgIContainer *image)
+{
+  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::OnStartContainer");
+
+  NS_ASSERTION(image, "imgStatusTrackerObserver::OnStartContainer called with a null image!");
+  if (!image) return NS_ERROR_UNEXPECTED;
+
+  NS_ABORT_IF_FALSE(mTracker->GetImage(),
+                    "OnStartContainer callback before we've created our image");
+  NS_ABORT_IF_FALSE(image == mTracker->GetImage(),
+                    "OnStartContainer callback from an image we don't own");
+  mTracker->RecordStartContainer(image);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->GetConsumers());
+  while (iter.HasMore()) {
+    mTracker->SendStartContainer(iter.GetNext(), image);
+  }
+
+  return NS_OK;
+}
+
+/* void onStartFrame (in imgIRequest request, in unsigned long frame); */
+NS_IMETHODIMP imgStatusTrackerObserver::OnStartFrame(imgIRequest *request,
+                                       uint32_t frame)
+{
+  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::OnStartFrame");
+  NS_ABORT_IF_FALSE(mTracker->GetImage(),
+                    "OnStartFrame callback before we've created our image");
+
+  mTracker->RecordStartFrame(frame);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->GetConsumers());
+  while (iter.HasMore()) {
+    mTracker->SendStartFrame(iter.GetNext(), frame);
+  }
+
+  return NS_OK;
+}
+
+/* [noscript] void onDataAvailable (in imgIRequest request, in boolean aCurrentFrame, [const] in nsIntRect rect); */
+NS_IMETHODIMP imgStatusTrackerObserver::OnDataAvailable(imgIRequest *request,
+                                          bool aCurrentFrame,
+                                          const nsIntRect * rect)
+{
+  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::OnDataAvailable");
+  NS_ABORT_IF_FALSE(mTracker->GetImage(),
+                    "OnDataAvailable callback before we've created our image");
+
+  mTracker->RecordDataAvailable(aCurrentFrame, rect);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->GetConsumers());
+  while (iter.HasMore()) {
+    mTracker->SendDataAvailable(iter.GetNext(), aCurrentFrame, rect);
+  }
+
+  return NS_OK;
+}
+
+/* void onStopFrame (in imgIRequest request, in unsigned long frame); */
+NS_IMETHODIMP imgStatusTrackerObserver::OnStopFrame(imgIRequest *request,
+                                      uint32_t frame)
+{
+  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::OnStopFrame");
+  NS_ABORT_IF_FALSE(mTracker->GetImage(),
+                    "OnStopFrame callback before we've created our image");
+
+  mTracker->RecordStopFrame(frame);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->GetConsumers());
+  while (iter.HasMore()) {
+    mTracker->SendStopFrame(iter.GetNext(), frame);
+  }
+
+  mTracker->MaybeUnblockOnload();
+
+  return NS_OK;
+}
+
+/* void onStopContainer (in imgIRequest request, in imgIContainer image); */
+NS_IMETHODIMP imgStatusTrackerObserver::OnStopContainer(imgIRequest *request,
+                                          imgIContainer *image)
+{
+  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::OnStopContainer");
+  NS_ABORT_IF_FALSE(mTracker->GetImage(),
+                    "OnDataContainer callback before we've created our image");
+
+  mTracker->RecordStopContainer(image);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->GetConsumers());
+  while (iter.HasMore()) {
+    mTracker->SendStopContainer(iter.GetNext(), image);
+  }
+
+  // This is really hacky. We need to handle the case where we start decoding,
+  // block onload, but then hit an error before we get to our first frame. In
+  // theory we would just hook in at OnStopDecode, but OnStopDecode is broken
+  // until we fix bug 505385. OnStopContainer is actually going away at that
+  // point. So for now we take advantage of the fact that OnStopContainer is
+  // always fired in the decoders at the same time as OnStopDecode.
+  mTracker->MaybeUnblockOnload();
+
+  return NS_OK;
+}
+
+/* void onStopDecode (in imgIRequest request, in nsresult status, in wstring statusArg); */
+NS_IMETHODIMP imgStatusTrackerObserver::OnStopDecode(imgIRequest *aRequest,
+                                       nsresult aStatus,
+                                       const PRUnichar *aStatusArg)
+{
+  LOG_SCOPE(gImgLog, "imgStatusTrackerObserver::OnStopDecode");
+  NS_ABORT_IF_FALSE(mTracker->GetImage(),
+                    "OnDataDecode callback before we've created our image");
+
+  // We finished the decode, and thus have the decoded frames. Update the cache
+  // entry size to take this into account.
+  mTracker->GetRequest()->UpdateCacheEntrySize();
+
+  mTracker->RecordStopDecode(aStatus, aStatusArg);
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->GetConsumers());
+  while (iter.HasMore()) {
+    mTracker->SendStopDecode(iter.GetNext(), aStatus, aStatusArg);
+  }
+
+  if (NS_FAILED(aStatus)) {
+    // Some kind of problem has happened with image decoding.
+    // Report the URI to net:failed-to-process-uri-conent observers.
+
+    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+    if (os) {
+      nsCOMPtr<nsIURI> uri;
+      mTracker->GetRequest()->GetURI(getter_AddRefs(uri));
+      os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr);
+    }
+  }
+
+  // RasterImage and everything below it is completely correct and
+  // bulletproof about its handling of decoder notifications.
+  // Unfortunately, here and above we have to make some gross and
+  // inappropriate use of things to get things to work without
+  // completely overhauling the decoder observer interface (this will,
+  // thankfully, happen in bug 505385). From imgRequest and above (for
+  // the time being), OnStopDecode is just a companion to OnStopRequest
+  // that signals success or failure of the _load_ (not the _decode_).
+  // Within imgStatusTracker, we ignore OnStopDecode notifications from the
+  // decoder and RasterImage and generate our own every time we send
+  // OnStopRequest. From within SendStopDecode, we actually send
+  // OnStopContainer.  For more information, see bug 435296.
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP imgStatusTrackerObserver::OnStopRequest(imgIRequest *aRequest,
+                                        bool aLastPart)
+{
+  NS_NOTREACHED("imgRequest(imgIDecoderObserver)::OnStopRequest");
+  return NS_OK;
+}
+
+/* void onDiscard (in imgIRequest request); */
+NS_IMETHODIMP imgStatusTrackerObserver::OnDiscard(imgIRequest *aRequest)
+{
+  NS_ABORT_IF_FALSE(mTracker->GetImage(),
+                    "OnDiscard callback before we've created our image");
+
+  mTracker->RecordDiscard();
+
+  // Update the cache entry size, since we just got rid of frame data
+  mTracker->GetRequest()->UpdateCacheEntrySize();
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->GetConsumers());
+  while (iter.HasMore()) {
+    mTracker->SendDiscard(iter.GetNext());
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP imgStatusTrackerObserver::OnImageIsAnimated(imgIRequest *aRequest)
+{
+  NS_ABORT_IF_FALSE(mTracker->GetImage(),
+                    "OnImageIsAnimated callback before we've created our image");
+  mTracker->RecordImageIsAnimated();
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mTracker->GetConsumers());
+  while (iter.HasMore()) {
+    mTracker->SendImageIsAnimated(iter.GetNext());
+  }
+
+  return NS_OK;
+}
+
+// imgStatusTracker methods
+
 static nsresult
 GetResultFromImageStatus(uint32_t aStatus)
 {
   if (aStatus & imgIRequest::STATUS_ERROR)
     return NS_IMAGELIB_ERROR_FAILURE;
   if (aStatus & imgIRequest::STATUS_LOAD_COMPLETE)
     return NS_IMAGELIB_SUCCESS_LOAD_FINISHED;
   return NS_OK;
 }
 
 imgStatusTracker::imgStatusTracker(Image* aImage, imgRequest* aRequest)
   : mImage(aImage),
     mRequest(aRequest),
     mState(0),
     mImageStatus(imgIRequest::STATUS_NONE),
-    mHadLastPart(false)
+    mHadLastPart(false),
+    mBlockingOnload(false),
+    mTrackerObserver(new imgStatusTrackerObserver(this))
 {}
 
 imgStatusTracker::imgStatusTracker(const imgStatusTracker& aOther)
   : mImage(aOther.mImage),
     mRequest(aOther.mRequest),
     mState(aOther.mState),
     mImageStatus(aOther.mImageStatus),
-    mHadLastPart(aOther.mHadLastPart)
+    mHadLastPart(aOther.mHadLastPart),
+    mBlockingOnload(aOther.mBlockingOnload)
     // Note: we explicitly don't copy mRequestRunnable, because it won't be
     // nulled out when the mRequestRunnable's Run function eventually gets
     // called.
 {}
 
 void
 imgStatusTracker::SetImage(Image* aImage)
 {
@@ -235,17 +508,17 @@ imgStatusTracker::SyncNotify(imgRequestP
     bool isAnimated = false;
 
     nsresult rv = mImage->GetAnimated(&isAnimated);
     if (NS_SUCCEEDED(rv) && isAnimated) {
       proxy->OnImageIsAnimated();
     }
   }
 
-  // See bug 505385 and imgRequest::OnStopDecode for more information on why we
+  // See bug 505385 and imgStatusTrackerObserver::OnStopDecode for more information on why we
   // call OnStopContainer based on stateDecodeStopped, and why OnStopDecode is
   // called with OnStopRequest.
   if (mState & stateDecodeStopped) {
     NS_ABORT_IF_FALSE(mImage, "stopped decoding without ever having an image?");
     proxy->OnStopContainer(mImage);
   }
 
   if (mState & stateRequestStopped) {
@@ -396,23 +669,23 @@ imgStatusTracker::SendStopFrame(imgReque
     aProxy->OnStopFrame(aFrame);
 }
 
 void
 imgStatusTracker::RecordStopContainer(imgIContainer* aContainer)
 {
   NS_ABORT_IF_FALSE(mImage,
                     "RecordStopContainer called before we have an Image");
-  // No-op: see imgRequest::OnStopDecode for more information
+  // No-op: see imgStatusTrackerObserver::OnStopDecode for more information
 }
 
 void
 imgStatusTracker::SendStopContainer(imgRequestProxy* aProxy, imgIContainer* aContainer)
 {
-  // No-op: see imgRequest::OnStopDecode for more information
+  // No-op: see imgStatusTrackerObserver::OnStopDecode for more information
 }
 
 void
 imgStatusTracker::RecordStopDecode(nsresult aStatus, const PRUnichar* statusArg)
 {
   NS_ABORT_IF_FALSE(mImage,
                     "RecordStopDecode called before we have an Image");
   mState |= stateDecodeStopped;
@@ -423,17 +696,17 @@ imgStatusTracker::RecordStopDecode(nsres
   else
     mImageStatus = imgIRequest::STATUS_ERROR;
 }
 
 void
 imgStatusTracker::SendStopDecode(imgRequestProxy* aProxy, nsresult aStatus,
                                  const PRUnichar* statusArg)
 {
-  // See imgRequest::OnStopDecode for more information on why we call
+  // See imgStatusTrackerObserver::OnStopDecode for more information on why we call
   // OnStopContainer from here this, and why imgRequestProxy::OnStopDecode() is
   // called from OnStopRequest() and SyncNotify().
   if (!aProxy->NotificationsDeferred())
     aProxy->OnStopContainer(mImage);
 }
 
 void
 imgStatusTracker::RecordDiscard()
@@ -528,17 +801,17 @@ imgStatusTracker::RecordStopRequest(bool
   // If we were successful in loading, note that the image is complete.
   if (NS_SUCCEEDED(aStatus))
     mImageStatus |= imgIRequest::STATUS_LOAD_COMPLETE;
 }
 
 void
 imgStatusTracker::SendStopRequest(imgRequestProxy* aProxy, bool aLastPart, nsresult aStatus)
 {
-  // See bug 505385 and imgRequest::OnStopDecode for more information on why
+  // See bug 505385 and imgStatusTrackerObserver::OnStopDecode for more information on why
   // OnStopDecode is called with OnStopRequest.
   if (!aProxy->NotificationsDeferred()) {
     aProxy->OnStopDecode(GetResultFromImageStatus(mImageStatus), nullptr);
     aProxy->OnStopRequest(aLastPart);
   }
 }
 
 void
@@ -565,8 +838,25 @@ imgStatusTracker::RecordUnblockOnload()
 
 void
 imgStatusTracker::SendUnblockOnload(imgRequestProxy* aProxy)
 {
   if (!aProxy->NotificationsDeferred()) {
     aProxy->UnblockOnload();
   }
 }
+
+void
+imgStatusTracker::MaybeUnblockOnload()
+{
+  if (!mBlockingOnload) {
+    return;
+  }
+
+  mBlockingOnload = false;
+
+  RecordUnblockOnload();
+
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
+  while (iter.HasMore()) {
+    SendUnblockOnload(iter.GetNext());
+  }
+}
--- a/image/src/imgStatusTracker.h
+++ b/image/src/imgStatusTracker.h
@@ -7,39 +7,64 @@
 #ifndef imgStatusTracker_h__
 #define imgStatusTracker_h__
 
 class imgIContainer;
 class imgRequest;
 class imgRequestProxy;
 class imgStatusNotifyRunnable;
 class imgRequestNotifyRunnable;
+class imgStatusTracker;
 struct nsIntRect;
 namespace mozilla {
 namespace image {
 class Image;
 } // namespace image
 } // namespace mozilla
 
 
 #include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
 #include "nsTObserverArray.h"
 #include "nsIRunnable.h"
 #include "nscore.h"
+#include "nsWeakReference.h"
+#include "imgIDecoderObserver.h"
 
 enum {
   stateRequestStarted    = PR_BIT(0),
   stateHasSize           = PR_BIT(1),
   stateDecodeStarted     = PR_BIT(2),
   stateDecodeStopped     = PR_BIT(3),
   stateFrameStopped      = PR_BIT(4),
   stateRequestStopped    = PR_BIT(5),
   stateBlockingOnload    = PR_BIT(6)
 };
 
+class imgStatusTrackerObserver : public imgIDecoderObserver,
+                                 public nsSupportsWeakReference
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_IMGIDECODEROBSERVER
+  NS_DECL_IMGICONTAINEROBSERVER
+
+  imgStatusTrackerObserver(imgStatusTracker* aTracker)
+  : mTracker(aTracker) {}
+
+  virtual ~imgStatusTrackerObserver() {}
+
+  void SetTracker(imgStatusTracker* aTracker) {
+    mTracker = aTracker;
+  }
+
+private:
+  imgStatusTracker* mTracker;
+};
+
 /*
  * The image status tracker is a class that encapsulates all the loading and
  * decoding status about an Image, and makes it possible to send notifications
  * to imgRequestProxys, both synchronously (i.e., the status now) and
  * asynchronously (the status later).
  *
  * When a new proxy needs to be notified of the current state of an image, call
  * the Notify() method on this class with the relevant proxy as its argument,
@@ -162,32 +187,40 @@ public:
   // NB: If UnblockOnload is sent, and then we are asked to replay the
   // notifications, we will not send a BlockOnload/UnblockOnload pair.  This
   // is different from all the other notifications.
   void RecordBlockOnload();
   void SendBlockOnload(imgRequestProxy* aProxy);
   void RecordUnblockOnload();
   void SendUnblockOnload(imgRequestProxy* aProxy);
 
+  void MaybeUnblockOnload();
+
   // Weak pointer getters - no AddRefs.
   inline mozilla::image::Image* GetImage() const { return mImage; };
   inline imgRequest* GetRequest() const { return mRequest; };
 
+  inline imgIDecoderObserver* GetDecoderObserver() { return mTrackerObserver.get(); }
+
 private:
   friend class imgStatusNotifyRunnable;
   friend class imgRequestNotifyRunnable;
+  friend class imgStatusTrackerObserver;
 
   nsCOMPtr<nsIRunnable> mRequestRunnable;
 
   // Weak pointers to the image and request. The request owns the image, and
   // the image (or the request, if there's no image) owns the status tracker.
   mozilla::image::Image* mImage;
   imgRequest* mRequest;
   uint32_t mState;
   uint32_t mImageStatus;
   bool mHadLastPart;
+  bool mBlockingOnload;
 
   // List of proxies attached to the image. Each proxy represents a consumer
   // using the image.
   nsTObserverArray<imgRequestProxy*> mConsumers;
+
+  nsRefPtr<imgStatusTrackerObserver> mTrackerObserver;
 };
 
 #endif