1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/base/nsWindowMemoryReporter.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,946 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "amIAddonManager.h" 1.11 +#include "nsWindowMemoryReporter.h" 1.12 +#include "nsGlobalWindow.h" 1.13 +#include "nsIDocument.h" 1.14 +#include "nsIDOMWindowCollection.h" 1.15 +#include "nsIEffectiveTLDService.h" 1.16 +#include "mozilla/ClearOnShutdown.h" 1.17 +#include "mozilla/Preferences.h" 1.18 +#include "mozilla/Services.h" 1.19 +#include "mozilla/StaticPtr.h" 1.20 +#include "nsNetCID.h" 1.21 +#include "nsPrintfCString.h" 1.22 +#include "XPCJSMemoryReporter.h" 1.23 +#include "js/MemoryMetrics.h" 1.24 +#include "nsServiceManagerUtils.h" 1.25 + 1.26 +using namespace mozilla; 1.27 + 1.28 +StaticRefPtr<nsWindowMemoryReporter> sWindowReporter; 1.29 + 1.30 +/** 1.31 + * Don't trigger a ghost window check when a DOM window is detached if we've 1.32 + * run it this recently. 1.33 + */ 1.34 +const int32_t kTimeBetweenChecks = 45; /* seconds */ 1.35 + 1.36 +nsWindowMemoryReporter::nsWindowMemoryReporter() 1.37 + : mLastCheckForGhostWindows(TimeStamp::NowLoRes()), 1.38 + mCycleCollectorIsRunning(false), 1.39 + mCheckTimerWaitingForCCEnd(false) 1.40 +{ 1.41 +} 1.42 + 1.43 +nsWindowMemoryReporter::~nsWindowMemoryReporter() 1.44 +{ 1.45 + KillCheckTimer(); 1.46 +} 1.47 + 1.48 +NS_IMPL_ISUPPORTS(nsWindowMemoryReporter, nsIMemoryReporter, nsIObserver, 1.49 + nsISupportsWeakReference) 1.50 + 1.51 +static nsresult 1.52 +AddNonJSSizeOfWindowAndItsDescendents(nsGlobalWindow* aWindow, 1.53 + nsTabSizes* aSizes) 1.54 +{ 1.55 + // Measure the window. 1.56 + nsWindowSizes windowSizes(moz_malloc_size_of); 1.57 + aWindow->AddSizeOfIncludingThis(&windowSizes); 1.58 + windowSizes.addToTabSizes(aSizes); 1.59 + 1.60 + // Measure the inner window, if there is one. 1.61 + nsWindowSizes innerWindowSizes(moz_malloc_size_of); 1.62 + nsGlobalWindow* inner = aWindow->GetCurrentInnerWindowInternal(); 1.63 + if (inner) { 1.64 + inner->AddSizeOfIncludingThis(&innerWindowSizes); 1.65 + innerWindowSizes.addToTabSizes(aSizes); 1.66 + } 1.67 + 1.68 + nsCOMPtr<nsIDOMWindowCollection> frames; 1.69 + nsresult rv = aWindow->GetFrames(getter_AddRefs(frames)); 1.70 + NS_ENSURE_SUCCESS(rv, rv); 1.71 + 1.72 + uint32_t length; 1.73 + rv = frames->GetLength(&length); 1.74 + NS_ENSURE_SUCCESS(rv, rv); 1.75 + 1.76 + // Measure this window's descendents. 1.77 + for (uint32_t i = 0; i < length; i++) { 1.78 + nsCOMPtr<nsIDOMWindow> child; 1.79 + rv = frames->Item(i, getter_AddRefs(child)); 1.80 + NS_ENSURE_SUCCESS(rv, rv); 1.81 + NS_ENSURE_STATE(child); 1.82 + 1.83 + nsGlobalWindow* childWin = 1.84 + static_cast<nsGlobalWindow*>(static_cast<nsIDOMWindow *>(child.get())); 1.85 + 1.86 + rv = AddNonJSSizeOfWindowAndItsDescendents(childWin, aSizes); 1.87 + NS_ENSURE_SUCCESS(rv, rv); 1.88 + } 1.89 + return NS_OK; 1.90 +} 1.91 + 1.92 +static nsresult 1.93 +NonJSSizeOfTab(nsPIDOMWindow* aWindow, size_t* aDomSize, size_t* aStyleSize, size_t* aOtherSize) 1.94 +{ 1.95 + nsGlobalWindow* window = static_cast<nsGlobalWindow*>(aWindow); 1.96 + 1.97 + nsTabSizes sizes; 1.98 + nsresult rv = AddNonJSSizeOfWindowAndItsDescendents(window, &sizes); 1.99 + NS_ENSURE_SUCCESS(rv, rv); 1.100 + 1.101 + *aDomSize = sizes.mDom; 1.102 + *aStyleSize = sizes.mStyle; 1.103 + *aOtherSize = sizes.mOther; 1.104 + return NS_OK; 1.105 +} 1.106 + 1.107 +/* static */ void 1.108 +nsWindowMemoryReporter::Init() 1.109 +{ 1.110 + MOZ_ASSERT(!sWindowReporter); 1.111 + sWindowReporter = new nsWindowMemoryReporter(); 1.112 + ClearOnShutdown(&sWindowReporter); 1.113 + RegisterStrongMemoryReporter(sWindowReporter); 1.114 + RegisterNonJSSizeOfTab(NonJSSizeOfTab); 1.115 + 1.116 + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 1.117 + if (os) { 1.118 + // DOM_WINDOW_DESTROYED_TOPIC announces what we call window "detachment", 1.119 + // when a window's docshell is set to nullptr. 1.120 + os->AddObserver(sWindowReporter, DOM_WINDOW_DESTROYED_TOPIC, 1.121 + /* weakRef = */ true); 1.122 + os->AddObserver(sWindowReporter, "after-minimize-memory-usage", 1.123 + /* weakRef = */ true); 1.124 + os->AddObserver(sWindowReporter, "cycle-collector-begin", 1.125 + /* weakRef = */ true); 1.126 + os->AddObserver(sWindowReporter, "cycle-collector-end", 1.127 + /* weakRef = */ true); 1.128 + } 1.129 + 1.130 + RegisterStrongMemoryReporter(new GhostWindowsReporter()); 1.131 + RegisterGhostWindowsDistinguishedAmount(GhostWindowsReporter::DistinguishedAmount); 1.132 +} 1.133 + 1.134 +static already_AddRefed<nsIURI> 1.135 +GetWindowURI(nsIDOMWindow *aWindow) 1.136 +{ 1.137 + nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(aWindow); 1.138 + NS_ENSURE_TRUE(pWindow, nullptr); 1.139 + 1.140 + nsCOMPtr<nsIDocument> doc = pWindow->GetExtantDoc(); 1.141 + nsCOMPtr<nsIURI> uri; 1.142 + 1.143 + if (doc) { 1.144 + uri = doc->GetDocumentURI(); 1.145 + } 1.146 + 1.147 + if (!uri) { 1.148 + nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrincipal = 1.149 + do_QueryInterface(aWindow); 1.150 + NS_ENSURE_TRUE(scriptObjPrincipal, nullptr); 1.151 + 1.152 + // GetPrincipal() will print a warning if the window does not have an outer 1.153 + // window, so check here for an outer window first. This code is 1.154 + // functionally correct if we leave out the GetOuterWindow() check, but we 1.155 + // end up printing a lot of warnings during debug mochitests. 1.156 + if (pWindow->GetOuterWindow()) { 1.157 + nsIPrincipal* principal = scriptObjPrincipal->GetPrincipal(); 1.158 + if (principal) { 1.159 + principal->GetURI(getter_AddRefs(uri)); 1.160 + } 1.161 + } 1.162 + } 1.163 + 1.164 + return uri.forget(); 1.165 +} 1.166 + 1.167 +static void 1.168 +AppendWindowURI(nsGlobalWindow *aWindow, nsACString& aStr) 1.169 +{ 1.170 + nsCOMPtr<nsIURI> uri = GetWindowURI(aWindow); 1.171 + 1.172 + if (uri) { 1.173 + nsCString spec; 1.174 + uri->GetSpec(spec); 1.175 + 1.176 + // A hack: replace forward slashes with '\\' so they aren't 1.177 + // treated as path separators. Users of the reporters 1.178 + // (such as about:memory) have to undo this change. 1.179 + spec.ReplaceChar('/', '\\'); 1.180 + 1.181 + aStr += spec; 1.182 + } else { 1.183 + // If we're unable to find a URI, we're dealing with a chrome window with 1.184 + // no document in it (or somesuch), so we call this a "system window". 1.185 + aStr += NS_LITERAL_CSTRING("[system]"); 1.186 + } 1.187 +} 1.188 + 1.189 +MOZ_DEFINE_MALLOC_SIZE_OF(WindowsMallocSizeOf) 1.190 + 1.191 +// The key is the window ID. 1.192 +typedef nsDataHashtable<nsUint64HashKey, nsCString> WindowPaths; 1.193 + 1.194 +static nsresult 1.195 +ReportAmount(const nsCString& aBasePath, const char* aPathTail, 1.196 + size_t aAmount, const nsCString& aDescription, 1.197 + uint32_t aKind, uint32_t aUnits, 1.198 + nsIMemoryReporterCallback* aCb, 1.199 + nsISupports* aClosure) 1.200 +{ 1.201 + if (aAmount == 0) { 1.202 + return NS_OK; 1.203 + } 1.204 + 1.205 + nsAutoCString path(aBasePath); 1.206 + path += aPathTail; 1.207 + 1.208 + return aCb->Callback(EmptyCString(), path, aKind, aUnits, 1.209 + aAmount, aDescription, aClosure); 1.210 +} 1.211 + 1.212 +static nsresult 1.213 +ReportSize(const nsCString& aBasePath, const char* aPathTail, 1.214 + size_t aAmount, const nsCString& aDescription, 1.215 + nsIMemoryReporterCallback* aCb, 1.216 + nsISupports* aClosure) 1.217 +{ 1.218 + return ReportAmount(aBasePath, aPathTail, aAmount, aDescription, 1.219 + nsIMemoryReporter::KIND_HEAP, 1.220 + nsIMemoryReporter::UNITS_BYTES, aCb, aClosure); 1.221 +} 1.222 + 1.223 +static nsresult 1.224 +ReportCount(const nsCString& aBasePath, const char* aPathTail, 1.225 + size_t aAmount, const nsCString& aDescription, 1.226 + nsIMemoryReporterCallback* aCb, 1.227 + nsISupports* aClosure) 1.228 +{ 1.229 + return ReportAmount(aBasePath, aPathTail, aAmount, aDescription, 1.230 + nsIMemoryReporter::KIND_OTHER, 1.231 + nsIMemoryReporter::UNITS_COUNT, aCb, aClosure); 1.232 +} 1.233 + 1.234 +static nsresult 1.235 +CollectWindowReports(nsGlobalWindow *aWindow, 1.236 + amIAddonManager *addonManager, 1.237 + nsWindowSizes *aWindowTotalSizes, 1.238 + nsTHashtable<nsUint64HashKey> *aGhostWindowIDs, 1.239 + WindowPaths *aWindowPaths, 1.240 + WindowPaths *aTopWindowPaths, 1.241 + nsIMemoryReporterCallback *aCb, 1.242 + nsISupports *aClosure) 1.243 +{ 1.244 + nsAutoCString windowPath("explicit/"); 1.245 + 1.246 + // Avoid calling aWindow->GetTop() if there's no outer window. It will work 1.247 + // just fine, but will spew a lot of warnings. 1.248 + nsGlobalWindow *top = nullptr; 1.249 + nsCOMPtr<nsIURI> location; 1.250 + if (aWindow->GetOuterWindow()) { 1.251 + // Our window should have a null top iff it has a null docshell. 1.252 + MOZ_ASSERT(!!aWindow->GetTop() == !!aWindow->GetDocShell()); 1.253 + top = aWindow->GetTop(); 1.254 + if (top) { 1.255 + location = GetWindowURI(top); 1.256 + } 1.257 + } 1.258 + if (!location) { 1.259 + location = GetWindowURI(aWindow); 1.260 + } 1.261 + 1.262 + if (addonManager && location) { 1.263 + bool ok; 1.264 + nsAutoCString id; 1.265 + if (NS_SUCCEEDED(addonManager->MapURIToAddonID(location, id, &ok)) && ok) { 1.266 + windowPath += NS_LITERAL_CSTRING("add-ons/") + id + 1.267 + NS_LITERAL_CSTRING("/"); 1.268 + } 1.269 + } 1.270 + 1.271 + windowPath += NS_LITERAL_CSTRING("window-objects/"); 1.272 + 1.273 + if (top) { 1.274 + windowPath += NS_LITERAL_CSTRING("top("); 1.275 + AppendWindowURI(top, windowPath); 1.276 + windowPath += NS_LITERAL_CSTRING(", id="); 1.277 + windowPath.AppendInt(top->WindowID()); 1.278 + windowPath += NS_LITERAL_CSTRING(")"); 1.279 + 1.280 + aTopWindowPaths->Put(aWindow->WindowID(), windowPath); 1.281 + 1.282 + windowPath += aWindow->IsFrozen() ? NS_LITERAL_CSTRING("/cached/") 1.283 + : NS_LITERAL_CSTRING("/active/"); 1.284 + } else { 1.285 + if (aGhostWindowIDs->Contains(aWindow->WindowID())) { 1.286 + windowPath += NS_LITERAL_CSTRING("top(none)/ghost/"); 1.287 + } else { 1.288 + windowPath += NS_LITERAL_CSTRING("top(none)/detached/"); 1.289 + } 1.290 + } 1.291 + 1.292 + windowPath += NS_LITERAL_CSTRING("window("); 1.293 + AppendWindowURI(aWindow, windowPath); 1.294 + windowPath += NS_LITERAL_CSTRING(")"); 1.295 + 1.296 + // Use |windowPath|, but replace "explicit/" with "event-counts/". 1.297 + nsCString censusWindowPath(windowPath); 1.298 + censusWindowPath.Replace(0, strlen("explicit"), "event-counts"); 1.299 + 1.300 + // Remember the path for later. 1.301 + aWindowPaths->Put(aWindow->WindowID(), windowPath); 1.302 + 1.303 +#define REPORT_SIZE(_pathTail, _amount, _desc) \ 1.304 + do { \ 1.305 + nsresult rv = ReportSize(windowPath, _pathTail, _amount, \ 1.306 + NS_LITERAL_CSTRING(_desc), aCb, aClosure); \ 1.307 + NS_ENSURE_SUCCESS(rv, rv); \ 1.308 + } while (0) 1.309 + 1.310 +#define REPORT_COUNT(_pathTail, _amount, _desc) \ 1.311 + do { \ 1.312 + nsresult rv = ReportCount(censusWindowPath, _pathTail, _amount, \ 1.313 + NS_LITERAL_CSTRING(_desc), aCb, aClosure); \ 1.314 + NS_ENSURE_SUCCESS(rv, rv); \ 1.315 + } while (0) 1.316 + 1.317 + nsWindowSizes windowSizes(WindowsMallocSizeOf); 1.318 + aWindow->AddSizeOfIncludingThis(&windowSizes); 1.319 + 1.320 + REPORT_SIZE("/dom/element-nodes", windowSizes.mDOMElementNodesSize, 1.321 + "Memory used by the element nodes in a window's DOM."); 1.322 + aWindowTotalSizes->mDOMElementNodesSize += windowSizes.mDOMElementNodesSize; 1.323 + 1.324 + REPORT_SIZE("/dom/text-nodes", windowSizes.mDOMTextNodesSize, 1.325 + "Memory used by the text nodes in a window's DOM."); 1.326 + aWindowTotalSizes->mDOMTextNodesSize += windowSizes.mDOMTextNodesSize; 1.327 + 1.328 + REPORT_SIZE("/dom/cdata-nodes", windowSizes.mDOMCDATANodesSize, 1.329 + "Memory used by the CDATA nodes in a window's DOM."); 1.330 + aWindowTotalSizes->mDOMCDATANodesSize += windowSizes.mDOMCDATANodesSize; 1.331 + 1.332 + REPORT_SIZE("/dom/comment-nodes", windowSizes.mDOMCommentNodesSize, 1.333 + "Memory used by the comment nodes in a window's DOM."); 1.334 + aWindowTotalSizes->mDOMCommentNodesSize += windowSizes.mDOMCommentNodesSize; 1.335 + 1.336 + REPORT_SIZE("/dom/event-targets", windowSizes.mDOMEventTargetsSize, 1.337 + "Memory used by the event targets table in a window's DOM, and " 1.338 + "the objects it points to, which include XHRs."); 1.339 + aWindowTotalSizes->mDOMEventTargetsSize += windowSizes.mDOMEventTargetsSize; 1.340 + 1.341 + REPORT_COUNT("/dom/event-targets", windowSizes.mDOMEventTargetsCount, 1.342 + "Number of non-node event targets in the event targets table " 1.343 + "in a window's DOM, such as XHRs."); 1.344 + aWindowTotalSizes->mDOMEventTargetsCount += 1.345 + windowSizes.mDOMEventTargetsCount; 1.346 + 1.347 + REPORT_COUNT("/dom/event-listeners", windowSizes.mDOMEventListenersCount, 1.348 + "Number of event listeners in a window, including event " 1.349 + "listeners on nodes and other event targets."); 1.350 + aWindowTotalSizes->mDOMEventListenersCount += 1.351 + windowSizes.mDOMEventListenersCount; 1.352 + 1.353 + REPORT_SIZE("/dom/other", windowSizes.mDOMOtherSize, 1.354 + "Memory used by a window's DOM that isn't measured by the " 1.355 + "other 'dom/' numbers."); 1.356 + aWindowTotalSizes->mDOMOtherSize += windowSizes.mDOMOtherSize; 1.357 + 1.358 + REPORT_SIZE("/property-tables", 1.359 + windowSizes.mPropertyTablesSize, 1.360 + "Memory used for the property tables within a window."); 1.361 + aWindowTotalSizes->mPropertyTablesSize += windowSizes.mPropertyTablesSize; 1.362 + 1.363 + REPORT_SIZE("/style-sheets", windowSizes.mStyleSheetsSize, 1.364 + "Memory used by style sheets within a window."); 1.365 + aWindowTotalSizes->mStyleSheetsSize += windowSizes.mStyleSheetsSize; 1.366 + 1.367 + REPORT_SIZE("/layout/pres-shell", windowSizes.mLayoutPresShellSize, 1.368 + "Memory used by layout's PresShell, along with any structures " 1.369 + "allocated in its arena and not measured elsewhere, " 1.370 + "within a window."); 1.371 + aWindowTotalSizes->mLayoutPresShellSize += windowSizes.mLayoutPresShellSize; 1.372 + 1.373 + REPORT_SIZE("/layout/line-boxes", windowSizes.mArenaStats.mLineBoxes, 1.374 + "Memory used by line boxes within a window."); 1.375 + aWindowTotalSizes->mArenaStats.mLineBoxes 1.376 + += windowSizes.mArenaStats.mLineBoxes; 1.377 + 1.378 + REPORT_SIZE("/layout/rule-nodes", windowSizes.mArenaStats.mRuleNodes, 1.379 + "Memory used by CSS rule nodes within a window."); 1.380 + aWindowTotalSizes->mArenaStats.mRuleNodes 1.381 + += windowSizes.mArenaStats.mRuleNodes; 1.382 + 1.383 + REPORT_SIZE("/layout/style-contexts", windowSizes.mArenaStats.mStyleContexts, 1.384 + "Memory used by style contexts within a window."); 1.385 + aWindowTotalSizes->mArenaStats.mStyleContexts 1.386 + += windowSizes.mArenaStats.mStyleContexts; 1.387 + 1.388 + REPORT_SIZE("/layout/style-sets", windowSizes.mLayoutStyleSetsSize, 1.389 + "Memory used by style sets within a window."); 1.390 + aWindowTotalSizes->mLayoutStyleSetsSize += windowSizes.mLayoutStyleSetsSize; 1.391 + 1.392 + REPORT_SIZE("/layout/text-runs", windowSizes.mLayoutTextRunsSize, 1.393 + "Memory used for text-runs (glyph layout) in the PresShell's " 1.394 + "frame tree, within a window."); 1.395 + aWindowTotalSizes->mLayoutTextRunsSize += windowSizes.mLayoutTextRunsSize; 1.396 + 1.397 + REPORT_SIZE("/layout/pres-contexts", windowSizes.mLayoutPresContextSize, 1.398 + "Memory used for the PresContext in the PresShell's frame " 1.399 + "within a window."); 1.400 + aWindowTotalSizes->mLayoutPresContextSize += 1.401 + windowSizes.mLayoutPresContextSize; 1.402 + 1.403 + // There are many different kinds of frames, but it is very likely 1.404 + // that only a few matter. Implement a cutoff so we don't bloat 1.405 + // about:memory with many uninteresting entries. 1.406 + const size_t FRAME_SUNDRIES_THRESHOLD = 1.407 + js::MemoryReportingSundriesThreshold(); 1.408 + 1.409 + size_t frameSundriesSize = 0; 1.410 +#define FRAME_ID(classname) \ 1.411 + { \ 1.412 + size_t frameSize \ 1.413 + = windowSizes.mArenaStats.FRAME_ID_STAT_FIELD(classname); \ 1.414 + if (frameSize < FRAME_SUNDRIES_THRESHOLD) { \ 1.415 + frameSundriesSize += frameSize; \ 1.416 + } else { \ 1.417 + REPORT_SIZE("/layout/frames/" # classname, frameSize, \ 1.418 + "Memory used by frames of " \ 1.419 + "type " #classname " within a window."); \ 1.420 + } \ 1.421 + aWindowTotalSizes->mArenaStats.FRAME_ID_STAT_FIELD(classname) \ 1.422 + += frameSize; \ 1.423 + } 1.424 +#include "nsFrameIdList.h" 1.425 +#undef FRAME_ID 1.426 + 1.427 + if (frameSundriesSize > 0) { 1.428 + REPORT_SIZE("/layout/frames/sundries", frameSundriesSize, 1.429 + "The sum of all memory used by frames which were too small " 1.430 + "to be shown individually."); 1.431 + } 1.432 + 1.433 +#undef REPORT_SIZE 1.434 +#undef REPORT_COUNT 1.435 + 1.436 + return NS_OK; 1.437 +} 1.438 + 1.439 +typedef nsTArray< nsRefPtr<nsGlobalWindow> > WindowArray; 1.440 + 1.441 +static 1.442 +PLDHashOperator 1.443 +GetWindows(const uint64_t& aId, nsGlobalWindow*& aWindow, void* aClosure) 1.444 +{ 1.445 + ((WindowArray *)aClosure)->AppendElement(aWindow); 1.446 + 1.447 + return PL_DHASH_NEXT; 1.448 +} 1.449 + 1.450 +struct ReportGhostWindowsEnumeratorData 1.451 +{ 1.452 + nsIMemoryReporterCallback* callback; 1.453 + nsISupports* closure; 1.454 + nsresult rv; 1.455 +}; 1.456 + 1.457 +static PLDHashOperator 1.458 +ReportGhostWindowsEnumerator(nsUint64HashKey* aIDHashKey, void* aClosure) 1.459 +{ 1.460 + ReportGhostWindowsEnumeratorData *data = 1.461 + static_cast<ReportGhostWindowsEnumeratorData*>(aClosure); 1.462 + 1.463 + nsGlobalWindow::WindowByIdTable* windowsById = 1.464 + nsGlobalWindow::GetWindowsTable(); 1.465 + if (!windowsById) { 1.466 + NS_WARNING("Couldn't get window-by-id hashtable?"); 1.467 + return PL_DHASH_NEXT; 1.468 + } 1.469 + 1.470 + nsGlobalWindow* window = windowsById->Get(aIDHashKey->GetKey()); 1.471 + if (!window) { 1.472 + NS_WARNING("Could not look up window?"); 1.473 + return PL_DHASH_NEXT; 1.474 + } 1.475 + 1.476 + nsAutoCString path; 1.477 + path.AppendLiteral("ghost-windows/"); 1.478 + AppendWindowURI(window, path); 1.479 + 1.480 + nsresult rv = data->callback->Callback( 1.481 + /* process = */ EmptyCString(), 1.482 + path, 1.483 + nsIMemoryReporter::KIND_OTHER, 1.484 + nsIMemoryReporter::UNITS_COUNT, 1.485 + /* amount = */ 1, 1.486 + /* description = */ NS_LITERAL_CSTRING("A ghost window."), 1.487 + data->closure); 1.488 + 1.489 + if (NS_FAILED(rv) && NS_SUCCEEDED(data->rv)) { 1.490 + data->rv = rv; 1.491 + } 1.492 + 1.493 + return PL_DHASH_NEXT; 1.494 +} 1.495 + 1.496 +NS_IMETHODIMP 1.497 +nsWindowMemoryReporter::CollectReports(nsIMemoryReporterCallback* aCb, 1.498 + nsISupports* aClosure) 1.499 +{ 1.500 + nsGlobalWindow::WindowByIdTable* windowsById = 1.501 + nsGlobalWindow::GetWindowsTable(); 1.502 + NS_ENSURE_TRUE(windowsById, NS_OK); 1.503 + 1.504 + // Hold on to every window in memory so that window objects can't be 1.505 + // destroyed while we're calling the memory reporter callback. 1.506 + WindowArray windows; 1.507 + windowsById->Enumerate(GetWindows, &windows); 1.508 + 1.509 + // Get the IDs of all the "ghost" windows, and call aCb->Callback() for each 1.510 + // one. 1.511 + nsTHashtable<nsUint64HashKey> ghostWindows; 1.512 + CheckForGhostWindows(&ghostWindows); 1.513 + ReportGhostWindowsEnumeratorData reportGhostWindowsEnumData = 1.514 + { aCb, aClosure, NS_OK }; 1.515 + ghostWindows.EnumerateEntries(ReportGhostWindowsEnumerator, 1.516 + &reportGhostWindowsEnumData); 1.517 + nsresult rv = reportGhostWindowsEnumData.rv; 1.518 + NS_ENSURE_SUCCESS(rv, rv); 1.519 + 1.520 + WindowPaths windowPaths; 1.521 + WindowPaths topWindowPaths; 1.522 + 1.523 + // Collect window memory usage. 1.524 + nsWindowSizes windowTotalSizes(nullptr); 1.525 + nsCOMPtr<amIAddonManager> addonManager; 1.526 + if (XRE_GetProcessType() == GeckoProcessType_Default) { 1.527 + // Only try to access the service from the main process. 1.528 + addonManager = do_GetService("@mozilla.org/addons/integration;1"); 1.529 + } 1.530 + for (uint32_t i = 0; i < windows.Length(); i++) { 1.531 + rv = CollectWindowReports(windows[i], addonManager, 1.532 + &windowTotalSizes, &ghostWindows, 1.533 + &windowPaths, &topWindowPaths, aCb, 1.534 + aClosure); 1.535 + NS_ENSURE_SUCCESS(rv, rv); 1.536 + } 1.537 + 1.538 + // Report JS memory usage. We do this from here because the JS memory 1.539 + // reporter needs to be passed |windowPaths|. 1.540 + rv = xpc::JSReporter::CollectReports(&windowPaths, &topWindowPaths, 1.541 + aCb, aClosure); 1.542 + NS_ENSURE_SUCCESS(rv, rv); 1.543 + 1.544 +#define REPORT(_path, _amount, _desc) \ 1.545 + do { \ 1.546 + nsresult rv; \ 1.547 + rv = aCb->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \ 1.548 + KIND_OTHER, UNITS_BYTES, _amount, \ 1.549 + NS_LITERAL_CSTRING(_desc), aClosure); \ 1.550 + NS_ENSURE_SUCCESS(rv, rv); \ 1.551 + } while (0) 1.552 + 1.553 + REPORT("window-objects/dom/element-nodes", windowTotalSizes.mDOMElementNodesSize, 1.554 + "This is the sum of all windows' 'dom/element-nodes' numbers."); 1.555 + 1.556 + REPORT("window-objects/dom/text-nodes", windowTotalSizes.mDOMTextNodesSize, 1.557 + "This is the sum of all windows' 'dom/text-nodes' numbers."); 1.558 + 1.559 + REPORT("window-objects/dom/cdata-nodes", windowTotalSizes.mDOMCDATANodesSize, 1.560 + "This is the sum of all windows' 'dom/cdata-nodes' numbers."); 1.561 + 1.562 + REPORT("window-objects/dom/comment-nodes", windowTotalSizes.mDOMCommentNodesSize, 1.563 + "This is the sum of all windows' 'dom/comment-nodes' numbers."); 1.564 + 1.565 + REPORT("window-objects/dom/event-targets", windowTotalSizes.mDOMEventTargetsSize, 1.566 + "This is the sum of all windows' 'dom/event-targets' numbers."); 1.567 + 1.568 + REPORT("window-objects/dom/other", windowTotalSizes.mDOMOtherSize, 1.569 + "This is the sum of all windows' 'dom/other' numbers."); 1.570 + 1.571 + REPORT("window-objects/property-tables", 1.572 + windowTotalSizes.mPropertyTablesSize, 1.573 + "This is the sum of all windows' 'property-tables' numbers."); 1.574 + 1.575 + REPORT("window-objects/style-sheets", windowTotalSizes.mStyleSheetsSize, 1.576 + "This is the sum of all windows' 'style-sheets' numbers."); 1.577 + 1.578 + REPORT("window-objects/layout/pres-shell", windowTotalSizes.mLayoutPresShellSize, 1.579 + "This is the sum of all windows' 'layout/arenas' numbers."); 1.580 + 1.581 + REPORT("window-objects/layout/line-boxes", 1.582 + windowTotalSizes.mArenaStats.mLineBoxes, 1.583 + "This is the sum of all windows' 'layout/line-boxes' numbers."); 1.584 + 1.585 + REPORT("window-objects/layout/rule-nodes", 1.586 + windowTotalSizes.mArenaStats.mRuleNodes, 1.587 + "This is the sum of all windows' 'layout/rule-nodes' numbers."); 1.588 + 1.589 + REPORT("window-objects/layout/style-contexts", 1.590 + windowTotalSizes.mArenaStats.mStyleContexts, 1.591 + "This is the sum of all windows' 'layout/style-contexts' numbers."); 1.592 + 1.593 + REPORT("window-objects/layout/style-sets", windowTotalSizes.mLayoutStyleSetsSize, 1.594 + "This is the sum of all windows' 'layout/style-sets' numbers."); 1.595 + 1.596 + REPORT("window-objects/layout/text-runs", windowTotalSizes.mLayoutTextRunsSize, 1.597 + "This is the sum of all windows' 'layout/text-runs' numbers."); 1.598 + 1.599 + REPORT("window-objects/layout/pres-contexts", windowTotalSizes.mLayoutPresContextSize, 1.600 + "This is the sum of all windows' 'layout/pres-contexts' numbers."); 1.601 + 1.602 + size_t frameTotal = 0; 1.603 +#define FRAME_ID(classname) \ 1.604 + frameTotal += windowTotalSizes.mArenaStats.FRAME_ID_STAT_FIELD(classname); 1.605 +#include "nsFrameIdList.h" 1.606 +#undef FRAME_ID 1.607 + 1.608 + REPORT("window-objects/layout/frames", frameTotal, 1.609 + "Memory used for layout frames within windows. " 1.610 + "This is the sum of all windows' 'layout/frames/' numbers."); 1.611 + 1.612 +#undef REPORT 1.613 + 1.614 + return NS_OK; 1.615 +} 1.616 + 1.617 +uint32_t 1.618 +nsWindowMemoryReporter::GetGhostTimeout() 1.619 +{ 1.620 + return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60); 1.621 +} 1.622 + 1.623 +NS_IMETHODIMP 1.624 +nsWindowMemoryReporter::Observe(nsISupports *aSubject, const char *aTopic, 1.625 + const char16_t *aData) 1.626 +{ 1.627 + if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC)) { 1.628 + ObserveDOMWindowDetached(aSubject); 1.629 + } else if (!strcmp(aTopic, "after-minimize-memory-usage")) { 1.630 + ObserveAfterMinimizeMemoryUsage(); 1.631 + } else if (!strcmp(aTopic, "cycle-collector-begin")) { 1.632 + if (mCheckTimer) { 1.633 + mCheckTimerWaitingForCCEnd = true; 1.634 + KillCheckTimer(); 1.635 + } 1.636 + mCycleCollectorIsRunning = true; 1.637 + } else if (!strcmp(aTopic, "cycle-collector-end")) { 1.638 + mCycleCollectorIsRunning = false; 1.639 + if (mCheckTimerWaitingForCCEnd) { 1.640 + mCheckTimerWaitingForCCEnd = false; 1.641 + AsyncCheckForGhostWindows(); 1.642 + } 1.643 + } else { 1.644 + MOZ_ASSERT(false); 1.645 + } 1.646 + 1.647 + return NS_OK; 1.648 +} 1.649 + 1.650 +void 1.651 +nsWindowMemoryReporter::ObserveDOMWindowDetached(nsISupports* aWindow) 1.652 +{ 1.653 + nsWeakPtr weakWindow = do_GetWeakReference(aWindow); 1.654 + if (!weakWindow) { 1.655 + NS_WARNING("Couldn't take weak reference to a window?"); 1.656 + return; 1.657 + } 1.658 + 1.659 + mDetachedWindows.Put(weakWindow, TimeStamp()); 1.660 + 1.661 + AsyncCheckForGhostWindows(); 1.662 +} 1.663 + 1.664 +// static 1.665 +void 1.666 +nsWindowMemoryReporter::CheckTimerFired(nsITimer* aTimer, void* aClosure) 1.667 +{ 1.668 + if (sWindowReporter) { 1.669 + MOZ_ASSERT(!sWindowReporter->mCycleCollectorIsRunning); 1.670 + sWindowReporter->CheckForGhostWindows(); 1.671 + } 1.672 +} 1.673 + 1.674 +void 1.675 +nsWindowMemoryReporter::AsyncCheckForGhostWindows() 1.676 +{ 1.677 + if (mCheckTimer) { 1.678 + return; 1.679 + } 1.680 + 1.681 + if (mCycleCollectorIsRunning) { 1.682 + mCheckTimerWaitingForCCEnd = true; 1.683 + return; 1.684 + } 1.685 + 1.686 + // If more than kTimeBetweenChecks seconds have elapsed since the last check, 1.687 + // timerDelay is 0. Otherwise, it is kTimeBetweenChecks, reduced by the time 1.688 + // since the last check. Reducing the delay by the time since the last check 1.689 + // prevents the timer from being completely starved if it is repeatedly killed 1.690 + // and restarted. 1.691 + int32_t timeSinceLastCheck = (TimeStamp::NowLoRes() - mLastCheckForGhostWindows).ToSeconds(); 1.692 + int32_t timerDelay = (kTimeBetweenChecks - std::min(timeSinceLastCheck, kTimeBetweenChecks)) * PR_MSEC_PER_SEC; 1.693 + 1.694 + CallCreateInstance<nsITimer>("@mozilla.org/timer;1", getter_AddRefs(mCheckTimer)); 1.695 + 1.696 + if (mCheckTimer) { 1.697 + mCheckTimer->InitWithFuncCallback(CheckTimerFired, nullptr, 1.698 + timerDelay, nsITimer::TYPE_ONE_SHOT); 1.699 + } 1.700 +} 1.701 + 1.702 +static PLDHashOperator 1.703 +BackdateTimeStampsEnumerator(nsISupports *aKey, TimeStamp &aTimeStamp, 1.704 + void* aClosure) 1.705 +{ 1.706 + TimeStamp *minTimeStamp = static_cast<TimeStamp*>(aClosure); 1.707 + 1.708 + if (!aTimeStamp.IsNull() && aTimeStamp > *minTimeStamp) { 1.709 + aTimeStamp = *minTimeStamp; 1.710 + } 1.711 + 1.712 + return PL_DHASH_NEXT; 1.713 +} 1.714 + 1.715 +void 1.716 +nsWindowMemoryReporter::ObserveAfterMinimizeMemoryUsage() 1.717 +{ 1.718 + // Someone claims they've done enough GC/CCs so that all eligible windows 1.719 + // have been free'd. So we deem that any windows which satisfy ghost 1.720 + // criteria (1) and (2) now satisfy criterion (3) as well. 1.721 + // 1.722 + // To effect this change, we'll backdate some of our timestamps. 1.723 + 1.724 + TimeStamp minTimeStamp = TimeStamp::Now() - 1.725 + TimeDuration::FromSeconds(GetGhostTimeout()); 1.726 + 1.727 + mDetachedWindows.Enumerate(BackdateTimeStampsEnumerator, 1.728 + &minTimeStamp); 1.729 +} 1.730 + 1.731 +struct CheckForGhostWindowsEnumeratorData 1.732 +{ 1.733 + nsTHashtable<nsCStringHashKey> *nonDetachedDomains; 1.734 + nsTHashtable<nsUint64HashKey> *ghostWindowIDs; 1.735 + nsIEffectiveTLDService *tldService; 1.736 + uint32_t ghostTimeout; 1.737 + TimeStamp now; 1.738 +}; 1.739 + 1.740 +static PLDHashOperator 1.741 +CheckForGhostWindowsEnumerator(nsISupports *aKey, TimeStamp& aTimeStamp, 1.742 + void* aClosure) 1.743 +{ 1.744 + CheckForGhostWindowsEnumeratorData *data = 1.745 + static_cast<CheckForGhostWindowsEnumeratorData*>(aClosure); 1.746 + 1.747 + nsWeakPtr weakKey = do_QueryInterface(aKey); 1.748 + nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(weakKey); 1.749 + if (!window) { 1.750 + // The window object has been destroyed. Stop tracking its weak ref in our 1.751 + // hashtable. 1.752 + return PL_DHASH_REMOVE; 1.753 + } 1.754 + 1.755 + // Avoid calling GetTop() if we have no outer window. Nothing will break if 1.756 + // we do, but it will spew debug output, which can cause our test logs to 1.757 + // overflow. 1.758 + nsCOMPtr<nsIDOMWindow> top; 1.759 + if (window->GetOuterWindow()) { 1.760 + window->GetTop(getter_AddRefs(top)); 1.761 + } 1.762 + 1.763 + if (top) { 1.764 + // The window is no longer detached, so we no longer want to track it. 1.765 + return PL_DHASH_REMOVE; 1.766 + } 1.767 + 1.768 + nsCOMPtr<nsIURI> uri = GetWindowURI(window); 1.769 + 1.770 + nsAutoCString domain; 1.771 + if (uri) { 1.772 + // GetBaseDomain works fine if |uri| is null, but it outputs a warning 1.773 + // which ends up overrunning the mochitest logs. 1.774 + data->tldService->GetBaseDomain(uri, 0, domain); 1.775 + } 1.776 + 1.777 + if (data->nonDetachedDomains->Contains(domain)) { 1.778 + // This window shares a domain with a non-detached window, so reset its 1.779 + // clock. 1.780 + aTimeStamp = TimeStamp(); 1.781 + } else { 1.782 + // This window does not share a domain with a non-detached window, so it 1.783 + // meets ghost criterion (2). 1.784 + if (aTimeStamp.IsNull()) { 1.785 + // This may become a ghost window later; start its clock. 1.786 + aTimeStamp = data->now; 1.787 + } else if ((data->now - aTimeStamp).ToSeconds() > data->ghostTimeout) { 1.788 + // This definitely is a ghost window, so add it to ghostWindowIDs, if 1.789 + // that is not null. 1.790 + if (data->ghostWindowIDs) { 1.791 + nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(window); 1.792 + if (pWindow) { 1.793 + data->ghostWindowIDs->PutEntry(pWindow->WindowID()); 1.794 + } 1.795 + } 1.796 + } 1.797 + } 1.798 + 1.799 + return PL_DHASH_NEXT; 1.800 +} 1.801 + 1.802 +struct GetNonDetachedWindowDomainsEnumeratorData 1.803 +{ 1.804 + nsTHashtable<nsCStringHashKey> *nonDetachedDomains; 1.805 + nsIEffectiveTLDService *tldService; 1.806 +}; 1.807 + 1.808 +static PLDHashOperator 1.809 +GetNonDetachedWindowDomainsEnumerator(const uint64_t& aId, nsGlobalWindow* aWindow, 1.810 + void* aClosure) 1.811 +{ 1.812 + GetNonDetachedWindowDomainsEnumeratorData *data = 1.813 + static_cast<GetNonDetachedWindowDomainsEnumeratorData*>(aClosure); 1.814 + 1.815 + // Null outer window implies null top, but calling GetTop() when there's no 1.816 + // outer window causes us to spew debug warnings. 1.817 + if (!aWindow->GetOuterWindow() || !aWindow->GetTop()) { 1.818 + // This window is detached, so we don't care about its domain. 1.819 + return PL_DHASH_NEXT; 1.820 + } 1.821 + 1.822 + nsCOMPtr<nsIURI> uri = GetWindowURI(aWindow); 1.823 + 1.824 + nsAutoCString domain; 1.825 + if (uri) { 1.826 + data->tldService->GetBaseDomain(uri, 0, domain); 1.827 + } 1.828 + 1.829 + data->nonDetachedDomains->PutEntry(domain); 1.830 + return PL_DHASH_NEXT; 1.831 +} 1.832 + 1.833 +/** 1.834 + * Iterate over mDetachedWindows and update it to reflect the current state of 1.835 + * the world. In particular: 1.836 + * 1.837 + * - Remove weak refs to windows which no longer exist. 1.838 + * 1.839 + * - Remove references to windows which are no longer detached. 1.840 + * 1.841 + * - Reset the timestamp on detached windows which share a domain with a 1.842 + * non-detached window (they no longer meet ghost criterion (2)). 1.843 + * 1.844 + * - If a window now meets ghost criterion (2) but didn't before, set its 1.845 + * timestamp to now. 1.846 + * 1.847 + * Additionally, if aOutGhostIDs is not null, fill it with the window IDs of 1.848 + * all ghost windows we found. 1.849 + */ 1.850 +void 1.851 +nsWindowMemoryReporter::CheckForGhostWindows( 1.852 + nsTHashtable<nsUint64HashKey> *aOutGhostIDs /* = nullptr */) 1.853 +{ 1.854 + nsCOMPtr<nsIEffectiveTLDService> tldService = do_GetService( 1.855 + NS_EFFECTIVETLDSERVICE_CONTRACTID); 1.856 + if (!tldService) { 1.857 + NS_WARNING("Couldn't get TLDService."); 1.858 + return; 1.859 + } 1.860 + 1.861 + nsGlobalWindow::WindowByIdTable *windowsById = 1.862 + nsGlobalWindow::GetWindowsTable(); 1.863 + if (!windowsById) { 1.864 + NS_WARNING("GetWindowsTable returned null"); 1.865 + return; 1.866 + } 1.867 + 1.868 + mLastCheckForGhostWindows = TimeStamp::NowLoRes(); 1.869 + KillCheckTimer(); 1.870 + 1.871 + nsTHashtable<nsCStringHashKey> nonDetachedWindowDomains; 1.872 + 1.873 + // Populate nonDetachedWindowDomains. 1.874 + GetNonDetachedWindowDomainsEnumeratorData nonDetachedEnumData = 1.875 + { &nonDetachedWindowDomains, tldService }; 1.876 + windowsById->EnumerateRead(GetNonDetachedWindowDomainsEnumerator, 1.877 + &nonDetachedEnumData); 1.878 + 1.879 + // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs, 1.880 + // if it's not null. 1.881 + CheckForGhostWindowsEnumeratorData ghostEnumData = 1.882 + { &nonDetachedWindowDomains, aOutGhostIDs, tldService, 1.883 + GetGhostTimeout(), mLastCheckForGhostWindows }; 1.884 + mDetachedWindows.Enumerate(CheckForGhostWindowsEnumerator, 1.885 + &ghostEnumData); 1.886 +} 1.887 + 1.888 +NS_IMPL_ISUPPORTS(nsWindowMemoryReporter::GhostWindowsReporter, 1.889 + nsIMemoryReporter) 1.890 + 1.891 +/* static */ int64_t 1.892 +nsWindowMemoryReporter::GhostWindowsReporter::DistinguishedAmount() 1.893 +{ 1.894 + nsTHashtable<nsUint64HashKey> ghostWindows; 1.895 + sWindowReporter->CheckForGhostWindows(&ghostWindows); 1.896 + return ghostWindows.Count(); 1.897 +} 1.898 + 1.899 +void 1.900 +nsWindowMemoryReporter::KillCheckTimer() 1.901 +{ 1.902 + if (mCheckTimer) { 1.903 + mCheckTimer->Cancel(); 1.904 + mCheckTimer = nullptr; 1.905 + } 1.906 +} 1.907 + 1.908 +#ifdef DEBUG 1.909 +static PLDHashOperator 1.910 +UnlinkGhostWindowsEnumerator(nsUint64HashKey* aIDHashKey, void *) 1.911 +{ 1.912 + nsGlobalWindow::WindowByIdTable* windowsById = 1.913 + nsGlobalWindow::GetWindowsTable(); 1.914 + if (!windowsById) { 1.915 + return PL_DHASH_NEXT; 1.916 + } 1.917 + 1.918 + nsRefPtr<nsGlobalWindow> window = windowsById->Get(aIDHashKey->GetKey()); 1.919 + if (window) { 1.920 + window->RiskyUnlink(); 1.921 + } 1.922 + 1.923 + return PL_DHASH_NEXT; 1.924 +} 1.925 + 1.926 +/* static */ void 1.927 +nsWindowMemoryReporter::UnlinkGhostWindows() 1.928 +{ 1.929 + if (!sWindowReporter) { 1.930 + return; 1.931 + } 1.932 + 1.933 + nsGlobalWindow::WindowByIdTable* windowsById = 1.934 + nsGlobalWindow::GetWindowsTable(); 1.935 + if (!windowsById) { 1.936 + return; 1.937 + } 1.938 + 1.939 + // Hold on to every window in memory so that window objects can't be 1.940 + // destroyed while we're calling the UnlinkGhostWindows callback. 1.941 + WindowArray windows; 1.942 + windowsById->Enumerate(GetWindows, &windows); 1.943 + 1.944 + // Get the IDs of all the "ghost" windows, and unlink them all. 1.945 + nsTHashtable<nsUint64HashKey> ghostWindows; 1.946 + sWindowReporter->CheckForGhostWindows(&ghostWindows); 1.947 + ghostWindows.EnumerateEntries(UnlinkGhostWindowsEnumerator, nullptr); 1.948 +} 1.949 +#endif