browser_tabopen.js (6415B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /** 7 * WHOA THERE: We should never be adding new things to EXPECTED_REFLOWS. 8 * Instead of adding reflows to the list, you should be modifying your code to 9 * avoid the reflow. 10 * 11 * See https://firefox-source-docs.mozilla.org/performance/bestpractices.html 12 * for tips on how to do that. 13 */ 14 const EXPECTED_REFLOWS = [ 15 /** 16 * Nothing here! Please don't add anything new! 17 */ 18 ]; 19 20 /* 21 * This test ensures that there are no unexpected 22 * uninterruptible reflows when opening new tabs. 23 */ 24 add_task(async function () { 25 // Force-enable tab animations 26 gReduceMotionOverride = false; 27 28 // TODO (bug 1702653): Disable tab shadows for tests since the shadow 29 // can extend outside of the boundingClientRect. The tabRect will need 30 // to grow to include the shadow size. 31 gBrowser.tabContainer.setAttribute("noshadowfortests", "true"); 32 33 await ensureNoPreloadedBrowser(); 34 await disableFxaBadge(); 35 36 // The test starts on about:blank and opens an about:blank 37 // tab which triggers opening the toolbar since 38 // ensureNoPreloadedBrowser sets AboutNewTab.newTabURL to about:blank. 39 await SpecialPowers.pushPrefEnv({ 40 set: [["browser.toolbars.bookmarks.visibility", "never"]], 41 }); 42 43 // Prepare the window to avoid flicker and reflow that's unrelated to our 44 // tab opening operation. 45 gURLBar.focus(); 46 47 let tabStripRect = 48 gBrowser.tabContainer.arrowScrollbox.getBoundingClientRect(); 49 let firstTabRect = gBrowser.selectedTab.getBoundingClientRect(); 50 let tabPaddingStart = parseFloat( 51 getComputedStyle(gBrowser.selectedTab).paddingInlineStart 52 ); 53 let minTabWidth = firstTabRect.width - 2 * tabPaddingStart; 54 let maxTabWidth = firstTabRect.width; 55 let firstTabLabelRect = 56 gBrowser.selectedTab.textLabel.getBoundingClientRect(); 57 let newTabButtonRect = document 58 .getElementById("tabs-newtab-button") 59 .getBoundingClientRect(); 60 let textBoxRect = gURLBar 61 .querySelector("moz-input-box") 62 .getBoundingClientRect(); 63 64 let inRange = (val, min, max) => min <= val && val <= max; 65 66 info(`tabStripRect=${JSON.stringify(tabStripRect)}`); 67 info(`firstTabRect=${JSON.stringify(firstTabRect)}`); 68 info(`tabPaddingStart=${JSON.stringify(tabPaddingStart)}`); 69 info(`firstTabLabelRect=${JSON.stringify(firstTabLabelRect)}`); 70 info(`newTabButtonRect=${JSON.stringify(newTabButtonRect)}`); 71 info(`textBoxRect=${JSON.stringify(textBoxRect)}`); 72 73 let inTabStrip = function (r) { 74 return ( 75 r.y1 >= tabStripRect.top && 76 r.y2 <= tabStripRect.bottom && 77 r.x1 >= tabStripRect.left && 78 r.x2 <= tabStripRect.right 79 ); 80 }; 81 82 const kTabCloseIconWidth = 13; 83 84 let isExpectedChange = function (r) { 85 // We expect all changes to be within the tab strip. 86 if (!inTabStrip(r)) { 87 return false; 88 } 89 90 // The first tab should get deselected at the same time as the next tab 91 // starts appearing, so we should have one rect that includes the first tab 92 // but is wider. 93 if ( 94 inRange(r.w, minTabWidth, maxTabWidth * 2) && 95 inRange(r.x1, firstTabRect.x, firstTabRect.x + tabPaddingStart) 96 ) { 97 return true; 98 } 99 100 // The second tab gets painted several times due to tabopen animation. 101 let isSecondTabRect = 102 inRange( 103 r.x1, 104 // When the animation starts the tab close icon overflows. 105 // -1 for the border on Win7 106 firstTabRect.right - kTabCloseIconWidth - 1, 107 firstTabRect.right + firstTabRect.width 108 ) && 109 r.x2 < 110 firstTabRect.right + 111 firstTabRect.width + 112 // Sometimes the '+' is in the same rect. 113 newTabButtonRect.width; 114 115 if (isSecondTabRect) { 116 return true; 117 } 118 // The '+' icon moves with an animation. At the end of the animation 119 // the former and new positions can touch each other causing the rect 120 // to have twice the icon's width. 121 if ( 122 r.h == kTabCloseIconWidth && 123 r.w <= 2 * kTabCloseIconWidth + kMaxEmptyPixels 124 ) { 125 return true; 126 } 127 128 // We sometimes have a rect for the right most 2px of the '+' button. 129 if (r.h == 2 && r.w == 2) { 130 return true; 131 } 132 133 // Same for the 'X' icon. 134 if (r.h == 10 && r.w <= 2 * 10) { 135 return true; 136 } 137 138 // Other changes are unexpected. 139 return false; 140 }; 141 142 // Add a reflow observer and open a new tab. 143 await withPerfObserver( 144 async function () { 145 let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone"); 146 BrowserCommands.openTab(); 147 await BrowserTestUtils.waitForEvent( 148 gBrowser.selectedTab, 149 "TabAnimationEnd" 150 ); 151 await switchDone; 152 }, 153 { 154 expectedReflows: EXPECTED_REFLOWS, 155 frames: { 156 filter: rects => rects.filter(r => !isExpectedChange(r)), 157 exceptions: [ 158 { 159 name: 160 "bug 1446452 - the new tab should appear at the same time as the" + 161 " previous one gets deselected", 162 condition: r => 163 // In tab strip 164 r.y1 >= tabStripRect.top && 165 r.y2 <= tabStripRect.bottom && 166 // Position and size of the first tab. 167 r.x1 == firstTabRect.left && 168 inRange( 169 r.w, 170 firstTabRect.width - 1, // -1 as the border doesn't change 171 firstTabRect.width 172 ), 173 }, 174 { 175 name: "the urlbar placeolder moves up and down by a few pixels", 176 // This seems to only happen on the second run in --verify 177 condition: r => 178 r.x1 >= textBoxRect.left && 179 r.x2 <= textBoxRect.right && 180 r.y1 >= textBoxRect.top && 181 r.y2 <= textBoxRect.bottom, 182 }, 183 { 184 name: "bug 1477966 - the name of a deselected tab should appear immediately", 185 condition: r => 186 AppConstants.platform == "macosx" && 187 r.x1 >= firstTabLabelRect.x && 188 r.x2 <= firstTabLabelRect.right && 189 r.y1 >= firstTabLabelRect.y && 190 r.y2 <= firstTabLabelRect.bottom, 191 }, 192 ], 193 }, 194 } 195 ); 196 197 let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone"); 198 BrowserTestUtils.removeTab(gBrowser.selectedTab); 199 await switchDone; 200 });