browser_tabstrip_overflow_underflow.js (6258B)
1 "use strict"; 2 3 /** 4 * WHOA THERE: We should never be adding new things to EXPECTED_*_REFLOWS. 5 * This is a (now empty) list of known reflows. 6 * Instead of adding more reflows to the lists, you should be modifying your 7 * code to avoid the reflow. 8 * 9 * See https://firefox-source-docs.mozilla.org/performance/bestpractices.html 10 * for tips on how to do that. 11 */ 12 const EXPECTED_OVERFLOW_REFLOWS = [ 13 /** 14 * Nothing here! Please don't add anything new! 15 */ 16 ]; 17 18 const EXPECTED_UNDERFLOW_REFLOWS = [ 19 /** 20 * Nothing here! Please don't add anything new! 21 */ 22 ]; 23 24 /** 25 * This test ensures that there are no unexpected uninterruptible reflows when 26 * opening a new tab that will cause the existing tabs to overflow and the tab 27 * strip to become scrollable. It also tests that there are no unexpected 28 * uninterruptible reflows when closing that tab, which causes the tab strip to 29 * underflow. 30 */ 31 add_task(async function () { 32 // Force-enable tab animations 33 gReduceMotionOverride = false; 34 35 await ensureNoPreloadedBrowser(); 36 37 // The test starts on about:blank and opens an about:blank 38 // tab which triggers opening the toolbar since 39 // ensureNoPreloadedBrowser sets AboutNewTab.newTabURL to about:blank. 40 await SpecialPowers.pushPrefEnv({ 41 set: [["browser.toolbars.bookmarks.visibility", "never"]], 42 }); 43 44 const TAB_COUNT_FOR_OVERFLOW = computeMaxTabCount(); 45 46 await createTabs(TAB_COUNT_FOR_OVERFLOW); 47 48 gURLBar.focus(); 49 await disableFxaBadge(); 50 51 let tabStripRect = 52 gBrowser.tabContainer.arrowScrollbox.getBoundingClientRect(); 53 let textBoxRect = gURLBar 54 .querySelector("moz-input-box") 55 .getBoundingClientRect(); 56 57 let ignoreTabstripRects = { 58 filter: rects => 59 rects.filter( 60 r => 61 !( 62 // We expect plenty of changed rects within the tab strip. 63 ( 64 r.y1 >= tabStripRect.top && 65 r.y2 <= tabStripRect.bottom && 66 r.x1 >= tabStripRect.left && 67 r.x2 <= tabStripRect.right 68 ) 69 ) 70 ), 71 exceptions: [ 72 { 73 name: "the urlbar placeolder moves up and down by a few pixels", 74 condition: r => 75 r.x1 >= textBoxRect.left && 76 r.x2 <= textBoxRect.right && 77 r.y1 >= textBoxRect.top && 78 r.y2 <= textBoxRect.bottom, 79 }, 80 { 81 name: "bug 1446449 - spurious tab switch spinner", 82 condition: r => 83 // In the content area 84 r.y1 >= 85 document.getElementById("tabbrowser-tabbox").getBoundingClientRect() 86 .top, 87 }, 88 ], 89 }; 90 91 await withPerfObserver( 92 async function () { 93 let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone"); 94 BrowserCommands.openTab(); 95 await BrowserTestUtils.waitForEvent( 96 gBrowser.selectedTab, 97 "TabAnimationEnd" 98 ); 99 await switchDone; 100 await TestUtils.waitForCondition(() => { 101 return gBrowser.tabContainer.arrowScrollbox.hasAttribute( 102 "scrolledtoend" 103 ); 104 }); 105 }, 106 { expectedReflows: EXPECTED_OVERFLOW_REFLOWS, frames: ignoreTabstripRects } 107 ); 108 109 Assert.ok( 110 gBrowser.tabContainer.overflowing, 111 "Tabs should now be overflowed." 112 ); 113 114 // Now test that opening and closing a tab while overflowed doesn't cause 115 // us to reflow. 116 await withPerfObserver( 117 async function () { 118 let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone"); 119 BrowserCommands.openTab(); 120 await switchDone; 121 await TestUtils.waitForCondition(() => { 122 return gBrowser.tabContainer.arrowScrollbox.hasAttribute( 123 "scrolledtoend" 124 ); 125 }); 126 }, 127 { expectedReflows: [], frames: ignoreTabstripRects } 128 ); 129 130 await withPerfObserver( 131 async function () { 132 let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone"); 133 BrowserTestUtils.removeTab(gBrowser.selectedTab, { animate: true }); 134 await switchDone; 135 }, 136 { expectedReflows: [], frames: ignoreTabstripRects } 137 ); 138 139 // At this point, we have an overflowed tab strip, and we've got the last tab 140 // selected. This should mean that the first tab is scrolled out of view. 141 // Let's test that we don't reflow when switching to that first tab. 142 let lastTab = gBrowser.selectedTab; 143 let arrowScrollbox = gBrowser.tabContainer.arrowScrollbox; 144 145 // First, we'll check that the first tab is actually scrolled 146 // at least partially out of view. 147 Assert.greater( 148 arrowScrollbox.scrollPosition, 149 0, 150 "First tab should be partially scrolled out of view." 151 ); 152 153 // Now switch to the first tab. We shouldn't flush layout at all. 154 await withPerfObserver( 155 async function () { 156 let firstTab = gBrowser.tabs[0]; 157 await BrowserTestUtils.switchTab(gBrowser, firstTab); 158 await TestUtils.waitForCondition(() => { 159 return gBrowser.tabContainer.arrowScrollbox.hasAttribute( 160 "scrolledtostart" 161 ); 162 }); 163 }, 164 { expectedReflows: [], frames: ignoreTabstripRects } 165 ); 166 167 // Okay, now close the last tab. The tabstrip should stay overflowed, but removing 168 // one more after that should underflow it. 169 BrowserTestUtils.removeTab(lastTab); 170 171 Assert.ok( 172 gBrowser.tabContainer.overflowing, 173 "Tabs should still be overflowed." 174 ); 175 176 // Depending on the size of the window, it might take one or more tab 177 // removals to put the tab strip out of the overflow state, so we'll just 178 // keep testing removals until that occurs. 179 while (gBrowser.tabContainer.overflowing) { 180 lastTab = gBrowser.tabs.at(-1); 181 if (gBrowser.selectedTab !== lastTab) { 182 await BrowserTestUtils.switchTab(gBrowser, lastTab); 183 } 184 185 // ... and make sure we don't flush layout when closing it, and exiting 186 // the overflowed state. 187 await withPerfObserver( 188 async function () { 189 let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone"); 190 BrowserTestUtils.removeTab(lastTab, { animate: true }); 191 await switchDone; 192 await TestUtils.waitForCondition(() => !lastTab.isConnected); 193 }, 194 { 195 expectedReflows: EXPECTED_UNDERFLOW_REFLOWS, 196 frames: ignoreTabstripRects, 197 } 198 ); 199 } 200 201 await removeAllButFirstTab(); 202 });