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"
25 # include "nsXULPrototypeCache.h"
28 using namespace mozilla
;
29 using namespace mozilla::dom
;
31 StaticRefPtr
<nsWindowMemoryReporter
> sWindowReporter
;
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 nsGlobalWindowInner
* inner
= aWindow
->GetCurrentInnerWindowInternal();
60 inner
->AddSizeOfIncludingThis(windowSizes
);
63 windowSizes
.addToTabSizes(aSizes
);
65 BrowsingContext
* bc
= aWindow
->GetBrowsingContext();
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
));
79 static nsresult
NonJSSizeOfTab(nsPIDOMWindowOuter
* aWindow
, size_t* aDomSize
,
80 size_t* aStyleSize
, size_t* aOtherSize
) {
81 nsGlobalWindowOuter
* window
= nsGlobalWindowOuter::Cast(aWindow
);
84 nsresult rv
= AddNonJSSizeOfWindowAndItsDescendents(window
, &sizes
);
85 NS_ENSURE_SUCCESS(rv
, rv
);
87 *aDomSize
= sizes
.mDom
;
88 *aStyleSize
= sizes
.mStyle
;
89 *aOtherSize
= sizes
.mOther
;
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();
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
);
115 nsWindowMemoryReporter
* nsWindowMemoryReporter::Get() {
116 return sWindowReporter
;
119 static nsCString
GetWindowURISpec(nsGlobalWindowInner
* aWindow
) {
120 NS_ENSURE_TRUE(aWindow
, ""_ns
);
122 nsCOMPtr
<Document
> doc
= aWindow
->GetExtantDoc();
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()) {
139 nsIPrincipal
* principal
= scriptObjPrincipal
->GetPrincipal();
144 principal
->GetAsciiSpec(spec
);
148 static void AppendWindowURI(nsGlobalWindowInner
* aWindow
, nsACString
& aStr
,
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
;
158 if (aAnonymize
&& !aWindow
->IsChromeWindow()) {
159 aStr
.AppendPrintf("<anonymized-%" PRIu64
">", aWindow
->WindowID());
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('/', '\\');
169 MOZ_DEFINE_MALLOC_SIZE_OF(WindowsMallocSizeOf
)
171 // The key is the window ID.
172 typedef nsDataHashtable
<nsUint64HashKey
, nsCString
> WindowPaths
;
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
) {
183 nsAutoCString
path(aBasePath
);
186 aHandleReport
->Callback(EmptyCString(), path
, aKind
, aUnits
, aAmount
,
187 aDescription
, 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 CollectWindowReports(nsGlobalWindowInner
* aWindow
,
209 nsWindowSizes
* aWindowTotalSizes
,
210 nsTHashtable
<nsUint64HashKey
>* aGhostWindowIDs
,
211 WindowPaths
* aWindowPaths
,
212 WindowPaths
* aTopWindowPaths
,
213 nsIHandleReportCallback
* aHandleReport
,
214 nsISupports
* aData
, bool aAnonymize
) {
215 nsAutoCString
windowPath("explicit/");
217 // Avoid calling aWindow->GetInProcessTop() if there's no outer window. It
218 // will work just fine, but will spew a lot of warnings.
219 nsGlobalWindowOuter
* top
= nullptr;
220 if (aWindow
->GetOuterWindow()) {
221 // Our window should have a null top iff it has a null docshell.
222 MOZ_ASSERT(!!aWindow
->GetInProcessTopInternal() ==
223 !!aWindow
->GetDocShell());
224 top
= aWindow
->GetInProcessTopInternal();
227 windowPath
+= "window-objects/"_ns
;
230 windowPath
+= "top("_ns
;
231 AppendWindowURI(top
->GetCurrentInnerWindowInternal(), windowPath
,
233 windowPath
.AppendPrintf(", id=%" PRIu64
")", top
->WindowID());
235 aTopWindowPaths
->Put(aWindow
->WindowID(), windowPath
);
237 windowPath
+= aWindow
->IsFrozen() ? "/cached/"_ns
: "/active/"_ns
;
239 if (aGhostWindowIDs
->Contains(aWindow
->WindowID())) {
240 windowPath
+= "top(none)/ghost/"_ns
;
242 windowPath
+= "top(none)/detached/"_ns
;
246 windowPath
+= "window("_ns
;
247 AppendWindowURI(aWindow
, windowPath
, aAnonymize
);
248 windowPath
+= ")"_ns
;
250 // Use |windowPath|, but replace "explicit/" with "event-counts/".
251 nsCString
censusWindowPath(windowPath
);
252 censusWindowPath
.ReplaceLiteral(0, strlen("explicit"), "event-counts");
254 // Remember the path for later.
255 aWindowPaths
->Put(aWindow
->WindowID(), windowPath
);
257 // Report the size from windowSizes and add to the appropriate total in
258 // aWindowTotalSizes.
259 #define REPORT_SIZE(_pathTail, _field, _desc) \
260 ReportSize(windowPath, _pathTail, windowSizes._field, \
261 nsLiteralCString(_desc), aHandleReport, aData); \
262 aWindowTotalSizes->_field += windowSizes._field;
264 // Report the size, which is a sum of other sizes, and so doesn't require
265 // updating aWindowTotalSizes.
266 #define REPORT_SUM_SIZE(_pathTail, _amount, _desc) \
267 ReportSize(windowPath, _pathTail, _amount, nsLiteralCString(_desc), \
268 aHandleReport, aData);
270 // Like REPORT_SIZE, but for a count.
271 #define REPORT_COUNT(_pathTail, _field, _desc) \
272 ReportCount(censusWindowPath, _pathTail, windowSizes._field, \
273 nsLiteralCString(_desc), aHandleReport, aData); \
274 aWindowTotalSizes->_field += windowSizes._field;
276 // This SizeOfState contains the SeenPtrs used for all memory reporting of
278 SizeOfState
state(WindowsMallocSizeOf
);
279 nsWindowSizes
windowSizes(state
);
280 aWindow
->AddSizeOfIncludingThis(windowSizes
);
282 REPORT_SIZE("/dom/element-nodes", mDOMElementNodesSize
,
283 "Memory used by the element nodes in a window's DOM.");
285 REPORT_SIZE("/dom/text-nodes", mDOMTextNodesSize
,
286 "Memory used by the text nodes in a window's DOM.");
288 REPORT_SIZE("/dom/cdata-nodes", mDOMCDATANodesSize
,
289 "Memory used by the CDATA nodes in a window's DOM.");
291 REPORT_SIZE("/dom/comment-nodes", mDOMCommentNodesSize
,
292 "Memory used by the comment nodes in a window's DOM.");
294 REPORT_SIZE("/dom/event-targets", mDOMEventTargetsSize
,
295 "Memory used by the event targets table in a window's DOM, and "
296 "the objects it points to, which include XHRs.");
298 REPORT_SIZE("/dom/performance/user-entries", mDOMPerformanceUserEntries
,
299 "Memory used for performance user entries.");
301 REPORT_SIZE("/dom/performance/resource-entries",
302 mDOMPerformanceResourceEntries
,
303 "Memory used for performance resource entries.");
305 REPORT_SIZE("/dom/media-query-lists", mDOMMediaQueryLists
,
306 "Memory used by MediaQueryList objects for the window's "
309 REPORT_SIZE("/dom/resize-observers", mDOMResizeObserverControllerSize
,
310 "Memory used for resize observers.");
312 REPORT_SIZE("/dom/other", mDOMOtherSize
,
313 "Memory used by a window's DOM that isn't measured by the "
314 "other 'dom/' numbers.");
316 REPORT_SIZE("/layout/style-sheets", mLayoutStyleSheetsSize
,
317 "Memory used by document style sheets within a window.");
319 REPORT_SIZE("/layout/svg-mapped-declarations", mLayoutSvgMappedDeclarations
,
320 "Memory used by mapped declarations of SVG elements");
322 REPORT_SIZE("/layout/shadow-dom/style-sheets",
323 mLayoutShadowDomStyleSheetsSize
,
324 "Memory used by Shadow DOM style sheets within a window.");
326 // TODO(emilio): We might want to split this up between invalidation map /
327 // element-and-pseudos / revalidation too just like the style set.
328 REPORT_SIZE("/layout/shadow-dom/author-styles", mLayoutShadowDomAuthorStyles
,
329 "Memory used by Shadow DOM computed rule data within a window.");
331 REPORT_SIZE("/layout/pres-shell", mLayoutPresShellSize
,
332 "Memory used by layout's PresShell, along with any structures "
333 "allocated in its arena and not measured elsewhere, "
336 REPORT_SIZE("/layout/display-list", mLayoutRetainedDisplayListSize
,
337 "Memory used by the retained display list data, "
338 "along with any structures allocated in its arena and not "
339 "measured elsewhere, within a window.");
341 REPORT_SIZE("/layout/style-sets/stylist/rule-tree",
342 mLayoutStyleSetsStylistRuleTree
,
343 "Memory used by rule trees within style sets within a window.");
345 REPORT_SIZE("/layout/style-sets/stylist/element-and-pseudos-maps",
346 mLayoutStyleSetsStylistElementAndPseudosMaps
,
347 "Memory used by element and pseudos maps within style "
348 "sets within a window.");
350 REPORT_SIZE("/layout/style-sets/stylist/invalidation-map",
351 mLayoutStyleSetsStylistInvalidationMap
,
352 "Memory used by invalidation maps within style sets "
355 REPORT_SIZE("/layout/style-sets/stylist/revalidation-selectors",
356 mLayoutStyleSetsStylistRevalidationSelectors
,
357 "Memory used by selectors for cache revalidation within "
358 "style sets within a window.");
360 REPORT_SIZE("/layout/style-sets/stylist/other", mLayoutStyleSetsStylistOther
,
361 "Memory used by other Stylist data within style sets "
364 REPORT_SIZE("/layout/style-sets/other", mLayoutStyleSetsOther
,
365 "Memory used by other parts of style sets within a window.");
367 REPORT_SIZE("/layout/element-data-objects", mLayoutElementDataObjects
,
368 "Memory used for ElementData objects, but not the things "
369 "hanging off them.");
371 REPORT_SIZE("/layout/text-runs", mLayoutTextRunsSize
,
372 "Memory used for text-runs (glyph layout) in the PresShell's "
373 "frame tree, within a window.");
375 REPORT_SIZE("/layout/pres-contexts", mLayoutPresContextSize
,
376 "Memory used for the PresContext in the PresShell's frame "
379 REPORT_SIZE("/layout/frame-properties", mLayoutFramePropertiesSize
,
380 "Memory used for frame properties attached to frames "
383 REPORT_SIZE("/layout/computed-values/dom", mLayoutComputedValuesDom
,
384 "Memory used by ComputedValues objects accessible from DOM "
387 REPORT_SIZE("/layout/computed-values/non-dom", mLayoutComputedValuesNonDom
,
388 "Memory used by ComputedValues objects not accessible from DOM "
391 REPORT_SIZE("/layout/computed-values/visited", mLayoutComputedValuesVisited
,
392 "Memory used by ComputedValues objects used for visited styles.");
394 REPORT_SIZE("/property-tables", mPropertyTablesSize
,
395 "Memory used for the property tables within a window.");
397 REPORT_SIZE("/bindings", mBindingsSize
,
398 "Memory used by bindings within a window.");
400 REPORT_COUNT("/dom/event-targets", mDOMEventTargetsCount
,
401 "Number of non-node event targets in the event targets table "
402 "in a window's DOM, such as XHRs.");
404 REPORT_COUNT("/dom/event-listeners", mDOMEventListenersCount
,
405 "Number of event listeners in a window, including event "
406 "listeners on nodes and other event targets.");
408 // There are many different kinds of frames, but it is very likely
409 // that only a few matter. Implement a cutoff so we don't bloat
410 // about:memory with many uninteresting entries.
411 const size_t ARENA_SUNDRIES_THRESHOLD
=
412 js::MemoryReportingSundriesThreshold();
414 size_t presArenaSundriesSize
= 0;
415 #define ARENA_OBJECT(name_, sundries_size_, prefix_) \
417 size_t size = windowSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_); \
418 if (size < ARENA_SUNDRIES_THRESHOLD) { \
419 sundries_size_ += size; \
421 REPORT_SUM_SIZE(prefix_ #name_, size, \
422 "Memory used by objects of type " #name_ \
423 " within a window."); \
425 aWindowTotalSizes->mArenaSizes.NS_ARENA_SIZES_FIELD(name_) += size; \
427 #define PRES_ARENA_OBJECT(name_) \
428 ARENA_OBJECT(name_, presArenaSundriesSize, "/layout/pres-arena/")
429 #include "nsPresArenaObjectList.h"
430 #undef PRES_ARENA_OBJECT
432 if (presArenaSundriesSize
> 0) {
434 "/layout/pres-arena/sundries", presArenaSundriesSize
,
435 "The sum of all memory used by objects in the arena which were too "
436 "small to be shown individually.");
439 size_t displayListArenaSundriesSize
= 0;
440 #define DISPLAY_LIST_ARENA_OBJECT(name_) \
441 ARENA_OBJECT(name_, displayListArenaSundriesSize, \
442 "/layout/display-list-arena/")
443 #include "nsDisplayListArenaTypes.h"
444 #undef DISPLAY_LIST_ARENA_OBJECT
446 if (displayListArenaSundriesSize
> 0) {
448 "/layout/display-list-arena/sundries", displayListArenaSundriesSize
,
449 "The sum of all memory used by objects in the DL arena which were too "
450 "small to be shown individually.");
455 // There are many different kinds of style structs, but it is likely that
456 // only a few matter. Implement a cutoff so we don't bloat about:memory with
457 // many uninteresting entries.
458 const size_t STYLE_SUNDRIES_THRESHOLD
=
459 js::MemoryReportingSundriesThreshold();
461 size_t styleSundriesSize
= 0;
462 #define STYLE_STRUCT(name_) \
464 size_t size = windowSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_); \
465 if (size < STYLE_SUNDRIES_THRESHOLD) { \
466 styleSundriesSize += size; \
468 REPORT_SUM_SIZE("/layout/style-structs/" #name_, size, \
469 "Memory used by the " #name_ \
470 " style structs within a window."); \
472 aWindowTotalSizes->mStyleSizes.NS_STYLE_SIZES_FIELD(name_) += size; \
474 #include "nsStyleStructList.h"
477 if (styleSundriesSize
> 0) {
479 "/layout/style-structs/sundries", styleSundriesSize
,
480 "The sum of all memory used by style structs which were too "
481 "small to be shown individually.");
485 #undef REPORT_SUM_SIZE
489 typedef nsTArray
<RefPtr
<nsGlobalWindowInner
>> WindowArray
;
492 nsWindowMemoryReporter::CollectReports(nsIHandleReportCallback
* aHandleReport
,
493 nsISupports
* aData
, bool aAnonymize
) {
494 nsGlobalWindowInner::InnerWindowByIdTable
* windowsById
=
495 nsGlobalWindowInner::GetWindowsTable();
496 NS_ENSURE_TRUE(windowsById
, NS_OK
);
498 // Hold on to every window in memory so that window objects can't be
499 // destroyed while we're calling the memory reporter callback.
501 for (auto iter
= windowsById
->Iter(); !iter
.Done(); iter
.Next()) {
502 windows
.AppendElement(iter
.Data());
505 // Get the IDs of all the "ghost" windows, and call aHandleReport->Callback()
507 nsTHashtable
<nsUint64HashKey
> ghostWindows
;
508 CheckForGhostWindows(&ghostWindows
);
509 for (auto iter
= ghostWindows
.ConstIter(); !iter
.Done(); iter
.Next()) {
510 nsGlobalWindowInner::InnerWindowByIdTable
* windowsById
=
511 nsGlobalWindowInner::GetWindowsTable();
513 NS_WARNING("Couldn't get window-by-id hashtable?");
517 nsGlobalWindowInner
* window
= windowsById
->Get(iter
.Get()->GetKey());
519 NS_WARNING("Could not look up window?");
524 path
.AppendLiteral("ghost-windows/");
525 AppendWindowURI(window
, path
, aAnonymize
);
527 aHandleReport
->Callback(
528 /* process = */ EmptyCString(), path
, nsIMemoryReporter::KIND_OTHER
,
529 nsIMemoryReporter::UNITS_COUNT
,
531 /* description = */ "A ghost window."_ns
, aData
);
536 "ghost-windows", KIND_OTHER
, UNITS_COUNT
, ghostWindows
.Count(),
537 "The number of ghost windows present (the number of nodes underneath "
538 "explicit/window-objects/top(none)/ghost, modulo race conditions). A ghost "
539 "window is not shown in any tab, is not in a browsing context group with any "
540 "non-detached windows, and has met these criteria for at least "
541 "memory.ghost_window_timeout_seconds, or has survived a round of "
542 "about:memory's minimize memory usage button.\n\n"
543 "Ghost windows can happen legitimately, but they are often indicative of "
544 "leaks in the browser or add-ons.");
547 WindowPaths windowPaths
;
548 WindowPaths topWindowPaths
;
550 // Collect window memory usage.
551 SizeOfState
fakeState(nullptr); // this won't be used
552 nsWindowSizes
windowTotalSizes(fakeState
);
553 for (uint32_t i
= 0; i
< windows
.Length(); i
++) {
554 CollectWindowReports(windows
[i
], &windowTotalSizes
, &ghostWindows
,
555 &windowPaths
, &topWindowPaths
, aHandleReport
, aData
,
559 // Report JS memory usage. We do this from here because the JS memory
560 // reporter needs to be passed |windowPaths|.
561 xpc::JSReporter::CollectReports(&windowPaths
, &topWindowPaths
, aHandleReport
,
565 nsXULPrototypeCache::CollectMemoryReports(aHandleReport
, aData
);
568 #define REPORT(_path, _amount, _desc) \
569 aHandleReport->Callback(EmptyCString(), nsLiteralCString(_path), KIND_OTHER, \
570 UNITS_BYTES, _amount, nsLiteralCString(_desc), \
573 REPORT("window-objects/dom/element-nodes",
574 windowTotalSizes
.mDOMElementNodesSize
,
575 "This is the sum of all windows' 'dom/element-nodes' numbers.");
577 REPORT("window-objects/dom/text-nodes", windowTotalSizes
.mDOMTextNodesSize
,
578 "This is the sum of all windows' 'dom/text-nodes' numbers.");
580 REPORT("window-objects/dom/cdata-nodes", windowTotalSizes
.mDOMCDATANodesSize
,
581 "This is the sum of all windows' 'dom/cdata-nodes' numbers.");
583 REPORT("window-objects/dom/comment-nodes",
584 windowTotalSizes
.mDOMCommentNodesSize
,
585 "This is the sum of all windows' 'dom/comment-nodes' numbers.");
587 REPORT("window-objects/dom/event-targets",
588 windowTotalSizes
.mDOMEventTargetsSize
,
589 "This is the sum of all windows' 'dom/event-targets' numbers.");
591 REPORT("window-objects/dom/performance",
592 windowTotalSizes
.mDOMPerformanceUserEntries
+
593 windowTotalSizes
.mDOMPerformanceResourceEntries
,
594 "This is the sum of all windows' 'dom/performance/' numbers.");
596 REPORT("window-objects/dom/other", windowTotalSizes
.mDOMOtherSize
,
597 "This is the sum of all windows' 'dom/other' numbers.");
599 REPORT("window-objects/layout/style-sheets",
600 windowTotalSizes
.mLayoutStyleSheetsSize
,
601 "This is the sum of all windows' 'layout/style-sheets' numbers.");
603 REPORT("window-objects/layout/pres-shell",
604 windowTotalSizes
.mLayoutPresShellSize
,
605 "This is the sum of all windows' 'layout/arenas' numbers.");
607 REPORT("window-objects/layout/style-sets",
608 windowTotalSizes
.mLayoutStyleSetsStylistRuleTree
+
609 windowTotalSizes
.mLayoutStyleSetsStylistElementAndPseudosMaps
+
610 windowTotalSizes
.mLayoutStyleSetsStylistInvalidationMap
+
611 windowTotalSizes
.mLayoutStyleSetsStylistRevalidationSelectors
+
612 windowTotalSizes
.mLayoutStyleSetsStylistOther
+
613 windowTotalSizes
.mLayoutStyleSetsOther
,
614 "This is the sum of all windows' 'layout/style-sets/' numbers.");
616 REPORT("window-objects/layout/element-data-objects",
617 windowTotalSizes
.mLayoutElementDataObjects
,
618 "This is the sum of all windows' 'layout/element-data-objects' "
621 REPORT("window-objects/layout/text-runs",
622 windowTotalSizes
.mLayoutTextRunsSize
,
623 "This is the sum of all windows' 'layout/text-runs' numbers.");
625 REPORT("window-objects/layout/pres-contexts",
626 windowTotalSizes
.mLayoutPresContextSize
,
627 "This is the sum of all windows' 'layout/pres-contexts' numbers.");
629 REPORT("window-objects/layout/frame-properties",
630 windowTotalSizes
.mLayoutFramePropertiesSize
,
631 "This is the sum of all windows' 'layout/frame-properties' numbers.");
633 REPORT("window-objects/layout/computed-values",
634 windowTotalSizes
.mLayoutComputedValuesDom
+
635 windowTotalSizes
.mLayoutComputedValuesNonDom
+
636 windowTotalSizes
.mLayoutComputedValuesVisited
,
637 "This is the sum of all windows' 'layout/computed-values/' numbers.");
639 REPORT("window-objects/property-tables", windowTotalSizes
.mPropertyTablesSize
,
640 "This is the sum of all windows' 'property-tables' numbers.");
642 size_t presArenaTotal
= 0;
643 #define PRES_ARENA_OBJECT(name_) \
644 presArenaTotal += windowTotalSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_);
645 #include "nsPresArenaObjectList.h"
646 #undef PRES_ARENA_OBJECT
648 REPORT("window-objects/layout/pres-arena", presArenaTotal
,
649 "Memory used for the pres arena within windows. "
650 "This is the sum of all windows' 'layout/pres-arena/' numbers.");
652 size_t displayListArenaTotal
= 0;
653 #define DISPLAY_LIST_ARENA_OBJECT(name_) \
654 displayListArenaTotal += \
655 windowTotalSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_);
656 #include "nsDisplayListArenaTypes.h"
657 #undef DISPLAY_LIST_ARENA_OBJECT
659 REPORT("window-objects/layout/display-list-arena", displayListArenaTotal
,
660 "Memory used for the display list arena within windows. This is the "
661 "sum of all windows' 'layout/display-list-arena/' numbers.");
663 size_t styleTotal
= 0;
664 #define STYLE_STRUCT(name_) \
665 styleTotal += windowTotalSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_);
666 #include "nsStyleStructList.h"
669 REPORT("window-objects/layout/style-structs", styleTotal
,
670 "Memory used for style structs within windows. This is the sum of "
671 "all windows' 'layout/style-structs/' numbers.");
678 uint32_t nsWindowMemoryReporter::GetGhostTimeout() {
679 return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
683 nsWindowMemoryReporter::Observe(nsISupports
* aSubject
, const char* aTopic
,
684 const char16_t
* aData
) {
685 if (!strcmp(aTopic
, "after-minimize-memory-usage")) {
686 ObserveAfterMinimizeMemoryUsage();
687 } else if (!strcmp(aTopic
, "cycle-collector-begin")) {
689 mCheckTimerWaitingForCCEnd
= true;
692 mCycleCollectorIsRunning
= true;
693 } else if (!strcmp(aTopic
, "cycle-collector-end")) {
694 mCycleCollectorIsRunning
= false;
695 if (mCheckTimerWaitingForCCEnd
) {
696 mCheckTimerWaitingForCCEnd
= false;
697 AsyncCheckForGhostWindows();
706 void nsWindowMemoryReporter::ObserveDOMWindowDetached(
707 nsGlobalWindowInner
* aWindow
) {
708 nsWeakPtr weakWindow
= do_GetWeakReference(aWindow
);
710 NS_WARNING("Couldn't take weak reference to a window?");
714 mDetachedWindows
.Put(weakWindow
, TimeStamp());
716 AsyncCheckForGhostWindows();
720 void nsWindowMemoryReporter::CheckTimerFired(nsITimer
* aTimer
, void* aData
) {
721 if (sWindowReporter
) {
722 MOZ_ASSERT(!sWindowReporter
->mCycleCollectorIsRunning
);
723 sWindowReporter
->CheckForGhostWindows();
727 void nsWindowMemoryReporter::AsyncCheckForGhostWindows() {
732 if (mCycleCollectorIsRunning
) {
733 mCheckTimerWaitingForCCEnd
= true;
737 // If more than kTimeBetweenChecks seconds have elapsed since the last check,
738 // timerDelay is 0. Otherwise, it is kTimeBetweenChecks, reduced by the time
739 // since the last check. Reducing the delay by the time since the last check
740 // prevents the timer from being completely starved if it is repeatedly killed
742 int32_t timeSinceLastCheck
=
743 (TimeStamp::NowLoRes() - mLastCheckForGhostWindows
).ToSeconds();
745 (kTimeBetweenChecks
- std::min(timeSinceLastCheck
, kTimeBetweenChecks
)) *
748 NS_NewTimerWithFuncCallback(
749 getter_AddRefs(mCheckTimer
), CheckTimerFired
, nullptr, timerDelay
,
750 nsITimer::TYPE_ONE_SHOT
,
751 "nsWindowMemoryReporter::AsyncCheckForGhostWindows_timer");
754 void nsWindowMemoryReporter::ObserveAfterMinimizeMemoryUsage() {
755 // Someone claims they've done enough GC/CCs so that all eligible windows
756 // have been free'd. So we deem that any windows which satisfy ghost
757 // criteria (1) and (2) now satisfy criterion (3) as well.
759 // To effect this change, we'll backdate some of our timestamps.
761 TimeStamp minTimeStamp
=
762 TimeStamp::Now() - TimeDuration::FromSeconds(GetGhostTimeout());
764 for (auto iter
= mDetachedWindows
.Iter(); !iter
.Done(); iter
.Next()) {
765 TimeStamp
& timeStamp
= iter
.Data();
766 if (!timeStamp
.IsNull() && timeStamp
> minTimeStamp
) {
767 timeStamp
= minTimeStamp
;
773 * Iterate over mDetachedWindows and update it to reflect the current state of
774 * the world. In particular:
776 * - Remove weak refs to windows which no longer exist.
778 * - Remove references to windows which are no longer detached.
780 * - Reset the timestamp on detached windows which share a domain with a
781 * non-detached window (they no longer meet ghost criterion (2)).
783 * - If a window now meets ghost criterion (2) but didn't before, set its
786 * Additionally, if aOutGhostIDs is not null, fill it with the window IDs of
787 * all ghost windows we found.
789 void nsWindowMemoryReporter::CheckForGhostWindows(
790 nsTHashtable
<nsUint64HashKey
>* aOutGhostIDs
/* = nullptr */) {
791 nsGlobalWindowInner::InnerWindowByIdTable
* windowsById
=
792 nsGlobalWindowInner::GetWindowsTable();
794 NS_WARNING("GetWindowsTable returned null");
798 mLastCheckForGhostWindows
= TimeStamp::NowLoRes();
801 nsTHashtable
<nsPtrHashKey
<BrowsingContextGroup
>>
802 nonDetachedBrowsingContextGroups
;
804 // Populate nonDetachedBrowsingContextGroups.
805 for (auto iter
= windowsById
->Iter(); !iter
.Done(); iter
.Next()) {
806 // Null outer window implies null top, but calling GetInProcessTop() when
807 // there's no outer window causes us to spew debug warnings.
808 nsGlobalWindowInner
* window
= iter
.UserData();
809 if (!window
->GetOuterWindow() || !window
->GetInProcessTopInternal() ||
810 !window
->GetBrowsingContextGroup()) {
811 // This window is detached, so we don't care about its browsing
816 nonDetachedBrowsingContextGroups
.PutEntry(
817 window
->GetBrowsingContextGroup());
820 // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
822 uint32_t ghostTimeout
= GetGhostTimeout();
823 TimeStamp now
= mLastCheckForGhostWindows
;
824 mGhostWindowCount
= 0;
825 for (auto iter
= mDetachedWindows
.Iter(); !iter
.Done(); iter
.Next()) {
826 nsWeakPtr weakKey
= do_QueryInterface(iter
.Key());
827 nsCOMPtr
<mozIDOMWindow
> iwindow
= do_QueryReferent(weakKey
);
829 // The window object has been destroyed. Stop tracking its weak ref in
835 nsPIDOMWindowInner
* window
= nsPIDOMWindowInner::From(iwindow
);
837 // Avoid calling GetInProcessTop() if we have no outer window. Nothing
838 // will break if we do, but it will spew debug output, which can cause our
839 // test logs to overflow.
840 nsCOMPtr
<nsPIDOMWindowOuter
> top
;
841 if (window
->GetOuterWindow()) {
842 top
= window
->GetOuterWindow()->GetInProcessTop();
846 // The window is no longer detached, so we no longer want to track it.
851 TimeStamp
& timeStamp
= iter
.Data();
852 BrowsingContextGroup
* browsingContextGroup
=
853 window
->GetBrowsingContextGroup();
854 if (browsingContextGroup
&&
855 nonDetachedBrowsingContextGroups
.GetEntry(browsingContextGroup
)) {
856 // This window is in the same browsing context group as a non-detached
857 // window, so reset its clock.
858 timeStamp
= TimeStamp();
860 // This window is not in the same browsing context group as a non-detached
861 // window, so it meets ghost criterion (2).
862 if (timeStamp
.IsNull()) {
863 // This may become a ghost window later; start its clock.
865 } else if ((now
- timeStamp
).ToSeconds() > ghostTimeout
) {
866 // This definitely is a ghost window, so add it to aOutGhostIDs, if
869 if (aOutGhostIDs
&& window
) {
870 aOutGhostIDs
->PutEntry(window
->WindowID());
876 Telemetry::ScalarSetMaximum(
877 Telemetry::ScalarID::MEMORYREPORTER_MAX_GHOST_WINDOWS
, mGhostWindowCount
);
881 int64_t nsWindowMemoryReporter::GhostWindowsDistinguishedAmount() {
882 return sWindowReporter
->mGhostWindowCount
;
885 void nsWindowMemoryReporter::KillCheckTimer() {
887 mCheckTimer
->Cancel();
888 mCheckTimer
= nullptr;
894 void nsWindowMemoryReporter::UnlinkGhostWindows() {
895 if (!sWindowReporter
) {
899 nsGlobalWindowInner::InnerWindowByIdTable
* windowsById
=
900 nsGlobalWindowInner::GetWindowsTable();
905 // Hold on to every window in memory so that window objects can't be
906 // destroyed while we're calling the UnlinkGhostWindows callback.
908 for (auto iter
= windowsById
->Iter(); !iter
.Done(); iter
.Next()) {
909 windows
.AppendElement(iter
.Data());
912 // Get the IDs of all the "ghost" windows, and unlink them all.
913 nsTHashtable
<nsUint64HashKey
> ghostWindows
;
914 sWindowReporter
->CheckForGhostWindows(&ghostWindows
);
915 for (auto iter
= ghostWindows
.ConstIter(); !iter
.Done(); iter
.Next()) {
916 nsGlobalWindowInner::InnerWindowByIdTable
* windowsById
=
917 nsGlobalWindowInner::GetWindowsTable();
922 RefPtr
<nsGlobalWindowInner
> window
= windowsById
->Get(iter
.Get()->GetKey());
924 window
->RiskyUnlink();