Bug 505385 - Part 1: Hoist proxy list into imgStatusTracker. r=joe
authorBobby Holley <bobbyholley@gmail.com>
Thu, 11 Oct 2012 21:34:22 -0400
changeset 110146 2440e35a985fc544a24a771a706d6325590290b0
parent 110145 dab0a2f7a5cadf6035ae9729cd67464351ce71c3
child 110147 9a20e6883f785961b02cdd461e35e5c88b3e998c
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersjoe
bugs505385
milestone19.0a1
Bug 505385 - Part 1: Hoist proxy list into imgStatusTracker. r=joe
image/src/imgRequest.cpp
image/src/imgRequest.h
image/src/imgStatusTracker.cpp
image/src/imgStatusTracker.h
--- a/image/src/imgRequest.cpp
+++ b/image/src/imgRequest.cpp
@@ -178,49 +178,44 @@ bool imgRequest::HasCacheEntry() const
 
 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 (mObservers.IsEmpty()) {
+  if (GetStatusTracker().ConsumerCount() == 0) {
     NS_ABORT_IF_FALSE(mURI, "Trying to SetHasProxies without key uri.");
     mLoader->SetHasProxies(mURI);
   }
 
   proxy->SetPrincipal(mPrincipal);
 
-  mObservers.AppendElementUnlessExists(proxy);
+  GetStatusTracker().AddConsumer(proxy);
 }
 
 nsresult imgRequest::RemoveProxy(imgRequestProxy *proxy, nsresult aStatus)
 {
   LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy", "proxy", proxy);
 
   // This will remove our animation consumers, so after removing
   // this proxy, we don't end up without proxies with observers, but still
   // have animation consumers.
   proxy->ClearAnimationConsumers();
 
-  if (!mObservers.RemoveElement(proxy)) {
-    // Not one of our proxies; we're done
-    return NS_OK;
-  }
-
   // Let the status tracker do its thing before we potentially call Cancel()
   // below, because Cancel() may result in OnStopRequest being called back
   // before Cancel() returns, leaving the image in a different state then the
   // one it was in at this point.
+  imgStatusTracker& statusTracker = GetStatusTracker();
+  if (!statusTracker.RemoveConsumer(proxy, aStatus, !aNotify))
+    return NS_OK;
 
-  imgStatusTracker& statusTracker = GetStatusTracker();
-  statusTracker.EmulateRequestFinished(proxy, aStatus);
-
-  if (mObservers.IsEmpty()) {
+  if (statusTracker.ConsumerCount() == 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.");
 
       mLoader->SetHasNoProxies(mURI, mCacheEntry);
     } 
@@ -230,17 +225,17 @@ nsresult imgRequest::RemoveProxy(imgRequ
       mURI->GetSpec(spec);
       LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy no cache entry", "uri", spec.get());
     }
 #endif
 
     /* If |aStatus| is a failure code, then cancel the load if it is still in progress.
        Otherwise, let the load continue, keeping 'this' in the cache with no observers.
        This way, if a proxy is destroyed without calling cancel on it, it won't leak
-       and won't leave a bad pointer in mObservers.
+       and won't leave a bad pointer in the observer list.
      */
     if (statusTracker.IsLoading() && NS_FAILED(aStatus)) {
       LOG_MSG(gImgLog, "imgRequest::RemoveProxy", "load in progress.  canceling");
 
       this->Cancel(NS_BINDING_ABORTED);
     }
 
     /* break the cycle from the cache entry. */
@@ -278,17 +273,17 @@ void imgRequest::Cancel(nsresult aStatus
 
   imgStatusTracker& statusTracker = GetStatusTracker();
 
   if (mBlockingOnload) {
     mBlockingOnload = false;
 
     statusTracker.RecordUnblockOnload();
 
-    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
     while (iter.HasMore()) {
       statusTracker.SendUnblockOnload(iter.GetNext());
     }
   }
 
   statusTracker.RecordCancel();
 
   RemoveFromCache();
@@ -348,17 +343,17 @@ void imgRequest::AdjustPriority(imgReque
 {
   // only the first proxy is allowed to modify the priority of this image load.
   //
   // XXX(darin): this is probably not the most optimal algorithm as we may want
   // to increase the priority of requests that have a lot of proxies.  the key
   // concern though is that image loads remain lower priority than other pieces
   // of content such as link clicks, CSS, and JS.
   //
-  if (mObservers.SafeElementAt(0, nullptr) != proxy)
+  if (!GetStatusTracker().FirstConsumerIs(proxy))
     return;
 
   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest);
   if (p)
     p->AdjustPriority(delta);
 }
 
 void imgRequest::SetIsInCache(bool incache)
@@ -497,17 +492,17 @@ NS_IMETHODIMP imgRequest::FrameChanged(i
                                        const nsIntRect *dirtyRect)
 {
   LOG_SCOPE(gImgLog, "imgRequest::FrameChanged");
   NS_ABORT_IF_FALSE(mImage,
                     "FrameChanged callback before we've created our image");
 
   mImage->GetStatusTracker().RecordFrameChanged(container, dirtyRect);
 
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
   while (iter.HasMore()) {
     mImage->GetStatusTracker().SendFrameChanged(iter.GetNext(), container, dirtyRect);
   }
 
   return NS_OK;
 }
 
 /** imgIDecoderObserver methods **/
@@ -518,28 +513,28 @@ NS_IMETHODIMP imgRequest::OnStartDecode(
   LOG_SCOPE(gImgLog, "imgRequest::OnStartDecode");
   NS_ABORT_IF_FALSE(mImage,
                     "OnStartDecode callback before we've created our image");
 
 
   imgStatusTracker& tracker = mImage->GetStatusTracker();
   tracker.RecordStartDecode();
 
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  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(mObservers);
+    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,
@@ -566,17 +561,17 @@ NS_IMETHODIMP imgRequest::OnStartContain
   if (!image) return NS_ERROR_UNEXPECTED;
 
   NS_ABORT_IF_FALSE(mImage,
                     "OnStartContainer callback before we've created our image");
   NS_ABORT_IF_FALSE(image == mImage,
                     "OnStartContainer callback from an image we don't own");
   mImage->GetStatusTracker().RecordStartContainer(image);
 
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  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); */
@@ -584,17 +579,17 @@ NS_IMETHODIMP imgRequest::OnStartFrame(i
                                        uint32_t frame)
 {
   LOG_SCOPE(gImgLog, "imgRequest::OnStartFrame");
   NS_ABORT_IF_FALSE(mImage,
                     "OnStartFrame callback before we've created our image");
 
   mImage->GetStatusTracker().RecordStartFrame(frame);
 
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  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); */
@@ -603,17 +598,17 @@ NS_IMETHODIMP imgRequest::OnDataAvailabl
                                           const nsIntRect * rect)
 {
   LOG_SCOPE(gImgLog, "imgRequest::OnDataAvailable");
   NS_ABORT_IF_FALSE(mImage,
                     "OnDataAvailable callback before we've created our image");
 
   mImage->GetStatusTracker().RecordDataAvailable(aCurrentFrame, rect);
 
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  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); */
@@ -622,27 +617,27 @@ NS_IMETHODIMP imgRequest::OnStopFrame(im
 {
   LOG_SCOPE(gImgLog, "imgRequest::OnStopFrame");
   NS_ABORT_IF_FALSE(mImage,
                     "OnStopFrame callback before we've created our image");
 
   imgStatusTracker& tracker = mImage->GetStatusTracker();
   tracker.RecordStopFrame(frame);
 
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
   while (iter.HasMore()) {
     tracker.SendStopFrame(iter.GetNext(), frame);
   }
 
   if (mBlockingOnload) {
     mBlockingOnload = false;
 
     tracker.RecordUnblockOnload();
 
-    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
     while (iter.HasMore()) {
       tracker.SendUnblockOnload(iter.GetNext());
     }
   }
 
   return NS_OK;
 }
 
@@ -652,33 +647,33 @@ NS_IMETHODIMP imgRequest::OnStopContaine
 {
   LOG_SCOPE(gImgLog, "imgRequest::OnStopContainer");
   NS_ABORT_IF_FALSE(mImage,
                     "OnDataContainer callback before we've created our image");
 
   imgStatusTracker& tracker = mImage->GetStatusTracker();
   tracker.RecordStopContainer(image);
 
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  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(mObservers);
+    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
     while (iter.HasMore()) {
       tracker.SendUnblockOnload(iter.GetNext());
     }
   }
 
   return NS_OK;
 }
 
@@ -692,17 +687,17 @@ NS_IMETHODIMP imgRequest::OnStopDecode(i
                     "OnDataDecode callback before we've created our image");
 
   // We finished the decode, and thus have the decoded frames. Update the cache
   // entry size to take this into account.
   UpdateCacheEntrySize();
 
   mImage->GetStatusTracker().RecordStopDecode(aStatus, aStatusArg);
 
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  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.
@@ -741,31 +736,31 @@ NS_IMETHODIMP imgRequest::OnDiscard(imgI
   NS_ABORT_IF_FALSE(mImage,
                     "OnDiscard callback before we've created our image");
 
   mImage->GetStatusTracker().RecordDiscard();
 
   // Update the cache entry size, since we just got rid of frame data
   UpdateCacheEntrySize();
 
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  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(mObservers);
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
   while (iter.HasMore()) {
     mImage->GetStatusTracker().SendImageIsAnimated(iter.GetNext());
   }
 
   return NS_OK;
 }
 
 /** nsIRequestObserver methods **/
@@ -815,17 +810,17 @@ NS_IMETHODIMP imgRequest::OnStartRequest
 
   imgStatusTracker& statusTracker = GetStatusTracker();
   statusTracker.RecordStartRequest();
 
   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
   if (channel)
     channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
 
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
   while (iter.HasMore()) {
     statusTracker.SendStartRequest(iter.GetNext());
   }
 
   /* Get our principal */
   nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
   if (chan) {
     nsCOMPtr<nsIScriptSecurityManager> secMan =
@@ -833,27 +828,27 @@ NS_IMETHODIMP imgRequest::OnStartRequest
     if (secMan) {
       nsresult rv = secMan->GetChannelPrincipal(chan,
                                                 getter_AddRefs(mPrincipal));
       if (NS_FAILED(rv)) {
         return rv;
       }
 
       // Tell all of our proxies that we have a principal.
-      nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+      nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
       while (iter.HasMore()) {
         iter.GetNext()->SetPrincipal(mPrincipal);
       }
     }
   }
 
   SetCacheValidation(mCacheEntry, aRequest);
 
   // Shouldn't we be dead already if this gets hit?  Probably multipart/x-mixed-replace...
-  if (mObservers.IsEmpty()) {
+  if (GetStatusTracker().ConsumerCount() == 0) {
     this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
   }
 
   return NS_OK;
 }
 
 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
 NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
@@ -914,17 +909,17 @@ NS_IMETHODIMP imgRequest::OnStopRequest(
     UpdateCacheEntrySize();
   }
   else {
     // stops animations, removes from cache
     this->Cancel(status);
   }
 
   /* notify the kids */
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator srIter(mObservers);
+  nsTObserverArray<imgRequestProxy*>::ForwardIterator srIter(GetStatusTracker().GetConsumers());
   while (srIter.HasMore()) {
     statusTracker.SendStopRequest(srIter.GetNext(), lastPart, status);
   }
 
   mTimedChannel = nullptr;
   return NS_OK;
 }
 
@@ -1003,31 +998,33 @@ imgRequest::OnDataAvailable(nsIRequest *
     if (mContentType != newType || newType.EqualsLiteral(SVG_MIMETYPE)) {
       mContentType = newType;
 
       // If we've resniffed our MIME type and it changed, we need to create a
       // new status tracker to give to the image, because we don't have one of
       // our own any more.
       if (mResniffMimeType) {
         NS_ABORT_IF_FALSE(mIsMultiPartChannel, "Resniffing a non-multipart image");
-        mStatusTracker = new imgStatusTracker(nullptr);
+        imgStatusTracker* freshTracker = new imgStatusTracker(nullptr);
+        freshTracker->AdoptConsumers(mStatusTracker);
+        mStatusTracker = freshTracker;
       }
 
       mResniffMimeType = false;
 
       /* now we have mimetype, so we can infer the image type that we want */
       if (mContentType.EqualsLiteral(SVG_MIMETYPE)) {
         mImage = new VectorImage(mStatusTracker.forget());
       } else {
         mImage = new RasterImage(mStatusTracker.forget());
       }
       mImage->SetInnerWindowID(mInnerWindowId);
 
       // Notify any imgRequestProxys that are observing us that we have an Image.
-      nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
+    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(GetStatusTracker().GetConsumers());
       while (iter.HasMore()) {
         iter.GetNext()->SetImage(mImage);
       }
 
       /* set our mimetype as a property */
       nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1"));
       if (contentType) {
         contentType->SetData(mContentType);
--- a/image/src/imgRequest.h
+++ b/image/src/imgRequest.h
@@ -17,17 +17,16 @@
 #include "nsIStreamListener.h"
 #include "nsIURI.h"
 #include "nsIPrincipal.h"
 #include "nsITimedChannel.h"
 
 #include "nsCategoryCache.h"
 #include "nsCOMPtr.h"
 #include "nsStringGlue.h"
-#include "nsTObserverArray.h"
 #include "nsWeakReference.h"
 #include "nsError.h"
 #include "imgIRequest.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 
 class imgCacheValidator;
 class imgStatusTracker;
 class imgLoader;
@@ -194,18 +193,16 @@ private:
   // Status-tracker -- transferred to mImage, when it gets instantiated
   nsAutoPtr<imgStatusTracker> mStatusTracker;
   nsRefPtr<mozilla::image::Image> mImage;
   nsCOMPtr<nsIProperties> mProperties;
   nsCOMPtr<nsISupports> mSecurityInfo;
   nsCOMPtr<nsIChannel> mChannel;
   nsCOMPtr<nsIInterfaceRequestor> mPrevChannelSink;
 
-  nsTObserverArray<imgRequestProxy*> mObservers;
-
   nsCOMPtr<nsITimedChannel> mTimedChannel;
 
   nsCString mContentType;
 
   nsRefPtr<imgCacheEntry> mCacheEntry; /* we hold on to this to this so long as we have observers */
 
   void *mLoadId;
 
--- a/image/src/imgStatusTracker.cpp
+++ b/image/src/imgStatusTracker.cpp
@@ -8,16 +8,19 @@
 
 #include "imgRequest.h"
 #include "imgIContainer.h"
 #include "imgRequestProxy.h"
 #include "Image.h"
 #include "ImageLogging.h"
 #include "RasterImage.h"
 
+#include "mozilla/Util.h"
+#include "mozilla/Assertions.h"
+
 using namespace mozilla::image;
 
 static nsresult
 GetResultFromImageStatus(uint32_t aStatus)
 {
   if (aStatus & imgIRequest::STATUS_ERROR)
     return NS_IMAGELIB_ERROR_FAILURE;
   if (aStatus & imgIRequest::STATUS_LOAD_COMPLETE)
@@ -260,16 +263,38 @@ imgStatusTracker::EmulateRequestFinished
   }
 
   if (!(mState & stateRequestStopped)) {
     aProxy->OnStopRequest(true);
   }
 }
 
 void
+imgStatusTracker::AddConsumer(imgRequestProxy* aConsumer)
+{
+  mConsumers.AppendElementUnlessExists(aConsumer);
+}
+
+// XXX - The last two arguments should go away.
+bool
+imgStatusTracker::RemoveConsumer(imgRequestProxy* aConsumer, nsresult aStatus,
+                                 bool aOnlySendStopRequest)
+{
+  // Remove the proxy from the list.
+  bool removed = mConsumers.RemoveElement(aConsumer);
+  //MOZ_NONFATAL_ASSERT(removed, "Trying to remove a consumer we don't have");
+
+  // Consumers can get confused if they don't get all the proper teardown
+  // notifications. Part ways on good terms.
+  if (removed)
+    EmulateRequestFinished(aConsumer, aStatus, aOnlySendStopRequest);
+  return removed;
+}
+
+void
 imgStatusTracker::RecordCancel()
 {
   if (!(mImageStatus & imgIRequest::STATUS_LOAD_PARTIAL))
     mImageStatus |= imgIRequest::STATUS_ERROR;
 }
 
 void
 imgStatusTracker::RecordLoaded()
--- a/image/src/imgStatusTracker.h
+++ b/image/src/imgStatusTracker.h
@@ -16,16 +16,17 @@ struct nsIntRect;
 namespace mozilla {
 namespace image {
 class Image;
 } // namespace image
 } // namespace mozilla
 
 
 #include "nsCOMPtr.h"
+#include "nsTObserverArray.h"
 #include "nsIRunnable.h"
 #include "nscore.h"
 
 enum {
   stateRequestStarted    = PR_BIT(0),
   stateHasSize           = PR_BIT(1),
   stateDecodeStarted     = PR_BIT(2),
   stateDecodeStopped     = PR_BIT(3),
@@ -80,16 +81,34 @@ public:
   // OnStartRequest).
   void SyncNotify(imgRequestProxy* proxy);
 
   // Send some notifications that would be necessary to make |proxy| believe
   // the request is finished downloading and decoding.  We only send
   // OnStopRequest and UnblockOnload, and only if necessary.
   void EmulateRequestFinished(imgRequestProxy* proxy, nsresult aStatus);
 
+  // We manage a set of consumers that are using an image and thus concerned
+  // with its status. Weak pointers.
+  void AddConsumer(imgRequestProxy* aConsumer);
+  bool RemoveConsumer(imgRequestProxy* aConsumer, nsresult aStatus, bool aOnlySendStopRequest);
+  size_t ConsumerCount() const { return mConsumers.Length(); };
+
+  // This is intentionally non-general because its sole purpose is to support an
+  // some obscure network priority logic in imgRequest. That stuff could probably
+  // be improved, but it's too scary to mess with at the moment.
+  bool FirstConsumerIs(imgRequestProxy* aConsumer) {
+    return mConsumers.SafeElementAt(0, nullptr) == aConsumer;
+  }
+
+  // Temporary hack that goes away in the next patch.
+  const nsTObserverArray<imgRequestProxy*>& GetConsumers() { return mConsumers; };
+
+  void AdoptConsumers(imgStatusTracker* aTracker) { mConsumers = aTracker->mConsumers; }
+
   // Returns whether we are in the process of loading; that is, whether we have
   // not received OnStopRequest.
   bool IsLoading() const;
 
   // Get the current image status (as in imgIRequest).
   uint32_t GetImageStatus() const;
 
   // Following are all the notification methods. You must call the Record
@@ -155,11 +174,15 @@ private:
   nsCOMPtr<nsIRunnable> mRequestRunnable;
 
   // A weak pointer to the Image, because it owns us, and we
   // can't create a cycle.
   mozilla::image::Image* mImage;
   uint32_t mState;
   uint32_t mImageStatus;
   bool mHadLastPart;
+
+  // List of proxies attached to the image. Each proxy represents a consumer
+  // using the image.
+  nsTObserverArray<imgRequestProxy*> mConsumers;
 };
 
 #endif