Bug 502694 - Images should not have individual discard timers.r=jrmuizel
authorBobby Holley <bobbyholley@gmail.com>
Thu, 01 Jul 2010 10:39:44 -0700
changeset 46991 df154e9bd99924f2a85e2ad071b2b90bfd0d4ccd
parent 46990 8425474fc441d9ebe4d8191386b822a97ac5c7b6
child 46992 255acc6edc7b715720838633d677d1fa409c797f
push id14221
push userbobbyholley@stanford.edu
push dateThu, 01 Jul 2010 17:50:00 +0000
treeherdermozilla-central@255acc6edc7b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs502694
milestone2.0b2pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 502694 - Images should not have individual discard timers.r=jrmuizel
modules/libpr0n/build/nsImageModule.cpp
modules/libpr0n/src/Makefile.in
modules/libpr0n/src/imgContainer.cpp
modules/libpr0n/src/imgContainer.h
modules/libpr0n/src/imgDiscardTracker.cpp
modules/libpr0n/src/imgDiscardTracker.h
--- a/modules/libpr0n/build/nsImageModule.cpp
+++ b/modules/libpr0n/build/nsImageModule.cpp
@@ -53,16 +53,17 @@
 #include "nsXPCOMCID.h"
 #include "nsServiceManagerUtils.h"
 
 #include "imgContainer.h"
 #include "imgLoader.h"
 #include "imgRequest.h"
 #include "imgRequestProxy.h"
 #include "imgTools.h"
+#include "imgDiscardTracker.h"
 
 #ifdef IMG_BUILD_DECODER_gif
 // gif
 #include "nsGIFDecoder2.h"
 #endif
 
 #ifdef IMG_BUILD_DECODER_bmp
 // bmp/ico
@@ -290,13 +291,14 @@ imglib_Initialize(nsIModule* aSelf)
   imgLoader::InitCache();
   return NS_OK;
 }
 
 static void
 imglib_Shutdown(nsIModule* aSelf)
 {
   imgLoader::Shutdown();
+  imgDiscardTracker::Shutdown();
 }
 
 NS_IMPL_NSGETMODULE_WITH_CTOR_DTOR(nsImageLib2Module, components,
                                    imglib_Initialize, imglib_Shutdown)
 
--- a/modules/libpr0n/src/Makefile.in
+++ b/modules/libpr0n/src/Makefile.in
@@ -53,16 +53,17 @@ LIBXUL_LIBRARY  = 1
 CPPSRCS		= \
 			imgContainer.cpp \
 			imgFrame.cpp \
 			imgLoader.cpp    \
 			imgRequest.cpp   \
 			imgRequestProxy.cpp \
 			imgTools.cpp \
 			imgContainerRequest.cpp \
+			imgDiscardTracker.cpp \
 			$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 # Because imgFrame.cpp includes "cairo.h"
 CXXFLAGS += $(MOZ_CAIRO_CFLAGS)
 
 
--- a/modules/libpr0n/src/imgContainer.cpp
+++ b/modules/libpr0n/src/imgContainer.cpp
@@ -139,33 +139,36 @@ NS_IMPL_ISUPPORTS4(imgContainer, imgICon
 //******************************************************************************
 imgContainer::imgContainer() :
   mSize(0,0),
   mAnim(nsnull),
   mAnimationMode(kNormalAnimMode),
   mLoopCount(-1),
   mObserver(nsnull),
   mLockCount(0),
-  mDiscardTimer(nsnull),
   mDecoder(nsnull),
   mWorker(nsnull),
   mBytesDecoded(0),
   mDecoderFlags(imgIDecoder::DECODER_FLAG_NONE),
   mHasSize(PR_FALSE),
   mDecodeOnDraw(PR_FALSE),
   mMultipart(PR_FALSE),
   mInitialized(PR_FALSE),
   mDiscardable(PR_FALSE),
   mHasSourceData(PR_FALSE),
   mDecoded(PR_FALSE),
   mHasBeenDecoded(PR_FALSE),
   mWorkerPending(PR_FALSE),
   mInDecoder(PR_FALSE),
   mError(PR_FALSE)
 {
+  // Set up the discard tracker node.
+  mDiscardTrackerNode.curr = this;
+  mDiscardTrackerNode.prev = mDiscardTrackerNode.next = nsnull;
+
   // Statistics
   num_containers++;
 }
 
 //******************************************************************************
 imgContainer::~imgContainer()
 {
   if (mAnim)
@@ -185,20 +188,17 @@ imgContainer::~imgContainer()
              "Total source bytes: %lld, Source bytes for discardable containers %lld",
              this,
              num_containers,
              num_discardable_containers,
              total_source_bytes,
              discardable_source_bytes));
   }
 
-  if (mDiscardTimer) {
-    mDiscardTimer->Cancel();
-    mDiscardTimer = nsnull;
-  }
+  imgDiscardTracker::Remove(&mDiscardTrackerNode);
 
   // If we have a decoder open, shut it down
   if (mDecoder) {
     nsresult rv = ShutdownDecoder(eShutdownIntent_Interrupted);
     if (NS_FAILED(rv))
       NS_WARNING("Failed to shut down decoder in destructor!");
   }
 
@@ -1021,19 +1021,19 @@ NS_IMETHODIMP imgContainer::DecodingComp
   mHasBeenDecoded = PR_TRUE;
   if (mAnim)
     mAnim->doneDecoding = PR_TRUE;
 
   nsresult rv;
 
   // We now have one of the qualifications for discarding. Re-evaluate.
   if (CanDiscard()) {
-    NS_ABORT_IF_FALSE(!mDiscardTimer,
+    NS_ABORT_IF_FALSE(!DiscardingActive(),
                       "We shouldn't have been discardable before this");
-    rv = ResetDiscardTimer();
+    rv = imgDiscardTracker::Reset(&mDiscardTrackerNode);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
   // If there's only 1 frame, optimize it. Optimizing animated images
   // is not supported.
   //
   // We don't optimize the frame for multipart images because we reuse
   // the frame.
@@ -1340,17 +1340,17 @@ NS_IMETHODIMP imgContainer::SourceDataCo
              mSourceDataMimeType.get(),
              mSourceData.Elements(),
              buf,
              mSourceData.Length()));
   }
 
   // We now have one of the qualifications for discarding. Re-evaluate.
   if (CanDiscard()) {
-    nsresult rv = ResetDiscardTimer();
+    nsresult rv = imgDiscardTracker::Reset(&mDiscardTrackerNode);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
   return NS_OK;
 }
 
 //******************************************************************************
 /* void newSourceData(); */
 NS_IMETHODIMP imgContainer::NewSourceData()
@@ -2009,112 +2009,78 @@ NS_IMETHODIMP imgContainer::GetKeys(PRUi
   if (!mProperties) {
     *count = 0;
     *keys = nsnull;
     return NS_OK;
   }
   return mProperties->GetKeys(count, keys);
 }
 
-static int
-get_discard_timer_ms (void)
-{
-  /* FIXME: don't hardcode this */
-  return 15000; /* 15 seconds */
-}
-
 void
-imgContainer::sDiscardTimerCallback(nsITimer *aTimer, void *aClosure)
+imgContainer::Discard()
 {
-  // Retrieve self pointer and null out the expired timer
-  imgContainer *self = (imgContainer *) aClosure;
-  NS_ABORT_IF_FALSE(aTimer == self->mDiscardTimer, 
-                    "imgContainer::DiscardTimerCallback() got a callback "
-                    "for an unknown timer");
-  self->mDiscardTimer = nsnull;
-
   // We should be ok for discard
-  NS_ABORT_IF_FALSE(self->CanDiscard(), "Hit discard callback but can't discard!");
+  NS_ABORT_IF_FALSE(CanDiscard(), "Asked to discard but can't!");
 
   // We should never discard when we have an active decoder
-  NS_ABORT_IF_FALSE(!self->mDecoder, "Discard callback fired with open decoder!");
+  NS_ABORT_IF_FALSE(!mDecoder, "Asked to discard with open decoder!");
 
   // As soon as an image becomes animated, it becomes non-discardable and any
   // timers are cancelled.
-  NS_ABORT_IF_FALSE(!self->mAnim, "Discard callback fired for animated image!");
+  NS_ABORT_IF_FALSE(!mAnim, "Asked to discard for animated image!");
 
   // For post-operation logging
-  int old_frame_count = self->mFrames.Length();
+  int old_frame_count = mFrames.Length();
 
   // Delete all the decoded frames, then clear the array.
   for (int i = 0; i < old_frame_count; ++i)
-    delete self->mFrames[i];
-  self->mFrames.Clear();
+    delete mFrames[i];
+  mFrames.Clear();
 
   // Flag that we no longer have decoded frames for this image
-  self->mDecoded = PR_FALSE;
+  mDecoded = PR_FALSE;
 
   // Notify that we discarded
-  nsCOMPtr<imgIDecoderObserver> observer(do_QueryReferent(self->mObserver));
+  nsCOMPtr<imgIDecoderObserver> observer(do_QueryReferent(mObserver));
   if (observer)
     observer->OnDiscard(nsnull);
 
   // Log
   PR_LOG(gCompressedImageAccountingLog, PR_LOG_DEBUG,
          ("CompressedImageAccounting: discarded uncompressed image "
           "data from imgContainer %p (%s) - %d frames (cached count: %d); "
           "Total Containers: %d, Discardable containers: %d, "
           "Total source bytes: %lld, Source bytes for discardable containers %lld",
-          self,
-          self->mSourceDataMimeType.get(),
+          this,
+          mSourceDataMimeType.get(),
           old_frame_count,
-          self->mFrames.Length(),
+          mFrames.Length(),
           num_containers,
           num_discardable_containers,
           total_source_bytes,
           discardable_source_bytes));
 }
 
-nsresult
-imgContainer::ResetDiscardTimer()
-{
-  // We should not call this function if we can't discard
-  NS_ABORT_IF_FALSE(CanDiscard(), "Calling ResetDiscardTimer but can't discard!");
-
-  // As soon as an image becomes animated it is set non-discardable
-  NS_ABORT_IF_FALSE(!mAnim, "Trying to reset discard timer on animated image!");
-
-  // If we have a timer already ticking, cancel it
-  if (mDiscardTimer) {
-    nsresult rv = mDiscardTimer->Cancel();
-    CONTAINER_ENSURE_SUCCESS(rv);
-    mDiscardTimer = nsnull;
-  }
-
-  // Create a new timer
-  mDiscardTimer = do_CreateInstance("@mozilla.org/timer;1");
-  CONTAINER_ENSURE_TRUE(mDiscardTimer, NS_ERROR_OUT_OF_MEMORY);
-
-  // Activate the timer
-  return mDiscardTimer->InitWithFuncCallback(sDiscardTimerCallback,
-                                             (void *) this,
-                                             get_discard_timer_ms (),
-                                             nsITimer::TYPE_ONE_SHOT);
-}
-
 // Helper method to determine if we can discard an image
 PRBool
 imgContainer::CanDiscard() {
   return (DiscardingEnabled() && // Globally enabled...
           mDiscardable &&        // ...Enabled at creation time...
           (mLockCount == 0) &&   // ...not temporarily disabled...
           mHasSourceData &&      // ...have the source data...
           mDecoded);             // ...and have something to discard.
 }
 
+// Helper method to tell us whether the clock is currently running for
+// discarding this image. Mainly for assertions.
+PRBool
+imgContainer::DiscardingActive() {
+  return !!(mDiscardTrackerNode.prev || mDiscardTrackerNode.next);
+}
+
 // Helper method to determine if we're storing the source data in a buffer
 // or just writing it directly to the decoder
 PRBool
 imgContainer::StoringSourceData() {
   return (mDecodeOnDraw || mDiscardable);
 }
 
 
@@ -2125,17 +2091,17 @@ imgContainer::InitDecoder (PRUint32 dFla
 {
   // Ensure that the decoder is not already initialized
   NS_ABORT_IF_FALSE(!mDecoder, "Calling InitDecoder() while already decoding!");
   
   // We shouldn't be firing up a decoder if we already have the frames decoded
   NS_ABORT_IF_FALSE(!mDecoded, "Calling InitDecoder() but already decoded!");
 
   // Since we're not decoded, we should not have a discard timer active
-  NS_ABORT_IF_FALSE(!mDiscardTimer, "Discard Timer active in InitDecoder()!");
+  NS_ABORT_IF_FALSE(!DiscardingActive(), "Discard Timer active in InitDecoder()!");
 
   // Find and instantiate the decoder
   nsCAutoString decoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/decoder;3?type=") +
                                               mSourceDataMimeType);
   mDecoder = do_CreateInstance(decoderCID.get());
   CONTAINER_ENSURE_TRUE(mDecoder, NS_IMAGELIB_ERROR_NO_DECODER);
 
   // Store the flags for this decoder
@@ -2272,20 +2238,21 @@ imgContainer::WriteToDecoder(const char 
 // we're discardable), since wanting the frames now is a good indicator of
 // wanting them again soon. If we're not decoded, this method kicks off
 // asynchronous decoding to generate the frames.
 nsresult
 imgContainer::WantDecodedFrames()
 {
   nsresult rv;
 
-  // If we can discard, we should have a timer already. reset it.
+  // If we can discard, the clock should be running. Reset it.
   if (CanDiscard()) {
-    NS_ABORT_IF_FALSE(mDiscardTimer, "Decoded and discardable but timer not set!");
-    rv = ResetDiscardTimer();
+    NS_ABORT_IF_FALSE(DiscardingActive(),
+                      "Decoded and discardable but discarding not activated!");
+    rv = imgDiscardTracker::Reset(&mDiscardTrackerNode);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
   // Request a decode (no-op if we're decoded)
   return RequestDecode();
 }
 
 //******************************************************************************
@@ -2440,21 +2407,17 @@ NS_IMETHODIMP imgContainer::Draw(gfxCont
 /* void lockImage() */
 NS_IMETHODIMP
 imgContainer::LockImage()
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
   // Cancel the discard timer if it's there
-  if (mDiscardTimer) {
-    mDiscardTimer->Cancel();
-    mDiscardTimer = nsnull; // It's wasteful to null out the discard timers each
-                            // time, but we'll wait to fix that until bug 502694.
-  }
+  imgDiscardTracker::Remove(&mDiscardTrackerNode);
 
   // Increment the lock count
   mLockCount++;
 
   return NS_OK;
 }
 
 //******************************************************************************
@@ -2466,25 +2429,25 @@ imgContainer::UnlockImage()
     return NS_ERROR_FAILURE;
 
   // It's an error to call this function if the lock count is 0
   NS_ABORT_IF_FALSE(mLockCount > 0,
                     "Calling UnlockImage with mLockCount == 0!");
   if (mLockCount == 0)
     return NS_ERROR_ABORT;
 
-  // We're locked, so we shouldn't have a discard timer set
-  NS_ABORT_IF_FALSE(!mDiscardTimer, "Locked, but discard timer set!");
+  // We're locked, so discarding should not be active
+  NS_ABORT_IF_FALSE(!DiscardingActive(), "Locked, but discarding activated");
 
   // Decrement our lock count
   mLockCount--;
 
   // We now _might_ have one of the qualifications for discarding. Re-evaluate.
   if (CanDiscard()) {
-    nsresult rv = ResetDiscardTimer();
+    nsresult rv = imgDiscardTracker::Reset(&mDiscardTrackerNode);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
   return NS_OK;
 }
 
 // Flushes up to aMaxBytes to the decoder.
 nsresult
--- a/modules/libpr0n/src/imgContainer.h
+++ b/modules/libpr0n/src/imgContainer.h
@@ -58,16 +58,17 @@
 #include "imgIContainer.h"
 #include "imgIDecoder.h"
 #include "nsIProperties.h"
 #include "nsITimer.h"
 #include "nsWeakReference.h"
 #include "nsTArray.h"
 #include "imgFrame.h"
 #include "nsThreadUtils.h"
+#include "imgDiscardTracker.h"
 
 #define NS_IMGCONTAINER_CID \
 { /* c76ff2c1-9bf6-418a-b143-3340c00112f7 */         \
      0x376ff2c1,                                     \
      0x9bf6,                                         \
      0x418a,                                         \
     {0xb1, 0x43, 0x33, 0x40, 0xc0, 0x01, 0x12, 0xf7} \
 }
@@ -152,16 +153,19 @@ public:
   static NS_METHOD WriteToContainer(nsIInputStream* in, void* closure,
                                     const char* fromRawSegment,
                                     PRUint32 toOffset, PRUint32 count,
                                     PRUint32 *writeCount);
 
   PRUint32 GetDecodedDataSize();
   PRUint32 GetSourceDataSize();
 
+  /* Triggers discarding. */
+  void Discard();
+
 private:
   struct Anim
   {
     //! Area of the first frame that needs to be redrawn on subsequent loops.
     nsIntRect                  firstFrameRefreshArea;
     // Note this doesn't hold a proper value until frame 2 finished decoding.
     PRUint32                   currentDecodingFrameIndex; // 0 to numFrames-1
     PRUint32                   currentAnimationFrameIndex; // 0 to numFrames-1
@@ -316,23 +320,24 @@ private: // data
   //! # loops remaining before animation stops (-1 no stop)
   PRInt32                    mLoopCount;
   
   //! imgIDecoderObserver
   nsWeakPtr                  mObserver;
 
   // Discard members
   PRUint32                   mLockCount;
-  nsCOMPtr<nsITimer>         mDiscardTimer;
+  imgDiscardTrackerNode      mDiscardTrackerNode;
 
   // Source data members
   nsTArray<char>             mSourceData;
   nsCString                  mSourceDataMimeType;
 
   friend class imgDecodeWorker;
+  friend class imgDiscardTracker;
 
   // Decoder and friends
   nsCOMPtr<imgIDecoder>          mDecoder;
   nsRefPtr<imgDecodeWorker>      mWorker;
   PRUint32                       mBytesDecoded;
   PRUint32                       mDecoderFlags;
 
   // Boolean flags (clustered together to conserve space):
@@ -348,20 +353,16 @@ private: // data
   PRPackedBool               mHasBeenDecoded:1;
 
   // Helpers for decoder
   PRPackedBool               mWorkerPending:1;
   PRPackedBool               mInDecoder:1;
 
   PRPackedBool               mError:1;  // Error handling
 
-  // Discard code
-  nsresult ResetDiscardTimer();
-  static void sDiscardTimerCallback(nsITimer *aTimer, void *aClosure);
-
   // Decoding
   nsresult WantDecodedFrames();
   nsresult SyncDecode();
   nsresult InitDecoder(PRUint32 dFlags);
   nsresult WriteToDecoder(const char *aBuffer, PRUint32 aCount);
   nsresult DecodeSomeData(PRUint32 aMaxBytes);
   PRBool   IsDecodeFinished();
 
@@ -372,16 +373,17 @@ private: // data
     eShutdownIntent_Error       = 2,
     eShutdownIntent_AllCount    = 3
   };
   nsresult ShutdownDecoder(eShutdownIntent aIntent);
 
   // Helpers
   void DoError();
   PRBool CanDiscard();
+  PRBool DiscardingActive();
   PRBool StoringSourceData();
 
 };
 
 // Decoding Helper Class
 //
 // We use this class to mimic the interactivity benefits of threading
 // in a single-threaded event loop. We want to progressively decode
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/src/imgDiscardTracker.cpp
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2001
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Bobby Holley <bobbyholley@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsComponentManagerUtils.h"
+#include "nsITimer.h"
+#include "imgContainer.h"
+#include "imgDiscardTracker.h"
+
+static PRBool sInitialized = PR_FALSE;
+static PRBool sTimerOn = PR_FALSE;
+/* TODO - don't hardcode. See bug 478398. */
+static PRUint32 sMinDiscardTimeoutMs = 10000;
+static nsITimer *sTimer = nsnull;
+static struct imgDiscardTrackerNode sHead, sSentinel, sTail;
+
+/*
+ * Puts an image in the back of the tracker queue. If the image is already
+ * in the tracker, this removes it first.
+ */
+nsresult
+imgDiscardTracker::Reset(imgDiscardTrackerNode *node)
+{
+  nsresult rv;
+  PRBool isSentinel = (node == &sSentinel);
+
+  // Sanity check the node.
+  NS_ABORT_IF_FALSE(isSentinel || node->curr, "Node doesn't point to anything!");
+
+  // We should not call this function if we can't discard
+  NS_ABORT_IF_FALSE(isSentinel || node->curr->CanDiscard(),
+                    "trying to reset discarding but can't discard!");
+
+  // As soon as an image becomes animated it is set non-discardable
+  NS_ABORT_IF_FALSE(isSentinel || !node->curr->mAnim,
+                    "Trying to reset discarding on animated image!");
+
+
+  // Initialize the first time through
+  if (NS_UNLIKELY(!sInitialized)) {
+    rv = Initialize();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Remove the node if it's in the list.
+  Remove(node);
+
+  // Append it to the list.
+  node->prev = sTail.prev;
+  node->next = &sTail;
+  node->prev->next = sTail.prev = node;
+
+  // Make sure the timer is running
+  rv = TimerOn();
+  NS_ENSURE_SUCCESS(rv,rv);
+
+  return NS_OK;
+}
+
+/*
+ * Removes a node from the tracker. No-op if the node is currently untracked.
+ */
+void
+imgDiscardTracker::Remove(imgDiscardTrackerNode *node)
+{
+  NS_ABORT_IF_FALSE(node != nsnull, "Can't pass null node");
+
+  // If we're not in a list, we have nothing to do.
+  if ((node->prev == nsnull) || (node->next == nsnull)) {
+    NS_ABORT_IF_FALSE(node->prev == node->next,
+                      "Node is half in a list!");
+    return;
+  }
+
+  // Connect around ourselves
+  node->prev->next = node->next;
+  node->next->prev = node->prev;
+
+  // Clean up the node we removed.
+  node->prev = node->next = nsnull;
+}
+
+/**
+ * Initialize the tracker.
+ */
+nsresult
+imgDiscardTracker::Initialize()
+{
+  nsresult rv;
+
+  // Set up the list. Head<->Sentinel<->Tail
+  sHead.curr = sTail.curr = sSentinel.curr = nsnull;
+  sHead.prev = sTail.next = nsnull;
+  sHead.next = sTail.prev = &sSentinel;
+  sSentinel.prev = &sHead;
+  sSentinel.next = &sTail;
+
+  // Create and start the timer
+  nsCOMPtr<nsITimer> t = do_CreateInstance("@mozilla.org/timer;1");
+  NS_ENSURE_TRUE(t, NS_ERROR_OUT_OF_MEMORY);
+  t.forget(&sTimer);
+  rv = TimerOn();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Mark us as initialized
+  sInitialized = PR_TRUE;
+
+  return NS_OK;
+}
+
+/**
+ * Shut down the tracker, deallocating the timer.
+ */
+void
+imgDiscardTracker::Shutdown()
+{
+  if (sTimer) {
+    sTimer->Cancel();
+    NS_RELEASE(sTimer);
+    sTimer = nsnull;
+  }
+}
+
+/**
+ * Enables the timer. No-op if the timer is already running.
+ */
+nsresult
+imgDiscardTracker::TimerOn()
+{
+  // Nothing to do if the timer's already on.
+  if (sTimerOn)
+    return NS_OK;
+  sTimerOn = PR_TRUE;
+
+  // Activate
+  return sTimer->InitWithFuncCallback(TimerCallback,
+                                      nsnull,
+                                      sMinDiscardTimeoutMs,
+                                      nsITimer::TYPE_REPEATING_SLACK);
+}
+
+/*
+ * Disables the timer. No-op if the timer isn't running.
+ */
+void
+imgDiscardTracker::TimerOff()
+{
+  // Nothing to do if the timer's already off.
+  if (!sTimerOn)
+    return;
+  sTimerOn = PR_FALSE;
+
+  // Deactivate
+  sTimer->Cancel();
+}
+
+/**
+ * Routine activated when the timer fires. This discards everything
+ * in front of sentinel, and resets the sentinel to the back of the
+ * list.
+ */
+void
+imgDiscardTracker::TimerCallback(nsITimer *aTimer, void *aClosure)
+{
+  imgDiscardTrackerNode *node;
+
+  // Remove and discard everything before the sentinel
+  for (node = sSentinel.prev; node != &sHead; node = sSentinel.prev) {
+    NS_ABORT_IF_FALSE(node->curr, "empty node!");
+    Remove(node);
+    node->curr->Discard();
+  }
+
+  // Append the sentinel to the back of the list
+  Reset(&sSentinel);
+
+  // If there's nothing in front of the sentinel, the next callback
+  // is guaranteed to be a no-op. Disable the timer as an optimization.
+  if (sSentinel.prev == &sHead)
+    TimerOff();
+}
new file mode 100644
--- /dev/null
+++ b/modules/libpr0n/src/imgDiscardTracker.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2001
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Bobby Holley <bobbyholley@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef __imgDiscardTracker_h__
+#define __imgDiscardTracker_h__
+
+class imgContainer;
+class nsITimer;
+
+// Struct to make an imgContainer insertable into the tracker list. This
+// is embedded within each imgContainer object, and we do 'this->curr = this'
+// on imgContainer construction. Thus, an imgContainer must always call
+// imgDiscardTracker::Remove() in its destructor to avoid having the tracker
+// point to bogus memory.
+struct imgDiscardTrackerNode
+{
+  // Pointer to the imgContainer that this node tracks
+  imgContainer *curr;
+
+  // Pointers to the previous and next nodes in the list
+  imgDiscardTrackerNode *prev, *next;
+};
+
+/**
+ * This static class maintains a linked list of imgContainer nodes. When Reset()
+ * is called, the node is removed from its position in the list (if it was there
+ * before) and appended to the end. When Remove() is called, the node is removed
+ * from the list. The timer fires once every MIN_DISCARD_TIMEOUT_MS ms. When it
+ * does, it calls Discard() on each container preceding it, and then appends
+ * itself to the end of the list. Thus, the discard timeout varies between
+ * MIN_DISCARD_TIMEOUT_MS and 2*MIN_DISCARD_TIMEOUT_MS.
+ */
+
+class imgDiscardTracker
+{
+  public:
+    static nsresult Reset(struct imgDiscardTrackerNode *node);
+    static void Remove(struct imgDiscardTrackerNode *node);
+    static void Shutdown();
+  private:
+    static nsresult Initialize();
+    static nsresult TimerOn();
+    static void TimerOff();
+    static void TimerCallback(nsITimer *aTimer, void *aClosure);
+};
+
+#endif /* __imgDiscardTracker_h__ */