Bug 1356271 - add a test to measure how many layout flushes it takes for a simple location bar search. r=florian
authorMike Conley <mconley@mozilla.com>
Wed, 14 Jun 2017 15:45:33 -0400
changeset 420716 1c901b2b59712b57efdc77ac7510fb7d271fd0d6
parent 420715 79ac0025e9e861d70f8fab9bfe7aa9e166c5e091
child 420717 2c33020e12d1ff0b279758bf938892bbf0097fec
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflorian
bugs1356271
milestone56.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 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.");
     }