Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/base/nsWindowMemoryReporter.cpp
Line
Count
Source (jump to first uncovered line)
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
#include "nsWindowSizes.h"
9
#include "nsGlobalWindow.h"
10
#include "nsIDocument.h"
11
#include "nsDOMWindowList.h"
12
#include "mozilla/ClearOnShutdown.h"
13
#include "mozilla/Preferences.h"
14
#include "mozilla/Services.h"
15
#include "mozilla/StaticPtr.h"
16
#include "mozilla/Telemetry.h"
17
#include "nsNetCID.h"
18
#include "nsPrintfCString.h"
19
#include "XPCJSMemoryReporter.h"
20
#include "js/MemoryMetrics.h"
21
#include "nsQueryObject.h"
22
#include "nsServiceManagerUtils.h"
23
#ifdef MOZ_XUL
24
#include "nsXULPrototypeCache.h"
25
#endif
26
27
using namespace mozilla;
28
using namespace mozilla::dom;
29
30
StaticRefPtr<nsWindowMemoryReporter> sWindowReporter;
31
32
/**
33
 * Don't trigger a ghost window check when a DOM window is detached if we've
34
 * run it this recently.
35
 */
36
const int32_t kTimeBetweenChecks = 45; /* seconds */
37
38
nsWindowMemoryReporter::nsWindowMemoryReporter()
39
  : mLastCheckForGhostWindows(TimeStamp::NowLoRes()),
40
    mCycleCollectorIsRunning(false),
41
    mCheckTimerWaitingForCCEnd(false),
42
    mGhostWindowCount(0)
43
3
{
44
3
}
45
46
nsWindowMemoryReporter::~nsWindowMemoryReporter()
47
0
{
48
0
  KillCheckTimer();
49
0
}
50
51
NS_IMPL_ISUPPORTS(nsWindowMemoryReporter, nsIMemoryReporter, nsIObserver,
52
                  nsISupportsWeakReference)
53
54
static nsresult
55
AddNonJSSizeOfWindowAndItsDescendents(nsGlobalWindowOuter* aWindow,
56
                                      nsTabSizes* aSizes)
57
0
{
58
0
  // Measure the window.
59
0
  SizeOfState state(moz_malloc_size_of);
60
0
  nsWindowSizes windowSizes(state);
61
0
  aWindow->AddSizeOfIncludingThis(windowSizes);
62
0
63
0
  // Measure the inner window, if there is one.
64
0
  nsGlobalWindowInner* inner = aWindow->GetCurrentInnerWindowInternal();
65
0
  if (inner) {
66
0
    inner->AddSizeOfIncludingThis(windowSizes);
67
0
  }
68
0
69
0
  windowSizes.addToTabSizes(aSizes);
70
0
71
0
  nsDOMWindowList* frames = aWindow->GetFrames();
72
0
73
0
  uint32_t length = frames->GetLength();
74
0
75
0
  // Measure this window's descendents.
76
0
  for (uint32_t i = 0; i < length; i++) {
77
0
      nsCOMPtr<nsPIDOMWindowOuter> child = frames->IndexedGetter(i);
78
0
      NS_ENSURE_STATE(child);
79
0
80
0
      nsGlobalWindowOuter* childWin = nsGlobalWindowOuter::Cast(child);
81
0
82
0
      nsresult rv = AddNonJSSizeOfWindowAndItsDescendents(childWin, aSizes);
83
0
      NS_ENSURE_SUCCESS(rv, rv);
84
0
  }
85
0
  return NS_OK;
86
0
}
87
88
static nsresult
89
NonJSSizeOfTab(nsPIDOMWindowOuter* aWindow, size_t* aDomSize, size_t* aStyleSize, size_t* aOtherSize)
90
0
{
91
0
  nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(aWindow);
92
0
93
0
  nsTabSizes sizes;
94
0
  nsresult rv = AddNonJSSizeOfWindowAndItsDescendents(window, &sizes);
95
0
  NS_ENSURE_SUCCESS(rv, rv);
96
0
97
0
  *aDomSize   = sizes.mDom;
98
0
  *aStyleSize = sizes.mStyle;
99
0
  *aOtherSize = sizes.mOther;
100
0
  return NS_OK;
101
0
}
102
103
/* static */ void
104
nsWindowMemoryReporter::Init()
105
3
{
106
3
  MOZ_ASSERT(!sWindowReporter);
107
3
  sWindowReporter = new nsWindowMemoryReporter();
108
3
  ClearOnShutdown(&sWindowReporter);
109
3
  RegisterStrongMemoryReporter(sWindowReporter);
110
3
  RegisterNonJSSizeOfTab(NonJSSizeOfTab);
111
3
112
3
  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
113
3
  if (os) {
114
3
    os->AddObserver(sWindowReporter, "after-minimize-memory-usage",
115
3
                    /* weakRef = */ true);
116
3
    os->AddObserver(sWindowReporter, "cycle-collector-begin",
117
3
                    /* weakRef = */ true);
118
3
    os->AddObserver(sWindowReporter, "cycle-collector-end",
119
3
                    /* weakRef = */ true);
120
3
  }
121
3
122
3
  RegisterGhostWindowsDistinguishedAmount(GhostWindowsDistinguishedAmount);
123
3
}
124
125
/* static */ nsWindowMemoryReporter*
126
nsWindowMemoryReporter::Get()
127
0
{
128
0
  return sWindowReporter;
129
0
}
130
131
static already_AddRefed<nsIURI>
132
GetWindowURI(nsGlobalWindowInner* aWindow)
133
0
{
134
0
  NS_ENSURE_TRUE(aWindow, nullptr);
135
0
136
0
  nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
137
0
  nsCOMPtr<nsIURI> uri;
138
0
139
0
  if (doc) {
140
0
    uri = doc->GetDocumentURI();
141
0
  }
142
0
143
0
  if (!uri) {
144
0
    nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrincipal =
145
0
      do_QueryObject(aWindow);
146
0
    NS_ENSURE_TRUE(scriptObjPrincipal, nullptr);
147
0
148
0
    // GetPrincipal() will print a warning if the window does not have an outer
149
0
    // window, so check here for an outer window first.  This code is
150
0
    // functionally correct if we leave out the GetOuterWindow() check, but we
151
0
    // end up printing a lot of warnings during debug mochitests.
152
0
    if (aWindow->GetOuterWindow()) {
153
0
      nsIPrincipal* principal = scriptObjPrincipal->GetPrincipal();
154
0
      if (principal) {
155
0
        principal->GetURI(getter_AddRefs(uri));
156
0
      }
157
0
    }
158
0
  }
159
0
160
0
  return uri.forget();
161
0
}
162
163
// Forward to the inner window if we need to when getting the window's URI.
164
static already_AddRefed<nsIURI>
165
GetWindowURI(nsGlobalWindowOuter* aWindow)
166
0
{
167
0
  NS_ENSURE_TRUE(aWindow, nullptr);
168
0
  return GetWindowURI(aWindow->GetCurrentInnerWindowInternal());
169
0
}
170
171
static void
172
AppendWindowURI(nsGlobalWindowInner *aWindow, nsACString& aStr, bool aAnonymize)
173
0
{
174
0
  nsCOMPtr<nsIURI> uri = GetWindowURI(aWindow);
175
0
176
0
  if (uri) {
177
0
    if (aAnonymize && !aWindow->IsChromeWindow()) {
178
0
      aStr.AppendPrintf("<anonymized-%" PRIu64 ">", aWindow->WindowID());
179
0
    } else {
180
0
      nsCString spec = uri->GetSpecOrDefault();
181
0
182
0
      // A hack: replace forward slashes with '\\' so they aren't
183
0
      // treated as path separators.  Users of the reporters
184
0
      // (such as about:memory) have to undo this change.
185
0
      spec.ReplaceChar('/', '\\');
186
0
187
0
      aStr += spec;
188
0
    }
189
0
  } else {
190
0
    // If we're unable to find a URI, we're dealing with a chrome window with
191
0
    // no document in it (or somesuch), so we call this a "system window".
192
0
    aStr += NS_LITERAL_CSTRING("[system]");
193
0
  }
194
0
}
195
196
MOZ_DEFINE_MALLOC_SIZE_OF(WindowsMallocSizeOf)
197
198
// The key is the window ID.
199
typedef nsDataHashtable<nsUint64HashKey, nsCString> WindowPaths;
200
201
static void
202
ReportAmount(const nsCString& aBasePath, const char* aPathTail,
203
             size_t aAmount, const nsCString& aDescription,
204
             uint32_t aKind, uint32_t aUnits,
205
             nsIHandleReportCallback* aHandleReport,
206
             nsISupports* aData)
207
0
{
208
0
  if (aAmount == 0) {
209
0
    return;
210
0
  }
211
0
212
0
  nsAutoCString path(aBasePath);
213
0
  path += aPathTail;
214
0
215
0
  aHandleReport->Callback(
216
0
    EmptyCString(), path, aKind, aUnits, aAmount, aDescription, aData);
217
0
}
218
219
static void
220
ReportSize(const nsCString& aBasePath, const char* aPathTail,
221
           size_t aAmount, const nsCString& aDescription,
222
           nsIHandleReportCallback* aHandleReport,
223
           nsISupports* aData)
224
0
{
225
0
  ReportAmount(aBasePath, aPathTail, aAmount, aDescription,
226
0
               nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
227
0
               aHandleReport, aData);
228
0
}
229
230
static void
231
ReportCount(const nsCString& aBasePath, const char* aPathTail,
232
            size_t aAmount, const nsCString& aDescription,
233
            nsIHandleReportCallback* aHandleReport,
234
            nsISupports* aData)
235
0
{
236
0
  ReportAmount(aBasePath, aPathTail, aAmount, aDescription,
237
0
               nsIMemoryReporter::KIND_OTHER, nsIMemoryReporter::UNITS_COUNT,
238
0
               aHandleReport, aData);
239
0
}
240
241
static void
242
CollectWindowReports(nsGlobalWindowInner *aWindow,
243
                     nsWindowSizes *aWindowTotalSizes,
244
                     nsTHashtable<nsUint64HashKey> *aGhostWindowIDs,
245
                     WindowPaths *aWindowPaths,
246
                     WindowPaths *aTopWindowPaths,
247
                     nsIHandleReportCallback *aHandleReport,
248
                     nsISupports *aData,
249
                     bool aAnonymize)
250
0
{
251
0
  nsAutoCString windowPath("explicit/");
252
0
253
0
  // Avoid calling aWindow->GetTop() if there's no outer window.  It will work
254
0
  // just fine, but will spew a lot of warnings.
255
0
  nsGlobalWindowOuter *top = nullptr;
256
0
  nsCOMPtr<nsIURI> location;
257
0
  if (aWindow->GetOuterWindow()) {
258
0
    // Our window should have a null top iff it has a null docshell.
259
0
    MOZ_ASSERT(!!aWindow->GetTopInternal() == !!aWindow->GetDocShell());
260
0
    top = aWindow->GetTopInternal();
261
0
    if (top) {
262
0
      location = GetWindowURI(top);
263
0
    }
264
0
  }
265
0
  if (!location) {
266
0
    location = GetWindowURI(aWindow);
267
0
  }
268
0
269
0
  windowPath += NS_LITERAL_CSTRING("window-objects/");
270
0
271
0
  if (top) {
272
0
    windowPath += NS_LITERAL_CSTRING("top(");
273
0
    AppendWindowURI(top->GetCurrentInnerWindowInternal(), windowPath, aAnonymize);
274
0
    windowPath.AppendPrintf(", id=%" PRIu64 ")", top->WindowID());
275
0
276
0
    aTopWindowPaths->Put(aWindow->WindowID(), windowPath);
277
0
278
0
    windowPath += aWindow->IsFrozen() ? NS_LITERAL_CSTRING("/cached/")
279
0
                                      : NS_LITERAL_CSTRING("/active/");
280
0
  } else {
281
0
    if (aGhostWindowIDs->Contains(aWindow->WindowID())) {
282
0
      windowPath += NS_LITERAL_CSTRING("top(none)/ghost/");
283
0
    } else {
284
0
      windowPath += NS_LITERAL_CSTRING("top(none)/detached/");
285
0
    }
286
0
  }
287
0
288
0
  windowPath += NS_LITERAL_CSTRING("window(");
289
0
  AppendWindowURI(aWindow, windowPath, aAnonymize);
290
0
  windowPath += NS_LITERAL_CSTRING(")");
291
0
292
0
  // Use |windowPath|, but replace "explicit/" with "event-counts/".
293
0
  nsCString censusWindowPath(windowPath);
294
0
  censusWindowPath.ReplaceLiteral(0, strlen("explicit"), "event-counts");
295
0
296
0
  // Remember the path for later.
297
0
  aWindowPaths->Put(aWindow->WindowID(), windowPath);
298
0
299
0
// Report the size from windowSizes and add to the appropriate total in
300
0
// aWindowTotalSizes.
301
0
#define REPORT_SIZE(_pathTail, _field, _desc) \
302
0
  ReportSize(windowPath, _pathTail, windowSizes._field, \
303
0
             NS_LITERAL_CSTRING(_desc), aHandleReport, aData); \
304
0
  aWindowTotalSizes->_field += windowSizes._field;
305
0
306
0
// Report the size, which is a sum of other sizes, and so doesn't require
307
0
// updating aWindowTotalSizes.
308
0
#define REPORT_SUM_SIZE(_pathTail, _amount, _desc) \
309
0
  ReportSize(windowPath, _pathTail, _amount, NS_LITERAL_CSTRING(_desc), \
310
0
             aHandleReport, aData);
311
0
312
0
// Like REPORT_SIZE, but for a count.
313
0
#define REPORT_COUNT(_pathTail, _field, _desc) \
314
0
  ReportCount(censusWindowPath, _pathTail, windowSizes._field, \
315
0
              NS_LITERAL_CSTRING(_desc), aHandleReport, aData); \
316
0
  aWindowTotalSizes->_field += windowSizes._field;
317
0
318
0
  // This SizeOfState contains the SeenPtrs used for all memory reporting of
319
0
  // this window.
320
0
  SizeOfState state(WindowsMallocSizeOf);
321
0
  nsWindowSizes windowSizes(state);
322
0
  aWindow->AddSizeOfIncludingThis(windowSizes);
323
0
324
0
  REPORT_SIZE("/dom/element-nodes", mDOMElementNodesSize,
325
0
              "Memory used by the element nodes in a window's DOM.");
326
0
327
0
  REPORT_SIZE("/dom/text-nodes", mDOMTextNodesSize,
328
0
              "Memory used by the text nodes in a window's DOM.");
329
0
330
0
  REPORT_SIZE("/dom/cdata-nodes", mDOMCDATANodesSize,
331
0
              "Memory used by the CDATA nodes in a window's DOM.");
332
0
333
0
  REPORT_SIZE("/dom/comment-nodes", mDOMCommentNodesSize,
334
0
              "Memory used by the comment nodes in a window's DOM.");
335
0
336
0
  REPORT_SIZE("/dom/event-targets", mDOMEventTargetsSize,
337
0
              "Memory used by the event targets table in a window's DOM, and "
338
0
              "the objects it points to, which include XHRs.");
339
0
340
0
  REPORT_SIZE("/dom/performance/user-entries", mDOMPerformanceUserEntries,
341
0
              "Memory used for performance user entries.");
342
0
343
0
  REPORT_SIZE("/dom/performance/resource-entries",
344
0
              mDOMPerformanceResourceEntries,
345
0
              "Memory used for performance resource entries.");
346
0
347
0
  REPORT_SIZE("/dom/media-query-lists", mDOMMediaQueryLists,
348
0
              "Memory used by MediaQueryList objects for the window's "
349
0
              "document.");
350
0
351
0
  REPORT_SIZE("/dom/other", mDOMOtherSize,
352
0
              "Memory used by a window's DOM that isn't measured by the "
353
0
              "other 'dom/' numbers.");
354
0
355
0
  REPORT_SIZE("/layout/style-sheets", mLayoutStyleSheetsSize,
356
0
              "Memory used by document style sheets within a window.");
357
0
358
0
  REPORT_SIZE("/layout/shadow-dom/style-sheets", mLayoutShadowDomStyleSheetsSize,
359
0
              "Memory used by Shadow DOM style sheets within a window.");
360
0
361
0
  // TODO(emilio): We might want to split this up between invalidation map /
362
0
  // element-and-pseudos / revalidation too just like the style set.
363
0
  REPORT_SIZE("/layout/shadow-dom/author-styles", mLayoutShadowDomAuthorStyles,
364
0
              "Memory used by Shadow DOM computed rule data within a window.");
365
0
366
0
  REPORT_SIZE("/layout/pres-shell", mLayoutPresShellSize,
367
0
              "Memory used by layout's PresShell, along with any structures "
368
0
              "allocated in its arena and not measured elsewhere, "
369
0
              "within a window.");
370
0
371
0
  REPORT_SIZE("/layout/style-sets/stylist/rule-tree",
372
0
              mLayoutStyleSetsStylistRuleTree,
373
0
              "Memory used by rule trees within style sets within a window.");
374
0
375
0
  REPORT_SIZE("/layout/style-sets/stylist/element-and-pseudos-maps",
376
0
              mLayoutStyleSetsStylistElementAndPseudosMaps,
377
0
              "Memory used by element and pseudos maps within style "
378
0
              "sets within a window.");
379
0
380
0
  REPORT_SIZE("/layout/style-sets/stylist/invalidation-map",
381
0
              mLayoutStyleSetsStylistInvalidationMap,
382
0
              "Memory used by invalidation maps within style sets "
383
0
              "within a window.");
384
0
385
0
  REPORT_SIZE("/layout/style-sets/stylist/revalidation-selectors",
386
0
              mLayoutStyleSetsStylistRevalidationSelectors,
387
0
              "Memory used by selectors for cache revalidation within "
388
0
              "style sets within a window.");
389
0
390
0
  REPORT_SIZE("/layout/style-sets/stylist/other",
391
0
              mLayoutStyleSetsStylistOther,
392
0
              "Memory used by other Stylist data within style sets "
393
0
              "within a window.");
394
0
395
0
  REPORT_SIZE("/layout/style-sets/other", mLayoutStyleSetsOther,
396
0
              "Memory used by other parts of style sets within a window.");
397
0
398
0
  REPORT_SIZE("/layout/element-data-objects",
399
0
              mLayoutElementDataObjects,
400
0
              "Memory used for ElementData objects, but not the things"
401
0
              "hanging off them.");
402
0
403
0
  REPORT_SIZE("/layout/text-runs", mLayoutTextRunsSize,
404
0
              "Memory used for text-runs (glyph layout) in the PresShell's "
405
0
              "frame tree, within a window.");
406
0
407
0
  REPORT_SIZE("/layout/pres-contexts", mLayoutPresContextSize,
408
0
              "Memory used for the PresContext in the PresShell's frame "
409
0
              "within a window.");
410
0
411
0
  REPORT_SIZE("/layout/frame-properties", mLayoutFramePropertiesSize,
412
0
              "Memory used for frame properties attached to frames "
413
0
              "within a window.");
414
0
415
0
  REPORT_SIZE("/layout/computed-values/dom", mLayoutComputedValuesDom,
416
0
              "Memory used by ComputedValues objects accessible from DOM "
417
0
              "elements.");
418
0
419
0
  REPORT_SIZE("/layout/computed-values/non-dom", mLayoutComputedValuesNonDom,
420
0
              "Memory used by ComputedValues objects not accessible from DOM "
421
0
              "elements.");
422
0
423
0
  REPORT_SIZE("/layout/computed-values/visited", mLayoutComputedValuesVisited,
424
0
              "Memory used by ComputedValues objects used for visited styles.");
425
0
426
0
  REPORT_SIZE("/property-tables", mPropertyTablesSize,
427
0
              "Memory used for the property tables within a window.");
428
0
429
0
  REPORT_SIZE("/bindings", mBindingsSize,
430
0
              "Memory used by bindings within a window.");
431
0
432
0
  REPORT_COUNT("/dom/event-targets", mDOMEventTargetsCount,
433
0
               "Number of non-node event targets in the event targets table "
434
0
               "in a window's DOM, such as XHRs.");
435
0
436
0
  REPORT_COUNT("/dom/event-listeners", mDOMEventListenersCount,
437
0
               "Number of event listeners in a window, including event "
438
0
               "listeners on nodes and other event targets.");
439
0
440
0
  REPORT_SIZE("/layout/line-boxes", mArenaSizes.mLineBoxes,
441
0
              "Memory used by line boxes within a window.");
442
0
443
0
  REPORT_SIZE("/layout/rule-nodes", mArenaSizes.mRuleNodes,
444
0
              "Memory used by CSS rule nodes within a window.");
445
0
446
0
  REPORT_SIZE("/layout/style-contexts", mArenaSizes.mComputedStyles,
447
0
              "Memory used by ComputedStyles within a window.");
448
0
449
0
  // There are many different kinds of style structs, but it is likely that
450
0
  // only a few matter. Implement a cutoff so we don't bloat about:memory with
451
0
  // many uninteresting entries.
452
0
  const size_t STYLE_SUNDRIES_THRESHOLD =
453
0
    js::MemoryReportingSundriesThreshold();
454
0
455
0
  // There are many different kinds of frames, but it is very likely
456
0
  // that only a few matter.  Implement a cutoff so we don't bloat
457
0
  // about:memory with many uninteresting entries.
458
0
  const size_t FRAME_SUNDRIES_THRESHOLD =
459
0
    js::MemoryReportingSundriesThreshold();
460
0
461
0
  size_t frameSundriesSize = 0;
462
0
#define FRAME_ID(classname, ...) \
463
0
  { \
464
0
    size_t size = windowSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(classname); \
465
0
    if (size < FRAME_SUNDRIES_THRESHOLD) { \
466
0
      frameSundriesSize += size; \
467
0
    } else { \
468
0
      REPORT_SUM_SIZE( \
469
0
        "/layout/frames/" # classname, size, \
470
0
        "Memory used by frames of type " #classname " within a window."); \
471
0
    } \
472
0
    aWindowTotalSizes->mArenaSizes.NS_ARENA_SIZES_FIELD(classname) += size; \
473
0
  }
474
0
#define ABSTRACT_FRAME_ID(...)
475
0
#include "nsFrameIdList.h"
476
0
#undef FRAME_ID
477
0
#undef ABSTRACT_FRAME_ID
478
0
479
0
  if (frameSundriesSize > 0) {
480
0
    REPORT_SUM_SIZE(
481
0
      "/layout/frames/sundries", frameSundriesSize,
482
0
      "The sum of all memory used by frames which were too small to be shown "
483
0
      "individually.");
484
0
  }
485
0
486
0
  // This is the style structs.
487
0
  size_t styleSundriesSize = 0;
488
0
#define STYLE_STRUCT(name_) \
489
0
  { \
490
0
    size_t size = windowSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_); \
491
0
    if (size < STYLE_SUNDRIES_THRESHOLD) { \
492
0
      styleSundriesSize += size; \
493
0
    } else { \
494
0
      REPORT_SUM_SIZE( \
495
0
        "/layout/style-structs/" # name_, size, \
496
0
        "Memory used by the " #name_ " style structs within a window."); \
497
0
    } \
498
0
    aWindowTotalSizes->mStyleSizes.NS_STYLE_SIZES_FIELD(name_) += size; \
499
0
  }
500
0
#include "nsStyleStructList.h"
501
0
#undef STYLE_STRUCT
502
0
503
0
  if (styleSundriesSize > 0) {
504
0
    REPORT_SUM_SIZE(
505
0
      "/layout/style-structs/sundries", styleSundriesSize,
506
0
      "The sum of all memory used by style structs which were too "
507
0
      "small to be shown individually.");
508
0
  }
509
0
510
0
#undef REPORT_SIZE
511
0
#undef REPORT_SUM_SIZE
512
0
#undef REPORT_COUNT
513
0
}
514
515
typedef nsTArray< RefPtr<nsGlobalWindowInner> > WindowArray;
516
517
NS_IMETHODIMP
518
nsWindowMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
519
                                       nsISupports* aData, bool aAnonymize)
520
0
{
521
0
  nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
522
0
    nsGlobalWindowInner::GetWindowsTable();
523
0
  NS_ENSURE_TRUE(windowsById, NS_OK);
524
0
525
0
  // Hold on to every window in memory so that window objects can't be
526
0
  // destroyed while we're calling the memory reporter callback.
527
0
  WindowArray windows;
528
0
  for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
529
0
    windows.AppendElement(iter.Data());
530
0
  }
531
0
532
0
  // Get the IDs of all the "ghost" windows, and call aHandleReport->Callback()
533
0
  // for each one.
534
0
  nsTHashtable<nsUint64HashKey> ghostWindows;
535
0
  CheckForGhostWindows(&ghostWindows);
536
0
  for (auto iter = ghostWindows.ConstIter(); !iter.Done(); iter.Next()) {
537
0
    nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
538
0
      nsGlobalWindowInner::GetWindowsTable();
539
0
    if (!windowsById) {
540
0
      NS_WARNING("Couldn't get window-by-id hashtable?");
541
0
      continue;
542
0
    }
543
0
544
0
    nsGlobalWindowInner* window = windowsById->Get(iter.Get()->GetKey());
545
0
    if (!window) {
546
0
      NS_WARNING("Could not look up window?");
547
0
      continue;
548
0
    }
549
0
550
0
    nsAutoCString path;
551
0
    path.AppendLiteral("ghost-windows/");
552
0
    AppendWindowURI(window, path, aAnonymize);
553
0
554
0
    aHandleReport->Callback(
555
0
      /* process = */ EmptyCString(),
556
0
      path,
557
0
      nsIMemoryReporter::KIND_OTHER,
558
0
      nsIMemoryReporter::UNITS_COUNT,
559
0
      /* amount = */ 1,
560
0
      /* description = */ NS_LITERAL_CSTRING("A ghost window."),
561
0
      aData);
562
0
  }
563
0
564
0
  MOZ_COLLECT_REPORT(
565
0
    "ghost-windows", KIND_OTHER, UNITS_COUNT, ghostWindows.Count(),
566
0
"The number of ghost windows present (the number of nodes underneath "
567
0
"explicit/window-objects/top(none)/ghost, modulo race conditions).  A ghost "
568
0
"window is not shown in any tab, is not in a tab group with any "
569
0
"non-detached windows, and has met these criteria for at least "
570
0
"memory.ghost_window_timeout_seconds, or has survived a round of "
571
0
"about:memory's minimize memory usage button.\n\n"
572
0
"Ghost windows can happen legitimately, but they are often indicative of "
573
0
"leaks in the browser or add-ons.");
574
0
575
0
  WindowPaths windowPaths;
576
0
  WindowPaths topWindowPaths;
577
0
578
0
  // Collect window memory usage.
579
0
  SizeOfState fakeState(nullptr);   // this won't be used
580
0
  nsWindowSizes windowTotalSizes(fakeState);
581
0
  for (uint32_t i = 0; i < windows.Length(); i++) {
582
0
    CollectWindowReports(windows[i],
583
0
                         &windowTotalSizes, &ghostWindows,
584
0
                         &windowPaths, &topWindowPaths, aHandleReport,
585
0
                         aData, aAnonymize);
586
0
  }
587
0
588
0
  // Report JS memory usage.  We do this from here because the JS memory
589
0
  // reporter needs to be passed |windowPaths|.
590
0
  xpc::JSReporter::CollectReports(&windowPaths, &topWindowPaths,
591
0
                                  aHandleReport, aData, aAnonymize);
592
0
593
0
#ifdef MOZ_XUL
594
0
  nsXULPrototypeCache::CollectMemoryReports(aHandleReport, aData);
595
0
#endif
596
0
597
0
#define REPORT(_path, _amount, _desc) \
598
0
  aHandleReport->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \
599
0
                          KIND_OTHER, UNITS_BYTES, _amount, \
600
0
                          NS_LITERAL_CSTRING(_desc), aData);
601
0
602
0
  REPORT("window-objects/dom/element-nodes",
603
0
         windowTotalSizes.mDOMElementNodesSize,
604
0
         "This is the sum of all windows' 'dom/element-nodes' numbers.");
605
0
606
0
  REPORT("window-objects/dom/text-nodes", windowTotalSizes.mDOMTextNodesSize,
607
0
         "This is the sum of all windows' 'dom/text-nodes' numbers.");
608
0
609
0
  REPORT("window-objects/dom/cdata-nodes", windowTotalSizes.mDOMCDATANodesSize,
610
0
         "This is the sum of all windows' 'dom/cdata-nodes' numbers.");
611
0
612
0
  REPORT("window-objects/dom/comment-nodes", windowTotalSizes.mDOMCommentNodesSize,
613
0
         "This is the sum of all windows' 'dom/comment-nodes' numbers.");
614
0
615
0
  REPORT("window-objects/dom/event-targets", windowTotalSizes.mDOMEventTargetsSize,
616
0
         "This is the sum of all windows' 'dom/event-targets' numbers.");
617
0
618
0
  REPORT("window-objects/dom/performance",
619
0
         windowTotalSizes.mDOMPerformanceUserEntries +
620
0
         windowTotalSizes.mDOMPerformanceResourceEntries,
621
0
         "This is the sum of all windows' 'dom/performance/' numbers.");
622
0
623
0
  REPORT("window-objects/dom/other", windowTotalSizes.mDOMOtherSize,
624
0
         "This is the sum of all windows' 'dom/other' numbers.");
625
0
626
0
  REPORT("window-objects/layout/style-sheets",
627
0
         windowTotalSizes.mLayoutStyleSheetsSize,
628
0
         "This is the sum of all windows' 'layout/style-sheets' numbers.");
629
0
630
0
  REPORT("window-objects/layout/pres-shell",
631
0
         windowTotalSizes.mLayoutPresShellSize,
632
0
         "This is the sum of all windows' 'layout/arenas' numbers.");
633
0
634
0
  REPORT("window-objects/layout/style-sets",
635
0
         windowTotalSizes.mLayoutStyleSetsStylistRuleTree +
636
0
         windowTotalSizes.mLayoutStyleSetsStylistElementAndPseudosMaps +
637
0
         windowTotalSizes.mLayoutStyleSetsStylistInvalidationMap +
638
0
         windowTotalSizes.mLayoutStyleSetsStylistRevalidationSelectors +
639
0
         windowTotalSizes.mLayoutStyleSetsStylistOther +
640
0
         windowTotalSizes.mLayoutStyleSetsOther,
641
0
         "This is the sum of all windows' 'layout/style-sets/' numbers.");
642
0
643
0
  REPORT("window-objects/layout/element-data-objects",
644
0
         windowTotalSizes.mLayoutElementDataObjects,
645
0
         "This is the sum of all windows' 'layout/element-data-objects' "
646
0
         "numbers.");
647
0
648
0
  REPORT("window-objects/layout/text-runs", windowTotalSizes.mLayoutTextRunsSize,
649
0
         "This is the sum of all windows' 'layout/text-runs' numbers.");
650
0
651
0
  REPORT("window-objects/layout/pres-contexts",
652
0
         windowTotalSizes.mLayoutPresContextSize,
653
0
         "This is the sum of all windows' 'layout/pres-contexts' numbers.");
654
0
655
0
  REPORT("window-objects/layout/frame-properties",
656
0
         windowTotalSizes.mLayoutFramePropertiesSize,
657
0
         "This is the sum of all windows' 'layout/frame-properties' numbers.");
658
0
659
0
  REPORT("window-objects/layout/computed-values",
660
0
         windowTotalSizes.mLayoutComputedValuesDom +
661
0
         windowTotalSizes.mLayoutComputedValuesNonDom +
662
0
         windowTotalSizes.mLayoutComputedValuesVisited,
663
0
         "This is the sum of all windows' 'layout/computed-values/' numbers.");
664
0
665
0
  REPORT("window-objects/property-tables",
666
0
         windowTotalSizes.mPropertyTablesSize,
667
0
         "This is the sum of all windows' 'property-tables' numbers.");
668
0
669
0
  REPORT("window-objects/layout/line-boxes",
670
0
         windowTotalSizes.mArenaSizes.mLineBoxes,
671
0
         "This is the sum of all windows' 'layout/line-boxes' numbers.");
672
0
673
0
  REPORT("window-objects/layout/rule-nodes",
674
0
         windowTotalSizes.mArenaSizes.mRuleNodes,
675
0
         "This is the sum of all windows' 'layout/rule-nodes' numbers.");
676
0
677
0
  REPORT("window-objects/layout/style-contexts",
678
0
         windowTotalSizes.mArenaSizes.mComputedStyles,
679
0
         "This is the sum of all windows' 'layout/style-contexts' numbers.");
680
0
681
0
  size_t frameTotal = 0;
682
0
#define FRAME_ID(classname, ...) \
683
0
  frameTotal += windowTotalSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(classname);
684
0
#define ABSTRACT_FRAME_ID(...)
685
0
#include "nsFrameIdList.h"
686
0
#undef FRAME_ID
687
0
#undef ABSTRACT_FRAME_ID
688
0
689
0
  REPORT("window-objects/layout/frames", frameTotal,
690
0
         "Memory used for layout frames within windows. "
691
0
         "This is the sum of all windows' 'layout/frames/' numbers.");
692
0
693
0
  size_t styleTotal = 0;
694
0
#define STYLE_STRUCT(name_) \
695
0
  styleTotal += \
696
0
    windowTotalSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_);
697
0
#include "nsStyleStructList.h"
698
0
#undef STYLE_STRUCT
699
0
700
0
  REPORT("window-objects/layout/style-structs", styleTotal,
701
0
         "Memory used for style structs within windows. This is the sum of "
702
0
         "all windows' 'layout/style-structs/' numbers.");
703
0
704
0
#undef REPORT
705
0
706
0
  return NS_OK;
707
0
}
708
709
uint32_t
710
nsWindowMemoryReporter::GetGhostTimeout()
711
0
{
712
0
  return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
713
0
}
714
715
NS_IMETHODIMP
716
nsWindowMemoryReporter::Observe(nsISupports *aSubject, const char *aTopic,
717
                                const char16_t *aData)
718
0
{
719
0
  if (!strcmp(aTopic, "after-minimize-memory-usage")) {
720
0
    ObserveAfterMinimizeMemoryUsage();
721
0
  } else if (!strcmp(aTopic, "cycle-collector-begin")) {
722
0
    if (mCheckTimer) {
723
0
      mCheckTimerWaitingForCCEnd = true;
724
0
      KillCheckTimer();
725
0
    }
726
0
    mCycleCollectorIsRunning = true;
727
0
  } else if (!strcmp(aTopic, "cycle-collector-end")) {
728
0
    mCycleCollectorIsRunning = false;
729
0
    if (mCheckTimerWaitingForCCEnd) {
730
0
      mCheckTimerWaitingForCCEnd = false;
731
0
      AsyncCheckForGhostWindows();
732
0
    }
733
0
  } else {
734
0
    MOZ_ASSERT(false);
735
0
  }
736
0
737
0
  return NS_OK;
738
0
}
739
740
void
741
nsWindowMemoryReporter::ObserveDOMWindowDetached(nsGlobalWindowInner* aWindow)
742
0
{
743
0
  nsWeakPtr weakWindow = do_GetWeakReference(aWindow);
744
0
  if (!weakWindow) {
745
0
    NS_WARNING("Couldn't take weak reference to a window?");
746
0
    return;
747
0
  }
748
0
749
0
  mDetachedWindows.Put(weakWindow, TimeStamp());
750
0
751
0
  AsyncCheckForGhostWindows();
752
0
}
753
754
// static
755
void
756
nsWindowMemoryReporter::CheckTimerFired(nsITimer* aTimer, void* aData)
757
0
{
758
0
  if (sWindowReporter) {
759
0
    MOZ_ASSERT(!sWindowReporter->mCycleCollectorIsRunning);
760
0
    sWindowReporter->CheckForGhostWindows();
761
0
  }
762
0
}
763
764
void
765
nsWindowMemoryReporter::AsyncCheckForGhostWindows()
766
0
{
767
0
  if (mCheckTimer) {
768
0
    return;
769
0
  }
770
0
771
0
  if (mCycleCollectorIsRunning) {
772
0
    mCheckTimerWaitingForCCEnd = true;
773
0
    return;
774
0
  }
775
0
776
0
  // If more than kTimeBetweenChecks seconds have elapsed since the last check,
777
0
  // timerDelay is 0.  Otherwise, it is kTimeBetweenChecks, reduced by the time
778
0
  // since the last check.  Reducing the delay by the time since the last check
779
0
  // prevents the timer from being completely starved if it is repeatedly killed
780
0
  // and restarted.
781
0
  int32_t timeSinceLastCheck = (TimeStamp::NowLoRes() - mLastCheckForGhostWindows).ToSeconds();
782
0
  int32_t timerDelay = (kTimeBetweenChecks - std::min(timeSinceLastCheck, kTimeBetweenChecks)) * PR_MSEC_PER_SEC;
783
0
784
0
  NS_NewTimerWithFuncCallback(getter_AddRefs(mCheckTimer),
785
0
                              CheckTimerFired, nullptr,
786
0
                              timerDelay, nsITimer::TYPE_ONE_SHOT,
787
0
                              "nsWindowMemoryReporter::AsyncCheckForGhostWindows_timer");
788
0
}
789
790
void
791
nsWindowMemoryReporter::ObserveAfterMinimizeMemoryUsage()
792
0
{
793
0
  // Someone claims they've done enough GC/CCs so that all eligible windows
794
0
  // have been free'd.  So we deem that any windows which satisfy ghost
795
0
  // criteria (1) and (2) now satisfy criterion (3) as well.
796
0
  //
797
0
  // To effect this change, we'll backdate some of our timestamps.
798
0
799
0
  TimeStamp minTimeStamp = TimeStamp::Now() -
800
0
                           TimeDuration::FromSeconds(GetGhostTimeout());
801
0
802
0
  for (auto iter = mDetachedWindows.Iter(); !iter.Done(); iter.Next()) {
803
0
    TimeStamp& timeStamp = iter.Data();
804
0
    if (!timeStamp.IsNull() && timeStamp > minTimeStamp) {
805
0
      timeStamp = minTimeStamp;
806
0
    }
807
0
  }
808
0
}
809
810
/**
811
 * Iterate over mDetachedWindows and update it to reflect the current state of
812
 * the world.  In particular:
813
 *
814
 *   - Remove weak refs to windows which no longer exist.
815
 *
816
 *   - Remove references to windows which are no longer detached.
817
 *
818
 *   - Reset the timestamp on detached windows which share a domain with a
819
 *     non-detached window (they no longer meet ghost criterion (2)).
820
 *
821
 *   - If a window now meets ghost criterion (2) but didn't before, set its
822
 *     timestamp to now.
823
 *
824
 * Additionally, if aOutGhostIDs is not null, fill it with the window IDs of
825
 * all ghost windows we found.
826
 */
827
void
828
nsWindowMemoryReporter::CheckForGhostWindows(
829
  nsTHashtable<nsUint64HashKey> *aOutGhostIDs /* = nullptr */)
830
0
{
831
0
  nsGlobalWindowInner::InnerWindowByIdTable *windowsById =
832
0
    nsGlobalWindowInner::GetWindowsTable();
833
0
  if (!windowsById) {
834
0
    NS_WARNING("GetWindowsTable returned null");
835
0
    return;
836
0
  }
837
0
838
0
  mLastCheckForGhostWindows = TimeStamp::NowLoRes();
839
0
  KillCheckTimer();
840
0
841
0
  nsTHashtable<nsPtrHashKey<TabGroup>> nonDetachedTabGroups;
842
0
843
0
  // Populate nonDetachedTabGroups.
844
0
  for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
845
0
    // Null outer window implies null top, but calling GetTop() when there's no
846
0
    // outer window causes us to spew debug warnings.
847
0
    nsGlobalWindowInner* window = iter.UserData();
848
0
    if (!window->GetOuterWindow() || !window->GetTopInternal()) {
849
0
      // This window is detached, so we don't care about its tab group.
850
0
      continue;
851
0
    }
852
0
853
0
    nonDetachedTabGroups.PutEntry(window->TabGroup());
854
0
  }
855
0
856
0
  // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
857
0
  // if it's not null.
858
0
  uint32_t ghostTimeout = GetGhostTimeout();
859
0
  TimeStamp now = mLastCheckForGhostWindows;
860
0
  mGhostWindowCount = 0;
861
0
  for (auto iter = mDetachedWindows.Iter(); !iter.Done(); iter.Next()) {
862
0
    nsWeakPtr weakKey = do_QueryInterface(iter.Key());
863
0
    nsCOMPtr<mozIDOMWindow> iwindow = do_QueryReferent(weakKey);
864
0
    if (!iwindow) {
865
0
      // The window object has been destroyed.  Stop tracking its weak ref in
866
0
      // our hashtable.
867
0
      iter.Remove();
868
0
      continue;
869
0
    }
870
0
871
0
    nsPIDOMWindowInner* window = nsPIDOMWindowInner::From(iwindow);
872
0
873
0
    // Avoid calling GetTop() if we have no outer window.  Nothing will break if
874
0
    // we do, but it will spew debug output, which can cause our test logs to
875
0
    // overflow.
876
0
    nsCOMPtr<nsPIDOMWindowOuter> top;
877
0
    if (window->GetOuterWindow()) {
878
0
      top = window->GetOuterWindow()->GetTop();
879
0
    }
880
0
881
0
    if (top) {
882
0
      // The window is no longer detached, so we no longer want to track it.
883
0
      iter.Remove();
884
0
      continue;
885
0
    }
886
0
887
0
    TimeStamp& timeStamp = iter.Data();
888
0
889
0
    if (nonDetachedTabGroups.GetEntry(window->TabGroup())) {
890
0
      // This window is in the same tab group as a non-detached
891
0
      // window, so reset its clock.
892
0
      timeStamp = TimeStamp();
893
0
    } else {
894
0
      // This window is not in the same tab group as a non-detached
895
0
      // window, so it meets ghost criterion (2).
896
0
      if (timeStamp.IsNull()) {
897
0
        // This may become a ghost window later; start its clock.
898
0
        timeStamp = now;
899
0
      } else if ((now - timeStamp).ToSeconds() > ghostTimeout) {
900
0
        // This definitely is a ghost window, so add it to aOutGhostIDs, if
901
0
        // that is not null.
902
0
        mGhostWindowCount++;
903
0
        if (aOutGhostIDs && window) {
904
0
          aOutGhostIDs->PutEntry(window->WindowID());
905
0
        }
906
0
      }
907
0
    }
908
0
  }
909
0
910
0
  Telemetry::ScalarSetMaximum(Telemetry::ScalarID::MEMORYREPORTER_MAX_GHOST_WINDOWS,
911
0
                              mGhostWindowCount);
912
0
}
913
914
/* static */ int64_t
915
nsWindowMemoryReporter::GhostWindowsDistinguishedAmount()
916
0
{
917
0
  return sWindowReporter->mGhostWindowCount;
918
0
}
919
920
void
921
nsWindowMemoryReporter::KillCheckTimer()
922
0
{
923
0
  if (mCheckTimer) {
924
0
    mCheckTimer->Cancel();
925
0
    mCheckTimer = nullptr;
926
0
  }
927
0
}
928
929
#ifdef DEBUG
930
/* static */ void
931
nsWindowMemoryReporter::UnlinkGhostWindows()
932
{
933
  if (!sWindowReporter) {
934
    return;
935
  }
936
937
  nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
938
    nsGlobalWindowInner::GetWindowsTable();
939
  if (!windowsById) {
940
    return;
941
  }
942
943
  // Hold on to every window in memory so that window objects can't be
944
  // destroyed while we're calling the UnlinkGhostWindows callback.
945
  WindowArray windows;
946
  for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
947
    windows.AppendElement(iter.Data());
948
  }
949
950
  // Get the IDs of all the "ghost" windows, and unlink them all.
951
  nsTHashtable<nsUint64HashKey> ghostWindows;
952
  sWindowReporter->CheckForGhostWindows(&ghostWindows);
953
  for (auto iter = ghostWindows.ConstIter(); !iter.Done(); iter.Next()) {
954
    nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
955
      nsGlobalWindowInner::GetWindowsTable();
956
    if (!windowsById) {
957
      continue;
958
    }
959
960
    RefPtr<nsGlobalWindowInner> window = windowsById->Get(iter.Get()->GetKey());
961
    if (window) {
962
      window->RiskyUnlink();
963
    }
964
  }
965
}
966
#endif