fune/dom/base/nsWindowMemoryReporter.cpp
Dorel Luca 4bbd01493b Backed out 43 changesets (bug 1561724, bug 1561150, bug 1541557, bug 1561122, bug 1560400, bug 1558298, bug 1561999, bug 1532795, bug 1561061) for multiple failures. CLOSED TREE
Backed out changeset 1a4fe24a016c (bug 1541557)
Backed out changeset 6fc41e51bcee (bug 1561061)
Backed out changeset d916d89a5c90 (bug 1561122)
Backed out changeset 757d285aafdd (bug 1560400)
Backed out changeset a7eab5ca061d (bug 1541557)
Backed out changeset d972bbdfe039 (bug 1541557)
Backed out changeset 8802daac6779 (bug 1541557)
Backed out changeset 92c01418b96f (bug 1561150)
Backed out changeset fa5e186e1635 (bug 1561061)
Backed out changeset aa2bee0b18c3 (bug 1560400)
Backed out changeset adf832af8e48 (bug 1561150)
Backed out changeset 72630a7c6e67 (bug 1561999)
Backed out changeset c35aff2a9336 (bug 1561724)
Backed out changeset 19e0edc92077 (bug 1561150)
Backed out changeset 0b3e2164f128 (bug 1561150)
Backed out changeset 43211ebfe738 (bug 1561122)
Backed out changeset c2d0956f41d8 (bug 1561061)
Backed out changeset bf0f0e95c61c (bug 1560400)
Backed out changeset 84633034590f (bug 1560400)
Backed out changeset d5415970da5f (bug 1532795)
Backed out changeset 119caddcb066 (bug 1532795)
Backed out changeset fbbe113aeef2 (bug 1532795)
Backed out changeset 8a3d311c7fac (bug 1532795)
Backed out changeset 1471732eca80 (bug 1532795)
Backed out changeset 46ff845a7b0c (bug 1541557)
Backed out changeset c2697f04d38c (bug 1541557)
Backed out changeset 75ebd6fce136 (bug 1541557)
Backed out changeset 189dc8a35981 (bug 1541557)
Backed out changeset b4ed40bea269 (bug 1541557)
Backed out changeset 158a4000c44b (bug 1541557)
Backed out changeset 61fa2745733f (bug 1541557)
Backed out changeset d2ee912c5189 (bug 1558298)
Backed out changeset 7a0aab00327b (bug 1558298)
Backed out changeset fddf2808fedf (bug 1558298)
Backed out changeset 0f6b382f0626 (bug 1558298)
Backed out changeset 6ccaa25367f2 (bug 1558298)
Backed out changeset d27574cfbb0e (bug 1558298)
Backed out changeset 162bc1fc2730 (bug 1558298)
Backed out changeset f94500dd11e3 (bug 1558298)
Backed out changeset fb67ac962bc5 (bug 1558298)
Backed out changeset c634099abb9d (bug 1558298)
Backed out changeset 8d4419c439e1 (bug 1558298)
Backed out changeset d8b7ed5e149f (bug 1558298)

--HG--
rename : testing/mochitest/tests/SimpleTest/MozillaLogger.js => testing/specialpowers/content/MozillaLogger.js
rename : testing/specialpowers/content/SpecialPowersParent.jsm => testing/specialpowers/content/SpecialPowersObserver.jsm
rename : testing/specialpowers/content/SpecialPowersAPIParent.jsm => testing/specialpowers/content/SpecialPowersObserverAPI.js
rename : testing/specialpowers/content/SpecialPowersChild.jsm => testing/specialpowers/content/specialpowers.js
rename : testing/specialpowers/content/SpecialPowersAPI.jsm => testing/specialpowers/content/specialpowersAPI.js
2019-07-02 10:51:56 +03:00

921 lines
34 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsWindowMemoryReporter.h"
#include "nsWindowSizes.h"
#include "nsGlobalWindow.h"
#include "mozilla/dom/Document.h"
#include "nsDOMWindowList.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/Telemetry.h"
#include "nsNetCID.h"
#include "nsPrintfCString.h"
#include "XPCJSMemoryReporter.h"
#include "js/MemoryMetrics.h"
#include "nsQueryObject.h"
#include "nsServiceManagerUtils.h"
#ifdef MOZ_XUL
# include "nsXULPrototypeCache.h"
#endif
using namespace mozilla;
using namespace mozilla::dom;
StaticRefPtr<nsWindowMemoryReporter> sWindowReporter;
/**
* Don't trigger a ghost window check when a DOM window is detached if we've
* run it this recently.
*/
const int32_t kTimeBetweenChecks = 45; /* seconds */
nsWindowMemoryReporter::nsWindowMemoryReporter()
: mLastCheckForGhostWindows(TimeStamp::NowLoRes()),
mCycleCollectorIsRunning(false),
mCheckTimerWaitingForCCEnd(false),
mGhostWindowCount(0) {}
nsWindowMemoryReporter::~nsWindowMemoryReporter() { KillCheckTimer(); }
NS_IMPL_ISUPPORTS(nsWindowMemoryReporter, nsIMemoryReporter, nsIObserver,
nsISupportsWeakReference)
static nsresult AddNonJSSizeOfWindowAndItsDescendents(
nsGlobalWindowOuter* aWindow, nsTabSizes* aSizes) {
// Measure the window.
SizeOfState state(moz_malloc_size_of);
nsWindowSizes windowSizes(state);
aWindow->AddSizeOfIncludingThis(windowSizes);
// Measure the inner window, if there is one.
nsGlobalWindowInner* inner = aWindow->GetCurrentInnerWindowInternal();
if (inner) {
inner->AddSizeOfIncludingThis(windowSizes);
}
windowSizes.addToTabSizes(aSizes);
nsDOMWindowList* frames = aWindow->GetFrames();
uint32_t length = frames->GetLength();
// Measure this window's descendents.
for (uint32_t i = 0; i < length; i++) {
nsCOMPtr<nsPIDOMWindowOuter> child = frames->IndexedGetter(i);
NS_ENSURE_STATE(child);
nsGlobalWindowOuter* childWin = nsGlobalWindowOuter::Cast(child);
nsresult rv = AddNonJSSizeOfWindowAndItsDescendents(childWin, aSizes);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
static nsresult NonJSSizeOfTab(nsPIDOMWindowOuter* aWindow, size_t* aDomSize,
size_t* aStyleSize, size_t* aOtherSize) {
nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(aWindow);
nsTabSizes sizes;
nsresult rv = AddNonJSSizeOfWindowAndItsDescendents(window, &sizes);
NS_ENSURE_SUCCESS(rv, rv);
*aDomSize = sizes.mDom;
*aStyleSize = sizes.mStyle;
*aOtherSize = sizes.mOther;
return NS_OK;
}
/* static */
void nsWindowMemoryReporter::Init() {
MOZ_ASSERT(!sWindowReporter);
sWindowReporter = new nsWindowMemoryReporter();
ClearOnShutdown(&sWindowReporter);
RegisterStrongMemoryReporter(sWindowReporter);
RegisterNonJSSizeOfTab(NonJSSizeOfTab);
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
os->AddObserver(sWindowReporter, "after-minimize-memory-usage",
/* weakRef = */ true);
os->AddObserver(sWindowReporter, "cycle-collector-begin",
/* weakRef = */ true);
os->AddObserver(sWindowReporter, "cycle-collector-end",
/* weakRef = */ true);
}
RegisterGhostWindowsDistinguishedAmount(GhostWindowsDistinguishedAmount);
}
/* static */
nsWindowMemoryReporter* nsWindowMemoryReporter::Get() {
return sWindowReporter;
}
static already_AddRefed<nsIURI> GetWindowURI(nsGlobalWindowInner* aWindow) {
NS_ENSURE_TRUE(aWindow, nullptr);
nsCOMPtr<Document> doc = aWindow->GetExtantDoc();
nsCOMPtr<nsIURI> uri;
if (doc) {
uri = doc->GetDocumentURI();
}
if (!uri) {
nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrincipal =
do_QueryObject(aWindow);
NS_ENSURE_TRUE(scriptObjPrincipal, nullptr);
// GetPrincipal() will print a warning if the window does not have an outer
// window, so check here for an outer window first. This code is
// functionally correct if we leave out the GetOuterWindow() check, but we
// end up printing a lot of warnings during debug mochitests.
if (aWindow->GetOuterWindow()) {
nsIPrincipal* principal = scriptObjPrincipal->GetPrincipal();
if (principal) {
principal->GetURI(getter_AddRefs(uri));
}
}
}
return uri.forget();
}
// Forward to the inner window if we need to when getting the window's URI.
static already_AddRefed<nsIURI> GetWindowURI(nsGlobalWindowOuter* aWindow) {
NS_ENSURE_TRUE(aWindow, nullptr);
return GetWindowURI(aWindow->GetCurrentInnerWindowInternal());
}
static void AppendWindowURI(nsGlobalWindowInner* aWindow, nsACString& aStr,
bool aAnonymize) {
nsCOMPtr<nsIURI> uri = GetWindowURI(aWindow);
if (uri) {
if (aAnonymize && !aWindow->IsChromeWindow()) {
aStr.AppendPrintf("<anonymized-%" PRIu64 ">", aWindow->WindowID());
} else {
nsCString spec = uri->GetSpecOrDefault();
// A hack: replace forward slashes with '\\' so they aren't
// treated as path separators. Users of the reporters
// (such as about:memory) have to undo this change.
spec.ReplaceChar('/', '\\');
aStr += spec;
}
} else {
// If we're unable to find a URI, we're dealing with a chrome window with
// no document in it (or somesuch), so we call this a "system window".
aStr += NS_LITERAL_CSTRING("[system]");
}
}
MOZ_DEFINE_MALLOC_SIZE_OF(WindowsMallocSizeOf)
// The key is the window ID.
typedef nsDataHashtable<nsUint64HashKey, nsCString> WindowPaths;
static void ReportAmount(const nsCString& aBasePath, const char* aPathTail,
size_t aAmount, const nsCString& aDescription,
uint32_t aKind, uint32_t aUnits,
nsIHandleReportCallback* aHandleReport,
nsISupports* aData) {
if (aAmount == 0) {
return;
}
nsAutoCString path(aBasePath);
path += aPathTail;
aHandleReport->Callback(EmptyCString(), path, aKind, aUnits, aAmount,
aDescription, aData);
}
static void ReportSize(const nsCString& aBasePath, const char* aPathTail,
size_t aAmount, const nsCString& aDescription,
nsIHandleReportCallback* aHandleReport,
nsISupports* aData) {
ReportAmount(aBasePath, aPathTail, aAmount, aDescription,
nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
aHandleReport, aData);
}
static void ReportCount(const nsCString& aBasePath, const char* aPathTail,
size_t aAmount, const nsCString& aDescription,
nsIHandleReportCallback* aHandleReport,
nsISupports* aData) {
ReportAmount(aBasePath, aPathTail, aAmount, aDescription,
nsIMemoryReporter::KIND_OTHER, nsIMemoryReporter::UNITS_COUNT,
aHandleReport, aData);
}
static void CollectWindowReports(nsGlobalWindowInner* aWindow,
nsWindowSizes* aWindowTotalSizes,
nsTHashtable<nsUint64HashKey>* aGhostWindowIDs,
WindowPaths* aWindowPaths,
WindowPaths* aTopWindowPaths,
nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) {
nsAutoCString windowPath("explicit/");
// Avoid calling aWindow->GetTop() if there's no outer window. It will work
// just fine, but will spew a lot of warnings.
nsGlobalWindowOuter* top = nullptr;
nsCOMPtr<nsIURI> location;
if (aWindow->GetOuterWindow()) {
// Our window should have a null top iff it has a null docshell.
MOZ_ASSERT(!!aWindow->GetTopInternal() == !!aWindow->GetDocShell());
top = aWindow->GetTopInternal();
if (top) {
location = GetWindowURI(top);
}
}
if (!location) {
location = GetWindowURI(aWindow);
}
windowPath += NS_LITERAL_CSTRING("window-objects/");
if (top) {
windowPath += NS_LITERAL_CSTRING("top(");
AppendWindowURI(top->GetCurrentInnerWindowInternal(), windowPath,
aAnonymize);
windowPath.AppendPrintf(", id=%" PRIu64 ")", top->WindowID());
aTopWindowPaths->Put(aWindow->WindowID(), windowPath);
windowPath += aWindow->IsFrozen() ? NS_LITERAL_CSTRING("/cached/")
: NS_LITERAL_CSTRING("/active/");
} else {
if (aGhostWindowIDs->Contains(aWindow->WindowID())) {
windowPath += NS_LITERAL_CSTRING("top(none)/ghost/");
} else {
windowPath += NS_LITERAL_CSTRING("top(none)/detached/");
}
}
windowPath += NS_LITERAL_CSTRING("window(");
AppendWindowURI(aWindow, windowPath, aAnonymize);
windowPath += NS_LITERAL_CSTRING(")");
// Use |windowPath|, but replace "explicit/" with "event-counts/".
nsCString censusWindowPath(windowPath);
censusWindowPath.ReplaceLiteral(0, strlen("explicit"), "event-counts");
// Remember the path for later.
aWindowPaths->Put(aWindow->WindowID(), windowPath);
// Report the size from windowSizes and add to the appropriate total in
// aWindowTotalSizes.
#define REPORT_SIZE(_pathTail, _field, _desc) \
ReportSize(windowPath, _pathTail, windowSizes._field, \
NS_LITERAL_CSTRING(_desc), aHandleReport, aData); \
aWindowTotalSizes->_field += windowSizes._field;
// Report the size, which is a sum of other sizes, and so doesn't require
// updating aWindowTotalSizes.
#define REPORT_SUM_SIZE(_pathTail, _amount, _desc) \
ReportSize(windowPath, _pathTail, _amount, NS_LITERAL_CSTRING(_desc), \
aHandleReport, aData);
// Like REPORT_SIZE, but for a count.
#define REPORT_COUNT(_pathTail, _field, _desc) \
ReportCount(censusWindowPath, _pathTail, windowSizes._field, \
NS_LITERAL_CSTRING(_desc), aHandleReport, aData); \
aWindowTotalSizes->_field += windowSizes._field;
// This SizeOfState contains the SeenPtrs used for all memory reporting of
// this window.
SizeOfState state(WindowsMallocSizeOf);
nsWindowSizes windowSizes(state);
aWindow->AddSizeOfIncludingThis(windowSizes);
REPORT_SIZE("/dom/element-nodes", mDOMElementNodesSize,
"Memory used by the element nodes in a window's DOM.");
REPORT_SIZE("/dom/text-nodes", mDOMTextNodesSize,
"Memory used by the text nodes in a window's DOM.");
REPORT_SIZE("/dom/cdata-nodes", mDOMCDATANodesSize,
"Memory used by the CDATA nodes in a window's DOM.");
REPORT_SIZE("/dom/comment-nodes", mDOMCommentNodesSize,
"Memory used by the comment nodes in a window's DOM.");
REPORT_SIZE("/dom/event-targets", mDOMEventTargetsSize,
"Memory used by the event targets table in a window's DOM, and "
"the objects it points to, which include XHRs.");
REPORT_SIZE("/dom/performance/user-entries", mDOMPerformanceUserEntries,
"Memory used for performance user entries.");
REPORT_SIZE("/dom/performance/resource-entries",
mDOMPerformanceResourceEntries,
"Memory used for performance resource entries.");
REPORT_SIZE("/dom/media-query-lists", mDOMMediaQueryLists,
"Memory used by MediaQueryList objects for the window's "
"document.");
REPORT_SIZE("/dom/other", mDOMOtherSize,
"Memory used by a window's DOM that isn't measured by the "
"other 'dom/' numbers.");
REPORT_SIZE("/layout/style-sheets", mLayoutStyleSheetsSize,
"Memory used by document style sheets within a window.");
REPORT_SIZE("/layout/svg-mapped-declarations", mLayoutSvgMappedDeclarations,
"Memory used by mapped declarations of SVG elements");
REPORT_SIZE("/layout/shadow-dom/style-sheets",
mLayoutShadowDomStyleSheetsSize,
"Memory used by Shadow DOM style sheets within a window.");
// TODO(emilio): We might want to split this up between invalidation map /
// element-and-pseudos / revalidation too just like the style set.
REPORT_SIZE("/layout/shadow-dom/author-styles", mLayoutShadowDomAuthorStyles,
"Memory used by Shadow DOM computed rule data within a window.");
REPORT_SIZE("/layout/pres-shell", mLayoutPresShellSize,
"Memory used by layout's PresShell, along with any structures "
"allocated in its arena and not measured elsewhere, "
"within a window.");
REPORT_SIZE("/layout/display-list", mLayoutRetainedDisplayListSize,
"Memory used by the retained display list data, "
"along with any structures allocated in its arena and not "
"measured elsewhere, within a window.");
REPORT_SIZE("/layout/style-sets/stylist/rule-tree",
mLayoutStyleSetsStylistRuleTree,
"Memory used by rule trees within style sets within a window.");
REPORT_SIZE("/layout/style-sets/stylist/element-and-pseudos-maps",
mLayoutStyleSetsStylistElementAndPseudosMaps,
"Memory used by element and pseudos maps within style "
"sets within a window.");
REPORT_SIZE("/layout/style-sets/stylist/invalidation-map",
mLayoutStyleSetsStylistInvalidationMap,
"Memory used by invalidation maps within style sets "
"within a window.");
REPORT_SIZE("/layout/style-sets/stylist/revalidation-selectors",
mLayoutStyleSetsStylistRevalidationSelectors,
"Memory used by selectors for cache revalidation within "
"style sets within a window.");
REPORT_SIZE("/layout/style-sets/stylist/other", mLayoutStyleSetsStylistOther,
"Memory used by other Stylist data within style sets "
"within a window.");
REPORT_SIZE("/layout/style-sets/other", mLayoutStyleSetsOther,
"Memory used by other parts of style sets within a window.");
REPORT_SIZE("/layout/element-data-objects", mLayoutElementDataObjects,
"Memory used for ElementData objects, but not the things "
"hanging off them.");
REPORT_SIZE("/layout/text-runs", mLayoutTextRunsSize,
"Memory used for text-runs (glyph layout) in the PresShell's "
"frame tree, within a window.");
REPORT_SIZE("/layout/pres-contexts", mLayoutPresContextSize,
"Memory used for the PresContext in the PresShell's frame "
"within a window.");
REPORT_SIZE("/layout/frame-properties", mLayoutFramePropertiesSize,
"Memory used for frame properties attached to frames "
"within a window.");
REPORT_SIZE("/layout/computed-values/dom", mLayoutComputedValuesDom,
"Memory used by ComputedValues objects accessible from DOM "
"elements.");
REPORT_SIZE("/layout/computed-values/non-dom", mLayoutComputedValuesNonDom,
"Memory used by ComputedValues objects not accessible from DOM "
"elements.");
REPORT_SIZE("/layout/computed-values/visited", mLayoutComputedValuesVisited,
"Memory used by ComputedValues objects used for visited styles.");
REPORT_SIZE("/property-tables", mPropertyTablesSize,
"Memory used for the property tables within a window.");
REPORT_SIZE("/bindings", mBindingsSize,
"Memory used by bindings within a window.");
REPORT_COUNT("/dom/event-targets", mDOMEventTargetsCount,
"Number of non-node event targets in the event targets table "
"in a window's DOM, such as XHRs.");
REPORT_COUNT("/dom/event-listeners", mDOMEventListenersCount,
"Number of event listeners in a window, including event "
"listeners on nodes and other event targets.");
REPORT_SIZE("/layout/line-boxes", mArenaSizes.mLineBoxes,
"Memory used by line boxes within a window.");
// There are many different kinds of style structs, but it is likely that
// only a few matter. Implement a cutoff so we don't bloat about:memory with
// many uninteresting entries.
const size_t STYLE_SUNDRIES_THRESHOLD =
js::MemoryReportingSundriesThreshold();
// There are many different kinds of frames, but it is very likely
// that only a few matter. Implement a cutoff so we don't bloat
// about:memory with many uninteresting entries.
const size_t FRAME_SUNDRIES_THRESHOLD =
js::MemoryReportingSundriesThreshold();
size_t frameSundriesSize = 0;
#define FRAME_ID(classname, ...) \
{ \
size_t size = windowSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(classname); \
if (size < FRAME_SUNDRIES_THRESHOLD) { \
frameSundriesSize += size; \
} else { \
REPORT_SUM_SIZE("/layout/frames/" #classname, size, \
"Memory used by frames of type " #classname \
" within a window."); \
} \
aWindowTotalSizes->mArenaSizes.NS_ARENA_SIZES_FIELD(classname) += size; \
}
#define ABSTRACT_FRAME_ID(...)
#include "mozilla/FrameIdList.h"
#undef FRAME_ID
#undef ABSTRACT_FRAME_ID
if (frameSundriesSize > 0) {
REPORT_SUM_SIZE(
"/layout/frames/sundries", frameSundriesSize,
"The sum of all memory used by frames which were too small to be shown "
"individually.");
}
// This is the style structs.
size_t styleSundriesSize = 0;
#define STYLE_STRUCT(name_) \
{ \
size_t size = windowSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_); \
if (size < STYLE_SUNDRIES_THRESHOLD) { \
styleSundriesSize += size; \
} else { \
REPORT_SUM_SIZE("/layout/style-structs/" #name_, size, \
"Memory used by the " #name_ \
" style structs within a window."); \
} \
aWindowTotalSizes->mStyleSizes.NS_STYLE_SIZES_FIELD(name_) += size; \
}
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
if (styleSundriesSize > 0) {
REPORT_SUM_SIZE(
"/layout/style-structs/sundries", styleSundriesSize,
"The sum of all memory used by style structs which were too "
"small to be shown individually.");
}
#undef REPORT_SIZE
#undef REPORT_SUM_SIZE
#undef REPORT_COUNT
}
typedef nsTArray<RefPtr<nsGlobalWindowInner>> WindowArray;
NS_IMETHODIMP
nsWindowMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) {
nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
nsGlobalWindowInner::GetWindowsTable();
NS_ENSURE_TRUE(windowsById, NS_OK);
// Hold on to every window in memory so that window objects can't be
// destroyed while we're calling the memory reporter callback.
WindowArray windows;
for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
windows.AppendElement(iter.Data());
}
// Get the IDs of all the "ghost" windows, and call aHandleReport->Callback()
// for each one.
nsTHashtable<nsUint64HashKey> ghostWindows;
CheckForGhostWindows(&ghostWindows);
for (auto iter = ghostWindows.ConstIter(); !iter.Done(); iter.Next()) {
nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
nsGlobalWindowInner::GetWindowsTable();
if (!windowsById) {
NS_WARNING("Couldn't get window-by-id hashtable?");
continue;
}
nsGlobalWindowInner* window = windowsById->Get(iter.Get()->GetKey());
if (!window) {
NS_WARNING("Could not look up window?");
continue;
}
nsAutoCString path;
path.AppendLiteral("ghost-windows/");
AppendWindowURI(window, path, aAnonymize);
aHandleReport->Callback(
/* process = */ EmptyCString(), path, nsIMemoryReporter::KIND_OTHER,
nsIMemoryReporter::UNITS_COUNT,
/* amount = */ 1,
/* description = */ NS_LITERAL_CSTRING("A ghost window."), aData);
}
// clang-format off
MOZ_COLLECT_REPORT(
"ghost-windows", KIND_OTHER, UNITS_COUNT, ghostWindows.Count(),
"The number of ghost windows present (the number of nodes underneath "
"explicit/window-objects/top(none)/ghost, modulo race conditions). A ghost "
"window is not shown in any tab, is not in a tab group with any "
"non-detached windows, and has met these criteria for at least "
"memory.ghost_window_timeout_seconds, or has survived a round of "
"about:memory's minimize memory usage button.\n\n"
"Ghost windows can happen legitimately, but they are often indicative of "
"leaks in the browser or add-ons.");
// clang-format on
WindowPaths windowPaths;
WindowPaths topWindowPaths;
// Collect window memory usage.
SizeOfState fakeState(nullptr); // this won't be used
nsWindowSizes windowTotalSizes(fakeState);
for (uint32_t i = 0; i < windows.Length(); i++) {
CollectWindowReports(windows[i], &windowTotalSizes, &ghostWindows,
&windowPaths, &topWindowPaths, aHandleReport, aData,
aAnonymize);
}
// Report JS memory usage. We do this from here because the JS memory
// reporter needs to be passed |windowPaths|.
xpc::JSReporter::CollectReports(&windowPaths, &topWindowPaths, aHandleReport,
aData, aAnonymize);
#ifdef MOZ_XUL
nsXULPrototypeCache::CollectMemoryReports(aHandleReport, aData);
#endif
#define REPORT(_path, _amount, _desc) \
aHandleReport->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \
KIND_OTHER, UNITS_BYTES, _amount, \
NS_LITERAL_CSTRING(_desc), aData);
REPORT("window-objects/dom/element-nodes",
windowTotalSizes.mDOMElementNodesSize,
"This is the sum of all windows' 'dom/element-nodes' numbers.");
REPORT("window-objects/dom/text-nodes", windowTotalSizes.mDOMTextNodesSize,
"This is the sum of all windows' 'dom/text-nodes' numbers.");
REPORT("window-objects/dom/cdata-nodes", windowTotalSizes.mDOMCDATANodesSize,
"This is the sum of all windows' 'dom/cdata-nodes' numbers.");
REPORT("window-objects/dom/comment-nodes",
windowTotalSizes.mDOMCommentNodesSize,
"This is the sum of all windows' 'dom/comment-nodes' numbers.");
REPORT("window-objects/dom/event-targets",
windowTotalSizes.mDOMEventTargetsSize,
"This is the sum of all windows' 'dom/event-targets' numbers.");
REPORT("window-objects/dom/performance",
windowTotalSizes.mDOMPerformanceUserEntries +
windowTotalSizes.mDOMPerformanceResourceEntries,
"This is the sum of all windows' 'dom/performance/' numbers.");
REPORT("window-objects/dom/other", windowTotalSizes.mDOMOtherSize,
"This is the sum of all windows' 'dom/other' numbers.");
REPORT("window-objects/layout/style-sheets",
windowTotalSizes.mLayoutStyleSheetsSize,
"This is the sum of all windows' 'layout/style-sheets' numbers.");
REPORT("window-objects/layout/pres-shell",
windowTotalSizes.mLayoutPresShellSize,
"This is the sum of all windows' 'layout/arenas' numbers.");
REPORT("window-objects/layout/style-sets",
windowTotalSizes.mLayoutStyleSetsStylistRuleTree +
windowTotalSizes.mLayoutStyleSetsStylistElementAndPseudosMaps +
windowTotalSizes.mLayoutStyleSetsStylistInvalidationMap +
windowTotalSizes.mLayoutStyleSetsStylistRevalidationSelectors +
windowTotalSizes.mLayoutStyleSetsStylistOther +
windowTotalSizes.mLayoutStyleSetsOther,
"This is the sum of all windows' 'layout/style-sets/' numbers.");
REPORT("window-objects/layout/element-data-objects",
windowTotalSizes.mLayoutElementDataObjects,
"This is the sum of all windows' 'layout/element-data-objects' "
"numbers.");
REPORT("window-objects/layout/text-runs",
windowTotalSizes.mLayoutTextRunsSize,
"This is the sum of all windows' 'layout/text-runs' numbers.");
REPORT("window-objects/layout/pres-contexts",
windowTotalSizes.mLayoutPresContextSize,
"This is the sum of all windows' 'layout/pres-contexts' numbers.");
REPORT("window-objects/layout/frame-properties",
windowTotalSizes.mLayoutFramePropertiesSize,
"This is the sum of all windows' 'layout/frame-properties' numbers.");
REPORT("window-objects/layout/computed-values",
windowTotalSizes.mLayoutComputedValuesDom +
windowTotalSizes.mLayoutComputedValuesNonDom +
windowTotalSizes.mLayoutComputedValuesVisited,
"This is the sum of all windows' 'layout/computed-values/' numbers.");
REPORT("window-objects/property-tables", windowTotalSizes.mPropertyTablesSize,
"This is the sum of all windows' 'property-tables' numbers.");
REPORT("window-objects/layout/line-boxes",
windowTotalSizes.mArenaSizes.mLineBoxes,
"This is the sum of all windows' 'layout/line-boxes' numbers.");
size_t frameTotal = 0;
#define FRAME_ID(classname, ...) \
frameTotal += windowTotalSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(classname);
#define ABSTRACT_FRAME_ID(...)
#include "mozilla/FrameIdList.h"
#undef FRAME_ID
#undef ABSTRACT_FRAME_ID
REPORT("window-objects/layout/frames", frameTotal,
"Memory used for layout frames within windows. "
"This is the sum of all windows' 'layout/frames/' numbers.");
size_t styleTotal = 0;
#define STYLE_STRUCT(name_) \
styleTotal += windowTotalSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_);
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
REPORT("window-objects/layout/style-structs", styleTotal,
"Memory used for style structs within windows. This is the sum of "
"all windows' 'layout/style-structs/' numbers.");
#undef REPORT
return NS_OK;
}
uint32_t nsWindowMemoryReporter::GetGhostTimeout() {
return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
}
NS_IMETHODIMP
nsWindowMemoryReporter::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (!strcmp(aTopic, "after-minimize-memory-usage")) {
ObserveAfterMinimizeMemoryUsage();
} else if (!strcmp(aTopic, "cycle-collector-begin")) {
if (mCheckTimer) {
mCheckTimerWaitingForCCEnd = true;
KillCheckTimer();
}
mCycleCollectorIsRunning = true;
} else if (!strcmp(aTopic, "cycle-collector-end")) {
mCycleCollectorIsRunning = false;
if (mCheckTimerWaitingForCCEnd) {
mCheckTimerWaitingForCCEnd = false;
AsyncCheckForGhostWindows();
}
} else {
MOZ_ASSERT(false);
}
return NS_OK;
}
void nsWindowMemoryReporter::ObserveDOMWindowDetached(
nsGlobalWindowInner* aWindow) {
nsWeakPtr weakWindow = do_GetWeakReference(aWindow);
if (!weakWindow) {
NS_WARNING("Couldn't take weak reference to a window?");
return;
}
mDetachedWindows.Put(weakWindow, TimeStamp());
AsyncCheckForGhostWindows();
}
// static
void nsWindowMemoryReporter::CheckTimerFired(nsITimer* aTimer, void* aData) {
if (sWindowReporter) {
MOZ_ASSERT(!sWindowReporter->mCycleCollectorIsRunning);
sWindowReporter->CheckForGhostWindows();
}
}
void nsWindowMemoryReporter::AsyncCheckForGhostWindows() {
if (mCheckTimer) {
return;
}
if (mCycleCollectorIsRunning) {
mCheckTimerWaitingForCCEnd = true;
return;
}
// If more than kTimeBetweenChecks seconds have elapsed since the last check,
// timerDelay is 0. Otherwise, it is kTimeBetweenChecks, reduced by the time
// since the last check. Reducing the delay by the time since the last check
// prevents the timer from being completely starved if it is repeatedly killed
// and restarted.
int32_t timeSinceLastCheck =
(TimeStamp::NowLoRes() - mLastCheckForGhostWindows).ToSeconds();
int32_t timerDelay =
(kTimeBetweenChecks - std::min(timeSinceLastCheck, kTimeBetweenChecks)) *
PR_MSEC_PER_SEC;
NS_NewTimerWithFuncCallback(
getter_AddRefs(mCheckTimer), CheckTimerFired, nullptr, timerDelay,
nsITimer::TYPE_ONE_SHOT,
"nsWindowMemoryReporter::AsyncCheckForGhostWindows_timer");
}
void nsWindowMemoryReporter::ObserveAfterMinimizeMemoryUsage() {
// Someone claims they've done enough GC/CCs so that all eligible windows
// have been free'd. So we deem that any windows which satisfy ghost
// criteria (1) and (2) now satisfy criterion (3) as well.
//
// To effect this change, we'll backdate some of our timestamps.
TimeStamp minTimeStamp =
TimeStamp::Now() - TimeDuration::FromSeconds(GetGhostTimeout());
for (auto iter = mDetachedWindows.Iter(); !iter.Done(); iter.Next()) {
TimeStamp& timeStamp = iter.Data();
if (!timeStamp.IsNull() && timeStamp > minTimeStamp) {
timeStamp = minTimeStamp;
}
}
}
/**
* Iterate over mDetachedWindows and update it to reflect the current state of
* the world. In particular:
*
* - Remove weak refs to windows which no longer exist.
*
* - Remove references to windows which are no longer detached.
*
* - Reset the timestamp on detached windows which share a domain with a
* non-detached window (they no longer meet ghost criterion (2)).
*
* - If a window now meets ghost criterion (2) but didn't before, set its
* timestamp to now.
*
* Additionally, if aOutGhostIDs is not null, fill it with the window IDs of
* all ghost windows we found.
*/
void nsWindowMemoryReporter::CheckForGhostWindows(
nsTHashtable<nsUint64HashKey>* aOutGhostIDs /* = nullptr */) {
nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
nsGlobalWindowInner::GetWindowsTable();
if (!windowsById) {
NS_WARNING("GetWindowsTable returned null");
return;
}
mLastCheckForGhostWindows = TimeStamp::NowLoRes();
KillCheckTimer();
nsTHashtable<nsPtrHashKey<TabGroup>> nonDetachedTabGroups;
// Populate nonDetachedTabGroups.
for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
// Null outer window implies null top, but calling GetTop() when there's no
// outer window causes us to spew debug warnings.
nsGlobalWindowInner* window = iter.UserData();
if (!window->GetOuterWindow() || !window->GetTopInternal()) {
// This window is detached, so we don't care about its tab group.
continue;
}
nonDetachedTabGroups.PutEntry(window->TabGroup());
}
// Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
// if it's not null.
uint32_t ghostTimeout = GetGhostTimeout();
TimeStamp now = mLastCheckForGhostWindows;
mGhostWindowCount = 0;
for (auto iter = mDetachedWindows.Iter(); !iter.Done(); iter.Next()) {
nsWeakPtr weakKey = do_QueryInterface(iter.Key());
nsCOMPtr<mozIDOMWindow> iwindow = do_QueryReferent(weakKey);
if (!iwindow) {
// The window object has been destroyed. Stop tracking its weak ref in
// our hashtable.
iter.Remove();
continue;
}
nsPIDOMWindowInner* window = nsPIDOMWindowInner::From(iwindow);
// Avoid calling GetTop() if we have no outer window. Nothing will break if
// we do, but it will spew debug output, which can cause our test logs to
// overflow.
nsCOMPtr<nsPIDOMWindowOuter> top;
if (window->GetOuterWindow()) {
top = window->GetOuterWindow()->GetTop();
}
if (top) {
// The window is no longer detached, so we no longer want to track it.
iter.Remove();
continue;
}
TimeStamp& timeStamp = iter.Data();
if (nonDetachedTabGroups.GetEntry(window->TabGroup())) {
// This window is in the same tab group as a non-detached
// window, so reset its clock.
timeStamp = TimeStamp();
} else {
// This window is not in the same tab group as a non-detached
// window, so it meets ghost criterion (2).
if (timeStamp.IsNull()) {
// This may become a ghost window later; start its clock.
timeStamp = now;
} else if ((now - timeStamp).ToSeconds() > ghostTimeout) {
// This definitely is a ghost window, so add it to aOutGhostIDs, if
// that is not null.
mGhostWindowCount++;
if (aOutGhostIDs && window) {
aOutGhostIDs->PutEntry(window->WindowID());
}
}
}
}
Telemetry::ScalarSetMaximum(
Telemetry::ScalarID::MEMORYREPORTER_MAX_GHOST_WINDOWS, mGhostWindowCount);
}
/* static */
int64_t nsWindowMemoryReporter::GhostWindowsDistinguishedAmount() {
return sWindowReporter->mGhostWindowCount;
}
void nsWindowMemoryReporter::KillCheckTimer() {
if (mCheckTimer) {
mCheckTimer->Cancel();
mCheckTimer = nullptr;
}
}
#ifdef DEBUG
/* static */
void nsWindowMemoryReporter::UnlinkGhostWindows() {
if (!sWindowReporter) {
return;
}
nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
nsGlobalWindowInner::GetWindowsTable();
if (!windowsById) {
return;
}
// Hold on to every window in memory so that window objects can't be
// destroyed while we're calling the UnlinkGhostWindows callback.
WindowArray windows;
for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
windows.AppendElement(iter.Data());
}
// Get the IDs of all the "ghost" windows, and unlink them all.
nsTHashtable<nsUint64HashKey> ghostWindows;
sWindowReporter->CheckForGhostWindows(&ghostWindows);
for (auto iter = ghostWindows.ConstIter(); !iter.Done(); iter.Next()) {
nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
nsGlobalWindowInner::GetWindowsTable();
if (!windowsById) {
continue;
}
RefPtr<nsGlobalWindowInner> window = windowsById->Get(iter.Get()->GetKey());
if (window) {
window->RiskyUnlink();
}
}
}
#endif