Bug 1363505 - Add tab squeeze reflow test. r=florian
authorMike Conley <mconley@mozilla.com>
Wed, 10 May 2017 22:16:12 -0400
changeset 361871 5e037ab2ee52aa9d1e26a8143f7967fa2dac0e25
parent 361870 798f637191ec569406ce4bbc8253423df1953bc0
child 361872 1ae492c2da06f838a2ebe6a639068fbe6b080d8c
push id31946
push userryanvm@gmail.com
push dateThu, 01 Jun 2017 20:43:38 +0000
treeherdermozilla-central@b138d2f271fd [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
@@ -15,37 +15,17 @@ const EXPECTED_REFLOWS = [
    */
 ];
 
 /*
  * 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
@@ -20,37 +20,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;
 
@@ -62,9 +42,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,52 @@
+"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",
+  ],
+];
+
+/*
+ * 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();
+}