Bug 1363505 - Add tab squeeze reflow test. r?florian
MozReview-Commit-ID: Jd7uVrNaMbh
--- 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();
+}