Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a97293b
Add ZenWorkspacesSync engine for workspace syncing
kristijanribaric Feb 25, 2026
02870f1
Refactor workspace sync to apply workspace data immediately and pinne…
kristijanribaric Feb 26, 2026
d5c932e
Refactor control flow statements in ZenSessionManager
kristijanribaric Feb 26, 2026
e1497d8
Refactor arrow function formatting for consistency
kristijanribaric Feb 27, 2026
072c998
refactor: Implement conflict-resolution sync with syncMeta tracking
kristijanribaric Mar 11, 2026
095cc1c
refactor: Migrate to multi-record sync engine with per-item tracking
kristijanribaric Mar 12, 2026
03ae282
update session manager to build last snapshot after data cleanup in s…
kristijanribaric Apr 30, 2026
43b221f
move ZenWorkspacesSync.sys.mjs to zen/sync
kristijanribaric Apr 30, 2026
f3fe62a
ran lint fix
kristijanribaric Apr 30, 2026
f67ce1c
fix workspace reordering sync
kristijanribaric Apr 30, 2026
7e43e74
add force option to addToEssentials for immediate pinning in sync ope…
kristijanribaric Apr 30, 2026
883f513
update folder rename event to "TabGroupUpdate" and notify observers o…
kristijanribaric Apr 30, 2026
d0b6efc
ran lint fix
kristijanribaric Apr 30, 2026
b3ae110
implement syncDefaultUserContextId method in ZenPinnedTabManager to u…
kristijanribaric Apr 30, 2026
7c0ce26
notifications refactor, added zen sync manager, fixed zenDefaultUserC…
kristijanribaric May 1, 2026
a28c057
remove redundant await in ZenSpaceManager after propagateWorkspaceDat…
kristijanribaric May 1, 2026
2138903
reverted unnecessary assignment of canEssentialBeAdded call to a vari…
kristijanribaric May 1, 2026
83051fb
Ran lint fix
kristijanribaric May 1, 2026
689a2ab
Moved sync logic from ZenSessionManager.sys.mjs to ZenSyncManager.sys…
kristijanribaric May 1, 2026
8148035
Fixed a bug where reordering folders wouldn't notify the change to sy…
kristijanribaric May 1, 2026
f449897
update tab attribute check to 'zen-essential' in ZenSpaceManager.mjs;…
kristijanribaric May 1, 2026
bf1dd6c
fix zenDefaultUserContextId not being properly synced for essientials…
kristijanribaric May 2, 2026
43d200d
revert on_TabUngrouped in ZenWindowSync.sys.mjs
kristijanribaric May 2, 2026
603e585
simplified zenDefaultUserContextId assigning on tabs created by sync
kristijanribaric May 2, 2026
092b8e4
remove redundant observer notifications in folder and tab group event…
kristijanribaric May 2, 2026
1d8584b
remove redundant typeof check before calling setUserContextId
kristijanribaric May 2, 2026
73d846d
restored accidentally removed comment
kristijanribaric May 2, 2026
05b1188
Merge branch 'zen-browser:dev' into zen-sync
kristijanribaric May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/browser/components/preferences/sync-js.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
diff --git a/browser/components/preferences/sync.js b/browser/components/preferences/sync.js
index dc89a9c41a0dbd44054ede0025d333773f0ae908..7fd91bd704b3b187277e4c8b076f990cb56ea8dc 100644
--- a/browser/components/preferences/sync.js
+++ b/browser/components/preferences/sync.js
@@ -40,6 +40,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.workspaces", type: "bool" },
]);

/**
@@ -512,6 +513,7 @@ const SYNC_ENGINE_SETTINGS = [
},
{ id: "syncAddons", pref: "services.sync.engine.addons", type: "addons" },
{ id: "syncSettings", pref: "services.sync.engine.prefs", type: "settings" },
+ { id: "syncWorkspaces", pref: "services.sync.engine.workspaces", type: "workspaces" },
];

SYNC_ENGINE_SETTINGS.forEach(({ id, pref }) => {
15 changes: 15 additions & 0 deletions src/services/sync/modules/service-sys-mjs.patch
Original file line number Diff line number Diff line change
@@ -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;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
diff --git a/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs b/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs
index 5702ff28cc22206f5ce16584dac8a78d816562ce..2132ee9ad8f553b3effeb7c4386e5fae46b80507 100644
--- a/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs
@@ -270,11 +270,11 @@ _ContextualIdentityService.prototype = {
});
},

- create(name, icon, color) {
+ create(name, icon, color, id = null) {
this.ensureDataReady();

- // Retrieve the next userContextId available.
- let userContextId = ++this._lastUserContextId;
+ // If explicit ID is provided, use it if it's not already in use, otherwise use the next available ID.
+ let userContextId = id !== null && !this._identities.some(i => i.userContextId === id) ? id : ++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
7 changes: 6 additions & 1 deletion src/zen/common/modules/ZenSessionStore.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class ZenSessionStore extends nsZenPreloadedFeature {
});

restoreInitialTabData(tab, tabData) {
if (typeof tab.setUserContextId === "function") {
Comment thread
kristijanribaric marked this conversation as resolved.
Outdated
tab.setUserContextId(tabData.userContextId || 0);
}
if (tabData.zenWorkspace) {
tab.setAttribute("zen-workspace-id", tabData.zenWorkspace);
}
Expand All @@ -33,8 +36,10 @@ class ZenSessionStore extends nsZenPreloadedFeature {
if (tabData.zenEssential) {
tab.setAttribute("zen-essential", "true");
}
if (tabData.zenDefaultUserContextId) {
if (tabData.zenDefaultUserContextId && !(tabData.userContextId || 0)) {
Comment thread
kristijanribaric marked this conversation as resolved.
Outdated
tab.setAttribute("zenDefaultUserContextId", "true");
} else {
tab.removeAttribute("zenDefaultUserContextId");
}
if (tabData._zenPinnedInitialState) {
tab._zenPinnedInitialState = tabData._zenPinnedInitialState;
Expand Down
9 changes: 8 additions & 1 deletion src/zen/folders/ZenFolder.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class nsZenFolder extends MozTabbrowserTabGroup {

this.labelElement.onRenameFinished = newLabel => {
this.name = newLabel.trim() || "Folder";
const event = new CustomEvent("ZenFolderRenamed", {
const event = new CustomEvent("TabGroupUpdate", {
bubbles: true,
});
this.dispatchEvent(event);
Expand Down Expand Up @@ -172,6 +172,13 @@ export class nsZenFolder extends MozTabbrowserTabGroup {
}

async delete() {
if (this.id) {
Comment thread
kristijanribaric marked this conversation as resolved.
Outdated
Services.obs.notifyObservers(
null,
"zen-workspace-item-changed",
`f~${this.id}`
);
}
for (const tab of this.allItemsRecursive) {
if (tab.hasAttribute("zen-empty-tab")) {
// Manually remove the empty tabs as removeTabs() inside removeTabGroup
Expand Down
70 changes: 61 additions & 9 deletions src/zen/folders/ZenFolders.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

import { nsZenDOMOperatedFeature } from "chrome://browser/content/zen-components/ZenCommonUtils.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
SessionSaver: "resource:///modules/sessionstore/SessionSaver.sys.mjs",
});

function formatRelativeTime(timestamp) {
const now = Date.now();

Expand Down Expand Up @@ -317,6 +322,14 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
if (groupIsCollapsiblePins(group)) {
return;
}
// Mark tab as modified for sync when its folder membership changes.
if (tab.id && group?.isZenFolder) {
Services.obs.notifyObservers(
null,
"zen-workspace-item-changed",
`t~${tab.id}`
Comment thread
mr-cheffy marked this conversation as resolved.
Outdated
);
}
group.pinned = tab.pinned;
const isActiveFolder = group?.activeGroups?.length > 0;

Expand Down Expand Up @@ -361,6 +374,15 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
].sort((a, b) => a._tPos > b._tPos);
}
parentFolder.collapsed = isActiveFolder;
if (folder?.id) {
Services.obs.notifyObservers(
null,
"zen-workspace-item-changed",
`f~${folder.id}`
);
folder.dispatchEvent(new CustomEvent("TabGroupMoved", { bubbles: true }));
void lazy.SessionSaver.run();
Comment thread
kristijanribaric marked this conversation as resolved.
Outdated
}
}

on_FolderUngrouped(event) {
Expand All @@ -372,6 +394,15 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
for (const tab of folder.tabs) {
this.animateUnload(parentFolder, tab, true);
}
if (folder?.id) {
Services.obs.notifyObservers(
null,
"zen-workspace-item-changed",
`f~${folder.id}`
);
folder.dispatchEvent(new CustomEvent("TabGroupMoved", { bubbles: true }));
void lazy.SessionSaver.run();
Comment thread
kristijanribaric marked this conversation as resolved.
Outdated
}
}

async on_TabSelect(event) {
Expand Down Expand Up @@ -412,6 +443,14 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
async on_TabUngrouped(event) {
const tab = event.detail;
const group = event.target;
// Mark tab as modified for sync when its folder membership changes.
if (tab.id && group?.isZenFolder) {
Services.obs.notifyObservers(
Comment thread
kristijanribaric marked this conversation as resolved.
Outdated
null,
"zen-workspace-item-changed",
`t~${tab.id}`
);
}
if (
group.hasAttribute("split-view-group") &&
tab.hasAttribute("had-zen-pinned-changed")
Expand Down Expand Up @@ -641,16 +680,22 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
const insertBefore =
options.insertBefore ||
pinnedContainer.querySelector(".pinned-tabs-container-separator");
const emptyTab = gBrowser.addTab("about:blank", {
skipAnimation: true,
pinned: true,
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
_forZenEmptyTab: true,
createLazyBrowser: true,
});

gBrowser.pinTab(emptyTab);
tabs = [emptyTab, ...filteredTabs];
if (!options.skipEmptyTab) {
const emptyTab = gBrowser.addTab("about:blank", {
skipAnimation: true,
pinned: true,
triggeringPrincipal:
Services.scriptSecurityManager.getSystemPrincipal(),
_forZenEmptyTab: true,
createLazyBrowser: true,
});

gBrowser.pinTab(emptyTab);
tabs = [emptyTab, ...filteredTabs];
} else {
tabs = filteredTabs;
}

const folder = this._createFolderNode(options);

Expand Down Expand Up @@ -680,6 +725,13 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
}

this.#groupInit(folder);
if (folder.id) {
Services.obs.notifyObservers(
Comment thread
kristijanribaric marked this conversation as resolved.
Outdated
null,
"zen-workspace-item-changed",
`f~${folder.id}`
);
}
return folder;
}

Expand Down
1 change: 1 addition & 0 deletions src/zen/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ DIRS += [
"sessionstore",
"share",
"spaces",
"sync"
]
Loading
Loading