diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
index 550664b46dd1..473dac838a04 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -57,7 +57,7 @@
# DisplayPortUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered based
# the current structure that we may want to revisit.
-
+
diff --git a/browser/components/sidebar/SidebarState.sys.mjs b/browser/components/sidebar/SidebarState.sys.mjs
index 07072efd9629..1fa494121370 100644
--- a/browser/components/sidebar/SidebarState.sys.mjs
+++ b/browser/components/sidebar/SidebarState.sys.mjs
@@ -32,11 +32,20 @@ XPCOMUtils.defineLazyPreferenceGetter(
* When sidebar.visibility pref value is "always-show", the toolbar button serves to toggle this property
* @property {boolean} launcherDragActive
* Whether the launcher is currently being dragged.
+ * @property {boolean} pinnedTabsDragActive
+ * Whether the pinned tabs container is currently being dragged.
* @property {boolean} launcherHoverActive
* Whether the launcher is currently being hovered.
* @property {number} launcherWidth
* Current width of the sidebar launcher.
* @property {number} expandedLauncherWidth
+ * Width of the expanded launcher
+ * @property {number} pinnedTabsHeight
+ * Current height of the pinned tabs container
+ * @property {number} expandedPinnedTabsHeight
+ * Height of the pinned tabs container when the sidebar is expanded
+ * @property {number} collapsedPinnedTabsHeight
+ * Height of the pinned tabs container when the sidebar is collapsed
*/
const LAUNCHER_MINIMUM_WIDTH = 100;
@@ -66,6 +75,7 @@ export class SidebarState {
launcherHoverActive: false,
launcherVisible: false,
panelOpen: false,
+ pinnedTabsDragActive: false,
});
/**
@@ -122,6 +132,26 @@ export class SidebarState {
return this.#controller._box;
}
+ /**
+ * Get the pinned tabs container element.
+ *
+ * @returns {XULElement}
+ */
+ get #pinnedTabsContainerEl() {
+ return this.#controller._pinnedTabsContainer;
+ }
+
+ /**
+ * Get the items-wrapper part of the pinned tabs container element.
+ *
+ * @returns {XULElement}
+ */
+ get #pinnedTabsItemsWrapper() {
+ return this.#pinnedTabsContainerEl.shadowRoot.querySelector(
+ "[part=items-wrapper]"
+ );
+ }
+
/**
* Get window object from the controller.
*/
@@ -193,6 +223,10 @@ export class SidebarState {
case "panelOpen":
// we need to know if we have a command value before finalizing panelOpen
break;
+ case "expandedPinnedTabsHeight":
+ case "collapsedPinnedTabsHeight":
+ this.#updatePinnedTabsHeight();
+ break;
default:
this[key] = value;
}
@@ -240,6 +274,9 @@ export class SidebarState {
expandedLauncherWidth: convertToInt(this.expandedLauncherWidth),
launcherExpanded: this.launcherExpanded,
launcherVisible: this.launcherVisible,
+ pinnedTabsHeight: this.pinnedTabsHeight,
+ expandedPinnedTabsHeight: this.expandedPinnedTabsHeight,
+ collapsedPinnedTabsHeight: this.collapsedPinnedTabsHeight,
};
// omit any properties with undefined values'
for (let [key, value] of Object.entries(props)) {
@@ -290,6 +327,24 @@ export class SidebarState {
this.#launcherContainerEl.style.maxWidth = `calc(${SIDEBAR_MAXIMUM_WIDTH} - ${width}px)`;
}
+ get expandedPinnedTabsHeight() {
+ return this.#props.expandedPinnedTabsHeight;
+ }
+
+ set expandedPinnedTabsHeight(height) {
+ this.#props.expandedPinnedTabsHeight = height;
+ this.#updatePinnedTabsHeight();
+ }
+
+ get collapsedPinnedTabsHeight() {
+ return this.#props.collapsedPinnedTabsHeight;
+ }
+
+ set collapsedPinnedTabsHeight(height) {
+ this.#props.collapsedPinnedTabsHeight = height;
+ this.#updatePinnedTabsHeight();
+ }
+
get defaultLauncherVisible() {
if (!this.revampEnabled) {
return false;
@@ -390,6 +445,12 @@ export class SidebarState {
if (!this.launcherDragActive) {
this.#updateLauncherWidth();
}
+ if (
+ !this.pinnedTabsDragActive &&
+ this.#controller.sidebarRevampVisibility !== "expand-on-hover"
+ ) {
+ this.#updatePinnedTabsHeight();
+ }
}
get launcherDragActive() {
@@ -429,6 +490,34 @@ export class SidebarState {
rootEl.toggleAttribute("sidebar-launcher-drag-active", active);
}
+ get pinnedTabsDragActive() {
+ return this.#props.pinnedTabsDragActive;
+ }
+
+ set pinnedTabsDragActive(active) {
+ this.#props.pinnedDragActive = active;
+
+ let itemsWrapperHeight =
+ this.#controllerGlobal.windowUtils.getBoundsWithoutFlushing(
+ this.#pinnedTabsItemsWrapper
+ ).height;
+ if (this.pinnedTabsHeight > itemsWrapperHeight) {
+ this.pinnedTabsHeight = itemsWrapperHeight;
+ if (this.#props.launcherExpanded) {
+ this.expandedPinnedTabsHeight = this.pinnedTabsHeight;
+ } else {
+ this.collapsedPinnedTabsHeight = this.pinnedTabsHeight;
+ }
+ } else if (!active) {
+ // Store the user-preferred pinned tabs height.
+ if (this.#props.launcherExpanded) {
+ this.expandedPinnedTabsHeight = this.pinnedTabsHeight;
+ } else {
+ this.collapsedPinnedTabsHeight = this.pinnedTabsHeight;
+ }
+ }
+ }
+
get launcherHoverActive() {
return this.#props.launcherHoverActive;
}
@@ -478,6 +567,31 @@ export class SidebarState {
);
}
+ get pinnedTabsHeight() {
+ return this.#props.pinnedTabsHeight;
+ }
+
+ set pinnedTabsHeight(height) {
+ this.#props.pinnedTabsHeight = height;
+ if (this.launcherExpanded) {
+ this.expandedPinnedTabsHeight = height;
+ } else {
+ this.collapsedPinnedTabsHeight = height;
+ }
+ }
+
+ /**
+ * When the sidebar is expanded/collapsed, resize the pinned tabs container to the user-preferred
+ * height (if available).
+ */
+ #updatePinnedTabsHeight() {
+ if (this.launcherExpanded && this.expandedPinnedTabsHeight) {
+ this.#pinnedTabsContainerEl.style.height = `${this.expandedPinnedTabsHeight}px`;
+ } else if (!this.launcherExpanded && this.collapsedPinnedTabsHeight) {
+ this.#pinnedTabsContainerEl.style.height = `${this.collapsedPinnedTabsHeight}px`;
+ }
+ }
+
#updateTabbrowser(isSidebarShown) {
this.#controllerGlobal.document
.getElementById("tabbrowser-tabbox")
diff --git a/browser/components/sidebar/browser-sidebar.js b/browser/components/sidebar/browser-sidebar.js
index b02ff13416f3..b6d3b5df755e 100644
--- a/browser/components/sidebar/browser-sidebar.js
+++ b/browser/components/sidebar/browser-sidebar.js
@@ -252,6 +252,8 @@ var SidebarController = {
lastOpenedId: null,
_box: null,
+ _pinnedTabsContainer: null,
+ _pinnedTabsItemsWrapper: null,
// The constructor of this label accesses the browser element due to the
// control="sidebar" attribute, so avoid getting this label during startup.
get _title() {
@@ -334,6 +336,10 @@ var SidebarController = {
return this._launcherSplitter.getAttribute("state") === "dragging";
},
+ get isPinnedTabsDragging() {
+ return this._pinnedTabsSplitter.getAttribute("state") === "dragging";
+ },
+
init() {
// Initialize global state manager.
this.SidebarManager;
@@ -343,11 +349,21 @@ var SidebarController = {
this._state = new this.SidebarState(this);
}
+ this._pinnedTabsContainer = document.getElementById(
+ "vertical-pinned-tabs-container"
+ );
+ this._pinnedTabsItemsWrapper =
+ this._pinnedTabsContainer.shadowRoot.querySelector(
+ "[part=items-wrapper]"
+ );
this._box = document.getElementById("sidebar-box");
this._splitter = document.getElementById("sidebar-splitter");
this._launcherSplitter = document.getElementById(
"sidebar-launcher-splitter"
);
+ this._pinnedTabsSplitter = document.getElementById(
+ "vertical-pinned-tabs-splitter"
+ );
this._reversePositionButton = document.getElementById(
"sidebar-reverse-position"
);
@@ -416,6 +432,7 @@ var SidebarController = {
this._splitter.addEventListener("command", this._browserResizeObserver);
}
this._enableLauncherDragging();
+ this._enablePinnedTabsSplitterDragging();
// Record Glean metrics.
this.recordVisibilitySetting();
@@ -436,6 +453,7 @@ var SidebarController = {
this._switcherListenersAdded = true;
}
this._disableLauncherDragging();
+ this._disablePinnedTabsDragging();
}
// We need to update the tab strip for vertical tabs during init
// as there will be no tabstrip-orientation-change event
@@ -514,6 +532,7 @@ var SidebarController = {
}
this._splitter.removeEventListener("command", this._browserResizeObserver);
this._disableLauncherDragging();
+ this._disablePinnedTabsDragging();
},
/**
@@ -1313,6 +1332,66 @@ var SidebarController = {
this._launcherSplitter.hidden = false;
},
+ /**
+ * Enable the splitter which can be used to resize the pinned tabs container.
+ */
+ _enablePinnedTabsSplitterDragging() {
+ if (!this._pinnedTabsSplitter.hidden) {
+ // Already showing the launcher splitter with observers connected.
+ // Nothing to do.
+ return;
+ }
+ this._pinnedTabsResizeObserver = new ResizeObserver(([entry]) => {
+ if (this.isPinnedTabsDragging) {
+ this._state.pinnedTabsDragActive = true;
+ }
+ if (
+ (entry.contentBoxSize[0].blockSize ===
+ this._state.expandedPinnedTabsHeight &&
+ this._state.launcherExpanded) ||
+ (entry.contentBoxSize[0].blockSize ===
+ this._state.collapsedPinnedTabsHeight &&
+ !this._state.launcherExpanded)
+ ) {
+ // condition already met, no need to re-update
+ return;
+ }
+ this._state.pinnedTabsHeight = entry.contentBoxSize[0].blockSize;
+ });
+
+ this._itemsWrapperResizeObserver = new ResizeObserver(async () => {
+ await window.promiseDocumentFlushed(() => {
+ // Adjust pinned tabs container height if needed
+ let itemsWrapperHeight = window.windowUtils.getBoundsWithoutFlushing(
+ this._pinnedTabsItemsWrapper
+ ).height;
+ requestAnimationFrame(() => {
+ if (this._state.pinnedTabsHeight > itemsWrapperHeight) {
+ this._state.pinnedTabsHeight = itemsWrapperHeight;
+ if (this._state.launcherExpanded) {
+ this._state.expandedPinnedTabsHeight =
+ this._state.pinnedTabsHeight;
+ } else {
+ this._state.collapsedPinnedTabsHeight =
+ this._state.pinnedTabsHeight;
+ }
+ }
+ });
+ });
+ });
+ this._pinnedTabsResizeObserver.observe(this._pinnedTabsContainer);
+ this._itemsWrapperResizeObserver.observe(this._pinnedTabsItemsWrapper);
+
+ this._pinnedTabsDropHandler = () =>
+ (this._state.pinnedTabsDragActive = false);
+ this._pinnedTabsSplitter.addEventListener(
+ "command",
+ this._pinnedTabsDropHandler
+ );
+
+ this._pinnedTabsSplitter.hidden = false;
+ },
+
/**
* Disable the launcher splitter and remove any active observers.
*/
@@ -1328,6 +1407,20 @@ var SidebarController = {
this._launcherSplitter.hidden = true;
},
+ /**
+ * Disable the pinned tabs splitter and remove any active observers.
+ */
+ _disablePinnedTabsDragging() {
+ if (this._pinnedTabsResizeObserver) {
+ this._pinnedTabsResizeObserver.disconnect();
+ }
+ if (this._itemsWrapperResizeObserver) {
+ this._itemsWrapperResizeObserver.disconnect();
+ }
+
+ this._pinnedTabsSplitter.hidden = true;
+ },
+
_loadSidebarExtension(commandID) {
let sidebar = this.sidebars.get(commandID);
if (typeof sidebar?.onload === "function") {
@@ -2020,6 +2113,11 @@ var SidebarController = {
this.mouseOverTask?.finalize();
}
}
+
+ document.documentElement.toggleAttribute(
+ "sidebar-expand-on-hover",
+ isEnabled
+ );
},
/**
@@ -2183,6 +2281,11 @@ XPCOMUtils.defineLazyPreferenceGetter(
!SidebarController.inSingleTabWindow
) {
SidebarController.recordTabsLayoutSetting(newValue);
+ if (newValue) {
+ SidebarController._enablePinnedTabsSplitterDragging();
+ } else {
+ SidebarController._disablePinnedTabsDragging();
+ }
}
}
);
diff --git a/browser/components/sidebar/tests/browser/browser_resize_sidebar.js b/browser/components/sidebar/tests/browser/browser_resize_sidebar.js
index 3c12e6395991..14195d78532b 100644
--- a/browser/components/sidebar/tests/browser/browser_resize_sidebar.js
+++ b/browser/components/sidebar/tests/browser/browser_resize_sidebar.js
@@ -31,7 +31,7 @@ async function dragLauncher(deltaX, shouldExpand) {
info(`Drag the launcher by ${deltaX} px.`);
const { sidebarMain, _launcherSplitter: splitter } = SidebarController;
EventUtils.synthesizeMouseAtCenter(splitter, { type: "mousedown" });
- await mouseMoveInChunks(splitter, deltaX, 10);
+ await mouseMoveInChunksHorizontal(splitter, deltaX, 10);
EventUtils.synthesizeMouse(splitter, 0, 0, { type: "mouseup" });
info(`The sidebar should be ${shouldExpand ? "expanded" : "collapsed"}.`);
@@ -44,7 +44,24 @@ async function dragLauncher(deltaX, shouldExpand) {
AccessibilityUtils.resetEnv();
}
-async function mouseMoveInChunks(el, deltaX, numberOfChunks) {
+async function dragPinnedTabs(deltaY) {
+ AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
+
+ // Let the pinned tabs splitter stabilize before attempting a drag-and-drop.
+ await waitForRepaint();
+
+ info(`Drag the launcher by ${deltaY} px.`);
+ const { _pinnedTabsSplitter: splitter } = SidebarController;
+ EventUtils.synthesizeMouseAtCenter(splitter, { type: "mousedown" });
+ await mouseMoveInChunksVertical(splitter, deltaY, 10);
+ EventUtils.synthesizeMouse(splitter, 0, 0, { type: "mouseup" });
+
+ info(`The pinned tabs container has been expanded.`);
+
+ AccessibilityUtils.resetEnv();
+}
+
+async function mouseMoveInChunksHorizontal(el, deltaX, numberOfChunks) {
let chunkIndex = 0;
const chunkSize = deltaX / numberOfChunks;
const finished = Promise.withResolvers();
@@ -64,10 +81,35 @@ async function mouseMoveInChunks(el, deltaX, numberOfChunks) {
await finished.promise;
}
+async function mouseMoveInChunksVertical(el, deltaY, numberOfChunks) {
+ let chunkIndex = 0;
+ const chunkSize = deltaY / numberOfChunks;
+ const finished = Promise.withResolvers();
+
+ function synthesizeMouseMove() {
+ info(`chunkSize: ${chunkSize}`);
+ // mousemove by a single chunk. Queue up the next chunk if necessary.
+ EventUtils.synthesizeMouse(el, 0, chunkSize, { type: "mousemove" });
+ if (++chunkIndex === numberOfChunks) {
+ finished.resolve();
+ } else {
+ requestAnimationFrame(synthesizeMouseMove);
+ }
+ }
+
+ await waitForRepaint();
+ requestAnimationFrame(synthesizeMouseMove);
+ await finished.promise;
+}
+
function getLauncherWidth({ SidebarController } = window) {
return SidebarController.sidebarContainer.style.width;
}
+function getPinnedTabsHeight({ SidebarController } = window) {
+ return SidebarController._pinnedTabsContainer.clientHeight;
+}
+
add_task(async function test_drag_expand_and_collapse() {
await dragLauncher(200, true);
ok(getLauncherWidth(), "Launcher width set.");
@@ -159,4 +201,38 @@ add_task(async function test_resize_after_toggling_revamp() {
parseInt(originalWidth),
"Vertical tab strip was resized."
);
+
+ await dragLauncher(-200, true);
+});
+
+add_task(async function test_resize_of_pinned_tabs() {
+ await SidebarController.initializeUIState({
+ launcherExpanded: true,
+ });
+
+ info("Open 10 new tabs using the new tab button.");
+ for (let i = 0; i < 10; i++) {
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `data:text/html,${i + 1}`
+ );
+ gBrowser.pinTab(gBrowser.selectedTab);
+ }
+ await SidebarController.waitUntilStable();
+
+ info("Resize the pinned tabs container.");
+ const originalHeight = getPinnedTabsHeight();
+ await dragPinnedTabs(200, true);
+ await SidebarController.waitUntilStable();
+ const newHeight = getPinnedTabsHeight();
+ info(`original: ${originalHeight}, new: ${newHeight}`);
+ Assert.greater(
+ parseInt(newHeight),
+ parseInt(originalHeight),
+ "Pinned tabs container was resized."
+ );
+
+ while (gBrowser.tabs.length > 1) {
+ BrowserTestUtils.removeTab(gBrowser.tabs.at(-1));
+ }
});
diff --git a/browser/components/sidebar/tests/browser/browser_sidebar_expand_on_hover.js b/browser/components/sidebar/tests/browser/browser_sidebar_expand_on_hover.js
index a514644fed33..24c4ededa263 100644
--- a/browser/components/sidebar/tests/browser/browser_sidebar_expand_on_hover.js
+++ b/browser/components/sidebar/tests/browser/browser_sidebar_expand_on_hover.js
@@ -201,9 +201,10 @@ add_task(async function test_expand_on_hover_pinned_tabs() {
SidebarController.sidebarMain.hasAttribute("expanded"),
"The launcher is expanded"
);
- is(
- Math.round(parseInt(verticalTabsComputedStyle.width)),
- Math.round(parseInt(pinnedTabComputedStyle.width)),
+ Assert.less(
+ Math.round(parseInt(verticalTabsComputedStyle.width)) %
+ Math.round(parseInt(pinnedTabComputedStyle.width)),
+ 10,
"The pinned tabs are full width when expanded"
);
diff --git a/browser/themes/shared/tabbrowser/tabs.css b/browser/themes/shared/tabbrowser/tabs.css
index 2beeaf77eabb..1f9b8358337f 100644
--- a/browser/themes/shared/tabbrowser/tabs.css
+++ b/browser/themes/shared/tabbrowser/tabs.css
@@ -105,6 +105,8 @@
--tab-group-color-gray-pale: #F2F9FF;
--tab-group-label-text-dark: var(--color-gray-100);
+ /* 5px of padding-block are adding to .tabbrowser-tab */
+ --tab-height-with-margin-padding: calc(5px + var(--tab-min-height) + (2 * var(--tab-block-margin)));
}
/* stylelint-disable-next-line media-query-no-invalid */
@@ -1351,7 +1353,8 @@ tab-group {
/* Vertical tabs styling */
#tabbrowser-arrowscrollbox[orient="vertical"] {
- min-height: 1px;
+ /* Don't allow resizing below the height of 3 tabs */
+ min-height: calc(3 * var(--tab-height-with-margin-padding));
&::part(scrollbutton-up),
&::part(scrollbutton-down) {
@@ -1424,8 +1427,8 @@ tab-group {
}
#vertical-pinned-tabs-container {
- /* Fit slightly more than 5 tabs + padding before overflowing */
- max-height: calc(5 * var(--tabstrip-min-height) + var(--space-large));
+ /* Don't allow resizing below the height of 1 row of pinned tabs */
+ min-height: var(--tab-height-with-margin-padding);
&:empty {
display: none;
@@ -1473,6 +1476,10 @@ tab-group {
}
}
+ &::part(items-wrapper) {
+ flex: 0 1 0;
+ }
+
:root:not([sidebar-expand-on-hover]) & {
--tab-inline-padding: calc((var(--tab-collapsed-background-width) + 2 *
var(--tab-pinned-margin-inline-expanded) - var(--icon-size-default)) / 2);
@@ -1488,6 +1495,7 @@ tab-group {
minmax(var(--tab-pinned-min-width-expanded), auto)
);
display: grid;
+ grid-auto-rows: var(--tab-height-with-margin-padding);
}
.tab-label-container {
@@ -1524,23 +1532,29 @@ tab-group {
}
}
-#vertical-pinned-tabs-container-separator {
+#vertical-pinned-tabs-splitter {
display: block;
- border-bottom: var(--tabstrip-inner-border);
+ border-top: var(--tabstrip-inner-border);
margin-inline: var(--tab-inner-inline-margin);
+ min-height: 2px;
#vertical-pinned-tabs-container:empty + & {
display: none;
}
+ &:hover {
+ background-color: var(--focus-outline-color);
+ border-radius: var(--border-radius-medium);
+ }
+
/* stylelint-disable-next-line media-query-no-invalid */
@media not -moz-pref("sidebar.visibility", "expand-on-hover") {
- /* We need these rules to apply at all times when the sidebar.visibility
- pref is not set to "expand-on-hover" as opposed to when the "sidebar-expand-on-hover" attribute
- has not been added to root. There are certain scenarios when that attribute is temporarily
- removed from root such as when toggling the sidebar to expand with the toolbar button. */
+ /* We need these rules to apply at all times when the sidebar.visibility
+ pref is not set to "expand-on-hover" as opposed to when the "sidebar-expand-on-hover" attribute
+ has not been added to root. There are certain scenarios when that attribute is temporarily
+ removed from root such as when toggling the sidebar to expand with the toolbar button. */
#tabbrowser-tabs[expanded] > & {
- display: none;
+ border: none;
}
}
}