From 647e219639112664cef39563bb6de0da468e5937 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Sat, 4 Jul 2026 11:18:08 +0200
Subject: [PATCH 001/116] gh-14470: Add space and container sync (gh-14471)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Kristijan Ribarić <96492763+kristijanribaric@users.noreply.github.com>
Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
---
prefs/zen/sync.yaml | 12 +
prefs/zen/workspaces.yaml | 3 -
.../preferences/config/account-sync-mjs.patch | 20 +
.../dialogs/syncChooseWhatToSync-js.patch | 2 +-
.../dialogs/syncChooseWhatToSync-xhtml.patch | 2 +-
.../preferences/sync-inc-xhtml.patch | 2 +-
.../sync/modules/service-sys-mjs.patch | 15 +
.../ContextualIdentityService-sys-mjs.patch | 37 ++
src/zen/moz.build | 1 +
.../sessionstore/ZenSessionManager.sys.mjs | 11 +
src/zen/spaces/ZenSpaceManager.mjs | 114 ++++-
src/zen/sync/ZenSyncManager.sys.mjs | 160 +++++++
src/zen/sync/ZenWorkspacesSync.sys.mjs | 404 ++++++++++++++++++
src/zen/sync/moz.build | 8 +
tools/ffprefs/src/main.rs | 11 +
15 files changed, 784 insertions(+), 18 deletions(-)
create mode 100644 prefs/zen/sync.yaml
create mode 100644 src/browser/components/preferences/config/account-sync-mjs.patch
create mode 100644 src/services/sync/modules/service-sys-mjs.patch
create mode 100644 src/toolkit/components/contextualidentity/ContextualIdentityService-sys-mjs.patch
create mode 100644 src/zen/sync/ZenSyncManager.sys.mjs
create mode 100644 src/zen/sync/ZenWorkspacesSync.sys.mjs
create mode 100644 src/zen/sync/moz.build
diff --git a/prefs/zen/sync.yaml b/prefs/zen/sync.yaml
new file mode 100644
index 000000000..dff75e481
--- /dev/null
+++ b/prefs/zen/sync.yaml
@@ -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/.
+
+- name: services.sync.engine.spaces
+ value: true
+ condition: "@IS_TWILIGHT@"
+
+- name: services.sync.engine.spaces
+ value: false
+ locked: true
+ condition: "!@IS_TWILIGHT@"
diff --git a/prefs/zen/workspaces.yaml b/prefs/zen/workspaces.yaml
index 24aaf887c..dfa8b7d6c 100644
--- a/prefs/zen/workspaces.yaml
+++ b/prefs/zen/workspaces.yaml
@@ -32,9 +32,6 @@
- name: zen.workspaces.scroll-modifier-key
value: ctrl
-- name: services.sync.engine.workspaces
- value: false
-
- name: zen.workspaces.separate-essentials
value: true
diff --git a/src/browser/components/preferences/config/account-sync-mjs.patch b/src/browser/components/preferences/config/account-sync-mjs.patch
new file mode 100644
index 000000000..ce14b3e05
--- /dev/null
+++ b/src/browser/components/preferences/config/account-sync-mjs.patch
@@ -0,0 +1,20 @@
+diff --git a/browser/components/preferences/config/account-sync.mjs b/browser/components/preferences/config/account-sync.mjs
+index b503987a08e2ce64bfc01c4e3a21e5e19ef834f0..b1c03a0b219fd3eddd93c1deaeb18ab79c85f168 100644
+--- a/browser/components/preferences/config/account-sync.mjs
++++ b/browser/components/preferences/config/account-sync.mjs
+@@ -42,6 +42,7 @@ Preferences.addAll([
+ { id: "services.sync.engine.creditcards", type: "bool" },
+ { id: "services.sync.engine.addons", type: "bool" },
+ { id: "services.sync.engine.prefs", type: "bool" },
++ { id: "services.sync.engine.spaces", type: "bool" },
+ ]);
+
+ /**
+@@ -546,6 +547,7 @@ const SYNC_ENGINE_SETTINGS = [
+ type: "payments",
+ },
+ { id: "syncAddons", pref: "services.sync.engine.addons", type: "addons" },
++ { id: "syncSpaces", pref: "services.sync.engine.spaces", type: "workspaces" },
+ { id: "syncSettings", pref: "services.sync.engine.prefs", type: "settings" },
+ ];
+
diff --git a/src/browser/components/preferences/dialogs/syncChooseWhatToSync-js.patch b/src/browser/components/preferences/dialogs/syncChooseWhatToSync-js.patch
index e066aca21..da89f90fb 100644
--- a/src/browser/components/preferences/dialogs/syncChooseWhatToSync-js.patch
+++ b/src/browser/components/preferences/dialogs/syncChooseWhatToSync-js.patch
@@ -6,7 +6,7 @@ index 64aa0d98a0622c01f3dcfff1a04bfcda368354d2..2013e04b0881ad2295d6897b91e1573c
{ id: "services.sync.engine.passwords", type: "bool" },
{ id: "services.sync.engine.addresses", type: "bool" },
{ id: "services.sync.engine.creditcards", type: "bool" },
-+ { id: "services.sync.engine.workspaces", type: "bool" },
++ { id: "services.sync.engine.spaces", type: "bool" },
]);
let gSyncChooseWhatToSync = {
diff --git a/src/browser/components/preferences/dialogs/syncChooseWhatToSync-xhtml.patch b/src/browser/components/preferences/dialogs/syncChooseWhatToSync-xhtml.patch
index 3b0ab370d..0ebce7576 100644
--- a/src/browser/components/preferences/dialogs/syncChooseWhatToSync-xhtml.patch
+++ b/src/browser/components/preferences/dialogs/syncChooseWhatToSync-xhtml.patch
@@ -20,7 +20,7 @@ index a893c5ec3d007820d98f5d92dd039640faa2c181..9cbd00102e44ccf98b37845474d92d57
+
+
+
diff --git a/src/browser/components/preferences/sync-inc-xhtml.patch b/src/browser/components/preferences/sync-inc-xhtml.patch
index 06944335a..4b6e6306d 100644
--- a/src/browser/components/preferences/sync-inc-xhtml.patch
+++ b/src/browser/components/preferences/sync-inc-xhtml.patch
@@ -6,7 +6,7 @@ index c379e1a5f82692406a92d9fcd3bca2769dfac5b2..af037dd3d995813d966524ac44a3795d
-+
++
+
+
+
diff --git a/src/services/sync/modules/service-sys-mjs.patch b/src/services/sync/modules/service-sys-mjs.patch
new file mode 100644
index 000000000..67522f59b
--- /dev/null
+++ b/src/services/sync/modules/service-sys-mjs.patch
@@ -0,0 +1,15 @@
+diff --git a/services/sync/modules/service.sys.mjs b/services/sync/modules/service.sys.mjs
+index c873293871ffaba305bc1bf41730d79c13546b85..0e0171cec13dfcbb296ec7bf03628370ce2fa93f 100644
+--- a/services/sync/modules/service.sys.mjs
++++ b/services/sync/modules/service.sys.mjs
+@@ -99,6 +99,10 @@ function getEngineModules() {
+ whenTrue: "ExtensionStorageEngineKinto",
+ whenFalse: "ExtensionStorageEngineBridge",
+ };
++ result.Workspaces = {
++ module: "resource:///modules/zen/ZenWorkspacesSync.sys.mjs",
++ symbol: "ZenWorkspacesEngine",
++ };
+ return result;
+ }
+
diff --git a/src/toolkit/components/contextualidentity/ContextualIdentityService-sys-mjs.patch b/src/toolkit/components/contextualidentity/ContextualIdentityService-sys-mjs.patch
new file mode 100644
index 000000000..1b108254d
--- /dev/null
+++ b/src/toolkit/components/contextualidentity/ContextualIdentityService-sys-mjs.patch
@@ -0,0 +1,37 @@
+diff --git a/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs b/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs
+index 5702ff28cc22206f5ce16584dac8a78d816562ce..3d08ecc97ce5995b30d8a4af0c33df329b428008 100644
+--- a/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs
++++ b/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs
+@@ -270,11 +270,29 @@ _ContextualIdentityService.prototype = {
+ });
+ },
+
+- create(name, icon, color) {
++ create(name, icon, color, id = null) {
+ this.ensureDataReady();
+
+- // Retrieve the next userContextId available.
+- let userContextId = ++this._lastUserContextId;
++ if (id !== null) {
++ id = typeof id == "string" ? Number(id) : id;
++ if (!Number.isSafeInteger(id) || id <= 0) {
++ throw new Error(`Invalid userContextId '${id}'`);
++ }
++ }
++
++ let userContextId;
++ if (
++ id !== null &&
++ !this._identities.some(identity => identity.userContextId === id)
++ ) {
++ userContextId = id;
++ this._lastUserContextId = Math.max(
++ this._lastUserContextId,
++ userContextId
++ );
++ } else {
++ userContextId = ++this._lastUserContextId;
++ }
+
+ // Throw an error if the next userContextId available is invalid (the one associated to
+ // MAX_USER_CONTEXT_ID is already reserved to "userContextIdInternal.webextStorageLocal", which
diff --git a/src/zen/moz.build b/src/zen/moz.build
index 8b1426065..4e3e80ecb 100644
--- a/src/zen/moz.build
+++ b/src/zen/moz.build
@@ -20,4 +20,5 @@ DIRS += [
"share",
"spaces",
"space-routing",
+ "sync",
]
diff --git a/src/zen/sessionstore/ZenSessionManager.sys.mjs b/src/zen/sessionstore/ZenSessionManager.sys.mjs
index 39f2d0eb2..bccec81c6 100644
--- a/src/zen/sessionstore/ZenSessionManager.sys.mjs
+++ b/src/zen/sessionstore/ZenSessionManager.sys.mjs
@@ -10,6 +10,7 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
ZenLiveFoldersManager:
"resource:///modules/zen/ZenLiveFoldersManager.sys.mjs",
+ ZenSyncStore: "resource:///modules/zen/ZenSyncManager.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
@@ -609,6 +610,7 @@ export class nsZenSessionManager {
}
);
this.#collectWindowData(windows);
+ lazy.ZenSyncStore.notifyAboutChanges();
// This would save the data to disk asynchronously or when quitting the app.
let sidebar = this.#sidebarWithoutCloning;
this.#file.data = sidebar;
@@ -622,6 +624,15 @@ export class nsZenSessionManager {
this.log(`Saving Zen session data with ${sidebar.tabs?.length || 0} tabs`);
}
+ /**
+ * Returns the sidebar data object.
+ *
+ * @returns {object}
+ */
+ getSidebarData() {
+ return this.#sidebarWithoutCloning;
+ }
+
/**
* Called when the last known backup should be deleted and a new one
* created. This uses the #deferredBackupTask to debounce clusters of
diff --git a/src/zen/spaces/ZenSpaceManager.mjs b/src/zen/spaces/ZenSpaceManager.mjs
index 51cfb89ba..997ea5eb6 100644
--- a/src/zen/spaces/ZenSpaceManager.mjs
+++ b/src/zen/spaces/ZenSpaceManager.mjs
@@ -11,6 +11,7 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
ZenSessionStore: "resource:///modules/zen/ZenSessionManager.sys.mjs",
+ ZenSyncStore: "resource:///modules/zen/ZenSyncManager.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "browserBackgroundElement", () => {
@@ -148,7 +149,7 @@ class nsZenWorkspaces {
if (!this.privateWindowOrDisabled) {
const observerFunction = async () => {
delete this._workspaceBookmarksCache;
- await this.workspaceBookmarks();
+ await this.#initializeWorkspaceBookmarks();
this._invalidateBookmarkContainers();
};
Services.obs.addObserver(observerFunction, "workspace-bookmarks-updated");
@@ -168,6 +169,63 @@ class nsZenWorkspaces {
}
}
+ /**
+ * Applies live sync changes: updates workspace cache, removes deleted items,
+ * then creates/updates pulled items.
+ *
+ * @param {{ spaces: Array}} pulled Reconcile-pulled items.
+ * @param {{ spaces: Array}} removals Items to remove.
+ */
+ async _applySyncChanges(pulled, removals = {}) {
+ if (!this.shouldHaveWorkspaces || this.privateWindowOrDisabled) {
+ return;
+ }
+ await this.promiseInitialized;
+
+ // 1. Update workspace cache (remove deleted, merge pulled)
+ const removedSpaceIds = new Set((removals.spaces || []).map(s => s.uuid));
+ if (removedSpaceIds.size || pulled.spaces?.length) {
+ const localMap = new Map(
+ this.getWorkspaces()
+ .filter(w => !removedSpaceIds.has(w.uuid))
+ .map(w => [w.uuid, w])
+ );
+ for (const space of pulled.spaces || []) {
+ if (!space?.uuid) {
+ continue;
+ }
+ const existing = localMap.get(space.uuid);
+ localMap.set(space.uuid, existing ? { ...existing, ...space } : space);
+ }
+ await this.propagateWorkspaces(
+ this.#getOrderedWorkspacesByPosition(Array.from(localMap.values()))
+ );
+ this.#propagateWorkspaceData();
+ }
+ }
+
+ #getOrderedWorkspacesByPosition(workspaces) {
+ return [...workspaces]
+ .map((workspace, index) => ({ workspace, index }))
+ .sort((a, b) => {
+ const aPosition =
+ typeof a.workspace.position === "number"
+ ? a.workspace.position
+ : a.index;
+ const bPosition =
+ typeof b.workspace.position === "number"
+ ? b.workspace.position
+ : b.index;
+ return aPosition - bPosition || a.index - b.index;
+ })
+ .map(({ workspace }) => {
+ // strip the position property that comes from pulled workspaces
+ const rest = { ...workspace };
+ delete rest.position;
+ return rest;
+ });
+ }
+
#afterLoadInit() {
const onResize = (...args) => {
requestAnimationFrame(() => {
@@ -691,17 +749,17 @@ class nsZenWorkspaces {
return spacesForSS;
}
- async workspaceBookmarks() {
+ async #initializeWorkspaceBookmarks() {
if (this.privateWindowOrDisabled) {
this._workspaceBookmarksCache = {
bookmarks: [],
lastChangeTimestamp: 0,
};
- return this._workspaceBookmarksCache;
+ return;
}
if (this._workspaceBookmarksCache) {
- return this._workspaceBookmarksCache;
+ return;
}
const [bookmarks, lastChangeTimestamp] = await Promise.all([
@@ -710,8 +768,6 @@ class nsZenWorkspaces {
]);
this._workspaceBookmarksCache = { bookmarks, lastChangeTimestamp };
-
- return this._workspaceCache;
}
restoreWorkspacesFromSessionStore(aWinData = {}) {
@@ -771,7 +827,7 @@ class nsZenWorkspaces {
return (async () => {
await this.#waitForPromises();
this.#afterLoadInit();
- await this.workspaceBookmarks();
+ await this.#initializeWorkspaceBookmarks();
await this.changeWorkspace(activeWorkspace, { onInit: true });
this.#fixTabPositions();
this.onWindowResize();
@@ -797,6 +853,13 @@ class nsZenWorkspaces {
})();
}
+ #markWorkspaceChanged(workspaceId) {
+ lazy.ZenSyncStore.markItemChanged({
+ type: "space",
+ id: workspaceId,
+ });
+ }
+
async selectStartPage() {
if (!this.workspaceEnabled || gZenUIManager.testingEnabled) {
return;
@@ -1214,12 +1277,19 @@ class nsZenWorkspaces {
} else {
workspacesData.push(workspaceData);
}
+ // mark item as changed for sync
+ this.#markWorkspaceChanged(workspaceData.uuid);
+
this.#propagateWorkspaceData();
}
removeWorkspace(windowID) {
let { promise, resolve } = Promise.withResolvers();
this.#deleteWorkspaceOwnedTabs(windowID);
+
+ // mark item as changed for sync
+ this.#markWorkspaceChanged(windowID);
+
let workspacesData = this.getWorkspaces();
// Remove the workspace from the cache
workspacesData = workspacesData.filter(
@@ -1351,30 +1421,50 @@ class nsZenWorkspaces {
if (this.privateWindowOrDisabled) {
return;
}
- const workspaces = this._workspaceCache;
+ const workspaces = this.getWorkspaces();
+ // Track previous positions so we only notify observers for workspaces whose
+ // position changed during the reorder.
+ const previousPositions = new Map(
+ workspaces.map((workspace, index) => [workspace.uuid, index])
+ );
+
const workspace = workspaces.find(w => w.uuid === id);
if (!workspace) {
console.warn(`Workspace with ID ${id} not found for reordering.`);
return;
}
+
// Remove the workspace from its current position
const currentIndex = workspaces.indexOf(workspace);
if (currentIndex === -1) {
console.warn(`Workspace with ID ${id} not found in the list.`);
return;
}
- workspaces.splice(currentIndex, 1);
+
// Insert the workspace at the new position
- if (newPosition < 0 || newPosition > workspaces.length) {
+ if (newPosition < 0 || newPosition >= workspaces.length) {
console.warn(
`Invalid position ${newPosition} for reordering workspace with ID ${id}.`
);
return;
}
+
+ workspaces.splice(currentIndex, 1);
workspaces.splice(newPosition, 0, workspace);
+
// Propagate the changes if the order has changed
if (currentIndex !== newPosition) {
- this.#propagateWorkspaceData();
+ this._workspaceCache = workspaces;
+
+ for (const [i, ws] of workspaces.entries()) {
+ if (previousPositions.get(ws.uuid) === i) {
+ continue;
+ }
+ // mark item as changed for sync
+ this.#markWorkspaceChanged(ws.uuid);
+ }
+
+ this.#propagateWorkspaceData(workspaces);
}
}
@@ -2493,7 +2583,7 @@ class nsZenWorkspaces {
for (const tab of gBrowser.tabs) {
if (
!tab.hasAttribute("zen-workspace-id") &&
- !tab.hasAttribute("zen-workspace-id")
+ !tab.hasAttribute("zen-essential")
) {
tab.setAttribute("zen-workspace-id", workspace.uuid);
}
diff --git a/src/zen/sync/ZenSyncManager.sys.mjs b/src/zen/sync/ZenSyncManager.sys.mjs
new file mode 100644
index 000000000..1b454da81
--- /dev/null
+++ b/src/zen/sync/ZenSyncManager.sys.mjs
@@ -0,0 +1,160 @@
+/* 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.defineESModuleGetters(lazy, {
+ ZenSessionStore: "resource:///modules/zen/ZenSessionManager.sys.mjs",
+ ContextualIdentityService:
+ "resource://gre/modules/ContextualIdentityService.sys.mjs",
+ ZenWindowSync: "resource:///modules/zen/ZenWindowSync.sys.mjs",
+});
+
+function normalizeUserContextId(value) {
+ const normalized = typeof value === "string" ? Number(value) : value;
+ if (!Number.isSafeInteger(normalized) || normalized <= 0) {
+ return null;
+ }
+ return normalized;
+}
+
+class ZenSyncManager {
+ getSidebarData() {
+ return lazy.ZenSessionStore.getSidebarData();
+ }
+
+ /**
+ * Whether to ignore changes to items. This is used to prevent
+ * infinite loops when applying incoming sync changes.
+ *
+ * @type {boolean}
+ */
+ #ignoreChanges = false;
+
+ #changedItems = new Map();
+
+ markItemChanged(item) {
+ if (item.type && item.id && !this.#ignoreChanges) {
+ const key = `${item.type}~${item.id}`;
+ this.#changedItems.set(key, { type: item.type, id: item.id });
+ }
+ }
+
+ #getChangedItems() {
+ return Array.from(this.#changedItems.values());
+ }
+
+ #clearChangedItems() {
+ this.#changedItems.clear();
+ }
+
+ notifyAboutChanges() {
+ const changedItems = this.#getChangedItems();
+
+ for (const item of changedItems) {
+ Services.obs.notifyObservers(
+ { wrappedJSObject: item },
+ "zen-workspace-item-changed"
+ );
+ }
+ this.#clearChangedItems();
+ }
+
+ async applyIncomingBatch(pulled, removals) {
+ try {
+ this.#ignoreChanges = true;
+ this.#applyIncomingContainers(
+ pulled.containers || [],
+ removals.containers || []
+ );
+
+ const win = lazy.ZenWindowSync.firstSyncedWindow;
+ if (win?.gZenWorkspaces) {
+ await win.gZenWorkspaces._applySyncChanges(pulled, removals);
+ }
+ } catch (e) {
+ console.error("ZenSyncManager: Failed to apply incoming sync data:", e);
+ throw e;
+ } finally {
+ this.#ignoreChanges = false;
+ }
+ }
+
+ #applyIncomingContainers(pulledContainers, removedContainers) {
+ const localContainersById = new Map(
+ lazy.ContextualIdentityService.getPublicIdentities().map(container => [
+ container.userContextId,
+ container,
+ ])
+ );
+
+ for (const container of pulledContainers) {
+ if (!container.name) {
+ continue;
+ }
+
+ const userContextId = normalizeUserContextId(container.userContextId);
+ if (userContextId === null) {
+ console.warn(
+ "ZenSyncManager: Ignoring incoming container with invalid userContextId",
+ { container }
+ );
+ continue;
+ }
+
+ const existsLocally = localContainersById.has(userContextId);
+
+ if (existsLocally) {
+ lazy.ContextualIdentityService.update(
+ userContextId,
+ container.name,
+ container.icon,
+ container.color
+ );
+ continue;
+ }
+
+ const createdIdentity = lazy.ContextualIdentityService.create(
+ container.name,
+ container.icon,
+ container.color,
+ userContextId
+ );
+ if (createdIdentity) {
+ localContainersById.set(createdIdentity.userContextId, createdIdentity);
+ }
+ if (createdIdentity && createdIdentity.userContextId !== userContextId) {
+ console.warn("ZenSyncManager: Container sync created unexpected ID", {
+ requestedId: userContextId,
+ createdId: createdIdentity.userContextId,
+ name: container.name,
+ });
+ }
+ }
+
+ for (const container of removedContainers) {
+ const userContextId = normalizeUserContextId(container.userContextId);
+ if (userContextId === null) {
+ console.warn(
+ "ZenSyncManager: Ignoring container removal with invalid userContextId",
+ { container }
+ );
+ continue;
+ }
+
+ if (!localContainersById.has(userContextId)) {
+ continue;
+ }
+
+ try {
+ lazy.ContextualIdentityService.remove(userContextId);
+ localContainersById.delete(userContextId);
+ } catch {
+ // Container may already be gone locally.
+ }
+ }
+ }
+}
+
+export const ZenSyncStore = new ZenSyncManager();
diff --git a/src/zen/sync/ZenWorkspacesSync.sys.mjs b/src/zen/sync/ZenWorkspacesSync.sys.mjs
new file mode 100644
index 000000000..175319169
--- /dev/null
+++ b/src/zen/sync/ZenWorkspacesSync.sys.mjs
@@ -0,0 +1,404 @@
+/* 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/. */
+
+import {
+ Store,
+ SyncEngine,
+ Tracker,
+} from "resource://services-sync/engines.sys.mjs";
+import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
+import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ ZenSyncStore: "resource:///modules/zen/ZenSyncManager.sys.mjs",
+ ContextualIdentityService:
+ "resource://gre/modules/ContextualIdentityService.sys.mjs",
+});
+
+const RECORD_ID_PREFIX_BY_TYPE = Object.freeze({
+ space: "s",
+ container: "c",
+});
+
+const RECORD_TYPE_BY_PREFIX = Object.freeze({
+ s: "space",
+ c: "container",
+});
+
+/**
+ * Sync record wrapper for workspace and container items stored in the
+ * Workspaces engine collection.
+ */
+export class ZenWorkspacesRecord extends CryptoWrapper {
+ _logName = "Sync.Record.ZenWorkspaces";
+}
+
+ZenWorkspacesRecord.prototype.type = "workspaces";
+
+function parseRecordId(id) {
+ const sep = id.indexOf("~");
+ if (sep === -1) {
+ return null;
+ }
+ const prefix = id.slice(0, sep);
+ const key = id.slice(sep + 1);
+ return { type: RECORD_TYPE_BY_PREFIX[prefix] || prefix, key };
+}
+
+function createRecordId(type, id) {
+ const prefix = RECORD_ID_PREFIX_BY_TYPE[type];
+ if (!prefix) {
+ throw new Error(`Unknown Workspaces Sync record type: ${type}`);
+ }
+ return `${prefix}~${id}`;
+}
+
+function normalizeUserContextId(value) {
+ const normalized = typeof value === "string" ? Number(value) : value;
+ if (!Number.isSafeInteger(normalized) || normalized <= 0) {
+ return null;
+ }
+ return normalized;
+}
+
+/**
+ * Strips the sync-envelope fields (`id` and `type`) from incoming record data
+ * and restores the item's real identity key where needed
+ *
+ * @param {object} data
+ */
+function stripSyncFields(data) {
+ const rest = { ...data };
+ delete rest.id;
+ delete rest.type;
+ return rest;
+}
+
+/**
+ * Sync store implementation that serializes local workspace and container
+ * state into records and applies incoming remote changes.
+ */
+class ZenWorkspacesStore extends Store {
+ constructor(name, engine) {
+ super(name, engine);
+ }
+
+ async getAllIDs() {
+ const ids = {};
+ const sidebar = lazy.ZenSyncStore.getSidebarData();
+
+ for (const space of sidebar.spaces || []) {
+ if (space.uuid) {
+ ids[createRecordId("space", space.uuid)] = true;
+ }
+ }
+
+ for (const c of lazy.ContextualIdentityService.getPublicIdentities()) {
+ ids[createRecordId("container", c.userContextId)] = true;
+ }
+
+ return ids;
+ }
+
+ async itemExists(id) {
+ const parsed = parseRecordId(id);
+ if (!parsed) {
+ return false;
+ }
+ const sidebar = lazy.ZenSyncStore.getSidebarData();
+
+ switch (parsed.type) {
+ case "space":
+ return (sidebar.spaces || []).some(s => s.uuid === parsed.key);
+ case "container":
+ return lazy.ContextualIdentityService.getPublicIdentities().some(
+ c => String(c.userContextId) === parsed.key
+ );
+ default:
+ return false;
+ }
+ }
+
+ async createRecord(id, collection) {
+ const record = new ZenWorkspacesRecord(collection, id);
+ const parsed = parseRecordId(id);
+ if (!parsed) {
+ record.deleted = true;
+ return record;
+ }
+
+ const sidebar = lazy.ZenSyncStore.getSidebarData();
+
+ switch (parsed.type) {
+ case "space": {
+ const spaces = sidebar.spaces || [];
+ const idx = spaces.findIndex(s => s.uuid === parsed.key);
+ if (idx === -1) {
+ record.deleted = true;
+ return record;
+ }
+ const rest = { ...spaces[idx] };
+ delete rest.syncStatus;
+ record.cleartext = { id, type: "space", ...rest, position: idx };
+ break;
+ }
+
+ case "container": {
+ const container =
+ lazy.ContextualIdentityService.getPublicIdentities().find(
+ c => String(c.userContextId) === parsed.key
+ );
+ if (!container) {
+ record.deleted = true;
+ return record;
+ }
+ record.cleartext = {
+ id,
+ type: "container",
+ userContextId: container.userContextId,
+ name: container.name,
+ icon: container.icon,
+ color: container.color,
+ };
+ break;
+ }
+
+ default:
+ record.deleted = true;
+ }
+
+ return record;
+ }
+
+ async applyIncomingBatch(records, _countTelemetry) {
+ const pulled = { spaces: [], containers: [] };
+ const removals = { spaces: [], containers: [] };
+
+ for (const record of records) {
+ if (record.deleted) {
+ this._collectRemoval(record.id, removals);
+ continue;
+ }
+ const data = record.cleartext;
+ if (!data?.type) {
+ continue;
+ }
+ const clean = stripSyncFields(data);
+ switch (data.type) {
+ case "space":
+ pulled.spaces.push(clean);
+ break;
+ case "container":
+ pulled.containers.push(clean);
+ break;
+ }
+ }
+
+ // Suppress change tracking while applying incoming data to prevent
+ // feedback loops where applied items get re-uploaded immediately.
+ this.engine._tracker.ignoreAll = true;
+ try {
+ await lazy.ZenSyncStore.applyIncomingBatch(pulled, removals);
+ } finally {
+ this.engine._tracker.ignoreAll = false;
+ }
+ return [];
+ }
+
+ _collectRemoval(id, removals) {
+ const parsed = parseRecordId(id);
+ if (!parsed) {
+ return;
+ }
+ switch (parsed.type) {
+ case "space":
+ removals.spaces.push({ uuid: parsed.key });
+ break;
+ case "container": {
+ const userContextId = normalizeUserContextId(parsed.key);
+ if (userContextId === null) {
+ console.warn(
+ "ZenWorkspacesStore: Ignoring container removal with invalid userContextId",
+ { id }
+ );
+ break;
+ }
+ removals.containers.push({ userContextId });
+ break;
+ }
+ }
+ }
+
+ async create(record) {
+ await this._applySingle(record);
+ }
+
+ async update(record) {
+ await this._applySingle(record);
+ }
+
+ async _applySingle(record) {
+ this.engine._tracker.ignoreAll = true;
+ try {
+ if (record.deleted) {
+ const removals = { spaces: [], containers: [] };
+ this._collectRemoval(record.id, removals);
+ await lazy.ZenSyncStore.applyIncomingBatch(
+ { spaces: [], containers: [] },
+ removals
+ );
+ return;
+ }
+ const data = record.cleartext;
+ if (!data?.type) {
+ return;
+ }
+ const clean = stripSyncFields(data);
+ const pulled = { spaces: [], containers: [] };
+ switch (data.type) {
+ case "space":
+ pulled.spaces.push(clean);
+ break;
+ case "container":
+ pulled.containers.push(clean);
+ break;
+ }
+ await lazy.ZenSyncStore.applyIncomingBatch(pulled, {
+ spaces: [],
+ containers: [],
+ });
+ } finally {
+ this.engine._tracker.ignoreAll = false;
+ }
+ }
+
+ async remove() {
+ // No-op: never delete user data on wipe
+ }
+
+ async wipe() {
+ // No-op: never delete user data on wipe
+ }
+
+ changeItemID() {
+ // No-op
+ }
+}
+
+/**
+ * Sync tracker that watches workspace and contextual identity observers and
+ * marks the corresponding record IDs as changed.
+ */
+class ZenWorkspacesTracker extends Tracker {
+ #changedIDs = {};
+ #ignoreAll = false;
+
+ get ignoreAll() {
+ return this.#ignoreAll;
+ }
+
+ set ignoreAll(value) {
+ this.#ignoreAll = value;
+ }
+
+ onStart() {
+ Services.obs.addObserver(this, "zen-workspace-item-changed");
+ Services.obs.addObserver(this, "contextual-identity-created");
+ Services.obs.addObserver(this, "contextual-identity-updated");
+ Services.obs.addObserver(this, "contextual-identity-deleted");
+ }
+
+ onStop() {
+ Services.obs.removeObserver(this, "zen-workspace-item-changed");
+ Services.obs.removeObserver(this, "contextual-identity-created");
+ Services.obs.removeObserver(this, "contextual-identity-updated");
+ Services.obs.removeObserver(this, "contextual-identity-deleted");
+ }
+
+ observe(subject, topic, _data) {
+ if (this.#ignoreAll) {
+ return;
+ }
+ if (topic === "zen-workspace-item-changed") {
+ const type = subject?.wrappedJSObject?.type;
+ const id = subject?.wrappedJSObject?.id;
+ if (type && id) {
+ this._trackChange({ type, id });
+ }
+ } else if (topic.startsWith("contextual-identity-")) {
+ const id = subject?.wrappedJSObject?.userContextId;
+ if (id && normalizeUserContextId(id) !== null) {
+ this._trackChange({ type: "container", id });
+ }
+ }
+ }
+
+ _trackChange(data) {
+ if (data.type && data.id) {
+ const id = createRecordId(data.type, data.id);
+ this.#changedIDs[id] = Date.now() / 1000;
+ this.score += SCORE_INCREMENT_XLARGE;
+ }
+ }
+
+ async getChangedIDs() {
+ return { ...this.#changedIDs };
+ }
+
+ async addChangedID(id, when) {
+ this.#changedIDs[id] = when;
+ return true;
+ }
+
+ async removeChangedID(...ids) {
+ for (const id of ids) {
+ delete this.#changedIDs[id];
+ }
+ return true;
+ }
+
+ clearChangedIDs() {
+ this.#changedIDs = {};
+ }
+}
+
+/**
+ * Sync engine entrypoint that wires the Workspaces record, store, and tracker
+ * implementations into Firefox Sync.
+ */
+export class ZenWorkspacesEngine extends SyncEngine {
+ static get name() {
+ return "Workspaces";
+ }
+
+ constructor(service) {
+ super("Workspaces", service);
+ }
+
+ get _storeObj() {
+ return ZenWorkspacesStore;
+ }
+
+ get _trackerObj() {
+ return ZenWorkspacesTracker;
+ }
+
+ get _recordObj() {
+ return ZenWorkspacesRecord;
+ }
+
+ get version() {
+ return 2;
+ }
+
+ get syncPriority() {
+ return 8;
+ }
+
+ get allowSkippedRecord() {
+ return false;
+ }
+}
diff --git a/src/zen/sync/moz.build b/src/zen/sync/moz.build
new file mode 100644
index 000000000..d10ad7a19
--- /dev/null
+++ b/src/zen/sync/moz.build
@@ -0,0 +1,8 @@
+# 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 += [
+ "ZenSyncManager.sys.mjs",
+ "ZenWorkspacesSync.sys.mjs",
+]
diff --git a/tools/ffprefs/src/main.rs b/tools/ffprefs/src/main.rs
index ac11c8e5b..20724730d 100644
--- a/tools/ffprefs/src/main.rs
+++ b/tools/ffprefs/src/main.rs
@@ -346,6 +346,17 @@ fn expand_pref_values(prefs: &mut [Preference]) {
}
pref.value = new_value.clone();
}
+ // Also change the condition if it contains placeholders
+ if let Some(condition) = &pref.condition {
+ let mut new_condition = condition.clone();
+ for (key, value) in &env_values {
+ let placeholder = format!("@{}@", key);
+ if new_condition.contains(&placeholder) {
+ new_condition = new_condition.replace(&placeholder, if *value { "1" } else { "0" });
+ }
+ pref.condition = Some(new_condition.clone());
+ }
+ }
}
}
From e9c35b0f927ed044e426a71758eda7c72d2fb6e0 Mon Sep 17 00:00:00 2001
From: InlitX
Date: Sun, 5 Jul 2026 12:19:18 +0200
Subject: [PATCH 002/116] gh-10749: Localize command palette actions, emoji
picker, etc (gh-14473)
---
crowdin.yml | 2 +
.../browser/browser/zen-command-palette.ftl | 36 ++++++++++
locales/en-US/browser/browser/zen-general.ftl | 2 +
.../base/content/zen-panels/emojis-picker.inc | 2 +-
src/zen/spaces/ZenSpaceCreation.mjs | 2 +-
src/zen/urlbar/ZenUBActionsProvider.sys.mjs | 8 ++-
src/zen/urlbar/ZenUBGlobalActions.sys.mjs | 69 ++++++++++---------
7 files changed, 86 insertions(+), 35 deletions(-)
create mode 100644 locales/en-US/browser/browser/zen-command-palette.ftl
diff --git a/crowdin.yml b/crowdin.yml
index 964f52d44..2fd87a2fc 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -24,3 +24,5 @@ files:
translation: browser/browser/zen-boosts.ftl
- source: en-US/browser/browser/zen-space-routing.ftl
translation: browser/browser/zen-space-routing.ftl
+ - source: en-US/browser/browser/zen-command-palette.ftl
+ translation: browser/browser/zen-command-palette.ftl
diff --git a/locales/en-US/browser/browser/zen-command-palette.ftl b/locales/en-US/browser/browser/zen-command-palette.ftl
new file mode 100644
index 000000000..c8af413e2
--- /dev/null
+++ b/locales/en-US/browser/browser/zen-command-palette.ftl
@@ -0,0 +1,36 @@
+# 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/.
+
+zen-action-toggle-compact-mode = Toggle Compact Mode
+zen-action-open-theme-picker = Open Theme Picker
+zen-action-new-split-view = New Split View
+zen-action-new-folder = New Folder
+zen-action-copy-current-url = Copy Current URL
+zen-action-settings = Settings
+zen-action-open-private-window = Open Private Window
+zen-action-open-new-window = Open New Window
+zen-action-new-blank-window = New Blank Window
+zen-action-pin-tab = Pin Tab
+zen-action-unpin-tab = Unpin Tab
+zen-action-open-space-routing = Open Space Routing
+zen-action-new-boost = New Boost
+zen-action-next-space = Next Space
+zen-action-previous-space = Previous Space
+zen-action-close-tab = Close Tab
+zen-action-reload-tab = Reload Tab
+zen-action-reload-tab-without-cache = Reload Tab Without Cache
+zen-action-next-tab = Next Tab
+zen-action-previous-tab = Previous Tab
+zen-action-capture-screenshot = Capture Screenshot
+zen-action-toggle-tabs-on-right = Toggle Tabs on right
+zen-action-add-to-essentials = Add to Essentials
+zen-action-remove-from-essentials = Remove from Essentials
+zen-action-find-in-page = Find in Page
+zen-action-manage-extensions = Manage Extensions
+zen-action-switch-to-automatic-appearance = Switch to Automatic Appearance
+zen-action-switch-to-light-mode = Switch to Light Mode
+zen-action-switch-to-dark-mode = Switch to Dark Mode
+zen-action-print = Print
+zen-action-focus-on = Focus on
+zen-action-extension = Extension
diff --git a/locales/en-US/browser/browser/zen-general.ftl b/locales/en-US/browser/browser/zen-general.ftl
index cf6c3b95b..93bf074f1 100644
--- a/locales/en-US/browser/browser/zen-general.ftl
+++ b/locales/en-US/browser/browser/zen-general.ftl
@@ -87,6 +87,8 @@ zen-icons-picker-emoji =
.label = Emojis
zen-icons-picker-svg =
.label = Icons
+zen-emojis-picker-search =
+ .placeholder = Search emojis
urlbar-search-mode-zen_actions = Actions
zen-site-data-settings = Settings
diff --git a/src/browser/base/content/zen-panels/emojis-picker.inc b/src/browser/base/content/zen-panels/emojis-picker.inc
index 1b8a35ec6..2bc42e234 100644
--- a/src/browser/base/content/zen-panels/emojis-picker.inc
+++ b/src/browser/base/content/zen-panels/emojis-picker.inc
@@ -14,7 +14,7 @@
diff --git a/src/zen/spaces/ZenSpaceCreation.mjs b/src/zen/spaces/ZenSpaceCreation.mjs
index c84a5bf7b..52045f64a 100644
--- a/src/zen/spaces/ZenSpaceCreation.mjs
+++ b/src/zen/spaces/ZenSpaceCreation.mjs
@@ -192,7 +192,7 @@ class nsZenWorkspaceCreation extends MozXULElement {
this.currentProfile = {
id: 0,
- name: "Default",
+ name: ContextualIdentityService.formatContextLabel("user-context-none"),
};
} else {
this.inputProfile.parentNode.hidden = true;
diff --git a/src/zen/urlbar/ZenUBActionsProvider.sys.mjs b/src/zen/urlbar/ZenUBActionsProvider.sys.mjs
index 6814e1261..08fa090b0 100644
--- a/src/zen/urlbar/ZenUBActionsProvider.sys.mjs
+++ b/src/zen/urlbar/ZenUBActionsProvider.sys.mjs
@@ -27,6 +27,10 @@ ChromeUtils.defineESModuleGetters(lazy, {
UrlUtils: "resource://gre/modules/UrlUtils.sys.mjs",
});
+ChromeUtils.defineLazyGetter(lazy, "l10n", () => {
+ return new Localization(["browser/zen-command-palette.ftl"], true);
+});
+
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"enabledPref",
@@ -182,7 +186,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
.workspaceElement(workspace.uuid)
?.style.getPropertyValue("--zen-primary-color");
actions.push({
- label: "Focus on",
+ label: lazy.l10n.formatValueSync("zen-action-focus-on"),
extraPayload: {
workspaceId: workspace.uuid,
prettyName: workspace.name,
@@ -215,7 +219,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
.map(addon => {
return {
icon: "chrome://browser/skin/zen-icons/extension.svg",
- label: "Extension",
+ label: lazy.l10n.formatValueSync("zen-action-extension"),
commandId: `zen:extension-${addon.id}`,
extraPayload: {
extensionId: addon.id,
diff --git a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs
index 613c0073f..517938ec5 100644
--- a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs
+++ b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs
@@ -13,58 +13,62 @@ XPCOMUtils.defineLazyPreferenceGetter(
2
);
+ChromeUtils.defineLazyGetter(lazy, "l10n", () => {
+ return new Localization(["browser/zen-command-palette.ftl"], true);
+});
+
function isNotEmptyTab(window) {
return !window.gBrowser.selectedTab.hasAttribute("zen-empty-tab");
}
const globalActionsTemplate = [
{
- label: "Toggle Compact Mode",
+ l10nId: "zen-action-toggle-compact-mode",
command: "cmd_zenCompactModeToggle",
icon: "chrome://browser/skin/zen-icons/sidebar.svg",
},
{
- label: "Open Theme Picker",
+ l10nId: "zen-action-open-theme-picker",
command: "cmd_zenOpenZenThemePicker",
icon: "chrome://browser/skin/zen-icons/edit-theme.svg",
},
{
- label: "New Split View",
+ l10nId: "zen-action-new-split-view",
command: "cmd_zenNewEmptySplit",
icon: "chrome://browser/skin/zen-icons/split.svg",
},
{
- label: "New Folder",
+ l10nId: "zen-action-new-folder",
command: "cmd_zenOpenFolderCreation",
icon: "chrome://browser/skin/zen-icons/folder.svg",
},
{
- label: "Copy Current URL",
+ l10nId: "zen-action-copy-current-url",
command: "cmd_zenCopyCurrentURL",
icon: "chrome://browser/skin/zen-icons/link.svg",
},
{
- label: "Settings",
+ l10nId: "zen-action-settings",
command: window => window.openPreferences(),
icon: "chrome://browser/skin/zen-icons/settings.svg",
},
{
- label: "Open Private Window",
+ l10nId: "zen-action-open-private-window",
command: "Tools:PrivateBrowsing",
icon: "chrome://browser/skin/zen-icons/private-window.svg",
},
{
- label: "Open New Window",
+ l10nId: "zen-action-open-new-window",
command: "cmd_newNavigator",
icon: "chrome://browser/skin/zen-icons/window.svg",
},
{
- label: "New Blank Window",
+ l10nId: "zen-action-new-blank-window",
command: "cmd_zenNewNavigatorUnsynced",
icon: "chrome://browser/skin/zen-icons/window.svg",
},
{
- label: "Pin Tab",
+ l10nId: "zen-action-pin-tab",
command: "cmd_zenTogglePinTab",
icon: "chrome://browser/skin/zen-icons/pin.svg",
isAvailable: window => {
@@ -73,7 +77,7 @@ const globalActionsTemplate = [
},
},
{
- label: "Unpin Tab",
+ l10nId: "zen-action-unpin-tab",
command: "cmd_zenTogglePinTab",
icon: "chrome://browser/skin/zen-icons/unpin.svg",
isAvailable: window => {
@@ -82,12 +86,12 @@ const globalActionsTemplate = [
},
},
{
- label: "Open Space Routing",
+ l10nId: "zen-action-open-space-routing",
command: "cmd_zenOpenSpaceRoutingSettings",
icon: "chrome://browser/skin/zen-icons/selectable/airplane.svg",
},
{
- label: "New Boost",
+ l10nId: "zen-action-new-boost",
icon: "chrome://browser/skin/zen-icons/boost.svg",
isAvailable: window => {
if (!isNotEmptyTab(window)) {
@@ -130,7 +134,7 @@ const globalActionsTemplate = [
},
},
{
- label: "Next Space",
+ l10nId: "zen-action-next-space",
command: "cmd_zenWorkspaceForward",
icon: "chrome://browser/skin/zen-icons/forward.svg",
isAvailable: window => {
@@ -138,7 +142,7 @@ const globalActionsTemplate = [
},
},
{
- label: "Previous Space",
+ l10nId: "zen-action-previous-space",
command: "cmd_zenWorkspaceBackward",
icon: "chrome://browser/skin/zen-icons/back.svg",
isAvailable: window => {
@@ -147,7 +151,7 @@ const globalActionsTemplate = [
},
},
{
- label: "Close Tab",
+ l10nId: "zen-action-close-tab",
command: "cmd_close",
icon: "chrome://browser/skin/zen-icons/close.svg",
isAvailable: window => {
@@ -155,27 +159,27 @@ const globalActionsTemplate = [
},
},
{
- label: "Reload Tab",
+ l10nId: "zen-action-reload-tab",
command: "Browser:Reload",
icon: "chrome://browser/skin/zen-icons/reload.svg",
},
{
- label: "Reload Tab Without Cache",
+ l10nId: "zen-action-reload-tab-without-cache",
command: "Browser:ReloadSkipCache",
icon: "chrome://browser/skin/zen-icons/reload.svg",
},
{
- label: "Next Tab",
+ l10nId: "zen-action-next-tab",
command: "Browser:NextTab",
icon: "chrome://browser/skin/zen-icons/forward.svg",
},
{
- label: "Previous Tab",
+ l10nId: "zen-action-previous-tab",
command: "Browser:PrevTab",
icon: "chrome://browser/skin/zen-icons/back.svg",
},
{
- label: "Capture Screenshot",
+ l10nId: "zen-action-capture-screenshot",
command: "Browser:Screenshot",
icon: "chrome://browser/skin/zen-icons/screenshot.svg",
isAvailable: window => {
@@ -183,12 +187,12 @@ const globalActionsTemplate = [
},
},
{
- label: "Toggle Tabs on right",
+ l10nId: "zen-action-toggle-tabs-on-right",
command: "cmd_zenToggleTabsOnRight",
icon: "chrome://browser/skin/zen-icons/sidebars-right.svg",
},
{
- label: "Add to Essentials",
+ l10nId: "zen-action-add-to-essentials",
command: window =>
window.gZenPinnedTabManager.addToEssentials(window.gBrowser.selectedTab),
isAvailable: window => {
@@ -201,7 +205,7 @@ const globalActionsTemplate = [
icon: "chrome://browser/skin/zen-icons/essential-add.svg",
},
{
- label: "Remove from Essentials",
+ l10nId: "zen-action-remove-from-essentials",
command: window =>
window.gZenPinnedTabManager.removeEssentials(window.gBrowser.selectedTab),
isAvailable: window =>
@@ -209,7 +213,7 @@ const globalActionsTemplate = [
icon: "chrome://browser/skin/zen-icons/essential-remove.svg",
},
{
- label: "Find in Page",
+ l10nId: "zen-action-find-in-page",
command: "cmd_find",
icon: "chrome://browser/skin/zen-icons/search-page.svg",
isAvailable: window => {
@@ -217,12 +221,12 @@ const globalActionsTemplate = [
},
},
{
- label: "Manage Extensions",
+ l10nId: "zen-action-manage-extensions",
command: "Tools:Addons",
icon: "chrome://browser/skin/zen-icons/extension.svg",
},
{
- label: "Switch to Automatic Appearance",
+ l10nId: "zen-action-switch-to-automatic-appearance",
command: () => Services.prefs.setIntPref("zen.view.window.scheme", 2),
icon: "chrome://browser/skin/zen-icons/sparkles.svg",
isAvailable: () => {
@@ -230,7 +234,7 @@ const globalActionsTemplate = [
},
},
{
- label: "Switch to Light Mode",
+ l10nId: "zen-action-switch-to-light-mode",
command: () => Services.prefs.setIntPref("zen.view.window.scheme", 1),
icon: "chrome://browser/skin/zen-icons/face-sun.svg",
isAvailable: () => {
@@ -238,7 +242,7 @@ const globalActionsTemplate = [
},
},
{
- label: "Switch to Dark Mode",
+ l10nId: "zen-action-switch-to-dark-mode",
command: () => Services.prefs.setIntPref("zen.view.window.scheme", 0),
icon: "chrome://browser/skin/zen-icons/moon-stars.svg",
isAvailable: () => {
@@ -246,7 +250,7 @@ const globalActionsTemplate = [
},
},
{
- label: "Print",
+ l10nId: "zen-action-print",
command: "cmd_print",
icon: "chrome://browser/skin/zen-icons/print.svg",
isAvailable: window => {
@@ -266,7 +270,10 @@ export const globalActions = globalActionsTemplate.map(action => ({
commandId:
typeof action.command === "string"
? action.command
- : `zen:global-action-${action.label.toLowerCase().replace(/\s+/g, "-")}`,
+ : `zen:global-action-${action.l10nId.replace("zen-action-", "")}`,
extraPayload: {},
...action,
+ get label() {
+ return lazy.l10n.formatValueSync(action.l10nId);
+ },
}));
From 79d3d6a9936cd3dc9cb961647cb82e63ec564333 Mon Sep 17 00:00:00 2001
From: Ashvin Jangid <142579833+ashvwinn@users.noreply.github.com>
Date: Sun, 5 Jul 2026 16:15:07 +0530
Subject: [PATCH 003/116] gh-14460: prevent nested glance tabs (gh-14480)
Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
---
src/zen/glance/ZenGlanceManager.mjs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/zen/glance/ZenGlanceManager.mjs b/src/zen/glance/ZenGlanceManager.mjs
index c5f503059..4db6e72fd 100644
--- a/src/zen/glance/ZenGlanceManager.mjs
+++ b/src/zen/glance/ZenGlanceManager.mjs
@@ -1551,6 +1551,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
return (
owner &&
owner.pinned &&
+ !owner.hasAttribute("glance-id") &&
this._lazyPref.SHOULD_OPEN_EXTERNAL_TABS_IN_GLANCE &&
owner.linkedBrowser?.browsingContext?.isAppTab &&
this.tabDomainsDiffer(owner, uri) &&
From 3a72290f162612aaaf504a217695ef75427b5c95 Mon Sep 17 00:00:00 2001
From: Andrey Bochkarev <50177704+octaviusz@users.noreply.github.com>
Date: Sun, 5 Jul 2026 13:45:35 +0300
Subject: [PATCH 004/116] gh-13870: Fix essentials overlap and fix two space
wrap-around (gh-14476)
---
src/zen/spaces/ZenSpaceManager.mjs | 248 ++++++------------------
src/zen/tabs/zen-tabs/vertical-tabs.css | 4 -
2 files changed, 56 insertions(+), 196 deletions(-)
diff --git a/src/zen/spaces/ZenSpaceManager.mjs b/src/zen/spaces/ZenSpaceManager.mjs
index 997ea5eb6..6ae2e4abe 100644
--- a/src/zen/spaces/ZenSpaceManager.mjs
+++ b/src/zen/spaces/ZenSpaceManager.mjs
@@ -449,7 +449,7 @@ class nsZenWorkspaces {
container = 0;
}
let essentialsContainer = document.querySelector(
- `.zen-essentials-container[container="${container}"]:not([cloned])`
+ `.zen-essentials-container[container="${container}"]`
);
if (!essentialsContainer) {
essentialsContainer = document.createXULElement("hbox");
@@ -1864,6 +1864,11 @@ 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) {
@@ -2017,28 +2022,20 @@ class nsZenWorkspaces {
diff += spaceLen;
}
const isGoingLeft = diff < 0;
- const clonedEssentials = [];
+ const essentialsAnimData = [];
if (shouldAnimate && this.shouldAnimateEssentials && previousWorkspace) {
+ const containerIds = new Map();
for (const workspace of workspaces) {
- const essentialsContainer = this.getEssentialsSection(
- workspace.containerTabId
- );
- let lastCloned = clonedEssentials[clonedEssentials.length - 1];
- if (lastCloned && lastCloned.contextId == workspace.containerTabId) {
- lastCloned.repeat++;
- lastCloned.workspaces.push(workspace);
- continue;
+ const containerId = workspace.containerTabId;
+ if (!containerIds.has(containerId)) {
+ containerIds.set(containerId, []);
}
- essentialsContainer.setAttribute("hidden", "true");
- const essentialsClone = essentialsContainer.cloneNode(true);
- essentialsClone.removeAttribute("hidden");
- essentialsClone.setAttribute("cloned", "true");
- clonedEssentials.push({
- container: essentialsClone,
- workspaces: [workspace],
- contextId: workspace.containerTabId,
- originalContainer: essentialsContainer,
- repeat: 0,
+ containerIds.get(containerId).push(workspace);
+ }
+ for (const [containerId, spaces] of containerIds) {
+ essentialsAnimData.push({
+ element: this.getEssentialsSection(containerId),
+ workspaces: spaces,
});
}
}
@@ -2102,6 +2099,12 @@ 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
@@ -2145,190 +2148,59 @@ class nsZenWorkspaces {
}
}
if (this.shouldAnimateEssentials && previousWorkspace) {
- // Animate essentials
- const newWorkspaceEssentialsContainer = clonedEssentials.find(cloned =>
- cloned.workspaces.some(w => w.uuid === newWorkspace.uuid)
- );
- // Get a list of essentials containers that are in between the first and last workspace
- const essentialsContainersInBetween = clonedEssentials.filter(cloned => {
- const essentialsWorkspaces = cloned.workspaces;
- const firstIndex = workspaces.findIndex(
- w => w.uuid === essentialsWorkspaces[0].uuid
+ for (const data of essentialsAnimData) {
+ const container = data.element;
+ const essentialsWorkspaces = data.workspaces;
+
+ const containsPrev = essentialsWorkspaces.some(
+ w => w.uuid === previousWorkspace.uuid
);
- const lastIndex = workspaces.findIndex(
- w =>
- w.uuid ===
- essentialsWorkspaces[essentialsWorkspaces.length - 1].uuid
+ const containsNew = essentialsWorkspaces.some(
+ w => w.uuid === newWorkspace.uuid
);
- const [start, end] = [
- Math.min(previousWorkspaceIndex, newWorkspaceIndex),
- Math.max(previousWorkspaceIndex, newWorkspaceIndex),
- ];
-
- // Check if any part of the container overlaps with the movement range
- return firstIndex <= end && lastIndex >= start;
- });
- for (const cloned of clonedEssentials) {
- const container = cloned.container;
- const essentialsWorkspaces = cloned.workspaces;
- const repeats = cloned.repeat;
- // Animate like the workspaces above expect essentials are a bit more
- // complicated because they are not based on workspaces but on containers
- // So, if we have the following arangement:
- // | [workspace1] [workspace2] [workspace3] [workspace4]
- // | [container1] [container1] [container2] [container1]
- // And if we are changing from workspace 1 to workspace 4,
- // we should be doing the following:
- // First container (repeat 2 times) will stay in place until
- // we reach container 3, then animate to the left and container 2
- // also move to the left after that while container 1 in workspace 4
- // will slide in from the right
-
- // Get the index from first and last workspace
- const firstWorkspaceIndex = workspaces.findIndex(
- w => w.uuid === essentialsWorkspaces[0].uuid
- );
- const lastWorkspaceIndex = workspaces.findIndex(
- w =>
- w.uuid ===
- essentialsWorkspaces[essentialsWorkspaces.length - 1].uuid
- );
- cloned.originalContainer.style.removeProperty("transform");
- // Check if the container is even going to appear on the screen, to save on animation
- if (
- // We also need to check if the container is even going to appear on the screen.
- // In order to do this, we need to check if the container is between the first and last workspace.
- // Note that essential containers can have multiple workspaces,
- // so we need to check if any of the workspaces in the container are between the
- // first and last workspace.
- !essentialsContainersInBetween.find(
- ce =>
- ce.workspaces.some(
- w => w.uuid === essentialsWorkspaces[0].uuid
- ) &&
- ce.workspaces.some(
- w =>
- w.uuid ===
- essentialsWorkspaces[essentialsWorkspaces.length - 1].uuid
- )
- )
- ) {
+ if (!containsPrev && !containsNew) {
+ container.setAttribute("hidden", "true");
continue;
}
- cloned.originalContainer.parentNode.appendChild(container);
- let stepsInBetween =
- Math.abs(
- newWorkspaceIndex -
- (isGoingLeft ? firstWorkspaceIndex : lastWorkspaceIndex)
- ) + 1;
- const usingSameContainer =
- newWorkspaceEssentialsContainer?.workspaces.some(
- w => w.uuid === newWorkspace.uuid
- ) &&
- newWorkspaceEssentialsContainer?.workspaces.some(
- w => w.uuid === previousWorkspace.uuid
- );
- let newOffset =
- -(
- newWorkspaceIndex -
- (isGoingLeft ? firstWorkspaceIndex : lastWorkspaceIndex) +
- (!isGoingLeft ? repeats - 1 : -repeats + 1)
- ) * 100;
- let existingOffset =
- -(
- newWorkspaceIndex -
- (isGoingLeft ? lastWorkspaceIndex : firstWorkspaceIndex) +
- (isGoingLeft ? repeats - 1 : -repeats + 1)
- ) * 100;
+ container.removeAttribute("hidden");
- // If we are on the same container and both new and old workspace are in the same "essentialsWorkspaces"
- // we can simply not animate the essentials
- if (
- usingSameContainer &&
- essentialsWorkspaces.some(w => w.uuid === newWorkspace.uuid) &&
- essentialsWorkspaces.some(w => w.uuid === previousWorkspace.uuid)
- ) {
- newOffset = 0;
- existingOffset = 0;
+ if (containsPrev && containsNew) {
+ container.style.transform = "translateX(0%)";
+ continue;
}
- const needsOffsetAdjustment =
- stepsInBetween > essentialsWorkspaces.length || usingSameContainer;
+ let existingOffset, newOffset;
+ const currentTransform =
+ parseFloat(container.style.transform.split("(")[1]) || 0;
+ if (containsPrev && !containsNew) {
+ existingOffset = currentTransform;
+ newOffset = isGoingLeft ? 100 : -100;
+ } else {
+ existingOffset = currentTransform || (isGoingLeft ? -100 : 100);
+ newOffset = 0;
+ }
- if (repeats > 0 && needsOffsetAdjustment) {
- if (!isGoingLeft) {
- if (existingOffset !== 0) {
- existingOffset += 100;
- }
- if (newOffset !== 0) {
- newOffset += 100;
- }
+ if (spaceLen === 2) {
+ if (containsPrev && !containsNew) {
+ existingOffset = currentTransform;
+ newOffset = currentTransform >= 0 ? 100 : -100;
} else {
- if (existingOffset !== 0) {
- existingOffset -= 100;
- }
- if (newOffset !== 0) {
- newOffset -= 100;
- }
+ existingOffset = currentTransform >= 0 ? 100 : -100;
+ newOffset = 0;
}
}
- // Special case: going forward from single reused container to a new one
- if (
- !usingSameContainer &&
- !isGoingLeft &&
- lastWorkspaceIndex === newWorkspaceIndex - 1
- ) {
- existingOffset = 0;
- newOffset = -100;
- stepsInBetween = 1;
- }
- if (
- !usingSameContainer &&
- isGoingLeft &&
- firstWorkspaceIndex === newWorkspaceIndex + 1
- ) {
- existingOffset = 0;
- newOffset = 100;
- stepsInBetween = 1;
- }
- if (
- !usingSameContainer &&
- isGoingLeft &&
- (firstWorkspaceIndex === newWorkspaceIndex - 1 ||
- firstWorkspaceIndex === newWorkspaceIndex)
- ) {
- existingOffset = -100;
- newOffset = 0;
- stepsInBetween = 1;
- }
- if (
- !usingSameContainer &&
- !isGoingLeft &&
- firstWorkspaceIndex === newWorkspaceIndex
- ) {
- existingOffset = 100;
- newOffset = 0;
- stepsInBetween = 1;
- }
-
const newTransform = `translateX(${newOffset}%)`;
let existingTransform = `translateX(${existingOffset}%)`;
- if (container.style.transform && container.style.transform !== "none") {
- existingTransform = container.style.transform;
- }
if (shouldAnimate) {
container.style.transform = existingTransform;
animations.push(
gZenUIManager.motion.animate(
container,
{
- transform: [
- existingTransform,
- new Array(stepsInBetween).fill(newTransform).join(","),
- ],
+ transform: [existingTransform, newTransform],
},
{
type: "spring",
@@ -2357,17 +2229,12 @@ class nsZenWorkspaces {
this.#currentSpaceSwitchContext.animations = [];
document.documentElement.removeAttribute("animating-background");
if (shouldAnimate) {
- for (const cloned of clonedEssentials) {
- cloned.container.remove();
+ for (const data of essentialsAnimData) {
+ data.element.style.removeProperty("transform");
}
this._alwaysAnimatePaddingTop = true;
this.updateTabsContainers();
}
- const essentialsContainer = this.getEssentialsSection(
- newWorkspace.containerTabId
- );
- essentialsContainer.removeAttribute("hidden");
- essentialsContainer.style.transform = "none";
gBrowser.tabContainer._invalidateCachedTabs();
gZenUIManager.tabsWrapper.style.removeProperty("scrollbar-width");
this._animatingChange = false;
@@ -3242,9 +3109,6 @@ class nsZenWorkspaces {
...normalContainers,
];
for (const container of containers) {
- if (container.hasAttribute("cloned")) {
- continue;
- }
for (const tab of container.children) {
if (gBrowser.isTab(tab)) {
tabs.push(tab);
diff --git a/src/zen/tabs/zen-tabs/vertical-tabs.css b/src/zen/tabs/zen-tabs/vertical-tabs.css
index 0e5609008..c5efa0be4 100644
--- a/src/zen/tabs/zen-tabs/vertical-tabs.css
+++ b/src/zen/tabs/zen-tabs/vertical-tabs.css
@@ -1156,10 +1156,6 @@
pointer-events: none;
position: fixed; /* Fix position to prevent scrolling */
}
-
- &[cloned] {
- pointer-events: none;
- }
}
.tabbrowser-tab[zen-essential="true"],
From 68c8fe18bcdbe046903742da8071af2ad6f322aa Mon Sep 17 00:00:00 2001
From: Andrey Bochkarev <50177704+octaviusz@users.noreply.github.com>
Date: Sun, 5 Jul 2026 18:01:20 +0300
Subject: [PATCH 005/116] gh-14242: Fix route tab doesn't change container
(gh-14482)
---
src/browser/components/tabbrowser/content/tabbrowser-js.patch | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/browser/components/tabbrowser/content/tabbrowser-js.patch b/src/browser/components/tabbrowser/content/tabbrowser-js.patch
index 006b24ec7..65a98c74d 100644
--- a/src/browser/components/tabbrowser/content/tabbrowser-js.patch
+++ b/src/browser/components/tabbrowser/content/tabbrowser-js.patch
@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
-index 08b5b56e069d038d72c87355920c4ce8a55ed805..209c0ddc0297adb8340180c58de07ae107790751 100644
+index 08b5b56e069d038d72c87355920c4ce8a55ed805..e1a3a18081c7a8153f8d12c187bf4dd522944fcb 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js
@@ -511,6 +511,7 @@
@@ -308,7 +308,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..209c0ddc0297adb8340180c58de07ae1
+
+ let hasZenDefaultUserContextId = false;
+ let zenForcedWorkspaceId = undefined;
-+ if (beforeRouteResult.isRouteFound && typeof userContextId !== "undefined") {
++ if (beforeRouteResult.isRouteFound && typeof userContextId === "undefined") {
+ userContextId = beforeRouteResult.userContextId;
+ hasZenDefaultUserContextId = true;
+ } else if (typeof gZenWorkspaces !== "undefined" && !_forZenEmptyTab) {
From 58611690b5be27aeeca237ad85439de81f38fc33 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Sun, 5 Jul 2026 20:32:03 +0200
Subject: [PATCH 006/116] gh-14470: Fixed wrong sync engine name (gh-14486)
---
src/services/sync/modules/service-sys-mjs.patch | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/services/sync/modules/service-sys-mjs.patch b/src/services/sync/modules/service-sys-mjs.patch
index 67522f59b..54a3f1cef 100644
--- a/src/services/sync/modules/service-sys-mjs.patch
+++ b/src/services/sync/modules/service-sys-mjs.patch
@@ -6,7 +6,7 @@ index c873293871ffaba305bc1bf41730d79c13546b85..0e0171cec13dfcbb296ec7bf03628370
whenTrue: "ExtensionStorageEngineKinto",
whenFalse: "ExtensionStorageEngineBridge",
};
-+ result.Workspaces = {
++ result.Spaces = {
+ module: "resource:///modules/zen/ZenWorkspacesSync.sys.mjs",
+ symbol: "ZenWorkspacesEngine",
+ };
From 5ecbc13eb6283c95899f5b01749c440b12ec0ffb Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Mon, 6 Jul 2026 09:55:03 +0200
Subject: [PATCH 007/116] gh-14470: Fixed patch export for sync modules
(gh-14490)
---
src/services/sync/modules/service-sys-mjs.patch | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/services/sync/modules/service-sys-mjs.patch b/src/services/sync/modules/service-sys-mjs.patch
index 54a3f1cef..daf57720d 100644
--- a/src/services/sync/modules/service-sys-mjs.patch
+++ b/src/services/sync/modules/service-sys-mjs.patch
@@ -1,5 +1,5 @@
diff --git a/services/sync/modules/service.sys.mjs b/services/sync/modules/service.sys.mjs
-index c873293871ffaba305bc1bf41730d79c13546b85..0e0171cec13dfcbb296ec7bf03628370ce2fa93f 100644
+index 4f4efe572909441abdd81909282936702c225621..81ab06a9414ed656b644a21b40b683f06a4f8dbf 100644
--- a/services/sync/modules/service.sys.mjs
+++ b/services/sync/modules/service.sys.mjs
@@ -99,6 +99,10 @@ function getEngineModules() {
@@ -12,4 +12,4 @@ index c873293871ffaba305bc1bf41730d79c13546b85..0e0171cec13dfcbb296ec7bf03628370
+ };
return result;
}
-
+
From 27d290c77d5525717edee20a1974cfff359ad8b1 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Tue, 7 Jul 2026 21:31:01 +0200
Subject: [PATCH 008/116] gh-14514: Sync upstream Firefox to version `152.0.5`
(gh-14513)
---
README.md | 4 ++--
build/firefox-cache/l10n-last-commit-hash | 2 +-
surfer.json | 8 ++++----
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 290101abd..d0ed8484a 100644
--- a/README.md
+++ b/README.md
@@ -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.4`! 🚀
-- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 152.0.4`!
+- [`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`!
### Contributing
diff --git a/build/firefox-cache/l10n-last-commit-hash b/build/firefox-cache/l10n-last-commit-hash
index 0d260214c..b305fc342 100644
--- a/build/firefox-cache/l10n-last-commit-hash
+++ b/build/firefox-cache/l10n-last-commit-hash
@@ -1 +1 @@
-3985a970489e32fc751e1b80abf4d5534c905e45
\ No newline at end of file
+6bd10168f81fb90f878132d87cf681200afbcd91
\ No newline at end of file
diff --git a/surfer.json b/surfer.json
index 9e421eee6..02a49383c 100644
--- a/surfer.json
+++ b/surfer.json
@@ -5,8 +5,8 @@
"binaryName": "zen",
"version": {
"product": "firefox",
- "version": "152.0.4",
- "candidate": "152.0.4",
+ "version": "152.0.5",
+ "candidate": "152.0.5",
"candidateBuild": 1
},
"buildOptions": {
@@ -20,7 +20,7 @@
"brandShortName": "Zen",
"brandFullName": "Zen Browser",
"release": {
- "displayVersion": "1.21.5b",
+ "displayVersion": "1.21.6b",
"github": {
"repo": "zen-browser/desktop"
},
@@ -54,4 +54,4 @@
"licenseType": "MPL-2.0"
},
"updateHostname": "updates.zen-browser.app"
-}
+}
\ No newline at end of file
From decd9548ddb8c57c22a33148d85cc399e3b848f2 Mon Sep 17 00:00:00 2001
From: Ashvin Jangid <142579833+ashvwinn@users.noreply.github.com>
Date: Wed, 8 Jul 2026 01:05:23 +0530
Subject: [PATCH 009/116] gh-14507: Fix essentials being added to folder
without being removed (gh-14508)
Co-authored-by: mr. m
---
src/zen/folders/ZenFolder.mjs | 3 +++
src/zen/folders/ZenFolders.mjs | 16 +++++++---------
2 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/src/zen/folders/ZenFolder.mjs b/src/zen/folders/ZenFolder.mjs
index 0d33826c6..088131fa6 100644
--- a/src/zen/folders/ZenFolder.mjs
+++ b/src/zen/folders/ZenFolder.mjs
@@ -289,6 +289,9 @@ export class nsZenFolder extends MozTabbrowserTabGroup {
addTabs(tabs) {
let tabsFromOutside = [];
for (let tab of tabs) {
+ if (tab.hasAttribute("zen-essential")) {
+ gZenPinnedTabManager.removeEssentials(tab, false);
+ }
if (tab.group !== this) {
tabsFromOutside.push(tab);
}
diff --git a/src/zen/folders/ZenFolders.mjs b/src/zen/folders/ZenFolders.mjs
index 3267d579f..bf9452114 100644
--- a/src/zen/folders/ZenFolders.mjs
+++ b/src/zen/folders/ZenFolders.mjs
@@ -622,15 +622,13 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
}
createFolder(tabs = [], options = {}) {
- const filteredTabs = tabs
- .filter(tab => !tab.hasAttribute("zen-essential"))
- .map(tab => {
- gBrowser.pinTab(tab);
- if (tab?.group?.hasAttribute("split-view-group")) {
- tab = tab.group;
- }
- return tab;
- });
+ const filteredTabs = tabs.map(tab => {
+ gBrowser.pinTab(tab);
+ if (tab?.group?.hasAttribute("split-view-group")) {
+ tab = tab.group;
+ }
+ return tab;
+ });
const workspacePinned = gZenWorkspaces.workspaceElement(
options.workspaceId
From 5e17ef46ca0e191c45a5b0fc753a9ca1300cb061 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Wed, 8 Jul 2026 20:59:48 +0200
Subject: [PATCH 010/116] gh-14521: Fixed theme settings group being visible
(gh-14524)
---
src/browser/themes/shared/preferences/zen-preferences.css | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/browser/themes/shared/preferences/zen-preferences.css b/src/browser/themes/shared/preferences/zen-preferences.css
index 852e7be0f..fe659afca 100644
--- a/src/browser/themes/shared/preferences/zen-preferences.css
+++ b/src/browser/themes/shared/preferences/zen-preferences.css
@@ -484,11 +484,10 @@ groupbox h2 {
#helpButton,
#support-firefox,
#tabGroupSuggestions,
-#web-appearance-manage-themes-link,
#setting-control-sidebarChatbotFieldset,
#setting-control-supportFirefox,
.mission-message,
-html|setting-group:is([data-subcategory="layout"], [groupid="support"]) {
+html|setting-group:is([data-subcategory="layout"], [groupid="support"], [groupid="browserTheme"]) {
display: none !important;
}
From 03f251ff4ac6101f20d56bc71bd9c80f5381ffb9 Mon Sep 17 00:00:00 2001
From: Andrey Bochkarev <50177704+octaviusz@users.noreply.github.com>
Date: Thu, 9 Jul 2026 02:36:51 +0300
Subject: [PATCH 011/116] gh-13870: Remove two spaces cycling (gh-14529)
---
src/zen/spaces/ZenSpaceManager.mjs | 22 ----------------------
1 file changed, 22 deletions(-)
diff --git a/src/zen/spaces/ZenSpaceManager.mjs b/src/zen/spaces/ZenSpaceManager.mjs
index 6ae2e4abe..0a3d8ca1c 100644
--- a/src/zen/spaces/ZenSpaceManager.mjs
+++ b/src/zen/spaces/ZenSpaceManager.mjs
@@ -1864,11 +1864,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 +2094,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 +2170,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) {
From 61e079014451b48f3a5dea451ac8c890e6277c42 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kristijan=20Ribari=C4=87?=
<96492763+kristijanribaric@users.noreply.github.com>
Date: Thu, 9 Jul 2026 01:37:00 +0200
Subject: [PATCH 012/116] gh-14470: Rename all occurances of Workspaces to
Spaces in sync (gh-14528)
---
src/zen/sync/ZenWorkspacesSync.sys.mjs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/zen/sync/ZenWorkspacesSync.sys.mjs b/src/zen/sync/ZenWorkspacesSync.sys.mjs
index 175319169..0b0946ed4 100644
--- a/src/zen/sync/ZenWorkspacesSync.sys.mjs
+++ b/src/zen/sync/ZenWorkspacesSync.sys.mjs
@@ -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() {
From fe0cf3601ada6958defe7411d873861302124c91 Mon Sep 17 00:00:00 2001
From: Ashvin Jangid <142579833+ashvwinn@users.noreply.github.com>
Date: Sat, 11 Jul 2026 16:15:20 +0530
Subject: [PATCH 013/116] 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
---
src/zen/spaces/ZenSpaceManager.mjs | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/zen/spaces/ZenSpaceManager.mjs b/src/zen/spaces/ZenSpaceManager.mjs
index 0a3d8ca1c..91944c83e 100644
--- a/src/zen/spaces/ZenSpaceManager.mjs
+++ b/src/zen/spaces/ZenSpaceManager.mjs
@@ -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;
}
From 274b9f9f83f12b5ee7f0329bf3acaa989db65181 Mon Sep 17 00:00:00 2001
From: Nicholas Leigh
Date: Sat, 11 Jul 2026 04:38:05 -0700
Subject: [PATCH 014/116] gh-14495: Fix misaligned symbolic space icons in
Space options menu on Linux (gh-14498)
---
src/zen/spaces/ZenSpaceManager.mjs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/zen/spaces/ZenSpaceManager.mjs b/src/zen/spaces/ZenSpaceManager.mjs
index 91944c83e..0c7c6b19f 100644
--- a/src/zen/spaces/ZenSpaceManager.mjs
+++ b/src/zen/spaces/ZenSpaceManager.mjs
@@ -1164,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");
}
From a1a2268f9bf6dbdc47c5f18c43a9ccc5a1b0a694 Mon Sep 17 00:00:00 2001
From: Andrey Bochkarev <50177704+octaviusz@users.noreply.github.com>
Date: Sun, 12 Jul 2026 13:57:45 +0300
Subject: [PATCH 015/116] gh-14511: Prevent switching spaces when dragging
outside (gh-14515)
fixed: https://github.com/zen-browser/desktop/issues/14511
---
src/zen/drag-and-drop/ZenDragAndDrop.js | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/zen/drag-and-drop/ZenDragAndDrop.js b/src/zen/drag-and-drop/ZenDragAndDrop.js
index 67cf9a238..8443eddfa 100644
--- a/src/zen/drag-and-drop/ZenDragAndDrop.js
+++ b/src/zen/drag-and-drop/ZenDragAndDrop.js
@@ -69,6 +69,7 @@
#changeSpaceTimer = null;
#isAnimatingTabMove = false;
#firstHapticFeedbackPlayed = false;
+ #lastDragOverTime = 0;
#dragOverSplit = {};
@@ -723,9 +724,11 @@
const { isNearLeftEdge, isNearRightEdge } =
this.#shouldSwitchSpace(event);
if (isNearLeftEdge || isNearRightEdge) {
- if (!this.#changeSpaceTimer && !this.#isOutOfWindow) {
+ this.#lastDragOverTime = Date.now();
+ if (!this.#changeSpaceTimer) {
this.#changeSpaceTimer = setTimeout(() => {
- if (this.#isOutOfWindow) {
+ const timeSinceLastDragOver = Date.now() - this.#lastDragOverTime;
+ if (timeSinceLastDragOver > 250) {
return;
}
this.clearDragOverVisuals();
From e837158e4cc6860a49e4c7924fc92cddefcfe0c2 Mon Sep 17 00:00:00 2001
From: Andrey Bochkarev <50177704+octaviusz@users.noreply.github.com>
Date: Sun, 12 Jul 2026 18:29:29 +0300
Subject: [PATCH 016/116] gh-14545: Correctly handle dnd moving a tab to an
empty space (gh-14562)
---
src/zen/drag-and-drop/ZenDragAndDrop.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/zen/drag-and-drop/ZenDragAndDrop.js b/src/zen/drag-and-drop/ZenDragAndDrop.js
index 8443eddfa..266ffbf35 100644
--- a/src/zen/drag-and-drop/ZenDragAndDrop.js
+++ b/src/zen/drag-and-drop/ZenDragAndDrop.js
@@ -1336,7 +1336,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];
From c7e4217204d981125d18bab7bb4a45c9ba9c2557 Mon Sep 17 00:00:00 2001
From: Andrey Bochkarev <50177704+octaviusz@users.noreply.github.com>
Date: Mon, 13 Jul 2026 14:40:43 +0300
Subject: [PATCH 017/116] 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.
---
.../components/tabbrowser/content/tabbrowser-js.patch | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/browser/components/tabbrowser/content/tabbrowser-js.patch b/src/browser/components/tabbrowser/content/tabbrowser-js.patch
index 65a98c74d..3306027ee 100644
--- a/src/browser/components/tabbrowser/content/tabbrowser-js.patch
+++ b/src/browser/components/tabbrowser/content/tabbrowser-js.patch
@@ -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
From 529755c48aae8987c461fcaf5195c94e4a3872a9 Mon Sep 17 00:00:00 2001
From: Andrey Bochkarev <50177704+octaviusz@users.noreply.github.com>
Date: Tue, 14 Jul 2026 20:07:08 +0300
Subject: [PATCH 018/116] gh-14575: Tab order flips when moving tabs between
workspaces (gh-14581)
---
src/zen/spaces/ZenSpaceManager.mjs | 33 ++++++++++++++++--------------
1 file changed, 18 insertions(+), 15 deletions(-)
diff --git a/src/zen/spaces/ZenSpaceManager.mjs b/src/zen/spaces/ZenSpaceManager.mjs
index 0c7c6b19f..78d990899 100644
--- a/src/zen/spaces/ZenSpaceManager.mjs
+++ b/src/zen/spaces/ZenSpaceManager.mjs
@@ -1589,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
@@ -1603,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) {
@@ -1624,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]");
@@ -1696,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);
}
@@ -2473,7 +2482,9 @@ 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) {
ctxCommand.setAttribute("disabled", "true");
@@ -2936,15 +2947,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]);
From 31357dfe9262818d674802878c6308df8870e81f Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Wed, 15 Jul 2026 10:31:58 +0200
Subject: [PATCH 019/116] gh-14594: Sync upstream Firefox to version `152.0.6`
(gh-14585)
---
README.md | 4 ++--
build/firefox-cache/l10n-last-commit-hash | 2 +-
surfer.json | 6 +++---
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index d0ed8484a..78c5ab935 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/build/firefox-cache/l10n-last-commit-hash b/build/firefox-cache/l10n-last-commit-hash
index b305fc342..2c5d209bf 100644
--- a/build/firefox-cache/l10n-last-commit-hash
+++ b/build/firefox-cache/l10n-last-commit-hash
@@ -1 +1 @@
-6bd10168f81fb90f878132d87cf681200afbcd91
\ No newline at end of file
+e7fbb446077536139cdd78b6a0386a97237af20f
\ No newline at end of file
diff --git a/surfer.json b/surfer.json
index 02a49383c..5354992d2 100644
--- a/surfer.json
+++ b/surfer.json
@@ -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.7b",
"github": {
"repo": "zen-browser/desktop"
},
From fcda16bfe6a29be75aca06be91d31b8265c38918 Mon Sep 17 00:00:00 2001
From: Andrey Bochkarev <50177704+octaviusz@users.noreply.github.com>
Date: Wed, 15 Jul 2026 11:52:15 +0300
Subject: [PATCH 020/116] gh-14511: Refactor windowDragLeave handler and
related logic (gh-14566)
---
src/zen/drag-and-drop/ZenDragAndDrop.js | 102 ++++++++++--------------
src/zen/tabs/ZenPinnedTabManager.mjs | 10 +++
2 files changed, 54 insertions(+), 58 deletions(-)
diff --git a/src/zen/drag-and-drop/ZenDragAndDrop.js b/src/zen/drag-and-drop/ZenDragAndDrop.js
index 266ffbf35..f4a4e17ee 100644
--- a/src/zen/drag-and-drop/ZenDragAndDrop.js
+++ b/src/zen/drag-and-drop/ZenDragAndDrop.js
@@ -69,7 +69,6 @@
#changeSpaceTimer = null;
#isAnimatingTabMove = false;
#firstHapticFeedbackPlayed = false;
- #lastDragOverTime = 0;
#dragOverSplit = {};
@@ -662,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
@@ -716,22 +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) {
- this.#lastDragOverTime = Date.now();
- if (!this.#changeSpaceTimer) {
+ if (!this.#changeSpaceTimer && !this.#isOutOfWindow) {
this.#changeSpaceTimer = setTimeout(() => {
- const timeSinceLastDragOver = Date.now() - this.#lastDragOverTime;
- if (timeSinceLastDragOver > 250) {
- return;
- }
this.clearDragOverVisuals();
+ this.#maybeClearVerticalPinnedGridDragOver();
gZenWorkspaces
.changeWorkspaceShortcut(
isNearLeftEdge ? -1 : 1,
@@ -899,6 +897,10 @@
}
handle_windowDragLeave(event) {
+ // If relatedTarget exists, then we are still in the window
+ if (event.relatedTarget) {
+ return;
+ }
const canvas = this._tabbrowserTabs._dndCanvas;
if (!this.#isMovingTab() || !canvas) {
return;
@@ -907,51 +909,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) {
@@ -1481,10 +1470,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();
diff --git a/src/zen/tabs/ZenPinnedTabManager.mjs b/src/zen/tabs/ZenPinnedTabManager.mjs
index 4a441f8b2..b75224925 100644
--- a/src/zen/tabs/ZenPinnedTabManager.mjs
+++ b/src/zen/tabs/ZenPinnedTabManager.mjs
@@ -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")
From 425f0ae1c392d44cfab9e43312fb6e854285c4e5 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Thu, 16 Jul 2026 10:02:53 +0200
Subject: [PATCH 021/116] gh-14605: Bump release versions (gh-14608)
---
surfer.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/surfer.json b/surfer.json
index 5354992d2..74a473e8c 100644
--- a/surfer.json
+++ b/surfer.json
@@ -20,7 +20,7 @@
"brandShortName": "Zen",
"brandFullName": "Zen Browser",
"release": {
- "displayVersion": "1.21.7b",
+ "displayVersion": "1.21.8b",
"github": {
"repo": "zen-browser/desktop"
},
From 247cde9c0957788aaf5c0a2ec017173fa479c314 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Thu, 16 Jul 2026 22:28:09 +0200
Subject: [PATCH 022/116] gh-14618: Fixed workspaces not being able to be
removed (gh-14620)
---
src/zen/spaces/ZenSpaceManager.mjs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/zen/spaces/ZenSpaceManager.mjs b/src/zen/spaces/ZenSpaceManager.mjs
index 78d990899..71bbd6517 100644
--- a/src/zen/spaces/ZenSpaceManager.mjs
+++ b/src/zen/spaces/ZenSpaceManager.mjs
@@ -2486,7 +2486,8 @@ class nsZenWorkspaces {
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");
@@ -2506,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) {
From a791c5dadcfb8fd5f5ebd1d0f627b8903ca6c703 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Thu, 16 Jul 2026 22:32:30 +0200
Subject: [PATCH 023/116] gh-14619: Fixed split view drag opening new windows
(gh-14621)
---
src/zen/drag-and-drop/ZenDragAndDrop.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/zen/drag-and-drop/ZenDragAndDrop.js b/src/zen/drag-and-drop/ZenDragAndDrop.js
index f4a4e17ee..7db5ae22e 100644
--- a/src/zen/drag-and-drop/ZenDragAndDrop.js
+++ b/src/zen/drag-and-drop/ZenDragAndDrop.js
@@ -898,7 +898,10 @@
handle_windowDragLeave(event) {
// If relatedTarget exists, then we are still in the window
- if (event.relatedTarget) {
+ if (
+ event.relatedTarget ||
+ event.target.id === "zen-split-view-fake-browser"
+ ) {
return;
}
const canvas = this._tabbrowserTabs._dndCanvas;
From 278580ea43a36fc9f54b21248f839844c0bca577 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Fri, 17 Jul 2026 17:08:15 +0200
Subject: [PATCH 024/116] gh-14616: Fixed add to essentials being disabled
(gh-14631)
---
src/zen/tabs/ZenPinnedTabManager.mjs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/zen/tabs/ZenPinnedTabManager.mjs b/src/zen/tabs/ZenPinnedTabManager.mjs
index b75224925..40f25ab85 100644
--- a/src/zen/tabs/ZenPinnedTabManager.mjs
+++ b/src/zen/tabs/ZenPinnedTabManager.mjs
@@ -837,7 +837,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
if (essentialTabsTarget) {
if (gZenWorkspaces.containerSpecificEssentials) {
const targetContainerId =
- gZenWorkspaces.getActiveWorkspaceFromCache().containerTabId;
+ gZenWorkspaces.getActiveWorkspaceFromCache().containerTabId || 0;
const sameContextId =
(tab.getAttribute("usercontextid") || 0) == targetContainerId;
if (!sameContextId && tab.hasAttribute("zen-essential")) {
@@ -1022,7 +1022,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
return (
!(
(tab.getAttribute("usercontextid") || 0) !=
- gZenWorkspaces.getActiveWorkspaceFromCache().containerTabId &&
+ (gZenWorkspaces.getActiveWorkspaceFromCache().containerTabId || 0) &&
gZenWorkspaces.containerSpecificEssentials
) && gBrowser._numZenEssentials < this.maxEssentialTabs
);
From 323a3ab1ff6b74436f9f3c341a5c0f48b7eb82a4 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Sat, 18 Jul 2026 15:31:46 +0200
Subject: [PATCH 025/116] gh-14600: Fixed external links not being routed
properly (gh-14637)
---
src/browser/components/tabbrowser/content/tabbrowser-js.patch | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/browser/components/tabbrowser/content/tabbrowser-js.patch b/src/browser/components/tabbrowser/content/tabbrowser-js.patch
index 3306027ee..a696d2299 100644
--- a/src/browser/components/tabbrowser/content/tabbrowser-js.patch
+++ b/src/browser/components/tabbrowser/content/tabbrowser-js.patch
@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
-index 08b5b56e069d038d72c87355920c4ce8a55ed805..0e4f57c21d28ccf173d8d1e13e96c2c1f8a79f34 100644
+index 08b5b56e069d038d72c87355920c4ce8a55ed805..515035c4c65e96982473605a150e4b0b79604f6d 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js
@@ -511,6 +511,7 @@
@@ -308,7 +308,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..0e4f57c21d28ccf173d8d1e13e96c2c1
+
+ let hasZenDefaultUserContextId = false;
+ let zenForcedWorkspaceId = undefined;
-+ if (beforeRouteResult.isRouteFound && typeof userContextId === "undefined") {
++ if (beforeRouteResult.isRouteFound && (typeof userContextId === "undefined" || fromExternal)) {
+ userContextId = beforeRouteResult.userContextId;
+ hasZenDefaultUserContextId = true;
+ } else if (typeof gZenWorkspaces !== "undefined" && !_forZenEmptyTab) {
From 55c224bea775a7ae4099c75b30b6f2be34589072 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Sat, 18 Jul 2026 19:23:15 +0200
Subject: [PATCH 026/116] gh-14424: Prevent startup hangs if no sibling exists
(gh-14639)
---
src/zen/folders/ZenFolders.mjs | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/zen/folders/ZenFolders.mjs b/src/zen/folders/ZenFolders.mjs
index bf9452114..653beb6e9 100644
--- a/src/zen/folders/ZenFolders.mjs
+++ b/src/zen/folders/ZenFolders.mjs
@@ -1297,8 +1297,17 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
}
default: {
// Should insert after zen-empty-tab
- const start =
+ let start =
parentWorkingData.node.groupStartElement.nextElementSibling;
+ start ||=
+ parentWorkingData.node.tabs[0] ||
+ parentWorkingData.node.groupStartElement;
+ if (!start) {
+ console.error(
+ `Zen Folders: Could not find start element for parent folder with id ${stateData.parentId} while restoring session.`
+ );
+ break;
+ }
start.after(node);
}
}
From 1b41af52d37122f619614a1b65b3ecef525c82fe Mon Sep 17 00:00:00 2001
From: Andrey Bochkarev <50177704+octaviusz@users.noreply.github.com>
Date: Sun, 19 Jul 2026 16:20:20 +0300
Subject: [PATCH 027/116] gh-14428: Resolve wrong essentials padding when
cleanup (gh-14642)
---
src/zen/spaces/ZenSpaceCreation.mjs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/zen/spaces/ZenSpaceCreation.mjs b/src/zen/spaces/ZenSpaceCreation.mjs
index 52045f64a..5033ce650 100644
--- a/src/zen/spaces/ZenSpaceCreation.mjs
+++ b/src/zen/spaces/ZenSpaceCreation.mjs
@@ -370,7 +370,6 @@ class nsZenWorkspaceCreation extends MozXULElement {
}
this.remove();
- gZenUIManager.updateTabsToolbar();
const workspace = gZenWorkspaces.getActiveWorkspace();
gZenWorkspaces._organizeWorkspaceStripLocations(workspace);
@@ -398,6 +397,7 @@ class nsZenWorkspaceCreation extends MozXULElement {
}
this.#hiddenElements = [];
+ gZenUIManager.updateTabsToolbar();
}
}
From e21e5ed16dd2390cee104e38df8b23d4c1d4d06d Mon Sep 17 00:00:00 2001
From: Ashvin Jangid <142579833+ashvwinn@users.noreply.github.com>
Date: Tue, 21 Jul 2026 04:41:54 +0530
Subject: [PATCH 028/116] gh-14461: Fix boost size override not persisting
(gh-14664)
---
src/zen/boosts/ZenBoostsEditor.mjs | 5 ++++-
src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 21 +++---------------
src/zen/boosts/actors/ZenBoostsParent.sys.mjs | 22 +++++++++++++++++++
3 files changed, 29 insertions(+), 19 deletions(-)
diff --git a/src/zen/boosts/ZenBoostsEditor.mjs b/src/zen/boosts/ZenBoostsEditor.mjs
index f67ad0fa3..2f37a99a5 100644
--- a/src/zen/boosts/ZenBoostsEditor.mjs
+++ b/src/zen/boosts/ZenBoostsEditor.mjs
@@ -659,7 +659,10 @@ ${cssSelector} {
this.currentBoostData.sizeOverride = 0.9;
} else if (this.currentBoostData.sizeOverride == 0.9) {
this.currentBoostData.sizeOverride = 1;
- await this.zenBoostsChild.sendQuery("ZenBoost:DisableSizeOverride");
+ await this.zenBoostsChild.updateBoostSizeOverride(
+ this.currentBoostData.sizeOverride,
+ /* disable: */ true
+ );
}
this.currentBoostData.changeWasMade = true;
diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs
index 13551682f..364490698 100644
--- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs
+++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs
@@ -280,9 +280,6 @@ export class ZenBoostsChild extends JSWindowActorChild {
case "ZenBoost:OpenInspector":
this.sendAsyncMessage("ZenBoost:OpenInspector");
break;
- case "ZenBoost:DisableSizeOverride":
- this.disableSizeOverride();
- break;
}
return null;
}
@@ -359,13 +356,9 @@ export class ZenBoostsChild extends JSWindowActorChild {
this.#loadStyleSheet(boost.styleSheet);
}
- if (
- boostData.sizeOverride &&
- isFinite(boostData.sizeOverride) &&
- boostData.sizeOverride !== 1
- ) {
- browsingContext.fullZoom = boostData.sizeOverride;
- }
+ this.sendAsyncMessage("ZenBoost:UpdateBoostSize", {
+ sizeOverride: boostData.sizeOverride,
+ });
browsingContext.isZenBoostsInverted = boostData.smartInvert;
if (boostData.enableColorBoost) {
@@ -552,14 +545,6 @@ export class ZenBoostsChild extends JSWindowActorChild {
this.sendNotify("selector-picker-state-update", "ondisable");
}
- disableSizeOverride() {
- const browsingContext = this.browsingContext;
- if (!browsingContext || browsingContext.parent !== null) {
- return;
- }
- browsingContext.fullZoom = 1;
- }
-
sendNotify(topic, msg = null) {
this.sendAsyncMessage("ZenBoost:Notify", { topic, msg });
}
diff --git a/src/zen/boosts/actors/ZenBoostsParent.sys.mjs b/src/zen/boosts/actors/ZenBoostsParent.sys.mjs
index 50e251955..0cffb5a73 100644
--- a/src/zen/boosts/actors/ZenBoostsParent.sys.mjs
+++ b/src/zen/boosts/actors/ZenBoostsParent.sys.mjs
@@ -72,6 +72,23 @@ export class ZenBoostsParent extends JSWindowActorParent {
}
}
+ /**
+ * Updates the boost size override on webpage
+ *
+ * @param {number} sizeOverride - The size to apply on the webpage as a override
+ * @param {boolean} disable - Whether to disable the override (sets override to the default 1)
+ */
+ updateBoostSizeOverride(sizeOverride, disable = false) {
+ if (
+ sizeOverride &&
+ isFinite(sizeOverride) &&
+ (sizeOverride !== 1 || disable)
+ ) {
+ const fullZoom = this.browsingContext.topChromeWindow.FullZoom;
+ fullZoom.setZoom(sizeOverride);
+ }
+ }
+
/**
* Handles messages received from child actors.
* Retrieves boost data for a domain when requested.
@@ -174,6 +191,11 @@ export class ZenBoostsParent extends JSWindowActorParent {
: null,
};
}
+ case "ZenBoost:UpdateBoostSize": {
+ const { sizeOverride } = message.data;
+ this.updateBoostSizeOverride(sizeOverride);
+ break;
+ }
default: {
console.warn(`[ZenBoostsParent]: Unknown message: ${message.name}`);
break;
From b4563ae65b08254b10755de5dff500c4ed6b9143 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Tue, 21 Jul 2026 14:47:07 +0200
Subject: [PATCH 029/116] gh-14651: Sync upstream Firefox to version `153.0`
(gh-14652)
---
README.md | 4 +-
build/firefox-cache/l10n-last-commit-hash | 2 +-
src/-stylelintrc-js.patch | 8 +-
.../base/content/browser-box-inc-xhtml.patch | 4 +-
.../base/content/navigator-toolbox-js.patch | 8 +-
.../ui/modules/AIWindow-sys-mjs.patch | 8 +-
.../extensions/parent/ext-browser-js.patch | 10 +-
.../extensions/parent/ext-tabs-js.patch | 8 +-
.../components/nova/NovaPrefs-sys-mjs.patch | 13 -
.../places/PlacesUIUtils-sys-mjs.patch | 11 +-
.../components/preferences/main-js.patch | 12 +-
.../preferences/preferences-js.patch | 14 +-
.../preferences/preferences-xhtml.patch | 10 +-
.../tabbrowser/content/tabbrowser-js.patch | 224 +-
.../UrlbarProvidersManager-sys-mjs.patch | 18 +-
.../urlbar/UrlbarResult-sys-mjs.patch | 15 -
.../urlbar/UrlbarTokenizer-sys-mjs.patch | 28 -
.../urlbar/UrlbarUtils-sys-mjs.patch | 2 +-
.../urlbar/UrlbarValueFormatter-sys-mjs.patch | 22 +-
.../urlbar/content/UrlbarInput-mjs.patch | 56 +-
.../urlbar/content/UrlbarResult-mjs.patch | 15 +
.../urlbar/content/UrlbarShared-mjs.patch | 28 +
.../UrlbarView-mjs.patch} | 16 +-
.../identity-block/identity-block-css.patch | 16 +-
src/browser/themes/shared/jar-inc-mn.patch | 7 +-
.../shared/tabbrowser/content-area-css.patch | 12 +-
.../themes/shared/tabbrowser/tabs-css.patch | 42 +-
.../themes/shared/urlbar-searchbar-css.patch | 34 +-
.../themes/shared/urlbar/variables-css.patch | 13 -
src/docshell/base/BrowsingContext-h.patch | 18 +-
.../MediaController-webidl.patch | 7 +-
src/eslint-file-globals-config-mjs.patch | 8 +-
..._urlbar_closeonwindowblur_preference.patch | 6 +-
src/layout/base/nsPresContext-h.patch | 10 +-
src/layout/generic/nsTextPaintStyle-cpp.patch | 16 +-
src/layout/style/StyleColor-cpp.patch | 36 +-
src/layout/svg/SVGImageContext-cpp.patch | 8 +-
src/layout/svg/SVGImageContext-h.patch | 25 +-
src/servo/ports/geckolib/cbindgen-toml.patch | 8 +-
.../extensions/content/aboutaddons-css.patch | 17 +-
.../dist/tokens-shared-css.patch | 12 +-
.../shared/in-content/common-shared-css.patch | 8 +-
src/zen/@types/lib.gecko.darwin.d.ts | 33 +-
src/zen/@types/lib.gecko.dom.d.ts | 881 ++++++-
src/zen/@types/lib.gecko.glean.d.ts | 874 +++++--
src/zen/@types/lib.gecko.linux.d.ts | 13 +-
src/zen/@types/lib.gecko.modules.d.ts | 147 +-
src/zen/@types/lib.gecko.nsresult.d.ts | 11 +-
src/zen/@types/lib.gecko.services.d.ts | 3 -
src/zen/@types/lib.gecko.tweaks.d.ts | 3 -
src/zen/@types/lib.gecko.win32.d.ts | 49 +-
src/zen/@types/lib.gecko.xpcom.d.ts | 2163 +++++++++--------
src/zen/@types/lib.gecko.xpidl.d.ts | 13 +-
src/zen/mods/ZenStyleSheetCache.cpp | 12 +-
src/zen/mods/ZenStyleSheetCache.h | 3 +-
src/zen/urlbar/ZenUBActionsProvider.sys.mjs | 7 +-
surfer.json | 4 +-
57 files changed, 3225 insertions(+), 1830 deletions(-)
delete mode 100644 src/browser/components/nova/NovaPrefs-sys-mjs.patch
delete mode 100644 src/browser/components/urlbar/UrlbarResult-sys-mjs.patch
delete mode 100644 src/browser/components/urlbar/UrlbarTokenizer-sys-mjs.patch
create mode 100644 src/browser/components/urlbar/content/UrlbarResult-mjs.patch
create mode 100644 src/browser/components/urlbar/content/UrlbarShared-mjs.patch
rename src/browser/components/urlbar/{UrlbarView-sys-mjs.patch => content/UrlbarView-mjs.patch} (72%)
delete mode 100644 src/browser/themes/shared/urlbar/variables-css.patch
diff --git a/README.md b/README.md
index 78c5ab935..17eea24ba 100644
--- a/README.md
+++ b/README.md
@@ -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.6`! 🚀
-- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 152.0.6`!
+- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `153.0`! 🚀
+- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 153.0`!
### Contributing
diff --git a/build/firefox-cache/l10n-last-commit-hash b/build/firefox-cache/l10n-last-commit-hash
index 2c5d209bf..4e1b64816 100644
--- a/build/firefox-cache/l10n-last-commit-hash
+++ b/build/firefox-cache/l10n-last-commit-hash
@@ -1 +1 @@
-e7fbb446077536139cdd78b6a0386a97237af20f
\ No newline at end of file
+b1d3fe8f65751e6d695fa9ef94b11ad81e70dac1
\ No newline at end of file
diff --git a/src/-stylelintrc-js.patch b/src/-stylelintrc-js.patch
index 2454e3824..baadd6208 100644
--- a/src/-stylelintrc-js.patch
+++ b/src/-stylelintrc-js.patch
@@ -1,5 +1,5 @@
diff --git a/.stylelintrc.js b/.stylelintrc.js
-index 3c9fecf731126fdbf900d1bdcd3635dd31ed53ef..c3a210b8153e9699c6cbdc0d568bb72433976b2c 100644
+index b484bf6600a7e1b8ad2aed8c8b7fe5e84cc3d023..f89712e2c13dfa0bcb48e55cd3872c67f5c98dad 100644
--- a/.stylelintrc.js
+++ b/.stylelintrc.js
@@ -67,7 +67,7 @@ module.exports = {
@@ -11,9 +11,9 @@ index 3c9fecf731126fdbf900d1bdcd3635dd31ed53ef..c3a210b8153e9699c6cbdc0d568bb724
{
ignore: ["blockless-at-rules"],
},
-@@ -274,7 +274,7 @@ module.exports = {
- // Remove this line setting `csscontrols/use-logical` to null after implementing fixes
- "csstools/use-logical": null,
+@@ -280,7 +280,7 @@ module.exports = {
+ "media-query-no-invalid": null,
+ "stylelint-plugin-mozilla/media-query-no-invalid": true,
"stylelint-plugin-mozilla/no-base-design-tokens": true,
- "stylelint-plugin-mozilla/use-design-tokens": true,
+ "stylelint-plugin-mozilla/use-design-tokens": false,
diff --git a/src/browser/base/content/browser-box-inc-xhtml.patch b/src/browser/base/content/browser-box-inc-xhtml.patch
index e807e8553..a45ec45a0 100644
--- a/src/browser/base/content/browser-box-inc-xhtml.patch
+++ b/src/browser/base/content/browser-box-inc-xhtml.patch
@@ -1,5 +1,5 @@
diff --git a/browser/base/content/browser-box.inc.xhtml b/browser/base/content/browser-box.inc.xhtml
-index 31cd4f927c273573b38021f84417101c57377902..f293e1c61d3b7a80b7dc472d927893f0439d6af9 100644
+index 4a16fe93daeefb88af024c1dac05bc1f518c3fcb..a420768e4047937c8becefb69c1017e13fc12231 100644
--- a/browser/base/content/browser-box.inc.xhtml
+++ b/browser/base/content/browser-box.inc.xhtml
@@ -3,12 +3,22 @@
@@ -10,7 +10,7 @@ index 31cd4f927c273573b38021f84417101c57377902..f293e1c61d3b7a80b7dc472d927893f0
+
+
+
-
+