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 "amIAddonManager.h"
8 #include "nsWindowMemoryReporter.h"
9 #include "nsGlobalWindow.h"
10 #include "nsIDocument.h"
11 #include "nsIDOMWindowCollection.h"
12 #include "nsIEffectiveTLDService.h"
13 #include "mozilla/ClearOnShutdown.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/Services.h"
16 #include "mozilla/StaticPtr.h"
18 #include "nsPrintfCString.h"
19 #include "XPCJSMemoryReporter.h"
20 #include "js/MemoryMetrics.h"
21 #include "nsServiceManagerUtils.h"
23 using namespace mozilla
;
25 StaticRefPtr
<nsWindowMemoryReporter
> sWindowReporter
;
28 * Don't trigger a ghost window check when a DOM window is detached if we've
29 * run it this recently.
31 const int32_t kTimeBetweenChecks
= 45; /* seconds */
33 nsWindowMemoryReporter::nsWindowMemoryReporter()
34 : mLastCheckForGhostWindows(TimeStamp::NowLoRes()),
35 mCycleCollectorIsRunning(false),
36 mCheckTimerWaitingForCCEnd(false)
40 nsWindowMemoryReporter::~nsWindowMemoryReporter()
45 NS_IMPL_ISUPPORTS(nsWindowMemoryReporter
, nsIMemoryReporter
, nsIObserver
,
46 nsISupportsWeakReference
)
49 AddNonJSSizeOfWindowAndItsDescendents(nsGlobalWindow
* aWindow
,
52 // Measure the window.
53 nsWindowSizes
windowSizes(moz_malloc_size_of
);
54 aWindow
->AddSizeOfIncludingThis(&windowSizes
);
55 windowSizes
.addToTabSizes(aSizes
);
57 // Measure the inner window, if there is one.
58 nsWindowSizes
innerWindowSizes(moz_malloc_size_of
);
59 nsGlobalWindow
* inner
= aWindow
->IsOuterWindow() ? aWindow
->GetCurrentInnerWindowInternal()
62 inner
->AddSizeOfIncludingThis(&innerWindowSizes
);
63 innerWindowSizes
.addToTabSizes(aSizes
);
66 nsCOMPtr
<nsIDOMWindowCollection
> frames
;
67 nsresult rv
= aWindow
->GetFrames(getter_AddRefs(frames
));
68 NS_ENSURE_SUCCESS(rv
, rv
);
71 rv
= frames
->GetLength(&length
);
72 NS_ENSURE_SUCCESS(rv
, rv
);
74 // Measure this window's descendents.
75 for (uint32_t i
= 0; i
< length
; i
++) {
76 nsCOMPtr
<nsIDOMWindow
> child
;
77 rv
= frames
->Item(i
, getter_AddRefs(child
));
78 NS_ENSURE_SUCCESS(rv
, rv
);
79 NS_ENSURE_STATE(child
);
81 nsGlobalWindow
* childWin
=
82 static_cast<nsGlobalWindow
*>(static_cast<nsIDOMWindow
*>(child
.get()));
84 rv
= AddNonJSSizeOfWindowAndItsDescendents(childWin
, aSizes
);
85 NS_ENSURE_SUCCESS(rv
, rv
);
91 NonJSSizeOfTab(nsPIDOMWindow
* aWindow
, size_t* aDomSize
, size_t* aStyleSize
, size_t* aOtherSize
)
93 nsGlobalWindow
* window
= static_cast<nsGlobalWindow
*>(aWindow
);
96 nsresult rv
= AddNonJSSizeOfWindowAndItsDescendents(window
, &sizes
);
97 NS_ENSURE_SUCCESS(rv
, rv
);
99 *aDomSize
= sizes
.mDom
;
100 *aStyleSize
= sizes
.mStyle
;
101 *aOtherSize
= sizes
.mOther
;
106 nsWindowMemoryReporter::Init()
108 MOZ_ASSERT(!sWindowReporter
);
109 sWindowReporter
= new nsWindowMemoryReporter();
110 ClearOnShutdown(&sWindowReporter
);
111 RegisterStrongMemoryReporter(sWindowReporter
);
112 RegisterNonJSSizeOfTab(NonJSSizeOfTab
);
114 nsCOMPtr
<nsIObserverService
> os
= services::GetObserverService();
116 // DOM_WINDOW_DESTROYED_TOPIC announces what we call window "detachment",
117 // when a window's docshell is set to nullptr.
118 os
->AddObserver(sWindowReporter
, DOM_WINDOW_DESTROYED_TOPIC
,
119 /* weakRef = */ true);
120 os
->AddObserver(sWindowReporter
, "after-minimize-memory-usage",
121 /* weakRef = */ true);
122 os
->AddObserver(sWindowReporter
, "cycle-collector-begin",
123 /* weakRef = */ true);
124 os
->AddObserver(sWindowReporter
, "cycle-collector-end",
125 /* weakRef = */ true);
128 RegisterStrongMemoryReporter(new GhostWindowsReporter());
129 RegisterGhostWindowsDistinguishedAmount(GhostWindowsReporter::DistinguishedAmount
);
132 static already_AddRefed
<nsIURI
>
133 GetWindowURI(nsIDOMWindow
*aWindow
)
135 nsCOMPtr
<nsPIDOMWindow
> pWindow
= do_QueryInterface(aWindow
);
136 NS_ENSURE_TRUE(pWindow
, nullptr);
138 nsCOMPtr
<nsIDocument
> doc
= pWindow
->GetExtantDoc();
139 nsCOMPtr
<nsIURI
> uri
;
142 uri
= doc
->GetDocumentURI();
146 nsCOMPtr
<nsIScriptObjectPrincipal
> scriptObjPrincipal
=
147 do_QueryInterface(aWindow
);
148 NS_ENSURE_TRUE(scriptObjPrincipal
, nullptr);
150 // GetPrincipal() will print a warning if the window does not have an outer
151 // window, so check here for an outer window first. This code is
152 // functionally correct if we leave out the GetOuterWindow() check, but we
153 // end up printing a lot of warnings during debug mochitests.
154 if (pWindow
->GetOuterWindow()) {
155 nsIPrincipal
* principal
= scriptObjPrincipal
->GetPrincipal();
157 principal
->GetURI(getter_AddRefs(uri
));
166 AppendWindowURI(nsGlobalWindow
*aWindow
, nsACString
& aStr
, bool aAnonymize
)
168 nsCOMPtr
<nsIURI
> uri
= GetWindowURI(aWindow
);
171 if (aAnonymize
&& !aWindow
->IsChromeWindow()) {
172 aStr
.AppendPrintf("<anonymized-%d>", aWindow
->WindowID());
177 // A hack: replace forward slashes with '\\' so they aren't
178 // treated as path separators. Users of the reporters
179 // (such as about:memory) have to undo this change.
180 spec
.ReplaceChar('/', '\\');
185 // If we're unable to find a URI, we're dealing with a chrome window with
186 // no document in it (or somesuch), so we call this a "system window".
187 aStr
+= NS_LITERAL_CSTRING("[system]");
191 MOZ_DEFINE_MALLOC_SIZE_OF(WindowsMallocSizeOf
)
193 // The key is the window ID.
194 typedef nsDataHashtable
<nsUint64HashKey
, nsCString
> WindowPaths
;
197 ReportAmount(const nsCString
& aBasePath
, const char* aPathTail
,
198 size_t aAmount
, const nsCString
& aDescription
,
199 uint32_t aKind
, uint32_t aUnits
,
200 nsIMemoryReporterCallback
* aCb
,
201 nsISupports
* aClosure
)
207 nsAutoCString
path(aBasePath
);
210 return aCb
->Callback(EmptyCString(), path
, aKind
, aUnits
,
211 aAmount
, aDescription
, aClosure
);
215 ReportSize(const nsCString
& aBasePath
, const char* aPathTail
,
216 size_t aAmount
, const nsCString
& aDescription
,
217 nsIMemoryReporterCallback
* aCb
,
218 nsISupports
* aClosure
)
220 return ReportAmount(aBasePath
, aPathTail
, aAmount
, aDescription
,
221 nsIMemoryReporter::KIND_HEAP
,
222 nsIMemoryReporter::UNITS_BYTES
, aCb
, aClosure
);
226 ReportCount(const nsCString
& aBasePath
, const char* aPathTail
,
227 size_t aAmount
, const nsCString
& aDescription
,
228 nsIMemoryReporterCallback
* aCb
,
229 nsISupports
* aClosure
)
231 return ReportAmount(aBasePath
, aPathTail
, aAmount
, aDescription
,
232 nsIMemoryReporter::KIND_OTHER
,
233 nsIMemoryReporter::UNITS_COUNT
, aCb
, aClosure
);
237 CollectWindowReports(nsGlobalWindow
*aWindow
,
238 amIAddonManager
*addonManager
,
239 nsWindowSizes
*aWindowTotalSizes
,
240 nsTHashtable
<nsUint64HashKey
> *aGhostWindowIDs
,
241 WindowPaths
*aWindowPaths
,
242 WindowPaths
*aTopWindowPaths
,
243 nsIMemoryReporterCallback
*aCb
,
244 nsISupports
*aClosure
,
247 nsAutoCString
windowPath("explicit/");
249 // Avoid calling aWindow->GetTop() if there's no outer window. It will work
250 // just fine, but will spew a lot of warnings.
251 nsGlobalWindow
*top
= nullptr;
252 nsCOMPtr
<nsIURI
> location
;
253 if (aWindow
->GetOuterWindow()) {
254 // Our window should have a null top iff it has a null docshell.
255 MOZ_ASSERT(!!aWindow
->GetTop() == !!aWindow
->GetDocShell());
256 top
= aWindow
->GetTop();
258 location
= GetWindowURI(top
);
262 location
= GetWindowURI(aWindow
);
265 if (addonManager
&& location
) {
268 if (NS_SUCCEEDED(addonManager
->MapURIToAddonID(location
, id
, &ok
)) && ok
) {
269 // Add-on names are not privacy-sensitive, so we can use them with
271 windowPath
+= NS_LITERAL_CSTRING("add-ons/") + id
+
272 NS_LITERAL_CSTRING("/");
276 windowPath
+= NS_LITERAL_CSTRING("window-objects/");
279 windowPath
+= NS_LITERAL_CSTRING("top(");
280 AppendWindowURI(top
, windowPath
, aAnonymize
);
281 windowPath
+= NS_LITERAL_CSTRING(", id=");
282 windowPath
.AppendInt(top
->WindowID());
283 windowPath
+= NS_LITERAL_CSTRING(")");
285 aTopWindowPaths
->Put(aWindow
->WindowID(), windowPath
);
287 windowPath
+= aWindow
->IsFrozen() ? NS_LITERAL_CSTRING("/cached/")
288 : NS_LITERAL_CSTRING("/active/");
290 if (aGhostWindowIDs
->Contains(aWindow
->WindowID())) {
291 windowPath
+= NS_LITERAL_CSTRING("top(none)/ghost/");
293 windowPath
+= NS_LITERAL_CSTRING("top(none)/detached/");
297 windowPath
+= NS_LITERAL_CSTRING("window(");
298 AppendWindowURI(aWindow
, windowPath
, aAnonymize
);
299 windowPath
+= NS_LITERAL_CSTRING(")");
301 // Use |windowPath|, but replace "explicit/" with "event-counts/".
302 nsCString
censusWindowPath(windowPath
);
303 censusWindowPath
.Replace(0, strlen("explicit"), "event-counts");
305 // Remember the path for later.
306 aWindowPaths
->Put(aWindow
->WindowID(), windowPath
);
308 #define REPORT_SIZE(_pathTail, _amount, _desc) \
310 nsresult rv = ReportSize(windowPath, _pathTail, _amount, \
311 NS_LITERAL_CSTRING(_desc), aCb, aClosure); \
312 NS_ENSURE_SUCCESS(rv, rv); \
315 #define REPORT_COUNT(_pathTail, _amount, _desc) \
317 nsresult rv = ReportCount(censusWindowPath, _pathTail, _amount, \
318 NS_LITERAL_CSTRING(_desc), aCb, aClosure); \
319 NS_ENSURE_SUCCESS(rv, rv); \
322 nsWindowSizes
windowSizes(WindowsMallocSizeOf
);
323 aWindow
->AddSizeOfIncludingThis(&windowSizes
);
325 REPORT_SIZE("/dom/element-nodes", windowSizes
.mDOMElementNodesSize
,
326 "Memory used by the element nodes in a window's DOM.");
327 aWindowTotalSizes
->mDOMElementNodesSize
+= windowSizes
.mDOMElementNodesSize
;
329 REPORT_SIZE("/dom/text-nodes", windowSizes
.mDOMTextNodesSize
,
330 "Memory used by the text nodes in a window's DOM.");
331 aWindowTotalSizes
->mDOMTextNodesSize
+= windowSizes
.mDOMTextNodesSize
;
333 REPORT_SIZE("/dom/cdata-nodes", windowSizes
.mDOMCDATANodesSize
,
334 "Memory used by the CDATA nodes in a window's DOM.");
335 aWindowTotalSizes
->mDOMCDATANodesSize
+= windowSizes
.mDOMCDATANodesSize
;
337 REPORT_SIZE("/dom/comment-nodes", windowSizes
.mDOMCommentNodesSize
,
338 "Memory used by the comment nodes in a window's DOM.");
339 aWindowTotalSizes
->mDOMCommentNodesSize
+= windowSizes
.mDOMCommentNodesSize
;
341 REPORT_SIZE("/dom/event-targets", windowSizes
.mDOMEventTargetsSize
,
342 "Memory used by the event targets table in a window's DOM, and "
343 "the objects it points to, which include XHRs.");
344 aWindowTotalSizes
->mDOMEventTargetsSize
+= windowSizes
.mDOMEventTargetsSize
;
346 REPORT_COUNT("/dom/event-targets", windowSizes
.mDOMEventTargetsCount
,
347 "Number of non-node event targets in the event targets table "
348 "in a window's DOM, such as XHRs.");
349 aWindowTotalSizes
->mDOMEventTargetsCount
+=
350 windowSizes
.mDOMEventTargetsCount
;
352 REPORT_COUNT("/dom/event-listeners", windowSizes
.mDOMEventListenersCount
,
353 "Number of event listeners in a window, including event "
354 "listeners on nodes and other event targets.");
355 aWindowTotalSizes
->mDOMEventListenersCount
+=
356 windowSizes
.mDOMEventListenersCount
;
358 REPORT_SIZE("/dom/other", windowSizes
.mDOMOtherSize
,
359 "Memory used by a window's DOM that isn't measured by the "
360 "other 'dom/' numbers.");
361 aWindowTotalSizes
->mDOMOtherSize
+= windowSizes
.mDOMOtherSize
;
363 REPORT_SIZE("/property-tables",
364 windowSizes
.mPropertyTablesSize
,
365 "Memory used for the property tables within a window.");
366 aWindowTotalSizes
->mPropertyTablesSize
+= windowSizes
.mPropertyTablesSize
;
368 REPORT_SIZE("/style-sheets", windowSizes
.mStyleSheetsSize
,
369 "Memory used by style sheets within a window.");
370 aWindowTotalSizes
->mStyleSheetsSize
+= windowSizes
.mStyleSheetsSize
;
372 REPORT_SIZE("/layout/pres-shell", windowSizes
.mLayoutPresShellSize
,
373 "Memory used by layout's PresShell, along with any structures "
374 "allocated in its arena and not measured elsewhere, "
376 aWindowTotalSizes
->mLayoutPresShellSize
+= windowSizes
.mLayoutPresShellSize
;
378 REPORT_SIZE("/layout/line-boxes", windowSizes
.mArenaStats
.mLineBoxes
,
379 "Memory used by line boxes within a window.");
380 aWindowTotalSizes
->mArenaStats
.mLineBoxes
381 += windowSizes
.mArenaStats
.mLineBoxes
;
383 REPORT_SIZE("/layout/rule-nodes", windowSizes
.mArenaStats
.mRuleNodes
,
384 "Memory used by CSS rule nodes within a window.");
385 aWindowTotalSizes
->mArenaStats
.mRuleNodes
386 += windowSizes
.mArenaStats
.mRuleNodes
;
388 REPORT_SIZE("/layout/style-contexts", windowSizes
.mArenaStats
.mStyleContexts
,
389 "Memory used by style contexts within a window.");
390 aWindowTotalSizes
->mArenaStats
.mStyleContexts
391 += windowSizes
.mArenaStats
.mStyleContexts
;
393 REPORT_SIZE("/layout/style-sets", windowSizes
.mLayoutStyleSetsSize
,
394 "Memory used by style sets within a window.");
395 aWindowTotalSizes
->mLayoutStyleSetsSize
+= windowSizes
.mLayoutStyleSetsSize
;
397 REPORT_SIZE("/layout/text-runs", windowSizes
.mLayoutTextRunsSize
,
398 "Memory used for text-runs (glyph layout) in the PresShell's "
399 "frame tree, within a window.");
400 aWindowTotalSizes
->mLayoutTextRunsSize
+= windowSizes
.mLayoutTextRunsSize
;
402 REPORT_SIZE("/layout/pres-contexts", windowSizes
.mLayoutPresContextSize
,
403 "Memory used for the PresContext in the PresShell's frame "
405 aWindowTotalSizes
->mLayoutPresContextSize
+=
406 windowSizes
.mLayoutPresContextSize
;
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 FRAME_SUNDRIES_THRESHOLD
=
412 js::MemoryReportingSundriesThreshold();
414 size_t frameSundriesSize
= 0;
415 #define FRAME_ID(classname) \
418 = windowSizes.mArenaStats.FRAME_ID_STAT_FIELD(classname); \
419 if (frameSize < FRAME_SUNDRIES_THRESHOLD) { \
420 frameSundriesSize += frameSize; \
422 REPORT_SIZE("/layout/frames/" # classname, frameSize, \
423 "Memory used by frames of " \
424 "type " #classname " within a window."); \
426 aWindowTotalSizes->mArenaStats.FRAME_ID_STAT_FIELD(classname) \
429 #include "nsFrameIdList.h"
432 if (frameSundriesSize
> 0) {
433 REPORT_SIZE("/layout/frames/sundries", frameSundriesSize
,
434 "The sum of all memory used by frames which were too small "
435 "to be shown individually.");
444 typedef nsTArray
< nsRefPtr
<nsGlobalWindow
> > WindowArray
;
448 GetWindows(const uint64_t& aId
, nsGlobalWindow
*& aWindow
, void* aClosure
)
450 ((WindowArray
*)aClosure
)->AppendElement(aWindow
);
452 return PL_DHASH_NEXT
;
455 struct ReportGhostWindowsEnumeratorData
457 nsIMemoryReporterCallback
* mCallback
;
463 static PLDHashOperator
464 ReportGhostWindowsEnumerator(nsUint64HashKey
* aIDHashKey
, void* aData
)
466 ReportGhostWindowsEnumeratorData
*data
=
467 static_cast<ReportGhostWindowsEnumeratorData
*>(aData
);
469 nsGlobalWindow::WindowByIdTable
* windowsById
=
470 nsGlobalWindow::GetWindowsTable();
472 NS_WARNING("Couldn't get window-by-id hashtable?");
473 return PL_DHASH_NEXT
;
476 nsGlobalWindow
* window
= windowsById
->Get(aIDHashKey
->GetKey());
478 NS_WARNING("Could not look up window?");
479 return PL_DHASH_NEXT
;
483 path
.AppendLiteral("ghost-windows/");
484 AppendWindowURI(window
, path
, data
->mAnonymize
);
486 nsresult rv
= data
->mCallback
->Callback(
487 /* process = */ EmptyCString(),
489 nsIMemoryReporter::KIND_OTHER
,
490 nsIMemoryReporter::UNITS_COUNT
,
492 /* description = */ NS_LITERAL_CSTRING("A ghost window."),
495 if (NS_FAILED(rv
) && NS_SUCCEEDED(data
->mRv
)) {
499 return PL_DHASH_NEXT
;
503 nsWindowMemoryReporter::CollectReports(nsIMemoryReporterCallback
* aCb
,
504 nsISupports
* aClosure
, bool aAnonymize
)
506 nsGlobalWindow::WindowByIdTable
* windowsById
=
507 nsGlobalWindow::GetWindowsTable();
508 NS_ENSURE_TRUE(windowsById
, NS_OK
);
510 // Hold on to every window in memory so that window objects can't be
511 // destroyed while we're calling the memory reporter callback.
513 windowsById
->Enumerate(GetWindows
, &windows
);
515 // Get the IDs of all the "ghost" windows, and call aCb->Callback() for each
517 nsTHashtable
<nsUint64HashKey
> ghostWindows
;
518 CheckForGhostWindows(&ghostWindows
);
519 ReportGhostWindowsEnumeratorData reportGhostWindowsEnumData
=
520 { aCb
, aClosure
, aAnonymize
, NS_OK
};
521 ghostWindows
.EnumerateEntries(ReportGhostWindowsEnumerator
,
522 &reportGhostWindowsEnumData
);
523 nsresult rv
= reportGhostWindowsEnumData
.mRv
;
524 NS_ENSURE_SUCCESS(rv
, rv
);
526 WindowPaths windowPaths
;
527 WindowPaths topWindowPaths
;
529 // Collect window memory usage.
530 nsWindowSizes
windowTotalSizes(nullptr);
531 nsCOMPtr
<amIAddonManager
> addonManager
;
532 if (XRE_GetProcessType() == GeckoProcessType_Default
) {
533 // Only try to access the service from the main process.
534 addonManager
= do_GetService("@mozilla.org/addons/integration;1");
536 for (uint32_t i
= 0; i
< windows
.Length(); i
++) {
537 rv
= CollectWindowReports(windows
[i
], addonManager
,
538 &windowTotalSizes
, &ghostWindows
,
539 &windowPaths
, &topWindowPaths
, aCb
,
540 aClosure
, aAnonymize
);
541 NS_ENSURE_SUCCESS(rv
, rv
);
544 // Report JS memory usage. We do this from here because the JS memory
545 // reporter needs to be passed |windowPaths|.
546 rv
= xpc::JSReporter::CollectReports(&windowPaths
, &topWindowPaths
,
547 aCb
, aClosure
, aAnonymize
);
548 NS_ENSURE_SUCCESS(rv
, rv
);
550 #define REPORT(_path, _amount, _desc) \
553 rv = aCb->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \
554 KIND_OTHER, UNITS_BYTES, _amount, \
555 NS_LITERAL_CSTRING(_desc), aClosure); \
556 NS_ENSURE_SUCCESS(rv, rv); \
559 REPORT("window-objects/dom/element-nodes", windowTotalSizes
.mDOMElementNodesSize
,
560 "This is the sum of all windows' 'dom/element-nodes' numbers.");
562 REPORT("window-objects/dom/text-nodes", windowTotalSizes
.mDOMTextNodesSize
,
563 "This is the sum of all windows' 'dom/text-nodes' numbers.");
565 REPORT("window-objects/dom/cdata-nodes", windowTotalSizes
.mDOMCDATANodesSize
,
566 "This is the sum of all windows' 'dom/cdata-nodes' numbers.");
568 REPORT("window-objects/dom/comment-nodes", windowTotalSizes
.mDOMCommentNodesSize
,
569 "This is the sum of all windows' 'dom/comment-nodes' numbers.");
571 REPORT("window-objects/dom/event-targets", windowTotalSizes
.mDOMEventTargetsSize
,
572 "This is the sum of all windows' 'dom/event-targets' numbers.");
574 REPORT("window-objects/dom/other", windowTotalSizes
.mDOMOtherSize
,
575 "This is the sum of all windows' 'dom/other' numbers.");
577 REPORT("window-objects/property-tables",
578 windowTotalSizes
.mPropertyTablesSize
,
579 "This is the sum of all windows' 'property-tables' numbers.");
581 REPORT("window-objects/style-sheets", windowTotalSizes
.mStyleSheetsSize
,
582 "This is the sum of all windows' 'style-sheets' numbers.");
584 REPORT("window-objects/layout/pres-shell", windowTotalSizes
.mLayoutPresShellSize
,
585 "This is the sum of all windows' 'layout/arenas' numbers.");
587 REPORT("window-objects/layout/line-boxes",
588 windowTotalSizes
.mArenaStats
.mLineBoxes
,
589 "This is the sum of all windows' 'layout/line-boxes' numbers.");
591 REPORT("window-objects/layout/rule-nodes",
592 windowTotalSizes
.mArenaStats
.mRuleNodes
,
593 "This is the sum of all windows' 'layout/rule-nodes' numbers.");
595 REPORT("window-objects/layout/style-contexts",
596 windowTotalSizes
.mArenaStats
.mStyleContexts
,
597 "This is the sum of all windows' 'layout/style-contexts' numbers.");
599 REPORT("window-objects/layout/style-sets", windowTotalSizes
.mLayoutStyleSetsSize
,
600 "This is the sum of all windows' 'layout/style-sets' numbers.");
602 REPORT("window-objects/layout/text-runs", windowTotalSizes
.mLayoutTextRunsSize
,
603 "This is the sum of all windows' 'layout/text-runs' numbers.");
605 REPORT("window-objects/layout/pres-contexts", windowTotalSizes
.mLayoutPresContextSize
,
606 "This is the sum of all windows' 'layout/pres-contexts' numbers.");
608 size_t frameTotal
= 0;
609 #define FRAME_ID(classname) \
610 frameTotal += windowTotalSizes.mArenaStats.FRAME_ID_STAT_FIELD(classname);
611 #include "nsFrameIdList.h"
614 REPORT("window-objects/layout/frames", frameTotal
,
615 "Memory used for layout frames within windows. "
616 "This is the sum of all windows' 'layout/frames/' numbers.");
624 nsWindowMemoryReporter::GetGhostTimeout()
626 return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
630 nsWindowMemoryReporter::Observe(nsISupports
*aSubject
, const char *aTopic
,
631 const char16_t
*aData
)
633 if (!strcmp(aTopic
, DOM_WINDOW_DESTROYED_TOPIC
)) {
634 ObserveDOMWindowDetached(aSubject
);
635 } else if (!strcmp(aTopic
, "after-minimize-memory-usage")) {
636 ObserveAfterMinimizeMemoryUsage();
637 } else if (!strcmp(aTopic
, "cycle-collector-begin")) {
639 mCheckTimerWaitingForCCEnd
= true;
642 mCycleCollectorIsRunning
= true;
643 } else if (!strcmp(aTopic
, "cycle-collector-end")) {
644 mCycleCollectorIsRunning
= false;
645 if (mCheckTimerWaitingForCCEnd
) {
646 mCheckTimerWaitingForCCEnd
= false;
647 AsyncCheckForGhostWindows();
657 nsWindowMemoryReporter::ObserveDOMWindowDetached(nsISupports
* aWindow
)
659 nsWeakPtr weakWindow
= do_GetWeakReference(aWindow
);
661 NS_WARNING("Couldn't take weak reference to a window?");
665 mDetachedWindows
.Put(weakWindow
, TimeStamp());
667 AsyncCheckForGhostWindows();
672 nsWindowMemoryReporter::CheckTimerFired(nsITimer
* aTimer
, void* aClosure
)
674 if (sWindowReporter
) {
675 MOZ_ASSERT(!sWindowReporter
->mCycleCollectorIsRunning
);
676 sWindowReporter
->CheckForGhostWindows();
681 nsWindowMemoryReporter::AsyncCheckForGhostWindows()
687 if (mCycleCollectorIsRunning
) {
688 mCheckTimerWaitingForCCEnd
= true;
692 // If more than kTimeBetweenChecks seconds have elapsed since the last check,
693 // timerDelay is 0. Otherwise, it is kTimeBetweenChecks, reduced by the time
694 // since the last check. Reducing the delay by the time since the last check
695 // prevents the timer from being completely starved if it is repeatedly killed
697 int32_t timeSinceLastCheck
= (TimeStamp::NowLoRes() - mLastCheckForGhostWindows
).ToSeconds();
698 int32_t timerDelay
= (kTimeBetweenChecks
- std::min(timeSinceLastCheck
, kTimeBetweenChecks
)) * PR_MSEC_PER_SEC
;
700 mCheckTimer
= do_CreateInstance("@mozilla.org/timer;1");
703 mCheckTimer
->InitWithFuncCallback(CheckTimerFired
, nullptr,
704 timerDelay
, nsITimer::TYPE_ONE_SHOT
);
708 static PLDHashOperator
709 BackdateTimeStampsEnumerator(nsISupports
*aKey
, TimeStamp
&aTimeStamp
,
712 TimeStamp
*minTimeStamp
= static_cast<TimeStamp
*>(aClosure
);
714 if (!aTimeStamp
.IsNull() && aTimeStamp
> *minTimeStamp
) {
715 aTimeStamp
= *minTimeStamp
;
718 return PL_DHASH_NEXT
;
722 nsWindowMemoryReporter::ObserveAfterMinimizeMemoryUsage()
724 // Someone claims they've done enough GC/CCs so that all eligible windows
725 // have been free'd. So we deem that any windows which satisfy ghost
726 // criteria (1) and (2) now satisfy criterion (3) as well.
728 // To effect this change, we'll backdate some of our timestamps.
730 TimeStamp minTimeStamp
= TimeStamp::Now() -
731 TimeDuration::FromSeconds(GetGhostTimeout());
733 mDetachedWindows
.Enumerate(BackdateTimeStampsEnumerator
,
737 struct CheckForGhostWindowsEnumeratorData
739 nsTHashtable
<nsCStringHashKey
> *nonDetachedDomains
;
740 nsTHashtable
<nsUint64HashKey
> *ghostWindowIDs
;
741 nsIEffectiveTLDService
*tldService
;
742 uint32_t ghostTimeout
;
746 static PLDHashOperator
747 CheckForGhostWindowsEnumerator(nsISupports
*aKey
, TimeStamp
& aTimeStamp
,
750 CheckForGhostWindowsEnumeratorData
*data
=
751 static_cast<CheckForGhostWindowsEnumeratorData
*>(aClosure
);
753 nsWeakPtr weakKey
= do_QueryInterface(aKey
);
754 nsCOMPtr
<nsPIDOMWindow
> window
= do_QueryReferent(weakKey
);
756 // The window object has been destroyed. Stop tracking its weak ref in our
758 return PL_DHASH_REMOVE
;
761 // Avoid calling GetTop() if we have no outer window. Nothing will break if
762 // we do, but it will spew debug output, which can cause our test logs to
764 nsCOMPtr
<nsIDOMWindow
> top
;
765 if (window
->GetOuterWindow()) {
766 window
->GetTop(getter_AddRefs(top
));
770 // The window is no longer detached, so we no longer want to track it.
771 return PL_DHASH_REMOVE
;
774 nsCOMPtr
<nsIURI
> uri
= GetWindowURI(window
);
776 nsAutoCString domain
;
778 // GetBaseDomain works fine if |uri| is null, but it outputs a warning
779 // which ends up overrunning the mochitest logs.
780 data
->tldService
->GetBaseDomain(uri
, 0, domain
);
783 if (data
->nonDetachedDomains
->Contains(domain
)) {
784 // This window shares a domain with a non-detached window, so reset its
786 aTimeStamp
= TimeStamp();
788 // This window does not share a domain with a non-detached window, so it
789 // meets ghost criterion (2).
790 if (aTimeStamp
.IsNull()) {
791 // This may become a ghost window later; start its clock.
792 aTimeStamp
= data
->now
;
793 } else if ((data
->now
- aTimeStamp
).ToSeconds() > data
->ghostTimeout
) {
794 // This definitely is a ghost window, so add it to ghostWindowIDs, if
796 if (data
->ghostWindowIDs
) {
797 nsCOMPtr
<nsPIDOMWindow
> pWindow
= do_QueryInterface(window
);
799 data
->ghostWindowIDs
->PutEntry(pWindow
->WindowID());
805 return PL_DHASH_NEXT
;
808 struct GetNonDetachedWindowDomainsEnumeratorData
810 nsTHashtable
<nsCStringHashKey
> *nonDetachedDomains
;
811 nsIEffectiveTLDService
*tldService
;
814 static PLDHashOperator
815 GetNonDetachedWindowDomainsEnumerator(const uint64_t& aId
, nsGlobalWindow
* aWindow
,
818 GetNonDetachedWindowDomainsEnumeratorData
*data
=
819 static_cast<GetNonDetachedWindowDomainsEnumeratorData
*>(aClosure
);
821 // Null outer window implies null top, but calling GetTop() when there's no
822 // outer window causes us to spew debug warnings.
823 if (!aWindow
->GetOuterWindow() || !aWindow
->GetTop()) {
824 // This window is detached, so we don't care about its domain.
825 return PL_DHASH_NEXT
;
828 nsCOMPtr
<nsIURI
> uri
= GetWindowURI(aWindow
);
830 nsAutoCString domain
;
832 data
->tldService
->GetBaseDomain(uri
, 0, domain
);
835 data
->nonDetachedDomains
->PutEntry(domain
);
836 return PL_DHASH_NEXT
;
840 * Iterate over mDetachedWindows and update it to reflect the current state of
841 * the world. In particular:
843 * - Remove weak refs to windows which no longer exist.
845 * - Remove references to windows which are no longer detached.
847 * - Reset the timestamp on detached windows which share a domain with a
848 * non-detached window (they no longer meet ghost criterion (2)).
850 * - If a window now meets ghost criterion (2) but didn't before, set its
853 * Additionally, if aOutGhostIDs is not null, fill it with the window IDs of
854 * all ghost windows we found.
857 nsWindowMemoryReporter::CheckForGhostWindows(
858 nsTHashtable
<nsUint64HashKey
> *aOutGhostIDs
/* = nullptr */)
860 nsCOMPtr
<nsIEffectiveTLDService
> tldService
= do_GetService(
861 NS_EFFECTIVETLDSERVICE_CONTRACTID
);
863 NS_WARNING("Couldn't get TLDService.");
867 nsGlobalWindow::WindowByIdTable
*windowsById
=
868 nsGlobalWindow::GetWindowsTable();
870 NS_WARNING("GetWindowsTable returned null");
874 mLastCheckForGhostWindows
= TimeStamp::NowLoRes();
877 nsTHashtable
<nsCStringHashKey
> nonDetachedWindowDomains
;
879 // Populate nonDetachedWindowDomains.
880 GetNonDetachedWindowDomainsEnumeratorData nonDetachedEnumData
=
881 { &nonDetachedWindowDomains
, tldService
};
882 windowsById
->EnumerateRead(GetNonDetachedWindowDomainsEnumerator
,
883 &nonDetachedEnumData
);
885 // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
887 CheckForGhostWindowsEnumeratorData ghostEnumData
=
888 { &nonDetachedWindowDomains
, aOutGhostIDs
, tldService
,
889 GetGhostTimeout(), mLastCheckForGhostWindows
};
890 mDetachedWindows
.Enumerate(CheckForGhostWindowsEnumerator
,
894 NS_IMPL_ISUPPORTS(nsWindowMemoryReporter::GhostWindowsReporter
,
898 nsWindowMemoryReporter::GhostWindowsReporter::DistinguishedAmount()
900 nsTHashtable
<nsUint64HashKey
> ghostWindows
;
901 sWindowReporter
->CheckForGhostWindows(&ghostWindows
);
902 return ghostWindows
.Count();
906 nsWindowMemoryReporter::KillCheckTimer()
909 mCheckTimer
->Cancel();
910 mCheckTimer
= nullptr;
915 static PLDHashOperator
916 UnlinkGhostWindowsEnumerator(nsUint64HashKey
* aIDHashKey
, void *)
918 nsGlobalWindow::WindowByIdTable
* windowsById
=
919 nsGlobalWindow::GetWindowsTable();
921 return PL_DHASH_NEXT
;
924 nsRefPtr
<nsGlobalWindow
> window
= windowsById
->Get(aIDHashKey
->GetKey());
926 window
->RiskyUnlink();
929 return PL_DHASH_NEXT
;
933 nsWindowMemoryReporter::UnlinkGhostWindows()
935 if (!sWindowReporter
) {
939 nsGlobalWindow::WindowByIdTable
* windowsById
=
940 nsGlobalWindow::GetWindowsTable();
945 // Hold on to every window in memory so that window objects can't be
946 // destroyed while we're calling the UnlinkGhostWindows callback.
948 windowsById
->Enumerate(GetWindows
, &windows
);
950 // Get the IDs of all the "ghost" windows, and unlink them all.
951 nsTHashtable
<nsUint64HashKey
> ghostWindows
;
952 sWindowReporter
->CheckForGhostWindows(&ghostWindows
);
953 ghostWindows
.EnumerateEntries(UnlinkGhostWindowsEnumerator
, nullptr);