Bug 1927094 - optimize lineScrollAmount so it doesn't iterate over all tabs, r=mconley,dao

Differential Revision: https://phabricator.services.mozilla.com/D226907
This commit is contained in:
Gijs Kruitbosch 2024-11-14 17:14:50 +00:00
parent 49a0a41b4b
commit f979fd2d7e
4 changed files with 176 additions and 7 deletions

View File

@ -41,6 +41,8 @@
#maxTabsPerRow;
#dragOverCreateGroupTimer;
#mustUpdateTabMinHeight = false;
#tabMinHeight = 36;
constructor() {
super();
@ -92,6 +94,18 @@
return (!tab.pinned || !arePositioningPinnedTabs()) && tab.visible;
};
// Override for performance reasons. This is the size of a single element
// that can be scrolled when using mouse wheel scrolling. If we don't do
// this then arrowscrollbox computes this value by calling
// _getScrollableElements and dividing the box size by that number.
// However in the tabstrip case we already know the answer to this as,
// when we're overflowing, it is always the same as the tab min width or
// height.
Object.defineProperty(this.arrowScrollbox, "lineScrollAmount", {
get: () =>
this.verticalMode ? this.#tabMinHeight : this._tabMinWidthPref,
});
this.baseConnect();
this._blockDblClick = false;
@ -163,6 +177,7 @@
}
);
this.#updateTabMinWidth(this._tabMinWidthPref);
this.#updateTabMinHeight();
CustomizableUI.addListener(this);
this._updateNewTabVisibility();
@ -202,6 +217,7 @@
this._positionPinnedTabs();
this.#updateTabMinWidth();
this.#updateTabMinHeight();
let indicatorTabs = gBrowser.visibleTabs.filter(tab => {
return (
@ -1529,7 +1545,11 @@
node = this.arrowScrollbox.lastChild;
}
return node.before(tab);
node.before(tab);
if (this.#mustUpdateTabMinHeight) {
this.#updateTabMinHeight();
}
}
#updateTabMinWidth(val) {
@ -1544,6 +1564,53 @@
}
}
#updateTabMinHeight() {
if (!this.verticalMode || !window.toolbar.visible) {
this.#mustUpdateTabMinHeight = false;
return;
}
// Find at least one tab we can scroll to.
let firstScrollableTab = this.visibleTabs.find(
this.arrowScrollbox._canScrollToElement
);
if (!firstScrollableTab) {
// If not, we're in a pickle. We should never get here except if we
// also don't use the outcome of this work (because there's nothing to
// scroll so we don't care about the scrollbox size).
// So just set a flag so we re-run once we do have a new tab.
this.#mustUpdateTabMinHeight = true;
return;
}
let { height } =
window.windowUtils.getBoundsWithoutFlushing(firstScrollableTab);
// Use the current known height or a sane default.
this.#tabMinHeight = height || 36;
// The height we got may be incorrect if a flush is pending so re-check it after
// a flush completes.
window
.promiseDocumentFlushed(() => {})
.then(
() => {
height =
window.windowUtils.getBoundsWithoutFlushing(
firstScrollableTab
).height;
if (height) {
this.#tabMinHeight = height;
}
},
() => {
/* ignore errors */
}
);
}
get _isCustomizing() {
return document.documentElement.hasAttribute("customizing");
}
@ -1791,6 +1858,7 @@
uiDensityChanged() {
this._positionPinnedTabs();
this._updateCloseButtons();
this.#updateTabMinHeight();
this._handleTabSelect(true);
}

View File

@ -321,6 +321,8 @@ support-files = ["tab_that_opens_dialog.html"]
["browser_restore_isAppTab.js"]
run-if = ["crashreporter"] # test requires crashreporter due to 1536221
["browser_scroll_size_determination.js"]
["browser_selectTabAtIndex.js"]
["browser_switch_by_scrolling.js"]

View File

@ -0,0 +1,96 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Check that when opening a new window with vertical tabs turned
* on/off, wheel events with DOM_DELTA_LINE deltaMode successfully
* scroll the tabstrip.
*/
async function scrolling_works(useVerticalTabs, uiDensity) {
info(`Testing UI density ${uiDensity}`);
await SpecialPowers.pushPrefEnv({
set: [
["sidebar.revamp", useVerticalTabs],
["sidebar.verticalTabs", useVerticalTabs],
],
});
let win = await BrowserTestUtils.openNewBrowserWindow();
win.gUIDensity.update(win.gUIDensity[uiDensity]);
await BrowserTestUtils.overflowTabs(registerCleanupFunction, win, {
overflowAtStart: false,
overflowTabFactor: 3,
});
await TestUtils.waitForCondition(() => {
return Array.from(win.gBrowser.tabs).every(tab => tab._fullyOpen);
});
win.gBrowser.pinTab(win.gBrowser.tabs[0]);
let firstScrollableTab = win.gBrowser.tabs[1];
// Scroll back to start.
win.gBrowser.selectedTab = firstScrollableTab;
await TestUtils.waitForTick();
await win.promiseDocumentFlushed(() => {});
// Check we're scrolled so the first scrollable tab is at the top.
let { arrowScrollbox } = win.gBrowser.tabContainer;
let side = useVerticalTabs ? "top" : "left";
let boxStart = arrowScrollbox.getBoundingClientRect()[side];
let firstPoint = boxStart + 5;
Assert.equal(
gBrowser.tabs.indexOf(arrowScrollbox._elementFromPoint(firstPoint)),
gBrowser.tabs.indexOf(firstScrollableTab),
"First tab should be scrolled into view."
);
// Scroll.
EventUtils.synthesizeWheel(
arrowScrollbox,
10,
10,
{
wheel: true,
deltaY: 1,
deltaMode: WheelEvent.DOM_DELTA_LINE,
},
win
);
// Check that some other tab is scrolled into view.
try {
await TestUtils.waitForCondition(() => {
return arrowScrollbox._elementFromPoint(firstPoint) != firstScrollableTab;
});
} catch (ex) {
Assert.ok(false, `Failed to see scroll, error: ${ex}`);
}
Assert.notEqual(
win.gBrowser.tabs.indexOf(arrowScrollbox._elementFromPoint(firstPoint)),
win.gBrowser.tabs.indexOf(firstScrollableTab),
"First tab should be scrolled out of view."
);
await SpecialPowers.popPrefEnv();
await BrowserTestUtils.closeWindow(win);
}
add_task(async function test_vertical_scroll() {
for (let density of ["MODE_NORMAL", "MODE_COMPACT", "MODE_TOUCH"]) {
await scrolling_works(true, density);
}
});
add_task(async function test_horizontal_scroll() {
for (let density of ["MODE_NORMAL", "MODE_COMPACT", "MODE_TOUCH"]) {
await scrolling_works(false, density);
}
});

View File

@ -1928,8 +1928,11 @@ export var BrowserTestUtils = {
if (!params.hasOwnProperty("overflowTabFactor")) {
params.overflowTabFactor = 1.1;
}
let index = params.overflowAtStart ? 0 : undefined;
let { gBrowser } = win;
let overflowDirection = gBrowser.tabContainer.verticalMode
? "height"
: "width";
let index = params.overflowAtStart ? 0 : undefined;
let arrowScrollbox = gBrowser.tabContainer.arrowScrollbox;
if (arrowScrollbox.hasAttribute("overflowing")) {
return;
@ -1949,12 +1952,12 @@ export var BrowserTestUtils = {
arrowScrollbox.smoothScroll = originalSmoothScroll;
});
let width = ele => ele.getBoundingClientRect().width;
let tabMinWidth = parseInt(
win.getComputedStyle(gBrowser.selectedTab).minWidth
);
let size = ele => ele.getBoundingClientRect()[overflowDirection];
let tabMinSize = gBrowser.tabContainer.verticalMode
? size(gBrowser.selectedTab)
: parseInt(win.getComputedStyle(gBrowser.selectedTab).minWidth);
let tabCountForOverflow = Math.ceil(
(width(arrowScrollbox) / tabMinWidth) * params.overflowTabFactor
(size(arrowScrollbox) / tabMinSize) * params.overflowTabFactor
);
while (gBrowser.tabs.length < tabCountForOverflow) {
promises.push(