feat: Added empty splits support and more urlbar actions, b=no-bug, c=workspaces, common, kbs, split-view, tests

This commit is contained in:
Mr. M
2025-09-14 17:41:12 +02:00
parent 471de3a25f
commit e6882a42d6
15 changed files with 217 additions and 53 deletions

View File

@@ -311,6 +311,7 @@ zen-split-view-shortcut-grid = Toggle Split View Grid
zen-split-view-shortcut-vertical = Toggle Split View Vertical zen-split-view-shortcut-vertical = Toggle Split View Vertical
zen-split-view-shortcut-horizontal = Toggle Split View Horizontal zen-split-view-shortcut-horizontal = Toggle Split View Horizontal
zen-split-view-shortcut-unsplit = Close Split View zen-split-view-shortcut-unsplit = Close Split View
zen-new-empty-split-view-shortcut = New Empty Split View
zen-key-select-tab-1 = Select tab #1 zen-key-select-tab-1 = Select tab #1
zen-key-select-tab-2 = Select tab #2 zen-key-select-tab-2 = Select tab #2
zen-key-select-tab-3 = Select tab #3 zen-key-select-tab-3 = Select tab #3

View File

@@ -7,6 +7,9 @@ zen-panel-ui-workspaces-create =
zen-panel-ui-folder-create = zen-panel-ui-folder-create =
.label = Create Folder .label = Create Folder
zen-panel-ui-new-empty-split =
.label = New Split
zen-workspaces-panel-context-delete = zen-workspaces-panel-context-delete =
.label = Delete Space .label = Delete Space
.accesskey = D .accesskey = D

View File

@@ -19,6 +19,7 @@
<command id="cmd_zenSplitViewUnsplit" /> <command id="cmd_zenSplitViewUnsplit" />
<command id="cmd_zenSplitViewLinkInNewTab" /> <command id="cmd_zenSplitViewLinkInNewTab" />
<command id="cmd_zenSplitViewContextMenu" /> <command id="cmd_zenSplitViewContextMenu" />
<command id="cmd_zenNewEmptySplit" />
<!-- Workspace commands --> <!-- Workspace commands -->
<command id="cmd_zenWorkspaceSwitch1" /> <command id="cmd_zenWorkspaceSwitch1" />

View File

@@ -3,10 +3,11 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
<menupopup id="zenCreateNewPopup"> <menupopup id="zenCreateNewPopup">
<menuitem data-l10n-id="tabs-toolbar-new-tab" command="cmd_newNavigatorTab" image="chrome://browser/skin/zen-icons/plus.svg" />
<menuseparator/>
<menuitem data-l10n-id="zen-panel-ui-folder-create" command="cmd_zenOpenFolderCreation" image="chrome://browser/skin/zen-icons/folder.svg" />
<menuitem data-l10n-id="zen-panel-ui-workspaces-create" command="cmd_zenOpenWorkspaceCreation" image="chrome://browser/skin/zen-icons/duplicate-tab.svg" /> <menuitem data-l10n-id="zen-panel-ui-workspaces-create" command="cmd_zenOpenWorkspaceCreation" image="chrome://browser/skin/zen-icons/duplicate-tab.svg" />
<menuitem data-l10n-id="zen-panel-ui-folder-create" command="cmd_zenOpenFolderCreation" image="chrome://browser/skin/zen-icons/folder.svg" />
<menuseparator/>
<menuitem data-l10n-id="zen-panel-ui-new-empty-split" command="cmd_zenNewEmptySplit" image="chrome://browser/skin/zen-icons/split.svg" />
<menuitem data-l10n-id="tabs-toolbar-new-tab" command="cmd_newNavigatorTab" image="chrome://browser/skin/zen-icons/plus.svg" />
</menupopup> </menupopup>
<menupopup id="zenWorkspaceMoreActions"> <menupopup id="zenWorkspaceMoreActions">

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010eedf8dcc29 100644 index 3204f253c23551650991d3385dd256d55892a012..e5a907a81526fde51087a0c33599fbb2948420ad 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js --- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js +++ b/browser/components/tabbrowser/content/tabbrowser.js
@@ -427,15 +427,64 @@ @@ -427,15 +427,64 @@
@@ -395,10 +395,10 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
+ gZenWorkspaces._initialTab._shouldRemove = true; + gZenWorkspaces._initialTab._shouldRemove = true;
+ } + }
+ } + }
+ } }
+ else { + else {
+ gZenWorkspaces._tabToRemoveForEmpty = this.selectedTab; + gZenWorkspaces._tabToRemoveForEmpty = this.selectedTab;
} + }
+ this._hasAlreadyInitializedZenSessionStore = true; + this._hasAlreadyInitializedZenSessionStore = true;
if (tabs.length > 1 || !tabs[0].selected) { if (tabs.length > 1 || !tabs[0].selected) {
@@ -522,17 +522,22 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
if ( if (
!this._beginRemoveTab(aTab, { !this._beginRemoveTab(aTab, {
closeWindowFastpath: true, closeWindowFastpath: true,
@@ -4796,7 +4937,9 @@ @@ -4796,7 +4937,13 @@
// We're not animating, so we can cancel the animation stopwatch. // We're not animating, so we can cancel the animation stopwatch.
Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId); Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId);
aTab._closeTimeAnimTimerId = null; aTab._closeTimeAnimTimerId = null;
- this._endRemoveTab(aTab);
+ if (animate && !gReduceMotion && !gZenUIManager.testingEnabled) {
+ gZenVerticalTabsManager.animateTabClose(aTab, (animate && !gReduceMotion)).then(() => { + gZenVerticalTabsManager.animateTabClose(aTab, (animate && !gReduceMotion)).then(() => {
this._endRemoveTab(aTab); + this._endRemoveTab(aTab);
+ }); + });
+ } else {
+ this._endRemoveTab(aTab);
+ }
return; return;
} }
@@ -4930,7 +5073,7 @@ @@ -4930,7 +5077,7 @@
closeWindowWithLastTab != null closeWindowWithLastTab != null
? closeWindowWithLastTab ? closeWindowWithLastTab
: !window.toolbar.visible || : !window.toolbar.visible ||
@@ -541,7 +546,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
if (closeWindow) { if (closeWindow) {
// We've already called beforeunload on all the relevant tabs if we get here, // We've already called beforeunload on all the relevant tabs if we get here,
@@ -4954,6 +5097,7 @@ @@ -4954,6 +5101,7 @@
newTab = true; newTab = true;
} }
@@ -549,7 +554,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
aTab._endRemoveArgs = [closeWindow, newTab]; aTab._endRemoveArgs = [closeWindow, newTab];
// swapBrowsersAndCloseOther will take care of closing the window without animation. // swapBrowsersAndCloseOther will take care of closing the window without animation.
@@ -4994,13 +5138,7 @@ @@ -4994,13 +5142,7 @@
aTab._mouseleave(); aTab._mouseleave();
if (newTab) { if (newTab) {
@@ -564,7 +569,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
} else { } else {
TabBarVisibility.update(); TabBarVisibility.update();
} }
@@ -5133,6 +5271,7 @@ @@ -5133,6 +5275,7 @@
this.tabs[i]._tPos = i; this.tabs[i]._tPos = i;
} }
@@ -572,7 +577,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
if (!this._windowIsClosing) { if (!this._windowIsClosing) {
// update tab close buttons state // update tab close buttons state
this.tabContainer._updateCloseButtons(); this.tabContainer._updateCloseButtons();
@@ -5345,6 +5484,7 @@ @@ -5345,6 +5488,7 @@
} }
let excludeTabs = new Set(aExcludeTabs); let excludeTabs = new Set(aExcludeTabs);
@@ -580,7 +585,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
// If this tab has a successor, it should be selectable, since // If this tab has a successor, it should be selectable, since
// hiding or closing a tab removes that tab as a successor. // hiding or closing a tab removes that tab as a successor.
@@ -5357,13 +5497,13 @@ @@ -5357,13 +5501,13 @@
!excludeTabs.has(aTab.owner) && !excludeTabs.has(aTab.owner) &&
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose") Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
) { ) {
@@ -596,7 +601,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
); );
let tab = this.tabContainer.findNextTab(aTab, { let tab = this.tabContainer.findNextTab(aTab, {
@@ -5379,7 +5519,7 @@ @@ -5379,7 +5523,7 @@
} }
if (tab) { if (tab) {
@@ -605,7 +610,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
} }
// If no qualifying visible tab was found, see if there is a tab in // If no qualifying visible tab was found, see if there is a tab in
@@ -5400,7 +5540,7 @@ @@ -5400,7 +5544,7 @@
}); });
} }
@@ -614,7 +619,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
} }
_blurTab(aTab) { _blurTab(aTab) {
@@ -5802,10 +5942,10 @@ @@ -5802,10 +5946,10 @@
SessionStore.deleteCustomTabValue(aTab, "hiddenBy"); SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
} }
@@ -627,7 +632,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
aTab.selected || aTab.selected ||
aTab.closing || aTab.closing ||
// Tabs that are sharing the screen, microphone or camera cannot be hidden. // Tabs that are sharing the screen, microphone or camera cannot be hidden.
@@ -5864,6 +6004,7 @@ @@ -5864,6 +6008,7 @@
* @param {MozTabbrowserTab|MozTabbrowserTabGroup|MozTabbrowserTabGroup.labelElement} aTab * @param {MozTabbrowserTab|MozTabbrowserTabGroup|MozTabbrowserTabGroup.labelElement} aTab
*/ */
replaceTabWithWindow(aTab, aOptions) { replaceTabWithWindow(aTab, aOptions) {
@@ -635,7 +640,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
if (this.tabs.length == 1) { if (this.tabs.length == 1) {
return null; return null;
} }
@@ -5997,7 +6138,7 @@ @@ -5997,7 +6142,7 @@
* `true` if element is a `<tab-group>` * `true` if element is a `<tab-group>`
*/ */
isTabGroup(element) { isTabGroup(element) {
@@ -644,7 +649,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
} }
/** /**
@@ -6073,8 +6214,8 @@ @@ -6073,8 +6218,8 @@
} }
// Don't allow mixing pinned and unpinned tabs. // Don't allow mixing pinned and unpinned tabs.
@@ -655,7 +660,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
} else { } else {
tabIndex = Math.max(tabIndex, this.pinnedTabCount); tabIndex = Math.max(tabIndex, this.pinnedTabCount);
} }
@@ -6100,10 +6241,16 @@ @@ -6100,10 +6245,16 @@
this.#handleTabMove( this.#handleTabMove(
element, element,
() => { () => {
@@ -674,7 +679,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
if (neighbor && this.isTab(element) && tabIndex > element._tPos) { if (neighbor && this.isTab(element) && tabIndex > element._tPos) {
neighbor.after(element); neighbor.after(element);
} else { } else {
@@ -6161,23 +6308,28 @@ @@ -6161,23 +6312,28 @@
#moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) { #moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) {
if (this.isTabGroupLabel(targetElement)) { if (this.isTabGroupLabel(targetElement)) {
targetElement = targetElement.group; targetElement = targetElement.group;
@@ -709,7 +714,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
} else if (!element.pinned && targetElement && targetElement.pinned) { } else if (!element.pinned && targetElement && targetElement.pinned) {
// If the caller asks to move an unpinned element next to a pinned // If the caller asks to move an unpinned element next to a pinned
// tab, move the unpinned element to be the first unpinned element // tab, move the unpinned element to be the first unpinned element
@@ -6190,14 +6342,34 @@ @@ -6190,14 +6346,34 @@
// move the tab group right before the first unpinned tab. // move the tab group right before the first unpinned tab.
// 4. Moving a tab group and the first unpinned tab is grouped: // 4. Moving a tab group and the first unpinned tab is grouped:
// move the tab group right before the first unpinned tab's tab group. // move the tab group right before the first unpinned tab's tab group.
@@ -745,7 +750,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
element.pinned element.pinned
? this.tabContainer.pinnedTabsContainer ? this.tabContainer.pinnedTabsContainer
: this.tabContainer; : this.tabContainer;
@@ -6206,7 +6378,7 @@ @@ -6206,7 +6382,7 @@
element, element,
() => { () => {
if (moveBefore) { if (moveBefore) {
@@ -754,7 +759,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
} else if (targetElement) { } else if (targetElement) {
targetElement.after(element); targetElement.after(element);
} else { } else {
@@ -6252,10 +6424,10 @@ @@ -6252,10 +6428,10 @@
* @param {TabMetricsContext} [metricsContext] * @param {TabMetricsContext} [metricsContext]
*/ */
moveTabToGroup(aTab, aGroup, metricsContext) { moveTabToGroup(aTab, aGroup, metricsContext) {
@@ -767,7 +772,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
return; return;
} }
if (aTab.group && aTab.group.id === aGroup.id) { if (aTab.group && aTab.group.id === aGroup.id) {
@@ -6285,6 +6457,7 @@ @@ -6285,6 +6461,7 @@
let state = { let state = {
tabIndex: tab._tPos, tabIndex: tab._tPos,
@@ -775,7 +780,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
}; };
if (tab.visible) { if (tab.visible) {
state.elementIndex = tab.elementIndex; state.elementIndex = tab.elementIndex;
@@ -6311,7 +6484,7 @@ @@ -6311,7 +6488,7 @@
let changedTabGroup = let changedTabGroup =
previousTabState.tabGroupId != currentTabState.tabGroupId; previousTabState.tabGroupId != currentTabState.tabGroupId;
@@ -784,7 +789,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
tab.dispatchEvent( tab.dispatchEvent(
new CustomEvent("TabMove", { new CustomEvent("TabMove", {
bubbles: true, bubbles: true,
@@ -6348,6 +6521,10 @@ @@ -6348,6 +6525,10 @@
moveActionCallback(); moveActionCallback();
@@ -795,7 +800,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
// Clear tabs cache after moving nodes because the order of tabs may have // Clear tabs cache after moving nodes because the order of tabs may have
// changed. // changed.
this.tabContainer._invalidateCachedTabs(); this.tabContainer._invalidateCachedTabs();
@@ -7249,7 +7426,7 @@ @@ -7249,7 +7430,7 @@
// preventDefault(). It will still raise the window if appropriate. // preventDefault(). It will still raise the window if appropriate.
break; break;
} }
@@ -804,7 +809,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
window.focus(); window.focus();
aEvent.preventDefault(); aEvent.preventDefault();
break; break;
@@ -7264,7 +7441,6 @@ @@ -7264,7 +7445,6 @@
} }
case "TabGroupCollapse": case "TabGroupCollapse":
aEvent.target.tabs.forEach(tab => { aEvent.target.tabs.forEach(tab => {
@@ -812,7 +817,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
}); });
break; break;
case "TabGroupCreateByUser": case "TabGroupCreateByUser":
@@ -8199,6 +8375,7 @@ @@ -8199,6 +8379,7 @@
aWebProgress.isTopLevel aWebProgress.isTopLevel
) { ) {
this.mTab.setAttribute("busy", "true"); this.mTab.setAttribute("busy", "true");
@@ -820,7 +825,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
gBrowser._tabAttrModified(this.mTab, ["busy"]); gBrowser._tabAttrModified(this.mTab, ["busy"]);
this.mTab._notselectedsinceload = !this.mTab.selected; this.mTab._notselectedsinceload = !this.mTab.selected;
} }
@@ -9200,7 +9377,7 @@ var TabContextMenu = { @@ -9200,7 +9381,7 @@ var TabContextMenu = {
); );
contextUnpinSelectedTabs.hidden = contextUnpinSelectedTabs.hidden =
!this.contextTab.pinned || !this.multiselected; !this.contextTab.pinned || !this.multiselected;
@@ -829,7 +834,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
// Build Ask Chat items // Build Ask Chat items
TabContextMenu.GenAI.buildTabMenu( TabContextMenu.GenAI.buildTabMenu(
document.getElementById("context_askChat"), document.getElementById("context_askChat"),
@@ -9520,6 +9697,7 @@ var TabContextMenu = { @@ -9520,6 +9701,7 @@ var TabContextMenu = {
) )
); );
} else { } else {

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/urlbar/UrlbarView.sys.mjs b/browser/components/urlbar/UrlbarView.sys.mjs diff --git a/browser/components/urlbar/UrlbarView.sys.mjs b/browser/components/urlbar/UrlbarView.sys.mjs
index fdbab8806fd320f4aacec46a42c8ef953580d00c..40568280c3ba2f0a36f4443a5116430d3c502ec1 100644 index fdbab8806fd320f4aacec46a42c8ef953580d00c..5ed31d5dbfa2e2041e6616f6b036d67928e64114 100644
--- a/browser/components/urlbar/UrlbarView.sys.mjs --- a/browser/components/urlbar/UrlbarView.sys.mjs
+++ b/browser/components/urlbar/UrlbarView.sys.mjs +++ b/browser/components/urlbar/UrlbarView.sys.mjs
@@ -613,7 +613,7 @@ export class UrlbarView { @@ -613,7 +613,7 @@ export class UrlbarView {
@@ -27,7 +27,7 @@ index fdbab8806fd320f4aacec46a42c8ef953580d00c..40568280c3ba2f0a36f4443a5116430d
+ setAccessibleFocus: + setAccessibleFocus:
+ this.controller._userSelectionBehavior == "arrow", + this.controller._userSelectionBehavior == "arrow",
+ }); + });
+ }, 150); + }, 140);
} }
} }

View File

@@ -111,7 +111,7 @@ export var ZenCustomizableUI = new (class {
return; return;
} }
const popup = window.document.getElementById('zenCreateNewPopup'); const popup = window.document.getElementById('zenCreateNewPopup');
popup.openPopup(button, 'after_start'); popup.openPopup(button, 'before_start');
}); });
} }

View File

@@ -274,22 +274,23 @@ var gZenUIManager = {
return true; return true;
}, },
handleNewTab(werePassedURL, searchClipboard, where) { handleNewTab(werePassedURL, searchClipboard, where, overridePreferance = false) {
// Validate browser state first // Validate browser state first
if (!this._validateBrowserState()) { if (!this._validateBrowserState()) {
console.warn('Browser state invalid for new tab operation'); console.warn('Browser state invalid for new tab operation');
return false; return false;
} }
if (this.testingEnabled) { if (this.testingEnabled && !overridePreferance) {
return false; return false;
} }
const shouldOpenURLBar = const shouldOpenURLBar =
gZenVerticalTabsManager._canReplaceNewTab && overridePreferance ||
(gZenVerticalTabsManager._canReplaceNewTab &&
!werePassedURL && !werePassedURL &&
!searchClipboard && !searchClipboard &&
where === 'tab'; where === 'tab');
if (!shouldOpenURLBar) { if (!shouldOpenURLBar) {
return false; return false;
@@ -399,7 +400,10 @@ var gZenUIManager = {
if (gURLBar.focused) { if (gURLBar.focused) {
setTimeout(() => { setTimeout(() => {
gURLBar.view.close({ elementPicked: onSwitch }); window.dispatchEvent(
new CustomEvent('ZenURLBarClosed', { detail: { onSwitch, onElementPicked } })
);
gURLBar.view.close({ elementPicked: onElementPicked });
gURLBar.updateTextOverflow(); gURLBar.updateTextOverflow();
// Ensure tab and browser are valid before updating state // Ensure tab and browser are valid before updating state

View File

@@ -79,6 +79,11 @@ document.addEventListener(
case 'cmd_zenSplitViewLinkInNewTab': case 'cmd_zenSplitViewLinkInNewTab':
gZenViewSplitter.splitLinkInNewTab(); gZenViewSplitter.splitLinkInNewTab();
break; break;
case 'cmd_zenNewEmptySplit':
setTimeout(() => {
gZenViewSplitter.createEmptySplit();
}, 0);
break;
case 'cmd_zenReplacePinnedUrlWithCurrent': case 'cmd_zenReplacePinnedUrlWithCurrent':
gZenPinnedTabManager.replacePinnedUrlWithCurrent(); gZenPinnedTabManager.replacePinnedUrlWithCurrent();
break; break;

View File

@@ -816,7 +816,7 @@ class nsZenKeyboardShortcutsLoader {
} }
class nsZenKeyboardShortcutsVersioner { class nsZenKeyboardShortcutsVersioner {
static LATEST_KBS_VERSION = 10; static LATEST_KBS_VERSION = 11;
constructor() {} constructor() {}
@@ -1078,6 +1078,21 @@ class nsZenKeyboardShortcutsVersioner {
) )
); );
} }
if (version < 11) {
// Migrate from version 10 to 11
data.push(
new KeyShortcut(
'zen-new-empty-split-view',
'+',
'',
ZEN_SPLIT_VIEW_SHORTCUTS_GROUP,
nsKeyShortcutModifiers.fromObject({ accel: true, alt: true }),
'cmd_zenNewEmptySplit',
'zen-new-empty-split-view-shortcut'
)
);
}
return data; return data;
} }
} }

View File

@@ -1008,6 +1008,15 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
tab.linkedBrowser.docShellIsActive = true; tab.linkedBrowser.docShellIsActive = true;
} }
this._maybeRemoveFakeBrowser(); this._maybeRemoveFakeBrowser();
{
const shouldDisableEmptySplits = tab.hasAttribute('zen-empty-tab') || tab.splitView;
const command = document.getElementById('cmd_zenNewEmptySplit');
if (shouldDisableEmptySplits) {
command.setAttribute('disabled', 'true');
} else {
command.removeAttribute('disabled');
}
}
} }
/** /**
@@ -1913,6 +1922,42 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
} }
return true; return true;
} }
createEmptySplit() {
const selectedTab = gBrowser.selectedTab;
const emptyTab = gZenWorkspaces._emptyTab;
const data = {
tabs: [selectedTab, emptyTab],
gridType: 'grid',
layoutTree: this.calculateLayoutTree([selectedTab, emptyTab], 'grid'),
};
this._data.push(data);
this.activateSplitView(data);
gBrowser.selectedTab = emptyTab;
window.addEventListener(
'ZenURLBarClosed',
(event) => {
const { onElementPicked } = event.detail;
const groupIndex = this._data.findIndex((group) => group.tabs.includes(emptyTab));
const newSelectedTab = gBrowser.selectedTab;
requestAnimationFrame(() => {
this.removeTabFromGroup(emptyTab, groupIndex);
if (onElementPicked) {
if (newSelectedTab === emptyTab || newSelectedTab === selectedTab) {
return;
}
this.splitTabs([selectedTab, newSelectedTab], 'grid', 1);
} else {
gBrowser.selectedTab = selectedTab;
}
});
},
{ once: true }
);
setTimeout(() => {
gZenUIManager.handleNewTab(false, false, 'tab', true);
}, 0);
}
} }
window.gZenViewSplitter = new nsZenViewSplitter(); window.gZenViewSplitter = new nsZenViewSplitter();

View File

@@ -14,3 +14,4 @@ support-files = [
["browser_split_browser_duplication.js"] ["browser_split_browser_duplication.js"]
["browser_split_view_with_glance.js"] ["browser_split_view_with_glance.js"]
["browser_split_view_with_folders.js"] ["browser_split_view_with_folders.js"]
["browser_split_view_empty.js"]

View File

@@ -0,0 +1,49 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const { UrlbarTestUtils } = ChromeUtils.importESModule(
'resource://testing-common/UrlbarTestUtils.sys.mjs'
);
add_task(async function test_Split_View_Empty() {
await BrowserTestUtils.withNewTab('https://example.com', async function () {
const originalTab = gBrowser.selectedTab;
const command = document.getElementById('cmd_zenNewEmptySplit');
command.doCommand();
await UrlbarTestUtils.promisePopupOpen(window, () => {});
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
waitForFocus,
value: 'https://example.com',
});
const waitForActivationPromise = BrowserTestUtils.waitForEvent(
window,
'ZenViewSplitter:SplitViewActivated'
);
let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
EventUtils.synthesizeMouseAtCenter(result.element.row, {});
await waitForActivationPromise;
await new Promise((resolve) => {
setTimeout(async () => {
resolve();
}, 100);
});
const selectedTab = gBrowser.selectedTab;
ok(
gBrowser.tabpanels.hasAttribute('zen-split-view'),
'The split view should not have crashed with two tabs in it'
);
ok(!gZenWorkspaces._emptyTab.splitView, 'The empty tab should not be in split view');
ok(!gZenWorkspaces._emptyTab.group, 'The empty tab should not be in a group');
ok(selectedTab.splitView, 'The selected tab should be in split view');
ok(originalTab.splitView, 'The original tab should be in split view');
Assert.equal(
gBrowser.tabpanels.querySelectorAll('[zen-split="true"]').length,
2,
'There should be two split views present'
);
await BrowserTestUtils.removeTab(selectedTab);
});
});

View File

@@ -64,21 +64,22 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
} }
/** /**
* @param {Window} window The window to check available actions for.
* @returns All the available global actions. * @returns All the available global actions.
*/ */
get #availableActions() { #getAvailableActions(window) {
return globalActions.filter((a) => return globalActions.filter((a) => a.isAvailable(window));
typeof a.isAvailable === 'function' ? a.isAvailable() : true
);
} }
/** /**
* Starts a search query amongst the available global actions. * Starts a search query amongst the available global actions.
* *
* @param {string} queryContext The query context object * @param {string} query The user's search query.
*
*/ */
#findMatchingActions(query) { #findMatchingActions(query) {
const actions = this.#availableActions; const window = lazy.BrowserWindowTracker.getTopWindow();
const actions = this.#getAvailableActions(window);
let results = []; let results = [];
for (let action of actions) { for (let action of actions) {
const label = action.label; const label = action.label;
@@ -261,6 +262,10 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
return; return;
} }
const ownerGlobal = details.element.ownerGlobal; const ownerGlobal = details.element.ownerGlobal;
if (typeof command === 'function') {
command(ownerGlobal);
return;
}
const commandToRun = ownerGlobal.document.getElementById(command); const commandToRun = ownerGlobal.document.getElementById(command);
if (commandToRun) { if (commandToRun) {
ownerGlobal.gBrowser.selectedBrowser.focus(); ownerGlobal.gBrowser.selectedBrowser.focus();

View File

@@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
export const globalActions = [ const globalActionsTemplate = [
{ {
label: 'Toggle Compact Mode', label: 'Toggle Compact Mode',
command: 'cmd_zenCompactModeToggle', command: 'cmd_zenCompactModeToggle',
@@ -15,4 +15,33 @@ export const globalActions = [
icon: 'chrome://browser/skin/zen-icons/edit-theme.svg', icon: 'chrome://browser/skin/zen-icons/edit-theme.svg',
suggestedIndex: 4, suggestedIndex: 4,
}, },
{
label: 'New Split View',
command: 'cmd_zenNewEmptySplit',
icon: 'chrome://browser/skin/zen-icons/split.svg',
suggestedIndex: 0,
},
{
label: 'New Folder',
command: 'cmd_zenOpenFolderCreation',
icon: 'chrome://browser/skin/zen-icons/folder.svg',
},
{
label: 'Copy Current URL',
command: 'cmd_zenCopyCurrentURL',
icon: 'chrome://browser/skin/zen-icons/edit-copy.svg',
suggestedIndex: 0,
},
{
label: 'Settings',
command: (window) => window.openPreferences(),
icon: 'chrome://browser/skin/zen-icons/settings.svg',
},
]; ];
export const globalActions = globalActionsTemplate.map((action) => ({
...action,
isAvailable: (window) => {
return window.document.getElementById(action.command)?.getAttribute('disabled') !== 'true';
},
}));