Bug 1363505 - Add tab squeeze reflow test. r?florian draft
authorMike Conley <mconley@mozilla.com>
Wed, 10 May 2017 22:16:12 -0400
changeset 587672 71f4d596efd1162dd0e833c06a4f923f7f829d68
parent 587671 eddca0161748d6360885dbe334cb5e6ae098d773
child 587673 4ffd918d9b2de5bc03ad106c95d752f98f42338b
push id61785
push usermconley@mozilla.com
push dateThu, 01 Jun 2017 14:35:55 +0000
reviewersflorian
bugs1363505
milestone55.0a1
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();
+}