"use strict"; /** * WHOA THERE: We should never be adding new things to EXPECTED_*_REFLOWS. * This is a (now empty) list of known reflows. * Instead of adding more reflows to the lists, you should be modifying your * code to avoid the reflow. * * See https://firefox-source-docs.mozilla.org/performance/bestpractices.html * for tips on how to do that. */ const EXPECTED_OVERFLOW_REFLOWS = [ /** * Nothing here! Please don't add anything new! */ ]; const EXPECTED_UNDERFLOW_REFLOWS = [ /** * Nothing here! Please don't add anything new! */ ]; /** * This test ensures that there are no unexpected uninterruptible reflows when * opening a new tab that will cause the existing tabs to overflow and the tab * strip to become scrollable. It also tests that there are no unexpected * uninterruptible reflows when closing that tab, which causes the tab strip to * underflow. */ add_task(async function () { // Force-enable tab animations gReduceMotionOverride = false; await ensureNoPreloadedBrowser(); // The test starts on about:blank and opens an about:blank // tab which triggers opening the toolbar since // ensureNoPreloadedBrowser sets AboutNewTab.newTabURL to about:blank. await SpecialPowers.pushPrefEnv({ set: [["browser.toolbars.bookmarks.visibility", "never"]], }); const TAB_COUNT_FOR_OVERFLOW = computeMaxTabCount(); await createTabs(TAB_COUNT_FOR_OVERFLOW); gURLBar.focus(); await disableFxaBadge(); let tabStripRect = gBrowser.tabContainer.arrowScrollbox.getBoundingClientRect(); let textBoxRect = gURLBar .querySelector("moz-input-box") .getBoundingClientRect(); let ignoreTabstripRects = { filter: rects => rects.filter( r => !( // We expect plenty of changed rects within the tab strip. ( r.y1 >= tabStripRect.top && r.y2 <= tabStripRect.bottom && r.x1 >= tabStripRect.left && r.x2 <= tabStripRect.right ) ) ), exceptions: [ { name: "the urlbar placeolder moves up and down by a few pixels", condition: r => r.x1 >= textBoxRect.left && r.x2 <= textBoxRect.right && r.y1 >= textBoxRect.top && r.y2 <= textBoxRect.bottom, }, { name: "bug 1446449 - spurious tab switch spinner", condition: r => // In the content area r.y1 >= document.getElementById("appcontent").getBoundingClientRect().top, }, ], }; await withPerfObserver( async function () { let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone"); BrowserOpenTab(); await BrowserTestUtils.waitForEvent( gBrowser.selectedTab, "TabAnimationEnd" ); await switchDone; await TestUtils.waitForCondition(() => { return gBrowser.tabContainer.arrowScrollbox.hasAttribute( "scrolledtoend" ); }); }, { expectedReflows: EXPECTED_OVERFLOW_REFLOWS, frames: ignoreTabstripRects } ); Assert.ok( gBrowser.tabContainer.hasAttribute("overflow"), "Tabs should now be overflowed." ); // Now test that opening and closing a tab while overflowed doesn't cause // us to reflow. await withPerfObserver( async function () { let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone"); BrowserOpenTab(); await switchDone; await TestUtils.waitForCondition(() => { return gBrowser.tabContainer.arrowScrollbox.hasAttribute( "scrolledtoend" ); }); }, { expectedReflows: [], frames: ignoreTabstripRects } ); await withPerfObserver( async function () { let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone"); BrowserTestUtils.removeTab(gBrowser.selectedTab, { animate: true }); await switchDone; }, { expectedReflows: [], frames: ignoreTabstripRects } ); // At this point, we have an overflowed tab strip, and we've got the last tab // selected. This should mean that the first tab is scrolled out of view. // Let's test that we don't reflow when switching to that first tab. let lastTab = gBrowser.selectedTab; let arrowScrollbox = gBrowser.tabContainer.arrowScrollbox; // First, we'll check that the first tab is actually scrolled // at least partially out of view. Assert.ok( arrowScrollbox.scrollPosition > 0, "First tab should be partially scrolled out of view." ); // Now switch to the first tab. We shouldn't flush layout at all. await withPerfObserver( async function () { let firstTab = gBrowser.tabs[0]; await BrowserTestUtils.switchTab(gBrowser, firstTab); await TestUtils.waitForCondition(() => { return gBrowser.tabContainer.arrowScrollbox.hasAttribute( "scrolledtostart" ); }); }, { expectedReflows: [], frames: ignoreTabstripRects } ); // Okay, now close the last tab. The tabstrip should stay overflowed, but removing // one more after that should underflow it. BrowserTestUtils.removeTab(lastTab); Assert.ok( gBrowser.tabContainer.hasAttribute("overflow"), "Tabs should still be overflowed." ); // Depending on the size of the window, it might take one or more tab // removals to put the tab strip out of the overflow state, so we'll just // keep testing removals until that occurs. while (gBrowser.tabContainer.hasAttribute("overflow")) { lastTab = gBrowser.tabs[gBrowser.tabs.length - 1]; if (gBrowser.selectedTab !== lastTab) { await BrowserTestUtils.switchTab(gBrowser, lastTab); } // ... and make sure we don't flush layout when closing it, and exiting // the overflowed state. await withPerfObserver( async function () { let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone"); BrowserTestUtils.removeTab(lastTab, { animate: true }); await switchDone; await TestUtils.waitForCondition(() => !lastTab.isConnected); }, { expectedReflows: EXPECTED_UNDERFLOW_REFLOWS, frames: ignoreTabstripRects, } ); } await removeAllButFirstTab(); });