nsWindowMemoryReporter.cpp (36178B)
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/. */ 6 7 #include "nsWindowMemoryReporter.h" 8 9 #include "XPCJSMemoryReporter.h" 10 #include "js/MemoryMetrics.h" 11 #include "mozilla/ClearOnShutdown.h" 12 #include "mozilla/Preferences.h" 13 #include "mozilla/Services.h" 14 #include "mozilla/StaticPtr.h" 15 #include "mozilla/Try.h" 16 #include "mozilla/dom/BrowsingContext.h" 17 #include "mozilla/dom/Document.h" 18 #include "nsGlobalWindowInner.h" 19 #include "nsGlobalWindowOuter.h" 20 #include "nsNetCID.h" 21 #include "nsPrintfCString.h" 22 #include "nsQueryObject.h" 23 #include "nsServiceManagerUtils.h" 24 #include "nsStyleStructList.h" 25 #include "nsWindowSizes.h" 26 #include "nsXULPrototypeCache.h" 27 28 using namespace mozilla; 29 using namespace mozilla::dom; 30 31 StaticRefPtr<nsWindowMemoryReporter> sWindowReporter; 32 33 /** 34 * Don't trigger a ghost window check when a DOM window is detached if we've 35 * run it this recently. 36 */ 37 const int32_t kTimeBetweenChecks = 45; /* seconds */ 38 39 nsWindowMemoryReporter::nsWindowMemoryReporter() 40 : mLastCheckForGhostWindows(TimeStamp::NowLoRes()), 41 mCycleCollectorIsRunning(false), 42 mCheckTimerWaitingForCCEnd(false), 43 mGhostWindowCount(0) {} 44 45 nsWindowMemoryReporter::~nsWindowMemoryReporter() { KillCheckTimer(); } 46 47 NS_IMPL_ISUPPORTS(nsWindowMemoryReporter, nsIMemoryReporter, nsIObserver, 48 nsISupportsWeakReference) 49 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); 56 57 // Measure the inner window, if there is one. 58 nsPIDOMWindowInner* inner = aWindow->GetCurrentInnerWindow(); 59 if (inner) { 60 nsGlobalWindowInner::Cast(inner)->AddSizeOfIncludingThis(windowSizes); 61 } 62 63 windowSizes.addToTabSizes(aSizes); 64 65 BrowsingContext* bc = aWindow->GetBrowsingContext(); 66 if (!bc) { 67 return NS_OK; 68 } 69 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)); 74 } 75 } 76 return NS_OK; 77 } 78 79 static nsresult NonJSSizeOfTab(nsPIDOMWindowOuter* aWindow, size_t* aDomSize, 80 size_t* aStyleSize, size_t* aOtherSize) { 81 nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(aWindow); 82 83 nsTabSizes sizes; 84 nsresult rv = AddNonJSSizeOfWindowAndItsDescendents(window, &sizes); 85 NS_ENSURE_SUCCESS(rv, rv); 86 87 *aDomSize = sizes.mDom; 88 *aStyleSize = sizes.mStyle; 89 *aOtherSize = sizes.mOther; 90 return NS_OK; 91 } 92 93 /* static */ 94 void nsWindowMemoryReporter::Init() { 95 MOZ_ASSERT(!sWindowReporter); 96 sWindowReporter = new nsWindowMemoryReporter(); 97 ClearOnShutdown(&sWindowReporter); 98 RegisterStrongMemoryReporter(sWindowReporter); 99 RegisterNonJSSizeOfTab(NonJSSizeOfTab); 100 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); 109 } 110 111 RegisterGhostWindowsDistinguishedAmount(GhostWindowsDistinguishedAmount); 112 } 113 114 /* static */ 115 nsWindowMemoryReporter* nsWindowMemoryReporter::Get() { 116 return sWindowReporter; 117 } 118 119 static nsCString GetWindowURISpec(nsPIDOMWindowInner* aWindow) { 120 NS_ENSURE_TRUE(aWindow, ""_ns); 121 122 nsCOMPtr<Document> doc = aWindow->GetExtantDoc(); 123 if (doc) { 124 nsCOMPtr<nsIURI> uri; 125 uri = doc->GetDocumentURI(); 126 return uri->GetSpecOrDefault(); 127 } 128 nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrincipal = 129 do_QueryObject(aWindow); 130 NS_ENSURE_TRUE(scriptObjPrincipal, ""_ns); 131 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; 138 } 139 nsIPrincipal* principal = scriptObjPrincipal->GetPrincipal(); 140 if (!principal) { 141 return ""_ns; 142 } 143 nsCString spec; 144 principal->GetAsciiSpec(spec); 145 return spec; 146 } 147 148 static void AppendWindowURI(nsPIDOMWindowInner* aWindow, nsACString& aStr, 149 bool aAnonymize) { 150 nsCString spec = GetWindowURISpec(aWindow); 151 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; 157 } 158 if (aAnonymize && !nsGlobalWindowInner::Cast(aWindow)->IsChromeWindow()) { 159 aStr.AppendPrintf("<anonymized-%" PRIu64 ">", aWindow->WindowID()); 160 return; 161 } 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; 167 } 168 169 MOZ_DEFINE_MALLOC_SIZE_OF(WindowsMallocSizeOf) 170 171 // The key is the window ID. 172 using WindowPaths = nsTHashMap<nsUint64HashKey, nsCString>; 173 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; 181 } 182 183 nsAutoCString path(aBasePath); 184 path += aPathTail; 185 186 aHandleReport->Callback(""_ns, path, aKind, aUnits, aAmount, aDescription, 187 aData); 188 } 189 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); 197 } 198 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); 206 } 207 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; 216 217 REPORT_DOM_SIZE(aBasePath, "/dom/element-nodes", mDOMElementNodesSize, 218 "Memory used by the element nodes in a window's DOM."); 219 220 REPORT_DOM_SIZE(aBasePath, "/dom/text-nodes", mDOMTextNodesSize, 221 "Memory used by the text nodes in a window's DOM."); 222 223 REPORT_DOM_SIZE(aBasePath, "/dom/cdata-nodes", mDOMCDATANodesSize, 224 "Memory used by the CDATA nodes in a window's DOM."); 225 226 REPORT_DOM_SIZE(aBasePath, "/dom/comment-nodes", mDOMCommentNodesSize, 227 "Memory used by the comment nodes in a window's DOM."); 228 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."); 233 234 REPORT_DOM_SIZE(aBasePath, "/dom/performance/user-entries", 235 mDOMPerformanceUserEntries, 236 "Memory used for performance user entries."); 237 238 REPORT_DOM_SIZE(aBasePath, "/dom/performance/resource-entries", 239 mDOMPerformanceResourceEntries, 240 "Memory used for performance resource entries."); 241 242 REPORT_DOM_SIZE(aBasePath, "/dom/media-query-lists", mDOMMediaQueryLists, 243 "Memory used by MediaQueryList objects for the window's " 244 "document."); 245 246 REPORT_DOM_SIZE(aBasePath, "/dom/resize-observers", 247 mDOMResizeObserverControllerSize, 248 "Memory used for resize observers."); 249 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 254 } 255 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/"); 264 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(); 273 } 274 275 windowPath += "window-objects/"_ns; 276 277 if (top) { 278 windowPath += "top("_ns; 279 AppendWindowURI(top->GetCurrentInnerWindow(), windowPath, aAnonymize); 280 windowPath.AppendPrintf(", id=%" PRIu64 ")", top->WindowID()); 281 282 aTopWindowPaths->InsertOrUpdate(aWindow->WindowID(), windowPath); 283 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; 290 } 291 } 292 293 windowPath += "window("_ns; 294 AppendWindowURI(aWindow, windowPath, aAnonymize); 295 windowPath += ")"_ns; 296 297 // Use |windowPath|, but replace "explicit/" with "event-counts/". 298 nsCString censusWindowPath(windowPath); 299 censusWindowPath.ReplaceLiteral(0, strlen("explicit"), "event-counts"); 300 301 // Remember the path for later. 302 aWindowPaths->InsertOrUpdate(aWindow->WindowID(), windowPath); 303 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; 310 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); 316 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; 322 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); 328 329 ReportDOMSize(windowPath, aWindowTotalSizes->mDOMSizes, aHandleReport, aData, 330 windowSizes.mDOMSizes); 331 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); 338 339 REPORT_SIZE("/layout/style-sheets", mLayoutStyleSheetsSize, 340 "Memory used by document style sheets within a window."); 341 342 REPORT_SIZE("/layout/svg-mapped-declarations", mLayoutSvgMappedDeclarations, 343 "Memory used by mapped declarations of SVG elements"); 344 345 REPORT_SIZE("/layout/shadow-dom/style-sheets", 346 mLayoutShadowDomStyleSheetsSize, 347 "Memory used by Shadow DOM style sheets within a window."); 348 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."); 353 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."); 358 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."); 363 364 REPORT_SIZE("/layout/style-sets/stylist/rule-tree", 365 mLayoutStyleSetsStylistRuleTree, 366 "Memory used by rule trees within style sets within a window."); 367 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."); 372 373 REPORT_SIZE("/layout/style-sets/stylist/invalidation-map", 374 mLayoutStyleSetsStylistInvalidationMap, 375 "Memory used by invalidation maps within style sets " 376 "within a window."); 377 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."); 382 383 REPORT_SIZE("/layout/style-sets/stylist/other", mLayoutStyleSetsStylistOther, 384 "Memory used by other Stylist data within style sets " 385 "within a window."); 386 387 REPORT_SIZE("/layout/style-sets/other", mLayoutStyleSetsOther, 388 "Memory used by other parts of style sets within a window."); 389 390 REPORT_SIZE("/layout/element-data-objects", mLayoutElementDataObjects, 391 "Memory used for ElementData objects, but not the things " 392 "hanging off them."); 393 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."); 397 398 REPORT_SIZE("/layout/pres-contexts", mLayoutPresContextSize, 399 "Memory used for the PresContext in the PresShell's frame " 400 "within a window."); 401 402 REPORT_SIZE("/layout/frame-properties", mLayoutFramePropertiesSize, 403 "Memory used for frame properties attached to frames " 404 "within a window."); 405 406 REPORT_SIZE("/layout/computed-values/dom", mLayoutComputedValuesDom, 407 "Memory used by ComputedValues objects accessible from DOM " 408 "elements."); 409 410 REPORT_SIZE("/layout/computed-values/non-dom", mLayoutComputedValuesNonDom, 411 "Memory used by ComputedValues objects not accessible from DOM " 412 "elements."); 413 414 REPORT_SIZE("/layout/computed-values/visited", mLayoutComputedValuesVisited, 415 "Memory used by ComputedValues objects used for visited styles."); 416 417 REPORT_SIZE("/property-tables", mPropertyTablesSize, 418 "Memory used for the property tables within a window."); 419 420 REPORT_SIZE("/bindings", mBindingsSize, 421 "Memory used by bindings within a window."); 422 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."); 426 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."); 430 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(); 436 437 size_t presArenaSundriesSize = 0; 438 #define ARENA_OBJECT(name_, sundries_size_, prefix_) \ 439 { \ 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."); \ 447 } \ 448 aWindowTotalSizes->mArenaSizes.NS_ARENA_SIZES_FIELD(name_) += size; \ 449 } 450 #define PRES_ARENA_OBJECT(name_) \ 451 ARENA_OBJECT(name_, presArenaSundriesSize, "/layout/pres-arena/") 452 #include "nsPresArenaObjectList.h" 453 #undef PRES_ARENA_OBJECT 454 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."); 460 } 461 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 468 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."); 474 } 475 476 #undef ARENA_OBJECT 477 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(); 483 484 size_t styleSundriesSize = 0; 485 #define PROCESS_STYLE_STRUCT(name_) \ 486 { \ 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."); \ 494 } \ 495 aWindowTotalSizes->mStyleSizes.NS_STYLE_SIZES_FIELD(name_) += size; \ 496 } 497 FOR_EACH_STYLE_STRUCT(PROCESS_STYLE_STRUCT, PROCESS_STYLE_STRUCT) 498 #undef PROCESS_STYLE_STRUCT 499 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."); 505 } 506 507 #undef REPORT_SIZE 508 #undef REPORT_SUM_SIZE 509 #undef REPORT_COUNT 510 } 511 512 using WindowArray = nsTArray<RefPtr<nsGlobalWindowInner>>; 513 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); 520 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()); 524 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); 529 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; 535 } 536 537 nsAutoCString path; 538 path.AppendLiteral("ghost-windows/"); 539 AppendWindowURI(window, path, aAnonymize); 540 541 aHandleReport->Callback( 542 /* process = */ ""_ns, path, nsIMemoryReporter::KIND_OTHER, 543 nsIMemoryReporter::UNITS_COUNT, 544 /* amount = */ 1, 545 /* description = */ "A ghost window."_ns, aData); 546 } 547 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 560 561 WindowPaths windowPaths; 562 WindowPaths topWindowPaths; 563 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); 571 } 572 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); 577 578 nsXULPrototypeCache::CollectMemoryReports(aHandleReport, aData); 579 580 #define REPORT(_path, _amount, _desc) \ 581 aHandleReport->Callback(""_ns, nsLiteralCString(_path), KIND_OTHER, \ 582 UNITS_BYTES, _amount, nsLiteralCString(_desc), \ 583 aData); 584 585 REPORT("window-objects/dom/element-nodes", 586 windowTotalSizes.mDOMSizes.mDOMElementNodesSize, 587 "This is the sum of all windows' 'dom/element-nodes' numbers."); 588 589 REPORT("window-objects/dom/text-nodes", 590 windowTotalSizes.mDOMSizes.mDOMTextNodesSize, 591 "This is the sum of all windows' 'dom/text-nodes' numbers."); 592 593 REPORT("window-objects/dom/cdata-nodes", 594 windowTotalSizes.mDOMSizes.mDOMCDATANodesSize, 595 "This is the sum of all windows' 'dom/cdata-nodes' numbers."); 596 597 REPORT("window-objects/dom/comment-nodes", 598 windowTotalSizes.mDOMSizes.mDOMCommentNodesSize, 599 "This is the sum of all windows' 'dom/comment-nodes' numbers."); 600 601 REPORT("window-objects/dom/event-targets", 602 windowTotalSizes.mDOMSizes.mDOMEventTargetsSize, 603 "This is the sum of all windows' 'dom/event-targets' numbers."); 604 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."); 609 610 REPORT("window-objects/dom/other", windowTotalSizes.mDOMSizes.mDOMOtherSize, 611 "This is the sum of all windows' 'dom/other' numbers."); 612 613 REPORT("window-objects/layout/style-sheets", 614 windowTotalSizes.mLayoutStyleSheetsSize, 615 "This is the sum of all windows' 'layout/style-sheets' numbers."); 616 617 REPORT("window-objects/layout/pres-shell", 618 windowTotalSizes.mLayoutPresShellSize, 619 "This is the sum of all windows' 'layout/arenas' numbers."); 620 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."); 629 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."); 634 635 REPORT("window-objects/layout/text-runs", 636 windowTotalSizes.mLayoutTextRunsSize, 637 "This is the sum of all windows' 'layout/text-runs' numbers."); 638 639 REPORT("window-objects/layout/pres-contexts", 640 windowTotalSizes.mLayoutPresContextSize, 641 "This is the sum of all windows' 'layout/pres-contexts' numbers."); 642 643 REPORT("window-objects/layout/frame-properties", 644 windowTotalSizes.mLayoutFramePropertiesSize, 645 "This is the sum of all windows' 'layout/frame-properties' numbers."); 646 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."); 652 653 REPORT("window-objects/property-tables", windowTotalSizes.mPropertyTablesSize, 654 "This is the sum of all windows' 'property-tables' numbers."); 655 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 661 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."); 665 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 672 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."); 676 677 size_t styleTotal = 0; 678 #define ADD_TO_STYLE_TOTAL(name_) \ 679 styleTotal += windowTotalSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_); 680 FOR_EACH_STYLE_STRUCT(ADD_TO_STYLE_TOTAL, ADD_TO_STYLE_TOTAL) 681 #undef ADD_TO_STYLE_TOTAL 682 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."); 686 687 #undef REPORT 688 689 return NS_OK; 690 } 691 692 uint32_t nsWindowMemoryReporter::GetGhostTimeout() { 693 return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60); 694 } 695 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(); 705 } 706 mCycleCollectorIsRunning = true; 707 } else if (!strcmp(aTopic, "cycle-collector-end")) { 708 mCycleCollectorIsRunning = false; 709 if (mCheckTimerWaitingForCCEnd) { 710 mCheckTimerWaitingForCCEnd = false; 711 AsyncCheckForGhostWindows(); 712 } 713 } else { 714 MOZ_ASSERT(false); 715 } 716 717 return NS_OK; 718 } 719 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; 726 } 727 728 mDetachedWindows.InsertOrUpdate(weakWindow, TimeStamp()); 729 730 AsyncCheckForGhostWindows(); 731 } 732 733 // static 734 void nsWindowMemoryReporter::CheckTimerFired(nsITimer* aTimer, void* aData) { 735 if (sWindowReporter) { 736 MOZ_ASSERT(!sWindowReporter->mCycleCollectorIsRunning); 737 sWindowReporter->CheckForGhostWindows(); 738 } 739 } 740 741 void nsWindowMemoryReporter::AsyncCheckForGhostWindows() { 742 if (mCheckTimer) { 743 return; 744 } 745 746 if (mCycleCollectorIsRunning) { 747 mCheckTimerWaitingForCCEnd = true; 748 return; 749 } 750 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; 761 762 NS_NewTimerWithFuncCallback( 763 getter_AddRefs(mCheckTimer), CheckTimerFired, nullptr, timerDelay, 764 nsITimer::TYPE_ONE_SHOT, 765 "nsWindowMemoryReporter::AsyncCheckForGhostWindows_timer"_ns); 766 } 767 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. 772 // 773 // To effect this change, we'll backdate some of our timestamps. 774 775 TimeStamp minTimeStamp = 776 TimeStamp::Now() - TimeDuration::FromSeconds(GetGhostTimeout()); 777 778 for (auto iter = mDetachedWindows.Iter(); !iter.Done(); iter.Next()) { 779 TimeStamp& timeStamp = iter.Data(); 780 if (!timeStamp.IsNull() && timeStamp > minTimeStamp) { 781 timeStamp = minTimeStamp; 782 } 783 } 784 } 785 786 /** 787 * Iterate over mDetachedWindows and update it to reflect the current state of 788 * the world. In particular: 789 * 790 * - Remove weak refs to windows which no longer exist. 791 * 792 * - Remove references to windows which are no longer detached. 793 * 794 * - Reset the timestamp on detached windows which share a domain with a 795 * non-detached window (they no longer meet ghost criterion (2)). 796 * 797 * - If a window now meets ghost criterion (2) but didn't before, set its 798 * timestamp to now. 799 * 800 * Additionally, if aOutGhostIDs is not null, fill it with the window IDs of 801 * all ghost windows we found. 802 */ 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; 810 } 811 812 mLastCheckForGhostWindows = TimeStamp::NowLoRes(); 813 KillCheckTimer(); 814 815 nsTHashSet<BrowsingContextGroup*> nonDetachedBrowsingContextGroups; 816 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; 827 } 828 829 nonDetachedBrowsingContextGroups.Insert(window->GetBrowsingContextGroup()); 830 } 831 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; 845 } 846 847 nsPIDOMWindowInner* window = nsPIDOMWindowInner::From(iwindow); 848 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(); 855 } 856 857 if (top) { 858 // The window is no longer detached, so we no longer want to track it. 859 iter.Remove(); 860 continue; 861 } 862 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()); 883 } 884 } 885 } 886 } 887 } 888 889 /* static */ 890 int64_t nsWindowMemoryReporter::GhostWindowsDistinguishedAmount() { 891 return sWindowReporter->mGhostWindowCount; 892 } 893 894 void nsWindowMemoryReporter::KillCheckTimer() { 895 if (mCheckTimer) { 896 mCheckTimer->Cancel(); 897 mCheckTimer = nullptr; 898 } 899 } 900 901 #ifdef DEBUG 902 /* static */ 903 void nsWindowMemoryReporter::UnlinkGhostWindows() { 904 if (!sWindowReporter) { 905 return; 906 } 907 908 nsGlobalWindowInner::InnerWindowByIdTable* windowsById = 909 nsGlobalWindowInner::GetWindowsTable(); 910 if (!windowsById) { 911 return; 912 } 913 914 // Hold on to every window in memory so that window objects can't be 915 // destroyed while we're calling the UnlinkGhostWindows callback. 916 const auto windows = ToTArray<WindowArray>(windowsById->Values()); 917 918 // Get the IDs of all the "ghost" windows, and unlink them all. 919 nsTHashSet<uint64_t> ghostWindows; 920 sWindowReporter->CheckForGhostWindows(&ghostWindows); 921 for (const auto& key : ghostWindows) { 922 nsGlobalWindowInner::InnerWindowByIdTable* windowsById = 923 nsGlobalWindowInner::GetWindowsTable(); 924 if (!windowsById) { 925 continue; 926 } 927 928 RefPtr<nsGlobalWindowInner> window = windowsById->Get(key); 929 if (window) { 930 window->RiskyUnlink(); 931 } 932 } 933 } 934 #endif