Bug 1387088 - Add a reflow test for when the user is typing a search into the AwesomeBar. r?florian
MozReview-Commit-ID: BP0zlLU9Ltp
--- a/browser/base/content/test/performance/browser.ini
+++ b/browser/base/content/test/performance/browser.ini
@@ -10,13 +10,15 @@ skip-if = !e10s
skip-if = !debug
[browser_tabclose_grow_reflows.js]
[browser_tabclose_reflows.js]
[browser_tabopen_reflows.js]
[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 = (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_keyed_search_reflows.js
@@ -0,0 +1,183 @@
+"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);
+
+/**
+ * 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: [
+ "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: 36, // 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: 1584, // This number should only ever go down - never up.
+ },
+
+ {
+ stack: [
+ "_handleOverflow@chrome://global/content/bindings/autocomplete.xml",
+ "handleOverUnderflow@chrome://global/content/bindings/autocomplete.xml",
+ "_onChanged@chrome://global/content/bindings/autocomplete.xml",
+ "_appendCurrentResult/<@chrome://global/content/bindings/autocomplete.xml",
+ ],
+ times: 6,
+ },
+
+ {
+ 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 promiseSearchComplete(win) {
+ let URLBar = win.gURLBar;
+ if (URLBar.popup.state != "open") {
+ await BrowserTestUtils.waitForEvent(URLBar.popup, "popupshown");
+ }
+ await BrowserTestUtils.waitForCondition(() => {
+ return URLBar.controller.searchStatus >=
+ Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+ });
+
+ // 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 }));
+}
+
+add_task(async function setup() {
+ await addDummyHistoryEntries();
+});
+
+/**
+ * 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 = "";
+
+ await withReflowObserver(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();
+ };
+
+ // Only keying in 6 characters because the number of reflows triggered
+ // is so high that we risk timing out the test if we key in any more.
+ const SEARCH_TERM = "ows-10";
+ for (let i = 0; i < SEARCH_TERM.length; ++i) {
+ let char = SEARCH_TERM[i];
+ EventUtils.synthesizeKey(char, {}, win);
+ await promiseSearchComplete(win);
+ }
+
+ let hiddenPromise = BrowserTestUtils.waitForEvent(URLBar.popup, "popuphidden");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+ await hiddenPromise;
+ }, EXPECTED_REFLOWS_FIRST_OPEN, win);
+
+ await BrowserTestUtils.closeWindow(win);
+});
--- a/browser/base/content/test/performance/browser_urlbar_search_reflows.js
+++ b/browser/base/content/test/performance/browser_urlbar_search_reflows.js
@@ -1,17 +1,14 @@
"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.
@@ -143,69 +140,20 @@ const EXPECTED_REFLOWS_SECOND_OPEN = [
"_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();
- });
+ await addDummyHistoryEntries();
});
/**
* 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() {
@@ -230,17 +178,39 @@ add_task(async function() {
oldInvalidate(reason);
};
popup.onResultsAdded = () => {
dirtyFrameFn();
oldResultsAdded();
};
- await promiseAutocompleteResultPopup(win);
+ 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;
};
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,8 +1,13 @@
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
/**
* 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.
*
@@ -87,16 +92,22 @@ async function withReflowObserver(testFn
dirtyFrameFn();
// Stack trace is empty. Reflow was triggered by native code, which
// we ignore.
if (path === "") {
return;
}
+ // synthesizeKey from EventUtils.js causes us to reflow. That's the test
+ // harness and we don't care about that, so we'll filter that out.
+ if (path.startsWith("synthesizeKey@chrome://mochikit/content/tests/SimpleTest/EventUtils.js")) {
+ return;
+ }
+
let index = expectedReflows.findIndex(reflow => path.startsWith(reflow.stack.join("|")));
if (index != -1) {
Assert.ok(true, "expected uninterruptible reflow: '" +
JSON.stringify(pathWithLineNumbers, null, "\t") + "'");
if (--expectedReflows[index].times == 0) {
expectedReflows.splice(index, 1);
}
@@ -219,8 +230,30 @@ async function createTabs(howMany) {
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();
}
+
+/**
+ * Adds some entries to the Places database so that we can
+ * do semi-realistic look-ups in the URL bar.
+ */
+async function addDummyHistoryEntries() {
+ 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();
+ });
+}