commit 284de9eac57ac641b3a0fc8ce0b1d903a3458249
parent e992d5f0c52f8716f2e4ebef134d81d757333e9f
Author: Jonathan Sudiaman <jsudiaman@mozilla.com>
Date: Mon, 6 Oct 2025 14:14:31 +0000
Bug 1986944 - Add splitters to resize each content area r=emilio,desktop-theme-reviewers,tabbrowser-reviewers,sclements
Differential Revision: https://phabricator.services.mozilla.com/D264887
Diffstat:
4 files changed, 114 insertions(+), 13 deletions(-)
diff --git a/browser/components/tabbrowser/test/browser/tabs/browser_tab_splitview.js b/browser/components/tabbrowser/test/browser/tabs/browser_tab_splitview.js
@@ -43,6 +43,14 @@ async function checkSplitViewPanelVisible(tab, isVisible) {
}
}
+function dragSplitter(deltaX, splitter) {
+ AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
+ EventUtils.synthesizeMouseAtCenter(splitter, { type: "mousedown" });
+ EventUtils.synthesizeMouse(splitter, deltaX, 0, { type: "mousemove" });
+ EventUtils.synthesizeMouse(splitter, 0, 0, { type: "mouseup" });
+ AccessibilityUtils.resetEnv();
+}
+
add_task(async function test_splitViewCreateAndAddTabs() {
let tab1 = BrowserTestUtils.addTab(gBrowser, "about:blank");
let tab2 = BrowserTestUtils.addTab(gBrowser, "about:blank");
@@ -177,3 +185,42 @@ add_task(async function test_split_view_preserves_multiple_pairings() {
splitView1.close();
splitView2.close();
});
+
+add_task(async function test_resize_split_view_panels() {
+ const tab1 = await addTabAndLoadBrowser();
+ const tab2 = await addTabAndLoadBrowser();
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+
+ info("Activate split view.");
+ const splitView = gBrowser.addTabSplitView([tab1, tab2]);
+ const { tabpanels } = gBrowser;
+ await BrowserTestUtils.waitForMutationCondition(
+ tabpanels,
+ { childList: true },
+ () => tabpanels.querySelector(".split-view-splitter")
+ );
+ await BrowserTestUtils.waitForMutationCondition(
+ tabpanels.splitViewSplitter,
+ { attributes: true },
+ () => BrowserTestUtils.isVisible(tabpanels.splitViewSplitter)
+ );
+
+ info("Resize split view panels.");
+ const leftPanel = document.getElementById(tab1.linkedPanel);
+ const rightPanel = document.getElementById(tab2.linkedPanel);
+ const originalLeftWidth = leftPanel.getBoundingClientRect().width;
+ const originalRightWidth = rightPanel.getBoundingClientRect().width;
+ dragSplitter(-100, tabpanels.splitViewSplitter);
+ Assert.less(
+ leftPanel.getBoundingClientRect().width,
+ originalLeftWidth,
+ "Left panel is smaller."
+ );
+ Assert.greater(
+ rightPanel.getBoundingClientRect().width,
+ originalRightWidth,
+ "Right panel is larger."
+ );
+
+ splitView.close();
+});
diff --git a/browser/themes/shared/tabbrowser/content-area.css b/browser/themes/shared/tabbrowser/content-area.css
@@ -109,6 +109,11 @@
min-height: 0;
}
+.browserSidebarContainer {
+ position: absolute;
+ inset: 0;
+}
+
.sidebar-browser-stack {
flex: 1;
}
@@ -125,6 +130,7 @@
padding: 0;
color-scheme: unset;
background: var(--tabpanel-background-color);
+ display: flex;
&[pendingpaint] {
background-image: url("chrome://global/skin/icons/pendingpaint.png");
@@ -145,12 +151,33 @@
-moz-user-focus: none !important;
}
- .split-view-panel[column="0"] {
- grid-column: 1;
- }
+ &[splitview] {
+ --panel-min-width: 140px;
+
+ .split-view-panel {
+ position: static;
+ flex: 1;
+ min-width: var(--panel-min-width);
+ max-width: calc(100% - var(--panel-min-width));
+ }
+
+ .split-view-panel[column="0"] {
+ order: 0;
+ }
+
+ .split-view-splitter {
+ order: 1;
+ flex: none;
+ }
+
+ .split-view-panel[column="1"] {
+ order: 2;
+ }
- .split-view-panel[column="1"] {
- grid-column: 2;
+ /* Panels with a custom width shouldn't grow. */
+ .split-view-panel[width] {
+ flex: none;
+ }
}
}
diff --git a/toolkit/content/widgets/tabbox.js b/toolkit/content/widgets/tabbox.js
@@ -263,6 +263,13 @@
*/
#splitViewPanels = [];
+ /**
+ * The splitter placed in between Split View panels.
+ *
+ * @type {XULElement}
+ */
+ #splitViewSplitter = null;
+
constructor() {
super();
this._tabbox = null;
@@ -278,6 +285,17 @@
return (this._tabbox = this.closest("tabbox"));
}
+ get splitViewSplitter() {
+ if (!this.#splitViewSplitter) {
+ const splitter = document.createXULElement("splitter");
+ splitter.className = "split-view-splitter";
+ splitter.setAttribute("resizebefore", "sibling");
+ splitter.setAttribute("resizeafter", "none");
+ this.#splitViewSplitter = splitter;
+ }
+ return this.#splitViewSplitter;
+ }
+
/**
* nsIDOMXULRelatedElement
*/
@@ -329,12 +347,11 @@
}
for (const [i, panel] of newPanels.entries()) {
const panelEl = document.getElementById(panel);
- if (panelEl) {
- panelEl.classList.add("split-view-panel");
- panelEl.setAttribute("column", i);
- }
+ panelEl?.classList.add("split-view-panel");
+ panelEl?.setAttribute("column", i);
}
this.#splitViewPanels = newPanels;
+ this.#isSplitViewActive = !!newPanels.length;
}
get splitViewPanels() {
@@ -350,16 +367,25 @@
*/
removePanelFromSplitView(panel, updateArray = true) {
const panelEl = document.getElementById(panel);
- if (panelEl) {
- panelEl.classList.remove("split-view-panel");
- panelEl.removeAttribute("column");
- }
+ panelEl?.classList.remove("split-view-panel");
+ panelEl?.removeAttribute("column");
if (updateArray) {
const index = this.#splitViewPanels.indexOf(panel);
if (index !== -1) {
this.#splitViewPanels.splice(index, 1);
}
}
+ this.#isSplitViewActive = !!this.#splitViewPanels.length;
+ }
+
+ set #isSplitViewActive(isActive) {
+ this.toggleAttribute("splitview", isActive);
+ this.splitViewSplitter.hidden = !isActive;
+ if (isActive) {
+ // Place splitter after first panel, so that it can be resized.
+ const firstPanel = document.getElementById(this.splitViewPanels[0]);
+ firstPanel?.after(this.#splitViewSplitter);
+ }
}
}
diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css
@@ -462,6 +462,7 @@ deck > *|*:not(:-moz-native-anonymous) {
tabpanels > .deck-selected,
tabpanels > .split-view-panel,
+tabpanels > .split-view-splitter,
deck > .deck-selected {
-moz-subtree-hidden-only-visually: 0;
visibility: inherit;