mirror of
https://github.com/zen-browser/desktop.git
synced 2025-10-14 05:46:26 +00:00
Merge branch 'dev' of https://github.com/zen-browser/desktop into dev
This commit is contained in:
@@ -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-horizontal = Toggle Split View Horizontal
|
||||
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-2 = Select tab #2
|
||||
zen-key-select-tab-3 = Select tab #3
|
||||
|
@@ -7,6 +7,9 @@ zen-panel-ui-workspaces-create =
|
||||
zen-panel-ui-folder-create =
|
||||
.label = Create Folder
|
||||
|
||||
zen-panel-ui-new-empty-split =
|
||||
.label = New Split
|
||||
|
||||
zen-workspaces-panel-context-delete =
|
||||
.label = Delete Space
|
||||
.accesskey = D
|
||||
|
@@ -19,6 +19,7 @@
|
||||
<command id="cmd_zenSplitViewUnsplit" />
|
||||
<command id="cmd_zenSplitViewLinkInNewTab" />
|
||||
<command id="cmd_zenSplitViewContextMenu" />
|
||||
<command id="cmd_zenNewEmptySplit" />
|
||||
|
||||
<!-- Workspace commands -->
|
||||
<command id="cmd_zenWorkspaceSwitch1" />
|
||||
|
@@ -3,10 +3,11 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
<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-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 id="zenWorkspaceMoreActions">
|
||||
|
@@ -1,5 +1,5 @@
|
||||
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
|
||||
+++ b/browser/components/tabbrowser/content/tabbrowser.js
|
||||
@@ -427,15 +427,64 @@
|
||||
@@ -395,10 +395,10 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
+ gZenWorkspaces._initialTab._shouldRemove = true;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
+ else {
|
||||
+ gZenWorkspaces._tabToRemoveForEmpty = this.selectedTab;
|
||||
}
|
||||
+ }
|
||||
+ this._hasAlreadyInitializedZenSessionStore = true;
|
||||
|
||||
if (tabs.length > 1 || !tabs[0].selected) {
|
||||
@@ -522,17 +522,22 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
if (
|
||||
!this._beginRemoveTab(aTab, {
|
||||
closeWindowFastpath: true,
|
||||
@@ -4796,7 +4937,9 @@
|
||||
@@ -4796,7 +4937,13 @@
|
||||
// We're not animating, so we can cancel the animation stopwatch.
|
||||
Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId);
|
||||
aTab._closeTimeAnimTimerId = null;
|
||||
+ gZenVerticalTabsManager.animateTabClose(aTab, (animate && !gReduceMotion)).then(() => {
|
||||
this._endRemoveTab(aTab);
|
||||
+ });
|
||||
- this._endRemoveTab(aTab);
|
||||
+ if (animate && !gReduceMotion && !gZenUIManager.testingEnabled) {
|
||||
+ gZenVerticalTabsManager.animateTabClose(aTab, (animate && !gReduceMotion)).then(() => {
|
||||
+ this._endRemoveTab(aTab);
|
||||
+ });
|
||||
+ } else {
|
||||
+ this._endRemoveTab(aTab);
|
||||
+ }
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4930,7 +5073,7 @@
|
||||
@@ -4930,7 +5077,7 @@
|
||||
closeWindowWithLastTab != null
|
||||
? closeWindowWithLastTab
|
||||
: !window.toolbar.visible ||
|
||||
@@ -541,7 +546,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
|
||||
if (closeWindow) {
|
||||
// We've already called beforeunload on all the relevant tabs if we get here,
|
||||
@@ -4954,6 +5097,7 @@
|
||||
@@ -4954,6 +5101,7 @@
|
||||
|
||||
newTab = true;
|
||||
}
|
||||
@@ -549,7 +554,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
aTab._endRemoveArgs = [closeWindow, newTab];
|
||||
|
||||
// swapBrowsersAndCloseOther will take care of closing the window without animation.
|
||||
@@ -4994,13 +5138,7 @@
|
||||
@@ -4994,13 +5142,7 @@
|
||||
aTab._mouseleave();
|
||||
|
||||
if (newTab) {
|
||||
@@ -564,7 +569,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
} else {
|
||||
TabBarVisibility.update();
|
||||
}
|
||||
@@ -5133,6 +5271,7 @@
|
||||
@@ -5133,6 +5275,7 @@
|
||||
this.tabs[i]._tPos = i;
|
||||
}
|
||||
|
||||
@@ -572,7 +577,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
if (!this._windowIsClosing) {
|
||||
// update tab close buttons state
|
||||
this.tabContainer._updateCloseButtons();
|
||||
@@ -5345,6 +5484,7 @@
|
||||
@@ -5345,6 +5488,7 @@
|
||||
}
|
||||
|
||||
let excludeTabs = new Set(aExcludeTabs);
|
||||
@@ -580,7 +585,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
|
||||
// If this tab has a successor, it should be selectable, since
|
||||
// hiding or closing a tab removes that tab as a successor.
|
||||
@@ -5357,13 +5497,13 @@
|
||||
@@ -5357,13 +5501,13 @@
|
||||
!excludeTabs.has(aTab.owner) &&
|
||||
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
|
||||
) {
|
||||
@@ -596,7 +601,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
);
|
||||
|
||||
let tab = this.tabContainer.findNextTab(aTab, {
|
||||
@@ -5379,7 +5519,7 @@
|
||||
@@ -5379,7 +5523,7 @@
|
||||
}
|
||||
|
||||
if (tab) {
|
||||
@@ -605,7 +610,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -5802,10 +5942,10 @@
|
||||
@@ -5802,10 +5946,10 @@
|
||||
SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
|
||||
}
|
||||
|
||||
@@ -627,7 +632,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
aTab.selected ||
|
||||
aTab.closing ||
|
||||
// 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
|
||||
*/
|
||||
replaceTabWithWindow(aTab, aOptions) {
|
||||
@@ -635,7 +640,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
if (this.tabs.length == 1) {
|
||||
return null;
|
||||
}
|
||||
@@ -5997,7 +6138,7 @@
|
||||
@@ -5997,7 +6142,7 @@
|
||||
* `true` if element is a `<tab-group>`
|
||||
*/
|
||||
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.
|
||||
@@ -655,7 +660,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
} else {
|
||||
tabIndex = Math.max(tabIndex, this.pinnedTabCount);
|
||||
}
|
||||
@@ -6100,10 +6241,16 @@
|
||||
@@ -6100,10 +6245,16 @@
|
||||
this.#handleTabMove(
|
||||
element,
|
||||
() => {
|
||||
@@ -674,7 +679,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
if (neighbor && this.isTab(element) && tabIndex > element._tPos) {
|
||||
neighbor.after(element);
|
||||
} else {
|
||||
@@ -6161,23 +6308,28 @@
|
||||
@@ -6161,23 +6312,28 @@
|
||||
#moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) {
|
||||
if (this.isTabGroupLabel(targetElement)) {
|
||||
targetElement = targetElement.group;
|
||||
@@ -709,7 +714,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
} else if (!element.pinned && targetElement && targetElement.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
|
||||
@@ -6190,14 +6342,34 @@
|
||||
@@ -6190,14 +6346,34 @@
|
||||
// move the tab group right before the first unpinned tab.
|
||||
// 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.
|
||||
@@ -745,7 +750,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
element.pinned
|
||||
? this.tabContainer.pinnedTabsContainer
|
||||
: this.tabContainer;
|
||||
@@ -6206,7 +6378,7 @@
|
||||
@@ -6206,7 +6382,7 @@
|
||||
element,
|
||||
() => {
|
||||
if (moveBefore) {
|
||||
@@ -754,7 +759,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
} else if (targetElement) {
|
||||
targetElement.after(element);
|
||||
} else {
|
||||
@@ -6252,10 +6424,10 @@
|
||||
@@ -6252,10 +6428,10 @@
|
||||
* @param {TabMetricsContext} [metricsContext]
|
||||
*/
|
||||
moveTabToGroup(aTab, aGroup, metricsContext) {
|
||||
@@ -767,7 +772,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
return;
|
||||
}
|
||||
if (aTab.group && aTab.group.id === aGroup.id) {
|
||||
@@ -6285,6 +6457,7 @@
|
||||
@@ -6285,6 +6461,7 @@
|
||||
|
||||
let state = {
|
||||
tabIndex: tab._tPos,
|
||||
@@ -775,7 +780,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
};
|
||||
if (tab.visible) {
|
||||
state.elementIndex = tab.elementIndex;
|
||||
@@ -6311,7 +6484,7 @@
|
||||
@@ -6311,7 +6488,7 @@
|
||||
let changedTabGroup =
|
||||
previousTabState.tabGroupId != currentTabState.tabGroupId;
|
||||
|
||||
@@ -784,7 +789,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
tab.dispatchEvent(
|
||||
new CustomEvent("TabMove", {
|
||||
bubbles: true,
|
||||
@@ -6348,6 +6521,10 @@
|
||||
@@ -6348,6 +6525,10 @@
|
||||
|
||||
moveActionCallback();
|
||||
|
||||
@@ -795,7 +800,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
// Clear tabs cache after moving nodes because the order of tabs may have
|
||||
// changed.
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
@@ -7249,7 +7426,7 @@
|
||||
@@ -7249,7 +7430,7 @@
|
||||
// preventDefault(). It will still raise the window if appropriate.
|
||||
break;
|
||||
}
|
||||
@@ -804,7 +809,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
window.focus();
|
||||
aEvent.preventDefault();
|
||||
break;
|
||||
@@ -7264,7 +7441,6 @@
|
||||
@@ -7264,7 +7445,6 @@
|
||||
}
|
||||
case "TabGroupCollapse":
|
||||
aEvent.target.tabs.forEach(tab => {
|
||||
@@ -812,7 +817,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
});
|
||||
break;
|
||||
case "TabGroupCreateByUser":
|
||||
@@ -8199,6 +8375,7 @@
|
||||
@@ -8199,6 +8379,7 @@
|
||||
aWebProgress.isTopLevel
|
||||
) {
|
||||
this.mTab.setAttribute("busy", "true");
|
||||
@@ -820,7 +825,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
gBrowser._tabAttrModified(this.mTab, ["busy"]);
|
||||
this.mTab._notselectedsinceload = !this.mTab.selected;
|
||||
}
|
||||
@@ -9200,7 +9377,7 @@ var TabContextMenu = {
|
||||
@@ -9200,7 +9381,7 @@ var TabContextMenu = {
|
||||
);
|
||||
contextUnpinSelectedTabs.hidden =
|
||||
!this.contextTab.pinned || !this.multiselected;
|
||||
@@ -829,7 +834,7 @@ index 3204f253c23551650991d3385dd256d55892a012..0285b0bcf1e5ba769011c82729e010ee
|
||||
// Build Ask Chat items
|
||||
TabContextMenu.GenAI.buildTabMenu(
|
||||
document.getElementById("context_askChat"),
|
||||
@@ -9520,6 +9697,7 @@ var TabContextMenu = {
|
||||
@@ -9520,6 +9701,7 @@ var TabContextMenu = {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
|
@@ -11,24 +11,21 @@ index fdbab8806fd320f4aacec46a42c8ef953580d00c..5501c329a9fd12bcc8e12de396113497
|
||||
// Try to reuse the cached top-sites context. If it's not cached, then
|
||||
// there will be a gap of time between when the input is focused and
|
||||
// when the view opens that can be perceived as flicker.
|
||||
@@ -823,6 +823,19 @@ export class UrlbarView {
|
||||
@@ -823,7 +823,16 @@ export class UrlbarView {
|
||||
// them, resembling tab-to-search. In that case, the input value is
|
||||
// still associated with the first result.
|
||||
this.input.setResultForCurrentValue(firstResult);
|
||||
+ } else if (firstResult.payload.zenAction) {
|
||||
+ this.#selectElement(this.getFirstSelectableElement(), {
|
||||
+ updateInput: false,
|
||||
+ setAccessibleFocus:
|
||||
+ this.controller._userSelectionBehavior == "arrow",
|
||||
+ });
|
||||
+ this.window.setTimeout(() => {
|
||||
- }
|
||||
+ }
|
||||
+ this.window.setTimeout(() => {
|
||||
+ if (queryContext.results[0].payload.zenAction) {
|
||||
+ this.#selectElement(this.getFirstSelectableElement(), {
|
||||
+ updateInput: false,
|
||||
+ setAccessibleFocus:
|
||||
+ this.controller._userSelectionBehavior == "arrow",
|
||||
+ });
|
||||
+ }, 150);
|
||||
}
|
||||
+ }
|
||||
+ }, 10);
|
||||
}
|
||||
|
||||
@@ -3189,7 +3202,7 @@ export class UrlbarView {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
diff --git a/toolkit/components/extensions/parent/ext-runtime.js b/toolkit/components/extensions/parent/ext-runtime.js
|
||||
index 0d7a3e505b6bd30548c6dda1504dd343a517b083..71bab6f1562ef6ec43541e52573d2ed5c4e8e3af 100644
|
||||
index 0d7a3e505b6bd30548c6dda1504dd343a517b083..54400def5e02e886765fab68c3854a6b3c24ef2b 100644
|
||||
--- a/toolkit/components/extensions/parent/ext-runtime.js
|
||||
+++ b/toolkit/components/extensions/parent/ext-runtime.js
|
||||
@@ -333,7 +333,7 @@ this.runtime = class extends ExtensionAPIPersistent {
|
||||
@@ -7,7 +7,7 @@ index 0d7a3e505b6bd30548c6dda1504dd343a517b083..71bab6f1562ef6ec43541e52573d2ed5
|
||||
getBrowserInfo: function () {
|
||||
const { name, vendor, version, appBuildID } = Services.appinfo;
|
||||
- const info = { name, vendor, version, buildID: appBuildID };
|
||||
+ const info = { name, vendor, version: AppConstants.ZEN_FIREFOX_VERSION, buildID: appBuildID };
|
||||
+ const info = { name: 'firefox', vendor, version: AppConstants.ZEN_FIREFOX_VERSION, buildID: appBuildID };
|
||||
return Promise.resolve(info);
|
||||
},
|
||||
|
||||
|
@@ -111,7 +111,7 @@ export var ZenCustomizableUI = new (class {
|
||||
return;
|
||||
}
|
||||
const popup = window.document.getElementById('zenCreateNewPopup');
|
||||
popup.openPopup(button, 'after_start');
|
||||
popup.openPopup(button, 'before_start');
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -274,22 +274,23 @@ var gZenUIManager = {
|
||||
return true;
|
||||
},
|
||||
|
||||
handleNewTab(werePassedURL, searchClipboard, where) {
|
||||
handleNewTab(werePassedURL, searchClipboard, where, overridePreferance = false) {
|
||||
// Validate browser state first
|
||||
if (!this._validateBrowserState()) {
|
||||
console.warn('Browser state invalid for new tab operation');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.testingEnabled) {
|
||||
if (this.testingEnabled && !overridePreferance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const shouldOpenURLBar =
|
||||
gZenVerticalTabsManager._canReplaceNewTab &&
|
||||
!werePassedURL &&
|
||||
!searchClipboard &&
|
||||
where === 'tab';
|
||||
overridePreferance ||
|
||||
(gZenVerticalTabsManager._canReplaceNewTab &&
|
||||
!werePassedURL &&
|
||||
!searchClipboard &&
|
||||
where === 'tab');
|
||||
|
||||
if (!shouldOpenURLBar) {
|
||||
return false;
|
||||
@@ -399,7 +400,10 @@ var gZenUIManager = {
|
||||
|
||||
if (gURLBar.focused) {
|
||||
setTimeout(() => {
|
||||
gURLBar.view.close({ elementPicked: onSwitch });
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('ZenURLBarClosed', { detail: { onSwitch, onElementPicked } })
|
||||
);
|
||||
gURLBar.view.close({ elementPicked: onElementPicked });
|
||||
gURLBar.updateTextOverflow();
|
||||
|
||||
// Ensure tab and browser are valid before updating state
|
||||
|
@@ -502,21 +502,52 @@ button.popup-notification-dropmarker {
|
||||
}
|
||||
|
||||
.urlbarView-shortcutContent {
|
||||
border-radius: 4px;
|
||||
padding: 6px 8px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
margin-left: auto;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 95%);
|
||||
padding: 6px 8px;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
font-size: 10px;
|
||||
|
||||
&:empty {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.urlbarView-prettyName,
|
||||
.urlbarView-shortcutContent {
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.urlbarView-prettyName {
|
||||
padding: 4px 6px;
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 90%);
|
||||
margin-left: 6px;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
display: flex;
|
||||
color: color-mix(in srgb, var(--zen-primary-color), currentColor 95%);
|
||||
|
||||
& img {
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
&[workspaceIcon] {
|
||||
scale: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
&[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.urlbarView-row[has-action]:is([type='switchtab'], [type='remotetab'], [type='clipboard']) {
|
||||
& .urlbarView-action:last-child {
|
||||
margin-left: auto !important;
|
||||
@@ -575,14 +606,15 @@ button.popup-notification-dropmarker {
|
||||
--urlbarView-item-block-padding: 10px;
|
||||
color: light-dark(rgba(0, 0, 0, 0.7), rgba(255, 255, 255, 0.7)) !important;
|
||||
|
||||
&:hover.urlbarView-favicon,
|
||||
&:hover,
|
||||
& .urlbarView-shortcutContent {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--zen-branding-bg-reverse) 5%,
|
||||
transparent 95%
|
||||
) !important;
|
||||
&:hover {
|
||||
&,
|
||||
& .urlbarView-favicon {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--zen-branding-bg-reverse) 5%,
|
||||
transparent 95%
|
||||
) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&[selected] {
|
||||
@@ -612,6 +644,10 @@ button.popup-notification-dropmarker {
|
||||
fill: var(--zen-selected-color) !important;
|
||||
background-color: rgba(255, 255, 255, 0.9) !important;
|
||||
}
|
||||
|
||||
& .urlbarView-prettyName {
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 80%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -79,6 +79,11 @@ document.addEventListener(
|
||||
case 'cmd_zenSplitViewLinkInNewTab':
|
||||
gZenViewSplitter.splitLinkInNewTab();
|
||||
break;
|
||||
case 'cmd_zenNewEmptySplit':
|
||||
setTimeout(() => {
|
||||
gZenViewSplitter.createEmptySplit();
|
||||
}, 0);
|
||||
break;
|
||||
case 'cmd_zenReplacePinnedUrlWithCurrent':
|
||||
gZenPinnedTabManager.replacePinnedUrlWithCurrent();
|
||||
break;
|
||||
|
@@ -816,7 +816,7 @@ class nsZenKeyboardShortcutsLoader {
|
||||
}
|
||||
|
||||
class nsZenKeyboardShortcutsVersioner {
|
||||
static LATEST_KBS_VERSION = 10;
|
||||
static LATEST_KBS_VERSION = 11;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1362,6 +1377,9 @@ var gZenKeyboardShortcutsManager = {
|
||||
* @returns {string|null} The shortcut as a string or null if not found
|
||||
*/
|
||||
getShortcutDisplayFromCommand(command) {
|
||||
if (!command) {
|
||||
return null;
|
||||
}
|
||||
const shortcut = this.getShortcutFromCommand(command);
|
||||
if (shortcut) {
|
||||
return shortcut.toUserString();
|
||||
|
@@ -1008,6 +1008,15 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
tab.linkedBrowser.docShellIsActive = true;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
|
@@ -14,3 +14,4 @@ support-files = [
|
||||
["browser_split_browser_duplication.js"]
|
||||
["browser_split_view_with_glance.js"]
|
||||
["browser_split_view_with_folders.js"]
|
||||
["browser_split_view_empty.js"]
|
||||
|
49
src/zen/tests/split_view/browser_split_view_empty.js
Normal file
49
src/zen/tests/split_view/browser_split_view_empty.js
Normal 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);
|
||||
});
|
||||
});
|
@@ -21,6 +21,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
UrlbarTokenizer: 'resource:///modules/UrlbarTokenizer.sys.mjs',
|
||||
QueryScorer: 'resource:///modules/UrlbarProviderInterventions.sys.mjs',
|
||||
BrowserWindowTracker: 'resource:///modules/BrowserWindowTracker.sys.mjs',
|
||||
AddonManager: 'resource://gre/modules/AddonManager.sys.mjs',
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
@@ -63,25 +64,81 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
|
||||
);
|
||||
}
|
||||
|
||||
#getWorkspaceActions(window) {
|
||||
if (window.gZenWorkspaces.privateWindowOrDisabled) {
|
||||
return [];
|
||||
}
|
||||
const workspaces = window.gZenWorkspaces._workspaceCache?.workspaces;
|
||||
if (!workspaces?.length) {
|
||||
return [];
|
||||
}
|
||||
let actions = [];
|
||||
const activeSpaceUUID = window.gZenWorkspaces.activeWorkspace;
|
||||
for (const workspace of workspaces) {
|
||||
if (workspace.uuid !== activeSpaceUUID) {
|
||||
const accentColor = window.gZenWorkspaces
|
||||
.workspaceElement(workspace.uuid)
|
||||
?.style.getPropertyValue('--zen-primary-color');
|
||||
actions.push({
|
||||
label: 'Focus on',
|
||||
extraPayload: {
|
||||
workspaceId: workspace.uuid,
|
||||
prettyName: workspace.name,
|
||||
prettyIcon: workspace.icon,
|
||||
accentColor,
|
||||
},
|
||||
icon: 'chrome://browser/skin/zen-icons/forward.svg',
|
||||
});
|
||||
}
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
async #getExtensionActions(window) {
|
||||
const addons = await lazy.AddonManager.getAddonsByTypes(['extension']);
|
||||
return addons
|
||||
.filter(
|
||||
(addon) =>
|
||||
addon.isActive &&
|
||||
!addon.isSystem &&
|
||||
window.gUnifiedExtensions.browserActionFor(window.WebExtensionPolicy.getByID(addon.id))
|
||||
)
|
||||
.map((addon) => {
|
||||
return {
|
||||
icon: 'chrome://browser/skin/zen-icons/extension.svg',
|
||||
label: 'Extension',
|
||||
extraPayload: {
|
||||
extensionId: addon.id,
|
||||
prettyName: addon.name,
|
||||
prettyIcon: addon.iconURL,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Window} window The window to check available actions for.
|
||||
* @returns All the available global actions.
|
||||
*/
|
||||
get #availableActions() {
|
||||
return globalActions.filter((a) =>
|
||||
typeof a.isAvailable === 'function' ? a.isAvailable() : true
|
||||
);
|
||||
async #getAvailableActions(window) {
|
||||
return globalActions
|
||||
.filter((a) => a.isAvailable(window))
|
||||
.concat(this.#getWorkspaceActions(window))
|
||||
.concat(await this.#getExtensionActions(window));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const actions = this.#availableActions;
|
||||
async #findMatchingActions(query) {
|
||||
const window = lazy.BrowserWindowTracker.getTopWindow();
|
||||
const actions = await this.#getAvailableActions(window);
|
||||
let results = [];
|
||||
for (let action of actions) {
|
||||
const label = action.label;
|
||||
const label = action.extraPayload?.prettyName || action.label;
|
||||
const score = this.#calculateFuzzyScore(label, query);
|
||||
if (score > MINIMUM_QUERY_SCORE) {
|
||||
results.push({
|
||||
@@ -155,7 +212,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
const actionsResults = this.#findMatchingActions(query);
|
||||
const actionsResults = await this.#findMatchingActions(query);
|
||||
if (!actionsResults.length) {
|
||||
return;
|
||||
}
|
||||
@@ -173,6 +230,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
|
||||
shortcutContent: ownerGlobal.gZenKeyboardShortcutsManager.getShortcutDisplayFromCommand(
|
||||
action.command
|
||||
),
|
||||
...action.extraPayload,
|
||||
});
|
||||
|
||||
let result = new lazy.UrlbarResult(
|
||||
@@ -206,6 +264,9 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
|
||||
* @returns {object} An object describing the view update.
|
||||
*/
|
||||
getViewUpdate(result) {
|
||||
const prettyIconIsSvg =
|
||||
result.payload.prettyIcon &&
|
||||
(result.payload.prettyIcon.endsWith('.svg') || result.payload.prettyIcon.endsWith('.png'));
|
||||
return {
|
||||
icon: {
|
||||
attributes: {
|
||||
@@ -219,6 +280,27 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
|
||||
shortcutContent: {
|
||||
textContent: result.payload.shortcutContent || '',
|
||||
},
|
||||
prettyName: {
|
||||
attributes: {
|
||||
hidden: !result.payload.prettyName,
|
||||
style: `--zen-primary-color: ${result.payload.accentColor || 'currentColor'}`,
|
||||
},
|
||||
},
|
||||
prettyNameStrong: {
|
||||
textContent: result.payload.prettyName
|
||||
? prettyIconIsSvg || !result.payload.prettyIcon
|
||||
? result.payload.prettyName
|
||||
: `${result.payload.prettyIcon} ${result.payload.prettyName}`
|
||||
: '',
|
||||
attributes: { dir: 'ltr' },
|
||||
},
|
||||
prettyNameIcon: {
|
||||
attributes: {
|
||||
src: result.payload.prettyIcon || '',
|
||||
hidden: !prettyIconIsSvg || !result.payload.prettyIcon,
|
||||
workspaceIcon: !!result.payload.workspaceId,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -244,6 +326,23 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tag: 'span',
|
||||
classList: ['urlbarView-prettyName'],
|
||||
hidden: true,
|
||||
name: 'prettyName',
|
||||
children: [
|
||||
{
|
||||
tag: 'img',
|
||||
name: 'prettyNameIcon',
|
||||
attributes: { hidden: true },
|
||||
},
|
||||
{
|
||||
name: 'prettyNameStrong',
|
||||
tag: 'strong',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'shortcutContent',
|
||||
tag: 'span',
|
||||
@@ -257,10 +356,28 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
|
||||
const result = details.result;
|
||||
const payload = result.payload;
|
||||
const command = payload.zenCommand;
|
||||
const ownerGlobal = details.element.ownerGlobal;
|
||||
if (typeof command === 'function') {
|
||||
command(ownerGlobal);
|
||||
return;
|
||||
}
|
||||
// Switch workspace if theres a workspaceId in the payload.
|
||||
if (payload.workspaceId) {
|
||||
ownerGlobal.gZenWorkspaces.changeWorkspaceWithID(payload.workspaceId);
|
||||
return;
|
||||
}
|
||||
if (payload.extensionId) {
|
||||
const action = ownerGlobal.gUnifiedExtensions.browserActionFor(
|
||||
ownerGlobal.WebExtensionPolicy.getByID(payload.extensionId)
|
||||
);
|
||||
if (action) {
|
||||
action.openPopup(ownerGlobal, /* without user interaction = */ true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
const ownerGlobal = details.element.ownerGlobal;
|
||||
const commandToRun = ownerGlobal.document.getElementById(command);
|
||||
if (commandToRun) {
|
||||
ownerGlobal.gBrowser.selectedBrowser.focus();
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* 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/. */
|
||||
|
||||
export const globalActions = [
|
||||
const globalActionsTemplate = [
|
||||
{
|
||||
label: 'Toggle Compact Mode',
|
||||
command: 'cmd_zenCompactModeToggle',
|
||||
@@ -15,4 +15,44 @@ export const globalActions = [
|
||||
icon: 'chrome://browser/skin/zen-icons/edit-theme.svg',
|
||||
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',
|
||||
},
|
||||
{
|
||||
label: 'Open New Window',
|
||||
command: 'cmd_newNavigator',
|
||||
icon: 'chrome://browser/skin/zen-icons/window.svg',
|
||||
},
|
||||
{
|
||||
label: 'Open Private Window',
|
||||
command: 'Tools:PrivateBrowsing',
|
||||
icon: 'chrome://browser/skin/zen-icons/private-window.svg',
|
||||
},
|
||||
];
|
||||
|
||||
export const globalActions = globalActionsTemplate.map((action) => ({
|
||||
isAvailable: (window) => {
|
||||
return window.document.getElementById(action.command)?.getAttribute('disabled') !== 'true';
|
||||
},
|
||||
extraPayload: {},
|
||||
...action,
|
||||
}));
|
||||
|
Reference in New Issue
Block a user