Skip to content

Update ZenViewSplitter and session store to support tab splitting for empty tabs. Added 'allow-split' attribute handling in various components to enhance tab management functionality. #9385

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion l10n
Submodule l10n updated 196 files
17 changes: 16 additions & 1 deletion src/browser/base/zen-components/ZenKeyboardShortcuts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,7 @@ class ZenKeyboardShortcutsLoader {
}

class ZenKeyboardShortcutsVersioner {
static LATEST_KBS_VERSION = 8;
static LATEST_KBS_VERSION = 9;

constructor() {}

Expand Down Expand Up @@ -937,6 +937,21 @@ class ZenKeyboardShortcutsVersioner {
)
);
}
if (version < 9) {
// Migrate from 8 to 9
// In this new version, we add the "Add a Split New Tab to the Right" shortcut
data.push(
new KeyShortcut(
'zen-split-view-new-tab-to-right',
'+',
'',
ZEN_SPLIT_VIEW_SHORTCUTS_GROUP,
KeyShortcutModifiers.fromObject({ accel: true, shift: true }),
'code:gZenViewSplitter.createSplitNewTabToRight()',
'zen-split-view-shortcut-add-new-tab-to-right'
)
);
}
return data;
}
}
Expand Down
76 changes: 72 additions & 4 deletions src/browser/base/zen-components/ZenViewSplitter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
*/
onTabSelect(event) {
const previousTab = event.detail.previousTab;
if (previousTab && !previousTab.hasAttribute('zen-empty-tab')) {
if (previousTab && (!previousTab.hasAttribute('zen-empty-tab') || previousTab.hasAttribute('allow-split'))) {
this._lastOpenedTab = previousTab;
}
}
Expand Down Expand Up @@ -178,7 +178,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
// tab copy or move
draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
// not our drop then
if (!draggedTab || gBrowser.selectedTab.hasAttribute('zen-empty-tab')) {
if (!draggedTab || (gBrowser.selectedTab.hasAttribute('zen-empty-tab') && !gBrowser.selectedTab.hasAttribute('allow-split'))) {
return;
}
draggedTab.container._finishMoveTogetherSelectedTabs(draggedTab);
Expand Down Expand Up @@ -893,7 +893,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
return false;
}
for (const tab of window.gBrowser.selectedTabs) {
if (tab.splitView || tab.hasAttribute('zen-empty-tab') || tab.hasAttribute('zen-essential')) {
if (tab.splitView || (tab.hasAttribute('zen-empty-tab') && !tab.hasAttribute('allow-split')) || tab.hasAttribute('zen-essential')) {
return false;
}
}
Expand Down Expand Up @@ -944,7 +944,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
*/
splitTabs(tabs, gridType, initialIndex = 0) {
// TODO: Add support for splitting essential tabs
tabs = tabs.filter((t) => !t.hidden && !t.hasAttribute('zen-empty-tab') && !t.hasAttribute('zen-essential'));
tabs = tabs.filter((t) => !t.hidden && (!t.hasAttribute('zen-empty-tab') || t.hasAttribute('allow-split')) && !t.hasAttribute('zen-essential'));
if (tabs.length < 2 || tabs.length > this.MAX_TABS) {
return;
}
Expand Down Expand Up @@ -1506,6 +1506,74 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
this.splitTabs(selected_tabs, gridType);
}

/**
* Creates a vertical split with a new tab and focuses the URL bar
* Used for Ctrl+Shift+'+' shortcut
* Can be called repeatedly to add more tabs to the right
*/
createSplitNewTabToRight() {
const currentTab = window.gBrowser.selectedTab;

if (currentTab.splitView) {
const groupIndex = this._data.findIndex((group) => group.tabs.includes(currentTab));
if (groupIndex >= 0) {
const group = this._data[groupIndex];
if (group.tabs.length < this.MAX_TABS) {
const newTab = this.openAndSwitchToTab('about:blank', { _forZenEmptyTab: true, _forZenEmptyTabAllowSplit: true });
group.tabs.push(newTab);

// Get the root node of the layout tree
const rootNode = group.layoutTree;

// For 'vsep' layout, add the new tab to the right end of the row
if (group.gridType === 'vsep' || rootNode.direction === 'row') {
// Calculate new sizes for all tabs
const newSize = 100 / (rootNode.children.length + 1);
const resizeFactor = newSize / (100 / rootNode.children.length);

// Resize existing tabs
rootNode.children.forEach((child) => {
child.sizeInParent *= resizeFactor;
});

// Add new tab at the end (not using addChild which puts it at the beginning)
const newNode = new SplitLeafNode(newTab, newSize);
newNode.parent = rootNode;
rootNode.children.push(newNode);

// Move tab to the group if needed
let splitGroup = this._getSplitViewGroup(group.tabs);
if (splitGroup) {
gBrowser.moveTabToGroup(newTab, splitGroup);
}

// Activate the updated layout
this.activateSplitView(group, true);

window.gBrowser.selectedTab = newTab;
window.gURLBar.select();
document.getElementById('Browser:OpenLocation').doCommand();
return;
}
}
}

// If we reached the maximum or layout isn't compatible, notify user
// (or silently do nothing)
return;
}

// First time pressing shortcut - create initial split
const newTab = this.openAndSwitchToTab('about:blank', { _forZenEmptyTab: true, _forZenEmptyTabAllowSplit: true });
this.splitTabs([currentTab, newTab], 'vsep');

window.gBrowser.selectedTab = newTab;

// Focus the URL bar
window.gURLBar.select();
document.getElementById('Browser:OpenLocation').doCommand();
}

/**
* @description removes the tab from the split
* @param container - The container element
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ index f814772114948f87cbb3c3a7231c95ea1f68d776..ae35daa518ca25f4fc95ef983519c390
return;
}
- if (aTab == aWindow.FirefoxViewHandler.tab) {
+ if (aTab == aWindow.FirefoxViewHandler.tab || aTab.hasAttribute("zen-empty-tab")) {
+ if (aTab == aWindow.FirefoxViewHandler.tab || (aTab.hasAttribute("zen-empty-tab") && !aTab.hasAttribute("allow-split"))) {
return;
}

Expand All @@ -32,7 +32,7 @@ index f814772114948f87cbb3c3a7231c95ea1f68d776..ae35daa518ca25f4fc95ef983519c390
// update the internal state data for this window
for (let tab of tabs) {
- if (tab == aWindow.FirefoxViewHandler.tab) {
+ if (tab == aWindow.FirefoxViewHandler.tab || tab.hasAttribute("zen-empty-tab")) {
+ if (tab == aWindow.FirefoxViewHandler.tab || (tab.hasAttribute("zen-empty-tab") && !tab.hasAttribute("allow-split"))) {
continue;
}
let tabData = lazy.TabState.collect(tab, TAB_CUSTOM_VALUES.get(tab));
Expand All @@ -41,7 +41,7 @@ index f814772114948f87cbb3c3a7231c95ea1f68d776..ae35daa518ca25f4fc95ef983519c390
// a window is closed, point to the first item in the tab strip instead (it will never be the Firefox View tab,
// since it's only inserted into the tab strip after it's selected).
- if (aWindow.FirefoxViewHandler.tab?.selected) {
+ if (aWindow.FirefoxViewHandler.tab?.selected || tabbrowser.selectedTab.hasAttribute("zen-empty-tab")) {
+ if (aWindow.FirefoxViewHandler.tab?.selected || (tabbrowser.selectedTab.hasAttribute("zen-empty-tab") && !tabbrowser.selectedTab.hasAttribute("allow-split"))) {
selectedIndex = 1;
winData.title = tabbrowser.tabs[0].label;
}
Expand All @@ -54,6 +54,9 @@ index f814772114948f87cbb3c3a7231c95ea1f68d776..ae35daa518ca25f4fc95ef983519c390
+ }
+ if (tabData.zenIsEmpty) {
+ tab.setAttribute("zen-empty-tab", "true");
+ if (tabData.zenAllowSplit) {
+ tab.setAttribute("allow-split", "true");
+ }
+ }
+ if (tabData.zenHasStaticLabel) {
+ tab.setAttribute("zen-has-static-label", "true");
Expand Down
1 change: 1 addition & 0 deletions src/browser/components/sessionstore/TabState-sys-mjs.patch
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ index 8f7ed557e6aa61e7e16ed4a8d785ad5fe651b3d8..aff63696d198055886960072a6130318
+ tabData.zenPinnedIcon = tab.getAttribute("zen-pinned-icon");
+ tabData.zenIsEmpty = tab.hasAttribute("zen-empty-tab");
+ tabData.zenHasStaticLabel = tab.hasAttribute("zen-has-static-label");
+ tabData.zenAllowSplit = tab.hasAttribute("allow-split");
+
tabData.searchMode = tab.ownerGlobal.gURLBar.getSearchMode(browser, true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ index e5d16e605b7edf11fc9f52b93e415087b76398f1..40b6e29d26c7f8d692a2a9a7d924b289
// (Normally hidden tabs would be unhidden when selected, but that doesn't
// happen for Firefox View).
- if (aTab.closing || (aTab.hidden && !aTab.selected)) {
+ if (aTab.closing || (aTab.hidden && !aTab.selected) || aTab.hasAttribute("zen-empty-tab")) {
+ if (aTab.closing || (aTab.hidden && !aTab.selected) || (aTab.hasAttribute("zen-empty-tab") && !aTab.hasAttribute("allow-split"))) {
return;
}

Expand All @@ -16,7 +16,7 @@ index e5d16e605b7edf11fc9f52b93e415087b76398f1..40b6e29d26c7f8d692a2a9a7d924b289
this._recentlyUsedTabs = Array.prototype.filter.call(
gBrowser.tabs,
- tab => !tab.closing && !tab.hidden
+ tab => !tab.closing && !tab.hidden && !tab.hasAttribute("zen-empty-tab")
+ tab => !tab.closing && !tab.hidden && (!tab.hasAttribute("zen-empty-tab") || tab.hasAttribute("allow-split"))
);
this._sortRecentlyUsedTabs();
},
4 changes: 2 additions & 2 deletions src/browser/components/tabbrowser/content/tab-js.patch
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ index 777eabb7524f2b021a03b3f54d69faee49b7381d..192d2fa058f7d9f043532c0ae6e4c081

get visible() {
- return this.isOpen && !this.hidden && !this.group?.collapsed;
+ return this.isOpen && !this.hidden && !this.group?.collapsed && !this.hasAttribute("zen-empty-tab");
+ return this.isOpen && !this.hidden && !this.group?.collapsed && (!this.hasAttribute("zen-empty-tab") || this.hasAttribute("allow-split"));
}

get hidden() {
Expand All @@ -44,7 +44,7 @@ index 777eabb7524f2b021a03b3f54d69faee49b7381d..192d2fa058f7d9f043532c0ae6e4c081
}

- return true;
+ return !this.hasAttribute("zen-empty-tab");
+ return !this.hasAttribute("zen-empty-tab") || this.hasAttribute("allow-split");
}

get lastAccessed() {
Expand Down
29 changes: 21 additions & 8 deletions src/browser/components/tabbrowser/content/tabbrowser-js.patch
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ index 628aa6596627c85efe361fc1ece8fd58f7ee653e..06a4aae50ebe8a42d08d4689d8e80a63
userContextId,
skipLoad,
+ _forZenEmptyTab,
+ _forZenEmptyTabAllowSplit,
} = {}) {
let b = document.createXULElement("browser");
// Use the JSM global to create the permanentKey, so that if the
Expand Down Expand Up @@ -195,14 +196,15 @@ index 628aa6596627c85efe361fc1ece8fd58f7ee653e..06a4aae50ebe8a42d08d4689d8e80a63
schemelessInput,
hasValidUserGestureActivation = false,
textDirectiveUserActivation = false,
+ _forZenEmptyTab,
} = {}
) {
// all callers of addTab that pass a params object need to pass
+ _forZenEmptyTab,
_forZenEmptyTabAllowSplit,
} = {}
) {
// all callers of addTab that pass a params object need to pass
@@ -2639,6 +2693,12 @@
);
}
);
}

+ let hasZenDefaultUserContextId = false;
+ let zenForcedWorkspaceId = undefined;
+ if (typeof ZenWorkspaces !== "undefined" && !_forZenEmptyTab) {
Expand All @@ -224,6 +226,9 @@ index 628aa6596627c85efe361fc1ece8fd58f7ee653e..06a4aae50ebe8a42d08d4689d8e80a63
+ }
+ if (_forZenEmptyTab) {
+ t.setAttribute("zen-empty-tab", "true");
+ if (_forZenEmptyTabAllowSplit) {
+ t.setAttribute("allow-split", "true");
+ }
+ }
if (insertTab) {
// insert the tab into the tab container in the correct position
Expand All @@ -233,6 +238,7 @@ index 628aa6596627c85efe361fc1ece8fd58f7ee653e..06a4aae50ebe8a42d08d4689d8e80a63
openWindowInfo,
skipLoad,
+ _forZenEmptyTab,
+ _forZenEmptyTabAllowSplit,
}));

if (focusUrlBar) {
Expand Down Expand Up @@ -280,7 +286,8 @@ index 628aa6596627c85efe361fc1ece8fd58f7ee653e..06a4aae50ebe8a42d08d4689d8e80a63
openWindowInfo,
name,
skipLoad,
+ _forZenEmptyTab
+ _forZenEmptyTab,
+ _forZenEmptyTabAllowSplit
});
}

Expand All @@ -293,6 +300,9 @@ index 628aa6596627c85efe361fc1ece8fd58f7ee653e..06a4aae50ebe8a42d08d4689d8e80a63
+ }
+ if (tabData.zenIsEmpty) {
+ tab.setAttribute("zen-empty-tab", "true");
+ if (tabData.zenAllowSplit) {
+ tab.setAttribute("allow-split", "true");
+ }
+ }
+ if (tabData.zenHasStaticLabel) {
+ tab.setAttribute("zen-has-static-label", "true");
Expand Down Expand Up @@ -333,6 +343,9 @@ index 628aa6596627c85efe361fc1ece8fd58f7ee653e..06a4aae50ebe8a42d08d4689d8e80a63
+ }
+ if (tabData.zenIsEmpty) {
+ tab.setAttribute("zen-empty-tab", "true");
+ if (tabData.zenAllowSplit) {
+ tab.setAttribute("allow-split", "true");
+ }
+ }
+ if (tabData.zenHasStaticLabel) {
+ tab.setAttribute("zen-has-static-label", "true");
Expand Down
2 changes: 1 addition & 1 deletion src/browser/components/tabbrowser/content/tabs-js.patch
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ index fa96568d366fd3608f9bd583fa793150bd815c8b..1c940a3b162919256ca73fa867c5c261
get visibleTabs() {
if (!this.#visibleTabs) {
- this.#visibleTabs = this.openTabs.filter(tab => tab.visible);
+ this.#visibleTabs = this.openTabs.filter(tab => tab.visible && !tab.hasAttribute("zen-empty-tab"));
+ this.#visibleTabs = this.openTabs.filter(tab => tab.visible && (!tab.hasAttribute("zen-empty-tab") || tab.hasAttribute("allow-split")));
}
return this.#visibleTabs;
}
Expand Down
2 changes: 1 addition & 1 deletion src/browser/modules/URILoadingHelper-sys-mjs.patch
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ index 313e895980c502d79f64f5a6ab838f6a96c83835..8a1a853567aeae2c368005d375781579
// we'll open a new tab instead.
let tab = w.gBrowser.getTabForBrowser(targetBrowser);
- if (tab == w.FirefoxViewHandler.tab) {
+ if (tab == w.FirefoxViewHandler.tab || tab.hasAttribute("zen-empty-tab")) {
+ if (tab == w.FirefoxViewHandler.tab || (tab.hasAttribute("zen-empty-tab") && !tab.hasAttribute("allow-split"))) {
where = "tab";
targetBrowser = null;
} else if (
2 changes: 1 addition & 1 deletion src/toolkit/content/widgets/tabbox-js.patch
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ index 033582a3badb65d50f58a11f8a259e28eaa04ef3..ef634d76549f0fa4d5789e2bbfb9409f
return null;
}
- if (filter(tab)) {
+ if (filter(tab) && !tab.hasAttribute("zen-glance-tab") && !tab.hasAttribute("zen-empty-tab")) {
+ if (filter(tab) && !tab.hasAttribute("zen-glance-tab") && (!tab.hasAttribute("zen-empty-tab") || tab.hasAttribute("allow-split"))) {
return tab;
}
}
Expand Down