Back out bug 731419 and bug 732820 due to orange.
authorJustin Lebar <justin.lebar@gmail.com>
Fri, 09 Mar 2012 18:32:42 -0500
changeset 88666 1fa083bd043407162a6d67bd671a109bcbf5c825
parent 88665 55e63a03ccad43dcd2395c52035d066d939a6d86
child 88667 18f2ec24a60d092e822a9819480608442b2a908d
push id6952
push userjlebar@mozilla.com
push dateFri, 09 Mar 2012 23:32:57 +0000
treeherdermozilla-inbound@1fa083bd0434 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs731419, 732820
milestone13.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Back out bug 731419 and bug 732820 due to orange. Backs out changesets 55e63a03ccad 45d2f5e2fe31 f78900832562 6184b50776fc 35d8045aeadd 42e887fec034.
content/base/src/nsDocument.cpp
image/public/imgIContainer.idl
image/public/imgIRequest.idl
image/src/DiscardTracker.cpp
image/src/DiscardTracker.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/VectorImage.cpp
image/src/VectorImage.h
image/src/imgFrame.cpp
image/src/imgFrame.h
image/src/imgRequestProxy.cpp
image/test/mochitest/Makefile.in
image/test/mochitest/test_drawDiscardedImage.html
mfbt/LinkedList.h
modules/libpref/src/init/all.js
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -3242,37 +3242,24 @@ nsDocument::MaybeRescheduleAnimationFram
 
 void
 nsIDocument::TakeFrameRequestCallbacks(FrameRequestCallbackList& aCallbacks)
 {
   aCallbacks.AppendElements(mFrameRequestCallbacks);
   mFrameRequestCallbacks.Clear();
 }
 
-PLDHashOperator RequestDiscardEnumerator(imgIRequest* aKey,
-                                         PRUint32 aData,
-                                         void* userArg)
-{
-  aKey->RequestDiscard();
-  return PL_DHASH_NEXT;
-}
-
 void
 nsDocument::DeleteShell()
 {
   mExternalResourceMap.HideViewers();
   if (IsEventHandlingEnabled()) {
     RevokeAnimationFrameNotifications();
   }
 
-  // When our shell goes away, request that all our images be immediately
-  // discarded, so we don't carry around decoded image data for a document we
-  // no longer intend to paint.
-  mImageTracker.EnumerateRead(RequestDiscardEnumerator, nsnull);
-
   mPresShell = nsnull;
 }
 
 void
 nsDocument::RevokeAnimationFrameNotifications()
 {
   if (!mFrameRequestCallbacks.IsEmpty()) {
     mPresShell->GetPresContext()->RefreshDriver()->
@@ -8317,42 +8304,36 @@ nsDocument::RemoveImage(imgIRequest* aIm
   NS_ABORT_IF_FALSE(found, "Removing image that wasn't in the tracker!");
   NS_ABORT_IF_FALSE(count > 0, "Entry in the cache tracker with count 0!");
 
   // We're removing, so decrement the count.
   count--;
 
   // If the count is now zero, remove from the tracker.
   // Otherwise, set the new value.
-  if (count != 0) {
+  if (count == 0) {
+    mImageTracker.Remove(aImage);
+  } else {
     mImageTracker.Put(aImage, count);
-    return NS_OK;
-  }
-
-  mImageTracker.Remove(aImage);
+  }
 
   nsresult rv = NS_OK;
 
-  // Now that we're no longer tracking this image, unlock it if we'd
-  // previously locked it.
-  if (mLockingImages) {
+  // If we removed the image from the tracker and we're locking images, unlock
+  // this image.
+  if (count == 0 && mLockingImages)
     rv = aImage->UnlockImage();
-  }
-
-  // If we're animating images, remove our request to animate this one.
-  if (mAnimatingImages) {
+
+  // If we removed the image from the tracker and we're animating images,
+  // remove our request to animate this image.
+  if (count == 0 && mAnimatingImages) {
     nsresult rv2 = aImage->DecrementAnimationConsumers();
     rv = NS_SUCCEEDED(rv) ? rv2 : rv;
   }
 
-  // Request that the image be discarded if nobody else holds a lock on it.
-  // Do this even if !mLockingImages, because even if we didn't just unlock
-  // this image, it might still be a candidate for discarding.
-  aImage->RequestDiscard();
-
   return rv;
 }
 
 PLDHashOperator LockEnumerator(imgIRequest* aKey,
                                PRUint32 aData,
                                void*    userArg)
 {
   aKey->LockImage();
--- a/image/public/imgIContainer.idl
+++ b/image/public/imgIContainer.idl
@@ -88,17 +88,17 @@ native gfxGraphicsFilter(gfxPattern::Gra
 /**
  * imgIContainer is the interface that represents an image. It allows
  * access to frames as Thebes surfaces, and permits users to extract subregions
  * as other imgIContainers. It also allows drawing of images on to Thebes
  * contexts.
  *
  * Internally, imgIContainer also manages animation of images.
  */
-[scriptable, uuid(8bf87433-be67-413b-9497-00071c5002bd)]
+[scriptable, uuid(2506249c-e0a1-4d8f-846c-2d478247f8d8)]
 interface imgIContainer : nsISupports
 {
   /**
    * The width of the container rectangle.  In the case of any error,
    * zero is returned, and an exception will be thrown.
    */
   readonly attribute PRInt32 width;
 
@@ -279,22 +279,16 @@ interface imgIContainer : nsISupports
     *
     * Upon instantiation images have a lock count of zero. It is an error to
     * call this method without first having made a matching lockImage() call.
     * In other words, the lock count is not allowed to be negative.
     */
   void unlockImage();
 
   /**
-   * If this image is unlocked, discard its decoded data.  If the image is
-   * locked or has already been discarded, do nothing.
-   */
-  void requestDiscard();
-
-  /**
     * Indicates that this imgIContainer has been triggered to update
     * its internal animation state. Likely this should only be called
     * from within nsImageFrame or objects of similar type.
     */
   [notxpcom] void requestRefresh([const] in TimeStamp aTime);
 
   /**
    * Animation mode Constants
--- a/image/public/imgIRequest.idl
+++ b/image/public/imgIRequest.idl
@@ -47,17 +47,17 @@ interface nsIPrincipal;
 
 /**
  * imgIRequest interface
  *
  * @author Stuart Parmenter <stuart@mozilla.com>
  * @version 0.1
  * @see imagelib2
  */
-[scriptable, uuid(d35a9adb-8328-4b64-b06f-72a165acd080)]
+[scriptable, uuid(c3bf4e2a-f64b-4ac1-a84e-18631b1802ab)]
 interface imgIRequest : nsIRequest
 {
   /**
    * the image container...
    * @return the image object associated with the request.
    * @attention NEED DOCS
    */
   readonly attribute imgIContainer image;
@@ -190,22 +190,16 @@ interface imgIRequest : nsIRequest
   /**
    * Unlocks an image.
    *
    * @see imgIContainer::unlockImage for documentation of the underlying call.
    */
   void unlockImage();
 
   /**
-   * If this image is unlocked, discard the image's decoded data.  If the image
-   * is locked or is already discarded, do nothing.
-   */
-  void requestDiscard();
-
-  /**
    * If this request is for an animated image, the method creates a new
    * request which contains the current frame of the image.
    * Otherwise returns the same request.
    */
   imgIRequest getStaticRequest();
 
   /**
    * Requests that the image animate (if it has an animation).
--- a/image/src/DiscardTracker.cpp
+++ b/image/src/DiscardTracker.cpp
@@ -1,198 +1,245 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* -*- 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 Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * 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 "RasterImage.h"
 #include "DiscardTracker.h"
 #include "mozilla/Preferences.h"
 
 namespace mozilla {
 namespace image {
 
-static const char* sDiscardTimeoutPref = "image.mem.min_discard_timeout_ms";
-
-/* static */ LinkedList<DiscardTracker::Node> DiscardTracker::sDiscardableImages;
-/* static */ nsCOMPtr<nsITimer> DiscardTracker::sTimer;
-/* static */ bool DiscardTracker::sInitialized = false;
-/* static */ bool DiscardTracker::sTimerOn = false;
-/* static */ bool DiscardTracker::sDiscardRunnablePending = false;
-/* static */ PRUint64 DiscardTracker::sCurrentDecodedImageBytes = 0;
-/* static */ PRUint32 DiscardTracker::sMinDiscardTimeoutMs = 10000;
-/* static */ PRUint32 DiscardTracker::sMaxDecodedImageKB = 42 * 1024;
+static bool sInitialized = false;
+static bool sTimerOn = false;
+static PRUint32 sMinDiscardTimeoutMs = 10000; /* Default if pref unreadable. */
+static nsITimer *sTimer = nsnull;
+static struct DiscardTrackerNode sHead, sSentinel, sTail;
 
 /*
- * When we notice we're using too much memory for decoded images, we enqueue a
- * DiscardRunnable, which runs this code.
+ * Puts an image in the back of the tracker queue. If the image is already
+ * in the tracker, this removes it first.
  */
-NS_IMETHODIMP
-DiscardTracker::DiscardRunnable::Run()
-{
-  sDiscardRunnablePending = false;
-  DiscardTracker::DiscardNow();
-  return NS_OK;
-}
-
-int
-DiscardTimeoutChangedCallback(const char* aPref, void *aClosure)
+nsresult
+DiscardTracker::Reset(DiscardTrackerNode *node)
 {
-  DiscardTracker::ReloadTimeout();
-  return 0;
-}
+  nsresult rv;
+#ifdef DEBUG
+  bool isSentinel = (node == &sSentinel);
+
+  // Sanity check the node.
+  NS_ABORT_IF_FALSE(isSentinel || node->curr, "Node doesn't point to anything!");
 
-nsresult
-DiscardTracker::Reset(Node *node)
-{
-  // We shouldn't call Reset() with a null |img| pointer, on images which can't
-  // be discarded, or on animated images (which should be marked as
-  // non-discardable, anyway).
-  MOZ_ASSERT(node->img);
-  MOZ_ASSERT(node->img->CanDiscard());
-  MOZ_ASSERT(!node->img->mAnim);
+  // 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!");
 
-  // Initialize the first time through.
-  nsresult rv;
+  // 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!");
+#endif
+
+  // Initialize the first time through
   if (NS_UNLIKELY(!sInitialized)) {
     rv = Initialize();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  // Insert the node at the front of the list and note when it was inserted.
-  bool wasInList = node->isInList();
-  if (wasInList) {
-    node->remove();
-  }
-  node->timestamp = TimeStamp::Now();
-  sDiscardableImages.insertFront(node);
+  // Remove the node if it's in the list.
+  Remove(node);
 
-  // If the node wasn't already in the list of discardable images, then we may
-  // need to discard some images to stay under the sMaxDecodedImageKB limit.
-  // Call MaybeDiscardSoon to do this check.
-  if (!wasInList) {
-    MaybeDiscardSoon();
-  }
+  // 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 = EnableTimer();
+  // Make sure the timer is running
+  rv = TimerOn();
   NS_ENSURE_SUCCESS(rv,rv);
 
   return NS_OK;
 }
 
-void
-DiscardTracker::Remove(Node *node)
-{
-  if (node->isInList())
-    node->remove();
-
-  if (sDiscardableImages.isEmpty())
-    DisableTimer();
-}
-
-/**
- * Shut down the tracker, deallocating the timer.
+/*
+ * Removes a node from the tracker. No-op if the node is currently untracked.
  */
 void
-DiscardTracker::Shutdown()
+DiscardTracker::Remove(DiscardTrackerNode *node)
 {
-  if (sTimer) {
-    sTimer->Cancel();
-    sTimer = NULL;
+  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;
 }
 
 /*
  * Discard all the images we're tracking.
  */
 void
 DiscardTracker::DiscardAll()
 {
   if (!sInitialized)
     return;
 
-  sDiscardableImages.clear();
+  // Remove the sentinel from the list so that the only elements in the list
+  // which don't track an image are the head and tail.
+  Remove(&sSentinel);
 
-  // The list is empty, so there's no need to leave the timer on.
-  DisableTimer();
+  // Discard all tracked images.
+  for (DiscardTrackerNode *node = sHead.next;
+       node != &sTail; node = sHead.next) {
+    NS_ABORT_IF_FALSE(node->curr, "empty node!");
+    Remove(node);
+    node->curr->Discard();
+  }
+
+  // Add the sentinel back to the (now empty) list.
+  Reset(&sSentinel);
+
+  // Because the sentinel is the only element in the list, the next timer event
+  // would be a no-op.  Disable the timer as an optimization.
+  TimerOff();
 }
 
-void
-DiscardTracker::InformAllocation(PRUint64 bytes)
+static int
+DiscardTimeoutChangedCallback(const char* aPref, void *aClosure)
 {
-  // This function is called back e.g. from RasterImage::Discard(); be careful!
-
-  sCurrentDecodedImageBytes += bytes;
-  MOZ_ASSERT(sCurrentDecodedImageBytes >= 0);
-
-  // If we're using too much memory for decoded images, MaybeDiscardSoon will
-  // enqueue a callback to discard some images.
-  MaybeDiscardSoon();
+  DiscardTracker::ReloadTimeout();
+  return 0;
 }
 
 /**
  * Initialize the tracker.
  */
 nsresult
 DiscardTracker::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;
+
   // Watch the timeout pref for changes.
   Preferences::RegisterCallback(DiscardTimeoutChangedCallback,
-                                sDiscardTimeoutPref);
+                                DISCARD_TIMEOUT_PREF);
 
-  Preferences::AddUintVarCache(&sMaxDecodedImageKB,
-                              "image.mem.max_decoded_image_kb",
-                              50 * 1024);
+  ReloadTimeout();
 
-  // Create the timer.
-  sTimer = do_CreateInstance("@mozilla.org/timer;1");
-
-  // Read the timeout pref and start the timer.
-  ReloadTimeout();
+  // 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 = true;
 
   return NS_OK;
 }
 
 /**
+ * Shut down the tracker, deallocating the timer.
+ */
+void
+DiscardTracker::Shutdown()
+{
+  if (sTimer) {
+    sTimer->Cancel();
+    NS_RELEASE(sTimer);
+    sTimer = nsnull;
+  }
+}
+
+/**
  * Read the discard timeout from about:config.
  */
 void
 DiscardTracker::ReloadTimeout()
 {
-  // Read the timeout pref.
+  nsresult rv;
+
+  // read the timeout pref
   PRInt32 discardTimeout;
-  nsresult rv = Preferences::GetInt(sDiscardTimeoutPref, &discardTimeout);
+  rv = Preferences::GetInt(DISCARD_TIMEOUT_PREF, &discardTimeout);
 
-  // If we got something bogus, return.
+  // If we got something bogus, return
   if (!NS_SUCCEEDED(rv) || discardTimeout <= 0)
     return;
 
-  // If the value didn't change, return.
+  // If the value didn't change, return
   if ((PRUint32) discardTimeout == sMinDiscardTimeoutMs)
     return;
 
-  // Update the value.
+  // Update the value
   sMinDiscardTimeoutMs = (PRUint32) discardTimeout;
 
-  // Restart the timer so the new timeout takes effect.
-  DisableTimer();
-  EnableTimer();
+  // If the timer's on, restart the clock to make changes take effect
+  if (sTimerOn) {
+    TimerOff();
+    TimerOn();
+  }
 }
 
 /**
  * Enables the timer. No-op if the timer is already running.
  */
 nsresult
-DiscardTracker::EnableTimer()
+DiscardTracker::TimerOn()
 {
   // Nothing to do if the timer's already on.
   if (sTimerOn)
     return NS_OK;
   sTimerOn = true;
 
   // Activate
   return sTimer->InitWithFuncCallback(TimerCallback,
@@ -200,77 +247,47 @@ DiscardTracker::EnableTimer()
                                       sMinDiscardTimeoutMs,
                                       nsITimer::TYPE_REPEATING_SLACK);
 }
 
 /*
  * Disables the timer. No-op if the timer isn't running.
  */
 void
-DiscardTracker::DisableTimer()
+DiscardTracker::TimerOff()
 {
   // Nothing to do if the timer's already off.
   if (!sTimerOn)
     return;
   sTimerOn = false;
 
   // Deactivate
   sTimer->Cancel();
 }
 
 /**
- * Routine activated when the timer fires. This discards everything that's
- * older than sMinDiscardTimeoutMs, and tries to discard enough images so that
- * we go under sMaxDecodedImageKB.
+ * Routine activated when the timer fires. This discards everything
+ * in front of sentinel, and resets the sentinel to the back of the
+ * list.
  */
 void
 DiscardTracker::TimerCallback(nsITimer *aTimer, void *aClosure)
 {
-  DiscardNow();
-}
-
-void
-DiscardTracker::DiscardNow()
-{
-  // Assuming the list is ordered with oldest discard tracker nodes at the back
-  // and newest ones at the front, iterate from back to front discarding nodes
-  // until we encounter one which is new enough to keep and until we go under
-  // our sMaxDecodedImageKB limit.
+  DiscardTrackerNode *node;
 
-  TimeStamp now = TimeStamp::Now();
-  Node* node;
-  while ((node = sDiscardableImages.getLast())) {
-    if ((now - node->timestamp).ToMilliseconds() > sMinDiscardTimeoutMs ||
-        sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024) {
-
-      // Discarding the image should cause sCurrentDecodedImageBytes to
-      // decrease via a call to InformAllocation().
-      node->img->Discard();
-
-      // Careful: Discarding may have caused the node to have been removed
-      // from the list.
-      Remove(node);
-    }
-    else {
-      break;
-    }
+  // 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();
   }
 
-  // If the list is empty, disable the timer.
-  if (sDiscardableImages.isEmpty())
-    DisableTimer();
-}
+  // Append the sentinel to the back of the list
+  Reset(&sSentinel);
 
-void
-DiscardTracker::MaybeDiscardSoon()
-{
-  // Are we carrying around too much decoded image data?  If so, enqueue an
-  // event to try to get us down under our limit.
-  if (sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024 &&
-      !sDiscardableImages.isEmpty() && !sDiscardRunnablePending) {
-    sDiscardRunnablePending = true;
-    nsRefPtr<DiscardRunnable> runnable = new DiscardRunnable();
-    NS_DispatchToCurrentThread(runnable);
-  }
+  // 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();
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/DiscardTracker.h
+++ b/image/src/DiscardTracker.h
@@ -1,116 +1,90 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* -*- 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 Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * 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 mozilla_imagelib_DiscardTracker_h_
 #define mozilla_imagelib_DiscardTracker_h_
 
-#include "mozilla/LinkedList.h"
-#include "mozilla/TimeStamp.h"
+#define DISCARD_TIMEOUT_PREF "image.mem.min_discard_timeout_ms"
 
 class nsITimer;
 
 namespace mozilla {
 namespace image {
+class RasterImage;
 
-class RasterImage;
+// Struct to make a RasterImage insertable into the tracker list. This
+// is embedded within each RasterImage object, and we do 'this->curr = this'
+// on RasterImage construction. Thus, a RasterImage must always call
+// DiscardTracker::Remove() in its destructor to avoid having the tracker
+// point to bogus memory.
+struct DiscardTrackerNode
+{
+  // Pointer to the RasterImage that this node tracks
+  RasterImage *curr;
+
+  // Pointers to the previous and next nodes in the list
+  DiscardTrackerNode *prev, *next;
+};
 
 /**
- * This static class maintains a linked list of RasterImage objects which are
- * eligible for discarding.
- *
- * When Reset() is called, the node is removed from its position in the list
- * (if it was there before) and appended to the beginnings of the list.
- *
- * Periodically (on a timer and when we notice that we're using more memory
- * than we'd like for decoded images), we go through the list and discard
- * decoded data from images at the end of the list.
+ * This static class maintains a linked list of RasterImage 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 DiscardTracker
 {
   public:
-    /**
-     * The DiscardTracker keeps a linked list of Node objects.  Each object
-     * points to a RasterImage and contains a timestamp indicating when the
-     * node was inserted into the tracker.
-     *
-     * This structure is embedded within each RasterImage object, and we do
-     * |mDiscardTrackerNode.img = this| on RasterImage construction.  Thus, a
-     * RasterImage must always call DiscardTracker::Remove() in its destructor
-     * to avoid having the tracker point to bogus memory.
-     */
-    struct Node : public LinkedListElement<Node>
-    {
-      RasterImage *img;
-      TimeStamp timestamp;
-    };
-
-    /**
-     * Add an image to the front of the tracker's list, or move it to the front
-     * if it's already in the list.
-     */
-    static nsresult Reset(struct Node* node);
-
-    /**
-     * Remove a node from the tracker; do nothing if the node is currently
-     * untracked.
-     */
-    static void Remove(struct Node* node);
-
-    /**
-     * Shut the discard tracker down.  This should be called on XPCOM shutdown
-     * so we destroy the discard timer's nsITimer.
-     */
+    static nsresult Reset(struct DiscardTrackerNode *node);
+    static void Remove(struct DiscardTrackerNode *node);
     static void Shutdown();
-
-    /**
-     * Discard the decoded image data for all images tracked by the discard
-     * tracker.
-     */
+    static void ReloadTimeout();
     static void DiscardAll();
-
-    /**
-     * Inform the discard tracker that we've allocated or deallocated some
-     * memory for a decoded image.  We use this to determine when we've
-     * allocated too much memory and should discard some images.
-     */
-    static void InformAllocation(PRUint64 bytes);
-
   private:
-    /**
-     * This is called when the discard timer fires; it calls into DiscardNow().
-     */
-    friend int DiscardTimeoutChangedCallback(const char* aPref, void *aClosure);
-
-    /**
-     * When run, this runnable sets sDiscardRunnablePending to false and calls
-     * DiscardNow().
-     */
-    class DiscardRunnable : public nsRunnable
-    {
-      NS_IMETHOD Run();
-    };
-
     static nsresult Initialize();
-    static void ReloadTimeout();
-    static nsresult EnableTimer();
-    static void DisableTimer();
-    static void MaybeDiscardSoon();
+    static nsresult TimerOn();
+    static void TimerOff();
     static void TimerCallback(nsITimer *aTimer, void *aClosure);
-    static void DiscardNow();
-
-    static LinkedList<Node> sDiscardableImages;
-    static nsCOMPtr<nsITimer> sTimer;
-    static bool sInitialized;
-    static bool sTimerOn;
-    static bool sDiscardRunnablePending;
-    static PRUint64 sCurrentDecodedImageBytes;
-    static PRUint32 sMinDiscardTimeoutMs;
-    static PRUint32 sMaxDecodedImageKB;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif /* mozilla_imagelib_DiscardTracker_h_ */
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -209,17 +209,18 @@ RasterImage::RasterImage(imgStatusTracke
   mDiscardable(false),
   mHasSourceData(false),
   mDecoded(false),
   mHasBeenDecoded(false),
   mInDecoder(false),
   mAnimationFinished(false)
 {
   // Set up the discard tracker node.
-  mDiscardTrackerNode.img = this;
+  mDiscardTrackerNode.curr = this;
+  mDiscardTrackerNode.prev = mDiscardTrackerNode.next = nsnull;
   Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
 
   // Statistics
   num_containers++;
 
   // Register our pref observers if we haven't yet.
   if (NS_UNLIKELY(!gInitializedPrefCaches)) {
     InitPrefCaches();
@@ -2176,17 +2177,18 @@ RasterImage::Discard(bool force)
   // Flag that we no longer have decoded frames for this image
   mDecoded = false;
 
   // Notify that we discarded
   nsCOMPtr<imgIDecoderObserver> observer(do_QueryReferent(mObserver));
   if (observer)
     observer->OnDiscard(nsnull);
 
-  DiscardTracker::Remove(&mDiscardTrackerNode);
+  if (force)
+    DiscardTracker::Remove(&mDiscardTrackerNode);
 
   // Log
   PR_LOG(gCompressedImageAccountingLog, PR_LOG_DEBUG,
          ("CompressedImageAccounting: discarded uncompressed image "
           "data from RasterImage %p (%s) - %d frames (cached count: %d); "
           "Total Containers: %d, Discardable containers: %d, "
           "Total source bytes: %lld, Source bytes for discardable containers %lld",
           this,
@@ -2214,17 +2216,17 @@ RasterImage::CanForciblyDiscard() {
   return mDiscardable &&         // ...Enabled at creation time...
          mHasSourceData;         // ...have the source data...
 }
 
 // Helper method to tell us whether the clock is currently running for
 // discarding this image. Mainly for assertions.
 bool
 RasterImage::DiscardingActive() {
-  return mDiscardTrackerNode.isInList();
+  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
 bool
 RasterImage::StoringSourceData() const {
   return (mDecodeOnDraw || mDiscardable);
 }
@@ -2689,28 +2691,16 @@ RasterImage::UnlockImage()
   if (CanDiscard()) {
     nsresult rv = DiscardTracker::Reset(&mDiscardTrackerNode);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
   return NS_OK;
 }
 
-//******************************************************************************
-/* void requestDiscard() */
-NS_IMETHODIMP
-RasterImage::RequestDiscard()
-{
-  if (CanDiscard()) {
-    Discard();
-  }
-
-  return NS_OK;
-}
-
 // Flushes up to aMaxBytes to the decoder.
 nsresult
 RasterImage::DecodeSomeData(PRUint32 aMaxBytes)
 {
   // We should have a decoder if we get here
   NS_ABORT_IF_FALSE(mDecoder, "trying to decode without decoder!");
 
   // If we have nothing to decode, return
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -193,17 +193,16 @@ public:
   NS_IMETHOD GetImageContainer(mozilla::layers::ImageContainer **_retval NS_OUTPARAM);
   NS_IMETHOD CopyFrame(PRUint32 aWhichFrame, PRUint32 aFlags, gfxImageSurface **_retval NS_OUTPARAM);
   NS_IMETHOD ExtractFrame(PRUint32 aWhichFrame, const nsIntRect & aRect, PRUint32 aFlags, imgIContainer **_retval NS_OUTPARAM);
   NS_IMETHOD Draw(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter, const gfxMatrix & aUserSpaceToImageSpace, const gfxRect & aFill, const nsIntRect & aSubimage, const nsIntSize & aViewportSize, PRUint32 aFlags);
   NS_IMETHOD_(nsIFrame *) GetRootLayoutFrame(void);
   NS_SCRIPTABLE NS_IMETHOD RequestDecode(void);
   NS_SCRIPTABLE NS_IMETHOD LockImage(void);
   NS_SCRIPTABLE NS_IMETHOD UnlockImage(void);
-  NS_SCRIPTABLE NS_IMETHOD RequestDiscard(void);
   NS_SCRIPTABLE NS_IMETHOD ResetAnimation(void);
   NS_IMETHOD_(void) RequestRefresh(const mozilla::TimeStamp& aTime);
   // END NS_DECL_IMGICONTAINER
 
   RasterImage(imgStatusTracker* aStatusTracker = nsnull);
   virtual ~RasterImage();
 
   virtual nsresult StartAnimation();
@@ -630,17 +629,17 @@ private: // data
   //! # loops remaining before animation stops (-1 no stop)
   PRInt32                    mLoopCount;
   
   //! imgIDecoderObserver
   nsWeakPtr                  mObserver;
 
   // Discard members
   PRUint32                   mLockCount;
-  DiscardTracker::Node       mDiscardTrackerNode;
+  DiscardTrackerNode         mDiscardTrackerNode;
 
   // Source data members
   FallibleTArray<char>       mSourceData;
   nsCString                  mSourceDataMimeType;
   nsCString                  mURIString;
 
   friend class DiscardTracker;
 
--- a/image/src/VectorImage.cpp
+++ b/image/src/VectorImage.cpp
@@ -622,25 +622,16 @@ VectorImage::LockImage()
 NS_IMETHODIMP
 VectorImage::UnlockImage()
 {
   // This method is for image-discarding, which only applies to RasterImages.
   return NS_OK;
 }
 
 //******************************************************************************
-/* void requestDiscard() */
-NS_IMETHODIMP
-VectorImage::RequestDiscard()
-{
-  // This method is for image-discarding, which only applies to RasterImages.
-  return NS_OK;
-}
-
-//******************************************************************************
 /* void resetAnimation (); */
 NS_IMETHODIMP
 VectorImage::ResetAnimation()
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
   if (!mIsFullyLoaded || !mHaveAnimations) {
--- a/image/src/VectorImage.h
+++ b/image/src/VectorImage.h
@@ -76,17 +76,16 @@ public:
   NS_IMETHOD GetImageContainer(mozilla::layers::ImageContainer **_retval NS_OUTPARAM) { *_retval = NULL; return NS_OK; }
   NS_IMETHOD CopyFrame(PRUint32 aWhichFrame, PRUint32 aFlags, gfxImageSurface **_retval NS_OUTPARAM);
   NS_IMETHOD ExtractFrame(PRUint32 aWhichFrame, const nsIntRect & aRect, PRUint32 aFlags, imgIContainer **_retval NS_OUTPARAM);
   NS_IMETHOD Draw(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter, const gfxMatrix & aUserSpaceToImageSpace, const gfxRect & aFill, const nsIntRect & aSubimage, const nsIntSize & aViewportSize, PRUint32 aFlags);
   NS_IMETHOD_(nsIFrame *) GetRootLayoutFrame(void);
   NS_SCRIPTABLE NS_IMETHOD RequestDecode(void);
   NS_SCRIPTABLE NS_IMETHOD LockImage(void);
   NS_SCRIPTABLE NS_IMETHOD UnlockImage(void);
-  NS_SCRIPTABLE NS_IMETHOD RequestDiscard(void);
   NS_SCRIPTABLE NS_IMETHOD ResetAnimation(void);
   NS_IMETHOD_(void) RequestRefresh(const mozilla::TimeStamp& aTime);
   // END NS_DECL_IMGICONTAINER
 
   VectorImage(imgStatusTracker* aStatusTracker = nsnull);
   virtual ~VectorImage();
 
   // Methods inherited from Image
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -32,17 +32,16 @@
  * 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 "imgFrame.h"
-#include "DiscardTracker.h"
 
 #include <limits.h>
 
 #include "prmem.h"
 #include "prenv.h"
 
 #include "gfxPlatform.h"
 #include "gfxUtils.h"
@@ -63,18 +62,16 @@ static PRUint32 gTotalDDBs = 0;
 static PRUint32 gTotalDDBSize = 0;
 // only use up a maximum of 64MB in DDBs
 #define kMaxDDBSize (64*1024*1024)
 // and don't let anything in that's bigger than 4MB
 #define kMaxSingleDDBSize (4*1024*1024)
 
 #endif
 
-using namespace mozilla::image;
-
 // Returns true if an image of aWidth x aHeight is allowed and legal.
 static bool AllowedImageSize(PRInt32 aWidth, PRInt32 aHeight)
 {
   // reject over-wide or over-tall images
   const PRInt32 k64KLimit = 0x0000FFFF;
   if (NS_UNLIKELY(aWidth > k64KLimit || aHeight > k64KLimit )) {
     NS_WARNING("image too big");
     return false;
@@ -145,17 +142,16 @@ imgFrame::imgFrame() :
   mSinglePixel(false),
   mNeverUseDeviceSurface(false),
   mFormatChanged(false),
   mCompositingFailed(false)
 #ifdef USE_WIN_SURFACE
   , mIsDDBSurface(false)
 #endif
   , mLocked(false)
-  , mInformedDiscardTracker(false)
 {
   static bool hasCheckedOptimize = false;
   if (!hasCheckedOptimize) {
     if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) {
       gDisableOptimize = true;
     }
     hasCheckedOptimize = true;
   }
@@ -165,20 +161,16 @@ imgFrame::~imgFrame()
 {
   PR_FREEIF(mPalettedImageData);
 #ifdef USE_WIN_SURFACE
   if (mIsDDBSurface) {
       gTotalDDBs--;
       gTotalDDBSize -= mSize.width * mSize.height * 4;
   }
 #endif
-
-  if (mInformedDiscardTracker) {
-    DiscardTracker::InformAllocation(-4 * mSize.height * mSize.width);
-  }
 }
 
 nsresult imgFrame::Init(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, 
                         gfxASurface::gfxImageFormat aFormat, PRUint8 aPaletteDepth /* = 0 */)
 {
   // assert for properties that should be verified by decoders, warn for properties related to bad content
   if (!AllowedImageSize(aWidth, aHeight))
     return NS_ERROR_FAILURE;
@@ -230,24 +222,16 @@ nsresult imgFrame::Init(PRInt32 aX, PRIn
 
 #ifdef XP_MACOSX
     if (!mNeverUseDeviceSurface && !ShouldUseImageSurfaces()) {
       mQuartzSurface = new gfxQuartzImageSurface(mImageSurface);
     }
 #endif
   }
 
-  // Inform the discard tracker that we've allocated some memory, but only if
-  // we're not a paletted image (paletted images are not usually large and are
-  // used only for animated frames, which we don't discard).
-  if (!mPalettedImageData) {
-    DiscardTracker::InformAllocation(4 * mSize.width * mSize.height);
-    mInformedDiscardTracker = true;
-  }
-
   return NS_OK;
 }
 
 nsresult imgFrame::Optimize()
 {
   if (gDisableOptimize)
     return NS_OK;
 
@@ -282,24 +266,16 @@ nsresult imgFrame::Optimize()
         mImageSurface = nsnull;
         mOptSurface = nsnull;
 #ifdef USE_WIN_SURFACE
         mWinSurface = nsnull;
 #endif
 #ifdef XP_MACOSX
         mQuartzSurface = nsnull;
 #endif
-
-        // We just dumped most of our allocated memory, so tell the discard
-        // tracker that we're not using any at all.
-        if (mInformedDiscardTracker) {
-          DiscardTracker::InformAllocation(-4 * mSize.width * mSize.height);
-          mInformedDiscardTracker = false;
-        }
-
         return NS_OK;
       }
     }
 
     // if it's not RGB24/ARGB32, don't optimize, but we never hit this at the moment
   }
 
   // if we're being forced to use image surfaces due to
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -190,17 +190,14 @@ private: // data
   PRInt8       mBlendMethod;
   bool mSinglePixel;
   bool mNeverUseDeviceSurface;
   bool mFormatChanged;
   bool mCompositingFailed;
   /** Indicates if the image data is currently locked */
   bool mLocked;
 
-  /** Have we called DiscardTracker::InformAllocation()? */
-  bool mInformedDiscardTracker;
-
 #ifdef XP_WIN
   bool mIsDDBSurface;
 #endif
 };
 
 #endif /* imgFrame_h */
--- a/image/src/imgRequestProxy.cpp
+++ b/image/src/imgRequestProxy.cpp
@@ -365,26 +365,16 @@ imgRequestProxy::UnlockImage()
   NS_ABORT_IF_FALSE(mLockCount > 0, "calling unlock but no locks!");
 
   mLockCount--;
   if (mImage)
     return mImage->UnlockImage();
   return NS_OK;
 }
 
-/* void requestDiscard (); */
-NS_IMETHODIMP
-imgRequestProxy::RequestDiscard()
-{
-  if (mImage) {
-    return mImage->RequestDiscard();
-  }
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 imgRequestProxy::IncrementAnimationConsumers()
 {
   mAnimationConsumers++;
   if (mImage)
     mImage->IncrementAnimationConsumers();
   return NS_OK;
 }
--- a/image/test/mochitest/Makefile.in
+++ b/image/test/mochitest/Makefile.in
@@ -83,17 +83,16 @@ include $(topsrcdir)/config/rules.mk
                 test_bug552605-1.html \
                 test_bug552605-2.html \
                 bug552605.sjs \
                 bug671906-iframe.html \
                 bug671906.sjs \
                 test_bug671906.html \
 		test_error_events.html \
 		error-early.png \
-		test_drawDiscardedImage.html \
                 $(NULL)
 
 # Tests disabled due to intermittent orange
 # test_bug435296.html disabled - See bug 578591
 # test_bug478398.html disabled - See bug 579139
 
 _CHROME_FILES = imgutils.js \
                 animationPolling.js \
deleted file mode 100644
--- a/image/test/mochitest/test_drawDiscardedImage.html
+++ /dev/null
@@ -1,85 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=731419
--->
-<head>
-  <title>Test for Bug 731419 - Draw an ostensibly discarded image to a canvas</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-
-<body>
-
-<!--
-  Load an image in an iframe, then draw that image to a canvas.  Then set the
-  iframe to display:none (after bug 731419, this causes the image's decoded
-  data to be discarded) and draw the image to a canvas again.  We should draw
-  the same image data both times.
--->
-
-<script>
-
-SimpleTest.waitForExplicitFinish();
-
-var data1;
-
-function drawImage()
-{
-  var canvas = document.getElementById('canvas');
-  var ctx = canvas.getContext('2d');
-  var iframeDoc = document.getElementById('iframe').contentDocument;
-
-  ctx.clearRect(0, 0, canvas.height, canvas.width);
-  ctx.drawImage(iframeDoc.getElementById('image'), 0, 0);
-  return canvas.toDataURL();
-}
-
-function iframeLoad()
-{
-  data1 = drawImage();
-  document.getElementById('iframe').style.display = 'none';
-
-  // Spin the event loop a few times to give the image in the display:none
-  // iframe a chance to be discarded.
-  SimpleTest.executeSoon(function() {
-    SimpleTest.executeSoon(function() {
-      SimpleTest.executeSoon(function() {
-        step2();
-      });
-    });
-  });
-}
-
-function step2()
-{
-  is(drawImage(), data1, "Same image before and after iframe display:none");
-  SimpleTest.finish();
-}
-
-</script>
-
-<canvas id='canvas'></canvas>
-
-<iframe id='iframe' onload='iframeLoad()' src='data:text/html,<img id="image"
-src="data:image/png;base64,
-iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADRElEQVQ4EQXBTWgcZQCA4ff7ZmZ3
-NpvNZLObTWpCuoZGIxWJplAKbVUKavUiHrQHaRG1XrV4SNuD4MFcRDwUoR4qEq2gFUlBEWmtppYi
-acSmMakxtfkx/5tNdmd35/8bn0cAzJ7IXwKGH/q8NDF48vy+7vk/3tzVXDs8nj9cAAiDcD70gwVi
-vvvr4tsjAAAAAmD2RD4GOL34wge21XHsnHWh9/aUjX1pC4C1UpXrP08zN7vMvvujPx3P/PD+0VH3
-BoAcTspXAbK9iuGe78+csy70ZnsVvh+xWQ8p1QI8dNK7CiT9CmeO28/4ZsuVX9/IvQwgmzLaU9LS
-AGh/3KJ5jw6A6ynyL7Xx7UCORiwQGRN0g7C4m4FX9poNV35681ShU6ZbxKDRLJVuZQl9RdSQRB4c
-OtDGoQNtPGHBuh0SaAa+ZvLjHYt8fwfZrpTl2cFp2ZwVDyQzSgLgVIndGN/tIP/c61y/WWb14gaV
-asTWioPSDabnfCqVkK7BHKHtPK0n06oFGQHgewJtbw8AujGNkYTNpTJxbYfaygqR0piYkaRkhMya
-eI2oX9dTQRIFmtrmz7EGpS9vESZjAN7tfo/UL2PouoZwbfxIo9jaoLWlzI7jEPmhLjVEbXs5IPAE
-jx5M0Z5RZDJwqjCENFN8XBtmOP0FXq1O6NR5snsRtsv4C+voCdHQpcfVtTn/xUKXTrMlyfck6BCC
-a02fkDZDqirF5JVrRA8ewagu8NbADN6az9btMoTqjnasKDTHjp5PSM3I5DQy7UliZbCz7bCwFDD/
-b52h3BCviVHOHv2bvmydyvwOM5MSmch9Ji4/SxMNcaNJTw707zdJmBqeo+G5BuO/V6AzQ5Oo01MI
-KBaTOOis3rPZrKeqrbn2hwXA10fY7zvicqeZKPQ8YpKxJCgIpEQXisBVhG6MYcQ0pGJp2XWnSpx8
-52o0ogF8c5/ltMlGIlYHo0qQrq9HxHWFvx3RqCoCFzwn4L+tiIVV5Y5MhWc/mlDnATQAgMkynbMb
-opoN4z2hUAlPBdpO6FNp+JTtkPVaHE7NYX94K/xqrBT/BvwDIAAAgALQAfT1aWJwtyYea9VEXoAo
-RfHGYhTfvRfF48BdYB3YAPgfnOuE39kFlREAAAAASUVORK5CYII=">'></iframe>
-
-</body>
-</html>
-
--- a/mfbt/LinkedList.h
+++ b/mfbt/LinkedList.h
@@ -351,28 +351,16 @@ public:
      * Return true if the list is empty, or false otherwise.
      */
     bool isEmpty()
     {
         return !sentinel.isInList();
     }
 
     /*
-     * Remove all the elements from the list.
-     *
-     * This runs in time linear to the list's length, because we have to mark
-     * each element as not in the list.
-     */
-    void clear()
-    {
-        while (popFirst())
-            continue;
-    }
-
-    /*
      * In a debug build, make sure that the list is sane (no cycles, consistent
      * next/prev pointers, only one sentinel).  Has no effect in release builds.
      */
     void debugAssertIsSane()
     {
 #ifdef DEBUG
         /*
          * Check for cycles in the forward singly-linked list using the
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3328,20 +3328,16 @@ pref("image.mem.min_discard_timeout_ms",
 pref("image.mem.decode_bytes_at_a_time", 4096);
 
 // The longest time we can spend in an iteration of an async decode
 pref("image.mem.max_ms_before_yield", 5);
 
 // The maximum source data size for which we auto sync decode
 pref("image.mem.max_bytes_for_sync_decode", 150000);
 
-// The maximum amount of decoded image data we'll willingly keep around (we
-// might keep around more than this, but we'll try to get down to this value).
-pref("image.mem.max_decoded_image_kb", 50 * 1024);
-
 // WebGL prefs
 pref("webgl.force-enabled", false);
 pref("webgl.disabled", false);
 pref("webgl.shader_validator", true);
 pref("webgl.force_osmesa", false);
 pref("webgl.osmesalib", "");
 pref("webgl.verbose", false);
 pref("webgl.prefer-native-gl", false);