diff --git a/dom/base/nsWindowMemoryReporter.cpp b/dom/base/nsWindowMemoryReporter.cpp index ef5cc69aa34..9be4e1bc356 100644 --- a/dom/base/nsWindowMemoryReporter.cpp +++ b/dom/base/nsWindowMemoryReporter.cpp @@ -37,57 +37,26 @@ #include "nsWindowMemoryReporter.h" #include "nsGlobalWindow.h" -#include "nsIEffectiveTLDService.h" -#include "mozilla/Services.h" -#include "mozilla/Preferences.h" -#include "nsNetCID.h" -#include "nsPrintfCString.h" -using namespace mozilla; nsWindowMemoryReporter::nsWindowMemoryReporter() - : mCheckForGhostWindowsCallbackPending(false) { - mDetachedWindows.Init(); } -NS_IMPL_ISUPPORTS3(nsWindowMemoryReporter, nsIMemoryMultiReporter, nsIObserver, - nsSupportsWeakReference) +NS_IMPL_ISUPPORTS1(nsWindowMemoryReporter, nsIMemoryMultiReporter) /* static */ void nsWindowMemoryReporter::Init() { - // The memory reporter manager will own this object. - nsWindowMemoryReporter *windowReporter = new nsWindowMemoryReporter(); - NS_RegisterMemoryMultiReporter(windowReporter); - - nsCOMPtr os = services::GetObserverService(); - if (os) { - // DOM_WINDOW_DESTROYED_TOPIC announces what we call window "detachment", - // when a window's docshell is set to NULL. - os->AddObserver(windowReporter, DOM_WINDOW_DESTROYED_TOPIC, - /* weakRef = */ true); - os->AddObserver(windowReporter, "after-minimize-memory-usage", - /* weakRef = */ true); - } - - GhostURLsReporter *ghostMultiReporter = - new GhostURLsReporter(windowReporter); - NS_RegisterMemoryMultiReporter(ghostMultiReporter); - - NumGhostsReporter *ghostReporter = - new NumGhostsReporter(windowReporter); - NS_RegisterMemoryReporter(ghostReporter); + // The memory reporter manager is going to own this object. + NS_RegisterMemoryMultiReporter(new nsWindowMemoryReporter()); } -static already_AddRefed -GetWindowURI(nsIDOMWindow *aWindow) +static void +AppendWindowURI(nsGlobalWindow *aWindow, nsACString& aStr) { - nsCOMPtr pWindow = do_QueryInterface(aWindow); - NS_ENSURE_TRUE(pWindow, NULL); - - nsCOMPtr doc = do_QueryInterface(pWindow->GetExtantDocument()); + nsCOMPtr doc = do_QueryInterface(aWindow->GetExtantDocument()); nsCOMPtr uri; if (doc) { @@ -95,25 +64,13 @@ GetWindowURI(nsIDOMWindow *aWindow) } if (!uri) { - nsCOMPtr scriptObjPrincipal = - do_QueryInterface(aWindow); - NS_ENSURE_TRUE(scriptObjPrincipal, NULL); - - nsIPrincipal *principal = scriptObjPrincipal->GetPrincipal(); + nsIPrincipal *principal = aWindow->GetPrincipal(); if (principal) { principal->GetURI(getter_AddRefs(uri)); } } - return uri.forget(); -} - -static void -AppendWindowURI(nsGlobalWindow *aWindow, nsACString& aStr) -{ - nsCOMPtr uri = GetWindowURI(aWindow); - if (uri) { nsCString spec; uri->GetSpec(spec); @@ -125,8 +82,6 @@ AppendWindowURI(nsGlobalWindow *aWindow, nsACString& aStr) aStr += spec; } else { - // If we're unable to find a URI, we're dealing with a chrome window with - // no document in it (or somesuch), so we call this a "system window". aStr += NS_LITERAL_CSTRING("[system]"); } } @@ -136,31 +91,68 @@ NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(DOMStyleMallocSizeOf, "windows") static nsresult CollectWindowReports(nsGlobalWindow *aWindow, nsWindowSizes *aWindowTotalSizes, - nsTHashtable *aGhostWindowIDs, nsIMemoryMultiReporterCallback *aCb, nsISupports *aClosure) { + // DOM window objects fall into one of three categories: + // - "active" windows are currently either displayed in an active + // tab, or a child of such a window. + // - "cached" windows are in the fastback cache. + // - "other" windows are closed (or navigated away from w/o being + // cached) yet held alive by either a website or our code. The + // latter case may be a memory leak, but not necessarily. + // + // For each window we show how much memory the window and its + // document, etc, use, and we report those per URI, where the URI is + // the document URI, if available, or the codebase of the principal in + // the window. In the case where we're unable to find a URI we're + // dealing with a chrome window with no document in it (or somesuch), + // and for that we make the URI be the string "[system]". + // + // Outer windows are lumped in with inner windows, because the amount + // of memory used by outer windows is small. + // + // The path we give to the reporter callback for "active" and "cached" + // windows (both inner and outer) is as follows: + // + // explicit/window-objects/top(, id=)//window()/... + // + // The path we give for "other" windows is as follows: + // + // explicit/window-objects/top(none)/window()/... + // + // Where: + // - is "active" or "cached", as described above. + // - is the window id (nsPIDOMWindow::WindowID()) of + // the top outer window (i.e. tab, or top level chrome window). + // - is the URI of the top outer window. Excepting + // special windows (such as browser.xul or hiddenWindow.html) it's + // what the address bar shows for the tab. + // - is the URI of aWindow. + // + // Exposing the top-outer-id ensures that each tab gets its own + // sub-tree, even if multiple tabs are showing the same URI. + nsCAutoString windowPath("explicit/window-objects/"); - // Our window should have a null top iff it has a null docshell. - MOZ_ASSERT(!!aWindow->GetTop() == !!aWindow->GetDocShell()); - nsGlobalWindow *top = aWindow->GetTop(); + windowPath += NS_LITERAL_CSTRING("top("); if (top) { - windowPath += NS_LITERAL_CSTRING("top("); AppendWindowURI(top, windowPath); windowPath += NS_LITERAL_CSTRING(", id="); windowPath.AppendInt(top->WindowID()); - windowPath += NS_LITERAL_CSTRING(")/"); + } else { + windowPath += NS_LITERAL_CSTRING("none"); + } + windowPath += NS_LITERAL_CSTRING(")/"); + nsIDocShell *docShell = aWindow->GetDocShell(); + if (docShell) { + MOZ_ASSERT(top, "'cached' or 'active' window lacks a top window"); windowPath += aWindow->IsFrozen() ? NS_LITERAL_CSTRING("cached/") : NS_LITERAL_CSTRING("active/"); } else { - if (aGhostWindowIDs->Contains(aWindow->WindowID())) { - windowPath += NS_LITERAL_CSTRING("top(none)/ghost/"); - } else { - windowPath += NS_LITERAL_CSTRING("top(none)/detached/"); - } + MOZ_ASSERT(!top, "'other' window has a top window"); } windowPath += NS_LITERAL_CSTRING("window("); @@ -241,20 +233,12 @@ nsWindowMemoryReporter::CollectReports(nsIMemoryMultiReporterCallback* aCb, WindowArray windows; windowsById->Enumerate(GetWindows, &windows); - // Get the IDs of all the "ghost" windows. - nsTHashtable ghostWindows; - ghostWindows.Init(); - CheckForGhostWindows(&ghostWindows); - - nsCOMPtr tldService = do_GetService( - NS_EFFECTIVETLDSERVICE_CONTRACTID); - NS_ENSURE_STATE(tldService); - // Collect window memory usage. + nsRefPtr *w = windows.Elements(); + nsRefPtr *end = w + windows.Length(); nsWindowSizes windowTotalSizes(NULL); - for (PRUint32 i = 0; i < windows.Length(); i++) { - nsresult rv = CollectWindowReports(windows[i], &windowTotalSizes, - &ghostWindows, aCb, aClosure); + for (; w != end; ++w) { + nsresult rv = CollectWindowReports(*w, &windowTotalSizes, aCb, aClosure); NS_ENSURE_SUCCESS(rv, rv); } @@ -302,391 +286,4 @@ nsWindowMemoryReporter::GetExplicitNonHeap(PRInt64* aAmount) return NS_OK; } -PRUint32 -nsWindowMemoryReporter::GetGhostTimeout() -{ - return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60); -} -NS_IMETHODIMP -nsWindowMemoryReporter::Observe(nsISupports *aSubject, const char *aTopic, - const PRUnichar *aData) -{ - if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC)) { - ObserveDOMWindowDetached(aSubject); - } else if (!strcmp(aTopic, "after-minimize-memory-usage")) { - ObserveAfterMinimizeMemoryUsage(); - } else { - MOZ_ASSERT(false); - } - - return NS_OK; -} - -void -nsWindowMemoryReporter::ObserveDOMWindowDetached(nsISupports* aWindow) -{ - nsWeakPtr weakWindow = do_GetWeakReference(aWindow); - if (!weakWindow) { - NS_WARNING("Couldn't take weak reference to a window?"); - return; - } - - mDetachedWindows.Put(weakWindow, TimeStamp()); - - if (!mCheckForGhostWindowsCallbackPending) { - nsCOMPtr runnable = - NS_NewRunnableMethod(this, - &nsWindowMemoryReporter::CheckForGhostWindowsCallback); - NS_DispatchToCurrentThread(runnable); - mCheckForGhostWindowsCallbackPending = true; - } -} - -static PLDHashOperator -BackdateTimeStampsEnumerator(nsISupports *aKey, TimeStamp &aTimeStamp, - void* aClosure) -{ - TimeStamp *minTimeStamp = static_cast(aClosure); - - if (!aTimeStamp.IsNull() && aTimeStamp > *minTimeStamp) { - aTimeStamp = *minTimeStamp; - } - - return PL_DHASH_NEXT; -} - -void -nsWindowMemoryReporter::ObserveAfterMinimizeMemoryUsage() -{ - // Someone claims they've done enough GC/CCs so that all eligible windows - // have been free'd. So we deem that any windows which satisfy ghost - // criteria (1) and (2) now satisfy criterion (3) as well. - // - // To effect this change, we'll backdate some of our timestamps. - - TimeStamp minTimeStamp = TimeStamp::Now() - - TimeDuration::FromSeconds(GetGhostTimeout()); - - mDetachedWindows.Enumerate(BackdateTimeStampsEnumerator, - &minTimeStamp); -} - -void -nsWindowMemoryReporter::CheckForGhostWindowsCallback() -{ - mCheckForGhostWindowsCallbackPending = false; - CheckForGhostWindows(); -} - -struct CheckForGhostWindowsEnumeratorData -{ - nsTHashtable *nonDetachedDomains; - nsTHashtable *ghostWindowIDs; - nsIEffectiveTLDService *tldService; - PRUint32 ghostTimeout; - TimeStamp now; -}; - -static PLDHashOperator -CheckForGhostWindowsEnumerator(nsISupports *aKey, TimeStamp& aTimeStamp, - void* aClosure) -{ - CheckForGhostWindowsEnumeratorData *data = - static_cast(aClosure); - - nsWeakPtr weakKey = do_QueryInterface(aKey); - nsCOMPtr window = do_QueryReferent(weakKey); - if (!window) { - // The window object has been destroyed. Stop tracking its weak ref in our - // hashtable. - return PL_DHASH_REMOVE; - } - - nsCOMPtr top; - window->GetTop(getter_AddRefs(top)); - if (top) { - // The window is no longer detached, so we no longer want to track it. - return PL_DHASH_REMOVE; - } - - nsCOMPtr uri = GetWindowURI(window); - - nsCAutoString domain; - if (uri) { - // GetBaseDomain works fine if |uri| is null, but it outputs a warning - // which ends up overrunning the mochitest logs. - data->tldService->GetBaseDomain(uri, 0, domain); - } - - if (data->nonDetachedDomains->Contains(domain)) { - // This window shares a domain with a non-detached window, so reset its - // clock. - aTimeStamp = TimeStamp(); - } else { - // This window does not share a domain with a non-detached window, so it - // meets ghost criterion (2). - if (aTimeStamp.IsNull()) { - // This may become a ghost window later; start its clock. - aTimeStamp = data->now; - } else if ((data->now - aTimeStamp).ToSeconds() > data->ghostTimeout) { - // This definitely is a ghost window, so add it to ghostWindowIDs, if - // that is not null. - if (data->ghostWindowIDs) { - nsCOMPtr pWindow = do_QueryInterface(window); - if (pWindow) { - data->ghostWindowIDs->PutEntry(pWindow->WindowID()); - } - } - } - } - - return PL_DHASH_NEXT; -} - -struct GetNonDetachedWindowDomainsEnumeratorData -{ - nsTHashtable *nonDetachedDomains; - nsIEffectiveTLDService *tldService; -}; - -static PLDHashOperator -GetNonDetachedWindowDomainsEnumerator(const PRUint64& aId, nsGlobalWindow* aWindow, - void* aClosure) -{ - GetNonDetachedWindowDomainsEnumeratorData *data = - static_cast(aClosure); - - if (!aWindow->GetTop()) { - // This window is detached, so we don't care about its domain. - return PL_DHASH_NEXT; - } - - nsCOMPtr uri = GetWindowURI(aWindow); - - nsCAutoString domain; - if (uri) { - data->tldService->GetBaseDomain(uri, 0, domain); - } - - data->nonDetachedDomains->PutEntry(domain); - return PL_DHASH_NEXT; -} - -/** - * Iterate over mDetachedWindows and update it to reflect the current state of - * the world. In particular: - * - * - Remove weak refs to windows which no longer exist. - * - * - Remove references to windows which are no longer detached. - * - * - Reset the timestamp on detached windows which share a domain with a - * non-detached window (they no longer meet ghost criterion (2)). - * - * - If a window now meets ghost criterion (2) but didn't before, set its - * timestamp to now. - * - * Additionally, if aOutGhostIDs is not null, fill it with the window IDs of - * all ghost windows we found. - */ -void -nsWindowMemoryReporter::CheckForGhostWindows( - nsTHashtable *aOutGhostIDs /* = NULL */) -{ - nsCOMPtr tldService = do_GetService( - NS_EFFECTIVETLDSERVICE_CONTRACTID); - if (!tldService) { - NS_WARNING("Couldn't get TLDService."); - return; - } - - nsGlobalWindow::WindowByIdTable *windowsById = - nsGlobalWindow::GetWindowsTable(); - if (!windowsById) { - NS_WARNING("GetWindowsTable returned null"); - return; - } - - nsTHashtable nonDetachedWindowDomains; - nonDetachedWindowDomains.Init(); - - // Populate nonDetachedWindowDomains. - GetNonDetachedWindowDomainsEnumeratorData nonDetachedEnumData = - { &nonDetachedWindowDomains, tldService }; - windowsById->EnumerateRead(GetNonDetachedWindowDomainsEnumerator, - &nonDetachedEnumData); - - // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs, - // if it's not null. - CheckForGhostWindowsEnumeratorData ghostEnumData = - { &nonDetachedWindowDomains, aOutGhostIDs, tldService, - GetGhostTimeout(), TimeStamp::Now() }; - mDetachedWindows.Enumerate(CheckForGhostWindowsEnumerator, - &ghostEnumData); -} - -NS_IMPL_ISUPPORTS1(nsWindowMemoryReporter::GhostURLsReporter, - nsIMemoryMultiReporter) - -nsWindowMemoryReporter:: -GhostURLsReporter::GhostURLsReporter( - nsWindowMemoryReporter* aWindowReporter) - : mWindowReporter(aWindowReporter) -{ -} - -NS_IMETHODIMP -nsWindowMemoryReporter:: -GhostURLsReporter::GetName(nsACString& aName) -{ - aName.AssignLiteral("ghost-windows"); - return NS_OK; -} - -NS_IMETHODIMP -nsWindowMemoryReporter:: -GhostURLsReporter::GetExplicitNonHeap(PRInt64* aOut) -{ - *aOut = 0; - return NS_OK; -} - -struct ReportGhostWindowsEnumeratorData -{ - nsIMemoryMultiReporterCallback* callback; - nsISupports* closure; - nsresult rv; -}; - -static PLDHashOperator -ReportGhostWindowsEnumerator(nsUint64HashKey* aIDHashKey, void* aClosure) -{ - ReportGhostWindowsEnumeratorData *data = - static_cast(aClosure); - - nsGlobalWindow::WindowByIdTable* windowsById = - nsGlobalWindow::GetWindowsTable(); - if (!windowsById) { - NS_WARNING("Couldn't get window-by-id hashtable?"); - return PL_DHASH_NEXT; - } - - nsGlobalWindow* window = windowsById->Get(aIDHashKey->GetKey()); - if (!window) { - NS_WARNING("Could not look up window?"); - return PL_DHASH_NEXT; - } - - nsCAutoString path; - path.AppendLiteral("ghost-windows/"); - AppendWindowURI(window, path); - - nsresult rv = data->callback->Callback( - /* process = */ EmptyCString(), - path, - nsIMemoryReporter::KIND_SUMMARY, - nsIMemoryReporter::UNITS_COUNT, - /* amount = */ 1, - /* desc = */ EmptyCString(), - data->closure); - - if (NS_FAILED(rv) && NS_SUCCEEDED(data->rv)) { - data->rv = rv; - } - - return PL_DHASH_NEXT; -} - -NS_IMETHODIMP -nsWindowMemoryReporter:: -GhostURLsReporter::CollectReports( - nsIMemoryMultiReporterCallback* aCb, - nsISupports* aClosure) -{ - // Get the IDs of all the ghost windows in existance. - nsTHashtable ghostWindows; - ghostWindows.Init(); - mWindowReporter->CheckForGhostWindows(&ghostWindows); - - ReportGhostWindowsEnumeratorData reportGhostWindowsEnumData = - { aCb, aClosure, NS_OK }; - - // Call aCb->Callback() for each ghost window. - ghostWindows.EnumerateEntries(ReportGhostWindowsEnumerator, - &reportGhostWindowsEnumData); - - return reportGhostWindowsEnumData.rv; -} - -NS_IMPL_ISUPPORTS1(nsWindowMemoryReporter::NumGhostsReporter, - nsIMemoryReporter) - -nsWindowMemoryReporter:: -NumGhostsReporter::NumGhostsReporter( - nsWindowMemoryReporter *aWindowReporter) - : mWindowReporter(aWindowReporter) -{} - -NS_IMETHODIMP -nsWindowMemoryReporter:: -NumGhostsReporter::GetProcess(nsACString& aProcess) -{ - aProcess.AssignLiteral(""); - return NS_OK; -} - -NS_IMETHODIMP -nsWindowMemoryReporter:: -NumGhostsReporter::GetPath(nsACString& aPath) -{ - aPath.AssignLiteral("ghost-windows"); - return NS_OK; -} - -NS_IMETHODIMP -nsWindowMemoryReporter:: -NumGhostsReporter::GetKind(PRInt32* aKind) -{ - *aKind = KIND_OTHER; - return NS_OK; -} - -NS_IMETHODIMP -nsWindowMemoryReporter:: -NumGhostsReporter::GetUnits(PRInt32* aUnits) -{ - *aUnits = nsIMemoryReporter::UNITS_COUNT; - return NS_OK; -} - -NS_IMETHODIMP -nsWindowMemoryReporter:: -NumGhostsReporter::GetDescription(nsACString& aDesc) -{ - nsPrintfCString str(1024, -"The number of ghost windows present (the number of nodes underneath \ -explicit/window-objects/top(none)/ghost, modulo race conditions). A ghost \ -window is not shown in any tab, does not share a domain with any non-detached \ -windows, and has met these criteria for at least %ds \ -(memory.ghost_window_timeout_seconds) or has survived a round of about:memory's \ -minimize memory usage button.\n\n\ -Ghost windows can happen legitimately, but they are often indicative of leaks \ -in the browser or add-ons.", - mWindowReporter->GetGhostTimeout()); - - aDesc.Assign(str); - return NS_OK; -} - -NS_IMETHODIMP -nsWindowMemoryReporter:: -NumGhostsReporter::GetAmount(PRInt64* aAmount) -{ - nsTHashtable ghostWindows; - ghostWindows.Init(); - mWindowReporter->CheckForGhostWindows(&ghostWindows); - - *aAmount = ghostWindows.Count(); - return NS_OK; -} diff --git a/dom/base/nsWindowMemoryReporter.h b/dom/base/nsWindowMemoryReporter.h index b0c7e0b420e..bf9497ce574 100644 --- a/dom/base/nsWindowMemoryReporter.h +++ b/dom/base/nsWindowMemoryReporter.h @@ -39,11 +39,6 @@ #define nsWindowMemoryReporter_h__ #include "nsIMemoryReporter.h" -#include "nsIObserver.h" -#include "nsDataHashtable.h" -#include "nsWeakReference.h" -#include "nsAutoPtr.h" -#include "mozilla/TimeStamp.h" // This should be used for any nsINode sub-class that has fields of its own // that it needs to measure; any sub-class that doesn't use it will inherit @@ -54,176 +49,29 @@ class nsWindowSizes { public: - nsWindowSizes(nsMallocSizeOfFun aMallocSizeOf) { - memset(this, 0, sizeof(nsWindowSizes)); - mMallocSizeOf = aMallocSizeOf; - } - nsMallocSizeOfFun mMallocSizeOf; - size_t mDOM; - size_t mStyleSheets; - size_t mLayoutArenas; - size_t mLayoutStyleSets; - size_t mLayoutTextRuns; + nsWindowSizes(nsMallocSizeOfFun aMallocSizeOf) { + memset(this, 0, sizeof(nsWindowSizes)); + mMallocSizeOf = aMallocSizeOf; + } + nsMallocSizeOfFun mMallocSizeOf; + size_t mDOM; + size_t mStyleSheets; + size_t mLayoutArenas; + size_t mLayoutStyleSets; + size_t mLayoutTextRuns; }; -/** - * nsWindowMemoryReporter is responsible for the 'explicit/window-objects' - * memory reporter. - * - * We classify DOM window objects into one of three categories: - * - * - "active" windows, which are displayed in a tab (as the top-level window - * or an iframe), - * - * - "cached" windows, which are in the fastback cache (aka the bfcache), and - * - * - "detached" windows, which have a null docshell. A window becomes - * detached when its