Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / dom / base / nsWindowMemoryReporter.cpp
blob5b9ceedc445475f7b87de87b88929294b78244f3
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"
18 #include "nsNetCID.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;
31 /**
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();
57 if (inner) {
58 inner->AddSizeOfIncludingThis(windowSizes);
61 windowSizes.addToTabSizes(aSizes);
63 BrowsingContext* bc = aWindow->GetBrowsingContext();
64 if (!bc) {
65 return NS_OK;
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));
74 return NS_OK;
77 static nsresult NonJSSizeOfTab(nsPIDOMWindowOuter* aWindow, size_t* aDomSize,
78 size_t* aStyleSize, size_t* aOtherSize) {
79 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(aWindow);
81 nsTabSizes sizes;
82 nsresult rv = AddNonJSSizeOfWindowAndItsDescendents(window, &sizes);
83 NS_ENSURE_SUCCESS(rv, rv);
85 *aDomSize = sizes.mDom;
86 *aStyleSize = sizes.mStyle;
87 *aOtherSize = sizes.mOther;
88 return NS_OK;
91 /* static */
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();
100 if (os) {
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);
112 /* static */
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();
121 if (doc) {
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()) {
135 return ""_ns;
137 nsIPrincipal* principal = scriptObjPrincipal->GetPrincipal();
138 if (!principal) {
139 return ""_ns;
141 nsCString spec;
142 principal->GetAsciiSpec(spec);
143 return spec;
146 static void AppendWindowURI(nsGlobalWindowInner* aWindow, nsACString& aStr,
147 bool aAnonymize) {
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;
154 return;
156 if (aAnonymize && !aWindow->IsChromeWindow()) {
157 aStr.AppendPrintf("<anonymized-%" PRIu64 ">", aWindow->WindowID());
158 return;
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('/', '\\');
164 aStr += spec;
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) {
177 if (aAmount == 0) {
178 return;
181 nsAutoCString path(aBasePath);
182 path += aPathTail;
184 aHandleReport->Callback(""_ns, path, aKind, aUnits, aAmount, aDescription,
185 aData);
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.");
227 REPORT_DOM_SIZE(
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 "
242 "document.");
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;
275 if (top) {
276 windowPath += "top("_ns;
277 AppendWindowURI(top->GetCurrentInnerWindowInternal(), windowPath,
278 aAnonymize);
279 windowPath.AppendPrintf(", id=%" PRIu64 ")", top->WindowID());
281 aTopWindowPaths->InsertOrUpdate(aWindow->WindowID(), windowPath);
283 windowPath += aWindow->IsFrozen() ? "/cached/"_ns : "/active/"_ns;
284 } else {
285 if (aGhostWindowIDs->Contains(aWindow->WindowID())) {
286 windowPath += "top(none)/ghost/"_ns;
287 } else {
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
323 // this window.
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, "
356 "within a window.");
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 "
375 "within a window.");
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 "
384 "within a window.");
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 "
399 "within a window.");
401 REPORT_SIZE("/layout/frame-properties", mLayoutFramePropertiesSize,
402 "Memory used for frame properties attached to frames "
403 "within a window.");
405 REPORT_SIZE("/layout/computed-values/dom", mLayoutComputedValuesDom,
406 "Memory used by ComputedValues objects accessible from DOM "
407 "elements.");
409 REPORT_SIZE("/layout/computed-values/non-dom", mLayoutComputedValuesNonDom,
410 "Memory used by ComputedValues objects not accessible from DOM "
411 "elements.");
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; \
442 } else { \
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) {
455 REPORT_SUM_SIZE(
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) {
469 REPORT_SUM_SIZE(
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.");
475 #undef ARENA_OBJECT
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; \
489 } else { \
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"
497 #undef STYLE_STRUCT
499 if (styleSundriesSize > 0) {
500 REPORT_SUM_SIZE(
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.");
506 #undef REPORT_SIZE
507 #undef REPORT_SUM_SIZE
508 #undef REPORT_COUNT
511 using WindowArray = nsTArray<RefPtr<nsGlobalWindowInner>>;
513 NS_IMETHODIMP
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);
531 if (!window) {
532 NS_WARNING("Could not look up window?");
533 continue;
536 nsAutoCString path;
537 path.AppendLiteral("ghost-windows/");
538 AppendWindowURI(window, path, aAnonymize);
540 aHandleReport->Callback(
541 /* process = */ ""_ns, path, nsIMemoryReporter::KIND_OTHER,
542 nsIMemoryReporter::UNITS_COUNT,
543 /* amount = */ 1,
544 /* description = */ "A ghost window."_ns, aData);
547 // clang-format off
548 MOZ_COLLECT_REPORT(
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.");
558 // clang-format on
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,
569 aAnonymize);
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,
575 aData, aAnonymize);
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), \
582 aData);
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' "
632 "numbers.");
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"
680 #undef STYLE_STRUCT
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.");
686 #undef REPORT
688 return NS_OK;
691 uint32_t nsWindowMemoryReporter::GetGhostTimeout() {
692 return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
695 NS_IMETHODIMP
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")) {
701 if (mCheckTimer) {
702 mCheckTimerWaitingForCCEnd = true;
703 KillCheckTimer();
705 mCycleCollectorIsRunning = true;
706 } else if (!strcmp(aTopic, "cycle-collector-end")) {
707 mCycleCollectorIsRunning = false;
708 if (mCheckTimerWaitingForCCEnd) {
709 mCheckTimerWaitingForCCEnd = false;
710 AsyncCheckForGhostWindows();
712 } else {
713 MOZ_ASSERT(false);
716 return NS_OK;
719 void nsWindowMemoryReporter::ObserveDOMWindowDetached(
720 nsGlobalWindowInner* aWindow) {
721 nsWeakPtr weakWindow = do_GetWeakReference(aWindow);
722 if (!weakWindow) {
723 NS_WARNING("Couldn't take weak reference to a window?");
724 return;
727 mDetachedWindows.InsertOrUpdate(weakWindow, TimeStamp());
729 AsyncCheckForGhostWindows();
732 // static
733 void nsWindowMemoryReporter::CheckTimerFired(nsITimer* aTimer, void* aData) {
734 if (sWindowReporter) {
735 MOZ_ASSERT(!sWindowReporter->mCycleCollectorIsRunning);
736 sWindowReporter->CheckForGhostWindows();
740 void nsWindowMemoryReporter::AsyncCheckForGhostWindows() {
741 if (mCheckTimer) {
742 return;
745 if (mCycleCollectorIsRunning) {
746 mCheckTimerWaitingForCCEnd = true;
747 return;
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
754 // and restarted.
755 int32_t timeSinceLastCheck =
756 (TimeStamp::NowLoRes() - mLastCheckForGhostWindows).ToSeconds();
757 int32_t timerDelay =
758 (kTimeBetweenChecks - std::min(timeSinceLastCheck, kTimeBetweenChecks)) *
759 PR_MSEC_PER_SEC;
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
797 * timestamp to now.
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();
806 if (!windowsById) {
807 NS_WARNING("GetWindowsTable returned null");
808 return;
811 mLastCheckForGhostWindows = TimeStamp::NowLoRes();
812 KillCheckTimer();
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
824 // context group.
825 continue;
828 nonDetachedBrowsingContextGroups.Insert(window->GetBrowsingContextGroup());
831 // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
832 // if it's not null.
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);
839 if (!iwindow) {
840 // The window object has been destroyed. Stop tracking its weak ref in
841 // our hashtable.
842 iter.Remove();
843 continue;
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();
856 if (top) {
857 // The window is no longer detached, so we no longer want to track it.
858 iter.Remove();
859 continue;
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();
870 } else {
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.
875 timeStamp = now;
876 } else if ((now - timeStamp).ToSeconds() > ghostTimeout) {
877 // This definitely is a ghost window, so add it to aOutGhostIDs, if
878 // that is not null.
879 mGhostWindowCount++;
880 if (aOutGhostIDs && window) {
881 aOutGhostIDs->Insert(window->WindowID());
887 Telemetry::ScalarSetMaximum(
888 Telemetry::ScalarID::MEMORYREPORTER_MAX_GHOST_WINDOWS, mGhostWindowCount);
891 /* static */
892 int64_t nsWindowMemoryReporter::GhostWindowsDistinguishedAmount() {
893 return sWindowReporter->mGhostWindowCount;
896 void nsWindowMemoryReporter::KillCheckTimer() {
897 if (mCheckTimer) {
898 mCheckTimer->Cancel();
899 mCheckTimer = nullptr;
903 #ifdef DEBUG
904 /* static */
905 void nsWindowMemoryReporter::UnlinkGhostWindows() {
906 if (!sWindowReporter) {
907 return;
910 nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
911 nsGlobalWindowInner::GetWindowsTable();
912 if (!windowsById) {
913 return;
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();
926 if (!windowsById) {
927 continue;
930 RefPtr<nsGlobalWindowInner> window = windowsById->Get(key);
931 if (window) {
932 window->RiskyUnlink();
936 #endif