Compare commits

..

16 Commits

Author SHA1 Message Date
Michael Y. Qiu
11af62c94e fix: Keyboard shortcut assignment issue caused by modifier, b=closes https://github.com/zen-browser/desktop/issues/10686, p=#12776 2026-03-16 09:10:13 +01:00
mr. m
4f0e7daa13 feat: Dont show reset line if the location is empty, b=no-bug, c=tabs 2026-03-15 12:45:53 +01:00
mr. m
c6e8b0d3d9 feat: Improve accesibility for split view buttons, b=no-bug, c=split-view, tabs 2026-03-15 12:22:30 +01:00
mr. m
8987c48abe fix: Fixed opening new windows not taking into account the latest state, b=closes #12662, c=no-component 2026-03-15 11:50:37 +01:00
mr. m
492d34a9fa fix: Fixed turning browser.pagethumbnails.capturing_disabled on breaking WS, b=closes #12772, c=no-component 2026-03-14 20:02:55 +01:00
mr. m
42ac8e6094 feat: Make gradient attributes trigger less re-styles, b=bug #12765, c=common 2026-03-14 10:21:32 +01:00
Afeefur
7dbe5b414d feat: Added 'unload all other spaces' option in spaces context menu, p=#12751
Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
2026-03-14 10:14:45 +01:00
Lukas
55c079d4ba feat: Add "Separate from pinned tab" when resetting pinned tab with CMD, p=#12710 2026-03-14 10:06:09 +01:00
Dylan Robinson
a629866c28 fix: keep bookmarks bar in fullscreen, b=closes #8163, p=#12763
Co-authored-by: mr. m <mr.m@tuta.com>
2026-03-14 10:05:42 +01:00
mr. m
6287f1a118 chore: Refactor spaces organization, p=#12764 2026-03-13 17:35:00 +01:00
mr. m
420d4ec064 Merge branch 'dev' of https://github.com/zen-browser/desktop into dev 2026-03-12 13:00:10 +01:00
mr. m
908b164996 feat: Allow gradient picker to have less opacity for Windows, b=no-bug, c=workspaces 2026-03-12 12:47:20 +01:00
S31ZUR3
cfbf8edfa9 fix: Fix video fullscreen rendering when split view is active, b=closes https://github.com/zen-browser/desktop/issues/11559, p=#12733
* Fix video fullscreen rendering when split view is active

Deactivate other split browsers and hide their containers during fullscreen
so Firefox can promote the active browser correctly.

* Address review: simplify fullscreen handling using inDOMFullscreen

* Discard changes to src/zen/split-view/ZenViewSplitter.mjs

* Formatting Changes

* Remove unnecessary empty line in zen-decks.css

Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>

---------

Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
2026-03-11 22:18:00 +01:00
mr. m
6f9aa2472b Merge branch 'dev' of https://github.com/zen-browser/desktop into dev 2026-03-11 19:07:42 +01:00
mr. m
0ac56e9d04 fix: Fixed chrome hide toolbar flag not being respected, b=closes #12736, c=no-component 2026-03-11 19:03:28 +01:00
Thomas
544fd480b1 feat: close new tab popup on Cmd/Ctrl + T, p=#12734 2026-03-11 18:46:27 +01:00
45 changed files with 726 additions and 291 deletions

View File

@@ -46,5 +46,6 @@ tabbrowser-reset-pin-button =
zen-tab-sublabel =
{ $tabSubtitle ->
[zen-default-pinned] Back to pinned url
[zen-default-pinned-cmd] Separate from pinned tab
*[other] { $tabSubtitle }
}

View File

@@ -35,6 +35,9 @@ zen-workspaces-panel-context-default-profile =
zen-workspaces-panel-unload =
.label = Unload Space
zen-workspaces-panel-unload-others =
.label = Unload All Other Spaces
zen-workspaces-how-to-reorder-title = How to reorder spaces
zen-workspaces-how-to-reorder-desc = Drag the space icons at the bottom of the sidebar to reorder them

View File

@@ -1,5 +1,5 @@
diff --git a/.stylelintignore b/.stylelintignore
index 185490999507b8a5032977237af50f5e61c71df1..e887fafa90b881e852a287ed8898638c995861ab 100644
index 185490999507b8a5032977237af50f5e61c71df1..77c1635d90c6480af4ebd3c6e3ec49780eaca571 100644
--- a/.stylelintignore
+++ b/.stylelintignore
@@ -106,3 +106,19 @@ build/pgo/blueprint/**/*.css
@@ -17,8 +17,8 @@ index 185490999507b8a5032977237af50f5e61c71df1..e887fafa90b881e852a287ed8898638c
+zen/common/styles/zen-theme.css
+zen/compact-mode/zen-compact-mode.css
+
+zen/split-view/zen-decks.css
+zen/workspaces/zen-workspaces.css
+zen/split-view/zen-split-view.css
+zen/spaces/zen-workspaces.css
+zen/common/styles/zen-toolbar.css
+
+*.inc

View File

@@ -1,5 +1,5 @@
diff --git a/browser/base/content/browser-box.inc.xhtml b/browser/base/content/browser-box.inc.xhtml
index 2faed30e09511c381051bc40910a883d1d7bc10d..3b8c89902502aa384473dd6f43be7ec49bad06ac 100644
index 2faed30e09511c381051bc40910a883d1d7bc10d..959fa83f647a8919641c5b852a4cb8814fca9ab5 100644
--- a/browser/base/content/browser-box.inc.xhtml
+++ b/browser/base/content/browser-box.inc.xhtml
@@ -3,6 +3,9 @@
@@ -17,7 +17,7 @@ index 2faed30e09511c381051bc40910a883d1d7bc10d..3b8c89902502aa384473dd6f43be7ec4
</vbox>
<splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" resizebefore="sibling" resizeafter="none" hidden="true"/>
+<vbox flex="1" id="zen-appcontent-wrapper">
+ <html:div id="zen-appcontent-navbar-wrapper">
+ <html:div id="zen-appcontent-navbar-wrapper" class="chromeclass-location">
+ <html:div id="zen-appcontent-navbar-container"></html:div>
+ </html:div>
+ <hbox id="zen-tabbox-wrapper" flex="1">

View File

@@ -1,5 +1,5 @@
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index e2e0526a0ddd617291f1f6c17bcfb807954b481f..c3d2afff6eaa788309d1c1a7fa40f9b8b4f0fffe 100644
index e2e0526a0ddd617291f1f6c17bcfb807954b481f..1373fe072b3c74a52413859d4ad3612cbe1a2bda 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -33,6 +33,7 @@ ChromeUtils.defineESModuleGetters(this, {
@@ -28,7 +28,7 @@ index e2e0526a0ddd617291f1f6c17bcfb807954b481f..c3d2afff6eaa788309d1c1a7fa40f9b8
AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
TranslationsParent.onLocationChange(gBrowser.selectedBrowser);
+ gZenPinnedTabManager.onLocationChange(gBrowser.selectedBrowser);
+ gZenPinnedTabManager.onLocationChange(gBrowser.selectedBrowser, location);
+
PictureInPicture.updateUrlbarToggle(gBrowser.selectedBrowser);

View File

@@ -17,7 +17,7 @@
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-browser-container.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-omnibox.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-workspaces.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-decks.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-split-view.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-folders.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-glance.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-popup.css" />
@@ -42,7 +42,7 @@
# Scripts used all over the browser
<script type="module" src="chrome://browser/content/zen-components/ZenMediaController.mjs"></script>
<script type="module" src="chrome://browser/content/zen-components/ZenWorkspaceCreation.mjs"></script>
<script type="module" src="resource:///modules/zen/ZenSpaceCreation.mjs"></script>
<script type="module" src="chrome://browser/content/zen-components/ZenGlanceManager.mjs"></script>
<script type="module" src="chrome://browser/content/zen-components/ZenPinnedTabManager.mjs"></script>
<script type="module" src="chrome://browser/content/zen-components/ZenViewSplitter.mjs"></script>

View File

@@ -7,7 +7,7 @@
#include ../../../zen/drag-and-drop/jar.inc.mn
#include ../../../zen/split-view/jar.inc.mn
#include ../../../zen/mods/jar.inc.mn
#include ../../../zen/workspaces/jar.inc.mn
#include ../../../zen/spaces/jar.inc.mn
#include ../../../zen/tabs/jar.inc.mn
#include ../../../zen/kbs/jar.inc.mn
#include ../../../zen/glance/jar.inc.mn

View File

@@ -40,6 +40,7 @@
<command id="cmd_zenCtxDeleteWorkspace" />
<command id="cmd_zenUnloadWorkspace" />
<command id="cmd_zenUnloadAllOtherWorkspace" />
<command id="cmd_zenChangeWorkspaceName" />
<command id="cmd_zenChangeWorkspaceIcon" />
<command id="cmd_zenReorderWorkspaces" />

View File

@@ -42,10 +42,11 @@
hide-if-usercontext-disabled="true">
<menupopup />
</menu>
<menuitem id="context_zenUnloadWorkspace" data-l10n-id="zen-workspaces-panel-unload" command="cmd_zenUnloadWorkspace"/>
<menuseparator id="context_zenWorkspacesSeparator"/>
<menuseparator/>
<menuitem id="context_zenReorderWorkspaces" data-l10n-id="zen-workspaces-panel-context-reorder" command="cmd_zenReorderWorkspaces"/>
<menuitem id="context_zenUnloadWorkspace" data-l10n-id="zen-workspaces-panel-unload" command="cmd_zenUnloadWorkspace"/>
<menuitem id="context_zenUnloadAllOtherWorkspace" data-l10n-id="zen-workspaces-panel-unload-others" command="cmd_zenUnloadAllOtherWorkspace"/>
<menuseparator/>
<menuitem data-l10n-id="zen-panel-ui-workspaces-create" command="cmd_zenOpenWorkspaceCreation"/>
<menuitem id="context_zenDeleteWorkspace" data-l10n-id="zen-workspaces-panel-context-delete" command="cmd_zenCtxDeleteWorkspace"/>

View File

@@ -89,7 +89,7 @@
# They must all go from the middle to the right side. They must always stay verically centered.
# And reach to 180 on the right side, meaning we must divide the width in 16 segments.
<box data-type="explicit-black-white" data-algo="float" data-num-dots="1"
data-position="337.5,180" style="background: rgb(224, 224, 224);"></box>
data-position="340,180" style="background: rgb(224, 224, 224);"></box>
<box data-type="explicit-black-white" data-algo="float" data-num-dots="1"
data-position="337.5,180" style="background: rgb(224, 224, 224);"></box>
<box data-type="explicit-black-white" data-algo="float" data-num-dots="1"
@@ -131,11 +131,11 @@
</box>
<html:input type="range" value="0.4" step="0.001" id="PanelUI-zen-gradient-generator-opacity"
#ifdef XP_MACOSX
max="0.75"
min="0.35"
max="0.9"
min="0.30"
#else
max="0.9"
min="0.35"
min="0.25"
#endif
/>
</vbox>

View File

@@ -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/.
#include zen-panels/gradient-generator.inc
#include zen-panels/theme-picker.inc
#include zen-panels/emojis-picker.inc
#include zen-panels/folders-search.inc
#include zen-panels/site-data.inc

View File

@@ -8,6 +8,6 @@
<script type="text/javascript" src="chrome://browser/content/zen-sets.js"></script>
<script type="module" src="chrome://browser/content/zen-components/ZenHasPolyfill.mjs"></script>
<script type="module" src="chrome://browser/content/zen-components/ZenWorkspaces.mjs"></script>
<script type="module" src="chrome://browser/content/zen-components/ZenWorkspace.mjs"></script>
<script type="module" src="chrome://browser/content/zen-components/ZenWorkspaceIcons.mjs"></script>
<script type="module" src="resource:///modules/zen/ZenSpaceManager.mjs"></script>
<script type="module" src="resource:///modules/zen/ZenSpace.mjs"></script>
<script type="module" src="resource:///modules/zen/ZenSpaceIcons.mjs"></script>

View File

@@ -15,7 +15,7 @@ index 4aad4e4fb4139aa3d81e00eefa82e26b697df973..831e42a4a55e277b5b8e81e4317a2007
src="chrome://browser/locale/places/bookmarkProperties.properties"/>
</stringbundleset>
+ <script src="chrome://browser/content/zen-components/ZenWorkspaceBookmarksStorage.js" />
+ <script src="chrome://browser/content/zen-components/ZenSpaceBookmarksStorage.js" />
+ <script src="chrome://browser/content/zenThemeModifier.js"></script>
<script src="chrome://browser/content/places/editBookmark.js"/>
<script src="chrome://browser/content/places/bookmarkProperties.js"/>

View File

@@ -1020,8 +1020,24 @@ var gZenCKSSettings = {
let shortcut;
if (event.code && event.code.startsWith("Key")) {
shortcut = event.code.slice(3);
} else if (event.code && event.code.startsWith("Digit")) {
shortcut = event.code.slice(5);
} else {
shortcut = event.key;
// Use physical key mapping for common symbols
const CODE_TO_KEY_MAP = {
Comma: ",",
Period: ".",
Slash: "/",
Semicolon: ";",
Quote: "'",
BracketLeft: "[",
BracketRight: "]",
Backslash: "\\",
Backquote: "`",
Minus: "-",
Equal: "=",
};
shortcut = CODE_TO_KEY_MAP[event.code] || event.key;
}
shortcut = shortcut.replace(/Ctrl|Control|Shift|Alt|Option|Cmd|Meta/, ""); // Remove all modifiers

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tab.js b/browser/components/tabbrowser/content/tab.js
index 836bee14d2b63604688ebe477a5d915a5e99b305..7e105a1ae07657b0a0e664a8e3d9d2eb894fa1d4 100644
index 836bee14d2b63604688ebe477a5d915a5e99b305..5f60aa3bedd4f80b887ea3e050fd86a21a6b280a 100644
--- a/browser/components/tabbrowser/content/tab.js
+++ b/browser/components/tabbrowser/content/tab.js
@@ -21,6 +21,7 @@
@@ -110,7 +110,26 @@ index 836bee14d2b63604688ebe477a5d915a5e99b305..7e105a1ae07657b0a0e664a8e3d9d2eb
}
get splitview() {
@@ -489,6 +515,8 @@
@@ -444,6 +470,10 @@
: this;
gBrowser.warmupTab(tabToWarm);
+ if (event.target.classList.contains("tab-reset-pin-button")) {
+ gZenPinnedTabManager.onResetPinButtonMouseOver(this, event);
+ }
+
// If the previous target wasn't part of this tab then this is a mouseenter event.
if (!this.contains(event.relatedTarget)) {
this._mouseenter();
@@ -455,6 +485,7 @@
if (!this.contains(event.relatedTarget)) {
this._mouseleave();
}
+ gZenPinnedTabManager.onResetPinButtonMouseOut(this);
}
on_dragstart(event) {
@@ -489,6 +520,8 @@
this.style.MozUserFocus = "ignore";
} else if (
event.target.classList.contains("tab-close-button") ||
@@ -119,7 +138,7 @@ index 836bee14d2b63604688ebe477a5d915a5e99b305..7e105a1ae07657b0a0e664a8e3d9d2eb
event.target.classList.contains("tab-icon-overlay") ||
event.target.classList.contains("tab-audio-button")
) {
@@ -543,6 +571,10 @@
@@ -543,16 +576,21 @@
this.style.MozUserFocus = "";
}
@@ -130,7 +149,40 @@ index 836bee14d2b63604688ebe477a5d915a5e99b305..7e105a1ae07657b0a0e664a8e3d9d2eb
on_click(event) {
if (event.button != 0) {
return;
@@ -603,6 +635,14 @@
}
- if (event.getModifierState("Accel") || event.shiftKey) {
+ if (event.shiftKey) {
return;
}
if (
+ !event.getModifierState("Accel") &&
gBrowser.multiSelectedTabsCount > 0 &&
!event.target.classList.contains("tab-close-button") &&
!event.target.classList.contains("tab-icon-overlay") &&
@@ -564,8 +602,9 @@
}
if (
- event.target.classList.contains("tab-icon-overlay") ||
- event.target.classList.contains("tab-audio-button")
+ !event.getModifierState("Accel") &&
+ (event.target.classList.contains("tab-icon-overlay") ||
+ event.target.classList.contains("tab-audio-button"))
) {
if (this.activeMediaBlocked) {
if (this.multiselected) {
@@ -583,7 +622,7 @@
return;
}
- if (event.target.classList.contains("tab-close-button")) {
+ if (!event.getModifierState("Accel") && event.target.classList.contains("tab-close-button")) {
if (this.multiselected) {
gBrowser.removeMultiSelectedTabs(
lazy.TabMetrics.userTriggeredContext(
@@ -603,6 +642,14 @@
// (see tabbrowser-tabs 'click' handler).
gBrowser.tabContainer._blockDblClick = true;
}
@@ -138,14 +190,14 @@ index 836bee14d2b63604688ebe477a5d915a5e99b305..7e105a1ae07657b0a0e664a8e3d9d2eb
+ if (event.target.classList.contains("tab-reset-pin-button")) {
+ gZenPinnedTabManager._onTabResetPinButton(event, this, 'reset');
+ gBrowser.tabContainer._blockDblClick = true;
+ } else if (event.target.classList.contains("tab-reset-button")) {
+ } else if (!event.getModifierState("Accel") && event.target.classList.contains("tab-reset-button")) {
+ gZenPinnedTabManager.onCloseTabShortcut(event, this);
+ gBrowser.tabContainer._blockDblClick = true;
+ }
}
on_dblclick(event) {
@@ -626,6 +666,8 @@
@@ -626,6 +673,8 @@
animate: true,
triggeringEvent: event,
});

View File

@@ -1,5 +1,5 @@
diff --git a/toolkit/content/widgets/arrowscrollbox.js b/toolkit/content/widgets/arrowscrollbox.js
index b80d1049bb6ae305f2ac9c4c35fe975fd508031c..9bc80194d4e35f663b5c15baae55fa48eed4ffe9 100644
index b80d1049bb6ae305f2ac9c4c35fe975fd508031c..be2cbdb20cb2064459b6f7bef56fd0470c3b7f40 100644
--- a/toolkit/content/widgets/arrowscrollbox.js
+++ b/toolkit/content/widgets/arrowscrollbox.js
@@ -98,6 +98,7 @@
@@ -15,7 +15,7 @@ index b80d1049bb6ae305f2ac9c4c35fe975fd508031c..9bc80194d4e35f663b5c15baae55fa48
on_wheel(event) {
// Don't consume the event if we can't scroll.
- if (!this.overflowing) {
+ if (!this.overflowing || this.id === 'tabbrowser-arrowscrollbox' || ((event.deltaY == 0 || window.gZenWorkspaces?._swipeState?.isGestureActive) && this.classList.contains('workspace-arrowscrollbox'))) {
+ if (!this.overflowing || this.id === 'tabbrowser-arrowscrollbox' || ((event.deltaY == 0 || window.gZenWorkspaces?._swipeManager?.isGestureActive) && this.classList.contains('workspace-arrowscrollbox'))) {
return;
}

View File

@@ -5,7 +5,7 @@
// prettier-ignore
// eslint-disable-next-line no-lone-blocks
{
Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/ZenWorkspaceBookmarksStorage.js", this);
Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/ZenSpaceBookmarksStorage.js", this);
ChromeUtils.importESModule("chrome://browser/content/ZenStartup.mjs", { global: "current" });
ChromeUtils.importESModule("chrome://browser/content/zen-components/ZenCompactMode.mjs", { global: "current" });

View File

@@ -76,6 +76,10 @@ window.gZenUIManager = {
this._initBookmarkCollapseListener();
gURLBar._setPlaceholder(null);
document
.getElementById("PersonalToolbar")
.setAttribute("fullscreentoolbar", "true");
},
/**
@@ -500,6 +504,12 @@ window.gZenUIManager = {
return false;
}
// Close the new tab popup on cmd/ctrl + t
if (!overridePreferance && gURLBar.hasAttribute("zen-newtab")) {
this.handleUrlbarClose();
return true;
}
// Clear any existing timeout
if (this._clearTimeout) {
clearTimeout(this._clearTimeout);

View File

@@ -88,7 +88,7 @@
display: none;
}
:root[zen-show-grainy-background="true"] & .zen-browser-grain {
&[zen-show-grainy-background="true"] .zen-browser-grain {
display: flex;
width: 100%;
height: 100%;

View File

@@ -129,6 +129,10 @@ document.addEventListener(
gZenWorkspaces.unloadWorkspace();
break;
}
case "cmd_zenUnloadAllOtherWorkspace": {
gZenWorkspaces.unloadAllOtherWorkspaces();
break;
}
case "cmd_zenNewNavigatorUnsynced":
OpenBrowserWindow({ zenSyncedWindow: false });
break;

View File

@@ -828,7 +828,7 @@
#createFakeTabSplit(dropElement, dropSide) {
// Remove drop indicator
this.clearDragOverVisuals();
this.clearDragOverVisuals({ clearSplitDropIndicator: false });
// Remove any existing fake tab
if (this.#dragOverSplit.fakeTab) {
@@ -1191,8 +1191,11 @@
}
}
clearDragOverVisuals() {
clearDragOverVisuals({ clearSplitDropIndicator = true } = {}) {
this.#removeDragOverBackground();
if (clearSplitDropIndicator) {
this._clearDragOverSplit();
}
gZenPinnedTabManager.removeTabContainersDragoverClass();
}
@@ -1217,6 +1220,10 @@
return true;
}
_moveTogetherSelectedTabs() {
// Override the default behavior of only moving together selected tabs.
}
// eslint-disable-next-line complexity
#applyDragoverIndicator(event, dropElement, movingTabs, draggedTab) {
// Doesn't show indicator when dragOverSplit

View File

@@ -17,4 +17,5 @@ DIRS += [
"toolkit",
"sessionstore",
"share",
"spaces",
]

View File

@@ -710,18 +710,35 @@ export class nsZenSessionManager {
}
/**
* Filters out tabs that are not useful to restore, such as empty tabs with no group association.
* If removeUnpinnedTabs is true, it also filters out unpinned tabs.
* Determines whether a tab should be collected based on its data.
*
* @param {Array} tabs - The array of tab data objects to filter.
* @returns {Array} The filtered array of tab data objects.
* @param {object} tabData - The tab data object to evaluate.
* @returns {boolean} True if the tab should be collected, false otherwise.
*/
#filterUnusedTabs(tabs) {
return tabs.filter(tab => {
// We need to ignore empty tabs with no group association
// as they are not useful to restore.
return !(tab.zenIsEmpty && !tab.groupId);
});
#shouldCollectTab(tabData) {
return tabData && !(tabData.zenIsEmpty && !tabData.groupId);
}
#collectUsedTabsFromWindows(aStateWindows) {
const tabIdRelationMap = new Map();
for (const window of aStateWindows) {
// Only accept the tabs with `_zenIsActiveTab` set to true from
// every window. We do this to avoid collecting tabs with invalid
// state when multiple windows are open. Note that if we a tab without
// this flag set in any other window, we just add it anyway.
for (const tabData of window.tabs || []) {
if (!this.#shouldCollectTab(tabData)) {
continue;
}
if (
!tabIdRelationMap.has(tabData.zenSyncId) ||
tabData._zenIsActiveTab
) {
tabIdRelationMap.set(tabData.zenSyncId, tabData);
}
}
}
return Array.from(tabIdRelationMap.values());
}
/**
@@ -731,25 +748,7 @@ export class nsZenSessionManager {
* @param {object} aStateWindows The array of window state objects.
*/
#collectTabsData(sidebarData, aStateWindows) {
const tabIdRelationMap = new Map();
for (const window of aStateWindows) {
// Only accept the tabs with `_zenIsActiveTab` set to true from
// every window. We do this to avoid collecting tabs with invalid
// state when multiple windows are open. Note that if we a tab without
// this flag set in any other window, we just add it anyway.
for (const tabData of window.tabs || []) {
if (
!tabIdRelationMap.has(tabData.zenSyncId) ||
tabData._zenIsActiveTab
) {
tabIdRelationMap.set(tabData.zenSyncId, tabData);
}
}
}
sidebarData.tabs = this.#filterUnusedTabs(
Array.from(tabIdRelationMap.values())
);
sidebarData.tabs = this.#collectUsedTabsFromWindows(aStateWindows);
let firstWindow = aStateWindows[0];
sidebarData.folders = firstWindow.folders;
@@ -826,19 +825,31 @@ export class nsZenSessionManager {
return;
}
this.log("Restoring new window with Zen session data");
const state = lazy.SessionStore.getCurrentState(true);
const windows = (state.windows || []).filter(
win =>
!win.isPrivate &&
!win.isPopup &&
!win.isTaskbarTab &&
!win.isZenUnsynced
);
void lazy.SessionStore.getCurrentState(true);
// We want to iterate all windows except from aWindow.__SSi (string).
// SessionStoreInternal._windows is an object, with the ID as key and the
// window data as value, so we need to filter out the values that have the
// same ID as aWindow.__SSi. but lets filter it into an array to make it easier to work with.
let windows = [];
for (const winKey of Object.keys(SessionStoreInternal._windows)) {
const winData = SessionStoreInternal._windows[winKey];
if (
winData &&
winKey !== aWindow.__SSi &&
!winData.isPrivate &&
!winData.isPopup &&
!winData.isTaskbarTab &&
!winData.isZenUnsynced
) {
windows.push(winData);
}
}
let windowToClone = windows[0] || {};
let newWindow = Cu.cloneInto(windowToClone, {});
newWindow.tabs = this.#collectUsedTabsFromWindows(windows);
let shouldRestoreOnlyPinned =
!lazy.gWindowSyncEnabled || lazy.gSyncOnlyPinnedTabs;
if (windows.length < 2) {
if (windows.length < 1) {
// We only want to restore the sidebar object if we found
// only one normal window to clone from (which is the one
// we are opening).
@@ -846,7 +857,6 @@ export class nsZenSessionManager {
this.#restoreWindowData(newWindow);
shouldRestoreOnlyPinned ||= this.#shouldRestoreOnlyPinned;
}
newWindow.tabs = this.#filterUnusedTabs(newWindow.tabs || []);
if (shouldRestoreOnlyPinned) {
// Don't bring over any unpinned tabs if window sync is disabled or if syncing only pinned tabs.
this.#filterUnpinnedTabs(newWindow);

View File

@@ -909,15 +909,19 @@ class nsZenWindowSync {
}
);
let mySrc = await new Promise((r, re) => {
let mySrc = await new Promise(r => {
const reader = new FileReader();
if (!browserBlob) {
r("");
return;
}
reader.readAsDataURL(browserBlob);
reader.onloadend = function () {
// result includes identifier 'data:image/png;base64,' plus the base64 data
r(reader.result);
};
reader.onerror = function () {
re(new Error("Failed to read blob as data URL"));
r("");
};
});

View File

@@ -1257,8 +1257,8 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
let colorToBlend;
let colorToBlendOpacity;
if (this.isMica) {
colorToBlend = !this.isDarkMode ? [0, 0, 0] : [255, 255, 255];
colorToBlendOpacity = 0.35;
colorToBlend = this.isDarkMode ? [0, 0, 0] : [255, 255, 255];
colorToBlendOpacity = 0.25;
} else if (AppConstants.platform === "macosx") {
colorToBlend = [255, 255, 255];
colorToBlendOpacity = 0.35;
@@ -1271,7 +1271,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
colorToBlendOpacity * (1 - (opacity + lazy.MIN_OPACITY))
);
baseColor = this.blendColors(baseColor, colorToBlend, blendedAlpha * 100);
if (AppConstants.platform !== "macosx") {
if (!this.canBeTransparent) {
opacity += colorToBlendOpacity * (1 - opacity);
}
}
@@ -1438,13 +1438,14 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
}
updateNoise(texture) {
document.documentElement.style.setProperty(
"--zen-grainy-background-opacity",
texture
);
document.documentElement.setAttribute(
"zen-show-grainy-background",
texture > 0 ? "true" : "false"
[lazy.browserBackgroundElement, lazy.toolbarBackgroundElement].forEach(
element => {
element.style.setProperty("--zen-grainy-background-opacity", texture);
element.setAttribute(
"zen-show-grainy-background",
texture > 0 ? "true" : "false"
);
}
);
}

View File

@@ -4,7 +4,8 @@
/* eslint-disable no-shadow */
import { nsZenThemePicker } from "chrome://browser/content/zen-components/ZenGradientGenerator.mjs";
import { nsZenThemePicker } from "resource:///modules/zen/ZenGradientGenerator.mjs";
import { ZenSpacesSwipe } from "resource:///modules/zen/ZenSpacesSwipe.mjs";
const lazy = {};
@@ -40,12 +41,6 @@ class nsZenWorkspaces {
#canDebug = Services.prefs.getBoolPref("zen.workspaces.debug", false);
#activeWorkspace = "";
_swipeState = {
isGestureActive: true,
lastDelta: 0,
direction: null,
};
_workspaceCache = [];
#lastScrollTime = 0;
@@ -194,7 +189,7 @@ class nsZenWorkspaces {
this.workspaceEnabled &&
!this.isPrivateWindow
) {
this.initializeGestureHandlers();
this._swipeManager = new ZenSpacesSwipe();
this.initializeWorkspaceNavigation();
}
}
@@ -647,73 +642,9 @@ class nsZenWorkspaces {
);
}
initializeGestureHandlers() {
const elements = [
gNavToolbox,
// event handlers do not work on elements inside shadow DOM so we need to attach them directly
document
.getElementById("tabbrowser-arrowscrollbox")
.shadowRoot.querySelector("scrollbox"),
];
// Attach gesture handlers to each element
for (const element of elements) {
if (!element) {
continue;
}
this.attachGestureHandlers(element);
}
}
attachGestureHandlers(element) {
element.addEventListener(
"MozSwipeGestureMayStart",
this._handleSwipeMayStart.bind(this),
true
);
element.addEventListener(
"MozSwipeGestureStart",
this._handleSwipeStart.bind(this),
true
);
element.addEventListener(
"MozSwipeGestureUpdate",
this._handleSwipeUpdate.bind(this),
true
);
// Use MozSwipeGesture instead of MozSwipeGestureEnd because MozSwipeGestureEnd is fired after animation ends,
// while MozSwipeGesture is fired immediately after swipe ends.
element.addEventListener(
"MozSwipeGesture",
this._handleSwipeEnd.bind(this),
true
);
element.addEventListener(
"MozSwipeGestureEnd",
() => {
Services.prefs.setBoolPref("zen.swipe.is-fast-swipe", false);
document.documentElement.removeAttribute("swipe-gesture");
gZenUIManager.tabsWrapper.style.removeProperty("scrollbar-width");
[lazy.browserBackgroundElement, lazy.toolbarBackgroundElement].forEach(
element => {
element.style.setProperty("--zen-background-opacity", "1");
}
);
delete this._hasAnimatedBackgrounds;
this.updateTabsContainers();
document.removeEventListener("popupshown", this.popupOpenHandler, {
once: true,
});
},
true
);
}
_popupOpenHandler() {
// If a popup is opened, we should stop the swipe gesture
if (this._swipeState?.isGestureActive) {
if (this._swipeManager?.isGestureActive) {
document.documentElement.removeAttribute("swipe-gesture");
gZenUIManager.tabsWrapper.style.removeProperty("scrollbar-width");
this.updateTabsContainers();
@@ -721,113 +652,6 @@ class nsZenWorkspaces {
}
}
_handleSwipeMayStart(event) {
if (this.privateWindowOrDisabled || this.#inChangingWorkspace) {
return;
}
if (
event.target.closest("#zen-sidebar-foot-buttons") ||
event.target.closest('#urlbar[zen-floating-urlbar="true"]')
) {
return;
}
// Only handle horizontal swipes
if (
event.direction === event.DIRECTION_LEFT ||
event.direction === event.DIRECTION_RIGHT
) {
event.preventDefault();
event.stopPropagation();
// Set allowed directions based on available workspaces
event.allowedDirections |= event.DIRECTION_LEFT | event.DIRECTION_RIGHT;
}
}
_handleSwipeStart(event) {
if (!this.workspaceEnabled) {
return;
}
gZenFolders.cancelPopupTimer();
document.documentElement.setAttribute("swipe-gesture", "true");
document.addEventListener("popupshown", this.popupOpenHandler, {
once: true,
});
event.preventDefault();
event.stopPropagation();
this._swipeState = {
isGestureActive: true,
lastDelta: 0,
direction: null,
};
Services.prefs.setBoolPref("zen.swipe.is-fast-swipe", true);
}
_handleSwipeUpdate(event) {
if (!this.workspaceEnabled || !this._swipeState?.isGestureActive) {
return;
}
event.preventDefault();
event.stopPropagation();
const delta = event.delta * 300;
const stripWidth =
window.windowUtils.getBoundsWithoutFlushing(
document.getElementById("navigator-toolbox")
).width +
window.windowUtils.getBoundsWithoutFlushing(
document.getElementById("zen-sidebar-splitter")
).width *
2;
let translateX = this._swipeState.lastDelta + delta;
// Add a force multiplier as we are translating the strip depending on how close to the edge we are
let forceMultiplier = Math.min(
1,
1 - Math.abs(translateX) / (stripWidth * 4.5)
); // 4.5 instead of 4 to add a bit of a buffer
if (forceMultiplier > 0.5) {
translateX *= forceMultiplier;
this._swipeState.lastDelta = delta + (translateX - delta) * 0.5;
} else {
translateX = this._swipeState.lastDelta;
}
if (Math.abs(delta) > 0.8) {
this._swipeState.direction = delta > 0 ? "left" : "right";
}
// Apply a translateX to the tab strip to give the user feedback on the swipe
const currentWorkspace = this.getActiveWorkspaceFromCache();
this._organizeWorkspaceStripLocations(currentWorkspace, true, translateX);
}
async _handleSwipeEnd(event) {
if (!this.workspaceEnabled) {
return;
}
event.preventDefault();
event.stopPropagation();
const isRTL = document.documentElement.matches(":-moz-locale-dir(rtl)");
const moveForward =
(event.direction === SimpleGestureEvent.DIRECTION_RIGHT) !== isRTL;
const rawDirection = moveForward ? 1 : -1;
const direction = this.naturalScroll ? -1 : 1;
await this.changeWorkspaceShortcut(rawDirection * direction, true);
// Reset swipe state
this._swipeState = {
isGestureActive: false,
lastDelta: 0,
direction: null,
};
}
get activeWorkspace() {
return this.#activeWorkspace;
}
@@ -847,6 +671,10 @@ class nsZenWorkspaces {
Services.prefs.setStringPref("zen.workspaces.active", value);
}
get isChangingWorkspace() {
return this.#inChangingWorkspace;
}
get shouldHaveWorkspaces() {
if (typeof this._shouldHaveWorkspaces === "undefined") {
let chromeFlags = window.docShell.treeOwner
@@ -1665,6 +1493,21 @@ class nsZenWorkspaces {
await gBrowser.explicitUnloadTabs(tabsToUnload); // TODO: unit test this
}
async unloadAllOtherWorkspaces() {
const workspaceId =
this.#contextMenuData?.workspaceId || this.activeWorkspace;
const tabsToUnload = this.allStoredTabs.filter(
tab =>
tab.getAttribute("zen-workspace-id") !== workspaceId &&
!tab.hasAttribute("zen-empty-tab") &&
!tab.hasAttribute("zen-essential") &&
!tab.hasAttribute("pending")
);
await gBrowser.explicitUnloadTabs(tabsToUnload); // TODO: unit test this
}
moveTabToWorkspace(tab, workspaceID) {
return this.moveTabsToWorkspace([tab], workspaceID);
}

View File

@@ -0,0 +1,210 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "browserBackgroundElement", () => {
return document.getElementById("zen-browser-background");
});
ChromeUtils.defineLazyGetter(lazy, "toolbarBackgroundElement", () => {
return document.getElementById("zen-toolbar-background");
});
export class ZenSpacesSwipe {
_swipeState = {
isGestureActive: false,
lastDelta: 0,
direction: null,
};
constructor() {
const elements = [
gNavToolbox,
// Event handlers do not work on elements inside shadow DOM
// so we need to attach them directly.
document
.getElementById("tabbrowser-arrowscrollbox")
?.shadowRoot?.querySelector("scrollbox"),
];
for (const element of elements) {
if (!element) {
continue;
}
this._attachWorkspaceSwipeGestures(element);
}
}
_attachWorkspaceSwipeGestures(element) {
element.addEventListener(
"MozSwipeGestureMayStart",
this._handleSwipeMayStart.bind(this),
true
);
element.addEventListener(
"MozSwipeGestureStart",
this._handleSwipeStart.bind(this),
true
);
element.addEventListener(
"MozSwipeGestureUpdate",
this._handleSwipeUpdate.bind(this),
true
);
// Use MozSwipeGesture instead of MozSwipeGestureEnd because MozSwipeGestureEnd is fired after animation ends,
// while MozSwipeGesture is fired immediately after swipe ends.
element.addEventListener(
"MozSwipeGesture",
this._handleSwipeEnd.bind(this),
true
);
element.addEventListener(
"MozSwipeGestureEnd",
() => {
this.onSwipeGestureAnimationEnd();
},
true
);
}
_handleSwipeMayStart(event) {
const ws = gZenWorkspaces;
if (ws.privateWindowOrDisabled || ws.isChangingWorkspace) {
return;
}
if (
event.target.closest("#zen-sidebar-foot-buttons") ||
event.target.closest('#urlbar[zen-floating-urlbar="true"]')
) {
return;
}
// Only handle horizontal swipes
if (
event.direction === event.DIRECTION_LEFT ||
event.direction === event.DIRECTION_RIGHT
) {
event.preventDefault();
event.stopPropagation();
// Set allowed directions based on available workspaces
event.allowedDirections |= event.DIRECTION_LEFT | event.DIRECTION_RIGHT;
}
}
_handleSwipeStart(event) {
const ws = gZenWorkspaces;
if (!ws.workspaceEnabled) {
return;
}
gZenFolders.cancelPopupTimer();
document.documentElement.setAttribute("swipe-gesture", "true");
document.addEventListener("popupshown", ws.popupOpenHandler, {
once: true,
});
event.preventDefault();
event.stopPropagation();
this._swipeState = {
isGestureActive: true,
lastDelta: 0,
direction: null,
};
Services.prefs.setBoolPref("zen.swipe.is-fast-swipe", true);
}
_handleSwipeUpdate(event) {
const ws = gZenWorkspaces;
if (!ws.workspaceEnabled || !this._swipeState?.isGestureActive) {
return;
}
event.preventDefault();
event.stopPropagation();
const delta = event.delta * 300;
const stripWidth =
window.windowUtils.getBoundsWithoutFlushing(
document.getElementById("navigator-toolbox")
).width +
window.windowUtils.getBoundsWithoutFlushing(
document.getElementById("zen-sidebar-splitter")
).width *
2;
let translateX = this._swipeState.lastDelta + delta;
// Add a force multiplier as we are translating the strip depending on how close to the edge we are
let forceMultiplier = Math.min(
1,
1 - Math.abs(translateX) / (stripWidth * 4.5)
); // 4.5 instead of 4 to add a bit of a buffer
if (forceMultiplier > 0.5) {
translateX *= forceMultiplier;
this._swipeState.lastDelta = delta + (translateX - delta) * 0.5;
} else {
translateX = this._swipeState.lastDelta;
}
if (Math.abs(delta) > 0.8) {
this._swipeState.direction = delta > 0 ? "left" : "right";
}
// Apply a translateX to the tab strip to give the user feedback on the swipe
const currentWorkspace = ws.getActiveWorkspaceFromCache();
ws._organizeWorkspaceStripLocations(currentWorkspace, true, translateX);
}
async _handleSwipeEnd(event) {
const ws = gZenWorkspaces;
if (!ws.workspaceEnabled) {
return;
}
event.preventDefault();
event.stopPropagation();
const isRTL = document.documentElement.matches(":-moz-locale-dir(rtl)");
const moveForward =
(event.direction === SimpleGestureEvent.DIRECTION_RIGHT) !== isRTL;
const rawDirection = moveForward ? 1 : -1;
const direction = ws.naturalScroll ? -1 : 1;
await ws.changeWorkspaceShortcut(rawDirection * direction, true);
// Reset swipe state
this._swipeState = {
isGestureActive: false,
lastDelta: 0,
direction: null,
};
}
onSwipeGestureAnimationEnd() {
const ws = gZenWorkspaces;
Services.prefs.setBoolPref("zen.swipe.is-fast-swipe", false);
document.documentElement.removeAttribute("swipe-gesture");
gZenUIManager.tabsWrapper.style.removeProperty("scrollbar-width");
[lazy.browserBackgroundElement, lazy.toolbarBackgroundElement].forEach(
element => {
element.style.setProperty("--zen-background-opacity", "1");
}
);
delete ws._hasAnimatedBackgrounds;
ws.updateTabsContainers();
document.removeEventListener("popupshown", ws.popupOpenHandler, {
once: true,
});
}
get isGestureActive() {
return this._swipeState?.isGestureActive;
}
}

View File

@@ -0,0 +1,7 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# 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/.
content/browser/zen-components/ZenSpaceBookmarksStorage.js (../../zen/spaces/ZenSpaceBookmarksStorage.js)
* content/browser/zen-styles/zen-workspaces.css (../../zen/spaces/zen-workspaces.css)
content/browser/zen-styles/zen-gradient-generator.css (../../zen/spaces/zen-gradient-generator.css)

12
src/zen/spaces/moz.build Normal file
View File

@@ -0,0 +1,12 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# 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/.
EXTRA_JS_MODULES.zen += [
"ZenGradientGenerator.mjs",
"ZenSpace.mjs",
"ZenSpaceCreation.mjs",
"ZenSpaceIcons.mjs",
"ZenSpaceManager.mjs",
"ZenSpacesSwipe.mjs",
]

View File

@@ -274,7 +274,7 @@
&:first-of-type {
width: 38px;
height: 38px;
border-width: 4px;
border-width: 6px;
pointer-events: all;
transition: transform 0.2s;
z-index: 999;

View File

@@ -13,7 +13,6 @@
font-size: x-small;
margin: 0 3px;
padding: 0;
appearance: auto;
position: relative;
-moz-window-dragging: no-drag;
@@ -243,7 +242,6 @@
.zen-current-workspace-indicator-name {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: auto;

View File

@@ -2,5 +2,5 @@
# 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/.
* content/browser/zen-styles/zen-decks.css (../../zen/split-view/zen-decks.css)
* content/browser/zen-styles/zen-split-view.css (../../zen/split-view/zen-split-view.css)
content/browser/zen-components/ZenViewSplitter.mjs (../../zen/split-view/ZenViewSplitter.mjs)

View File

@@ -38,6 +38,16 @@
/* Fix for issue https://github.com/zen-browser/desktop/issues/7564 */
position: absolute;
}
:root[inDOMFullscreen="true"] & {
&:not(.deck-selected) {
-moz-subtree-hidden-only-visually: 1 !important;
}
&.deck-selected {
inset: 0 !important;
}
}
}
.browserSidebarContainer[is-zen-split="true"],
@@ -59,7 +69,7 @@
}
#tabbrowser-tabpanels[zen-split-view="true"]:not(.zen-split-view-no-transition):not([zen-split-resizing]) > [zen-split="true"] {
--zen-active-split-outline-color: light-dark(var(--zen-primary-color), var(--button-background-color-primary));
--zen-active-split-outline-color: light-dark(hsl(from var(--zen-primary-color) h s calc(l - 20)), var(--button-background-color-primary));
transition: inset 0.09s ease-out !important;
& browser {
@@ -67,7 +77,7 @@
}
}
#tabbrowser-tabpanels[zen-split-view="true"] .browserSidebarContainer.deck-selected {
:root:not([inDOMFullscreen="true"]) #tabbrowser-tabpanels[zen-split-view="true"] .browserSidebarContainer.deck-selected {
&:not(.zen-glance-overlay) {
outline: 2px solid var(--zen-active-split-outline-color) !important;
}
@@ -163,7 +173,8 @@
display: flex;
-moz-context-properties: fill, fill-opacity;
border-radius: var(--tab-border-radius);
color: inherit;
color: contrast-color(var(--zen-active-split-outline-color));
opacity: 0.8;
fill: currentColor;
width: 16px;
height: 16px;

View File

@@ -115,7 +115,18 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
_onTabResetPinButton(event, tab) {
event.stopPropagation();
this._resetTabToStoredState(tab);
if (event.getModifierState("Accel")) {
let newTab = gBrowser.duplicateTab(tab, true);
newTab.addEventListener(
"SSTabRestored",
() => {
this._resetTabToStoredState(tab);
},
{ once: true }
);
} else {
this._resetTabToStoredState(tab);
}
gBrowser.selectedTab = tab;
}
@@ -170,6 +181,38 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
}
}
_onAccelKeyChange(e) {
let tab = this._tabWithResetPinButtonHovered;
if (!tab) {
return;
}
let accelHeld =
e.getModifierState("Accel") || (e.metaKey && e.type == "keydown");
this._setResetPinSublabel(tab, accelHeld);
// Up <-> down events until the mouse leaves the button.
// When hovered with accelHeld, we should listen to the next keyup event
let nextEvent = accelHeld ? "keyup" : "keydown";
let handler = nextE => this._onAccelKeyChange(nextE);
window.addEventListener(nextEvent, handler, { once: true });
}
_setResetPinSublabel(tab, accelHeld) {
let label = tab.querySelector(".zen-tab-sublabel");
document.l10n.setArgs(label, {
tabSubtitle: accelHeld ? "zen-default-pinned-cmd" : "zen-default-pinned",
});
}
onResetPinButtonMouseOver(tab, event) {
this._tabWithResetPinButtonHovered = tab;
this._onAccelKeyChange(event);
}
onResetPinButtonMouseOut(tab) {
this._setResetPinSublabel(tab, false);
delete this._tabWithResetPinButtonHovered;
}
resetPinnedTab(tab) {
if (!tab) {
tab = TabContextMenu.contextTab;
@@ -775,8 +818,15 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
}
}
onLocationChange(browser) {
const tab = gBrowser.getTabForBrowser(browser);
onLocationChange(aBrowser, aLocation) {
if (
(aLocation == "about:blank" &&
BrowserUIUtils.checkEmptyPageOrigin(aBrowser)) ||
aLocation == ""
) {
return;
}
const tab = gBrowser.getTabForBrowser(aBrowser);
if (
!tab ||
!tab.pinned ||
@@ -787,7 +837,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
}
// Remove # and ? from the URL
const pinUrl = tab._zenPinnedInitialState.entry.url.split("#")[0];
const currentUrl = browser.currentURI.spec.split("#")[0];
const currentUrl = aLocation.split("#")[0];
// Add an indicator that the pin has been changed
if (pinUrl === currentUrl) {
this.resetPinChangedUrl(tab);
@@ -812,7 +862,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
tab.removeAttribute("zen-show-sublabel");
const label = tab.querySelector(".zen-tab-sublabel");
window.document.l10n.setArgs(label, {
document.l10n.setArgs(label, {
tabSubtitle: "zen-default-pinned",
});
}

View File

@@ -325,11 +325,15 @@
display: none;
}
#tabbrowser-tabs:not([movingtab]) &:active:not(:has(.tab-content > image:active)) {
:root:not([zen-renaming-tab="true"])
#tabbrowser-tabs:not([movingtab])
&:active:not(:has(.tab-content > image:active)) {
scale: var(--zen-active-tab-scale);
}
#tabbrowser-tabs:not([movingtab]) & .tab-content > image:active {
:root:not([zen-renaming-tab="true"])
#tabbrowser-tabs:not([movingtab])
& .tab-content > image:active {
scale: 0.92;
}

View File

@@ -15,6 +15,8 @@ prefs = ["zen.workspaces.separate-essentials=false"]
["browser_pinned_nounload_reset.js"]
["browser_pinned_reset_button.js"]
["browser_pinned_reset_noswitch.js"]
["browser_pinned_switch.js"]

View File

@@ -0,0 +1,199 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
async function pinAndNavigateTab(url, navigateTo) {
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
gBrowser.pinTab(tab);
await gBrowser.TabStateFlusher.flush(tab.linkedBrowser);
await new Promise(r => setTimeout(r, 500));
BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, navigateTo);
await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, navigateTo);
return tab;
}
add_task(async function test_ResetPinButton_SelectsTab() {
const tab = await pinAndNavigateTab(
"https://example.com/1",
"https://example.com/2"
);
// Open another tab and select it
const otherTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"https://example.com/other"
);
Assert.notEqual(
gBrowser.selectedTab,
tab,
"The pinned tab should not be selected initially"
);
// Simulate clicking the reset pin button (without Accel key)
gZenPinnedTabManager._onTabResetPinButton(
{
stopPropagation() {},
getModifierState() {
return false;
},
},
tab
);
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(r => setTimeout(r, 100));
Assert.strictEqual(
gBrowser.selectedTab,
tab,
"The pinned tab should be selected after reset"
);
ok(
!tab.hasAttribute("zen-pinned-changed"),
"zen-pinned-changed should be removed after reset"
);
gBrowser.removeTab(otherTab);
gBrowser.removeTab(tab);
});
add_task(async function test_ResetPinButton_CmdClick_DuplicatesAndResets() {
const originalUrl = "https://example.com/1";
const navigatedUrl = "https://example.com/2";
const tab = await pinAndNavigateTab(originalUrl, navigatedUrl);
const tabCountBefore = gBrowser.tabs.length;
// Simulate CMD+click on the reset pin button
gZenPinnedTabManager._onTabResetPinButton(
{
stopPropagation() {},
getModifierState() {
return true;
},
},
tab
);
// Wait for the duplicate tab to be restored
const restoredEvent = await BrowserTestUtils.waitForEvent(
gBrowser.tabContainer,
"SSTabRestored"
);
const newTab = restoredEvent.target;
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(r => setTimeout(r, 100));
Assert.equal(
gBrowser.tabs.length,
tabCountBefore + 1,
"A new tab should be created from the duplicate"
);
Assert.equal(
newTab.linkedBrowser.currentURI.spec,
navigatedUrl,
"The duplicated tab should have the navigated URL"
);
ok(!newTab.pinned, "The duplicated tab should not be pinned");
Assert.strictEqual(
gBrowser.selectedTab,
tab,
"The pinned tab should be selected after CMD+click reset"
);
ok(
!tab.hasAttribute("zen-pinned-changed"),
"zen-pinned-changed should be removed after reset"
);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
originalUrl,
"The pinned tab should be reset to the original URL"
);
gBrowser.removeTab(newTab);
gBrowser.removeTab(tab);
});
add_task(async function test_Hover_SublabelChangesWithAccelKey() {
const tab = await pinAndNavigateTab(
"https://example.com/1",
"https://example.com/2"
);
// Track calls to document.l10n.setArgs to verify sublabel updates
const sublabelArgs = [];
const label = tab.querySelector(".zen-tab-sublabel");
const origSetArgs = document.l10n.setArgs;
document.l10n.setArgs = (el, args) => {
if (el === label) {
sublabelArgs.push(args.tabSubtitle);
}
origSetArgs.call(document.l10n, el, args);
};
try {
// Simulate hovering with no modifier key held
gZenPinnedTabManager.onResetPinButtonMouseOver(tab, {
getModifierState() {
return false;
},
metaKey: false,
type: "mouseover",
});
Assert.equal(
sublabelArgs.at(-1),
"zen-default-pinned",
"Sublabel should show default text on hover without Accel"
);
// Simulate pressing CMD while hovering
gZenPinnedTabManager._onAccelKeyChange({
getModifierState() {
return true;
},
metaKey: true,
type: "keydown",
});
Assert.equal(
sublabelArgs.at(-1),
"zen-default-pinned-cmd",
"Sublabel should show CMD text when Accel key is pressed"
);
// Simulate releasing CMD while still hovering
gZenPinnedTabManager._onAccelKeyChange({
getModifierState() {
return false;
},
metaKey: false,
type: "keyup",
});
Assert.equal(
sublabelArgs.at(-1),
"zen-default-pinned",
"Sublabel should revert to default text when Accel key is released"
);
// Simulate mouse out
gZenPinnedTabManager.onResetPinButtonMouseOut(tab);
Assert.equal(
sublabelArgs.at(-1),
"zen-default-pinned",
"Sublabel should show default text after mouse out"
);
ok(
!gZenPinnedTabManager._tabWithResetPinButtonHovered,
"Hovered tab reference should be cleared after mouse out"
);
} finally {
document.l10n.setArgs = origSetArgs;
}
gBrowser.removeTab(tab);
});

View File

@@ -1,12 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# 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/.
content/browser/zen-components/ZenWorkspaceIcons.mjs (../../zen/workspaces/ZenWorkspaceIcons.mjs)
content/browser/zen-components/ZenWorkspace.mjs (../../zen/workspaces/ZenWorkspace.mjs)
content/browser/zen-components/ZenWorkspaces.mjs (../../zen/workspaces/ZenWorkspaces.mjs)
content/browser/zen-components/ZenWorkspaceCreation.mjs (../../zen/workspaces/ZenWorkspaceCreation.mjs)
content/browser/zen-components/ZenWorkspaceBookmarksStorage.js (../../zen/workspaces/ZenWorkspaceBookmarksStorage.js)
content/browser/zen-components/ZenGradientGenerator.mjs (../../zen/workspaces/ZenGradientGenerator.mjs)
* content/browser/zen-styles/zen-workspaces.css (../../zen/workspaces/zen-workspaces.css)
content/browser/zen-styles/zen-gradient-generator.css (../../zen/workspaces/zen-gradient-generator.css)

View File

@@ -20,7 +20,7 @@
"brandShortName": "Zen",
"brandFullName": "Zen Browser",
"release": {
"displayVersion": "1.19.2b",
"displayVersion": "1.19.3b",
"github": {
"repo": "zen-browser/desktop"
},