Bug 1356271 - add a test to measure how many layout flushes it takes for a simple location bar search. r?florian draft
authorMike Conley <mconley@mozilla.com>
Wed, 14 Jun 2017 15:45:33 -0400
changeset 618499 d61559c8173b1ac41a96a27e414a442d67156182
parent 618498 264aa5a1bb019c829a2975565c92502a19cf5989
child 640096 ec7c5fa8ca9ab7523cced176012f2f1bf9af0f18
push id71361
push usermconley@mozilla.com
push dateMon, 31 Jul 2017 15:54:26 +0000
reviewersflorian
bugs1356271
milestone56.0a1
Bug 1356271 - add a test to measure how many layout flushes it takes for a simple location bar search. r?florian MozReview-Commit-ID: GFDvSIn53Zh
browser/base/content/test/performance/browser.ini
browser/base/content/test/performance/browser_urlbar_search_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
@@ -9,11 +9,13 @@ skip-if = !e10s
 [browser_startup_images.js]
 skip-if = !debug
 [browser_tabclose_grow_reflows.js]
 [browser_tabclose_reflows.js]
 [browser_tabopen_reflows.js]
 [browser_tabopen_squeeze_reflows.js]
 [browser_tabswitch_reflows.js]
 [browser_toolbariconcolor_restyles.js]
+[browser_urlbar_search_reflows.js]
+skip-if = (os == 'linux') || (os == 'mac' && !debug) # Disabled on Linux and OS X opt due to frequent failures. Bug 1385932 and Bug 1384582
 [browser_windowclose_reflows.js]
 [browser_windowopen_reflows.js]
 skip-if = os == 'linux' # Disabled due to frequent failures. Bug 1380465.
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/performance/browser_urlbar_search_reflows.js
@@ -0,0 +1,268 @@
+"use strict";
+
+// There are a _lot_ of reflows in this test, and processing them takes
+// time. On slower builds, we need to boost our allowed test time.
+requestLongerTimeout(5);
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+                                  "resource://testing-common/PlacesTestUtils.jsm");
+
+/**
+ * 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.
+ */
+
+/* These reflows happen only the first time the awesomebar panel opens. */
+const EXPECTED_REFLOWS_FIRST_OPEN = [
+  // Bug 1357054
+  {
+    stack: [
+      "_rebuild@chrome://browser/content/search/search.xml",
+      "set_popup@chrome://browser/content/search/search.xml",
+      "enableOneOffSearches@chrome://browser/content/urlbarBindings.xml",
+      "_enableOrDisableOneOffSearches@chrome://browser/content/urlbarBindings.xml",
+      "urlbar_XBL_Constructor/<@chrome://browser/content/urlbarBindings.xml",
+      "openPopup@chrome://global/content/bindings/popup.xml",
+      "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openPopup@chrome://global/content/bindings/autocomplete.xml",
+      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml"
+    ],
+    times: 1, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "adjustSiteIconStart@chrome://global/content/bindings/autocomplete.xml",
+      "set_siteIconStart@chrome://global/content/bindings/autocomplete.xml",
+      "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openPopup@chrome://global/content/bindings/autocomplete.xml",
+      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
+    ],
+  },
+
+  {
+    stack: [
+      "adjustSiteIconStart@chrome://global/content/bindings/autocomplete.xml",
+      "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
+      "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
+      "_invalidate@chrome://global/content/bindings/autocomplete.xml",
+      "invalidate@chrome://global/content/bindings/autocomplete.xml",
+    ],
+    times: 9, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "adjustHeight@chrome://global/content/bindings/autocomplete.xml",
+      "onxblpopupshown@chrome://global/content/bindings/autocomplete.xml"
+    ],
+    times: 5, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
+      "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
+      "set_siteIconStart@chrome://global/content/bindings/autocomplete.xml",
+    ],
+    times: 6, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "adjustHeight@chrome://global/content/bindings/autocomplete.xml",
+      "_invalidate/this._adjustHeightTimeout<@chrome://global/content/bindings/autocomplete.xml",
+    ],
+    times: 3, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
+      "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
+      "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
+      "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
+      "_invalidate@chrome://global/content/bindings/autocomplete.xml",
+      "invalidate@chrome://global/content/bindings/autocomplete.xml"
+    ],
+    times: 390, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openPopup@chrome://global/content/bindings/autocomplete.xml",
+      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
+    ],
+    times: 3, // This number should only ever go down - never up.
+  },
+
+  // Bug 1359989
+  {
+    stack: [
+      "openPopup@chrome://global/content/bindings/popup.xml",
+      "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openPopup@chrome://global/content/bindings/autocomplete.xml",
+      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
+    ],
+  },
+];
+
+/* These reflows happen everytime the awesomebar panel opens. */
+const EXPECTED_REFLOWS_SECOND_OPEN = [
+  {
+    stack: [
+      "adjustHeight@chrome://global/content/bindings/autocomplete.xml",
+      "onxblpopupshown@chrome://global/content/bindings/autocomplete.xml"
+    ],
+    times: 3, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "adjustHeight@chrome://global/content/bindings/autocomplete.xml",
+      "_invalidate/this._adjustHeightTimeout<@chrome://global/content/bindings/autocomplete.xml",
+    ],
+    times: 3, // This number should only ever go down - never up.
+  },
+
+  {
+    stack: [
+      "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
+      "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
+      "_reuseAcItem@chrome://global/content/bindings/autocomplete.xml",
+      "_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
+      "_invalidate@chrome://global/content/bindings/autocomplete.xml",
+      "invalidate@chrome://global/content/bindings/autocomplete.xml"
+    ],
+    times: 444, // This number should only ever go down - never up.
+  },
+
+  // Bug 1384256
+  {
+    stack: [
+      "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openPopup@chrome://global/content/bindings/autocomplete.xml",
+      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
+    ],
+    times: 3, // This number should only ever go down - never up.
+  },
+
+  // Bug 1359989
+  {
+    stack: [
+      "openPopup@chrome://global/content/bindings/popup.xml",
+      "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
+      "openPopup@chrome://global/content/bindings/autocomplete.xml",
+      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
+    ],
+  },
+];
+
+/**
+ * Returns a Promise that resolves once the AwesomeBar popup for a particular
+ * window has appeared after having done a search for its input text.
+ *
+ * @param win (browser window)
+ *        The window to do the search in.
+ * @returns Promise
+ */
+async function promiseAutocompleteResultPopup(win) {
+  let URLBar = win.gURLBar;
+  URLBar.controller.startSearch(URLBar.value);
+  await BrowserTestUtils.waitForEvent(URLBar.popup, "popupshown");
+  await BrowserTestUtils.waitForCondition(() => {
+    return URLBar.controller.searchStatus >=
+      Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+  });
+  let matchCount = URLBar.popup._matchCount;
+  await BrowserTestUtils.waitForCondition(() => {
+    return URLBar.popup.richlistbox.childNodes.length == matchCount;
+  });
+
+  URLBar.controller.stopSearch();
+  // There are several setTimeout(fn, 0); calls inside autocomplete.xml
+  // that we need to wait for. Since those have higher priority than
+  // idle callbacks, we can be sure they will have run once this
+  // idle callback is called. The timeout seems to be required in
+  // automation - presumably because the machines can be pretty busy
+  // especially if it's GC'ing from previous tests.
+  await new Promise(resolve => win.requestIdleCallback(resolve, { timeout: 1000 }));
+
+  let hiddenPromise = BrowserTestUtils.waitForEvent(URLBar.popup, "popuphidden");
+  EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+  await hiddenPromise;
+}
+
+const SEARCH_TERM = "urlbar-reflows";
+
+add_task(async function setup() {
+  const NUM_VISITS = 10;
+  let visits = [];
+
+  for (let i = 0; i < NUM_VISITS; ++i) {
+    visits.push({
+      uri: `http://example.com/urlbar-reflows-${i}`,
+      title: `Reflow test for URL bar entry #${i}`,
+    });
+  }
+
+  await PlacesTestUtils.addVisits(visits);
+
+  registerCleanupFunction(async function() {
+    await PlacesTestUtils.clearHistory();
+  });
+});
+
+/**
+ * This test ensures that there are no unexpected
+ * uninterruptible reflows when typing into the URL bar
+ * with the default values in Places.
+ */
+add_task(async function() {
+  let win = await BrowserTestUtils.openNewBrowserWindow();
+  await ensureNoPreloadedBrowser(win);
+
+  let URLBar = win.gURLBar;
+  let popup = URLBar.popup;
+
+  URLBar.focus();
+  URLBar.value = SEARCH_TERM;
+  let testFn = async function(dirtyFrameFn) {
+    let oldInvalidate = popup.invalidate.bind(popup);
+    let oldResultsAdded = popup.onResultsAdded.bind(popup);
+
+    // We need to invalidate the frame tree outside of the normal
+    // mechanism since invalidations and result additions to the
+    // URL bar occur without firing JS events (which is how we
+    // normally know to dirty the frame tree).
+    popup.invalidate = (reason) => {
+      dirtyFrameFn();
+      oldInvalidate(reason);
+    };
+
+    popup.onResultsAdded = () => {
+      dirtyFrameFn();
+      oldResultsAdded();
+    };
+
+    await promiseAutocompleteResultPopup(win);
+  };
+
+  await withReflowObserver(testFn, EXPECTED_REFLOWS_FIRST_OPEN, win);
+
+  await withReflowObserver(testFn, EXPECTED_REFLOWS_SECOND_OPEN, win);
+
+  await BrowserTestUtils.closeWindow(win);
+});
--- a/browser/base/content/test/performance/head.js
+++ b/browser/base/content/test/performance/head.js
@@ -1,15 +1,19 @@
 /**
  * Async utility function for ensuring that no unexpected uninterruptible
  * reflows occur during some period of time in a window.
  *
  * @param testFn (async function)
  *        The async function that will exercise the browser activity that is
  *        being tested for reflows.
+ *
+ *        The testFn will be passed a single argument, which is a frame dirtying
+ *        function that can be called if the test needs to trigger frame
+ *        dirtying outside of the normal mechanism.
  * @param expectedReflows (Array, optional)
  *        An Array of Objects representing reflows.
  *
  *        Example:
  *
  *        [
  *          {
  *            // This reflow is caused by lorem ipsum
@@ -116,17 +120,17 @@ async function withReflowObserver(testFn
                     .getInterface(Ci.nsIWebNavigation)
                     .QueryInterface(Ci.nsIDocShell);
   docShell.addWeakReflowObserver(observer);
 
   els.addListenerForAllEvents(win, dirtyFrameFn, true);
 
   try {
     dirtyFrameFn();
-    await testFn();
+    await testFn(dirtyFrameFn);
   } finally {
     for (let remainder of expectedReflows) {
       Assert.ok(false,
                 `Unused expected reflow: ${JSON.stringify(remainder.stack, null, "\t")}\n` +
                 `This reflow was supposed to be hit ${remainder.times} more time(s).\n` +
                 "This is probably a good thing - just remove it from the " +
                 "expected list.");
     }