Compare commits

..

13 Commits

Author SHA1 Message Date
mr. m
a791c5dadc gh-14619: Fixed split view drag opening new windows (gh-14621) 2026-07-16 22:32:30 +02:00
mr. m
247cde9c09 gh-14618: Fixed workspaces not being able to be removed (gh-14620) 2026-07-16 22:28:09 +02:00
mr. m
425f0ae1c3 gh-14605: Bump release versions (gh-14608) 2026-07-16 10:02:53 +02:00
Andrey Bochkarev
fcda16bfe6 gh-14511: Refactor windowDragLeave handler and related logic (gh-14566) 2026-07-15 10:52:15 +02:00
mr. m
31357dfe92 gh-14594: Sync upstream Firefox to version 152.0.6 (gh-14585) 2026-07-15 10:31:58 +02:00
Andrey Bochkarev
529755c48a gh-14575: Tab order flips when moving tabs between workspaces (gh-14581) 2026-07-14 19:07:08 +02:00
Andrey Bochkarev
c7e4217204 no-bug: Ignore zen-empty-tab in #isLastTabWindow (gh-14564)
When dragging a tab from the blank window to the main window, the blank
window doesn't close because '#isLastTabWindow' considers
'zen-empty-tab' as a normal tab.
2026-07-13 13:40:43 +02:00
Andrey Bochkarev
e837158e4c gh-14545: Correctly handle dnd moving a tab to an empty space (gh-14562) 2026-07-12 17:29:29 +02:00
Andrey Bochkarev
a1a2268f9b gh-14511: Prevent switching spaces when dragging outside (gh-14515)
fixed: https://github.com/zen-browser/desktop/issues/14511
2026-07-12 12:57:45 +02:00
Nicholas Leigh
274b9f9f83 gh-14495: Fix misaligned symbolic space icons in Space options menu on Linux (gh-14498) 2026-07-11 13:38:05 +02:00
Ashvin Jangid
fe0cf3601a gh-14544: Fix essentials promo ui leaving gap on pressing esc (gh-14547)
Fixes https://github.com/zen-browser/desktop/issues/14544

Also fixes essentials promo having weird ui with container specific
essentials


https://github.com/user-attachments/assets/f6df3e48-5fa2-471e-afbb-2472efa0620b
2026-07-11 12:45:20 +02:00
Kristijan Ribarić
61e0790144 gh-14470: Rename all occurances of Workspaces to Spaces in sync (gh-14528) 2026-07-09 01:37:00 +02:00
Andrey Bochkarev
03f251ff4a gh-13870: Remove two spaces cycling (gh-14529) 2026-07-09 01:36:51 +02:00
8 changed files with 117 additions and 114 deletions

View File

@@ -34,8 +34,8 @@ Zen is a firefox-based browser with the aim of pushing your productivity to a ne
### Firefox Versions
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `152.0.5`! 🚀
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 152.0.5`!
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `152.0.6`! 🚀
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 152.0.6`!
### Contributing

View File

@@ -1 +1 @@
6bd10168f81fb90f878132d87cf681200afbcd91
e7fbb446077536139cdd78b6a0386a97237af20f

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
index 08b5b56e069d038d72c87355920c4ce8a55ed805..e1a3a18081c7a8153f8d12c187bf4dd522944fcb 100644
index 08b5b56e069d038d72c87355920c4ce8a55ed805..0e4f57c21d28ccf173d8d1e13e96c2c1f8a79f34 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js
@@ -511,6 +511,7 @@
@@ -692,6 +692,15 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..e1a3a18081c7a8153f8d12c187bf4dd5
return;
}
@@ -5776,7 +5973,7 @@
*/
#isLastTabInWindow(tab) {
for (const otherTab of this.tabs) {
- if (otherTab != tab && otherTab.isOpen && !otherTab.hidden) {
+ if (otherTab != tab && otherTab.isOpen && !otherTab.hidden && !otherTab.hasAttribute("zen-empty-tab")) {
return false;
}
}
@@ -5867,7 +6064,7 @@
closeWindowWithLastTab != null
? closeWindowWithLastTab

View File

@@ -661,6 +661,13 @@
// can change the workspace after a short delay.
const splitter = document.getElementById("zen-sidebar-splitter");
let rect = window.windowUtils.getBoundsWithoutFlushing(gNavToolbox);
// If we are hovering over the essentials container, we can't change the workspace
const essentialsContainer = event.target.closest(
".zen-essentials-container"
);
if (essentialsContainer) {
return { isNearLeftEdge: false, isNearRightEdge: false };
}
if (!(
gZenCompactModeManager.preference &&
gZenCompactModeManager.canHideSidebar
@@ -715,20 +722,14 @@
#handle_sidebarDragOver(event) {
const dt = event.dataTransfer;
const draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
if (draggedTab.hasAttribute("zen-essential")) {
this.clearSpaceSwitchTimer();
return;
}
const { isNearLeftEdge, isNearRightEdge } =
this.#shouldSwitchSpace(event);
if (isNearLeftEdge || isNearRightEdge) {
if (!this.#changeSpaceTimer && !this.#isOutOfWindow) {
this.#changeSpaceTimer = setTimeout(() => {
if (this.#isOutOfWindow) {
return;
}
this.clearDragOverVisuals();
this.#maybeClearVerticalPinnedGridDragOver();
gZenWorkspaces
.changeWorkspaceShortcut(
isNearLeftEdge ? -1 : 1,
@@ -896,6 +897,13 @@
}
handle_windowDragLeave(event) {
// If relatedTarget exists, then we are still in the window
if (
event.relatedTarget ||
event.target.id === "zen-split-view-fake-browser"
) {
return;
}
const canvas = this._tabbrowserTabs._dndCanvas;
if (!this.#isMovingTab() || !canvas) {
return;
@@ -904,51 +912,38 @@
if (!isTab(draggedTab)) {
return;
}
let { screenX, clientX, screenY, clientY } = event;
if (!screenX && !screenY) {
if (this.#isOutOfWindow) {
return;
}
const { innerWidth: winWidth, innerHeight: winHeight } = window;
let allowedMargin = Services.prefs.getIntPref(
"zen.tabs.dnd-outside-window-margin",
5
);
const isOutOfWindow =
clientX <= allowedMargin ||
clientX >= winWidth - allowedMargin ||
clientY <= allowedMargin ||
clientY >= winHeight - allowedMargin;
if (isOutOfWindow && !this.#isOutOfWindow) {
this.#isOutOfWindow = true;
gZenViewSplitter.onBrowserDragEndToSplit(event, true);
this.#maybeClearVerticalPinnedGridDragOver();
this.clearSpaceSwitchTimer();
this.clearDragOverVisuals();
const dt = event.dataTransfer;
let dragData = draggedTab._dragData;
let movingTabs = dragData.movingTabs;
if (!this._browserDragImageWrapper) {
const wrappingDiv = document.createXULElement("vbox");
canvas.style.borderRadius = "8px";
canvas.style.border = "2px solid white";
wrappingDiv.style.width = 200 + "px";
wrappingDiv.style.height = 130 + "px";
wrappingDiv.style.position = "relative";
this.#maybeCreateDragImageDot(movingTabs, wrappingDiv);
wrappingDiv.appendChild(canvas);
this._browserDragImageWrapper = wrappingDiv;
document.documentElement.appendChild(wrappingDiv);
}
dt.updateDragImage(
this._browserDragImageWrapper,
this.originalDragImageArgs[1],
this.originalDragImageArgs[2]
);
window.addEventListener("dragenter", this.handle_windowDragEnter, {
once: true,
capture: true,
});
this.#isOutOfWindow = true;
gZenViewSplitter.onBrowserDragEndToSplit(event, true);
this.#maybeClearVerticalPinnedGridDragOver();
this.clearDragOverVisuals();
this.clearSpaceSwitchTimer();
const dt = event.dataTransfer;
let dragData = draggedTab._dragData;
let movingTabs = dragData.movingTabs;
if (!this._browserDragImageWrapper) {
const wrappingDiv = document.createXULElement("vbox");
canvas.style.borderRadius = "8px";
canvas.style.border = "2px solid white";
wrappingDiv.style.width = 200 + "px";
wrappingDiv.style.height = 130 + "px";
wrappingDiv.style.position = "relative";
this.#maybeCreateDragImageDot(movingTabs, wrappingDiv);
wrappingDiv.appendChild(canvas);
this._browserDragImageWrapper = wrappingDiv;
document.documentElement.appendChild(wrappingDiv);
}
dt.updateDragImage(
this._browserDragImageWrapper,
this.originalDragImageArgs[1],
this.originalDragImageArgs[2]
);
window.addEventListener("dragenter", this.handle_windowDragEnter, {
once: true,
capture: true,
});
}
handle_drop(event) {
@@ -1333,7 +1328,8 @@
// Essentials should be properly handled by ::animateVerticalPinnedGridDragOver
if (!dropElement || dropElement.hasAttribute("zen-essential")) {
this.clearDragOverVisuals();
return null;
// If dropElement is null or essential, dropElement should be the empty tab
return [dropBefore, gZenWorkspaces._emptyTab];
}
if (dropElement.hasAttribute("zen-empty-tab") && dropElement.group) {
let secondTab = dropElement.group.tabs[1];
@@ -1477,10 +1473,7 @@
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
let dragData = draggedTab._dragData;
let movingTabs = dragData.movingTabs;
if (
!gZenPinnedTabManager.canEssentialBeAdded(draggedTab) &&
!draggedTab.hasAttribute("zen-essential")
) {
if (!gZenPinnedTabManager.canEssentialBeAdded(draggedTab)) {
return;
}
let essentialsPromoStatus = this.createZenEssentialsPromo();

View File

@@ -460,16 +460,19 @@ class nsZenWorkspaces {
document
.getElementById("zen-essentials")
.appendChild(essentialsContainer);
// Set an initial hidden state if the essentials section is not supposed
// to be shown on the current workspace
if (
this.containerSpecificEssentials &&
this.getActiveWorkspaceFromCache()?.containerTabId != container
) {
essentialsContainer.setAttribute("hidden", "true");
}
}
// Set a hidden state if the essentials section is not supposed
// to be shown on the current workspace, else remove the hidden state
if (
this.containerSpecificEssentials &&
this.getActiveWorkspaceFromCache()?.containerTabId != container
) {
essentialsContainer.setAttribute("hidden", "true");
} else {
essentialsContainer.removeAttribute("hidden");
}
return essentialsContainer;
}
@@ -1161,6 +1164,9 @@ class nsZenWorkspaces {
const item = document.createXULElement("menuitem");
item.className = "zen-workspace-context-menu-item";
item.setAttribute("zen-workspace-id", workspace.uuid);
if (AppConstants.platform === "linux") {
disableCurrent = true;
}
if (!disableCurrent) {
item.setAttribute("type", "radio");
}
@@ -1583,6 +1589,11 @@ class nsZenWorkspaces {
}
moveTabsToWorkspace(tabs, workspaceID) {
const newTabPlacement = Services.prefs.getBoolPref(
"zen.view.show-newtab-button-top",
false
);
tabs = newTabPlacement ? tabs.reverse() : tabs;
for (let tab of tabs) {
const workspaceContainer = this.workspaceElement(workspaceID);
const container = tab.pinned
@@ -1597,14 +1608,11 @@ class nsZenWorkspaces {
}
if (container) {
const newtabPlacement = Services.prefs.getBoolPref(
"zen.view.show-newtab-button-top",
false
);
const insertElement = newtabPlacement
const insertElement = newTabPlacement
? container.firstChild
: container.lastChild;
const previousWorkspaceID = tab.getAttribute("zen-workspace-id");
if (tab.group?.hasAttribute("split-view-group")) {
gBrowser.zenHandleTabMove(tab.group, () => {
for (const subTab of tab.group.tabs) {
@@ -1618,6 +1626,12 @@ class nsZenWorkspaces {
tab.setAttribute("zen-workspace-id", workspaceID);
container.insertBefore(tab, insertElement);
});
if (this.lastSelectedWorkspaceTabs[previousWorkspaceID] === tab) {
// This tab is no longer the last selected tab in the previous workspace because it's being moved to
// the current workspace
delete this.lastSelectedWorkspaceTabs[previousWorkspaceID];
}
}
// also change glance tab if it's the same tab
const glanceTab = tab.querySelector(".tabbrowser-tab[zen-glance-tab]");
@@ -1690,6 +1704,7 @@ class nsZenWorkspaces {
try {
this.log("Changing workspace to", workspace?.uuid);
await this.#performWorkspaceChange(workspace, ...args);
this.updateWorkspacesChangeContextMenu();
} catch (e) {
console.error("gZenWorkspaces: Error changing workspace", e);
}
@@ -1864,11 +1879,6 @@ class nsZenWorkspaces {
}
const newTransform = diff * 100;
element.style.transform = `translateX(${newTransform + offsetPixels / 2}%)`;
// A special case for two spaces
if (spaceLen === 2 && diff !== 0) {
const side = offsetPixels > 0 ? -100 : 100;
element.style.transform = `translateX(${side + offsetPixels / 2}%)`;
}
}
// Hide other essentials with different containerTabId
for (const container of otherContainersEssentials) {
@@ -2099,12 +2109,6 @@ class nsZenWorkspaces {
offset += spaceLen;
}
offset = offset * 100;
// A special case for two spaces
if (spaceLen === 2 && offset !== 0) {
const currentTransform =
parseFloat(element.style.transform.split("(")[1]) || 0;
offset = currentTransform >= 0 ? 100 : -100;
}
const newTransform = `translateX(${offset}%)`;
// Only animate the workspace that is coming in, to avoid having multiple workspaces
// animating off-screen at the same time which can cause performance issues. With an off
@@ -2181,17 +2185,6 @@ class nsZenWorkspaces {
existingOffset = currentTransform || (isGoingLeft ? -100 : 100);
newOffset = 0;
}
if (spaceLen === 2) {
if (containsPrev && !containsNew) {
existingOffset = currentTransform;
newOffset = currentTransform >= 0 ? 100 : -100;
} else {
existingOffset = currentTransform >= 0 ? 100 : -100;
newOffset = 0;
}
}
const newTransform = `translateX(${newOffset}%)`;
let existingTransform = `translateX(${existingOffset}%)`;
if (shouldAnimate) {
@@ -2489,9 +2482,12 @@ class nsZenWorkspaces {
if (gZenWorkspaces.privateWindowOrDisabled) {
return;
}
const workspaces = this.getWorkspaces();
const workspaces = this.getWorkspaces().filter(
w => w.uuid !== this.activeWorkspace
);
const ctxCommand = document.getElementById("cmd_zenCtxDeleteWorkspace");
if (workspaces.length <= 1) {
const hasMultipleWorkspaces = !!workspaces.length;
if (!hasMultipleWorkspaces) {
ctxCommand.setAttribute("disabled", "true");
} else {
ctxCommand.removeAttribute("disabled");
@@ -2511,6 +2507,9 @@ class nsZenWorkspaces {
)) {
item.remove();
}
if (!hasMultipleWorkspaces) {
continue;
}
const separator = document.createXULElement("menuseparator");
separator.classList.add("zen-workspace-context-menu-item");
if (isMoveTabPopup) {
@@ -2952,15 +2951,7 @@ class nsZenWorkspaces {
? gBrowser.selectedTabs
: [TabContextMenu.contextTab];
document.getElementById("tabContextMenu").hidePopup();
for (let tab of tabs) {
const previousWorkspaceID = tab.getAttribute("zen-workspace-id");
this.moveTabToWorkspace(tab, workspaceID);
if (this.lastSelectedWorkspaceTabs[previousWorkspaceID] === tab) {
// This tab is no longer the last selected tab in the previous workspace because it's being moved to
// the current workspace
delete this.lastSelectedWorkspaceTabs[previousWorkspaceID];
}
}
this.moveTabsToWorkspace(tabs, workspaceID);
// Make sure we select the last tab in the new workspace
this.lastSelectedWorkspaceTabs[workspaceID] =
gZenGlanceManager.getTabOrGlanceParent(tabs[tabs.length - 1]);

View File

@@ -33,10 +33,10 @@ const RECORD_TYPE_BY_PREFIX = Object.freeze({
* Workspaces engine collection.
*/
export class ZenWorkspacesRecord extends CryptoWrapper {
_logName = "Sync.Record.ZenWorkspaces";
_logName = "Sync.Record.ZenSpaces";
}
ZenWorkspacesRecord.prototype.type = "workspaces";
ZenWorkspacesRecord.prototype.type = "spaces";
function parseRecordId(id) {
const sep = id.indexOf("~");
@@ -51,7 +51,7 @@ function parseRecordId(id) {
function createRecordId(type, id) {
const prefix = RECORD_ID_PREFIX_BY_TYPE[type];
if (!prefix) {
throw new Error(`Unknown Workspaces Sync record type: ${type}`);
throw new Error(`Unknown Spaces Sync record type: ${type}`);
}
return `${prefix}~${id}`;
}
@@ -371,11 +371,11 @@ class ZenWorkspacesTracker extends Tracker {
*/
export class ZenWorkspacesEngine extends SyncEngine {
static get name() {
return "Workspaces";
return "Spaces";
}
constructor(service) {
super("Workspaces", service);
super("Spaces", service);
}
get _storeObj() {

View File

@@ -835,6 +835,16 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
let isRegularTabs = false;
// Check for essentials container
if (essentialTabsTarget) {
if (gZenWorkspaces.containerSpecificEssentials) {
const targetContainerId =
gZenWorkspaces.getActiveWorkspaceFromCache().containerTabId;
const sameContextId =
(tab.getAttribute("usercontextid") || 0) == targetContainerId;
if (!sameContextId && tab.hasAttribute("zen-essential")) {
this.removeEssentials(tab, false);
moved = true;
}
}
if (
!tab.hasAttribute("zen-essential") &&
!tab?.group?.hasAttribute("split-view-group")

View File

@@ -5,8 +5,8 @@
"binaryName": "zen",
"version": {
"product": "firefox",
"version": "152.0.5",
"candidate": "152.0.5",
"version": "152.0.6",
"candidate": "152.0.6",
"candidateBuild": 1
},
"buildOptions": {
@@ -20,7 +20,7 @@
"brandShortName": "Zen",
"brandFullName": "Zen Browser",
"release": {
"displayVersion": "1.21.6b",
"displayVersion": "1.21.8b",
"github": {
"repo": "zen-browser/desktop"
},