Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / nsWindowMemoryReporter.cpp
blob5958c61a9ac6ab9b844c281360124548c28974f0
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 "nsGlobalWindowInner.h"
10 #include "nsGlobalWindowOuter.h"
11 #include "mozilla/dom/BrowsingContext.h"
12 #include "mozilla/dom/Document.h"
13 #include "mozilla/ClearOnShutdown.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/Services.h"
16 #include "mozilla/StaticPtr.h"
17 #include "mozilla/Telemetry.h"
18 #include "mozilla/Try.h"
19 #include "mozilla/ResultExtensions.h"
20 #include "nsNetCID.h"
21 #include "nsPrintfCString.h"
22 #include "XPCJSMemoryReporter.h"
23 #include "js/MemoryMetrics.h"
24 #include "nsQueryObject.h"
25 #include "nsServiceManagerUtils.h"
26 #include "nsXULPrototypeCache.h"
28 using namespace mozilla;
29 using namespace mozilla::dom;
31 StaticRefPtr<nsWindowMemoryReporter> sWindowReporter;
33 /**
34 * Don't trigger a ghost window check when a DOM window is detached if we've
35 * run it this recently.
37 const int32_t kTimeBetweenChecks = 45; /* seconds */
39 nsWindowMemoryReporter::nsWindowMemoryReporter()
40 : mLastCheckForGhostWindows(TimeStamp::NowLoRes()),
41 mCycleCollectorIsRunning(false),
42 mCheckTimerWaitingForCCEnd(false),
43 mGhostWindowCount(0) {}
45 nsWindowMemoryReporter::~nsWindowMemoryReporter() { KillCheckTimer(); }
47 NS_IMPL_ISUPPORTS(nsWindowMemoryReporter, nsIMemoryReporter, nsIObserver,
48 nsISupportsWeakReference)
50 static nsresult AddNonJSSizeOfWindowAndItsDescendents(
51 nsGlobalWindowOuter* aWindow, nsTabSizes* aSizes) {
52 // Measure the window.
53 SizeOfState state(moz_malloc_size_of);
54 nsWindowSizes windowSizes(state);
55 aWindow->AddSizeOfIncludingThis(windowSizes);
57 // Measure the inner window, if there is one.
58 nsPIDOMWindowInner* inner = aWindow->GetCurrentInnerWindow();
59 if (inner) {
60 nsGlobalWindowInner::Cast(inner)->AddSizeOfIncludingThis(windowSizes);
63 windowSizes.addToTabSizes(aSizes);
65 BrowsingContext* bc = aWindow->GetBrowsingContext();
66 if (!bc) {
67 return NS_OK;
70 // Measure this window's descendents.
71 for (const auto& frame : bc->Children()) {
72 if (auto* childWin = nsGlobalWindowOuter::Cast(frame->GetDOMWindow())) {
73 MOZ_TRY(AddNonJSSizeOfWindowAndItsDescendents(childWin, aSizes));
76 return NS_OK;
79 static nsresult NonJSSizeOfTab(nsPIDOMWindowOuter* aWindow, size_t* aDomSize,
80 size_t* aStyleSize, size_t* aOtherSize) {
81 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(aWindow);
83 nsTabSizes sizes;
84 nsresult rv = AddNonJSSizeOfWindowAndItsDescendents(window, &sizes);
85 NS_ENSURE_SUCCESS(rv, rv);
87 *aDomSize = sizes.mDom;
88 *aStyleSize = sizes.mStyle;
89 *aOtherSize = sizes.mOther;
90 return NS_OK;
93 /* static */
94 void nsWindowMemoryReporter::Init() {
95 MOZ_ASSERT(!sWindowReporter);
96 sWindowReporter = new nsWindowMemoryReporter();
97 ClearOnShutdown(&sWindowReporter);
98 RegisterStrongMemoryReporter(sWindowReporter);
99 RegisterNonJSSizeOfTab(NonJSSizeOfTab);
101 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
102 if (os) {
103 os->AddObserver(sWindowReporter, "after-minimize-memory-usage",
104 /* weakRef = */ true);
105 os->AddObserver(sWindowReporter, "cycle-collector-begin",
106 /* weakRef = */ true);
107 os->AddObserver(sWindowReporter, "cycle-collector-end",
108 /* weakRef = */ true);
111 RegisterGhostWindowsDistinguishedAmount(GhostWindowsDistinguishedAmount);
114 /* static */
115 nsWindowMemoryReporter* nsWindowMemoryReporter::Get() {
116 return sWindowReporter;
119 static nsCString GetWindowURISpec(nsPIDOMWindowInner* aWindow) {
120 NS_ENSURE_TRUE(aWindow, ""_ns);
122 nsCOMPtr<Document> doc = aWindow->GetExtantDoc();
123 if (doc) {
124 nsCOMPtr<nsIURI> uri;
125 uri = doc->GetDocumentURI();
126 return uri->GetSpecOrDefault();
128 nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrincipal =
129 do_QueryObject(aWindow);
130 NS_ENSURE_TRUE(scriptObjPrincipal, ""_ns);
132 // GetPrincipal() will print a warning if the window does not have an outer
133 // window, so check here for an outer window first. This code is
134 // functionally correct if we leave out the GetOuterWindow() check, but we
135 // end up printing a lot of warnings during debug mochitests.
136 if (!aWindow->GetOuterWindow()) {
137 return ""_ns;
139 nsIPrincipal* principal = scriptObjPrincipal->GetPrincipal();
140 if (!principal) {
141 return ""_ns;
143 nsCString spec;
144 principal->GetAsciiSpec(spec);
145 return spec;
148 static void AppendWindowURI(nsPIDOMWindowInner* aWindow, nsACString& aStr,
149 bool aAnonymize) {
150 nsCString spec = GetWindowURISpec(aWindow);
152 if (spec.IsEmpty()) {
153 // If we're unable to find a URI, we're dealing with a chrome window with
154 // no document in it (or somesuch), so we call this a "system window".
155 aStr += "[system]"_ns;
156 return;
158 if (aAnonymize && !nsGlobalWindowInner::Cast(aWindow)->IsChromeWindow()) {
159 aStr.AppendPrintf("<anonymized-%" PRIu64 ">", aWindow->WindowID());
160 return;
162 // A hack: replace forward slashes with '\\' so they aren't
163 // treated as path separators. Users of the reporters
164 // (such as about:memory) have to undo this change.
165 spec.ReplaceChar('/', '\\');
166 aStr += spec;
169 MOZ_DEFINE_MALLOC_SIZE_OF(WindowsMallocSizeOf)
171 // The key is the window ID.
172 using WindowPaths = nsTHashMap<nsUint64HashKey, nsCString>;
174 static void ReportAmount(const nsCString& aBasePath, const char* aPathTail,
175 size_t aAmount, const nsCString& aDescription,
176 uint32_t aKind, uint32_t aUnits,
177 nsIHandleReportCallback* aHandleReport,
178 nsISupports* aData) {
179 if (aAmount == 0) {
180 return;
183 nsAutoCString path(aBasePath);
184 path += aPathTail;
186 aHandleReport->Callback(""_ns, path, aKind, aUnits, aAmount, aDescription,
187 aData);
190 static void ReportSize(const nsCString& aBasePath, const char* aPathTail,
191 size_t aAmount, const nsCString& aDescription,
192 nsIHandleReportCallback* aHandleReport,
193 nsISupports* aData) {
194 ReportAmount(aBasePath, aPathTail, aAmount, aDescription,
195 nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
196 aHandleReport, aData);
199 static void ReportCount(const nsCString& aBasePath, const char* aPathTail,
200 size_t aAmount, const nsCString& aDescription,
201 nsIHandleReportCallback* aHandleReport,
202 nsISupports* aData) {
203 ReportAmount(aBasePath, aPathTail, aAmount, aDescription,
204 nsIMemoryReporter::KIND_OTHER, nsIMemoryReporter::UNITS_COUNT,
205 aHandleReport, aData);
208 static void ReportDOMSize(const nsCString& aBasePath,
209 nsDOMSizes& aTotalDOMSizes,
210 nsIHandleReportCallback* aHandleReport,
211 nsISupports* aData, nsDOMSizes aDOMSizes) {
212 #define REPORT_DOM_SIZE(_windowPath, _pathTail, _field, _desc) \
213 ReportSize(_windowPath, _pathTail, aDOMSizes._field, \
214 nsLiteralCString(_desc), aHandleReport, aData); \
215 aTotalDOMSizes._field += aDOMSizes._field;
217 REPORT_DOM_SIZE(aBasePath, "/dom/element-nodes", mDOMElementNodesSize,
218 "Memory used by the element nodes in a window's DOM.");
220 REPORT_DOM_SIZE(aBasePath, "/dom/text-nodes", mDOMTextNodesSize,
221 "Memory used by the text nodes in a window's DOM.");
223 REPORT_DOM_SIZE(aBasePath, "/dom/cdata-nodes", mDOMCDATANodesSize,
224 "Memory used by the CDATA nodes in a window's DOM.");
226 REPORT_DOM_SIZE(aBasePath, "/dom/comment-nodes", mDOMCommentNodesSize,
227 "Memory used by the comment nodes in a window's DOM.");
229 REPORT_DOM_SIZE(
230 aBasePath, "/dom/event-targets", mDOMEventTargetsSize,
231 "Memory used by the event targets table in a window's DOM, and "
232 "the objects it points to, which include XHRs.");
234 REPORT_DOM_SIZE(aBasePath, "/dom/performance/user-entries",
235 mDOMPerformanceUserEntries,
236 "Memory used for performance user entries.");
238 REPORT_DOM_SIZE(aBasePath, "/dom/performance/resource-entries",
239 mDOMPerformanceResourceEntries,
240 "Memory used for performance resource entries.");
242 REPORT_DOM_SIZE(aBasePath, "/dom/media-query-lists", mDOMMediaQueryLists,
243 "Memory used by MediaQueryList objects for the window's "
244 "document.");
246 REPORT_DOM_SIZE(aBasePath, "/dom/resize-observers",
247 mDOMResizeObserverControllerSize,
248 "Memory used for resize observers.");
250 REPORT_DOM_SIZE(aBasePath, "/dom/other", mDOMOtherSize,
251 "Memory used by a window's DOM that isn't measured by the "
252 "other 'dom/' numbers.");
253 #undef REPORT_DOM_SIZE
256 static void CollectWindowReports(nsGlobalWindowInner* aWindow,
257 nsWindowSizes* aWindowTotalSizes,
258 nsTHashSet<uint64_t>* aGhostWindowIDs,
259 WindowPaths* aWindowPaths,
260 WindowPaths* aTopWindowPaths,
261 nsIHandleReportCallback* aHandleReport,
262 nsISupports* aData, bool aAnonymize) {
263 nsAutoCString windowPath("explicit/");
265 // Avoid calling aWindow->GetInProcessTop() if there's no outer window. It
266 // will work just fine, but will spew a lot of warnings.
267 nsGlobalWindowOuter* top = nullptr;
268 if (aWindow->GetOuterWindow()) {
269 // Our window should have a null top iff it has a null docshell.
270 MOZ_ASSERT(!!aWindow->GetInProcessTopInternal() ==
271 !!aWindow->GetDocShell());
272 top = aWindow->GetInProcessTopInternal();
275 windowPath += "window-objects/"_ns;
277 if (top) {
278 windowPath += "top("_ns;
279 AppendWindowURI(top->GetCurrentInnerWindow(), windowPath, aAnonymize);
280 windowPath.AppendPrintf(", id=%" PRIu64 ")", top->WindowID());
282 aTopWindowPaths->InsertOrUpdate(aWindow->WindowID(), windowPath);
284 windowPath += aWindow->IsFrozen() ? "/cached/"_ns : "/active/"_ns;
285 } else {
286 if (aGhostWindowIDs->Contains(aWindow->WindowID())) {
287 windowPath += "top(none)/ghost/"_ns;
288 } else {
289 windowPath += "top(none)/detached/"_ns;
293 windowPath += "window("_ns;
294 AppendWindowURI(aWindow, windowPath, aAnonymize);
295 windowPath += ")"_ns;
297 // Use |windowPath|, but replace "explicit/" with "event-counts/".
298 nsCString censusWindowPath(windowPath);
299 censusWindowPath.ReplaceLiteral(0, strlen("explicit"), "event-counts");
301 // Remember the path for later.
302 aWindowPaths->InsertOrUpdate(aWindow->WindowID(), windowPath);
304 // Report the size from windowSizes and add to the appropriate total in
305 // aWindowTotalSizes.
306 #define REPORT_SIZE(_pathTail, _field, _desc) \
307 ReportSize(windowPath, _pathTail, windowSizes._field, \
308 nsLiteralCString(_desc), aHandleReport, aData); \
309 aWindowTotalSizes->_field += windowSizes._field;
311 // Report the size, which is a sum of other sizes, and so doesn't require
312 // updating aWindowTotalSizes.
313 #define REPORT_SUM_SIZE(_pathTail, _amount, _desc) \
314 ReportSize(windowPath, _pathTail, _amount, nsLiteralCString(_desc), \
315 aHandleReport, aData);
317 // Like REPORT_SIZE, but for a count.
318 #define REPORT_COUNT(_pathTail, _field, _desc) \
319 ReportCount(censusWindowPath, _pathTail, windowSizes._field, \
320 nsLiteralCString(_desc), aHandleReport, aData); \
321 aWindowTotalSizes->_field += windowSizes._field;
323 // This SizeOfState contains the SeenPtrs used for all memory reporting of
324 // this window.
325 SizeOfState state(WindowsMallocSizeOf);
326 nsWindowSizes windowSizes(state);
327 aWindow->AddSizeOfIncludingThis(windowSizes);
329 ReportDOMSize(windowPath, aWindowTotalSizes->mDOMSizes, aHandleReport, aData,
330 windowSizes.mDOMSizes);
332 nsCString dataDocumentPath(windowPath);
333 dataDocumentPath += "/data-documents";
334 nsWindowSizes dataDocumentSizes(state);
335 aWindow->CollectDOMSizesForDataDocuments(dataDocumentSizes);
336 ReportDOMSize(dataDocumentPath, aWindowTotalSizes->mDOMSizes, aHandleReport,
337 aData, dataDocumentSizes.mDOMSizes);
339 REPORT_SIZE("/layout/style-sheets", mLayoutStyleSheetsSize,
340 "Memory used by document style sheets within a window.");
342 REPORT_SIZE("/layout/svg-mapped-declarations", mLayoutSvgMappedDeclarations,
343 "Memory used by mapped declarations of SVG elements");
345 REPORT_SIZE("/layout/shadow-dom/style-sheets",
346 mLayoutShadowDomStyleSheetsSize,
347 "Memory used by Shadow DOM style sheets within a window.");
349 // TODO(emilio): We might want to split this up between invalidation map /
350 // element-and-pseudos / revalidation too just like the style set.
351 REPORT_SIZE("/layout/shadow-dom/author-styles", mLayoutShadowDomAuthorStyles,
352 "Memory used by Shadow DOM computed rule data within a window.");
354 REPORT_SIZE("/layout/pres-shell", mLayoutPresShellSize,
355 "Memory used by layout's PresShell, along with any structures "
356 "allocated in its arena and not measured elsewhere, "
357 "within a window.");
359 REPORT_SIZE("/layout/display-list", mLayoutRetainedDisplayListSize,
360 "Memory used by the retained display list data, "
361 "along with any structures allocated in its arena and not "
362 "measured elsewhere, within a window.");
364 REPORT_SIZE("/layout/style-sets/stylist/rule-tree",
365 mLayoutStyleSetsStylistRuleTree,
366 "Memory used by rule trees within style sets within a window.");
368 REPORT_SIZE("/layout/style-sets/stylist/element-and-pseudos-maps",
369 mLayoutStyleSetsStylistElementAndPseudosMaps,
370 "Memory used by element and pseudos maps within style "
371 "sets within a window.");
373 REPORT_SIZE("/layout/style-sets/stylist/invalidation-map",
374 mLayoutStyleSetsStylistInvalidationMap,
375 "Memory used by invalidation maps within style sets "
376 "within a window.");
378 REPORT_SIZE("/layout/style-sets/stylist/revalidation-selectors",
379 mLayoutStyleSetsStylistRevalidationSelectors,
380 "Memory used by selectors for cache revalidation within "
381 "style sets within a window.");
383 REPORT_SIZE("/layout/style-sets/stylist/other", mLayoutStyleSetsStylistOther,
384 "Memory used by other Stylist data within style sets "
385 "within a window.");
387 REPORT_SIZE("/layout/style-sets/other", mLayoutStyleSetsOther,
388 "Memory used by other parts of style sets within a window.");
390 REPORT_SIZE("/layout/element-data-objects", mLayoutElementDataObjects,
391 "Memory used for ElementData objects, but not the things "
392 "hanging off them.");
394 REPORT_SIZE("/layout/text-runs", mLayoutTextRunsSize,
395 "Memory used for text-runs (glyph layout) in the PresShell's "
396 "frame tree, within a window.");
398 REPORT_SIZE("/layout/pres-contexts", mLayoutPresContextSize,
399 "Memory used for the PresContext in the PresShell's frame "
400 "within a window.");
402 REPORT_SIZE("/layout/frame-properties", mLayoutFramePropertiesSize,
403 "Memory used for frame properties attached to frames "
404 "within a window.");
406 REPORT_SIZE("/layout/computed-values/dom", mLayoutComputedValuesDom,
407 "Memory used by ComputedValues objects accessible from DOM "
408 "elements.");
410 REPORT_SIZE("/layout/computed-values/non-dom", mLayoutComputedValuesNonDom,
411 "Memory used by ComputedValues objects not accessible from DOM "
412 "elements.");
414 REPORT_SIZE("/layout/computed-values/visited", mLayoutComputedValuesVisited,
415 "Memory used by ComputedValues objects used for visited styles.");
417 REPORT_SIZE("/property-tables", mPropertyTablesSize,
418 "Memory used for the property tables within a window.");
420 REPORT_SIZE("/bindings", mBindingsSize,
421 "Memory used by bindings within a window.");
423 REPORT_COUNT("/dom/event-targets", mDOMEventTargetsCount,
424 "Number of non-node event targets in the event targets table "
425 "in a window's DOM, such as XHRs.");
427 REPORT_COUNT("/dom/event-listeners", mDOMEventListenersCount,
428 "Number of event listeners in a window, including event "
429 "listeners on nodes and other event targets.");
431 // There are many different kinds of frames, but it is very likely
432 // that only a few matter. Implement a cutoff so we don't bloat
433 // about:memory with many uninteresting entries.
434 const size_t ARENA_SUNDRIES_THRESHOLD =
435 js::MemoryReportingSundriesThreshold();
437 size_t presArenaSundriesSize = 0;
438 #define ARENA_OBJECT(name_, sundries_size_, prefix_) \
440 size_t size = windowSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_); \
441 if (size < ARENA_SUNDRIES_THRESHOLD) { \
442 sundries_size_ += size; \
443 } else { \
444 REPORT_SUM_SIZE(prefix_ #name_, size, \
445 "Memory used by objects of type " #name_ \
446 " within a window."); \
448 aWindowTotalSizes->mArenaSizes.NS_ARENA_SIZES_FIELD(name_) += size; \
450 #define PRES_ARENA_OBJECT(name_) \
451 ARENA_OBJECT(name_, presArenaSundriesSize, "/layout/pres-arena/")
452 #include "nsPresArenaObjectList.h"
453 #undef PRES_ARENA_OBJECT
455 if (presArenaSundriesSize > 0) {
456 REPORT_SUM_SIZE(
457 "/layout/pres-arena/sundries", presArenaSundriesSize,
458 "The sum of all memory used by objects in the arena which were too "
459 "small to be shown individually.");
462 size_t displayListArenaSundriesSize = 0;
463 #define DISPLAY_LIST_ARENA_OBJECT(name_) \
464 ARENA_OBJECT(name_, displayListArenaSundriesSize, \
465 "/layout/display-list-arena/")
466 #include "nsDisplayListArenaTypes.h"
467 #undef DISPLAY_LIST_ARENA_OBJECT
469 if (displayListArenaSundriesSize > 0) {
470 REPORT_SUM_SIZE(
471 "/layout/display-list-arena/sundries", displayListArenaSundriesSize,
472 "The sum of all memory used by objects in the DL arena which were too "
473 "small to be shown individually.");
476 #undef ARENA_OBJECT
478 // There are many different kinds of style structs, but it is likely that
479 // only a few matter. Implement a cutoff so we don't bloat about:memory with
480 // many uninteresting entries.
481 const size_t STYLE_SUNDRIES_THRESHOLD =
482 js::MemoryReportingSundriesThreshold();
484 size_t styleSundriesSize = 0;
485 #define STYLE_STRUCT(name_) \
487 size_t size = windowSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_); \
488 if (size < STYLE_SUNDRIES_THRESHOLD) { \
489 styleSundriesSize += size; \
490 } else { \
491 REPORT_SUM_SIZE("/layout/style-structs/" #name_, size, \
492 "Memory used by the " #name_ \
493 " style structs within a window."); \
495 aWindowTotalSizes->mStyleSizes.NS_STYLE_SIZES_FIELD(name_) += size; \
497 #include "nsStyleStructList.h"
498 #undef STYLE_STRUCT
500 if (styleSundriesSize > 0) {
501 REPORT_SUM_SIZE(
502 "/layout/style-structs/sundries", styleSundriesSize,
503 "The sum of all memory used by style structs which were too "
504 "small to be shown individually.");
507 #undef REPORT_SIZE
508 #undef REPORT_SUM_SIZE
509 #undef REPORT_COUNT
512 using WindowArray = nsTArray<RefPtr<nsGlobalWindowInner>>;
514 NS_IMETHODIMP
515 nsWindowMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
516 nsISupports* aData, bool aAnonymize) {
517 nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
518 nsGlobalWindowInner::GetWindowsTable();
519 NS_ENSURE_TRUE(windowsById, NS_OK);
521 // Hold on to every window in memory so that window objects can't be
522 // destroyed while we're calling the memory reporter callback.
523 const auto windows = ToTArray<WindowArray>(windowsById->Values());
525 // Get the IDs of all the "ghost" windows, and call
526 // aHandleReport->Callback() for each one.
527 nsTHashSet<uint64_t> ghostWindows;
528 CheckForGhostWindows(&ghostWindows);
530 for (const auto& key : ghostWindows) {
531 nsGlobalWindowInner* window = windowsById->Get(key);
532 if (!window) {
533 NS_WARNING("Could not look up window?");
534 continue;
537 nsAutoCString path;
538 path.AppendLiteral("ghost-windows/");
539 AppendWindowURI(window, path, aAnonymize);
541 aHandleReport->Callback(
542 /* process = */ ""_ns, path, nsIMemoryReporter::KIND_OTHER,
543 nsIMemoryReporter::UNITS_COUNT,
544 /* amount = */ 1,
545 /* description = */ "A ghost window."_ns, aData);
548 // clang-format off
549 MOZ_COLLECT_REPORT(
550 "ghost-windows", KIND_OTHER, UNITS_COUNT, ghostWindows.Count(),
551 "The number of ghost windows present (the number of nodes underneath "
552 "explicit/window-objects/top(none)/ghost, modulo race conditions). A ghost "
553 "window is not shown in any tab, is not in a browsing context group with any "
554 "non-detached windows, and has met these criteria for at least "
555 "memory.ghost_window_timeout_seconds, or has survived a round of "
556 "about:memory's minimize memory usage button.\n\n"
557 "Ghost windows can happen legitimately, but they are often indicative of "
558 "leaks in the browser or add-ons.");
559 // clang-format on
561 WindowPaths windowPaths;
562 WindowPaths topWindowPaths;
564 // Collect window memory usage.
565 SizeOfState fakeState(nullptr); // this won't be used
566 nsWindowSizes windowTotalSizes(fakeState);
567 for (uint32_t i = 0; i < windows.Length(); i++) {
568 CollectWindowReports(windows[i], &windowTotalSizes, &ghostWindows,
569 &windowPaths, &topWindowPaths, aHandleReport, aData,
570 aAnonymize);
573 // Report JS memory usage. We do this from here because the JS memory
574 // reporter needs to be passed |windowPaths|.
575 xpc::JSReporter::CollectReports(&windowPaths, &topWindowPaths, aHandleReport,
576 aData, aAnonymize);
578 nsXULPrototypeCache::CollectMemoryReports(aHandleReport, aData);
580 #define REPORT(_path, _amount, _desc) \
581 aHandleReport->Callback(""_ns, nsLiteralCString(_path), KIND_OTHER, \
582 UNITS_BYTES, _amount, nsLiteralCString(_desc), \
583 aData);
585 REPORT("window-objects/dom/element-nodes",
586 windowTotalSizes.mDOMSizes.mDOMElementNodesSize,
587 "This is the sum of all windows' 'dom/element-nodes' numbers.");
589 REPORT("window-objects/dom/text-nodes",
590 windowTotalSizes.mDOMSizes.mDOMTextNodesSize,
591 "This is the sum of all windows' 'dom/text-nodes' numbers.");
593 REPORT("window-objects/dom/cdata-nodes",
594 windowTotalSizes.mDOMSizes.mDOMCDATANodesSize,
595 "This is the sum of all windows' 'dom/cdata-nodes' numbers.");
597 REPORT("window-objects/dom/comment-nodes",
598 windowTotalSizes.mDOMSizes.mDOMCommentNodesSize,
599 "This is the sum of all windows' 'dom/comment-nodes' numbers.");
601 REPORT("window-objects/dom/event-targets",
602 windowTotalSizes.mDOMSizes.mDOMEventTargetsSize,
603 "This is the sum of all windows' 'dom/event-targets' numbers.");
605 REPORT("window-objects/dom/performance",
606 windowTotalSizes.mDOMSizes.mDOMPerformanceUserEntries +
607 windowTotalSizes.mDOMSizes.mDOMPerformanceResourceEntries,
608 "This is the sum of all windows' 'dom/performance/' numbers.");
610 REPORT("window-objects/dom/other", windowTotalSizes.mDOMSizes.mDOMOtherSize,
611 "This is the sum of all windows' 'dom/other' numbers.");
613 REPORT("window-objects/layout/style-sheets",
614 windowTotalSizes.mLayoutStyleSheetsSize,
615 "This is the sum of all windows' 'layout/style-sheets' numbers.");
617 REPORT("window-objects/layout/pres-shell",
618 windowTotalSizes.mLayoutPresShellSize,
619 "This is the sum of all windows' 'layout/arenas' numbers.");
621 REPORT("window-objects/layout/style-sets",
622 windowTotalSizes.mLayoutStyleSetsStylistRuleTree +
623 windowTotalSizes.mLayoutStyleSetsStylistElementAndPseudosMaps +
624 windowTotalSizes.mLayoutStyleSetsStylistInvalidationMap +
625 windowTotalSizes.mLayoutStyleSetsStylistRevalidationSelectors +
626 windowTotalSizes.mLayoutStyleSetsStylistOther +
627 windowTotalSizes.mLayoutStyleSetsOther,
628 "This is the sum of all windows' 'layout/style-sets/' numbers.");
630 REPORT("window-objects/layout/element-data-objects",
631 windowTotalSizes.mLayoutElementDataObjects,
632 "This is the sum of all windows' 'layout/element-data-objects' "
633 "numbers.");
635 REPORT("window-objects/layout/text-runs",
636 windowTotalSizes.mLayoutTextRunsSize,
637 "This is the sum of all windows' 'layout/text-runs' numbers.");
639 REPORT("window-objects/layout/pres-contexts",
640 windowTotalSizes.mLayoutPresContextSize,
641 "This is the sum of all windows' 'layout/pres-contexts' numbers.");
643 REPORT("window-objects/layout/frame-properties",
644 windowTotalSizes.mLayoutFramePropertiesSize,
645 "This is the sum of all windows' 'layout/frame-properties' numbers.");
647 REPORT("window-objects/layout/computed-values",
648 windowTotalSizes.mLayoutComputedValuesDom +
649 windowTotalSizes.mLayoutComputedValuesNonDom +
650 windowTotalSizes.mLayoutComputedValuesVisited,
651 "This is the sum of all windows' 'layout/computed-values/' numbers.");
653 REPORT("window-objects/property-tables", windowTotalSizes.mPropertyTablesSize,
654 "This is the sum of all windows' 'property-tables' numbers.");
656 size_t presArenaTotal = 0;
657 #define PRES_ARENA_OBJECT(name_) \
658 presArenaTotal += windowTotalSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_);
659 #include "nsPresArenaObjectList.h"
660 #undef PRES_ARENA_OBJECT
662 REPORT("window-objects/layout/pres-arena", presArenaTotal,
663 "Memory used for the pres arena within windows. "
664 "This is the sum of all windows' 'layout/pres-arena/' numbers.");
666 size_t displayListArenaTotal = 0;
667 #define DISPLAY_LIST_ARENA_OBJECT(name_) \
668 displayListArenaTotal += \
669 windowTotalSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_);
670 #include "nsDisplayListArenaTypes.h"
671 #undef DISPLAY_LIST_ARENA_OBJECT
673 REPORT("window-objects/layout/display-list-arena", displayListArenaTotal,
674 "Memory used for the display list arena within windows. This is the "
675 "sum of all windows' 'layout/display-list-arena/' numbers.");
677 size_t styleTotal = 0;
678 #define STYLE_STRUCT(name_) \
679 styleTotal += windowTotalSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_);
680 #include "nsStyleStructList.h"
681 #undef STYLE_STRUCT
683 REPORT("window-objects/layout/style-structs", styleTotal,
684 "Memory used for style structs within windows. This is the sum of "
685 "all windows' 'layout/style-structs/' numbers.");
687 #undef REPORT
689 return NS_OK;
692 uint32_t nsWindowMemoryReporter::GetGhostTimeout() {
693 return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
696 NS_IMETHODIMP
697 nsWindowMemoryReporter::Observe(nsISupports* aSubject, const char* aTopic,
698 const char16_t* aData) {
699 if (!strcmp(aTopic, "after-minimize-memory-usage")) {
700 ObserveAfterMinimizeMemoryUsage();
701 } else if (!strcmp(aTopic, "cycle-collector-begin")) {
702 if (mCheckTimer) {
703 mCheckTimerWaitingForCCEnd = true;
704 KillCheckTimer();
706 mCycleCollectorIsRunning = true;
707 } else if (!strcmp(aTopic, "cycle-collector-end")) {
708 mCycleCollectorIsRunning = false;
709 if (mCheckTimerWaitingForCCEnd) {
710 mCheckTimerWaitingForCCEnd = false;
711 AsyncCheckForGhostWindows();
713 } else {
714 MOZ_ASSERT(false);
717 return NS_OK;
720 void nsWindowMemoryReporter::ObserveDOMWindowDetached(
721 nsGlobalWindowInner* aWindow) {
722 nsWeakPtr weakWindow = do_GetWeakReference(aWindow);
723 if (!weakWindow) {
724 NS_WARNING("Couldn't take weak reference to a window?");
725 return;
728 mDetachedWindows.InsertOrUpdate(weakWindow, TimeStamp());
730 AsyncCheckForGhostWindows();
733 // static
734 void nsWindowMemoryReporter::CheckTimerFired(nsITimer* aTimer, void* aData) {
735 if (sWindowReporter) {
736 MOZ_ASSERT(!sWindowReporter->mCycleCollectorIsRunning);
737 sWindowReporter->CheckForGhostWindows();
741 void nsWindowMemoryReporter::AsyncCheckForGhostWindows() {
742 if (mCheckTimer) {
743 return;
746 if (mCycleCollectorIsRunning) {
747 mCheckTimerWaitingForCCEnd = true;
748 return;
751 // If more than kTimeBetweenChecks seconds have elapsed since the last check,
752 // timerDelay is 0. Otherwise, it is kTimeBetweenChecks, reduced by the time
753 // since the last check. Reducing the delay by the time since the last check
754 // prevents the timer from being completely starved if it is repeatedly killed
755 // and restarted.
756 int32_t timeSinceLastCheck =
757 (TimeStamp::NowLoRes() - mLastCheckForGhostWindows).ToSeconds();
758 int32_t timerDelay =
759 (kTimeBetweenChecks - std::min(timeSinceLastCheck, kTimeBetweenChecks)) *
760 PR_MSEC_PER_SEC;
762 NS_NewTimerWithFuncCallback(
763 getter_AddRefs(mCheckTimer), CheckTimerFired, nullptr, timerDelay,
764 nsITimer::TYPE_ONE_SHOT,
765 "nsWindowMemoryReporter::AsyncCheckForGhostWindows_timer");
768 void nsWindowMemoryReporter::ObserveAfterMinimizeMemoryUsage() {
769 // Someone claims they've done enough GC/CCs so that all eligible windows
770 // have been free'd. So we deem that any windows which satisfy ghost
771 // criteria (1) and (2) now satisfy criterion (3) as well.
773 // To effect this change, we'll backdate some of our timestamps.
775 TimeStamp minTimeStamp =
776 TimeStamp::Now() - TimeDuration::FromSeconds(GetGhostTimeout());
778 for (auto iter = mDetachedWindows.Iter(); !iter.Done(); iter.Next()) {
779 TimeStamp& timeStamp = iter.Data();
780 if (!timeStamp.IsNull() && timeStamp > minTimeStamp) {
781 timeStamp = minTimeStamp;
787 * Iterate over mDetachedWindows and update it to reflect the current state of
788 * the world. In particular:
790 * - Remove weak refs to windows which no longer exist.
792 * - Remove references to windows which are no longer detached.
794 * - Reset the timestamp on detached windows which share a domain with a
795 * non-detached window (they no longer meet ghost criterion (2)).
797 * - If a window now meets ghost criterion (2) but didn't before, set its
798 * timestamp to now.
800 * Additionally, if aOutGhostIDs is not null, fill it with the window IDs of
801 * all ghost windows we found.
803 void nsWindowMemoryReporter::CheckForGhostWindows(
804 nsTHashSet<uint64_t>* aOutGhostIDs /* = nullptr */) {
805 nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
806 nsGlobalWindowInner::GetWindowsTable();
807 if (!windowsById) {
808 NS_WARNING("GetWindowsTable returned null");
809 return;
812 mLastCheckForGhostWindows = TimeStamp::NowLoRes();
813 KillCheckTimer();
815 nsTHashSet<BrowsingContextGroup*> nonDetachedBrowsingContextGroups;
817 // Populate nonDetachedBrowsingContextGroups.
818 for (const auto& entry : *windowsById) {
819 // Null outer window implies null top, but calling GetInProcessTop() when
820 // there's no outer window causes us to spew debug warnings.
821 nsGlobalWindowInner* window = entry.GetWeak();
822 if (!window->GetOuterWindow() || !window->GetInProcessTopInternal() ||
823 !window->GetBrowsingContextGroup()) {
824 // This window is detached, so we don't care about its browsing
825 // context group.
826 continue;
829 nonDetachedBrowsingContextGroups.Insert(window->GetBrowsingContextGroup());
832 // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
833 // if it's not null.
834 uint32_t ghostTimeout = GetGhostTimeout();
835 TimeStamp now = mLastCheckForGhostWindows;
836 mGhostWindowCount = 0;
837 for (auto iter = mDetachedWindows.Iter(); !iter.Done(); iter.Next()) {
838 nsWeakPtr weakKey = do_QueryInterface(iter.Key());
839 nsCOMPtr<mozIDOMWindow> iwindow = do_QueryReferent(weakKey);
840 if (!iwindow) {
841 // The window object has been destroyed. Stop tracking its weak ref in
842 // our hashtable.
843 iter.Remove();
844 continue;
847 nsPIDOMWindowInner* window = nsPIDOMWindowInner::From(iwindow);
849 // Avoid calling GetInProcessTop() if we have no outer window. Nothing
850 // will break if we do, but it will spew debug output, which can cause our
851 // test logs to overflow.
852 nsCOMPtr<nsPIDOMWindowOuter> top;
853 if (window->GetOuterWindow()) {
854 top = window->GetOuterWindow()->GetInProcessTop();
857 if (top) {
858 // The window is no longer detached, so we no longer want to track it.
859 iter.Remove();
860 continue;
863 TimeStamp& timeStamp = iter.Data();
864 BrowsingContextGroup* browsingContextGroup =
865 window->GetBrowsingContextGroup();
866 if (browsingContextGroup &&
867 nonDetachedBrowsingContextGroups.Contains(browsingContextGroup)) {
868 // This window is in the same browsing context group as a non-detached
869 // window, so reset its clock.
870 timeStamp = TimeStamp();
871 } else {
872 // This window is not in the same browsing context group as a non-detached
873 // window, so it meets ghost criterion (2).
874 if (timeStamp.IsNull()) {
875 // This may become a ghost window later; start its clock.
876 timeStamp = now;
877 } else if ((now - timeStamp).ToSeconds() > ghostTimeout) {
878 // This definitely is a ghost window, so add it to aOutGhostIDs, if
879 // that is not null.
880 mGhostWindowCount++;
881 if (aOutGhostIDs && window) {
882 aOutGhostIDs->Insert(window->WindowID());
888 Telemetry::ScalarSetMaximum(
889 Telemetry::ScalarID::MEMORYREPORTER_MAX_GHOST_WINDOWS, mGhostWindowCount);
892 /* static */
893 int64_t nsWindowMemoryReporter::GhostWindowsDistinguishedAmount() {
894 return sWindowReporter->mGhostWindowCount;
897 void nsWindowMemoryReporter::KillCheckTimer() {
898 if (mCheckTimer) {
899 mCheckTimer->Cancel();
900 mCheckTimer = nullptr;
904 #ifdef DEBUG
905 /* static */
906 void nsWindowMemoryReporter::UnlinkGhostWindows() {
907 if (!sWindowReporter) {
908 return;
911 nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
912 nsGlobalWindowInner::GetWindowsTable();
913 if (!windowsById) {
914 return;
917 // Hold on to every window in memory so that window objects can't be
918 // destroyed while we're calling the UnlinkGhostWindows callback.
919 const auto windows = ToTArray<WindowArray>(windowsById->Values());
921 // Get the IDs of all the "ghost" windows, and unlink them all.
922 nsTHashSet<uint64_t> ghostWindows;
923 sWindowReporter->CheckForGhostWindows(&ghostWindows);
924 for (const auto& key : ghostWindows) {
925 nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
926 nsGlobalWindowInner::GetWindowsTable();
927 if (!windowsById) {
928 continue;
931 RefPtr<nsGlobalWindowInner> window = windowsById->Get(key);
932 if (window) {
933 window->RiskyUnlink();
937 #endif