From 84a54c9dbbfb23ec935dd542fb14cd6c5baa7a45 Mon Sep 17 00:00:00 2001
From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com>
Date: Thu, 5 Feb 2026 11:05:37 +0100
Subject: [PATCH] feat: Add option to only sync up pinned tabs, b=closes
#12173, p=#12246, c=common, tabs
---
.../browser/preferences/zen-preferences.ftl | 3 ++
prefs/zen/window-sync.yaml | 3 ++
.../preferences/zenTabsManagement.inc.xhtml | 3 ++
src/zen/common/sys/ZenUIMigration.sys.mjs | 6 ++-
.../sessionstore/ZenSessionManager.sys.mjs | 17 ++++++--
src/zen/sessionstore/ZenWindowSync.sys.mjs | 41 ++++++++++++++++---
src/zen/tabs/ZenPinnedTabManager.mjs | 3 --
7 files changed, 63 insertions(+), 13 deletions(-)
diff --git a/locales/en-US/browser/browser/preferences/zen-preferences.ftl b/locales/en-US/browser/browser/preferences/zen-preferences.ftl
index 6eea5c8a5..7b304613c 100644
--- a/locales/en-US/browser/browser/preferences/zen-preferences.ftl
+++ b/locales/en-US/browser/browser/preferences/zen-preferences.ftl
@@ -59,6 +59,9 @@ zen-tabs-select-recently-used-on-close =
zen-tabs-close-on-back-with-no-history =
.label = Close tab and switch to its owner tab (or most recently used tab) when going back with no history
+zen-settings-workspaces-sync-unpinned-tabs =
+ .label = Sync only pinned tabs in workspaces
+
zen-tabs-cycle-by-attribute =
.label = Ctrl+Tab cycles within Essential or Workspace tabs only
zen-tabs-cycle-ignore-pending-tabs =
diff --git a/prefs/zen/window-sync.yaml b/prefs/zen/window-sync.yaml
index 93b6fc95f..d19b95f5b 100644
--- a/prefs/zen/window-sync.yaml
+++ b/prefs/zen/window-sync.yaml
@@ -13,3 +13,6 @@
- name: zen.window-sync.open-link-in-new-unsynced-window
value: true
+
+- name: zen.window-sync.sync-only-pinned-tabs
+ value: false
diff --git a/src/browser/components/preferences/zenTabsManagement.inc.xhtml b/src/browser/components/preferences/zenTabsManagement.inc.xhtml
index 037dcd88c..2cf7c9c9d 100644
--- a/src/browser/components/preferences/zenTabsManagement.inc.xhtml
+++ b/src/browser/components/preferences/zenTabsManagement.inc.xhtml
@@ -20,6 +20,9 @@
+
diff --git a/src/zen/common/sys/ZenUIMigration.sys.mjs b/src/zen/common/sys/ZenUIMigration.sys.mjs
index 1c6cac68e..1d6a8a7a7 100644
--- a/src/zen/common/sys/ZenUIMigration.sys.mjs
+++ b/src/zen/common/sys/ZenUIMigration.sys.mjs
@@ -12,7 +12,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
class nsZenUIMigration {
PREF_NAME = "zen.ui.migration.version";
- MIGRATION_VERSION = 6;
+ MIGRATION_VERSION = 7;
init(isNewProfile) {
if (!isNewProfile) {
@@ -127,6 +127,10 @@ class nsZenUIMigration {
}, 1000);
});
}
+
+ _migrateV7() {
+ Services.prefs.setBoolPref("zen.window-sync.sync-only-pinned-tabs", true);
+ }
}
export var gZenUIMigration = new nsZenUIMigration();
diff --git a/src/zen/sessionstore/ZenSessionManager.sys.mjs b/src/zen/sessionstore/ZenSessionManager.sys.mjs
index e965fd984..c812ffd07 100644
--- a/src/zen/sessionstore/ZenSessionManager.sys.mjs
+++ b/src/zen/sessionstore/ZenSessionManager.sys.mjs
@@ -11,6 +11,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
gWindowSyncEnabled: "resource:///modules/zen/ZenWindowSync.sys.mjs",
+ gSyncOnlyPinnedTabs: "resource:///modules/zen/ZenWindowSync.sys.mjs",
DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
});
@@ -562,7 +563,17 @@ export class nsZenSessionManager {
if (!sidebar) {
return;
}
- aWindowData.tabs = sidebar.tabs || [];
+ // If we should only sync the pinned tabs, we should only edit the unpinned
+ // tabs in the window data and keep the pinned tabs from the window data,
+ // as they should be the same as the ones in the sidebar.
+ if (lazy.gSyncOnlyPinnedTabs) {
+ let pinnedTabs = (sidebar.tabs || []).filter((tab) => tab.pinned);
+ let unpinedWindowTabs = (aWindowData.tabs || []).filter((tab) => !tab.pinned);
+ aWindowData.tabs = [...pinnedTabs, ...unpinedWindowTabs];
+ } else {
+ aWindowData.tabs = sidebar.tabs || [];
+ }
+
aWindowData.splitViewData = sidebar.splitViewData;
aWindowData.folders = sidebar.folders;
aWindowData.groups = sidebar.groups;
@@ -599,8 +610,8 @@ export class nsZenSessionManager {
this.#restoreWindowData(newWindow);
}
newWindow.tabs = this.#filterUnusedTabs(newWindow.tabs || []);
- if (!lazy.gWindowSyncEnabled) {
- // Don't bring over any unpinned tabs if window sync is disabled.
+ if (!lazy.gWindowSyncEnabled || lazy.gSyncOnlyPinnedTabs) {
+ // Don't bring over any unpinned tabs if window sync is disabled or if syncing only pinned tabs.
newWindow.tabs = newWindow.tabs.filter((tab) => tab.pinned);
}
diff --git a/src/zen/sessionstore/ZenWindowSync.sys.mjs b/src/zen/sessionstore/ZenWindowSync.sys.mjs
index cb6c2094a..fd837cf6e 100644
--- a/src/zen/sessionstore/ZenWindowSync.sys.mjs
+++ b/src/zen/sessionstore/ZenWindowSync.sys.mjs
@@ -19,6 +19,12 @@ ChromeUtils.defineESModuleGetters(lazy, {
});
XPCOMUtils.defineLazyPreferenceGetter(lazy, "gWindowSyncEnabled", "zen.window-sync.enabled", true);
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "gSyncOnlyPinnedTabs",
+ "zen.window-sync.sync-only-pinned-tabs",
+ true
+);
XPCOMUtils.defineLazyPreferenceGetter(lazy, "gShouldLog", "zen.window-sync.log", true);
const OBSERVING = ["browser-window-before-show", "sessionstore-windows-restored"];
@@ -241,7 +247,7 @@ class nsZenWindowSync {
if (tab.pinned && !tab._zenPinnedInitialState) {
await this.setPinnedTabState(tab);
}
- if (!lazy.gWindowSyncEnabled) {
+ if (!lazy.gWindowSyncEnabled || (lazy.gSyncOnlyPinnedTabs && !tab.pinned)) {
tab._zenContentsVisible = true;
}
}
@@ -953,6 +959,9 @@ class nsZenWindowSync {
*/
#delegateGenericSyncEvent(aEvent, flags = 0) {
const item = aEvent.target;
+ if (lazy.gSyncOnlyPinnedTabs && !item.pinned) {
+ return;
+ }
this.#syncItemForAllWindows(item, flags);
}
@@ -1089,16 +1098,19 @@ class nsZenWindowSync {
/* Mark: Event Handlers */
- on_TabOpen(aEvent) {
+ on_TabOpen(aEvent, { duringPinning = false } = {}) {
const tab = aEvent.target;
const window = tab.ownerGlobal;
const isUnsyncedWindow = window.gZenWorkspaces.privateWindowOrDisabled;
- if (tab.id) {
+ if (tab.id && !duringPinning) {
// This tab was opened as part of a sync operation.
return;
}
tab._zenContentsVisible = true;
tab.id = this.#newTabSyncId;
+ if (lazy.gSyncOnlyPinnedTabs && !tab.pinned) {
+ return;
+ }
if (isUnsyncedWindow || !lazy.gWindowSyncEnabled) {
return;
}
@@ -1116,6 +1128,9 @@ class nsZenWindowSync {
SYNC_FLAG_ICON | SYNC_FLAG_LABEL | SYNC_FLAG_MOVE
);
});
+ if (duringPinning && tab?.splitView) {
+ this.on_ZenSplitViewTabsSplit({ target: tab.group });
+ }
this.#maybeFlushTabState(tab);
}
@@ -1137,7 +1152,8 @@ class nsZenWindowSync {
}
on_TabMove(aEvent) {
- return this.#delegateGenericSyncEvent(aEvent, SYNC_FLAG_MOVE);
+ this.#delegateGenericSyncEvent(aEvent, SYNC_FLAG_MOVE);
+ return Promise.resolve();
}
on_TabPinned(aEvent) {
@@ -1149,7 +1165,14 @@ class nsZenWindowSync {
if (!tab._zenPinnedInitialState) {
tabStatePromise = this.setPinnedTabState(tab);
}
- return Promise.all([tabStatePromise, this.on_TabMove(aEvent)]);
+ return Promise.all([
+ tabStatePromise,
+ this.on_TabMove(aEvent).then(() => {
+ if (lazy.gSyncOnlyPinnedTabs) {
+ this.on_TabOpen({ target: tab }, { duringPinning: true });
+ }
+ }),
+ ]);
}
on_TabUnpinned(aEvent) {
@@ -1160,7 +1183,11 @@ class nsZenWindowSync {
delete targetTab._zenPinnedInitialState;
}
});
- return this.on_TabMove(aEvent);
+ return this.on_TabMove(aEvent).then(() => {
+ if (lazy.gSyncOnlyPinnedTabs) {
+ this.on_TabClose({ target: tab });
+ }
+ });
}
on_TabAddedToEssentials(aEvent) {
@@ -1351,4 +1378,6 @@ class nsZenWindowSync {
// eslint-disable-next-line mozilla/valid-lazy
export const gWindowSyncEnabled = lazy.gWindowSyncEnabled;
+// eslint-disable-next-line mozilla/valid-lazy
+export const gSyncOnlyPinnedTabs = lazy.gSyncOnlyPinnedTabs;
export const ZenWindowSync = new nsZenWindowSync();
diff --git a/src/zen/tabs/ZenPinnedTabManager.mjs b/src/zen/tabs/ZenPinnedTabManager.mjs
index c7a8d694f..0c4877aa9 100644
--- a/src/zen/tabs/ZenPinnedTabManager.mjs
+++ b/src/zen/tabs/ZenPinnedTabManager.mjs
@@ -560,9 +560,6 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
// eslint-disable-next-line complexity
moveToAnotherTabContainerIfNecessary(event, draggedTab, movingTabs, dropIndex) {
- if (!this.enabled) {
- return false;
- }
let newIndex = dropIndex;
let fromDifferentWindow = false;
movingTabs = Array.from(movingTabs || draggedTab)