feat: Added partial support for split views, b=no-bug, c=split-view

This commit is contained in:
mr. m
2025-12-14 12:03:37 +01:00
parent cc7780e078
commit fa1693d16b
2 changed files with 77 additions and 16 deletions

View File

@@ -36,6 +36,9 @@ const EVENTS = [
'TabGroupRemoved',
'TabGroupMoved',
//'ZenTabRemovedFromSplit',
//'ZenSplitViewTabsSplit',
'TabSelect',
'focus',
@@ -358,6 +361,8 @@ class nsZenWindowSync {
gBrowser.unpinTab(aTargetItem);
}
}
} else {
aTargetItem.pinned = aOriginalItem.pinned;
}
this.#moveItemToMatchOriginal(aOriginalItem, aTargetItem, aWindow, {
@@ -673,10 +678,9 @@ class nsZenWindowSync {
* @param {Object} aPreviousTab - The previously selected tab.
*/
async #onTabSwitchOrWindowFocus(aWindow, aPreviousTab = null) {
const selectedTab = aWindow.gBrowser.selectedTab;
// On some occasions, such as when closing a window, this
// function might be called multiple times for the same tab.
if (selectedTab === this.#lastSelectedTab) {
if (aWindow.gBrowser.selectedTab === this.#lastSelectedTab) {
return;
}
if (aPreviousTab?._zenContentsVisible) {
@@ -691,15 +695,20 @@ class nsZenWindowSync {
await this.#swapBrowserDocShellsAsync(otherTabToShow, aPreviousTab);
}
}
if (selectedTab._zenContentsVisible || selectedTab.hasAttribute('zen-empty-tab')) {
return;
}
const otherSelectedTab = this.#getActiveTabFromOtherWindows(aWindow, selectedTab.id);
selectedTab._zenContentsVisible = true;
if (otherSelectedTab) {
delete otherSelectedTab._zenContentsVisible;
await this.#swapBrowserDocShellsAsync(selectedTab, otherSelectedTab);
let promises = [];
for (const browserView of aWindow.gBrowser.selectedBrowsers) {
const selectedTab = aWindow.gBrowser.getTabForBrowser(browserView);
if (selectedTab._zenContentsVisible || selectedTab.hasAttribute('zen-empty-tab')) {
continue;
}
const otherSelectedTab = this.#getActiveTabFromOtherWindows(aWindow, selectedTab.id);
selectedTab._zenContentsVisible = true;
if (otherSelectedTab) {
delete otherSelectedTab._zenContentsVisible;
promises.push(this.#swapBrowserDocShellsAsync(selectedTab, otherSelectedTab));
}
}
await Promise.all(promises);
}
/**
@@ -931,11 +940,14 @@ class nsZenWindowSync {
const window = tabGroup.ownerGlobal;
const isFolder = tabGroup.isZenFolder;
const isSplitView = tabGroup.hasAttribute('split-view-group');
if (isSplitView) {
return; // Split view groups are synced via ZenSplitViewTabsSplit event.
}
// Tab groups already have an ID upon creation.
this.#runOnAllWindows(window, (win) => {
const newGroup = isFolder
? win.gZenFolders.createFolder([], {})
: win.gBrowser.addTabGroup({ splitView: isSplitView });
: win.gBrowser.addTabGroup([]);
newGroup.id = tabGroup.id;
newGroup.alreadySynced = true;
this.#syncItemWithOriginal(
@@ -969,6 +981,34 @@ class nsZenWindowSync {
on_TabGroupUpdate(aEvent) {
return this.#delegateGenericSyncEvent(aEvent, SYNC_FLAG_ICON | SYNC_FLAG_LABEL);
}
on_ZenTabRemovedFromSplit(aEvent) {
const tab = aEvent.target;
const window = tab.ownerGlobal;
this.#runOnAllWindows(window, (win) => {
const targetTab = this.#getItemFromWindow(win, tab.id);
if (targetTab && win.gZenViewSplitter) {
win.gZenViewSplitter.removeTabFromGroup(targetTab);
}
});
}
on_ZenSplitViewTabsSplit(aEvent) {
const tabGroup = aEvent.target;
const window = tabGroup.ownerGlobal;
const tabs = tabGroup.tabs;
this.#runOnAllWindows(window, (win) => {
const otherWindowTabs = tabs
.map((tab) => this.#getItemFromWindow(win, tab.id))
.filter(Boolean);
if (otherWindowTabs.length > 0 && win.gZenViewSplitter) {
const group = win.gZenViewSplitter.splitTabs(otherWindowTabs, 'grid');
if (group) {
group.id = tabGroup.id;
}
}
});
}
}
export const ZenWindowSync = new nsZenWindowSync();

View File

@@ -202,6 +202,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
this.resetTabState(tab, forUnsplit);
if (tab.group && tab.group.hasAttribute('split-view-group')) {
gBrowser.ungroupTab(tab);
this.#dispatchItemEvent('ZenTabRemovedFromSplit', tab);
}
if (group.tabs.length < 2) {
// We need to remove all remaining tabs from the group when unsplitting
@@ -895,6 +896,21 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
}
}
/**
* Dispatches a custom event on a tab.
*
* @param {string} eventName - The name of the event to dispatch.
* @param {HTMLElement} item - The item on which to dispatch the event.
*/
#dispatchItemEvent(eventName, item) {
const event = new CustomEvent(eventName, {
detail: { item },
bubbles: true,
cancelable: false,
});
item.dispatchEvent(event);
}
/**
* Removes a group.
*
@@ -905,6 +921,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
for (const tab of group.tabs.reverse()) {
if (tab.group?.hasAttribute('split-view-group')) {
gBrowser.ungroupTab(tab);
this.#dispatchItemEvent('ZenTabRemovedFromSplit', tab);
}
}
if (this.currentView === groupIndex) {
@@ -1069,7 +1086,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
* @param {string|undefined} gridType - The type of grid layout.
*/
splitTabs(tabs, gridType, initialIndex = 0) {
this.#withoutSplitViewTransition(() => {
return this.#withoutSplitViewTransition(() => {
// TODO: Add support for splitting essential tabs
tabs = tabs.filter((t) => !t.hidden && !t.hasAttribute('zen-empty-tab'));
if (tabs.length < 2 || tabs.length > this.MAX_TABS) {
@@ -1105,7 +1122,8 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
return;
}
this.activateSplitView(group, true);
return;
this.#dispatchItemEvent('ZenSplitViewTabsSplit', group);
return group;
}
// We are here if none of the tabs have been previously split
@@ -1150,6 +1168,8 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
return;
}
this.activateSplitView(splitData);
this.#dispatchItemEvent('ZenSplitViewTabsSplit', splitGroup);
return splitData;
});
}
@@ -1855,9 +1875,10 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
}
// We can't create an empty group, so only create if we have tabs
let group = null;
if (tabs?.length) {
// Create a new group with the initial tabs
gBrowser.addTabGroup(tabs, {
group = gBrowser.addTabGroup(tabs, {
label: '',
showCreateUI: false,
insertBefore: tabs[0],
@@ -1865,7 +1886,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
});
}
return null;
return group;
}
storeDataForSessionStore() {
@@ -1936,7 +1957,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
#withoutSplitViewTransition(callback) {
this.tabBrowserPanel.classList.add('zen-split-view-no-transition');
try {
callback();
return callback();
} finally {
requestAnimationFrame(() => {
this.tabBrowserPanel.classList.remove('zen-split-view-no-transition');