Bug 1363505 - Add tab squeeze reflow test. r=florian
☠☠ backed out by e603b83ca395 ☠ ☠
authorMike Conley <mconley@mozilla.com>
Wed, 10 May 2017 22:16:12 -0400
changeset 361825 6f6b73f4f305a649c503ff9bba3d1d1ca0b1aedd
parent 361824 0c50bc93a3dc0c78eae2c3599912b86c03509aea
child 361826 494590d4307a1a06e5388c66ba23119f33a25b64
push id31945
push userryanvm@gmail.com
push dateThu, 01 Jun 2017 20:42:17 +0000
treeherdermozilla-central@15e32469eb04 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflorian
bugs1363505
milestone55.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 1363505 - Add tab squeeze reflow test. r=florian MozReview-Commit-ID: Jd7uVrNaMbh
browser/base/content/test/performance/browser.ini
browser/base/content/test/performance/browser_tabclose_reflows.js
browser/base/content/test/performance/browser_tabopen_reflows.js
browser/base/content/test/performance/browser_tabopen_squeeze_reflows.js
browser/base/content/test/performance/head.js
--- a/browser/base/content/test/performance/browser.ini
+++ b/browser/base/content/test/performance/browser.ini
@@ -1,9 +1,10 @@
 [DEFAULT]
 support-files =
   head.js
 [browser_startup.js]
 [browser_tabclose_reflows.js]
 [browser_tabopen_reflows.js]
+[browser_tabopen_squeeze_reflows.js]
 [browser_toolbariconcolor_restyles.js]
 [browser_windowclose_reflows.js]
 [browser_windowopen_reflows.js]
--- a/browser/base/content/test/performance/browser_tabclose_reflows.js
+++ b/browser/base/content/test/performance/browser_tabclose_reflows.js
@@ -23,37 +23,17 @@ if (gMultiProcessBrowser) {
   );
 }
 
 /*
  * This test ensures that there are no unexpected
  * uninterruptible reflows when closing new tabs.
  */
 add_task(async function() {
-  // If we've got a preloaded browser, get rid of it so that it
-  // doesn't interfere with the test if it's loading. We have to
-  // do this before we disable preloading or changing the new tab
-  // URL, otherwise _getPreloadedBrowser will return null, despite
-  // the preloaded browser existing.
-  let preloaded = gBrowser._getPreloadedBrowser();
-  if (preloaded) {
-    preloaded.remove();
-  }
-
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.newtab.preload", false]],
-  });
-
-  let aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
-                             .getService(Ci.nsIAboutNewTabService);
-  aboutNewTabService.newTabURL = "about:blank";
-
-  registerCleanupFunction(() => {
-    aboutNewTabService.resetNewTabURL();
-  });
+  await ensureNoPreloadedBrowser();
 
   // Because the tab strip is a scrollable frame, we can't use the
   // default dirtying function from withReflowObserver and reliably
   // get reflows for the strip. Instead, we provide a node that's
   // already in the scrollable frame to dirty - in this case, the
   // original tab.
   let origTab = gBrowser.selectedTab;
 
--- a/browser/base/content/test/performance/browser_tabopen_reflows.js
+++ b/browser/base/content/test/performance/browser_tabopen_reflows.js
@@ -36,37 +36,17 @@ const EXPECTED_REFLOWS = [
   ],
 ];
 
 /*
  * This test ensures that there are no unexpected
  * uninterruptible reflows when opening new tabs.
  */
 add_task(async function() {
-  // If we've got a preloaded browser, get rid of it so that it
-  // doesn't interfere with the test if it's loading. We have to
-  // do this before we disable preloading or changing the new tab
-  // URL, otherwise _getPreloadedBrowser will return null, despite
-  // the preloaded browser existing.
-  let preloaded = gBrowser._getPreloadedBrowser();
-  if (preloaded) {
-    preloaded.remove();
-  }
-
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.newtab.preload", false]],
-  });
-
-  let aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
-                             .getService(Ci.nsIAboutNewTabService);
-  aboutNewTabService.newTabURL = "about:blank";
-
-  registerCleanupFunction(() => {
-    aboutNewTabService.resetNewTabURL();
-  });
+  await ensureNoPreloadedBrowser();
 
   // Because the tab strip is a scrollable frame, we can't use the
   // default dirtying function from withReflowObserver and reliably
   // get reflows for the strip. Instead, we provide a node that's
   // already in the scrollable frame to dirty - in this case, the
   // original tab.
   let origTab = gBrowser.selectedTab;
 
@@ -78,9 +58,8 @@ add_task(async function() {
         false, e => e.propertyName === "max-width");
     await switchDone;
   }, EXPECTED_REFLOWS, window, origTab);
 
   let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
   await switchDone;
 });
-
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/performance/browser_tabopen_squeeze_reflows.js
@@ -0,0 +1,64 @@
+"use strict";
+
+/**
+ * WHOA THERE: We should never be adding new things to EXPECTED_REFLOWS. This
+ * is a whitelist that should slowly go away as we improve the performance of
+ * the front-end. Instead of adding more reflows to the whitelist, you should
+ * be modifying your code to avoid the reflow.
+ *
+ * See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
+ * for tips on how to do that.
+ */
+const EXPECTED_REFLOWS = [
+  [
+    "select@chrome://global/content/bindings/textbox.xml",
+    "focusAndSelectUrlBar@chrome://browser/content/browser.js",
+    "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
+  ],
+
+  [
+    "select@chrome://global/content/bindings/textbox.xml",
+    "focusAndSelectUrlBar@chrome://browser/content/browser.js",
+    "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
+  ],
+
+  [
+    "select@chrome://global/content/bindings/textbox.xml",
+    "focusAndSelectUrlBar@chrome://browser/content/browser.js",
+    "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
+  ],
+];
+
+/*
+ * This test ensures that there are no unexpected
+ * uninterruptible reflows when opening a new tab that will
+ * cause the existing tabs to squeeze smaller.
+ */
+add_task(async function() {
+  await ensureNoPreloadedBrowser();
+
+  // Compute the number of tabs we can put into the strip without
+  // overflowing, and remove one, so that we can create
+  // TAB_COUNT_FOR_SQUEEE tabs, and then one more, which should
+  // cause the tab to squeeze to a smaller size rather than overflow.
+  const TAB_COUNT_FOR_SQUEEZE = computeMaxTabCount() - 1;
+
+  await createTabs(TAB_COUNT_FOR_SQUEEZE);
+
+  // Because the tab strip is a scrollable frame, we can't use the
+  // default dirtying function from withReflowObserver and reliably
+  // get reflows for the strip. Instead, we provide a node that's
+  // already in the scrollable frame to dirty - in this case, the
+  // original tab.
+  let origTab = gBrowser.selectedTab;
+
+  await withReflowObserver(async function() {
+    let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone");
+    BrowserOpenTab();
+    await BrowserTestUtils.waitForEvent(gBrowser.selectedTab, "transitionend",
+      false, e => e.propertyName === "max-width");
+    await switchDone;
+  }, EXPECTED_REFLOWS, window, origTab);
+
+  await removeAllButFirstTab();
+});
--- a/browser/base/content/test/performance/head.js
+++ b/browser/base/content/test/performance/head.js
@@ -148,8 +148,92 @@ async function withReflowObserver(testFn
 
     els.removeListenerForAllEvents(win, dirtyFrameFn, true);
     docShell.removeWeakReflowObserver(observer);
 
     elemToDirty.style.margin = "";
   }
 }
 
+async function ensureNoPreloadedBrowser() {
+  // If we've got a preloaded browser, get rid of it so that it
+  // doesn't interfere with the test if it's loading. We have to
+  // do this before we disable preloading or changing the new tab
+  // URL, otherwise _getPreloadedBrowser will return null, despite
+  // the preloaded browser existing.
+  let preloaded = gBrowser._getPreloadedBrowser();
+  if (preloaded) {
+    preloaded.remove();
+  }
+
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.newtab.preload", false]],
+  });
+
+  let aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
+                             .getService(Ci.nsIAboutNewTabService);
+  aboutNewTabService.newTabURL = "about:blank";
+
+  registerCleanupFunction(() => {
+    aboutNewTabService.resetNewTabURL();
+  });
+}
+
+/**
+ * Calculate and return how many additional tabs can be fit into the
+ * tabstrip without causing it to overflow.
+ *
+ * @return int
+ *         The maximum additional tabs that can be fit into the
+ *         tabstrip without causing it to overflow.
+ */
+function computeMaxTabCount() {
+  let currentTabCount = gBrowser.tabs.length;
+  let newTabButton =
+    document.getAnonymousElementByAttribute(gBrowser.tabContainer,
+                                            "class", "tabs-newtab-button");
+  let newTabRect = newTabButton.getBoundingClientRect();
+  let tabStripRect = gBrowser.tabContainer.mTabstrip.getBoundingClientRect();
+  let availableTabStripWidth = tabStripRect.width - newTabRect.width;
+
+  let tabMinWidth =
+    parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth, 10);
+
+  let maxTabCount = Math.floor(availableTabStripWidth / tabMinWidth) - currentTabCount;
+  Assert.ok(maxTabCount > 0,
+            "Tabstrip needs to be wide enough to accomodate at least 1 more tab " +
+            "without overflowing.");
+  return maxTabCount;
+}
+
+/**
+ * Helper function that opens up some number of about:blank tabs, and wait
+ * until they're all fully open.
+ *
+ * @param howMany (int)
+ *        How many about:blank tabs to open.
+ */
+async function createTabs(howMany) {
+  let uris = [];
+  while (howMany--) {
+    uris.push("about:blank");
+  }
+
+  gBrowser.loadTabs(uris, true, false);
+
+  await BrowserTestUtils.waitForCondition(() => {
+    return Array.from(gBrowser.tabs).every(tab => tab._fullyOpen);
+  });
+}
+
+/**
+ * Removes all of the tabs except the originally selected
+ * tab, and waits until all of the DOM nodes have been
+ * completely removed from the tab strip.
+ */
+async function removeAllButFirstTab() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.tabs.warnOnCloseOtherTabs", false]],
+  });
+  gBrowser.removeAllTabsBut(gBrowser.tabs[0]);
+  await BrowserTestUtils.waitForCondition(() => gBrowser.tabs.length == 1);
+  await SpecialPowers.popPrefEnv();
+}