Bug 1309082 - Part 1: Extract image tracking from nsDocument into a separate, refcounted object. r=bholley draft
authorCameron McCormack <cam@mcc.id.au>
Thu, 13 Oct 2016 13:02:30 +0800
changeset 424665 c1ee95315b655d4ecb378e6294128399b6c5423f
parent 424520 22be4ae74653b25186665f22e52a50e7027fd36b
child 424666 a8d23a3d5c66ce0c73ad0fb7452edd50a9b50f2e
push id32214
push userbmo:cam@mcc.id.au
push dateThu, 13 Oct 2016 05:02:53 +0000
reviewersbholley
bugs1309082
milestone52.0a1
Bug 1309082 - Part 1: Extract image tracking from nsDocument into a separate, refcounted object. r=bholley This is refcounted as we'll need to hold strong references to the ImageTracker from style structs that load images. MozReview-Commit-ID: 994gE9tOjAn
dom/base/ImageTracker.cpp
dom/base/ImageTracker.h
dom/base/ScriptSettings.cpp
dom/base/moz.build
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsIDocument.h
dom/base/nsImageLoadingContent.cpp
gfx/thebes/gfxSVGGlyphs.cpp
image/SVGDocumentWrapper.cpp
layout/base/nsPresShell.cpp
layout/style/nsStyleStruct.cpp
new file mode 100644
--- /dev/null
+++ b/dom/base/ImageTracker.cpp
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/* table of images used in a document, for batch locking/unlocking and
+ * animating */
+
+#include "ImageTracker.h"
+
+namespace mozilla {
+namespace dom {
+
+ImageTracker::ImageTracker()
+  : mLockingImages(false)
+  , mAnimatingImages(true)
+{
+}
+
+ImageTracker::~ImageTracker()
+{
+  SetImageLockingState(false);
+}
+
+nsresult
+ImageTracker::AddImage(imgIRequest* aImage)
+{
+  MOZ_ASSERT(aImage);
+
+  // See if the image is already in the hashtable. If it is, get the old count.
+  uint32_t oldCount = 0;
+  mImageTracker.Get(aImage, &oldCount);
+
+  // Put the image in the hashtable, with the proper count.
+  mImageTracker.Put(aImage, oldCount + 1);
+
+  nsresult rv = NS_OK;
+
+  // If this is the first insertion and we're locking images, lock this image
+  // too.
+  if (oldCount == 0 && mLockingImages) {
+    rv = aImage->LockImage();
+  }
+
+  // If this is the first insertion and we're animating images, request
+  // that this image be animated too.
+  if (oldCount == 0 && mAnimatingImages) {
+    nsresult rv2 = aImage->IncrementAnimationConsumers();
+    rv = NS_SUCCEEDED(rv) ? rv2 : rv;
+  }
+
+  return rv;
+}
+
+nsresult
+ImageTracker::RemoveImage(imgIRequest* aImage, uint32_t aFlags)
+{
+  NS_ENSURE_ARG_POINTER(aImage);
+
+  // Get the old count. It should exist and be > 0.
+  uint32_t count = 0;
+  DebugOnly<bool> found = mImageTracker.Get(aImage, &count);
+  MOZ_ASSERT(found, "Removing image that wasn't in the tracker!");
+  MOZ_ASSERT(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) {
+    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) {
+    rv = aImage->UnlockImage();
+  }
+
+  // If we're animating images, remove our request to animate this one.
+  if (mAnimatingImages) {
+    nsresult rv2 = aImage->DecrementAnimationConsumers();
+    rv = NS_SUCCEEDED(rv) ? rv2 : rv;
+  }
+
+  if (aFlags & REQUEST_DISCARD) {
+    // 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;
+}
+
+nsresult
+ImageTracker::SetImageLockingState(bool aLocked)
+{
+  if (XRE_IsContentProcess() &&
+      !Preferences::GetBool("image.mem.allow_locking_in_content_processes", true)) {
+    return NS_OK;
+  }
+
+  // If there's no change, there's nothing to do.
+  if (mLockingImages == aLocked)
+    return NS_OK;
+
+  // Otherwise, iterate over our images and perform the appropriate action.
+  for (auto iter = mImageTracker.Iter(); !iter.Done(); iter.Next()) {
+    imgIRequest* image = iter.Key();
+    if (aLocked) {
+      image->LockImage();
+    } else {
+      image->UnlockImage();
+    }
+  }
+
+  // Update state.
+  mLockingImages = aLocked;
+
+  return NS_OK;
+}
+
+void
+ImageTracker::SetImagesNeedAnimating(bool aAnimating)
+{
+  // If there's no change, there's nothing to do.
+  if (mAnimatingImages == aAnimating)
+    return;
+
+  // Otherwise, iterate over our images and perform the appropriate action.
+  for (auto iter = mImageTracker.Iter(); !iter.Done(); iter.Next()) {
+    imgIRequest* image = iter.Key();
+    if (aAnimating) {
+      image->IncrementAnimationConsumers();
+    } else {
+      image->DecrementAnimationConsumers();
+    }
+  }
+
+  // Update state.
+  mAnimatingImages = aAnimating;
+}
+
+void
+ImageTracker::RequestDiscardAll()
+{
+  for (auto iter = mImageTracker.Iter(); !iter.Done(); iter.Next()) {
+    iter.Key()->RequestDiscard();
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/ImageTracker.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/* table of images used in a document, for batch locking/unlocking and
+ * animating */
+
+#ifndef mozilla_dom_ImageTracker
+#define mozilla_dom_ImageTracker
+
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+
+class imgIRequest;
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * Image Tracking
+ *
+ * Style and content images register their imgIRequests with their document's
+ * image tracker, so that we can efficiently tell all descendant images when
+ * they are and are not visible. When an image is on-screen, we want to call
+ * LockImage() on it so that it doesn't do things like discarding frame data
+ * to save memory. The PresShell informs its document's image tracker whether
+ * its images should be locked or not via SetImageLockingState().
+ *
+ * See bug 512260.
+ */
+class ImageTracker
+{
+public:
+  ImageTracker();
+  ImageTracker(const ImageTracker&) = delete;
+  ImageTracker& operator=(const ImageTracker&) = delete;
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageTracker)
+
+  nsresult AddImage(imgIRequest* aImage);
+
+  enum { REQUEST_DISCARD = 0x1 };
+  nsresult RemoveImage(imgIRequest* aImage, uint32_t aFlags = 0);
+
+  // Makes the images on this document locked/unlocked. By default, the locking
+  // state is unlocked/false.
+  nsresult SetImageLockingState(bool aLocked);
+
+  // Makes the images on this document capable of having their animation
+  // active or suspended. An Image will animate as long as at least one of its
+  // owning Documents needs it to animate; otherwise it can suspend.
+  void SetImagesNeedAnimating(bool aAnimating);
+
+  void RequestDiscardAll();
+
+private:
+  ~ImageTracker();
+
+  nsDataHashtable<nsPtrHashKey<imgIRequest>, uint32_t> mImageTracker;
+  bool mLockingImages;
+  bool mAnimatingImages;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ImageTracker
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -781,18 +781,18 @@ AutoJSContext::AutoJSContext(MOZ_GUARD_O
   : mCx(nullptr)
 {
   JS::AutoSuppressGCAnalysis nogc;
   MOZ_ASSERT(!mCx, "mCx should not be initialized!");
   MOZ_ASSERT(NS_IsMainThread());
 
   MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 
-  if (IsJSAPIActive()) {
-    mCx = danger::GetJSContext();
+  if (dom::IsJSAPIActive()) {
+    mCx = dom::danger::GetJSContext();
   } else {
     mJSAPI.Init();
     mCx = mJSAPI.cx();
   }
 }
 
 AutoJSContext::operator JSContext*() const
 {
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -178,16 +178,17 @@ EXPORTS.mozilla.dom += [
     'EventSource.h',
     'File.h',
     'FileList.h',
     'FileReader.h',
     'FormData.h',
     'FragmentOrElement.h',
     'FromParser.h',
     'ImageEncoder.h',
+    'ImageTracker.h',
     'ImportManager.h',
     'Link.h',
     'Location.h',
     'MutableBlobStorage.h',
     'NameSpaceConstants.h',
     'Navigator.h',
     'NodeInfo.h',
     'NodeInfoInlines.h',
@@ -239,16 +240,17 @@ UNIFIED_SOURCES += [
     'Element.cpp',
     'EventSource.cpp',
     'File.cpp',
     'FileList.cpp',
     'FileReader.cpp',
     'FormData.cpp',
     'FragmentOrElement.cpp',
     'ImageEncoder.cpp',
+    'ImageTracker.cpp',
     'ImportManager.cpp',
     'Link.cpp',
     'Location.cpp',
     'MultipartBlobImpl.cpp',
     'MutableBlobStorage.cpp',
     'Navigator.cpp',
     'NodeInfo.cpp',
     'NodeIterator.cpp',
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -207,16 +207,17 @@
 #include "mozilla/dom/AnimatableBinding.h"
 #include "mozilla/dom/AnonymousContent.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/DocumentTimeline.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/ImageTracker.h"
 #include "mozilla/dom/MediaQueryList.h"
 #include "mozilla/dom/NodeFilterBinding.h"
 #include "mozilla/OwningNonNull.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/UndoManager.h"
 #include "mozilla/dom/WebComponentsBinding.h"
 #include "mozilla/dom/CustomElementsRegistryBinding.h"
 #include "mozilla/dom/CustomElementsRegistry.h"
@@ -1326,17 +1327,16 @@ nsIDocument::nsIDocument()
   PR_INIT_CLIST(&mDOMMediaQueryLists);
 }
 
 // NOTE! nsDocument::operator new() zeroes out all members, so don't
 // bother initializing members to 0.
 
 nsDocument::nsDocument(const char* aContentType)
   : nsIDocument()
-  , mAnimatingImages(true)
   , mViewportType(Unknown)
 {
   SetContentTypeInternal(nsDependentCString(aContentType));
 
   if (gDocumentLeakPRLog)
     MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
            ("DOCUMENT %p created", this));
 
@@ -1526,21 +1526,16 @@ nsDocument::~nsDocument()
   }
 
   delete mHeaderData;
 
   ClearAllBoxObjects();
 
   mPendingTitleChangeEvent.Revoke();
 
-  // We don't want to leave residual locks on images. Make sure we're in an
-  // unlocked state, and then clear the table.
-  SetImageLockingState(false);
-  mImageTracker.Clear();
-
   mPlugins.Clear();
 }
 
 NS_INTERFACE_TABLE_HEAD(nsDocument)
   NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
   NS_INTERFACE_TABLE_BEGIN
     NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsDocument, nsISupports, nsINode)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsINode)
@@ -3722,19 +3717,17 @@ nsDocument::DeleteShell()
   }
   if (nsPresContext* presContext = mPresShell->GetPresContext()) {
     presContext->RefreshDriver()->CancelPendingEvents(this);
   }
 
   // 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.
-  for (auto iter = mImageTracker.Iter(); !iter.Done(); iter.Next()) {
-    iter.Key()->RequestDiscard();
-  }
+  ImageTracker()->RequestDiscardAll();
 
   // Now that we no longer have a shell, we need to forget about any FontFace
   // objects for @font-face rules that came from the style set.
   RebuildUserFontSet();
 
   mPresShell = nullptr;
   mStyleSetFilled = false;
 }
@@ -8716,17 +8709,17 @@ nsDocument::OnPageShow(bool aPersisted,
     mIsShowing = true;
   }
 
   if (mAnimationController) {
     mAnimationController->OnPageShow();
   }
 
   if (aPersisted) {
-    SetImagesNeedAnimating(true);
+    ImageTracker()->SetImagesNeedAnimating(true);
   }
 
   UpdateVisibilityState();
 
   nsCOMPtr<EventTarget> target = aDispatchStartTarget;
   if (!target) {
     target = do_QueryInterface(GetWindow());
   }
@@ -8811,17 +8804,17 @@ nsDocument::OnPageHide(bool aPersisted,
   if (mAnimationController) {
     mAnimationController->OnPageHide();
   }
 
   // We do not stop the animations (bug 1024343)
   // when the page is refreshing while being dragged out
   nsDocShell* docShell = mDocumentContainer.get();
   if (aPersisted && !(docShell && docShell->InFrameSwap())) {
-    SetImagesNeedAnimating(false);
+    ImageTracker()->SetImagesNeedAnimating(false);
   }
 
   ExitPointerLock();
 
   // Now send out a PageHide event.
   nsCOMPtr<EventTarget> target = aDispatchStartTarget;
   if (!target) {
     target = do_QueryInterface(GetWindow());
@@ -10035,91 +10028,23 @@ nsIDocument::WarnOnceAbout(DocumentWarni
   nsContentUtils::ReportToConsole(flags,
                                   NS_LITERAL_CSTRING("DOM Core"), this,
                                   nsContentUtils::eDOM_PROPERTIES,
                                   kDocumentWarnings[aWarning],
                                   aParams,
                                   aParamsLength);
 }
 
-nsresult
-nsDocument::AddImage(imgIRequest* aImage)
-{
-  NS_ENSURE_ARG_POINTER(aImage);
-
-  // See if the image is already in the hashtable. If it is, get the old count.
-  uint32_t oldCount = 0;
-  mImageTracker.Get(aImage, &oldCount);
-
-  // Put the image in the hashtable, with the proper count.
-  mImageTracker.Put(aImage, oldCount + 1);
-
-  nsresult rv = NS_OK;
-
-  // If this is the first insertion and we're locking images, lock this image
-  // too.
-  if (oldCount == 0 && mLockingImages) {
-    rv = aImage->LockImage();
-  }
-
-  // If this is the first insertion and we're animating images, request
-  // that this image be animated too.
-  if (oldCount == 0 && mAnimatingImages) {
-    nsresult rv2 = aImage->IncrementAnimationConsumers();
-    rv = NS_SUCCEEDED(rv) ? rv2 : rv;
-  }
-
-  return rv;
-}
-
-nsresult
-nsDocument::RemoveImage(imgIRequest* aImage, uint32_t aFlags)
-{
-  NS_ENSURE_ARG_POINTER(aImage);
-
-  // Get the old count. It should exist and be > 0.
-  uint32_t count = 0;
-  DebugOnly<bool> found = mImageTracker.Get(aImage, &count);
-  MOZ_ASSERT(found, "Removing image that wasn't in the tracker!");
-  MOZ_ASSERT(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) {
-    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) {
-    rv = aImage->UnlockImage();
-  }
-
-  // If we're animating images, remove our request to animate this one.
-  if (mAnimatingImages) {
-    nsresult rv2 = aImage->DecrementAnimationConsumers();
-    rv = NS_SUCCEEDED(rv) ? rv2 : rv;
-  }
-
-  if (aFlags & REQUEST_DISCARD) {
-    // 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;
+mozilla::dom::ImageTracker*
+nsIDocument::ImageTracker()
+{
+  if (!mImageTracker) {
+    mImageTracker = new mozilla::dom::ImageTracker;
+  }
+  return mImageTracker;
 }
 
 nsresult
 nsDocument::AddPlugin(nsIObjectLoadingContent* aPlugin)
 {
   MOZ_ASSERT(aPlugin);
   if (!mPlugins.PutEntry(aPlugin)) {
     return NS_ERROR_OUT_OF_MEMORY;
@@ -10178,65 +10103,16 @@ nsDocument::NotifyMediaFeatureValuesChan
     nsCOMPtr<nsIContent> content = iter.Get()->GetKey();
     if (content->IsHTMLElement(nsGkAtoms::img)) {
       auto* imageElement = static_cast<HTMLImageElement*>(content.get());
       imageElement->MediaFeatureValuesChanged();
     }
   }
 }
 
-nsresult
-nsDocument::SetImageLockingState(bool aLocked)
-{
-  if (XRE_IsContentProcess() &&
-      !Preferences::GetBool("image.mem.allow_locking_in_content_processes", true)) {
-    return NS_OK;
-  }
-
-  // If there's no change, there's nothing to do.
-  if (mLockingImages == aLocked)
-    return NS_OK;
-
-  // Otherwise, iterate over our images and perform the appropriate action.
-  for (auto iter = mImageTracker.Iter(); !iter.Done(); iter.Next()) {
-    imgIRequest* image = iter.Key();
-    if (aLocked) {
-      image->LockImage();
-    } else {
-      image->UnlockImage();
-    }
-  }
-
-  // Update state.
-  mLockingImages = aLocked;
-
-  return NS_OK;
-}
-
-void
-nsDocument::SetImagesNeedAnimating(bool aAnimating)
-{
-  // If there's no change, there's nothing to do.
-  if (mAnimatingImages == aAnimating)
-    return;
-
-  // Otherwise, iterate over our images and perform the appropriate action.
-  for (auto iter = mImageTracker.Iter(); !iter.Done(); iter.Next()) {
-    imgIRequest* image = iter.Key();
-    if (aAnimating) {
-      image->IncrementAnimationConsumers();
-    } else {
-      image->DecrementAnimationConsumers();
-    }
-  }
-
-  // Update state.
-  mAnimatingImages = aAnimating;
-}
-
 already_AddRefed<Touch>
 nsIDocument::CreateTouch(nsGlobalWindow* aView,
                          EventTarget* aTarget,
                          int32_t aIdentifier,
                          int32_t aPageX, int32_t aPageY,
                          int32_t aScreenX, int32_t aScreenY,
                          int32_t aClientX, int32_t aClientY,
                          int32_t aRadiusX, int32_t aRadiusY,
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -89,16 +89,17 @@ class nsHtml5TreeOpExecutor;
 class nsDocumentOnStack;
 class nsISecurityConsoleMessage;
 class nsPIBoxObject;
 
 namespace mozilla {
 class EventChainPreVisitor;
 namespace dom {
 class BoxObject;
+class ImageTracker;
 class UndoManager;
 struct LifecycleCallbacks;
 class CallbackFunction;
 
 struct FullscreenRequest : public LinkedListElement<FullscreenRequest>
 {
   explicit FullscreenRequest(Element* aElement);
   FullscreenRequest(const FullscreenRequest&) = delete;
@@ -885,18 +886,16 @@ public:
   GetPendingAnimationTracker() final override
   {
     return mPendingAnimationTracker;
   }
 
   virtual mozilla::PendingAnimationTracker*
   GetOrCreatePendingAnimationTracker() override;
 
-  void SetImagesNeedAnimating(bool aAnimating) override;
-
   virtual void SuppressEventHandling(SuppressionType aWhat,
                                      uint32_t aIncrease) override;
 
   virtual void UnsuppressEventHandlingAndFireEvents(SuppressionType aWhat,
                                                     bool aFireEvents) override;
 
   void DecreaseEventSuppression() {
     MOZ_ASSERT(mEventsSuppressed);
@@ -979,20 +978,16 @@ public:
 
   virtual Element *GetElementById(const nsAString& aElementId) override;
   virtual const nsTArray<Element*>* GetAllElementsForId(const nsAString& aElementId) const override;
 
   virtual Element *LookupImageElement(const nsAString& aElementId) override;
   virtual void MozSetImageElement(const nsAString& aImageElementId,
                                   Element* aElement) override;
 
-  virtual nsresult AddImage(imgIRequest* aImage) override;
-  virtual nsresult RemoveImage(imgIRequest* aImage, uint32_t aFlags) override;
-  virtual nsresult SetImageLockingState(bool aLocked) override;
-
   // AddPlugin adds a plugin-related element to mPlugins when the element is
   // added to the tree.
   virtual nsresult AddPlugin(nsIObjectLoadingContent* aPlugin) override;
   // RemovePlugin removes a plugin-related element to mPlugins when the
   // element is removed from the tree.
   virtual void RemovePlugin(nsIObjectLoadingContent* aPlugin) override;
   // GetPlugins returns the plugin-related elements from
   // the frame and any subframes.
@@ -1412,22 +1407,16 @@ public:
   bool mHasWarnedAboutBoxObjects:1;
 
   bool mDelayFrameLoaderInitialization:1;
 
   bool mSynchronousDOMContentLoaded:1;
 
   bool mInXBLUpdate:1;
 
-  // Whether we're currently holding a lock on all of our images.
-  bool mLockingImages:1;
-
-  // Whether we currently require our images to animate
-  bool mAnimatingImages:1;
-
   // Whether we're currently under a FlushPendingNotifications call to
   // our presshell.  This is used to handle flush reentry correctly.
   bool mInFlush:1;
 
   // Parser aborted. True if the parser of this document was forcibly
   // terminated instead of letting it finish at its own pace.
   bool mParserAborted:1;
 
@@ -1587,19 +1576,16 @@ private:
   RefPtr<mozilla::dom::DOMImplementation> mDOMImplementation;
 
   RefPtr<nsContentList> mImageMaps;
 
   nsCString mScrollToRef;
   uint8_t mScrolledToRefAlready : 1;
   uint8_t mChangeScrollPosWhenScrollingToRef : 1;
 
-  // Tracking for images in the document.
-  nsDataHashtable< nsPtrHashKey<imgIRequest>, uint32_t> mImageTracker;
-
   // Tracking for plugins in the document.
   nsTHashtable< nsPtrHashKey<nsIObjectLoadingContent> > mPlugins;
 
   RefPtr<mozilla::dom::UndoManager> mUndoManager;
 
   RefPtr<mozilla::dom::DocumentTimeline> mDocumentTimeline;
   mozilla::LinkedList<mozilla::dom::DocumentTimeline> mTimelines;
 
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -130,16 +130,17 @@ class DOMStringList;
 class Element;
 struct ElementCreationOptions;
 struct ElementRegistrationOptions;
 class Event;
 class EventTarget;
 class FontFaceSet;
 class FrameRequestCallback;
 struct FullscreenRequest;
+class ImageTracker;
 class ImportManager;
 class HTMLBodyElement;
 struct LifecycleCallbackArgs;
 class Link;
 class Location;
 class MediaQueryList;
 class GlobalObject;
 class NodeFilter;
@@ -2066,21 +2067,16 @@ public:
   virtual mozilla::PendingAnimationTracker* GetPendingAnimationTracker() = 0;
 
   // Gets the tracker for animations that are waiting to start and
   // creates it if it doesn't already exist. As a result, the return value
   // will never be nullptr.
   virtual mozilla::PendingAnimationTracker*
   GetOrCreatePendingAnimationTracker() = 0;
 
-  // Makes the images on this document capable of having their animation
-  // active or suspended. An Image will animate as long as at least one of its
-  // owning Documents needs it to animate; otherwise it can suspend.
-  virtual void SetImagesNeedAnimating(bool aAnimating) = 0;
-
   enum SuppressionType {
     eAnimationsOnly = 0x1,
 
     // Note that suppressing events also suppresses animation frames, so
     // there's no need to split out events in its own bitmask.
     eEvents = 0x3,
   };
 
@@ -2349,39 +2345,17 @@ public:
    * throttled. We throttle requestAnimationFrame for documents which aren't
    * visible (e.g. scrolled out of the viewport).
    */
   bool ShouldThrottleFrameRequests();
 
   // This returns true when the document tree is being teared down.
   bool InUnlinkOrDeletion() { return mInUnlinkOrDeletion; }
 
-  /*
-   * Image Tracking
-   *
-   * Style and content images register their imgIRequests with their document
-   * so that the document can efficiently tell all descendant images when they
-   * are and are not visible. When an image is on-screen, we want to call
-   * LockImage() on it so that it doesn't do things like discarding frame data
-   * to save memory. The PresShell informs the document whether its images
-   * should be locked or not via SetImageLockingState().
-   *
-   * See bug 512260.
-   */
-
-  // Add/Remove images from the document image tracker
-  virtual nsresult AddImage(imgIRequest* aImage) = 0;
-  // If the REQUEST_DISCARD flag is passed then if the lock count is zero we
-  // will request the image be discarded now (instead of waiting).
-  enum { REQUEST_DISCARD = 0x1 };
-  virtual nsresult RemoveImage(imgIRequest* aImage, uint32_t aFlags = 0) = 0;
-
-  // Makes the images on this document locked/unlocked. By default, the locking
-  // state is unlocked/false.
-  virtual nsresult SetImageLockingState(bool aLocked) = 0;
+  mozilla::dom::ImageTracker* ImageTracker();
 
   virtual nsresult AddPlugin(nsIObjectLoadingContent* aPlugin) = 0;
   virtual void RemovePlugin(nsIObjectLoadingContent* aPlugin) = 0;
   virtual void GetPlugins(nsTArray<nsIObjectLoadingContent*>& aPlugins) = 0;
 
   virtual nsresult AddResponsiveContent(nsIContent* aContent) = 0;
   virtual void RemoveResponsiveContent(nsIContent* aContent) = 0;
   virtual void NotifyMediaFeatureValuesChanged() = 0;
@@ -2922,16 +2896,19 @@ protected:
   // which in turn holds a strong reference to this mNodeInfoManager.
   nsNodeInfoManager* mNodeInfoManager;
   RefPtr<mozilla::css::Loader> mCSSLoader;
   RefPtr<mozilla::css::ImageLoader> mStyleImageLoader;
   RefPtr<nsHTMLStyleSheet> mAttrStyleSheet;
   RefPtr<nsHTMLCSSStyleSheet> mStyleAttrStyleSheet;
   RefPtr<mozilla::SVGAttrAnimationRuleProcessor> mSVGAttrAnimationRuleProcessor;
 
+  // Tracking for images in the document.
+  RefPtr<mozilla::dom::ImageTracker> mImageTracker;
+
   // The set of all object, embed, applet, video/audio elements or
   // nsIObjectLoadingContent or nsIDocumentActivity for which this is the
   // owner document. (They might not be in the document.)
   // These are non-owning pointers, the elements are responsible for removing
   // themselves when they go away.
   nsAutoPtr<nsTHashtable<nsPtrHashKey<nsISupports> > > mActivityObservers;
 
   // The set of all links that need their status resolved.  Links must add themselves
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -41,25 +41,27 @@
 #include "nsSVGEffects.h"
 
 #include "gfxPrefs.h"
 
 #include "mozAutoDocUpdate.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/ImageTracker.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/Preferences.h"
 
 #ifdef LoadImage
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
 #endif
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 #ifdef DEBUG_chb
 static void PrintReqURL(imgIRequest* req) {
   if (!req) {
     printf("(null req)\n");
     return;
   }
 
@@ -1469,21 +1471,21 @@ nsImageLoadingContent::TrackImage(imgIRe
   nsIFrame* frame = GetOurPrimaryFrame();
   if ((frame && frame->GetVisibility() == Visibility::APPROXIMATELY_NONVISIBLE) ||
       (!frame && !mFrameCreateCalled)) {
     return;
   }
 
   if (aImage == mCurrentRequest && !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
     mCurrentRequestFlags |= REQUEST_IS_TRACKED;
-    doc->AddImage(mCurrentRequest);
+    doc->ImageTracker()->AddImage(mCurrentRequest);
   }
   if (aImage == mPendingRequest && !(mPendingRequestFlags & REQUEST_IS_TRACKED)) {
     mPendingRequestFlags |= REQUEST_IS_TRACKED;
-    doc->AddImage(mPendingRequest);
+    doc->ImageTracker()->AddImage(mPendingRequest);
   }
 }
 
 void
 nsImageLoadingContent::UntrackImage(imgIRequest* aImage,
                                     const Maybe<OnNonvisible>& aNonvisibleAction
                                       /* = Nothing() */)
 {
@@ -1496,32 +1498,34 @@ nsImageLoadingContent::UntrackImage(imgI
   // We may not be in the document.  If we outlived our document that's fine,
   // because the document empties out the tracker and unlocks all locked images
   // on destruction.  But if we were never in the document we may need to force
   // discarding the image here, since this is the only chance we have.
   nsIDocument* doc = GetOurCurrentDoc();
   if (aImage == mCurrentRequest) {
     if (doc && (mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
       mCurrentRequestFlags &= ~REQUEST_IS_TRACKED;
-      doc->RemoveImage(mCurrentRequest,
-                       aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)
-                         ? nsIDocument::REQUEST_DISCARD
-                         : 0);
+      doc->ImageTracker()->RemoveImage(
+        mCurrentRequest,
+        aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)
+          ? ImageTracker::REQUEST_DISCARD
+          : 0);
     } else if (aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)) {
       // If we're not in the document we may still need to be discarded.
       aImage->RequestDiscard();
     }
   }
   if (aImage == mPendingRequest) {
     if (doc && (mPendingRequestFlags & REQUEST_IS_TRACKED)) {
       mPendingRequestFlags &= ~REQUEST_IS_TRACKED;
-      doc->RemoveImage(mPendingRequest,
-                       aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)
-                         ? nsIDocument::REQUEST_DISCARD
-                         : 0);
+      doc->ImageTracker()->RemoveImage(
+        mPendingRequest,
+        aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)
+          ? ImageTracker::REQUEST_DISCARD
+          : 0);
     } else if (aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)) {
       // If we're not in the document we may still need to be discarded.
       aImage->RequestDiscard();
     }
   }
 }
 
 
--- a/gfx/thebes/gfxSVGGlyphs.cpp
+++ b/gfx/thebes/gfxSVGGlyphs.cpp
@@ -26,16 +26,17 @@
 #include "mozilla/LoadInfo.h"
 #include "nsSVGUtils.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsContentUtils.h"
 #include "gfxFont.h"
 #include "nsSMILAnimationController.h"
 #include "gfxContext.h"
 #include "harfbuzz/hb.h"
+#include "mozilla/dom/ImageTracker.h"
 
 #define SVG_CONTENT_TYPE NS_LITERAL_CSTRING("image/svg+xml")
 #define UTF8_CHARSET NS_LITERAL_CSTRING("utf-8")
 
 using namespace mozilla;
 
 typedef mozilla::dom::Element Element;
 
@@ -165,17 +166,17 @@ gfxSVGGlyphsDocument::SetupPresentation(
     }
 
     mDocument->FlushPendingNotifications(Flush_Layout);
 
     nsSMILAnimationController* controller = mDocument->GetAnimationController();
     if (controller) {
       controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE);
     }
-    mDocument->SetImagesNeedAnimating(true);
+    mDocument->ImageTracker()->SetImagesNeedAnimating(true);
 
     mViewer = viewer;
     mPresShell = presShell;
     mPresShell->AddPostRefreshObserver(this);
 
     return NS_OK;
 }
 
--- a/image/SVGDocumentWrapper.cpp
+++ b/image/SVGDocumentWrapper.cpp
@@ -25,16 +25,17 @@
 #include "nsSMILAnimationController.h"
 #include "nsServiceManagerUtils.h"
 #include "mozilla/dom/SVGSVGElement.h"
 #include "nsSVGEffects.h"
 #include "mozilla/dom/SVGAnimatedLength.h"
 #include "nsMimeTypes.h"
 #include "DOMSVGLength.h"
 #include "nsDocument.h"
+#include "mozilla/dom/ImageTracker.h"
 
 // undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
 #undef GetCurrentTime
 
 namespace mozilla {
 
 using namespace dom;
 using namespace gfx;
@@ -149,17 +150,17 @@ SVGDocumentWrapper::StartAnimation()
   }
 
   nsIDocument* doc = mViewer->GetDocument();
   if (doc) {
     nsSMILAnimationController* controller = doc->GetAnimationController();
     if (controller) {
       controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE);
     }
-    doc->SetImagesNeedAnimating(true);
+    doc->ImageTracker()->SetImagesNeedAnimating(true);
   }
 }
 
 void
 SVGDocumentWrapper::StopAnimation()
 {
   // Can be called for animated images during shutdown, after we've
   // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
@@ -168,17 +169,17 @@ SVGDocumentWrapper::StopAnimation()
   }
 
   nsIDocument* doc = mViewer->GetDocument();
   if (doc) {
     nsSMILAnimationController* controller = doc->GetAnimationController();
     if (controller) {
       controller->Pause(nsSMILTimeContainer::PAUSE_IMAGE);
     }
-    doc->SetImagesNeedAnimating(false);
+    doc->ImageTracker()->SetImagesNeedAnimating(false);
   }
 }
 
 void
 SVGDocumentWrapper::ResetAnimation()
 {
   SVGSVGElement* svgElem = GetRootSVGElem();
   if (!svgElem) {
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -190,16 +190,17 @@
 #include "nsLayoutStylesheetCache.h"
 #include "mozilla/layers/InputAPZContext.h"
 #include "mozilla/layers/ScrollInputMethods.h"
 #include "nsStyleSet.h"
 #include "mozilla/StyleSetHandle.h"
 #include "mozilla/StyleSetHandleInlines.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/StyleSheetInlines.h"
+#include "mozilla/dom/ImageTracker.h"
 
 #ifdef ANDROID
 #include "nsIDocShellTreeOwner.h"
 #endif
 
 #ifdef MOZ_B2G
 #include "nsIHardwareKeyHandler.h"
 #endif
@@ -10937,17 +10938,17 @@ PresShell::SetIsActive(bool aIsActive)
  * dependent factors changes.
  */
 nsresult
 PresShell::UpdateImageLockingState()
 {
   // We're locked if we're both thawed and active.
   bool locked = !mFrozen && mIsActive;
 
-  nsresult rv = mDocument->SetImageLockingState(locked);
+  nsresult rv = mDocument->ImageTracker()->SetImageLockingState(locked);
 
   if (locked) {
     // Request decodes for visible image frames; we want to start decoding as
     // quickly as possible when we get foregrounded to minimize flashing.
     for (auto iter = mApproximatelyVisibleFrames.Iter(); !iter.Done(); iter.Next()) {
       nsImageFrame* imageFrame = do_QueryFrame(iter.Get()->GetKey());
       if (imageFrame) {
         imageFrame->MaybeDecodeForPredictedSize();
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -28,16 +28,17 @@
 #include "nsBidiUtils.h"
 #include "nsLayoutUtils.h"
 
 #include "imgIRequest.h"
 #include "imgIContainer.h"
 #include "CounterStyleManager.h"
 
 #include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for PlaybackDirection
+#include "mozilla/dom/ImageTracker.h"
 #include "mozilla/Likely.h"
 #include "nsIURI.h"
 #include "nsIDocument.h"
 #include <algorithm>
 
 using namespace mozilla;
 
 static_assert((((1 << nsStyleStructID_Length) - 1) &
@@ -2006,17 +2007,17 @@ nsStyleImage::TrackImage(nsPresContext* 
   // Sanity
   MOZ_ASSERT(!mImageTracked, "Already tracking image!");
   MOZ_ASSERT(mType == eStyleImageType_Image,
              "Can't track image when there isn't one!");
 
   // Register the image with the document
   nsIDocument* doc = aContext->Document();
   if (doc) {
-    doc->AddImage(mImage);
+    doc->ImageTracker()->AddImage(mImage);
   }
 
   // Mark state
 #ifdef DEBUG
   mImageTracked = true;
 #endif
 }
 
@@ -2026,17 +2027,17 @@ nsStyleImage::UntrackImage(nsPresContext
   // Sanity
   MOZ_ASSERT(mImageTracked, "Image not tracked!");
   MOZ_ASSERT(mType == eStyleImageType_Image,
              "Can't untrack image when there isn't one!");
 
   // Unregister the image with the document
   nsIDocument* doc = aContext->Document();
   if (doc) {
-    doc->RemoveImage(mImage);
+    doc->ImageTracker()->RemoveImage(mImage);
   }
 
   // Mark state
 #ifdef DEBUG
   mImageTracked = false;
 #endif
 }
 
@@ -3433,17 +3434,17 @@ nsStyleContentData::TrackImage(nsPresCon
   MOZ_ASSERT(mType == eStyleContentType_Image,
              "Trying to do image tracking on non-image!");
   MOZ_ASSERT(mContent.mImage,
              "Can't track image when there isn't one!");
 
   // Register the image with the document
   nsIDocument* doc = aContext->Document();
   if (doc) {
-    doc->AddImage(mContent.mImage);
+    doc->ImageTracker()->AddImage(mContent.mImage);
   }
 
   // Mark state
 #ifdef DEBUG
   mImageTracked = true;
 #endif
 }
 
@@ -3455,17 +3456,17 @@ nsStyleContentData::UntrackImage(nsPresC
   MOZ_ASSERT(mType == eStyleContentType_Image,
              "Trying to do image tracking on non-image!");
   MOZ_ASSERT(mContent.mImage,
              "Can't untrack image when there isn't one!");
 
   // Unregister the image with the document
   nsIDocument* doc = aContext->Document();
   if (doc) {
-    doc->RemoveImage(mContent.mImage);
+    doc->ImageTracker()->RemoveImage(mContent.mImage);
   }
 
   // Mark state
 #ifdef DEBUG
   mImageTracked = false;
 #endif
 }