Bug 1383982 - Introduce a general mechanism for measuring memory usage of graph-like structures. r=erahm.
☠☠ backed out by 66e6d95dace3 ☠ ☠
authorNicholas Nethercote <nnethercote@mozilla.com>
Fri, 28 Jul 2017 15:03:44 +1000
changeset 371604 a57d8f30d1bf5de3ba5201a6f5e2a08ef1cf7d85
parent 371603 795aeb51da2b9b2b3667b58ca02e477657253478
child 371605 466818a625e91a40818d3467a80d0befd26a4038
push id32250
push usercbook@mozilla.com
push dateFri, 28 Jul 2017 13:24:57 +0000
treeherdermozilla-central@16ffc1d05422 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerserahm
bugs1383982, 1383977
milestone56.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
Bug 1383982 - Introduce a general mechanism for measuring memory usage of graph-like structures. r=erahm. All the SizeOf{In,Ex}cludingThis() functions take a MallocSizeOf function which measures memory blocks. This patch introduces a new type, SizeOfState, which includes a MallocSizeOf function *and* a table of already-measured pointers, called SeenPtrs. This gives us a general mechanism to measure graph-like data structures, by recording which nodes have already been measured. (This approach is used in a number of existing reporters, but not in a uniform fashion.) The patch also converts the window memory reporting to use SizeOfState in a lot of places, all the way through to the measurement of Elements. This is a precursor for bug 1383977 which will measure Stylo elements, which involve Arcs. The patch also converts the existing mAlreadyMeasuredOrphanTrees table in the OrphanReporter to use the new mechanism.
dom/base/Element.cpp
dom/base/Element.h
dom/base/FragmentOrElement.cpp
dom/base/Link.cpp
dom/base/Link.h
dom/base/nsDocument.cpp
dom/base/nsGenericDOMDataNode.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsINode.cpp
dom/base/nsINode.h
dom/base/nsWindowMemoryReporter.cpp
dom/base/nsWindowMemoryReporter.h
dom/html/HTMLAnchorElement.cpp
dom/html/HTMLAreaElement.cpp
dom/html/HTMLLinkElement.cpp
dom/svg/SVGPathElement.cpp
image/DynamicImage.cpp
image/DynamicImage.h
image/Image.cpp
image/Image.h
image/ImageWrapper.cpp
image/ImageWrapper.h
image/RasterImage.cpp
image/RasterImage.h
image/VectorImage.cpp
image/VectorImage.h
image/imgLoader.cpp
image/imgRequest.cpp
js/xpconnect/src/XPCJSRuntime.cpp
toolkit/components/places/tests/gtest/mock_Link.h
xpcom/base/SizeOfState.h
xpcom/base/moz.build
xpcom/ds/nsTHashtable.h
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -62,16 +62,17 @@
 #include "mozilla/DeclarationBlockInlines.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozilla/MouseEvents.h"
+#include "mozilla/SizeOfState.h"
 #include "mozilla/TextEditor.h"
 #include "mozilla/TextEvents.h"
 #include "nsNodeUtils.h"
 #include "mozilla/dom/DirectionalityUtils.h"
 #include "nsDocument.h"
 #include "nsAttrValueOrString.h"
 #include "nsAttrValueInlines.h"
 #include "nsCSSPseudoElements.h"
@@ -4132,8 +4133,19 @@ Element::ClearServoData() {
 
 void
 Element::SetCustomElementData(CustomElementData* aData)
 {
   nsExtendedDOMSlots *slots = ExtendedDOMSlots();
   MOZ_ASSERT(!slots->mCustomElementData, "Custom element data may not be changed once set.");
   slots->mCustomElementData = aData;
 }
+
+size_t
+Element::SizeOfExcludingThis(SizeOfState& aState) const
+{
+  size_t n = FragmentOrElement::SizeOfExcludingThis(aState);
+
+  // XXX: measure mServoData.
+
+  return n;
+}
+
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -200,16 +200,18 @@ public:
   {
     NS_ASSERTION(!HasServoData(), "expected ServoData to be cleared earlier");
   }
 
 #endif // MOZILLA_INTERNAL_API
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ELEMENT_IID)
 
+  NS_DECL_SIZEOF_EXCLUDING_THIS
+
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
 
   /**
    * Method to get the full state of this element.  See mozilla/EventStates.h
    * for the possible bits that could be set here.
    */
   EventStates State() const
   {
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -2492,25 +2492,25 @@ FragmentOrElement::FireNodeRemovedForChi
   for (child = GetFirstChild();
        child && child->GetParentNode() == this;
        child = child->GetNextSibling()) {
     nsContentUtils::MaybeFireNodeRemoved(child, this, doc);
   }
 }
 
 size_t
-FragmentOrElement::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+FragmentOrElement::SizeOfExcludingThis(SizeOfState& aState) const
 {
   size_t n = 0;
-  n += nsIContent::SizeOfExcludingThis(aMallocSizeOf);
-  n += mAttrsAndChildren.SizeOfExcludingThis(aMallocSizeOf);
+  n += nsIContent::SizeOfExcludingThis(aState);
+  n += mAttrsAndChildren.SizeOfExcludingThis(aState.mMallocSizeOf);
 
   nsDOMSlots* slots = GetExistingDOMSlots();
   if (slots) {
-    n += slots->SizeOfIncludingThis(aMallocSizeOf);
+    n += slots->SizeOfIncludingThis(aState.mMallocSizeOf);
   }
 
   return n;
 }
 
 void
 FragmentOrElement::SetIsElementInStyleScopeFlagOnSubtree(bool aInStyleScope)
 {
--- a/dom/base/Link.cpp
+++ b/dom/base/Link.cpp
@@ -825,24 +825,24 @@ Link::SetHrefAttribute(nsIURI *aURI)
   // "nature" of the nsIURL/nsIURI implementation.
   nsAutoCString href;
   (void)aURI->GetSpec(href);
   (void)mElement->SetAttr(kNameSpaceID_None, nsGkAtoms::href,
                           NS_ConvertUTF8toUTF16(href), true);
 }
 
 size_t
-Link::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+Link::SizeOfExcludingThis(mozilla::SizeOfState& aState) const
 {
   size_t n = 0;
 
   if (mCachedURI) {
     nsCOMPtr<nsISizeOf> iface = do_QueryInterface(mCachedURI);
     if (iface) {
-      n += iface->SizeOfIncludingThis(aMallocSizeOf);
+      n += iface->SizeOfIncludingThis(aState.mMallocSizeOf);
     }
   }
 
   // The following members don't need to be measured:
   // - mElement, because it is a pointer-to-self used to avoid QIs
   // - mHistory, because it is non-owning
 
   return n;
--- a/dom/base/Link.h
+++ b/dom/base/Link.h
@@ -109,17 +109,17 @@ public:
    * Checks if DNS Prefetching is ok
    *
    * @returns boolean
    *          Defaults to true; should be overridden for specialised cases
    */
   virtual bool HasDeferredDNSPrefetchRequest() { return true; }
 
   virtual size_t
-    SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+    SizeOfExcludingThis(mozilla::SizeOfState& aState) const;
 
   virtual bool ElementHasHref() const;
 
   // This is called by HTMLAnchorElement.
   void TryDNSPrefetch();
   void CancelDNSPrefetch(nsWrapperCache::FlagsType aDeferredFlag,
                          nsWrapperCache::FlagsType aRequestedFlag);
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -12327,49 +12327,50 @@ nsDocument::GetVisibilityState(nsAString
   aState.AssignASCII(entry.value, entry.length);
   return NS_OK;
 }
 
 /* virtual */ void
 nsIDocument::DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const
 {
   aWindowSizes->mDOMOtherSize +=
-    nsINode::SizeOfExcludingThis(aWindowSizes->mMallocSizeOf);
+    nsINode::SizeOfExcludingThis(aWindowSizes->mState);
 
   if (mPresShell) {
-    mPresShell->AddSizeOfIncludingThis(aWindowSizes->mMallocSizeOf,
+    mPresShell->AddSizeOfIncludingThis(aWindowSizes->mState.mMallocSizeOf,
                                        &aWindowSizes->mArenaStats,
                                        &aWindowSizes->mLayoutPresShellSize,
                                        &aWindowSizes->mLayoutStyleSetsSize,
                                        &aWindowSizes->mLayoutTextRunsSize,
                                        &aWindowSizes->mLayoutPresContextSize,
                                        &aWindowSizes->mLayoutFramePropertiesSize);
   }
 
   aWindowSizes->mPropertyTablesSize +=
-    mPropertyTable.SizeOfExcludingThis(aWindowSizes->mMallocSizeOf);
+    mPropertyTable.SizeOfExcludingThis(aWindowSizes->mState.mMallocSizeOf);
   for (uint32_t i = 0, count = mExtraPropertyTables.Length();
        i < count; ++i) {
     aWindowSizes->mPropertyTablesSize +=
-      mExtraPropertyTables[i]->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf);
+      mExtraPropertyTables[i]->SizeOfIncludingThis(
+        aWindowSizes->mState.mMallocSizeOf);
   }
 
   if (EventListenerManager* elm = GetExistingListenerManager()) {
     aWindowSizes->mDOMEventListenersCount += elm->ListenerCount();
   }
 
   // Measurement of the following members may be added later if DMD finds it
   // is worthwhile:
   // - many!
 }
 
 void
 nsIDocument::DocAddSizeOfIncludingThis(nsWindowSizes* aWindowSizes) const
 {
-  aWindowSizes->mDOMOtherSize += aWindowSizes->mMallocSizeOf(this);
+  aWindowSizes->mDOMOtherSize += aWindowSizes->mState.mMallocSizeOf(this);
   DocAddSizeOfExcludingThis(aWindowSizes);
 }
 
 static size_t
 SizeOfOwnedSheetArrayExcludingThis(const nsTArray<RefPtr<StyleSheet>>& aSheets,
                                    MallocSizeOf aMallocSizeOf)
 {
   size_t n = 0;
@@ -12380,17 +12381,17 @@ SizeOfOwnedSheetArrayExcludingThis(const
       continue;
     }
     n += sheet->SizeOfIncludingThis(aMallocSizeOf);
   }
   return n;
 }
 
 size_t
-nsDocument::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+nsDocument::SizeOfExcludingThis(SizeOfState& aState) const
 {
   // This SizeOfExcludingThis() overrides the one from nsINode.  But
   // nsDocuments can only appear at the top of the DOM tree, and we use the
   // specialized DocAddSizeOfExcludingThis() in that case.  So this should never
   // be called.
   MOZ_CRASH();
 }
 
@@ -12398,17 +12399,17 @@ void
 nsDocument::DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const
 {
   nsIDocument::DocAddSizeOfExcludingThis(aWindowSizes);
 
   for (nsIContent* node = nsINode::GetFirstChild();
        node;
        node = node->GetNextNode(this))
   {
-    size_t nodeSize = node->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf);
+    size_t nodeSize = node->SizeOfIncludingThis(aWindowSizes->mState);
     size_t* p;
 
     switch (node->NodeType()) {
     case nsIDOMNode::ELEMENT_NODE:
       p = &aWindowSizes->mDOMElementNodesSize;
       break;
     case nsIDOMNode::TEXT_NODE:
       p = &aWindowSizes->mDOMTextNodesSize;
@@ -12428,43 +12429,44 @@ nsDocument::DocAddSizeOfExcludingThis(ns
 
     if (EventListenerManager* elm = node->GetExistingListenerManager()) {
       aWindowSizes->mDOMEventListenersCount += elm->ListenerCount();
     }
   }
 
   aWindowSizes->mStyleSheetsSize +=
     SizeOfOwnedSheetArrayExcludingThis(mStyleSheets,
-                                       aWindowSizes->mMallocSizeOf);
+                                       aWindowSizes->mState.mMallocSizeOf);
   // Note that we do not own the sheets pointed to by mOnDemandBuiltInUASheets
   // (the nsLayoutStyleSheetCache singleton does).
   aWindowSizes->mStyleSheetsSize +=
     mOnDemandBuiltInUASheets.ShallowSizeOfExcludingThis(
-        aWindowSizes->mMallocSizeOf);
+      aWindowSizes->mState.mMallocSizeOf);
   for (auto& sheetArray : mAdditionalSheets) {
     aWindowSizes->mStyleSheetsSize +=
       SizeOfOwnedSheetArrayExcludingThis(sheetArray,
-                                         aWindowSizes->mMallocSizeOf);
+                                         aWindowSizes->mState.mMallocSizeOf);
   }
   // Lumping in the loader with the style-sheets size is not ideal,
   // but most of the things in there are in fact stylesheets, so it
   // doesn't seem worthwhile to separate it out.
   aWindowSizes->mStyleSheetsSize +=
-    CSSLoader()->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf);
+    CSSLoader()->SizeOfIncludingThis(aWindowSizes->mState.mMallocSizeOf);
+
+  aWindowSizes->mDOMOtherSize += mAttrStyleSheet
+                               ? mAttrStyleSheet->DOMSizeOfIncludingThis(
+                                   aWindowSizes->mState.mMallocSizeOf)
+                               : 0;
 
   aWindowSizes->mDOMOtherSize +=
-    mAttrStyleSheet ?
-    mAttrStyleSheet->DOMSizeOfIncludingThis(aWindowSizes->mMallocSizeOf) :
-    0;
+    mStyledLinks.ShallowSizeOfExcludingThis(
+      aWindowSizes->mState.mMallocSizeOf);
 
   aWindowSizes->mDOMOtherSize +=
-    mStyledLinks.ShallowSizeOfExcludingThis(aWindowSizes->mMallocSizeOf);
-
-  aWindowSizes->mDOMOtherSize +=
-    mIdentifierMap.SizeOfExcludingThis(aWindowSizes->mMallocSizeOf);
+    mIdentifierMap.SizeOfExcludingThis(aWindowSizes->mState.mMallocSizeOf);
 
   // Measurement of the following members may be added later if DMD finds it
   // is worthwhile:
   // - many!
 }
 
 already_AddRefed<nsIDocument>
 nsIDocument::Constructor(const GlobalObject& aGlobal,
--- a/dom/base/nsGenericDOMDataNode.cpp
+++ b/dom/base/nsGenericDOMDataNode.cpp
@@ -1105,15 +1105,15 @@ nsChangeHint
 nsGenericDOMDataNode::GetAttributeChangeHint(const nsIAtom* aAttribute,
                                              int32_t aModType) const
 {
   NS_NOTREACHED("Shouldn't be calling this!");
   return nsChangeHint(0);
 }
 
 size_t
-nsGenericDOMDataNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+nsGenericDOMDataNode::SizeOfExcludingThis(SizeOfState& aState) const
 {
-  size_t n = nsIContent::SizeOfExcludingThis(aMallocSizeOf);
-  n += mText.SizeOfExcludingThis(aMallocSizeOf);
+  size_t n = nsIContent::SizeOfExcludingThis(aState);
+  n += mText.SizeOfExcludingThis(aState.mMallocSizeOf);
   return n;
 }
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -13773,50 +13773,52 @@ void
 nsGlobalWindow::DisableTimeChangeNotifications()
 {
   mozilla::time::RemoveWindowListener(AsInner());
 }
 
 void
 nsGlobalWindow::AddSizeOfIncludingThis(nsWindowSizes* aWindowSizes) const
 {
-  aWindowSizes->mDOMOtherSize += aWindowSizes->mMallocSizeOf(this);
+  aWindowSizes->mDOMOtherSize += aWindowSizes->mState.mMallocSizeOf(this);
 
   if (IsInnerWindow()) {
     EventListenerManager* elm = GetExistingListenerManager();
     if (elm) {
       aWindowSizes->mDOMOtherSize +=
-        elm->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf);
+        elm->SizeOfIncludingThis(aWindowSizes->mState.mMallocSizeOf);
       aWindowSizes->mDOMEventListenersCount +=
         elm->ListenerCount();
     }
     if (mDoc) {
       // Multiple global windows can share a document. So only measure the
       // document if it (a) doesn't have a global window, or (b) it's the
       // primary document for the window.
       if (!mDoc->GetInnerWindow() ||
           mDoc->GetInnerWindow() == AsInner()) {
         mDoc->DocAddSizeOfIncludingThis(aWindowSizes);
       }
     }
   }
 
   if (mNavigator) {
     aWindowSizes->mDOMOtherSize +=
-      mNavigator->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf);
+      mNavigator->SizeOfIncludingThis(aWindowSizes->mState.mMallocSizeOf);
   }
 
   aWindowSizes->mDOMEventTargetsSize +=
-    mEventTargetObjects.ShallowSizeOfExcludingThis(aWindowSizes->mMallocSizeOf);
+    mEventTargetObjects.ShallowSizeOfExcludingThis(
+      aWindowSizes->mState.mMallocSizeOf);
 
   for (auto iter = mEventTargetObjects.ConstIter(); !iter.Done(); iter.Next()) {
     DOMEventTargetHelper* et = iter.Get()->GetKey();
     if (nsCOMPtr<nsISizeOfEventTarget> iSizeOf = do_QueryObject(et)) {
       aWindowSizes->mDOMEventTargetsSize +=
-        iSizeOf->SizeOfEventTargetIncludingThis(aWindowSizes->mMallocSizeOf);
+        iSizeOf->SizeOfEventTargetIncludingThis(
+          aWindowSizes->mState.mMallocSizeOf);
     }
     if (EventListenerManager* elm = et->GetExistingListenerManager()) {
       aWindowSizes->mDOMEventListenersCount += elm->ListenerCount();
     }
     ++aWindowSizes->mDOMEventTargetsCount;
   }
 
   if (IsInnerWindow() && mPerformance) {
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -2574,22 +2574,22 @@ nsINode::GetAccessibleNode()
   RefPtr<AccessibleNode> anode = new AccessibleNode(this);
   return anode.forget();
 #endif
 
   return nullptr;
 }
 
 size_t
-nsINode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+nsINode::SizeOfExcludingThis(SizeOfState& aState) const
 {
   size_t n = 0;
   EventListenerManager* elm = GetExistingListenerManager();
   if (elm) {
-    n += elm->SizeOfIncludingThis(aMallocSizeOf);
+    n += elm->SizeOfIncludingThis(aState.mMallocSizeOf);
   }
 
   // Measurement of the following members may be added later if DMD finds it is
   // worthwhile:
   // - mNodeInfo
   // - mSlots
   //
   // The following members are not measured:
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -14,16 +14,17 @@
 #include "nsIDOMNode.h"
 #include "mozilla/dom/NodeInfo.h"            // member (in nsCOMPtr)
 #include "nsIVariant.h"             // for use in GetUserData()
 #include "nsNodeInfoManager.h"      // for use in NodePrincipal()
 #include "nsPropertyTable.h"        // for typedefs
 #include "nsTObserverArray.h"       // for member
 #include "mozilla/ErrorResult.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/SizeOfState.h"    // for SizeOfState
 #include "mozilla/dom/EventTarget.h" // for base class
 #include "js/TypeDecls.h"     // for Handle, Value, JSObject, JSContext
 #include "mozilla/dom/DOMString.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsTHashtable.h"
 #include <iosfwd>
 
 // Including 'windows.h' will #define GetClassInfo to something else.
@@ -263,17 +264,18 @@ private:
 };
 
 // This should be used for any nsINode sub-class that has fields of its own
 // that it needs to measure;  any sub-class that doesn't use it will inherit
 // SizeOfExcludingThis from its super-class.  SizeOfIncludingThis() need not be
 // defined, it is inherited from nsINode.
 // This macro isn't actually specific to nodes, and bug 956400 will move it into MFBT.
 #define NS_DECL_SIZEOF_EXCLUDING_THIS \
-  virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+  virtual size_t SizeOfExcludingThis(mozilla::SizeOfState& aState) \
+    const override;
 
 // Categories of node properties
 // 0 is global.
 #define DOM_USER_DATA         1
 
 // IID for the nsINode interface
 #define NS_INODE_IID \
 { 0x70ba4547, 0x7699, 0x44fc, \
@@ -321,25 +323,25 @@ public:
   // - nsHTMLSelectElement:   mOptions, mRestoreState
   // - nsHTMLTableElement:    mTBodies, mRows, mTableInheritedAttributes
   // - nsHTMLTableSectionElement: mRows
   // - nsHTMLTextAreaElement: mControllers, mState
   //
   // The following members don't need to be measured:
   // - nsIContent: mPrimaryFrame, because it's non-owning and measured elsewhere
   //
-  virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+  virtual size_t SizeOfExcludingThis(mozilla::SizeOfState& aState) const;
 
   // SizeOfIncludingThis doesn't need to be overridden by sub-classes because
   // sub-classes of nsINode are guaranteed to be laid out in memory in such a
   // way that |this| points to the start of the allocated object, even in
-  // methods of nsINode's sub-classes, and so |aMallocSizeOf(this)| is always
+  // methods of nsINode's sub-classes, so aState.mMallocSizeOf(this) is always
   // safe to call no matter which object it was invoked on.
-  virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
-    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  virtual size_t SizeOfIncludingThis(mozilla::SizeOfState& aState) const {
+    return aState.mMallocSizeOf(this) + SizeOfExcludingThis(aState);
   }
 
   friend class nsNodeUtils;
   friend class nsNodeWeakReference;
   friend class nsNodeSupportsWeakRefTearoff;
   friend class nsAttrAndChildArray;
 
 #ifdef MOZILLA_INTERNAL_API
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -47,29 +47,29 @@ nsWindowMemoryReporter::~nsWindowMemoryR
 NS_IMPL_ISUPPORTS(nsWindowMemoryReporter, nsIMemoryReporter, nsIObserver,
                   nsISupportsWeakReference)
 
 static nsresult
 AddNonJSSizeOfWindowAndItsDescendents(nsGlobalWindow* aWindow,
                                       nsTabSizes* aSizes)
 {
   // Measure the window.
-  nsWindowSizes windowSizes(moz_malloc_size_of);
+  SizeOfState state(moz_malloc_size_of);
+  nsWindowSizes windowSizes(state);
   aWindow->AddSizeOfIncludingThis(&windowSizes);
-  windowSizes.addToTabSizes(aSizes);
 
   // Measure the inner window, if there is one.
-  nsWindowSizes innerWindowSizes(moz_malloc_size_of);
   nsGlobalWindow* inner = aWindow->IsOuterWindow() ? aWindow->GetCurrentInnerWindowInternal()
                                                    : nullptr;
   if (inner) {
-    inner->AddSizeOfIncludingThis(&innerWindowSizes);
-    innerWindowSizes.addToTabSizes(aSizes);
+    inner->AddSizeOfIncludingThis(&windowSizes);
   }
 
+  windowSizes.addToTabSizes(aSizes);
+
   nsCOMPtr<nsIDOMWindowCollection> frames = aWindow->GetFrames();
 
   uint32_t length;
   nsresult rv = frames->GetLength(&length);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Measure this window's descendents.
   for (uint32_t i = 0; i < length; i++) {
@@ -304,17 +304,20 @@ CollectWindowReports(nsGlobalWindow *aWi
 #define REPORT_SIZE(_pathTail, _amount, _desc) \
   ReportSize(windowPath, _pathTail, _amount, NS_LITERAL_CSTRING(_desc), \
              aHandleReport, aData);
 
 #define REPORT_COUNT(_pathTail, _amount, _desc) \
   ReportCount(censusWindowPath, _pathTail, _amount, NS_LITERAL_CSTRING(_desc), \
               aHandleReport, aData);
 
-  nsWindowSizes windowSizes(WindowsMallocSizeOf);
+  // This SizeOfState contains the SeenPtrs used for all memory reporting of
+  // this window.
+  SizeOfState state(WindowsMallocSizeOf);
+  nsWindowSizes windowSizes(state);
   aWindow->AddSizeOfIncludingThis(&windowSizes);
 
   REPORT_SIZE("/dom/element-nodes", windowSizes.mDOMElementNodesSize,
               "Memory used by the element nodes in a window's DOM.");
   aWindowTotalSizes->mDOMElementNodesSize += windowSizes.mDOMElementNodesSize;
 
   REPORT_SIZE("/dom/text-nodes", windowSizes.mDOMTextNodesSize,
               "Memory used by the text nodes in a window's DOM.");
@@ -513,17 +516,18 @@ nsWindowMemoryReporter::CollectReports(n
 "about:memory's minimize memory usage button.\n\n"
 "Ghost windows can happen legitimately, but they are often indicative of "
 "leaks in the browser or add-ons.");
 
   WindowPaths windowPaths;
   WindowPaths topWindowPaths;
 
   // Collect window memory usage.
-  nsWindowSizes windowTotalSizes(nullptr);
+  SizeOfState fakeState(nullptr);   // this won't be used
+  nsWindowSizes windowTotalSizes(fakeState);
   nsCOMPtr<amIAddonManager> addonManager;
   if (XRE_IsParentProcess()) {
     // Only try to access the service from the main process.
     addonManager = do_GetService("@mozilla.org/addons/integration;1");
   }
   for (uint32_t i = 0; i < windows.Length(); i++) {
     CollectWindowReports(windows[i], addonManager,
                          &windowTotalSizes, &ghostWindows,
--- a/dom/base/nsWindowMemoryReporter.h
+++ b/dom/base/nsWindowMemoryReporter.h
@@ -34,25 +34,25 @@ class nsWindowSizes {
   macro(Other, mLayoutPresShellSize) \
   macro(Style, mLayoutStyleSetsSize) \
   macro(Other, mLayoutTextRunsSize) \
   macro(Other, mLayoutPresContextSize) \
   macro(Other, mLayoutFramePropertiesSize) \
   macro(Other, mPropertyTablesSize) \
 
 public:
-  explicit nsWindowSizes(mozilla::MallocSizeOf aMallocSizeOf)
+  explicit nsWindowSizes(mozilla::SizeOfState& aState)
     :
       #define ZERO_SIZE(kind, mSize)  mSize(0),
       FOR_EACH_SIZE(ZERO_SIZE)
       #undef ZERO_SIZE
       mDOMEventTargetsCount(0),
       mDOMEventListenersCount(0),
       mArenaStats(),
-      mMallocSizeOf(aMallocSizeOf)
+      mState(aState)
   {}
 
   void addToTabSizes(nsTabSizes *sizes) const {
     #define ADD_TO_TAB_SIZES(kind, mSize) sizes->add(nsTabSizes::kind, mSize);
     FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
     #undef ADD_TO_TAB_SIZES
     mArenaStats.addToTabSizes(sizes);
   }
@@ -70,17 +70,17 @@ public:
   #define DECL_SIZE(kind, mSize) size_t mSize;
   FOR_EACH_SIZE(DECL_SIZE);
   #undef DECL_SIZE
 
   uint32_t mDOMEventTargetsCount;
   uint32_t mDOMEventListenersCount;
 
   nsArenaMemoryStats mArenaStats;
-  mozilla::MallocSizeOf mMallocSizeOf;
+  mozilla::SizeOfState& mState;
 
 #undef FOR_EACH_SIZE
 };
 
 /**
  * nsWindowMemoryReporter is responsible for the 'explicit/window-objects'
  * memory reporter.
  *
--- a/dom/html/HTMLAnchorElement.cpp
+++ b/dom/html/HTMLAnchorElement.cpp
@@ -401,16 +401,16 @@ HTMLAnchorElement::AfterSetAttr(int32_t 
 
 EventStates
 HTMLAnchorElement::IntrinsicState() const
 {
   return Link::LinkState() | nsGenericHTMLElement::IntrinsicState();
 }
 
 size_t
-HTMLAnchorElement::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+HTMLAnchorElement::SizeOfExcludingThis(mozilla::SizeOfState& aState) const
 {
-  return nsGenericHTMLElement::SizeOfExcludingThis(aMallocSizeOf) +
-         Link::SizeOfExcludingThis(aMallocSizeOf);
+  return nsGenericHTMLElement::SizeOfExcludingThis(aState) +
+         Link::SizeOfExcludingThis(aState);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLAreaElement.cpp
+++ b/dom/html/HTMLAreaElement.cpp
@@ -215,20 +215,20 @@ HTMLAreaElement::GetHrefURI() const
 
 EventStates
 HTMLAreaElement::IntrinsicState() const
 {
   return Link::LinkState() | nsGenericHTMLElement::IntrinsicState();
 }
 
 size_t
-HTMLAreaElement::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+HTMLAreaElement::SizeOfExcludingThis(mozilla::SizeOfState& aState) const
 {
-  return nsGenericHTMLElement::SizeOfExcludingThis(aMallocSizeOf) +
-         Link::SizeOfExcludingThis(aMallocSizeOf);
+  return nsGenericHTMLElement::SizeOfExcludingThis(aState) +
+         Link::SizeOfExcludingThis(aState);
 }
 
 JSObject*
 HTMLAreaElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return HTMLAreaElementBinding::Wrap(aCx, this, aGivenProto);
 }
 
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -504,20 +504,20 @@ HTMLLinkElement::GetCORSMode() const
 
 EventStates
 HTMLLinkElement::IntrinsicState() const
 {
   return Link::LinkState() | nsGenericHTMLElement::IntrinsicState();
 }
 
 size_t
-HTMLLinkElement::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+HTMLLinkElement::SizeOfExcludingThis(mozilla::SizeOfState& aState) const
 {
-  return nsGenericHTMLElement::SizeOfExcludingThis(aMallocSizeOf) +
-         Link::SizeOfExcludingThis(aMallocSizeOf);
+  return nsGenericHTMLElement::SizeOfExcludingThis(aState) +
+         Link::SizeOfExcludingThis(aState);
 }
 
 JSObject*
 HTMLLinkElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return HTMLLinkElementBinding::Wrap(aCx, this, aGivenProto);
 }
 
--- a/dom/svg/SVGPathElement.cpp
+++ b/dom/svg/SVGPathElement.cpp
@@ -41,20 +41,20 @@ SVGPathElement::SVGPathElement(already_A
   : SVGPathElementBase(aNodeInfo)
 {
 }
 
 //----------------------------------------------------------------------
 // memory reporting methods
 
 size_t
-SVGPathElement::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+SVGPathElement::SizeOfExcludingThis(mozilla::SizeOfState& aState) const
 {
-  return SVGPathElementBase::SizeOfExcludingThis(aMallocSizeOf) +
-         mD.SizeOfExcludingThis(aMallocSizeOf);
+  return SVGPathElementBase::SizeOfExcludingThis(aState) +
+         mD.SizeOfExcludingThis(aState.mMallocSizeOf);
 }
 
 //----------------------------------------------------------------------
 // nsIDOMNode methods
 
 NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPathElement)
 
 uint32_t
--- a/image/DynamicImage.cpp
+++ b/image/DynamicImage.cpp
@@ -27,17 +27,17 @@ namespace image {
 
 already_AddRefed<ProgressTracker>
 DynamicImage::GetProgressTracker()
 {
   return nullptr;
 }
 
 size_t
-DynamicImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
+DynamicImage::SizeOfSourceWithComputedFallback(SizeOfState& aState) const
 {
   return 0;
 }
 
 void
 DynamicImage::CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                     MallocSizeOf aMallocSizeOf) const
 {
--- a/image/DynamicImage.h
+++ b/image/DynamicImage.h
@@ -29,17 +29,17 @@ public:
   {
     MOZ_ASSERT(aDrawable, "Must have a gfxDrawable to wrap");
   }
 
   // Inherited methods from Image.
   nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
   virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
   virtual size_t SizeOfSourceWithComputedFallback(
-                                 MallocSizeOf aMallocSizeOf) const override;
+                                 SizeOfState& aState) const override;
   virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                      MallocSizeOf aMallocSizeOf) const override;
 
   virtual void IncrementAnimationConsumers() override;
   virtual void DecrementAnimationConsumers() override;
 #ifdef DEBUG
   virtual uint32_t GetAnimationConsumers() override;
 #endif
--- a/image/Image.cpp
+++ b/image/Image.cpp
@@ -10,17 +10,17 @@
 namespace mozilla {
 namespace image {
 
 ///////////////////////////////////////////////////////////////////////////////
 // Memory Reporting
 ///////////////////////////////////////////////////////////////////////////////
 
 ImageMemoryCounter::ImageMemoryCounter(Image* aImage,
-                                       MallocSizeOf aMallocSizeOf,
+                                       SizeOfState& aState,
                                        bool aIsUsed)
   : mIsUsed(aIsUsed)
 {
   MOZ_ASSERT(aImage);
 
   // Extract metadata about the image.
   RefPtr<ImageURL> imageURL(aImage->GetURI());
   if (imageURL) {
@@ -31,18 +31,18 @@ ImageMemoryCounter::ImageMemoryCounter(I
   int32_t height = 0;
   aImage->GetWidth(&width);
   aImage->GetHeight(&height);
   mIntrinsicSize.SizeTo(width, height);
 
   mType = aImage->GetType();
 
   // Populate memory counters for source and decoded data.
-  mValues.SetSource(aImage->SizeOfSourceWithComputedFallback(aMallocSizeOf));
-  aImage->CollectSizeOfSurfaces(mSurfaces, aMallocSizeOf);
+  mValues.SetSource(aImage->SizeOfSourceWithComputedFallback(aState));
+  aImage->CollectSizeOfSurfaces(mSurfaces, aState.mMallocSizeOf);
 
   // Compute totals.
   for (const SurfaceMemoryCounter& surfaceCounter : mSurfaces) {
     mValues += surfaceCounter.Values();
   }
 }
 
 
--- a/image/Image.h
+++ b/image/Image.h
@@ -89,19 +89,17 @@ private:
   const SurfaceKey mKey;
   MemoryCounter mValues;
   const SurfaceMemoryCounterType mType;
   const bool mIsLocked;
 };
 
 struct ImageMemoryCounter
 {
-  ImageMemoryCounter(Image* aImage,
-                     MallocSizeOf aMallocSizeOf,
-                     bool aIsUsed);
+  ImageMemoryCounter(Image* aImage, SizeOfState& aState, bool aIsUsed);
 
   nsCString& URI() { return mURI; }
   const nsCString& URI() const { return mURI; }
   const nsTArray<SurfaceMemoryCounter>& Surfaces() const { return mSurfaces; }
   const gfx::IntSize IntrinsicSize() const { return mIntrinsicSize; }
   const MemoryCounter& Values() const { return mValues; }
   uint16_t Type() const { return mType; }
   bool IsUsed() const { return mIsUsed; }
@@ -162,17 +160,17 @@ public:
   virtual void SetProgressTracker(ProgressTracker* aProgressTracker) {}
 
   /**
    * The size, in bytes, occupied by the compressed source data of the image.
    * If MallocSizeOf does not work on this platform, uses a fallback approach to
    * ensure that something reasonable is always returned.
    */
   virtual size_t
-    SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const = 0;
+    SizeOfSourceWithComputedFallback(SizeOfState& aState) const = 0;
 
   /**
    * Collect an accounting of the memory occupied by the image's surfaces (which
    * together make up its decoded data). Each surface is recorded as a separate
    * SurfaceMemoryCounter, stored in @aCounters.
    */
   virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                      MallocSizeOf aMallocSizeOf) const = 0;
--- a/image/ImageWrapper.cpp
+++ b/image/ImageWrapper.cpp
@@ -25,19 +25,19 @@ namespace image {
 
 already_AddRefed<ProgressTracker>
 ImageWrapper::GetProgressTracker()
 {
   return mInnerImage->GetProgressTracker();
 }
 
 size_t
-ImageWrapper::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
+ImageWrapper::SizeOfSourceWithComputedFallback(SizeOfState& aState) const
 {
-  return mInnerImage->SizeOfSourceWithComputedFallback(aMallocSizeOf);
+  return mInnerImage->SizeOfSourceWithComputedFallback(aState);
 }
 
 void
 ImageWrapper::CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                     MallocSizeOf aMallocSizeOf) const
 {
   mInnerImage->CollectSizeOfSurfaces(aCounters, aMallocSizeOf);
 }
--- a/image/ImageWrapper.h
+++ b/image/ImageWrapper.h
@@ -21,17 +21,17 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_IMGICONTAINER
 
   // Inherited methods from Image.
   nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
   virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
 
   virtual size_t
-    SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const override;
+    SizeOfSourceWithComputedFallback(SizeOfState& aState) const override;
   virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                      MallocSizeOf aMallocSizeOf) const override;
 
   virtual void IncrementAnimationConsumers() override;
   virtual void DecrementAnimationConsumers() override;
 #ifdef DEBUG
   virtual uint32_t GetAnimationConsumers() override;
 #endif
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -700,19 +700,20 @@ RasterImage::UpdateImageContainer()
   AutoTArray<ImageContainer::NonOwningImage, 1> imageList;
   imageList.AppendElement(ImageContainer::NonOwningImage(image, TimeStamp(),
                                                          mLastFrameID++,
                                                          mImageProducerID));
   container->SetCurrentImages(imageList);
 }
 
 size_t
-RasterImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
+RasterImage::SizeOfSourceWithComputedFallback(SizeOfState& aState) const
 {
-  return mSourceBuffer->SizeOfIncludingThisWithComputedFallback(aMallocSizeOf);
+  return mSourceBuffer->SizeOfIncludingThisWithComputedFallback(
+    aState.mMallocSizeOf);
 }
 
 void
 RasterImage::CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                    MallocSizeOf aMallocSizeOf) const
 {
   SurfaceCache::CollectSizeOfSurfaces(ImageKey(this), aCounters, aMallocSizeOf);
   if (mFrameAnimator) {
--- a/image/RasterImage.h
+++ b/image/RasterImage.h
@@ -162,17 +162,17 @@ public:
 
   nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
   virtual nsresult StartAnimation() override;
   virtual nsresult StopAnimation() override;
 
   // Methods inherited from Image
   virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override;
 
-  virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf)
+  virtual size_t SizeOfSourceWithComputedFallback(SizeOfState& aState)
     const override;
   virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                      MallocSizeOf aMallocSizeOf) const override;
 
   /* Triggers discarding. */
   void Discard();
 
 
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -375,28 +375,28 @@ VectorImage::Init(const char* aMimeType,
     SurfaceCache::LockImage(ImageKey(this));
   }
 
   mIsInitialized = true;
   return NS_OK;
 }
 
 size_t
-VectorImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
+VectorImage::SizeOfSourceWithComputedFallback(SizeOfState& aState) const
 {
   if (!mSVGDocumentWrapper) {
     return 0; // No document, so no memory used for the document.
   }
 
   nsIDocument* doc = mSVGDocumentWrapper->GetDocument();
   if (!doc) {
     return 0; // No document, so no memory used for the document.
   }
 
-  nsWindowSizes windowSizes(aMallocSizeOf);
+  nsWindowSizes windowSizes(aState);
   doc->DocAddSizeOfIncludingThis(&windowSizes);
 
   if (windowSizes.getTotalSize() == 0) {
     // MallocSizeOf fails on this platform. Because we also use this method for
     // determining the size of cache entries, we need to return something
     // reasonable here. Unfortunately, there's no way to estimate the document's
     // size accurately, so we just use a constant value of 100KB, which will
     // generally underestimate the true size.
--- a/image/VectorImage.h
+++ b/image/VectorImage.h
@@ -30,17 +30,17 @@ public:
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_IMGICONTAINER
 
   // (no public constructor - use ImageFactory)
 
   // Methods inherited from Image
   nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
-  virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf)
+  virtual size_t SizeOfSourceWithComputedFallback(SizeOfState& aState)
     const override;
   virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                      MallocSizeOf aMallocSizeOf) const override;
 
   virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
                                         nsISupports* aContext,
                                         nsIInputStream* aInStr,
                                         uint64_t aSourceOffset,
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -135,17 +135,18 @@ public:
         if (!image) {
           continue;
         }
 
         // Both this and EntryImageSizes measure images/content/raster/used/decoded
         // memory.  This function's measurement is secondary -- the result doesn't
         // go in the "explicit" tree -- so we use moz_malloc_size_of instead of
         // ImagesMallocSizeOf to prevent DMD from seeing it reported twice.
-        ImageMemoryCounter counter(image, moz_malloc_size_of, /* aIsUsed = */ true);
+        SizeOfState state(moz_malloc_size_of);
+        ImageMemoryCounter counter(image, state, /* aIsUsed = */ true);
 
         n += counter.Values().DecodedHeap();
         n += counter.Values().DecodedNonHeap();
       }
     }
     return n;
   }
 
@@ -404,17 +405,18 @@ private:
                                       nsTArray<ImageMemoryCounter>* aArray,
                                       bool aIsUsed)
   {
     RefPtr<image::Image> image = aRequest->GetImage();
     if (!image) {
       return;
     }
 
-    ImageMemoryCounter counter(image, ImagesMallocSizeOf, aIsUsed);
+    SizeOfState state(ImagesMallocSizeOf);
+    ImageMemoryCounter counter(image, state, aIsUsed);
 
     aArray->AppendElement(Move(counter));
   }
 };
 
 NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter)
 
 NS_IMPL_ISUPPORTS(nsProgressNotificationProxy,
--- a/image/imgRequest.cpp
+++ b/image/imgRequest.cpp
@@ -612,17 +612,18 @@ imgRequest::SetIsInCache(bool aInCache)
 void
 imgRequest::UpdateCacheEntrySize()
 {
   if (!mCacheEntry) {
     return;
   }
 
   RefPtr<Image> image = GetImage();
-  size_t size = image->SizeOfSourceWithComputedFallback(moz_malloc_size_of);
+  SizeOfState state(moz_malloc_size_of);
+  size_t size = image->SizeOfSourceWithComputedFallback(state);
   mCacheEntry->SetDataSize(size);
 }
 
 void
 imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry, nsIRequest* aRequest)
 {
   /* get the expires info */
   if (aCacheEntry) {
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2114,61 +2114,56 @@ class JSMainRuntimeCompartmentsReporter 
 
 NS_IMPL_ISUPPORTS(JSMainRuntimeCompartmentsReporter, nsIMemoryReporter)
 
 MOZ_DEFINE_MALLOC_SIZE_OF(OrphanMallocSizeOf)
 
 namespace xpc {
 
 static size_t
-SizeOfTreeIncludingThis(nsINode* tree)
+SizeOfTreeIncludingThis(nsINode* tree, SizeOfState& aState)
 {
-    size_t n = tree->SizeOfIncludingThis(OrphanMallocSizeOf);
+    size_t n = tree->SizeOfIncludingThis(aState);
     for (nsIContent* child = tree->GetFirstChild(); child; child = child->GetNextNode(tree))
-        n += child->SizeOfIncludingThis(OrphanMallocSizeOf);
+        n += child->SizeOfIncludingThis(aState);
 
     return n;
 }
 
 class OrphanReporter : public JS::ObjectPrivateVisitor
 {
   public:
     explicit OrphanReporter(GetISupportsFun aGetISupports)
       : JS::ObjectPrivateVisitor(aGetISupports)
+      , mState(OrphanMallocSizeOf)
+    {}
+
+    virtual size_t sizeOfIncludingThis(nsISupports* aSupports) override
     {
-    }
-
-    virtual size_t sizeOfIncludingThis(nsISupports* aSupports) override {
         size_t n = 0;
         nsCOMPtr<nsINode> node = do_QueryInterface(aSupports);
         // https://bugzilla.mozilla.org/show_bug.cgi?id=773533#c11 explains
         // that we have to skip XBL elements because they violate certain
         // assumptions.  Yuk.
         if (node && !node->IsInUncomposedDoc() &&
             !(node->IsElement() && node->AsElement()->IsInNamespace(kNameSpaceID_XBL)))
         {
             // This is an orphan node.  If we haven't already handled the
             // sub-tree that this node belongs to, measure the sub-tree's size
             // and then record its root so we don't measure it again.
             nsCOMPtr<nsINode> orphanTree = node->SubtreeRoot();
-            if (orphanTree &&
-                !mAlreadyMeasuredOrphanTrees.Contains(orphanTree)) {
-                // If PutEntry() fails we don't measure this tree, which could
-                // lead to under-measurement. But that's better than the
-                // alternatives, which are over-measurement or an OOM abort.
-                if (mAlreadyMeasuredOrphanTrees.PutEntry(orphanTree, fallible)) {
-                    n += SizeOfTreeIncludingThis(orphanTree);
-                }
+            if (orphanTree && !mState.HaveSeenPtr(orphanTree.get())) {
+                n += SizeOfTreeIncludingThis(orphanTree, mState);
             }
         }
         return n;
     }
 
   private:
-    nsTHashtable <nsISupportsHashKey> mAlreadyMeasuredOrphanTrees;
+    SizeOfState mState;
 };
 
 #ifdef DEBUG
 static bool
 StartsWithExplicit(nsACString& s)
 {
     return StringBeginsWith(s, NS_LITERAL_CSTRING("explicit/"));
 }
--- a/toolkit/components/places/tests/gtest/mock_Link.h
+++ b/toolkit/components/places/tests/gtest/mock_Link.h
@@ -37,17 +37,18 @@ public:
   {
     // Notify our callback function.
     mHandler(aState);
 
     // Break the cycle so the object can be destroyed.
     mDeathGrip = nullptr;
   }
 
-  virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override
+  virtual size_t SizeOfExcludingThis(mozilla::SizeOfState& aState)
+    const override
   {
     return 0;   // the value shouldn't matter
   }
 
   virtual void NodeInfoChanged(nsIDocument* aOldDoc) final override {}
 
 protected:
   ~mock_Link() {
new file mode 100644
--- /dev/null
+++ b/xpcom/base/SizeOfState.h
@@ -0,0 +1,73 @@
+/* -*- 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/. */
+
+#ifndef SizeOfState_h
+#define SizeOfState_h
+
+#include "mozilla/fallible.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Unused.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+
+// This file includes types that are useful during memory reporting, but which
+// cannot be put into mfbt/MemoryReporting.h because they depend on things that
+// are not in MFBT.
+
+namespace mozilla {
+
+// A table of seen pointers. Useful when measuring structures that contain
+// nodes that may be pointed to from multiple places, e.g. via RefPtr (in C++
+// code) or Arc (in Rust code).
+class SeenPtrs : public nsTHashtable<nsPtrHashKey<const void>>
+{
+public:
+  // Returns true if we have seen this pointer before, false otherwise. Also
+  // remembers this pointer for later queries.
+  bool HaveSeenPtr(const void* aPtr)
+  {
+    uint32_t oldCount = Count();
+
+    mozilla::Unused << PutEntry(aPtr, fallible);
+
+    // If the counts match, there are two possibilities.
+    //
+    // - Lookup succeeded: we've seen the pointer before, and didn't need to
+    //   add a new entry.
+    //
+    // - PutEntry() tried to add the entry and failed due to lack of memory. In
+    //   this case we can't tell if this pointer has been seen before (because
+    //   the table is in an unreliable state and may have dropped previous
+    //   insertions). When doing memory reporting it's better to err on the
+    //   side of under-reporting rather than over-reporting, so we assume we've
+    //   seen the pointer before.
+    //
+    return oldCount == Count();
+  }
+};
+
+// Memory reporting state. Some memory measuring functions
+// (SizeOfIncludingThis(), etc.) just need a MallocSizeOf parameter, but some
+// also need a record of pointers that have been seen and should not be
+// re-measured. This class encapsulates both of those things.
+class SizeOfState
+{
+public:
+  explicit SizeOfState(MallocSizeOf aMallocSizeOf)
+    : mMallocSizeOf(aMallocSizeOf)
+  {}
+
+  bool HaveSeenPtr(const void* aPtr) { return mSeenPtrs.HaveSeenPtr(aPtr); }
+
+  MallocSizeOf mMallocSizeOf;
+  SeenPtrs mSeenPtrs;
+};
+
+} // namespace mozilla
+
+#endif // SizeOfState_h
+
--- a/xpcom/base/moz.build
+++ b/xpcom/base/moz.build
@@ -106,16 +106,17 @@ EXPORTS.mozilla += [
     'IntentionalCrash.h',
     'JSObjectHolder.h',
     'LinuxUtils.h',
     'Logging.h',
     'MemoryReportingProcess.h',
     'nsMemoryInfoDumper.h',
     'NSPRLogModulesParser.h',
     'OwningNonNull.h',
+    'SizeOfState.h',
     'StaticMutex.h',
     'StaticPtr.h',
     'SystemMemoryReporter.h',
 ]
 
 # nsDebugImpl isn't unified because we disable PGO so that NS_ABORT_OOM isn't
 # optimized away oddly.
 SOURCES += [
--- a/xpcom/ds/nsTHashtable.h
+++ b/xpcom/ds/nsTHashtable.h
@@ -139,27 +139,32 @@ public:
   /**
    * Return true if an entry for the given key exists, false otherwise.
    * @param     aKey the key to retrieve
    * @return    true if the key exists, false if the key doesn't exist
    */
   bool Contains(KeyType aKey) const { return !!GetEntry(aKey); }
 
   /**
-   * Get the entry associated with a key, or create a new entry,
+   * Infallibly get the entry associated with a key, or create a new entry,
    * @param     aKey the key to retrieve
-   * @return    pointer to the entry class retreived; nullptr only if memory
-                can't be allocated
+   * @return    pointer to the entry retrieved; never nullptr
    */
   EntryType* PutEntry(KeyType aKey)
   {
     // infallible add
     return static_cast<EntryType*>(mTable.Add(EntryType::KeyToPointer(aKey)));
   }
 
+  /**
+   * Fallibly get the entry associated with a key, or create a new entry,
+   * @param     aKey the key to retrieve
+   * @return    pointer to the entry retrieved; nullptr only if memory can't
+   *            be allocated
+   */
   MOZ_MUST_USE
   EntryType* PutEntry(KeyType aKey, const fallible_t&)
   {
     return static_cast<EntryType*>(mTable.Add(EntryType::KeyToPointer(aKey),
                                               mozilla::fallible));
   }
 
   /**