1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/base/nsWindowMemoryReporter.h Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,247 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#ifndef nsWindowMemoryReporter_h__ 1.11 +#define nsWindowMemoryReporter_h__ 1.12 + 1.13 +#include "nsIMemoryReporter.h" 1.14 +#include "nsIObserver.h" 1.15 +#include "nsITimer.h" 1.16 +#include "nsDataHashtable.h" 1.17 +#include "nsWeakReference.h" 1.18 +#include "nsAutoPtr.h" 1.19 +#include "mozilla/Attributes.h" 1.20 +#include "mozilla/Assertions.h" 1.21 +#include "mozilla/MemoryReporting.h" 1.22 +#include "mozilla/PodOperations.h" 1.23 +#include "mozilla/TimeStamp.h" 1.24 +#include "nsArenaMemoryStats.h" 1.25 + 1.26 +class nsWindowSizes { 1.27 +#define FOR_EACH_SIZE(macro) \ 1.28 + macro(DOM, mDOMElementNodesSize) \ 1.29 + macro(DOM, mDOMTextNodesSize) \ 1.30 + macro(DOM, mDOMCDATANodesSize) \ 1.31 + macro(DOM, mDOMCommentNodesSize) \ 1.32 + macro(DOM, mDOMEventTargetsSize) \ 1.33 + macro(DOM, mDOMOtherSize) \ 1.34 + macro(Style, mStyleSheetsSize) \ 1.35 + macro(Other, mLayoutPresShellSize) \ 1.36 + macro(Style, mLayoutStyleSetsSize) \ 1.37 + macro(Other, mLayoutTextRunsSize) \ 1.38 + macro(Other, mLayoutPresContextSize) \ 1.39 + macro(Other, mPropertyTablesSize) \ 1.40 + 1.41 +public: 1.42 + nsWindowSizes(mozilla::MallocSizeOf aMallocSizeOf) 1.43 + : 1.44 + #define ZERO_SIZE(kind, mSize) mSize(0), 1.45 + FOR_EACH_SIZE(ZERO_SIZE) 1.46 + #undef ZERO_SIZE 1.47 + mDOMEventTargetsCount(0), 1.48 + mDOMEventListenersCount(0), 1.49 + mArenaStats(), 1.50 + mMallocSizeOf(aMallocSizeOf) 1.51 + {} 1.52 + 1.53 + void addToTabSizes(nsTabSizes *sizes) const { 1.54 + #define ADD_TO_TAB_SIZES(kind, mSize) sizes->add(nsTabSizes::kind, mSize); 1.55 + FOR_EACH_SIZE(ADD_TO_TAB_SIZES) 1.56 + #undef ADD_TO_TAB_SIZES 1.57 + mArenaStats.addToTabSizes(sizes); 1.58 + } 1.59 + 1.60 + #define DECL_SIZE(kind, mSize) size_t mSize; 1.61 + FOR_EACH_SIZE(DECL_SIZE); 1.62 + #undef DECL_SIZE 1.63 + 1.64 + uint32_t mDOMEventTargetsCount; 1.65 + uint32_t mDOMEventListenersCount; 1.66 + 1.67 + nsArenaMemoryStats mArenaStats; 1.68 + mozilla::MallocSizeOf mMallocSizeOf; 1.69 + 1.70 +#undef FOR_EACH_SIZE 1.71 +}; 1.72 + 1.73 +/** 1.74 + * nsWindowMemoryReporter is responsible for the 'explicit/window-objects' 1.75 + * memory reporter. 1.76 + * 1.77 + * We classify DOM window objects into one of three categories: 1.78 + * 1.79 + * - "active" windows, which are displayed in a tab (as the top-level window 1.80 + * or an iframe), 1.81 + * 1.82 + * - "cached" windows, which are in the fastback cache (aka the bfcache), and 1.83 + * 1.84 + * - "detached" windows, which have a null docshell. A window becomes 1.85 + * detached when its <iframe> or tab containing the window is destroyed -- 1.86 + * i.e., when the window is no longer active or cached. 1.87 + * 1.88 + * Additionally, we classify a subset of detached windows as "ghost" windows. 1.89 + * Although ghost windows can happen legitimately (a page can hold a reference 1.90 + * to a cross-domain window and then close its container), the presence of 1.91 + * ghost windows is often indicative of a memory leak. 1.92 + * 1.93 + * A window is a ghost if it meets the following three criteria: 1.94 + * 1.95 + * 1) The window is detached. 1.96 + * 1.97 + * 2) There exist no non-detached windows with the same base domain as 1.98 + * the window's principal. (For example, the base domain of 1.99 + * "wiki.mozilla.co.uk" is "mozilla.co.uk".) This criterion makes us less 1.100 + * likely to flag a legitimately held-alive detached window as a ghost. 1.101 + * 1.102 + * 3) The window has met criteria (1) and (2) above for at least 1.103 + * memory.ghost_window_timeout_seconds. This criterion is in place so we 1.104 + * don't immediately declare a window a ghost before the GC/CC has had a 1.105 + * chance to run. 1.106 + * 1.107 + * nsWindowMemoryReporter observes window detachment and uses mDetachedWindows 1.108 + * to remember when a window first met criteria (1) and (2). When we generate 1.109 + * a memory report, we use this accounting to determine which windows are 1.110 + * ghosts. 1.111 + * 1.112 + * 1.113 + * We use the following memory reporter path for active and cached windows: 1.114 + * 1.115 + * explicit/window-objects/top(<top-outer-uri>, id=<top-outer-id>)/<category>/window(<window-uri>)/... 1.116 + * 1.117 + * For detached and ghost windows, we use 1.118 + * 1.119 + * explicit/window-objects/top(none)/<category>/window(<window-uri>)/... 1.120 + * 1.121 + * Where 1.122 + * 1.123 + * - <category> is "active", "cached", "detached", or "ghost", as described 1.124 + * above. 1.125 + * 1.126 + * - <top-outer-id> is the window id of the top outer window (i.e. the tab, or 1.127 + * the top level chrome window). Exposing this ensures that each tab gets 1.128 + * its own sub-tree, even if multiple tabs are showing the same URI. 1.129 + * 1.130 + * - <top-uri> is the URI of the top window. Excepting special windows (such 1.131 + * as browser.xul or hiddenWindow.html) it's what the address bar shows for 1.132 + * the tab. 1.133 + * 1.134 + */ 1.135 +class nsWindowMemoryReporter MOZ_FINAL : public nsIMemoryReporter, 1.136 + public nsIObserver, 1.137 + public nsSupportsWeakReference 1.138 +{ 1.139 +public: 1.140 + NS_DECL_ISUPPORTS 1.141 + NS_DECL_NSIMEMORYREPORTER 1.142 + NS_DECL_NSIOBSERVER 1.143 + 1.144 + static void Init(); 1.145 + 1.146 + ~nsWindowMemoryReporter(); 1.147 + 1.148 +#ifdef DEBUG 1.149 + /** 1.150 + * Unlink all known ghost windows, to enable investigating what caused them 1.151 + * to become ghost windows in the first place. 1.152 + */ 1.153 + static void UnlinkGhostWindows(); 1.154 +#endif 1.155 + 1.156 +private: 1.157 + /** 1.158 + * nsGhostWindowReporter generates the "ghost-windows" report, which counts 1.159 + * the number of ghost windows present. 1.160 + */ 1.161 + class GhostWindowsReporter MOZ_FINAL : public nsIMemoryReporter 1.162 + { 1.163 + public: 1.164 + NS_DECL_ISUPPORTS 1.165 + 1.166 + static int64_t DistinguishedAmount(); 1.167 + 1.168 + NS_IMETHOD 1.169 + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData) 1.170 + { 1.171 + return MOZ_COLLECT_REPORT( 1.172 + "ghost-windows", KIND_OTHER, UNITS_COUNT, DistinguishedAmount(), 1.173 +"The number of ghost windows present (the number of nodes underneath " 1.174 +"explicit/window-objects/top(none)/ghost, modulo race conditions). A ghost " 1.175 +"window is not shown in any tab, does not share a domain with any non-detached " 1.176 +"windows, and has met these criteria for at least " 1.177 +"memory.ghost_window_timeout_seconds, or has survived a round of " 1.178 +"about:memory's minimize memory usage button.\n\n" 1.179 +"Ghost windows can happen legitimately, but they are often indicative of " 1.180 +"leaks in the browser or add-ons."); 1.181 + } 1.182 + }; 1.183 + 1.184 + // Protect ctor, use Init() instead. 1.185 + nsWindowMemoryReporter(); 1.186 + 1.187 + /** 1.188 + * Get the number of seconds for which a window must satisfy ghost criteria 1.189 + * (1) and (2) before we deem that it satisfies criterion (3). 1.190 + */ 1.191 + uint32_t GetGhostTimeout(); 1.192 + 1.193 + void ObserveDOMWindowDetached(nsISupports* aWindow); 1.194 + void ObserveAfterMinimizeMemoryUsage(); 1.195 + 1.196 + /** 1.197 + * Iterate over all weak window pointers in mDetachedWindows and update our 1.198 + * accounting of which windows meet ghost criterion (2). 1.199 + * 1.200 + * This method also cleans up mDetachedWindows, removing entries for windows 1.201 + * which have been destroyed or are no longer detached. 1.202 + * 1.203 + * If aOutGhostIDs is non-null, we populate it with the Window IDs of the 1.204 + * ghost windows. 1.205 + * 1.206 + * This is called asynchronously after we observe a DOM window being detached 1.207 + * from its docshell, and also right before we generate a memory report. 1.208 + */ 1.209 + void CheckForGhostWindows(nsTHashtable<nsUint64HashKey> *aOutGhostIDs = nullptr); 1.210 + 1.211 + /** 1.212 + * Eventually do a check for ghost windows, if we haven't done one recently 1.213 + * and we aren't already planning to do one soon. 1.214 + */ 1.215 + void AsyncCheckForGhostWindows(); 1.216 + 1.217 + /** 1.218 + * Kill the check timer, if it exists. 1.219 + */ 1.220 + void KillCheckTimer(); 1.221 + 1.222 + static void CheckTimerFired(nsITimer* aTimer, void* aClosure); 1.223 + 1.224 + /** 1.225 + * Maps a weak reference to a detached window (nsIWeakReference) to the time 1.226 + * when we observed that the window met ghost criterion (2) above. 1.227 + * 1.228 + * If the window has not yet met criterion (2) it maps to the null timestamp. 1.229 + * 1.230 + * (Although windows are not added to this table until they're detached, it's 1.231 + * possible for a detached window to become non-detached, and we won't 1.232 + * remove it from the table until CheckForGhostWindows runs.) 1.233 + */ 1.234 + nsDataHashtable<nsISupportsHashKey, mozilla::TimeStamp> mDetachedWindows; 1.235 + 1.236 + /** 1.237 + * Track the last time we ran CheckForGhostWindows(), to avoid running it 1.238 + * too often after a DOM window is detached. 1.239 + */ 1.240 + mozilla::TimeStamp mLastCheckForGhostWindows; 1.241 + 1.242 + nsCOMPtr<nsITimer> mCheckTimer; 1.243 + 1.244 + bool mCycleCollectorIsRunning; 1.245 + 1.246 + bool mCheckTimerWaitingForCCEnd; 1.247 +}; 1.248 + 1.249 +#endif // nsWindowMemoryReporter_h__ 1.250 +