Bug 827976 - Teach leaks.py to ignore the hidden window. r=Ehsan
authorFelipe Gomes <felipc@gmail.com>
Mon, 04 Mar 2019 20:21:31 +0000
changeset 520358 6013e1f3b6486837ce4ec98678afb6a39f37b10c
parent 520357 cd731fe8a50f93d96d36698d84a838d382df458b
child 520359 c7cfa9240f33da062f3e072b2cbc23c6377ec387
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersEhsan
bugs827976
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 827976 - Teach leaks.py to ignore the hidden window. r=Ehsan Differential Revision: https://phabricator.services.mozilla.com/D21089
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
testing/mochitest/leaks.py
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -434,22 +434,32 @@ nsDocShell::~nsDocShell() {
 
   if (--gDocShellCount == 0) {
     NS_IF_RELEASE(sURIFixup);
   }
 
   MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p destroyed\n", this));
 
 #ifdef DEBUG
+  nsAutoCString url;
+  if (mLastOpenedURI) {
+    url = mLastOpenedURI->GetSpecOrDefault();
+
+    // Data URLs can be very long, so truncate to avoid flooding the log.
+    const uint32_t maxURLLength = 1000;
+    if (url.Length() > maxURLLength) {
+      url.Truncate(maxURLLength);
+    }
+  }
   // We're counting the number of |nsDocShells| to help find leaks
   --gNumberOfDocShells;
   if (!PR_GetEnv("MOZ_QUIET")) {
-    printf_stderr("--DOCSHELL %p == %ld [pid = %d] [id = %s]\n", (void*)this,
-                  gNumberOfDocShells, getpid(),
-                  nsIDToCString(mHistoryID).get());
+    printf_stderr("--DOCSHELL %p == %ld [pid = %d] [id = %s] [url = %s]\n",
+                  (void*)this, gNumberOfDocShells, getpid(),
+                  nsIDToCString(mHistoryID).get(), url.get());
   }
 #endif
 }
 
 /* static */
 already_AddRefed<nsDocShell> nsDocShell::Create(
     BrowsingContext* aBrowsingContext) {
   MOZ_ASSERT(aBrowsingContext, "DocShell without a BrowsingContext!");
@@ -1165,16 +1175,20 @@ bool nsDocShell::SetCurrentURI(nsIURI* a
   bool uriIsEqual = false;
   if (!mCurrentURI || !aURI ||
       NS_FAILED(mCurrentURI->Equals(aURI, &uriIsEqual)) || !uriIsEqual) {
     mTitleValidForCurrentURI = false;
   }
 
   mCurrentURI = aURI;
 
+#ifdef DEBUG
+  mLastOpenedURI = aURI;
+#endif
+
   if (!NS_IsAboutBlank(mCurrentURI)) {
     mHasLoadedNonBlankURI = true;
   }
 
   bool isRoot = false;      // Is this the root docshell
   bool isSubFrame = false;  // Is this a subframe navigation?
 
   nsCOMPtr<nsIDocShellTreeItem> root;
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -971,16 +971,20 @@ class nsDocShell final : public nsDocLoa
 
   // An observed docshell wrapper is created when recording markers is enabled.
   mozilla::UniquePtr<mozilla::ObservedDocShell> mObserved;
 
   // mCurrentURI should be marked immutable on set if possible.
   nsCOMPtr<nsIURI> mCurrentURI;
   nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
 
+#ifdef DEBUG
+  nsCOMPtr<nsIURI> mLastOpenedURI;
+#endif
+
   // Reference to the SHEntry for this docshell until the page is destroyed.
   // Somebody give me better name
   nsCOMPtr<nsISHEntry> mOSHE;
 
   // Reference to the SHEntry for this docshell until the page is loaded
   // Somebody give me better name.
   // If mLSHE is non-null, non-pushState subframe loads don't create separate
   // root history entries. That is, frames loaded during the parent page
--- a/testing/mochitest/leaks.py
+++ b/testing/mochitest/leaks.py
@@ -16,17 +16,19 @@ class ShutdownLeaks(object):
     DOM windows (that are still around after test suite shutdown, despite running
     the GC) to the tests that created them and prints leak statistics.
     """
 
     def __init__(self, logger):
         self.logger = logger
         self.tests = []
         self.leakedWindows = {}
+        self.hiddenWindowsCount = 0
         self.leakedDocShells = set()
+        self.hiddenDocShellsCount = 0
         self.currentTest = None
         self.seenShutdown = set()
 
     def log(self, message):
         action = message['action']
 
         # Remove 'log' when clipboard is gone and/or structured.
         if action in ('log', 'process_output'):
@@ -74,16 +76,29 @@ class ShutdownLeaks(object):
                                   "shutdown" %
                                   (test["fileName"], len(test["leakedDocShells"])))
                 failures += 1
                 self.logger.info("TEST-INFO | %s | docShell(s) leaked: %s" %
                                  (test["fileName"], ', '.join(["[pid = %s] [id = %s]" %
                                                                x for x in test["leakedDocShells"]]
                                                               )))
 
+            if test["hiddenWindowsCount"] > 0:
+                # Note: to figure out how many hidden windows were created, we divide
+                # this number by 2, because 1 hidden window creation implies in
+                # 1 outer window + 1 inner window.
+                self.logger.info(
+                    "TEST-INFO | %s | This test created %d hidden window(s)"
+                    % (test["fileName"], test["hiddenWindowsCount"] / 2))
+
+            if test["hiddenDocShellsCount"] > 0:
+                self.logger.info(
+                    "TEST-INFO | %s | This test created %d hidden docshell(s)"
+                    % (test["fileName"], test["hiddenDocShellsCount"]))
+
         return failures
 
     def _logWindow(self, line):
         created = line[:2] == "++"
         pid = self._parseValue(line, "pid")
         serial = self._parseValue(line, "serial")
 
         # log line has invalid format
@@ -96,17 +111,21 @@ class ShutdownLeaks(object):
 
         if self.currentTest:
             windows = self.currentTest["windows"]
             if created:
                 windows.add(key)
             else:
                 windows.discard(key)
         elif int(pid) in self.seenShutdown and not created:
-            self.leakedWindows[key] = self._parseValue(line, "url")
+            url = self._parseValue(line, "url")
+            if not self._isHiddenWindowURL(url):
+                self.leakedWindows[key] = url
+            else:
+                self.hiddenWindowsCount += 1
 
     def _logDocShell(self, line):
         created = line[:2] == "++"
         pid = self._parseValue(line, "pid")
         id = self._parseValue(line, "id")
 
         # log line has invalid format
         if not pid or not id:
@@ -118,55 +137,65 @@ class ShutdownLeaks(object):
 
         if self.currentTest:
             docShells = self.currentTest["docShells"]
             if created:
                 docShells.add(key)
             else:
                 docShells.discard(key)
         elif int(pid) in self.seenShutdown and not created:
-            self.leakedDocShells.add(key)
+            url = self._parseValue(line, "url")
+            if not self._isHiddenWindowURL(url):
+                self.leakedDocShells.add(key)
+            else:
+                self.hiddenDocShellsCount += 1
 
     def _parseValue(self, line, name):
         match = re.search("\[%s = (.+?)\]" % name, line)
         if match:
             return match.group(1)
         return None
 
     def _parseLeakingTests(self):
         leakingTests = []
 
         for test in self.tests:
             leakedWindows = [
                 id for id in test["windows"] if id in self.leakedWindows]
             test["leakedWindows"] = [self.leakedWindows[id]
                                      for id in leakedWindows]
+            test["hiddenWindowsCount"] = self.hiddenWindowsCount
             test["leakedWindowsString"] = ', '.join(
                 ["[pid = %s] [serial = %s]" % x for x in leakedWindows])
             test["leakedDocShells"] = [
                 id for id in test["docShells"] if id in self.leakedDocShells]
+            test["hiddenDocShellsCount"] = self.hiddenDocShellsCount
             test["leakCount"] = len(
                 test["leakedWindows"]) + len(test["leakedDocShells"])
 
-            if test["leakCount"]:
+            if test["leakCount"] or test["hiddenWindowsCount"] or test["hiddenDocShellsCount"]:
                 leakingTests.append(test)
 
         return sorted(leakingTests, key=itemgetter("leakCount"), reverse=True)
 
     def _zipLeakedWindows(self, leakedWindows):
         counts = []
         counted = set()
 
         for url in leakedWindows:
             if url not in counted:
                 counts.append((url, leakedWindows.count(url)))
                 counted.add(url)
 
         return sorted(counts, key=itemgetter(1), reverse=True)
 
+    def _isHiddenWindowURL(self, url):
+        return (url == "resource://gre-resources/hiddenWindow.html" or  # Win / Linux
+                url == "chrome://browser/content/hiddenWindow.xul")     # Mac
+
 
 class LSANLeaks(object):
 
     """
     Parses the log when running an LSAN build, looking for interesting stack frames
     in allocation stacks, and prints out reports.
     """