LCOV - code coverage report
Current view: top level - dom/base - nsWindowMemoryReporter.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 38 363 10.5 %
Date: 2017-07-14 16:53:18 Functions: 8 28 28.6 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13