1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsWindowMemoryReporter.h"
8 #include "nsWindowSizes.h"
9 #include "nsGlobalWindow.h"
10 #include "mozilla/dom/BrowsingContext.h"
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/ClearOnShutdown.h"
13 #include "mozilla/Preferences.h"
14 #include "mozilla/Services.h"
15 #include "mozilla/StaticPtr.h"
16 #include "mozilla/Telemetry.h"
17 #include "mozilla/ResultExtensions.h"
19 #include "nsPrintfCString.h"
20 #include "XPCJSMemoryReporter.h"
21 #include "js/MemoryMetrics.h"
22 #include "nsQueryObject.h"
23 #include "nsServiceManagerUtils.h"
24 #include "nsXULPrototypeCache.h"
26 using namespace mozilla
;
27 using namespace mozilla::dom
;
29 StaticRefPtr
<nsWindowMemoryReporter
> sWindowReporter
;
32 * Don't trigger a ghost window check when a DOM window is detached if we've
33 * run it this recently.
35 const int32_t kTimeBetweenChecks
= 45; /* seconds */
37 nsWindowMemoryReporter::nsWindowMemoryReporter()
38 : mLastCheckForGhostWindows(TimeStamp::NowLoRes()),
39 mCycleCollectorIsRunning(false),
40 mCheckTimerWaitingForCCEnd(false),
41 mGhostWindowCount(0) {}
43 nsWindowMemoryReporter::~nsWindowMemoryReporter() { KillCheckTimer(); }
45 NS_IMPL_ISUPPORTS(nsWindowMemoryReporter
, nsIMemoryReporter
, nsIObserver
,
46 nsISupportsWeakReference
)
48 static nsresult
AddNonJSSizeOfWindowAndItsDescendents(
49 nsGlobalWindowOuter
* aWindow
, nsTabSizes
* aSizes
) {
50 // Measure the window.
51 SizeOfState
state(moz_malloc_size_of
);
52 nsWindowSizes
windowSizes(state
);
53 aWindow
->AddSizeOfIncludingThis(windowSizes
);
55 // Measure the inner window, if there is one.
56 nsGlobalWindowInner
* inner
= aWindow
->GetCurrentInnerWindowInternal();
58 inner
->AddSizeOfIncludingThis(windowSizes
);
61 windowSizes
.addToTabSizes(aSizes
);
63 BrowsingContext
* bc
= aWindow
->GetBrowsingContext();
68 // Measure this window's descendents.
69 for (const auto& frame
: bc
->Children()) {
70 if (auto* childWin
= nsGlobalWindowOuter::Cast(frame
->GetDOMWindow())) {
71 MOZ_TRY(AddNonJSSizeOfWindowAndItsDescendents(childWin
, aSizes
));
77 static nsresult
NonJSSizeOfTab(nsPIDOMWindowOuter
* aWindow
, size_t* aDomSize
,
78 size_t* aStyleSize
, size_t* aOtherSize
) {
79 nsGlobalWindowOuter
* window
= nsGlobalWindowOuter::Cast(aWindow
);
82 nsresult rv
= AddNonJSSizeOfWindowAndItsDescendents(window
, &sizes
);
83 NS_ENSURE_SUCCESS(rv
, rv
);
85 *aDomSize
= sizes
.mDom
;
86 *aStyleSize
= sizes
.mStyle
;
87 *aOtherSize
= sizes
.mOther
;
92 void nsWindowMemoryReporter::Init() {
93 MOZ_ASSERT(!sWindowReporter
);
94 sWindowReporter
= new nsWindowMemoryReporter();
95 ClearOnShutdown(&sWindowReporter
);
96 RegisterStrongMemoryReporter(sWindowReporter
);
97 RegisterNonJSSizeOfTab(NonJSSizeOfTab
);
99 nsCOMPtr
<nsIObserverService
> os
= services::GetObserverService();
101 os
->AddObserver(sWindowReporter
, "after-minimize-memory-usage",
102 /* weakRef = */ true);
103 os
->AddObserver(sWindowReporter
, "cycle-collector-begin",
104 /* weakRef = */ true);
105 os
->AddObserver(sWindowReporter
, "cycle-collector-end",
106 /* weakRef = */ true);
109 RegisterGhostWindowsDistinguishedAmount(GhostWindowsDistinguishedAmount
);
113 nsWindowMemoryReporter
* nsWindowMemoryReporter::Get() {
114 return sWindowReporter
;
117 static nsCString
GetWindowURISpec(nsGlobalWindowInner
* aWindow
) {
118 NS_ENSURE_TRUE(aWindow
, ""_ns
);
120 nsCOMPtr
<Document
> doc
= aWindow
->GetExtantDoc();
122 nsCOMPtr
<nsIURI
> uri
;
123 uri
= doc
->GetDocumentURI();
124 return uri
->GetSpecOrDefault();
126 nsCOMPtr
<nsIScriptObjectPrincipal
> scriptObjPrincipal
=
127 do_QueryObject(aWindow
);
128 NS_ENSURE_TRUE(scriptObjPrincipal
, ""_ns
);
130 // GetPrincipal() will print a warning if the window does not have an outer
131 // window, so check here for an outer window first. This code is
132 // functionally correct if we leave out the GetOuterWindow() check, but we
133 // end up printing a lot of warnings during debug mochitests.
134 if (!aWindow
->GetOuterWindow()) {
137 nsIPrincipal
* principal
= scriptObjPrincipal
->GetPrincipal();
142 principal
->GetAsciiSpec(spec
);
146 static void AppendWindowURI(nsGlobalWindowInner
* aWindow
, nsACString
& aStr
,
148 nsCString spec
= GetWindowURISpec(aWindow
);
150 if (spec
.IsEmpty()) {
151 // If we're unable to find a URI, we're dealing with a chrome window with
152 // no document in it (or somesuch), so we call this a "system window".
153 aStr
+= "[system]"_ns
;
156 if (aAnonymize
&& !aWindow
->IsChromeWindow()) {
157 aStr
.AppendPrintf("<anonymized-%" PRIu64
">", aWindow
->WindowID());
160 // A hack: replace forward slashes with '\\' so they aren't
161 // treated as path separators. Users of the reporters
162 // (such as about:memory) have to undo this change.
163 spec
.ReplaceChar('/', '\\');
167 MOZ_DEFINE_MALLOC_SIZE_OF(WindowsMallocSizeOf
)
169 // The key is the window ID.
170 using WindowPaths
= nsTHashMap
<nsUint64HashKey
, nsCString
>;
172 static void ReportAmount(const nsCString
& aBasePath
, const char* aPathTail
,
173 size_t aAmount
, const nsCString
& aDescription
,
174 uint32_t aKind
, uint32_t aUnits
,
175 nsIHandleReportCallback
* aHandleReport
,
176 nsISupports
* aData
) {
181 nsAutoCString
path(aBasePath
);
184 aHandleReport
->Callback(""_ns
, path
, aKind
, aUnits
, aAmount
, aDescription
,
188 static void ReportSize(const nsCString
& aBasePath
, const char* aPathTail
,
189 size_t aAmount
, const nsCString
& aDescription
,
190 nsIHandleReportCallback
* aHandleReport
,
191 nsISupports
* aData
) {
192 ReportAmount(aBasePath
, aPathTail
, aAmount
, aDescription
,
193 nsIMemoryReporter::KIND_HEAP
, nsIMemoryReporter::UNITS_BYTES
,
194 aHandleReport
, aData
);
197 static void ReportCount(const nsCString
& aBasePath
, const char* aPathTail
,
198 size_t aAmount
, const nsCString
& aDescription
,
199 nsIHandleReportCallback
* aHandleReport
,
200 nsISupports
* aData
) {
201 ReportAmount(aBasePath
, aPathTail
, aAmount
, aDescription
,
202 nsIMemoryReporter::KIND_OTHER
, nsIMemoryReporter::UNITS_COUNT
,
203 aHandleReport
, aData
);
206 static void ReportDOMSize(const nsCString
& aBasePath
,
207 nsDOMSizes
& aTotalDOMSizes
,
208 nsIHandleReportCallback
* aHandleReport
,
209 nsISupports
* aData
, nsDOMSizes aDOMSizes
) {
210 #define REPORT_DOM_SIZE(_windowPath, _pathTail, _field, _desc) \
211 ReportSize(_windowPath, _pathTail, aDOMSizes._field, \
212 nsLiteralCString(_desc), aHandleReport, aData); \
213 aTotalDOMSizes._field += aDOMSizes._field;
215 REPORT_DOM_SIZE(aBasePath
, "/dom/element-nodes", mDOMElementNodesSize
,
216 "Memory used by the element nodes in a window's DOM.");
218 REPORT_DOM_SIZE(aBasePath
, "/dom/text-nodes", mDOMTextNodesSize
,
219 "Memory used by the text nodes in a window's DOM.");
221 REPORT_DOM_SIZE(aBasePath
, "/dom/cdata-nodes", mDOMCDATANodesSize
,
222 "Memory used by the CDATA nodes in a window's DOM.");
224 REPORT_DOM_SIZE(aBasePath
, "/dom/comment-nodes", mDOMCommentNodesSize
,
225 "Memory used by the comment nodes in a window's DOM.");
228 aBasePath
, "/dom/event-targets", mDOMEventTargetsSize
,
229 "Memory used by the event targets table in a window's DOM, and "
230 "the objects it points to, which include XHRs.");
232 REPORT_DOM_SIZE(aBasePath
, "/dom/performance/user-entries",
233 mDOMPerformanceUserEntries
,
234 "Memory used for performance user entries.");
236 REPORT_DOM_SIZE(aBasePath
, "/dom/performance/resource-entries",
237 mDOMPerformanceResourceEntries
,
238 "Memory used for performance resource entries.");
240 REPORT_DOM_SIZE(aBasePath
, "/dom/media-query-lists", mDOMMediaQueryLists
,
241 "Memory used by MediaQueryList objects for the window's "
244 REPORT_DOM_SIZE(aBasePath
, "/dom/resize-observers",
245 mDOMResizeObserverControllerSize
,
246 "Memory used for resize observers.");
248 REPORT_DOM_SIZE(aBasePath
, "/dom/other", mDOMOtherSize
,
249 "Memory used by a window's DOM that isn't measured by the "
250 "other 'dom/' numbers.");
251 #undef REPORT_DOM_SIZE
254 static void CollectWindowReports(nsGlobalWindowInner
* aWindow
,
255 nsWindowSizes
* aWindowTotalSizes
,
256 nsTHashSet
<uint64_t>* aGhostWindowIDs
,
257 WindowPaths
* aWindowPaths
,
258 WindowPaths
* aTopWindowPaths
,
259 nsIHandleReportCallback
* aHandleReport
,
260 nsISupports
* aData
, bool aAnonymize
) {
261 nsAutoCString
windowPath("explicit/");
263 // Avoid calling aWindow->GetInProcessTop() if there's no outer window. It
264 // will work just fine, but will spew a lot of warnings.
265 nsGlobalWindowOuter
* top
= nullptr;
266 if (aWindow
->GetOuterWindow()) {
267 // Our window should have a null top iff it has a null docshell.
268 MOZ_ASSERT(!!aWindow
->GetInProcessTopInternal() ==
269 !!aWindow
->GetDocShell());
270 top
= aWindow
->GetInProcessTopInternal();
273 windowPath
+= "window-objects/"_ns
;
276 windowPath
+= "top("_ns
;
277 AppendWindowURI(top
->GetCurrentInnerWindowInternal(), windowPath
,
279 windowPath
.AppendPrintf(", id=%" PRIu64
")", top
->WindowID());
281 aTopWindowPaths
->InsertOrUpdate(aWindow
->WindowID(), windowPath
);
283 windowPath
+= aWindow
->IsFrozen() ? "/cached/"_ns
: "/active/"_ns
;
285 if (aGhostWindowIDs
->Contains(aWindow
->WindowID())) {
286 windowPath
+= "top(none)/ghost/"_ns
;
288 windowPath
+= "top(none)/detached/"_ns
;
292 windowPath
+= "window("_ns
;
293 AppendWindowURI(aWindow
, windowPath
, aAnonymize
);
294 windowPath
+= ")"_ns
;
296 // Use |windowPath|, but replace "explicit/" with "event-counts/".
297 nsCString
censusWindowPath(windowPath
);
298 censusWindowPath
.ReplaceLiteral(0, strlen("explicit"), "event-counts");
300 // Remember the path for later.
301 aWindowPaths
->InsertOrUpdate(aWindow
->WindowID(), windowPath
);
303 // Report the size from windowSizes and add to the appropriate total in
304 // aWindowTotalSizes.
305 #define REPORT_SIZE(_pathTail, _field, _desc) \
306 ReportSize(windowPath, _pathTail, windowSizes._field, \
307 nsLiteralCString(_desc), aHandleReport, aData); \
308 aWindowTotalSizes->_field += windowSizes._field;
310 // Report the size, which is a sum of other sizes, and so doesn't require
311 // updating aWindowTotalSizes.
312 #define REPORT_SUM_SIZE(_pathTail, _amount, _desc) \
313 ReportSize(windowPath, _pathTail, _amount, nsLiteralCString(_desc), \
314 aHandleReport, aData);
316 // Like REPORT_SIZE, but for a count.
317 #define REPORT_COUNT(_pathTail, _field, _desc) \
318 ReportCount(censusWindowPath, _pathTail, windowSizes._field, \
319 nsLiteralCString(_desc), aHandleReport, aData); \
320 aWindowTotalSizes->_field += windowSizes._field;
322 // This SizeOfState contains the SeenPtrs used for all memory reporting of
324 SizeOfState
state(WindowsMallocSizeOf
);
325 nsWindowSizes
windowSizes(state
);
326 aWindow
->AddSizeOfIncludingThis(windowSizes
);
328 ReportDOMSize(windowPath
, aWindowTotalSizes
->mDOMSizes
, aHandleReport
, aData
,
329 windowSizes
.mDOMSizes
);
331 nsCString
dataDocumentPath(windowPath
);
332 dataDocumentPath
+= "/data-documents";
333 nsWindowSizes
dataDocumentSizes(state
);
334 aWindow
->CollectDOMSizesForDataDocuments(dataDocumentSizes
);
335 ReportDOMSize(dataDocumentPath
, aWindowTotalSizes
->mDOMSizes
, aHandleReport
,
336 aData
, dataDocumentSizes
.mDOMSizes
);
338 REPORT_SIZE("/layout/style-sheets", mLayoutStyleSheetsSize
,
339 "Memory used by document style sheets within a window.");
341 REPORT_SIZE("/layout/svg-mapped-declarations", mLayoutSvgMappedDeclarations
,
342 "Memory used by mapped declarations of SVG elements");
344 REPORT_SIZE("/layout/shadow-dom/style-sheets",
345 mLayoutShadowDomStyleSheetsSize
,
346 "Memory used by Shadow DOM style sheets within a window.");
348 // TODO(emilio): We might want to split this up between invalidation map /
349 // element-and-pseudos / revalidation too just like the style set.
350 REPORT_SIZE("/layout/shadow-dom/author-styles", mLayoutShadowDomAuthorStyles
,
351 "Memory used by Shadow DOM computed rule data within a window.");
353 REPORT_SIZE("/layout/pres-shell", mLayoutPresShellSize
,
354 "Memory used by layout's PresShell, along with any structures "
355 "allocated in its arena and not measured elsewhere, "
358 REPORT_SIZE("/layout/display-list", mLayoutRetainedDisplayListSize
,
359 "Memory used by the retained display list data, "
360 "along with any structures allocated in its arena and not "
361 "measured elsewhere, within a window.");
363 REPORT_SIZE("/layout/style-sets/stylist/rule-tree",
364 mLayoutStyleSetsStylistRuleTree
,
365 "Memory used by rule trees within style sets within a window.");
367 REPORT_SIZE("/layout/style-sets/stylist/element-and-pseudos-maps",
368 mLayoutStyleSetsStylistElementAndPseudosMaps
,
369 "Memory used by element and pseudos maps within style "
370 "sets within a window.");
372 REPORT_SIZE("/layout/style-sets/stylist/invalidation-map",
373 mLayoutStyleSetsStylistInvalidationMap
,
374 "Memory used by invalidation maps within style sets "
377 REPORT_SIZE("/layout/style-sets/stylist/revalidation-selectors",
378 mLayoutStyleSetsStylistRevalidationSelectors
,
379 "Memory used by selectors for cache revalidation within "
380 "style sets within a window.");
382 REPORT_SIZE("/layout/style-sets/stylist/other", mLayoutStyleSetsStylistOther
,
383 "Memory used by other Stylist data within style sets "
386 REPORT_SIZE("/layout/style-sets/other", mLayoutStyleSetsOther
,
387 "Memory used by other parts of style sets within a window.");
389 REPORT_SIZE("/layout/element-data-objects", mLayoutElementDataObjects
,
390 "Memory used for ElementData objects, but not the things "
391 "hanging off them.");
393 REPORT_SIZE("/layout/text-runs", mLayoutTextRunsSize
,
394 "Memory used for text-runs (glyph layout) in the PresShell's "
395 "frame tree, within a window.");
397 REPORT_SIZE("/layout/pres-contexts", mLayoutPresContextSize
,
398 "Memory used for the PresContext in the PresShell's frame "
401 REPORT_SIZE("/layout/frame-properties", mLayoutFramePropertiesSize
,
402 "Memory used for frame properties attached to frames "
405 REPORT_SIZE("/layout/computed-values/dom", mLayoutComputedValuesDom
,
406 "Memory used by ComputedValues objects accessible from DOM "
409 REPORT_SIZE("/layout/computed-values/non-dom", mLayoutComputedValuesNonDom
,
410 "Memory used by ComputedValues objects not accessible from DOM "
413 REPORT_SIZE("/layout/computed-values/visited", mLayoutComputedValuesVisited
,
414 "Memory used by ComputedValues objects used for visited styles.");
416 REPORT_SIZE("/property-tables", mPropertyTablesSize
,
417 "Memory used for the property tables within a window.");
419 REPORT_SIZE("/bindings", mBindingsSize
,
420 "Memory used by bindings within a window.");
422 REPORT_COUNT("/dom/event-targets", mDOMEventTargetsCount
,
423 "Number of non-node event targets in the event targets table "
424 "in a window's DOM, such as XHRs.");
426 REPORT_COUNT("/dom/event-listeners", mDOMEventListenersCount
,
427 "Number of event listeners in a window, including event "
428 "listeners on nodes and other event targets.");
430 // There are many different kinds of frames, but it is very likely
431 // that only a few matter. Implement a cutoff so we don't bloat
432 // about:memory with many uninteresting entries.
433 const size_t ARENA_SUNDRIES_THRESHOLD
=
434 js::MemoryReportingSundriesThreshold();
436 size_t presArenaSundriesSize
= 0;
437 #define ARENA_OBJECT(name_, sundries_size_, prefix_) \
439 size_t size = windowSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_); \
440 if (size < ARENA_SUNDRIES_THRESHOLD) { \
441 sundries_size_ += size; \
443 REPORT_SUM_SIZE(prefix_ #name_, size, \
444 "Memory used by objects of type " #name_ \
445 " within a window."); \
447 aWindowTotalSizes->mArenaSizes.NS_ARENA_SIZES_FIELD(name_) += size; \
449 #define PRES_ARENA_OBJECT(name_) \
450 ARENA_OBJECT(name_, presArenaSundriesSize, "/layout/pres-arena/")
451 #include "nsPresArenaObjectList.h"
452 #undef PRES_ARENA_OBJECT
454 if (presArenaSundriesSize
> 0) {
456 "/layout/pres-arena/sundries", presArenaSundriesSize
,
457 "The sum of all memory used by objects in the arena which were too "
458 "small to be shown individually.");
461 size_t displayListArenaSundriesSize
= 0;
462 #define DISPLAY_LIST_ARENA_OBJECT(name_) \
463 ARENA_OBJECT(name_, displayListArenaSundriesSize, \
464 "/layout/display-list-arena/")
465 #include "nsDisplayListArenaTypes.h"
466 #undef DISPLAY_LIST_ARENA_OBJECT
468 if (displayListArenaSundriesSize
> 0) {
470 "/layout/display-list-arena/sundries", displayListArenaSundriesSize
,
471 "The sum of all memory used by objects in the DL arena which were too "
472 "small to be shown individually.");
477 // There are many different kinds of style structs, but it is likely that
478 // only a few matter. Implement a cutoff so we don't bloat about:memory with
479 // many uninteresting entries.
480 const size_t STYLE_SUNDRIES_THRESHOLD
=
481 js::MemoryReportingSundriesThreshold();
483 size_t styleSundriesSize
= 0;
484 #define STYLE_STRUCT(name_) \
486 size_t size = windowSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_); \
487 if (size < STYLE_SUNDRIES_THRESHOLD) { \
488 styleSundriesSize += size; \
490 REPORT_SUM_SIZE("/layout/style-structs/" #name_, size, \
491 "Memory used by the " #name_ \
492 " style structs within a window."); \
494 aWindowTotalSizes->mStyleSizes.NS_STYLE_SIZES_FIELD(name_) += size; \
496 #include "nsStyleStructList.h"
499 if (styleSundriesSize
> 0) {
501 "/layout/style-structs/sundries", styleSundriesSize
,
502 "The sum of all memory used by style structs which were too "
503 "small to be shown individually.");
507 #undef REPORT_SUM_SIZE
511 using WindowArray
= nsTArray
<RefPtr
<nsGlobalWindowInner
>>;
514 nsWindowMemoryReporter::CollectReports(nsIHandleReportCallback
* aHandleReport
,
515 nsISupports
* aData
, bool aAnonymize
) {
516 nsGlobalWindowInner::InnerWindowByIdTable
* windowsById
=
517 nsGlobalWindowInner::GetWindowsTable();
518 NS_ENSURE_TRUE(windowsById
, NS_OK
);
520 // Hold on to every window in memory so that window objects can't be
521 // destroyed while we're calling the memory reporter callback.
522 const auto windows
= ToTArray
<WindowArray
>(windowsById
->Values());
524 // Get the IDs of all the "ghost" windows, and call
525 // aHandleReport->Callback() for each one.
526 nsTHashSet
<uint64_t> ghostWindows
;
527 CheckForGhostWindows(&ghostWindows
);
529 for (const auto& key
: ghostWindows
) {
530 nsGlobalWindowInner
* window
= windowsById
->Get(key
);
532 NS_WARNING("Could not look up window?");
537 path
.AppendLiteral("ghost-windows/");
538 AppendWindowURI(window
, path
, aAnonymize
);
540 aHandleReport
->Callback(
541 /* process = */ ""_ns
, path
, nsIMemoryReporter::KIND_OTHER
,
542 nsIMemoryReporter::UNITS_COUNT
,
544 /* description = */ "A ghost window."_ns
, aData
);
549 "ghost-windows", KIND_OTHER
, UNITS_COUNT
, ghostWindows
.Count(),
550 "The number of ghost windows present (the number of nodes underneath "
551 "explicit/window-objects/top(none)/ghost, modulo race conditions). A ghost "
552 "window is not shown in any tab, is not in a browsing context group with any "
553 "non-detached windows, and has met these criteria for at least "
554 "memory.ghost_window_timeout_seconds, or has survived a round of "
555 "about:memory's minimize memory usage button.\n\n"
556 "Ghost windows can happen legitimately, but they are often indicative of "
557 "leaks in the browser or add-ons.");
560 WindowPaths windowPaths
;
561 WindowPaths topWindowPaths
;
563 // Collect window memory usage.
564 SizeOfState
fakeState(nullptr); // this won't be used
565 nsWindowSizes
windowTotalSizes(fakeState
);
566 for (uint32_t i
= 0; i
< windows
.Length(); i
++) {
567 CollectWindowReports(windows
[i
], &windowTotalSizes
, &ghostWindows
,
568 &windowPaths
, &topWindowPaths
, aHandleReport
, aData
,
572 // Report JS memory usage. We do this from here because the JS memory
573 // reporter needs to be passed |windowPaths|.
574 xpc::JSReporter::CollectReports(&windowPaths
, &topWindowPaths
, aHandleReport
,
577 nsXULPrototypeCache::CollectMemoryReports(aHandleReport
, aData
);
579 #define REPORT(_path, _amount, _desc) \
580 aHandleReport->Callback(""_ns, nsLiteralCString(_path), KIND_OTHER, \
581 UNITS_BYTES, _amount, nsLiteralCString(_desc), \
584 REPORT("window-objects/dom/element-nodes",
585 windowTotalSizes
.mDOMSizes
.mDOMElementNodesSize
,
586 "This is the sum of all windows' 'dom/element-nodes' numbers.");
588 REPORT("window-objects/dom/text-nodes",
589 windowTotalSizes
.mDOMSizes
.mDOMTextNodesSize
,
590 "This is the sum of all windows' 'dom/text-nodes' numbers.");
592 REPORT("window-objects/dom/cdata-nodes",
593 windowTotalSizes
.mDOMSizes
.mDOMCDATANodesSize
,
594 "This is the sum of all windows' 'dom/cdata-nodes' numbers.");
596 REPORT("window-objects/dom/comment-nodes",
597 windowTotalSizes
.mDOMSizes
.mDOMCommentNodesSize
,
598 "This is the sum of all windows' 'dom/comment-nodes' numbers.");
600 REPORT("window-objects/dom/event-targets",
601 windowTotalSizes
.mDOMSizes
.mDOMEventTargetsSize
,
602 "This is the sum of all windows' 'dom/event-targets' numbers.");
604 REPORT("window-objects/dom/performance",
605 windowTotalSizes
.mDOMSizes
.mDOMPerformanceUserEntries
+
606 windowTotalSizes
.mDOMSizes
.mDOMPerformanceResourceEntries
,
607 "This is the sum of all windows' 'dom/performance/' numbers.");
609 REPORT("window-objects/dom/other", windowTotalSizes
.mDOMSizes
.mDOMOtherSize
,
610 "This is the sum of all windows' 'dom/other' numbers.");
612 REPORT("window-objects/layout/style-sheets",
613 windowTotalSizes
.mLayoutStyleSheetsSize
,
614 "This is the sum of all windows' 'layout/style-sheets' numbers.");
616 REPORT("window-objects/layout/pres-shell",
617 windowTotalSizes
.mLayoutPresShellSize
,
618 "This is the sum of all windows' 'layout/arenas' numbers.");
620 REPORT("window-objects/layout/style-sets",
621 windowTotalSizes
.mLayoutStyleSetsStylistRuleTree
+
622 windowTotalSizes
.mLayoutStyleSetsStylistElementAndPseudosMaps
+
623 windowTotalSizes
.mLayoutStyleSetsStylistInvalidationMap
+
624 windowTotalSizes
.mLayoutStyleSetsStylistRevalidationSelectors
+
625 windowTotalSizes
.mLayoutStyleSetsStylistOther
+
626 windowTotalSizes
.mLayoutStyleSetsOther
,
627 "This is the sum of all windows' 'layout/style-sets/' numbers.");
629 REPORT("window-objects/layout/element-data-objects",
630 windowTotalSizes
.mLayoutElementDataObjects
,
631 "This is the sum of all windows' 'layout/element-data-objects' "
634 REPORT("window-objects/layout/text-runs",
635 windowTotalSizes
.mLayoutTextRunsSize
,
636 "This is the sum of all windows' 'layout/text-runs' numbers.");
638 REPORT("window-objects/layout/pres-contexts",
639 windowTotalSizes
.mLayoutPresContextSize
,
640 "This is the sum of all windows' 'layout/pres-contexts' numbers.");
642 REPORT("window-objects/layout/frame-properties",
643 windowTotalSizes
.mLayoutFramePropertiesSize
,
644 "This is the sum of all windows' 'layout/frame-properties' numbers.");
646 REPORT("window-objects/layout/computed-values",
647 windowTotalSizes
.mLayoutComputedValuesDom
+
648 windowTotalSizes
.mLayoutComputedValuesNonDom
+
649 windowTotalSizes
.mLayoutComputedValuesVisited
,
650 "This is the sum of all windows' 'layout/computed-values/' numbers.");
652 REPORT("window-objects/property-tables", windowTotalSizes
.mPropertyTablesSize
,
653 "This is the sum of all windows' 'property-tables' numbers.");
655 size_t presArenaTotal
= 0;
656 #define PRES_ARENA_OBJECT(name_) \
657 presArenaTotal += windowTotalSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_);
658 #include "nsPresArenaObjectList.h"
659 #undef PRES_ARENA_OBJECT
661 REPORT("window-objects/layout/pres-arena", presArenaTotal
,
662 "Memory used for the pres arena within windows. "
663 "This is the sum of all windows' 'layout/pres-arena/' numbers.");
665 size_t displayListArenaTotal
= 0;
666 #define DISPLAY_LIST_ARENA_OBJECT(name_) \
667 displayListArenaTotal += \
668 windowTotalSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_);
669 #include "nsDisplayListArenaTypes.h"
670 #undef DISPLAY_LIST_ARENA_OBJECT
672 REPORT("window-objects/layout/display-list-arena", displayListArenaTotal
,
673 "Memory used for the display list arena within windows. This is the "
674 "sum of all windows' 'layout/display-list-arena/' numbers.");
676 size_t styleTotal
= 0;
677 #define STYLE_STRUCT(name_) \
678 styleTotal += windowTotalSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_);
679 #include "nsStyleStructList.h"
682 REPORT("window-objects/layout/style-structs", styleTotal
,
683 "Memory used for style structs within windows. This is the sum of "
684 "all windows' 'layout/style-structs/' numbers.");
691 uint32_t nsWindowMemoryReporter::GetGhostTimeout() {
692 return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
696 nsWindowMemoryReporter::Observe(nsISupports
* aSubject
, const char* aTopic
,
697 const char16_t
* aData
) {
698 if (!strcmp(aTopic
, "after-minimize-memory-usage")) {
699 ObserveAfterMinimizeMemoryUsage();
700 } else if (!strcmp(aTopic
, "cycle-collector-begin")) {
702 mCheckTimerWaitingForCCEnd
= true;
705 mCycleCollectorIsRunning
= true;
706 } else if (!strcmp(aTopic
, "cycle-collector-end")) {
707 mCycleCollectorIsRunning
= false;
708 if (mCheckTimerWaitingForCCEnd
) {
709 mCheckTimerWaitingForCCEnd
= false;
710 AsyncCheckForGhostWindows();
719 void nsWindowMemoryReporter::ObserveDOMWindowDetached(
720 nsGlobalWindowInner
* aWindow
) {
721 nsWeakPtr weakWindow
= do_GetWeakReference(aWindow
);
723 NS_WARNING("Couldn't take weak reference to a window?");
727 mDetachedWindows
.InsertOrUpdate(weakWindow
, TimeStamp());
729 AsyncCheckForGhostWindows();
733 void nsWindowMemoryReporter::CheckTimerFired(nsITimer
* aTimer
, void* aData
) {
734 if (sWindowReporter
) {
735 MOZ_ASSERT(!sWindowReporter
->mCycleCollectorIsRunning
);
736 sWindowReporter
->CheckForGhostWindows();
740 void nsWindowMemoryReporter::AsyncCheckForGhostWindows() {
745 if (mCycleCollectorIsRunning
) {
746 mCheckTimerWaitingForCCEnd
= true;
750 // If more than kTimeBetweenChecks seconds have elapsed since the last check,
751 // timerDelay is 0. Otherwise, it is kTimeBetweenChecks, reduced by the time
752 // since the last check. Reducing the delay by the time since the last check
753 // prevents the timer from being completely starved if it is repeatedly killed
755 int32_t timeSinceLastCheck
=
756 (TimeStamp::NowLoRes() - mLastCheckForGhostWindows
).ToSeconds();
758 (kTimeBetweenChecks
- std::min(timeSinceLastCheck
, kTimeBetweenChecks
)) *
761 NS_NewTimerWithFuncCallback(
762 getter_AddRefs(mCheckTimer
), CheckTimerFired
, nullptr, timerDelay
,
763 nsITimer::TYPE_ONE_SHOT
,
764 "nsWindowMemoryReporter::AsyncCheckForGhostWindows_timer");
767 void nsWindowMemoryReporter::ObserveAfterMinimizeMemoryUsage() {
768 // Someone claims they've done enough GC/CCs so that all eligible windows
769 // have been free'd. So we deem that any windows which satisfy ghost
770 // criteria (1) and (2) now satisfy criterion (3) as well.
772 // To effect this change, we'll backdate some of our timestamps.
774 TimeStamp minTimeStamp
=
775 TimeStamp::Now() - TimeDuration::FromSeconds(GetGhostTimeout());
777 for (auto iter
= mDetachedWindows
.Iter(); !iter
.Done(); iter
.Next()) {
778 TimeStamp
& timeStamp
= iter
.Data();
779 if (!timeStamp
.IsNull() && timeStamp
> minTimeStamp
) {
780 timeStamp
= minTimeStamp
;
786 * Iterate over mDetachedWindows and update it to reflect the current state of
787 * the world. In particular:
789 * - Remove weak refs to windows which no longer exist.
791 * - Remove references to windows which are no longer detached.
793 * - Reset the timestamp on detached windows which share a domain with a
794 * non-detached window (they no longer meet ghost criterion (2)).
796 * - If a window now meets ghost criterion (2) but didn't before, set its
799 * Additionally, if aOutGhostIDs is not null, fill it with the window IDs of
800 * all ghost windows we found.
802 void nsWindowMemoryReporter::CheckForGhostWindows(
803 nsTHashSet
<uint64_t>* aOutGhostIDs
/* = nullptr */) {
804 nsGlobalWindowInner::InnerWindowByIdTable
* windowsById
=
805 nsGlobalWindowInner::GetWindowsTable();
807 NS_WARNING("GetWindowsTable returned null");
811 mLastCheckForGhostWindows
= TimeStamp::NowLoRes();
814 nsTHashSet
<BrowsingContextGroup
*> nonDetachedBrowsingContextGroups
;
816 // Populate nonDetachedBrowsingContextGroups.
817 for (const auto& entry
: *windowsById
) {
818 // Null outer window implies null top, but calling GetInProcessTop() when
819 // there's no outer window causes us to spew debug warnings.
820 nsGlobalWindowInner
* window
= entry
.GetWeak();
821 if (!window
->GetOuterWindow() || !window
->GetInProcessTopInternal() ||
822 !window
->GetBrowsingContextGroup()) {
823 // This window is detached, so we don't care about its browsing
828 nonDetachedBrowsingContextGroups
.Insert(window
->GetBrowsingContextGroup());
831 // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
833 uint32_t ghostTimeout
= GetGhostTimeout();
834 TimeStamp now
= mLastCheckForGhostWindows
;
835 mGhostWindowCount
= 0;
836 for (auto iter
= mDetachedWindows
.Iter(); !iter
.Done(); iter
.Next()) {
837 nsWeakPtr weakKey
= do_QueryInterface(iter
.Key());
838 nsCOMPtr
<mozIDOMWindow
> iwindow
= do_QueryReferent(weakKey
);
840 // The window object has been destroyed. Stop tracking its weak ref in
846 nsPIDOMWindowInner
* window
= nsPIDOMWindowInner::From(iwindow
);
848 // Avoid calling GetInProcessTop() if we have no outer window. Nothing
849 // will break if we do, but it will spew debug output, which can cause our
850 // test logs to overflow.
851 nsCOMPtr
<nsPIDOMWindowOuter
> top
;
852 if (window
->GetOuterWindow()) {
853 top
= window
->GetOuterWindow()->GetInProcessTop();
857 // The window is no longer detached, so we no longer want to track it.
862 TimeStamp
& timeStamp
= iter
.Data();
863 BrowsingContextGroup
* browsingContextGroup
=
864 window
->GetBrowsingContextGroup();
865 if (browsingContextGroup
&&
866 nonDetachedBrowsingContextGroups
.Contains(browsingContextGroup
)) {
867 // This window is in the same browsing context group as a non-detached
868 // window, so reset its clock.
869 timeStamp
= TimeStamp();
871 // This window is not in the same browsing context group as a non-detached
872 // window, so it meets ghost criterion (2).
873 if (timeStamp
.IsNull()) {
874 // This may become a ghost window later; start its clock.
876 } else if ((now
- timeStamp
).ToSeconds() > ghostTimeout
) {
877 // This definitely is a ghost window, so add it to aOutGhostIDs, if
880 if (aOutGhostIDs
&& window
) {
881 aOutGhostIDs
->Insert(window
->WindowID());
887 Telemetry::ScalarSetMaximum(
888 Telemetry::ScalarID::MEMORYREPORTER_MAX_GHOST_WINDOWS
, mGhostWindowCount
);
892 int64_t nsWindowMemoryReporter::GhostWindowsDistinguishedAmount() {
893 return sWindowReporter
->mGhostWindowCount
;
896 void nsWindowMemoryReporter::KillCheckTimer() {
898 mCheckTimer
->Cancel();
899 mCheckTimer
= nullptr;
905 void nsWindowMemoryReporter::UnlinkGhostWindows() {
906 if (!sWindowReporter
) {
910 nsGlobalWindowInner::InnerWindowByIdTable
* windowsById
=
911 nsGlobalWindowInner::GetWindowsTable();
916 // Hold on to every window in memory so that window objects can't be
917 // destroyed while we're calling the UnlinkGhostWindows callback.
918 const auto windows
= ToTArray
<WindowArray
>(windowsById
->Values());
920 // Get the IDs of all the "ghost" windows, and unlink them all.
921 nsTHashSet
<uint64_t> ghostWindows
;
922 sWindowReporter
->CheckForGhostWindows(&ghostWindows
);
923 for (const auto& key
: ghostWindows
) {
924 nsGlobalWindowInner::InnerWindowByIdTable
* windowsById
=
925 nsGlobalWindowInner::GetWindowsTable();
930 RefPtr
<nsGlobalWindowInner
> window
= windowsById
->Get(key
);
932 window
->RiskyUnlink();