Bug 1358719 - Add window resize reflow test. r?florian draft
authorMike Conley <mconley@mozilla.com>
Wed, 07 Mar 2018 13:29:25 -0500
changeset 766392 40cc067e07edd2d7491efddc73a4a14bfc2e8681
parent 766391 df4346b9df6fabaaa7bae6160d7c68d578dffded
push id102313
push usermconley@mozilla.com
push dateMon, 12 Mar 2018 20:33:28 +0000
reviewersflorian
bugs1358719
milestone61.0a1
Bug 1358719 - Add window resize reflow test. r?florian MozReview-Commit-ID: 57mLswnXhC8
browser/base/content/test/performance/browser.ini
browser/base/content/test/performance/browser_window_resize_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
@@ -25,12 +25,13 @@ run-if = debug || devedition || nightly_
 [browser_tabopen_squeeze_reflows.js]
 [browser_tabstrip_overflow_underflow_reflows.js]
 [browser_tabswitch_reflows.js]
 [browser_toolbariconcolor_restyles.js]
 [browser_urlbar_keyed_search_reflows.js]
 skip-if = (os == 'linux') || (os == 'win' && debug) # Disabled on Linux and Windows debug due to perma failures. Bug 1392320.
 [browser_urlbar_search_reflows.js]
 skip-if = (debug || ccov) && (os == 'linux' || os == 'win') # Disabled on Linux and Windows debug and ccov due to intermittent timeouts. Bug 1414126, bug 1426611.
+[browser_window_resize_reflows.js]
 [browser_windowclose_reflows.js]
 [browser_windowopen_flicker.js]
 skip-if = (debug && os == 'win') # Disabled on windows debug for intermittent leaks
 [browser_windowopen_reflows.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/performance/browser_window_resize_reflows.js
@@ -0,0 +1,138 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"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 = [
+  {
+    stack: [
+      "onOverflow@resource:///modules/CustomizableUI.jsm",
+    ],
+    maxCount: 48,
+  },
+
+  {
+    stack: [
+      "_moveItemsBackToTheirOrigin@resource:///modules/CustomizableUI.jsm",
+      "_onLazyResize@resource:///modules/CustomizableUI.jsm",
+    ],
+    maxCount: 5,
+  },
+
+  {
+    stack: [
+      "_onLazyResize@resource:///modules/CustomizableUI.jsm",
+    ],
+    maxCount: 4,
+  },
+];
+
+const gToolbar = document.getElementById("PersonalToolbar");
+
+/**
+ * Sets the visibility state on the Bookmarks Toolbar, and
+ * waits for it to transition to fully visible.
+ *
+ * @param visible (bool)
+ *        Whether or not the bookmarks toolbar should be made visible.
+ * @returns Promise
+ */
+async function toggleBookmarksToolbar(visible) {
+  let transitionPromise =
+    BrowserTestUtils.waitForEvent(gToolbar, "transitionend",
+                                  e => e.propertyName == "max-height");
+
+  setToolbarVisibility(gToolbar, visible);
+  await transitionPromise;
+}
+
+/**
+ * Resizes a browser window to a particular width and height, and
+ * waits for it to reach a "steady state" with respect to its overflowing
+ * toolbars.
+ * @param win (browser window)
+ *        The window to resize.
+ * @param width (int)
+ *        The width to resize the window to.
+ * @param height (int)
+ *        The height to resize the window to.
+ * @returns Promise
+ */
+async function resizeWindow(win, width, height) {
+  let toolbarEvent =
+    BrowserTestUtils.waitForEvent(win, "BookmarksToolbarVisibilityUpdated");
+  let resizeEvent =
+    BrowserTestUtils.waitForEvent(win, "resize");
+  let dwu = win.QueryInterface(Ci.nsIInterfaceRequestor)
+               .getInterface(Ci.nsIDOMWindowUtils);
+  dwu.ensureDirtyRootFrame();
+  win.resizeTo(width, height);
+  await resizeEvent;
+  forceImmediateToolbarOverflowHandling(win);
+  await toolbarEvent;
+}
+
+/*
+ * This test ensures that there are no unexpected
+ * uninterruptible reflows when resizing windows.
+ */
+add_task(async function() {
+  const BOOKMARKS_COUNT = 150;
+  const STARTING_WIDTH = 600;
+  const STARTING_HEIGHT = 400;
+  const SMALL_WIDTH = 150;
+  const SMALL_HEIGHT = 150;
+
+  await PlacesUtils.bookmarks.eraseEverything();
+
+  // Add a bunch of bookmarks to display in the Bookmarks toolbar
+  await PlacesUtils.bookmarks.insertTree({
+    guid: PlacesUtils.bookmarks.toolbarGuid,
+    children: Array(BOOKMARKS_COUNT).fill("")
+                                    .map((_, i) => ({ url: `http://test.places.${i}/`}))
+  });
+
+  let wasCollapsed = gToolbar.collapsed;
+  Assert.ok(wasCollapsed, "The toolbar is collapsed by default");
+  if (wasCollapsed) {
+    let promiseReady =
+      BrowserTestUtils.waitForEvent(gToolbar, "BookmarksToolbarVisibilityUpdated");
+    await toggleBookmarksToolbar(true);
+    await promiseReady;
+  }
+
+  registerCleanupFunction(async () => {
+    if (wasCollapsed) {
+      await toggleBookmarksToolbar(false);
+    }
+    await PlacesUtils.bookmarks.eraseEverything();
+    await PlacesUtils.history.clear();
+  });
+
+  let win = await prepareSettledWindow();
+
+  if (win.screen.availWidth < STARTING_WIDTH ||
+      win.screen.availHeight < STARTING_HEIGHT) {
+    Assert.ok(false, "This test is running on too small a display - " +
+              `(${STARTING_WIDTH}x${STARTING_HEIGHT} min)`);
+    return;
+  }
+
+  await resizeWindow(win, STARTING_WIDTH, STARTING_HEIGHT);
+
+  await withReflowObserver(async function() {
+    await resizeWindow(win, SMALL_WIDTH, SMALL_HEIGHT);
+    await resizeWindow(win, STARTING_WIDTH, STARTING_HEIGHT);
+  }, EXPECTED_REFLOWS, win);
+
+  await BrowserTestUtils.closeWindow(win);
+});
--- a/browser/base/content/test/performance/head.js
+++ b/browser/base/content/test/performance/head.js
@@ -194,28 +194,43 @@ async function ensureNoPreloadedBrowser(
                              .getService(Ci.nsIAboutNewTabService);
   aboutNewTabService.newTabURL = "about:blank";
 
   registerCleanupFunction(() => {
     aboutNewTabService.resetNewTabURL();
   });
 }
 
+/**
+ * The navigation toolbar is overflowable, meaning that some items
+ * will be moved and held within a sub-panel if the window gets too
+ * small to show their icons. The calculation for hiding those items
+ * occurs after resize events, and is debounced using a DeferredTask.
+ * This utility function allows us to fast-forward to just running
+ * that function for that DeferredTask instead of waiting for the
+ * debounce timeout to occur.
+ */
+function forceImmediateToolbarOverflowHandling(win) {
+  let overflowableToolbar = win.document.getElementById("nav-bar").overflowable;
+  if (overflowableToolbar._lazyResizeHandler && overflowableToolbar._lazyResizeHandler.isArmed) {
+    overflowableToolbar._lazyResizeHandler.disarm();
+    // Ensure the root frame is dirty before resize so that, if we're
+    // in the middle of a reflow test, we record the reflows deterministically.
+    let dwu = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIDOMWindowUtils);
+    dwu.ensureDirtyRootFrame();
+    overflowableToolbar._onLazyResize();
+  }
+}
+
 async function prepareSettledWindow() {
   let win = await BrowserTestUtils.openNewBrowserWindow();
 
   await ensureNoPreloadedBrowser(win);
-
-  let overflowableToolbar = win.document.getElementById("nav-bar").overflowable;
-  if (overflowableToolbar._lazyResizeHandler && overflowableToolbar._lazyResizeHandler.isArmed) {
-    info("forcing deferred overflow handling of the navigation toolbar to happen immediately");
-    overflowableToolbar._lazyResizeHandler.disarm();
-    overflowableToolbar._onLazyResize();
-  }
-
+  forceImmediateToolbarOverflowHandling(win);
   return win;
 }
 
 /**
  * Calculate and return how many additional tabs can be fit into the
  * tabstrip without causing it to overflow.
  *
  * @return int