Compare commits

...

47 Commits

Author SHA1 Message Date
Mr. M
c6cd912262 feat: Start making use of IDs instead of sync identifiers, b=no-bug, c=folders 2025-11-21 14:29:58 +01:00
Mr. M
a765d3642f Merge branch 'window-sync' of https://github.com/zen-browser/desktop into window-sync 2025-11-21 13:19:30 +01:00
Mr. M
40bc51f904 feat: Run session saver before opening a new window, b=no-bug, c=no-component 2025-11-21 13:16:57 +01:00
Mr. M
8a963a9257 feat: Move window sync to its own JS module, b=no-bug, c=workspaces 2025-11-21 13:16:43 +01:00
Mr. M
9c5bf19ae3 feat: Clone the previous state, b=no-bug, c=no-component 2025-11-21 13:12:22 +01:00
Mr. M
6fbba04dab feat: Run session saver before opening a new winodw, b=no-bug, c=tabs 2025-11-21 13:12:22 +01:00
mr. m
c5df63711b Discard changes to src/zen/tabs/ZenPinnedTabsStorage.mjs 2025-11-21 13:11:56 +01:00
mr. m
a61846bf9a Discard changes to src/browser/components/tabbrowser/content/tabbrowser-js.patch 2025-11-21 13:11:56 +01:00
mr. m
c2a5066181 Discard changes to src/browser/components/tabbrowser/content/tab-js.patch 2025-11-21 13:11:47 +01:00
mr. m
00417582fb Discard changes to src/browser/components/sessionstore/SessionStore-sys-mjs.patch 2025-11-21 13:11:47 +01:00
Mr. M
32d603ced9 chore: Update patches to ff 145, b=no-bug, c=no-component 2025-11-21 13:11:46 +01:00
Mr. M
2706e8761d feat: Dont restore windows that are already initialized, b=no-bug, c=no-component 2025-11-21 13:11:39 +01:00
Mr. M
61659c63d9 feat: Stop using pinned manager and use zen session sidebar, b=no-bug, c=common, folders, tabs, workspaces 2025-11-21 13:11:39 +01:00
Mr. M
34c725aad0 feat: Start doing out own session restore, b=no-bug, c=folders, tabs 2025-11-21 13:08:38 +01:00
mr. m
435762c682 Discard changes to prefs/browser.yaml 2025-11-21 13:06:46 +01:00
Mr. M
c60990c283 feat: Properly handle tab moves, b=no-bug, c=workspaces 2025-11-21 13:06:46 +01:00
Mr. M
1d26040fc6 feat: Start on new session restore, b=no-bug, c=no-component 2025-11-21 13:06:46 +01:00
Mr. M
acb3708936 feat: Dont session duplicate the tabs, b=no-bug, c=workspaces 2025-11-21 13:06:46 +01:00
mr. m
c3b9c3c526 feat: Also change icons and labels if the tab is pending, b=no-bug, c=tabs, workspaces 2025-11-21 13:06:45 +01:00
Mr. M
0adf260ddc feat: Full cross-window workspace syncing, b=no-bug, c=workspaces 2025-11-21 13:03:46 +01:00
Mr. M
ce986beb2f feat: Run session saver before opening a new window, b=no-bug, c=no-component 2025-11-17 18:51:14 +01:00
Mr. M
c86875b7b0 feat: Move window sync to its own JS module, b=no-bug, c=workspaces 2025-11-17 13:36:57 +01:00
Mr. M
eefc8cb20c feat: Clone the previous state, b=no-bug, c=no-component 2025-11-13 17:03:27 +01:00
Mr. M
12c921fd87 feat: Run session saver before opening a new winodw, b=no-bug, c=tabs 2025-11-13 14:40:52 +01:00
mr. m
e419c4dc9f Merge branch 'dev' into window-sync
Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
2025-11-13 13:58:38 +01:00
mr. m
68b37ac736 Discard changes to src/zen/tabs/ZenPinnedTabsStorage.mjs 2025-11-13 13:58:18 +01:00
mr. m
7f225ac3ee Discard changes to src/browser/components/tabbrowser/content/tabbrowser-js.patch 2025-11-13 13:57:55 +01:00
mr. m
3e39ef2538 Discard changes to src/browser/components/tabbrowser/content/tab-js.patch 2025-11-13 13:57:42 +01:00
mr. m
e5517eb164 Discard changes to src/browser/components/sessionstore/SessionStore-sys-mjs.patch 2025-11-13 13:57:32 +01:00
Mr. M
c4dd470864 chore: Update patches to ff 145, b=no-bug, c=no-component 2025-11-13 13:56:31 +01:00
Mr. M
bf1b0dcd48 feat: Dont restore windows that are already initialized, b=no-bug, c=no-component 2025-11-01 13:40:14 +01:00
Mr. M
76acc8b0e4 feat: Stop using pinned manager and use zen session sidebar, b=no-bug, c=common, folders, tabs, workspaces 2025-10-31 23:07:38 +01:00
mr. m
1b83b77cad Merge branch 'dev' into window-sync
Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
2025-10-31 21:56:19 +01:00
Mr. M
79ff574978 feat: Start doing out own session restore, b=no-bug, c=folders, tabs 2025-10-29 23:57:20 +01:00
mr. m
af20a65fa1 Merge branch 'dev' into window-sync 2025-10-19 23:11:11 +02:00
mr. m
4a7f8fc9c0 Merge branch 'dev' into window-sync 2025-10-19 12:53:06 +02:00
mr. m
a738a829de Merge branch 'dev' into window-sync 2025-10-18 19:53:04 +02:00
mr. m
240a031e38 Discard changes to prefs/browser.yaml 2025-10-18 19:52:53 +02:00
Mr. M
9bc7b9ce4e Merge branch 'window-sync' of https://github.com/zen-browser/desktop into window-sync 2025-09-28 23:45:17 +02:00
Mr. M
86006c8891 feat: Start on new session restore, b=no-bug, c=no-component 2025-09-28 23:45:13 +02:00
Mr. M
a55b1c7495 feat: Properly handle tab moves, b=no-bug, c=workspaces 2025-09-27 17:54:28 +02:00
mr. m
6e6337a95b Merge branch 'dev' into window-sync 2025-09-27 16:58:29 +02:00
mr. m
6b12153c8a Merge branch 'dev' into window-sync 2025-09-27 09:35:44 +02:00
mr. m
f6922ef2ba Merge branch 'dev' into window-sync 2025-09-03 13:06:44 +02:00
Mr. M
91f5d58fbc feat: Dont session duplicate the tabs, b=no-bug, c=workspaces 2025-09-02 16:21:04 +02:00
mr. m
7a4cdaa45c feat: Also change icons and labels if the tab is pending, b=no-bug, c=tabs, workspaces 2025-09-01 16:09:59 +02:00
Mr. M
81e854a89f feat: Full cross-window workspace syncing, b=no-bug, c=workspaces 2025-08-27 23:25:07 +02:00
20 changed files with 700 additions and 1004 deletions

View File

@@ -0,0 +1,21 @@
diff --git a/browser/components/sessionstore/SessionFile.sys.mjs b/browser/components/sessionstore/SessionFile.sys.mjs
index 31140cb8be3b529a0952ca8dc55165690b0e2120..605c9e0aa84da0a2d3171a0573e8cd95e27bd0c4 100644
--- a/browser/components/sessionstore/SessionFile.sys.mjs
+++ b/browser/components/sessionstore/SessionFile.sys.mjs
@@ -22,6 +22,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
RunState: "resource:///modules/sessionstore/RunState.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
SessionWriter: "resource:///modules/sessionstore/SessionWriter.sys.mjs",
+ ZenSessionStore: "resource:///modules/zen/ZenSessionManager.sys.mjs",
});
const PREF_UPGRADE_BACKUP = "browser.sessionstore.upgradeBackup.latestBuildID";
@@ -380,7 +381,7 @@ var SessionFileInternal = {
this._readOrigin = result.origin;
result.noFilesFound = noFilesFound;
-
+ await lazy.ZenSessionStore.readFile();
return result;
},

View File

@@ -0,0 +1,21 @@
diff --git a/browser/components/sessionstore/SessionStartup.sys.mjs b/browser/components/sessionstore/SessionStartup.sys.mjs
index be23213ae9ec7e59358a17276c6c3764d38d9996..ca5a8ccc916ceeab5140f1278d15233cefbe5815 100644
--- a/browser/components/sessionstore/SessionStartup.sys.mjs
+++ b/browser/components/sessionstore/SessionStartup.sys.mjs
@@ -40,6 +40,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
StartupPerformance:
"resource:///modules/sessionstore/StartupPerformance.sys.mjs",
sessionStoreLogger: "resource:///modules/sessionstore/SessionLogger.sys.mjs",
+ ZenSessionStore: "resource:///modules/zen/ZenSessionManager.sys.mjs",
});
const STATE_RUNNING_STR = "running";
@@ -179,6 +180,8 @@ export var SessionStartup = {
this._initialState = parsed;
}
+ lazy.ZenSessionStore.onFileRead(this._initialState);
+
if (this._initialState == null) {
// No valid session found.
this._sessionType = this.NO_SESSION;

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/sessionstore/SessionStore.sys.mjs b/browser/components/sessionstore/SessionStore.sys.mjs diff --git a/browser/components/sessionstore/SessionStore.sys.mjs b/browser/components/sessionstore/SessionStore.sys.mjs
index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0eeba2381c 100644 index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..9ef1996a0e8a3ebe55dc25921b8fc8cc0ac8a303 100644
--- a/browser/components/sessionstore/SessionStore.sys.mjs --- a/browser/components/sessionstore/SessionStore.sys.mjs
+++ b/browser/components/sessionstore/SessionStore.sys.mjs +++ b/browser/components/sessionstore/SessionStore.sys.mjs
@@ -127,6 +127,8 @@ const TAB_EVENTS = [ @@ -127,6 +127,8 @@ const TAB_EVENTS = [
@@ -11,7 +11,15 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
]; ];
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
@@ -1911,6 +1913,8 @@ var SessionStoreInternal = { @@ -196,6 +198,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
TabStateCache: "resource:///modules/sessionstore/TabStateCache.sys.mjs",
TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.sys.mjs",
setTimeout: "resource://gre/modules/Timer.sys.mjs",
+ ZenSessionStore: "resource:///modules/zen/ZenSessionManager.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "blankURI", () => {
@@ -1911,6 +1914,8 @@ var SessionStoreInternal = {
case "TabPinned": case "TabPinned":
case "TabUnpinned": case "TabUnpinned":
case "SwapDocShells": case "SwapDocShells":
@@ -20,7 +28,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
this.saveStateDelayed(win); this.saveStateDelayed(win);
break; break;
case "TabGroupCreate": case "TabGroupCreate":
@@ -2151,7 +2155,6 @@ var SessionStoreInternal = { @@ -2151,7 +2156,6 @@ var SessionStoreInternal = {
if (closedWindowState) { if (closedWindowState) {
let newWindowState; let newWindowState;
if ( if (
@@ -28,7 +36,17 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
!lazy.SessionStartup.willRestore() !lazy.SessionStartup.willRestore()
) { ) {
// We want to split the window up into pinned tabs and unpinned tabs. // We want to split the window up into pinned tabs and unpinned tabs.
@@ -2384,11 +2387,9 @@ var SessionStoreInternal = { @@ -2215,6 +2219,9 @@ var SessionStoreInternal = {
});
this._shouldRestoreLastSession = false;
}
+ else if (!aInitialState && isRegularWindow) {
+ lazy.ZenSessionStore.restoreNewWindow(aWindow, this);
+ }
if (this._restoreLastWindow && aWindow.toolbar.visible) {
// always reset (if not a popup window)
@@ -2384,11 +2391,9 @@ var SessionStoreInternal = {
tabbrowser.selectedTab.label; tabbrowser.selectedTab.label;
} }
@@ -40,7 +58,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
// Store the window's close date to figure out when each individual tab // Store the window's close date to figure out when each individual tab
// was closed. This timestamp should allow re-arranging data based on how // was closed. This timestamp should allow re-arranging data based on how
@@ -3373,7 +3374,7 @@ var SessionStoreInternal = { @@ -3373,7 +3378,7 @@ var SessionStoreInternal = {
if (!isPrivateWindow && tabState.isPrivate) { if (!isPrivateWindow && tabState.isPrivate) {
return; return;
} }
@@ -49,7 +67,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
return; return;
} }
@@ -4089,6 +4090,12 @@ var SessionStoreInternal = { @@ -4089,6 +4094,12 @@ var SessionStoreInternal = {
Math.min(tabState.index, tabState.entries.length) Math.min(tabState.index, tabState.entries.length)
); );
tabState.pinned = false; tabState.pinned = false;
@@ -62,7 +80,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
if (inBackground === false) { if (inBackground === false) {
aWindow.gBrowser.selectedTab = newTab; aWindow.gBrowser.selectedTab = newTab;
@@ -4525,6 +4532,7 @@ var SessionStoreInternal = { @@ -4525,6 +4536,7 @@ var SessionStoreInternal = {
// Append the tab if we're opening into a different window, // Append the tab if we're opening into a different window,
tabIndex: aSource == aTargetWindow ? pos : Infinity, tabIndex: aSource == aTargetWindow ? pos : Infinity,
pinned: state.pinned, pinned: state.pinned,
@@ -70,7 +88,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
userContextId: state.userContextId, userContextId: state.userContextId,
skipLoad: true, skipLoad: true,
preferredRemoteType, preferredRemoteType,
@@ -5374,7 +5382,7 @@ var SessionStoreInternal = { @@ -5374,7 +5386,7 @@ var SessionStoreInternal = {
for (let i = tabbrowser.pinnedTabCount; i < tabbrowser.tabs.length; i++) { for (let i = tabbrowser.pinnedTabCount; i < tabbrowser.tabs.length; i++) {
let tab = tabbrowser.tabs[i]; let tab = tabbrowser.tabs[i];
@@ -79,7 +97,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
removableTabs.push(tab); removableTabs.push(tab);
} }
} }
@@ -5434,7 +5442,7 @@ var SessionStoreInternal = { @@ -5434,7 +5446,7 @@ var SessionStoreInternal = {
} }
let workspaceID = aWindow.getWorkspaceID(); let workspaceID = aWindow.getWorkspaceID();
@@ -88,7 +106,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
winData.workspaceID = workspaceID; winData.workspaceID = workspaceID;
} }
}, },
@@ -5625,11 +5633,12 @@ var SessionStoreInternal = { @@ -5625,11 +5637,12 @@ var SessionStoreInternal = {
} }
let tabbrowser = aWindow.gBrowser; let tabbrowser = aWindow.gBrowser;
@@ -102,7 +120,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
// update the internal state data for this window // update the internal state data for this window
for (let tab of tabs) { for (let tab of tabs) {
if (tab == aWindow.FirefoxViewHandler.tab) { if (tab == aWindow.FirefoxViewHandler.tab) {
@@ -5640,6 +5649,7 @@ var SessionStoreInternal = { @@ -5640,6 +5653,7 @@ var SessionStoreInternal = {
tabsData.push(tabData); tabsData.push(tabData);
} }
@@ -110,7 +128,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
// update tab group state for this window // update tab group state for this window
winData.groups = []; winData.groups = [];
for (let tabGroup of aWindow.gBrowser.tabGroups) { for (let tabGroup of aWindow.gBrowser.tabGroups) {
@@ -5652,7 +5662,7 @@ var SessionStoreInternal = { @@ -5652,7 +5666,7 @@ var SessionStoreInternal = {
// a window is closed, point to the first item in the tab strip instead (it will never be the Firefox View tab, // a window is closed, point to the first item in the tab strip instead (it will never be the Firefox View tab,
// since it's only inserted into the tab strip after it's selected). // since it's only inserted into the tab strip after it's selected).
if (aWindow.FirefoxViewHandler.tab?.selected) { if (aWindow.FirefoxViewHandler.tab?.selected) {
@@ -119,7 +137,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
winData.title = tabbrowser.tabs[0].label; winData.title = tabbrowser.tabs[0].label;
} }
winData.selected = selectedIndex; winData.selected = selectedIndex;
@@ -5764,8 +5774,8 @@ var SessionStoreInternal = { @@ -5764,8 +5778,8 @@ var SessionStoreInternal = {
// selectTab represents. // selectTab represents.
let selectTab = 0; let selectTab = 0;
if (overwriteTabs) { if (overwriteTabs) {
@@ -130,7 +148,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
selectTab = Math.min(selectTab, winData.tabs.length); selectTab = Math.min(selectTab, winData.tabs.length);
} }
@@ -5808,6 +5818,8 @@ var SessionStoreInternal = { @@ -5808,6 +5822,8 @@ var SessionStoreInternal = {
winData.tabs, winData.tabs,
winData.groups ?? [] winData.groups ?? []
); );
@@ -139,7 +157,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
this._log.debug( this._log.debug(
`restoreWindow, createTabsForSessionRestore returned ${tabs.length} tabs` `restoreWindow, createTabsForSessionRestore returned ${tabs.length} tabs`
); );
@@ -6371,6 +6383,25 @@ var SessionStoreInternal = { @@ -6371,6 +6387,25 @@ var SessionStoreInternal = {
// Most of tabData has been restored, now continue with restoring // Most of tabData has been restored, now continue with restoring
// attributes that may trigger external events. // attributes that may trigger external events.
@@ -153,8 +171,8 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
+ if (tabData.zenHasStaticLabel) { + if (tabData.zenHasStaticLabel) {
+ tab.setAttribute("zen-has-static-label", "true"); + tab.setAttribute("zen-has-static-label", "true");
+ } + }
+ if (tabData.zenPinnedId) { + if (tabData.zenSyncId) {
+ tab.setAttribute("zen-pin-id", tabData.zenPinnedId); + tab.setAttribute("id", tabData.zenPinnedId);
+ } + }
+ if (tabData.zenDefaultUserContextId) { + if (tabData.zenDefaultUserContextId) {
+ tab.setAttribute("zenDefaultUserContextId", true); + tab.setAttribute("zenDefaultUserContextId", true);
@@ -165,7 +183,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
if (tabData.pinned) { if (tabData.pinned) {
tabbrowser.pinTab(tab); tabbrowser.pinTab(tab);
@@ -7289,7 +7320,7 @@ var SessionStoreInternal = { @@ -7289,7 +7324,7 @@ var SessionStoreInternal = {
let groupsToSave = new Map(); let groupsToSave = new Map();
for (let tIndex = 0; tIndex < window.tabs.length; ) { for (let tIndex = 0; tIndex < window.tabs.length; ) {

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/sessionstore/TabState.sys.mjs b/browser/components/sessionstore/TabState.sys.mjs diff --git a/browser/components/sessionstore/TabState.sys.mjs b/browser/components/sessionstore/TabState.sys.mjs
index 82721356d191055bec0d4b0ca49e481221988801..1ea5c394c704da295149443d7794961a12f2060b 100644 index 82721356d191055bec0d4b0ca49e481221988801..2d05ba4812e9a73bd896c1aeb007180bbc531a3c 100644
--- a/browser/components/sessionstore/TabState.sys.mjs --- a/browser/components/sessionstore/TabState.sys.mjs
+++ b/browser/components/sessionstore/TabState.sys.mjs +++ b/browser/components/sessionstore/TabState.sys.mjs
@@ -85,7 +85,22 @@ class _TabState { @@ -85,7 +85,22 @@ class _TabState {
@@ -7,7 +7,7 @@ index 82721356d191055bec0d4b0ca49e481221988801..1ea5c394c704da295149443d7794961a
} }
+ tabData.zenWorkspace = tab.getAttribute("zen-workspace-id"); + tabData.zenWorkspace = tab.getAttribute("zen-workspace-id");
+ tabData.zenPinnedId = tab.getAttribute("zen-pin-id"); + tabData.zenSyncId = tab.getAttribute("id");
+ tabData.zenEssential = tab.getAttribute("zen-essential"); + tabData.zenEssential = tab.getAttribute("zen-essential");
+ tabData.pinned = tabData.pinned || tabData.zenEssential; + tabData.pinned = tabData.pinned || tabData.zenEssential;
+ tabData.zenDefaultUserContextId = tab.getAttribute("zenDefaultUserContextId"); + tabData.zenDefaultUserContextId = tab.getAttribute("zenDefaultUserContextId");

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tab.js b/browser/components/tabbrowser/content/tab.js diff --git a/browser/components/tabbrowser/content/tab.js b/browser/components/tabbrowser/content/tab.js
index 4c1a48424316b29d27ae2bc8b64004df41c87bb6..f1ff9bf0947127a8e9115357cedac577b5fad08c 100644 index 4c1a48424316b29d27ae2bc8b64004df41c87bb6..ce54ed0c8a93d5521a436c55c9432c090b0420ac 100644
--- a/browser/components/tabbrowser/content/tab.js --- a/browser/components/tabbrowser/content/tab.js
+++ b/browser/components/tabbrowser/content/tab.js +++ b/browser/components/tabbrowser/content/tab.js
@@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
@@ -42,7 +42,7 @@ index 4c1a48424316b29d27ae2bc8b64004df41c87bb6..f1ff9bf0947127a8e9115357cedac577
".tab-label-container": ".tab-label-container":
"pinned,selected=visuallyselected,labeldirection", "pinned,selected=visuallyselected,labeldirection",
".tab-label": ".tab-label":
@@ -186,7 +189,7 @@ @@ -184,7 +187,7 @@
} }
set _visuallySelected(val) { set _visuallySelected(val) {
@@ -51,7 +51,7 @@ index 4c1a48424316b29d27ae2bc8b64004df41c87bb6..f1ff9bf0947127a8e9115357cedac577
return; return;
} }
@@ -222,11 +225,21 @@ @@ -220,11 +223,21 @@
} }
get visible() { get visible() {
@@ -78,7 +78,7 @@ index 4c1a48424316b29d27ae2bc8b64004df41c87bb6..f1ff9bf0947127a8e9115357cedac577
} }
get hidden() { get hidden() {
@@ -297,7 +310,7 @@ @@ -295,7 +308,7 @@
return false; return false;
} }
@@ -87,7 +87,7 @@ index 4c1a48424316b29d27ae2bc8b64004df41c87bb6..f1ff9bf0947127a8e9115357cedac577
} }
get lastAccessed() { get lastAccessed() {
@@ -374,8 +387,11 @@ @@ -372,8 +385,11 @@
} }
get group() { get group() {
@@ -101,7 +101,7 @@ index 4c1a48424316b29d27ae2bc8b64004df41c87bb6..f1ff9bf0947127a8e9115357cedac577
} }
return null; return null;
} }
@@ -470,6 +486,8 @@ @@ -468,6 +484,8 @@
this.style.MozUserFocus = "ignore"; this.style.MozUserFocus = "ignore";
} else if ( } else if (
event.target.classList.contains("tab-close-button") || event.target.classList.contains("tab-close-button") ||
@@ -110,7 +110,7 @@ index 4c1a48424316b29d27ae2bc8b64004df41c87bb6..f1ff9bf0947127a8e9115357cedac577
event.target.classList.contains("tab-icon-overlay") || event.target.classList.contains("tab-icon-overlay") ||
event.target.classList.contains("tab-audio-button") event.target.classList.contains("tab-audio-button")
) { ) {
@@ -524,6 +542,10 @@ @@ -522,6 +540,10 @@
this.style.MozUserFocus = ""; this.style.MozUserFocus = "";
} }
@@ -121,15 +121,7 @@ index 4c1a48424316b29d27ae2bc8b64004df41c87bb6..f1ff9bf0947127a8e9115357cedac577
on_click(event) { on_click(event) {
if (event.button != 0) { if (event.button != 0) {
return; return;
@@ -572,6 +594,7 @@ @@ -584,6 +606,14 @@
)
);
} else {
+ gZenPinnedTabManager._removePinnedAttributes(this, true);
gBrowser.removeTab(this, {
animate: true,
triggeringEvent: event,
@@ -584,6 +607,14 @@
// (see tabbrowser-tabs 'click' handler). // (see tabbrowser-tabs 'click' handler).
gBrowser.tabContainer._blockDblClick = true; gBrowser.tabContainer._blockDblClick = true;
} }
@@ -144,7 +136,7 @@ index 4c1a48424316b29d27ae2bc8b64004df41c87bb6..f1ff9bf0947127a8e9115357cedac577
} }
on_dblclick(event) { on_dblclick(event) {
@@ -607,6 +638,8 @@ @@ -607,6 +637,8 @@
animate: true, animate: true,
triggeringEvent: event, triggeringEvent: event,
}); });

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e6923fe5f 100644 index c0eafd4faf8d57b8486c5bf8917375850ec8147e..d1c089ba66defc74dbf06b283bc6ddca08b55b76 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js --- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js +++ b/browser/components/tabbrowser/content/tabbrowser.js
@@ -386,6 +386,7 @@ @@ -386,6 +386,7 @@
@@ -428,10 +428,10 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
+ gZenWorkspaces._initialTab._shouldRemove = true; + gZenWorkspaces._initialTab._shouldRemove = true;
+ } + }
+ } + }
+ } }
+ else { + else {
+ gZenWorkspaces._tabToRemoveForEmpty = this.selectedTab; + gZenWorkspaces._tabToRemoveForEmpty = this.selectedTab;
} + }
+ this._hasAlreadyInitializedZenSessionStore = true; + this._hasAlreadyInitializedZenSessionStore = true;
if (tabs.length > 1 || !tabs[0].selected) { if (tabs.length > 1 || !tabs[0].selected) {
@@ -518,17 +518,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
TabBarVisibility.update(); TabBarVisibility.update();
} }
@@ -4635,6 +4768,9 @@ @@ -4896,6 +5029,7 @@
return;
}
+ for (let tab of selectedTabs) {
+ gZenPinnedTabManager._removePinnedAttributes(tab, true);
+ }
this.removeTabs(selectedTabs, { isUserTriggered, telemetrySource });
}
@@ -4896,6 +5032,7 @@
telemetrySource, telemetrySource,
} = {} } = {}
) { ) {
@@ -536,7 +526,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
// When 'closeWindowWithLastTab' pref is enabled, closing all tabs // When 'closeWindowWithLastTab' pref is enabled, closing all tabs
// can be considered equivalent to closing the window. // can be considered equivalent to closing the window.
if ( if (
@@ -4985,6 +5122,7 @@ @@ -4985,6 +5119,7 @@
if (lastToClose) { if (lastToClose) {
this.removeTab(lastToClose, aParams); this.removeTab(lastToClose, aParams);
} }
@@ -544,7 +534,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@@ -5023,6 +5161,12 @@ @@ -5023,6 +5158,12 @@
aTab._closeTimeNoAnimTimerId = Glean.browserTabclose.timeNoAnim.start(); aTab._closeTimeNoAnimTimerId = Glean.browserTabclose.timeNoAnim.start();
} }
@@ -557,7 +547,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
// Handle requests for synchronously removing an already // Handle requests for synchronously removing an already
// asynchronously closing tab. // asynchronously closing tab.
if (!animate && aTab.closing) { if (!animate && aTab.closing) {
@@ -5037,6 +5181,9 @@ @@ -5037,6 +5178,9 @@
// state). // state).
let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width; let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width;
let isLastTab = this.#isLastTabInWindow(aTab); let isLastTab = this.#isLastTabInWindow(aTab);
@@ -567,7 +557,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
if ( if (
!this._beginRemoveTab(aTab, { !this._beginRemoveTab(aTab, {
closeWindowFastpath: true, closeWindowFastpath: true,
@@ -5085,7 +5232,13 @@ @@ -5085,7 +5229,13 @@
// We're not animating, so we can cancel the animation stopwatch. // We're not animating, so we can cancel the animation stopwatch.
Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId); Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId);
aTab._closeTimeAnimTimerId = null; aTab._closeTimeAnimTimerId = null;
@@ -582,7 +572,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
return; return;
} }
@@ -5219,7 +5372,7 @@ @@ -5219,7 +5369,7 @@
closeWindowWithLastTab != null closeWindowWithLastTab != null
? closeWindowWithLastTab ? closeWindowWithLastTab
: !window.toolbar.visible || : !window.toolbar.visible ||
@@ -591,7 +581,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
if (closeWindow) { if (closeWindow) {
// We've already called beforeunload on all the relevant tabs if we get here, // We've already called beforeunload on all the relevant tabs if we get here,
@@ -5243,6 +5396,7 @@ @@ -5243,6 +5393,7 @@
newTab = true; newTab = true;
} }
@@ -599,7 +589,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
aTab._endRemoveArgs = [closeWindow, newTab]; aTab._endRemoveArgs = [closeWindow, newTab];
// swapBrowsersAndCloseOther will take care of closing the window without animation. // swapBrowsersAndCloseOther will take care of closing the window without animation.
@@ -5283,13 +5437,7 @@ @@ -5283,13 +5434,7 @@
aTab._mouseleave(); aTab._mouseleave();
if (newTab) { if (newTab) {
@@ -614,7 +604,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
} else { } else {
TabBarVisibility.update(); TabBarVisibility.update();
} }
@@ -5422,6 +5570,7 @@ @@ -5422,6 +5567,7 @@
this.tabs[i]._tPos = i; this.tabs[i]._tPos = i;
} }
@@ -622,7 +612,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
if (!this._windowIsClosing) { if (!this._windowIsClosing) {
// update tab close buttons state // update tab close buttons state
this.tabContainer._updateCloseButtons(); this.tabContainer._updateCloseButtons();
@@ -5643,6 +5792,7 @@ @@ -5643,6 +5789,7 @@
} }
let excludeTabs = new Set(aExcludeTabs); let excludeTabs = new Set(aExcludeTabs);
@@ -630,7 +620,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
// If this tab has a successor, it should be selectable, since // If this tab has a successor, it should be selectable, since
// hiding or closing a tab removes that tab as a successor. // hiding or closing a tab removes that tab as a successor.
@@ -5655,13 +5805,13 @@ @@ -5655,13 +5802,13 @@
!excludeTabs.has(aTab.owner) && !excludeTabs.has(aTab.owner) &&
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose") Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
) { ) {
@@ -646,7 +636,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
); );
let tab = this.tabContainer.findNextTab(aTab, { let tab = this.tabContainer.findNextTab(aTab, {
@@ -5677,7 +5827,7 @@ @@ -5677,7 +5824,7 @@
} }
if (tab) { if (tab) {
@@ -655,7 +645,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
} }
// If no qualifying visible tab was found, see if there is a tab in // If no qualifying visible tab was found, see if there is a tab in
@@ -5698,7 +5848,7 @@ @@ -5698,7 +5845,7 @@
}); });
} }
@@ -664,7 +654,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
} }
_blurTab(aTab) { _blurTab(aTab) {
@@ -6104,10 +6254,10 @@ @@ -6104,10 +6251,10 @@
SessionStore.deleteCustomTabValue(aTab, "hiddenBy"); SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
} }
@@ -677,7 +667,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
aTab.selected || aTab.selected ||
aTab.closing || aTab.closing ||
// Tabs that are sharing the screen, microphone or camera cannot be hidden. // Tabs that are sharing the screen, microphone or camera cannot be hidden.
@@ -6166,6 +6316,7 @@ @@ -6166,6 +6313,7 @@
* @param {MozTabbrowserTab|MozTabbrowserTabGroup|MozTabbrowserTabGroup.labelElement} aTab * @param {MozTabbrowserTab|MozTabbrowserTabGroup|MozTabbrowserTabGroup.labelElement} aTab
*/ */
replaceTabWithWindow(aTab, aOptions) { replaceTabWithWindow(aTab, aOptions) {
@@ -685,7 +675,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
if (this.tabs.length == 1) { if (this.tabs.length == 1) {
return null; return null;
} }
@@ -6299,7 +6450,7 @@ @@ -6299,7 +6447,7 @@
* `true` if element is a `<tab-group>` * `true` if element is a `<tab-group>`
*/ */
isTabGroup(element) { isTabGroup(element) {
@@ -694,7 +684,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
} }
/** /**
@@ -6375,8 +6526,8 @@ @@ -6375,8 +6523,8 @@
} }
// Don't allow mixing pinned and unpinned tabs. // Don't allow mixing pinned and unpinned tabs.
@@ -705,7 +695,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
} else { } else {
tabIndex = Math.max(tabIndex, this.pinnedTabCount); tabIndex = Math.max(tabIndex, this.pinnedTabCount);
} }
@@ -6402,10 +6553,16 @@ @@ -6402,10 +6550,16 @@
this.#handleTabMove( this.#handleTabMove(
element, element,
() => { () => {
@@ -724,7 +714,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
if (neighbor && this.isTab(element) && tabIndex > element._tPos) { if (neighbor && this.isTab(element) && tabIndex > element._tPos) {
neighbor.after(element); neighbor.after(element);
} else { } else {
@@ -6463,23 +6620,28 @@ @@ -6463,23 +6617,28 @@
#moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) { #moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) {
if (this.isTabGroupLabel(targetElement)) { if (this.isTabGroupLabel(targetElement)) {
targetElement = targetElement.group; targetElement = targetElement.group;
@@ -759,7 +749,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
} else if (!element.pinned && targetElement && targetElement.pinned) { } else if (!element.pinned && targetElement && targetElement.pinned) {
// If the caller asks to move an unpinned element next to a pinned // If the caller asks to move an unpinned element next to a pinned
// tab, move the unpinned element to be the first unpinned element // tab, move the unpinned element to be the first unpinned element
@@ -6492,14 +6654,34 @@ @@ -6492,14 +6651,34 @@
// move the tab group right before the first unpinned tab. // move the tab group right before the first unpinned tab.
// 4. Moving a tab group and the first unpinned tab is grouped: // 4. Moving a tab group and the first unpinned tab is grouped:
// move the tab group right before the first unpinned tab's tab group. // move the tab group right before the first unpinned tab's tab group.
@@ -795,7 +785,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
element.pinned element.pinned
? this.tabContainer.pinnedTabsContainer ? this.tabContainer.pinnedTabsContainer
: this.tabContainer; : this.tabContainer;
@@ -6508,7 +6690,7 @@ @@ -6508,7 +6687,7 @@
element, element,
() => { () => {
if (moveBefore) { if (moveBefore) {
@@ -804,7 +794,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
} else if (targetElement) { } else if (targetElement) {
targetElement.after(element); targetElement.after(element);
} else { } else {
@@ -6580,10 +6762,10 @@ @@ -6580,10 +6759,10 @@
* @param {TabMetricsContext} [metricsContext] * @param {TabMetricsContext} [metricsContext]
*/ */
moveTabToGroup(aTab, aGroup, metricsContext) { moveTabToGroup(aTab, aGroup, metricsContext) {
@@ -817,7 +807,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
return; return;
} }
if (aTab.group && aTab.group.id === aGroup.id) { if (aTab.group && aTab.group.id === aGroup.id) {
@@ -6613,6 +6795,7 @@ @@ -6613,6 +6792,7 @@
let state = { let state = {
tabIndex: tab._tPos, tabIndex: tab._tPos,
@@ -825,7 +815,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
}; };
if (tab.visible) { if (tab.visible) {
state.elementIndex = tab.elementIndex; state.elementIndex = tab.elementIndex;
@@ -6639,7 +6822,7 @@ @@ -6639,7 +6819,7 @@
let changedTabGroup = let changedTabGroup =
previousTabState.tabGroupId != currentTabState.tabGroupId; previousTabState.tabGroupId != currentTabState.tabGroupId;
@@ -834,7 +824,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
tab.dispatchEvent( tab.dispatchEvent(
new CustomEvent("TabMove", { new CustomEvent("TabMove", {
bubbles: true, bubbles: true,
@@ -6676,6 +6859,10 @@ @@ -6676,6 +6856,10 @@
moveActionCallback(); moveActionCallback();
@@ -845,7 +835,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
// Clear tabs cache after moving nodes because the order of tabs may have // Clear tabs cache after moving nodes because the order of tabs may have
// changed. // changed.
this.tabContainer._invalidateCachedTabs(); this.tabContainer._invalidateCachedTabs();
@@ -7576,7 +7763,7 @@ @@ -7576,7 +7760,7 @@
// preventDefault(). It will still raise the window if appropriate. // preventDefault(). It will still raise the window if appropriate.
break; break;
} }
@@ -854,7 +844,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
window.focus(); window.focus();
aEvent.preventDefault(); aEvent.preventDefault();
break; break;
@@ -7593,7 +7780,6 @@ @@ -7593,7 +7777,6 @@
} }
case "TabGroupCollapse": case "TabGroupCollapse":
aEvent.target.tabs.forEach(tab => { aEvent.target.tabs.forEach(tab => {
@@ -862,7 +852,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
}); });
break; break;
case "TabGroupCreateByUser": case "TabGroupCreateByUser":
@@ -8542,6 +8728,7 @@ @@ -8542,6 +8725,7 @@
aWebProgress.isTopLevel aWebProgress.isTopLevel
) { ) {
this.mTab.setAttribute("busy", "true"); this.mTab.setAttribute("busy", "true");
@@ -870,7 +860,7 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
gBrowser._tabAttrModified(this.mTab, ["busy"]); gBrowser._tabAttrModified(this.mTab, ["busy"]);
this.mTab._notselectedsinceload = !this.mTab.selected; this.mTab._notselectedsinceload = !this.mTab.selected;
} }
@@ -9543,7 +9730,7 @@ var TabContextMenu = { @@ -9543,7 +9727,7 @@ var TabContextMenu = {
); );
contextUnpinSelectedTabs.hidden = contextUnpinSelectedTabs.hidden =
!this.contextTab.pinned || !this.multiselected; !this.contextTab.pinned || !this.multiselected;
@@ -879,11 +869,3 @@ index c0eafd4faf8d57b8486c5bf8917375850ec8147e..cead9e6e52f7354e20b8b64ad06a075e
// Build Ask Chat items // Build Ask Chat items
TabContextMenu.GenAI.buildTabMenu( TabContextMenu.GenAI.buildTabMenu(
document.getElementById("context_askChat"), document.getElementById("context_askChat"),
@@ -9863,6 +10050,7 @@ var TabContextMenu = {
)
);
} else {
+ gZenPinnedTabManager._removePinnedAttributes(this.contextTab, true);
gBrowser.removeTab(this.contextTab, {
animate: true,
...gBrowser.TabMetrics.userTriggeredContext(

View File

@@ -13,3 +13,4 @@
category app-startup nsBrowserGlue @mozilla.org/browser/browserglue;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} category app-startup nsBrowserGlue @mozilla.org/browser/browserglue;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
#include common/Components.manifest #include common/Components.manifest
#include sessionstore/SessionComponents.manifest

View File

@@ -1310,14 +1310,6 @@ window.gZenVerticalTabsManager = {
} else { } else {
gBrowser.setTabTitle(this._tabEdited); gBrowser.setTabTitle(this._tabEdited);
} }
if (this._tabEdited.getAttribute('zen-pin-id')) {
// Update pin title in storage
await gZenPinnedTabManager.updatePinTitle(
this._tabEdited,
this._tabEdited.label,
!!newName
);
}
// Maybe add some confetti here?!? // Maybe add some confetti here?!?
gZenUIManager.motion.animate( gZenUIManager.motion.animate(

View File

@@ -157,6 +157,17 @@ class ZenFolder extends MozTabbrowserTabGroup {
} }
} }
} }
async unpackTabs() {
this.collapsed = false;
for (let tab of this.allItems.reverse()) {
tab = tab.group.hasAttribute('split-view-group') ? tab.group : tab;
if (tab.hasAttribute('zen-empty-tab')) {
gBrowser.removeTab(tab);
} else {
gBrowser.ungroupTab(tab);
}
}
}
async delete() { async delete() {
for (const tab of this.allItemsRecursive) { for (const tab of this.allItemsRecursive) {
@@ -169,6 +180,16 @@ class ZenFolder extends MozTabbrowserTabGroup {
} }
await gBrowser.removeTabGroup(this, { isUserTriggered: true }); await gBrowser.removeTabGroup(this, { isUserTriggered: true });
} }
async delete() {
for (const tab of this.allItemsRecursive) {
if (tab.hasAttribute('zen-empty-tab')) {
// Manually remove the empty tabs as removeTabs() inside removeTabGroup
// does ignore them.
gBrowser.removeTab(tab);
}
}
await gBrowser.removeTabGroup(this, { isUserTriggered: true });
}
get allItemsRecursive() { get allItemsRecursive() {
const items = []; const items = [];

View File

@@ -506,9 +506,6 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
tabs = [emptyTab, ...filteredTabs]; tabs = [emptyTab, ...filteredTabs];
const folder = this._createFolderNode(options); const folder = this._createFolderNode(options);
if (options.initialPinId) {
folder.setAttribute('zen-pin-id', options.initialPinId);
}
if (options.insertAfter) { if (options.insertAfter) {
options.insertAfter.after(folder); options.insertAfter.after(folder);
@@ -938,7 +935,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
if (!parentFolder && folder.hasAttribute('split-view-group')) continue; if (!parentFolder && folder.hasAttribute('split-view-group')) continue;
const emptyFolderTabs = folder.tabs const emptyFolderTabs = folder.tabs
.filter((tab) => tab.hasAttribute('zen-empty-tab')) .filter((tab) => tab.hasAttribute('zen-empty-tab'))
.map((tab) => tab.getAttribute('zen-pin-id')); .map((tab) => tab.getAttribute('id'));
let prevSiblingInfo = null; let prevSiblingInfo = null;
const prevSibling = folder.previousElementSibling; const prevSibling = folder.previousElementSibling;
@@ -947,8 +944,8 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
if (prevSibling) { if (prevSibling) {
if (gBrowser.isTabGroup(prevSibling)) { if (gBrowser.isTabGroup(prevSibling)) {
prevSiblingInfo = { type: 'group', id: prevSibling.id }; prevSiblingInfo = { type: 'group', id: prevSibling.id };
} else if (gBrowser.isTab(prevSibling) && prevSibling.hasAttribute('zen-pin-id')) { } else if (gBrowser.isTab(prevSibling) && prevSibling.hasAttribute('id')) {
const zenPinId = prevSibling.getAttribute('zen-pin-id'); const zenPinId = prevSibling.getAttribute('id');
prevSiblingInfo = { type: 'tab', id: zenPinId }; prevSiblingInfo = { type: 'tab', id: zenPinId };
} else { } else {
prevSiblingInfo = { type: 'start', id: null }; prevSiblingInfo = { type: 'start', id: null };
@@ -967,7 +964,6 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
prevSiblingInfo: prevSiblingInfo, prevSiblingInfo: prevSiblingInfo,
emptyTabIds: emptyFolderTabs, emptyTabIds: emptyFolderTabs,
userIcon: userIcon?.getAttribute('href'), userIcon: userIcon?.getAttribute('href'),
pinId: folder.getAttribute('zen-pin-id'),
// note: We shouldn't be using the workspace-id anywhere, we are just // note: We shouldn't be using the workspace-id anywhere, we are just
// remembering it for the pinned tabs manager to use it later. // remembering it for the pinned tabs manager to use it later.
workspaceId: folder.getAttribute('zen-workspace-id'), workspaceId: folder.getAttribute('zen-workspace-id'),
@@ -994,9 +990,9 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
tabFolderWorkingData.set(folderData.id, workingData); tabFolderWorkingData.set(folderData.id, workingData);
const oldGroup = document.getElementById(folderData.id); const oldGroup = document.getElementById(folderData.id);
folderData.emptyTabIds.forEach((zenPinId) => { folderData.emptyTabIds.forEach((id) => {
oldGroup oldGroup
?.querySelector(`tab[zen-pin-id="${zenPinId}"]`) ?.querySelector(`tab[id="${id}"]`)
?.setAttribute('zen-empty-tab', true); ?.setAttribute('zen-empty-tab', true);
}); });
if (oldGroup) { if (oldGroup) {
@@ -1009,7 +1005,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
saveOnWindowClose: folderData.saveOnWindowClose, saveOnWindowClose: folderData.saveOnWindowClose,
workspaceId: folderData.workspaceId, workspaceId: folderData.workspaceId,
}); });
folder.setAttribute('zen-pin-id', folderData.pinId); folder.setAttribute('id', folderData.id);
workingData.node = folder; workingData.node = folder;
oldGroup.before(folder); oldGroup.before(folder);
} else { } else {
@@ -1041,8 +1037,8 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
if (parentWorkingData && parentWorkingData.node) { if (parentWorkingData && parentWorkingData.node) {
switch (stateData?.prevSiblingInfo?.type) { switch (stateData?.prevSiblingInfo?.type) {
case 'tab': { case 'tab': {
const tab = parentWorkingData.node.querySelector( const tab = document.getElementById(
`[zen-pin-id="${stateData.prevSiblingInfo.id}"]` stateData.prevSiblingInfo.id
); );
tab.after(node); tab.after(node);
break; break;

View File

@@ -13,4 +13,5 @@ DIRS += [
"tests", "tests",
"urlbar", "urlbar",
"toolkit", "toolkit",
"sessionstore",
] ]

View File

@@ -0,0 +1,11 @@
# 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/.
# Browser global components initializing before UI startup
category browser-before-ui-startup resource:///modules/zen/ZenSessionManager.sys.mjs ZenSessionStore.init
category browser-before-ui-startup resource:///modules/zen/ZenWindowSync.sys.mjs ZenWindowSync.init
# App shutdown consumers
category browser-quit-application-granted resource:///modules/zen/ZenSessionManager.sys.mjs ZenSessionStore.uninit
category browser-quit-application-granted resource:///modules/zen/ZenWindowSync.sys.mjs ZenWindowSync.uninit

View File

@@ -0,0 +1,40 @@
// 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/.
// Note that changing this hidden pref will make the previous session file
// unused, causing a new session file to be created on next write.
const SHOULD_COMPRESS_FILE = Services.prefs.getBoolPref('zen.session-store.compress-file', true);
const FILE_NAME = SHOULD_COMPRESS_FILE ? 'zen-sessions.jsonlz4' : 'zen-sessions.json';
export class nsZenSessionFile {
#path = PathUtils.join(PathUtils.profileDir, FILE_NAME);
#sidebar = [];
async read() {
try {
const data = await IOUtils.readJSON(this.#path, { compress: SHOULD_COMPRESS_FILE });
this.#sidebar = data.sidebar || [];
} catch {
// File doesn't exist yet, that's fine.
}
}
get sidebar() {
return this.#sidebar;
}
set sidebar(data) {
this.#sidebar = data;
}
async #write(data) {
await IOUtils.writeJSON(this.#path, data, { compress: SHOULD_COMPRESS_FILE });
}
async store() {
const data = { sidebar: this.#sidebar };
await this.#write(data);
}
}

View File

@@ -0,0 +1,172 @@
// 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, {
nsZenSessionFile: 'resource:///modules/zen/ZenSessionFile.sys.mjs',
PrivateBrowsingUtils: 'resource://gre/modules/PrivateBrowsingUtils.sys.mjs',
BrowserWindowTracker: 'resource:///modules/BrowserWindowTracker.sys.mjs',
TabGroupState: 'resource:///modules/sessionstore/TabGroupState.sys.mjs',
SessionStore: 'resource:///modules/sessionstore/SessionStore.sys.mjs',
SessionSaver: 'resource:///modules/sessionstore/SessionSaver.sys.mjs',
});
const LAZY_COLLECT_THRESHOLD = 5 * 60 * 1000; // 5 minutes
const OBSERVING = ['sessionstore-state-write-complete', 'browser-window-before-show'];
class nsZenSessionManager {
#file;
constructor() {
this.#file = new lazy.nsZenSessionFile();
}
// Called from SessionComponents.manifest on app-startup
init() {
for (let topic of OBSERVING) {
Services.obs.addObserver(this, topic);
}
}
uninit() {
for (let topic of OBSERVING) {
Services.obs.removeObserver(this, topic);
}
}
async readFile() {
await this.#file.read();
}
onFileRead(initialState) {
for (const winData of initialState.windows || []) {
this.restoreWindowData(winData);
}
}
get #sidebar() {
return this.#file.sidebar;
}
set #sidebar(data) {
this.#file.sidebar = data;
}
observe(aSubject, aTopic) {
switch (aTopic) {
case 'sessionstore-state-write-complete': {
this.#saveState(true);
break;
}
case 'browser-window-before-show': // catch new windows
this.#onBeforeBrowserWindowShown(aSubject);
break;
default:
break;
}
}
/** Handles the browser-window-before-show observer notification. */
#onBeforeBrowserWindowShown(aWindow) {
// TODO: Initialize new window
void aWindow;
}
get #topMostWindow() {
return lazy.BrowserWindowTracker.getTopWindow();
}
/**
* Saves the current session state. Collects data and writes to disk.
*
* @param forceUpdateAllWindows (optional)
* Forces us to recollect data for all windows and will bypass and
* update the corresponding caches.
*/
async #saveState(forceUpdateAllWindows = false) {
if (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing) {
// Don't save (or even collect) anything in permanent private
// browsing mode
return;
}
// Collect an initial snapshot of window data before we do the flush.
const window = this.#topMostWindow;
// We don't have any normal windows or no windows at all
if (!window) {
return;
}
this.#collectWindowData(this.#topMostWindow, forceUpdateAllWindows);
this.#file.store();
}
/**
* Collects session data for a given window.
*
* @param window
* The window to collect data for.
* @param forceUpdate
* Forces us to recollect data and will bypass and update the
* corresponding caches.
*/
#collectWindowData(window, forceUpdate = false) {
let sidebarData = this.#sidebar;
if (!sidebarData || forceUpdate) {
sidebarData = {};
}
// If it hasn't changed, don't update.
if (
!forceUpdate &&
sidebarData.lastCollected &&
Date.now() - sidebarData.lastCollected < LAZY_COLLECT_THRESHOLD
) {
return;
}
sidebarData.lastCollected = Date.now();
this.#collectTabsData(window, sidebarData);
this.#sidebar = sidebarData;
}
/**
* Collects session data for all tabs in a given window.
*
* @param aWindow
* The window to collect tab data for.
* @param winData
* The window data object to populate.
*/
#collectTabsData(aWindow, sidebarData) {
const winData = lazy.SessionStore.getWindowState(aWindow).windows[0];
if (!winData) return;
sidebarData.tabs = winData.tabs;
sidebarData.folders = winData.folders;
sidebarData.splitViewData = winData.splitViewData;
sidebarData.groups = winData.groups;
}
restoreWindowData(aWindowData) {
const sidebar = this.#file.sidebar;
if (!sidebar) {
return;
}
aWindowData.tabs = sidebar.tabs || [];
aWindowData.splitViewData = sidebar.splitViewData;
aWindowData.folders = sidebar.folders;
aWindowData.groups = sidebar.groups;
}
restoreNewWindow(aWindow, SessionStoreInternal) {
lazy.SessionSaver.run().then(() => {
const state = lazy.SessionStore.getCurrentState(true);
const windows = state.windows || {};
let newWindow = Cu.cloneInto(windows[0], {});
delete newWindow.selected;
const newState = { windows: [newWindow] };
SessionStoreInternal.restoreWindows(aWindow, newState, {});
});
}
}
export const ZenSessionStore = new nsZenSessionManager();

View File

@@ -0,0 +1,25 @@
// 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 OBSERVING = ['browser-window-before-show'];
class nsZenWindowSync {
constructor() {}
init() {
for (let topic of OBSERVING) {
Services.obs.addObserver(this, topic);
}
}
uninit() {
for (let topic of OBSERVING) {
Services.obs.removeObserver(this, topic);
}
}
observe(aSubject, aTopic) {}
}
export const ZenWindowSync = new nsZenWindowSync();

View File

@@ -0,0 +1,9 @@
# 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 += [
"ZenSessionFile.sys.mjs",
"ZenSessionManager.sys.mjs",
"ZenWindowSync.sys.mjs",
]

View File

@@ -6,24 +6,8 @@ import { nsZenDOMOperatedFeature } from 'chrome://browser/content/zen-components
const lazy = {}; const lazy = {};
class ZenPinnedTabsObserver { class ZenPinnedTabsObserver {
static ALL_EVENTS = [ static ALL_EVENTS = ['TabPinned', 'TabUnpinned'];
'TabPinned',
'TabUnpinned',
'TabMove',
'TabGroupCreate',
'TabGroupRemoved',
'TabGroupMoved',
'ZenFolderRenamed',
'ZenFolderIconChanged',
'TabGroupCollapse',
'TabGroupExpand',
'TabGrouped',
'TabUngrouped',
'ZenFolderChangedWorkspace',
'TabAddedToEssentials',
'TabRemovedFromEssentials',
];
#listeners = []; #listeners = [];
@@ -103,22 +87,13 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
} }
onTabIconChanged(tab, url = null) { onTabIconChanged(tab, url = null) {
tab.dispatchEvent(new CustomEvent('ZenTabIconChanged', { bubbles: true, detail: { tab } }));
tab.dispatchEvent(new CustomEvent('ZenTabIconChanged', { bubbles: true, detail: { tab } }));
const iconUrl = url ?? tab.iconImage.src; const iconUrl = url ?? tab.iconImage.src;
if (!iconUrl && tab.hasAttribute('zen-pin-id')) {
try {
setTimeout(async () => {
const favicon = await this.getFaviconAsBase64(tab.linkedBrowser.currentURI);
if (favicon) {
gBrowser.setIcon(tab, favicon);
}
});
} catch {
// Handle error
}
} else {
if (tab.hasAttribute('zen-essential')) { if (tab.hasAttribute('zen-essential')) {
tab.style.setProperty('--zen-essential-tab-icon', `url(${iconUrl})`); tab.style.setProperty('--zen-essential-tab-icon', `url(${iconUrl})`);
} if (tab.hasAttribute('zen-essential')) {
tab.style.setProperty('--zen-essential-tab-icon', `url(${iconUrl})`);
} }
} }
@@ -150,260 +125,6 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
return lazy.zenTabsEssentialsMax; return lazy.zenTabsEssentialsMax;
} }
async refreshPinnedTabs({ init = false } = {}) {
if (!this.enabled) {
return;
}
await ZenPinnedTabsStorage.promiseInitialized;
await this.#initializePinsCache();
setTimeout(async () => {
// Execute in a separate task to avoid blocking the main thread
await SessionStore.promiseAllWindowsRestored;
await gZenWorkspaces.promiseInitialized;
await this.#initializePinnedTabs(init);
if (init) {
this._hasFinishedLoading = true;
}
}, 10);
}
async #initializePinsCache() {
try {
// Get pin data
const pins = await ZenPinnedTabsStorage.getPins();
// Enhance pins with favicons
this._pinsCache = await Promise.all(
pins.map(async (pin) => {
try {
if (pin.isGroup) {
return pin; // Skip groups for now
}
const image = await this.getFaviconAsBase64(Services.io.newURI(pin.url));
return {
...pin,
iconUrl: image || null,
};
} catch {
// If favicon fetch fails, continue without icon
return {
...pin,
iconUrl: null,
};
}
})
);
} catch (ex) {
console.error('Failed to initialize pins cache:', ex);
this._pinsCache = [];
}
this.log(`Initialized pins cache with ${this._pinsCache.length} pins`);
return this._pinsCache;
}
#finishedInitializingPins() {
if (this.hasInitializedPins) {
return;
}
this._resolvePinnedInitializedInternal();
delete this._resolvePinnedInitializedInternal;
this.hasInitializedPins = true;
}
async #initializePinnedTabs(init = false) {
const pins = this._pinsCache;
if (!pins?.length || !init) {
this.#finishedInitializingPins();
return;
}
const pinnedTabsByUUID = new Map();
const pinsToCreate = new Set(pins.map((p) => p.uuid));
// First pass: identify existing tabs and remove those without pins
for (let tab of gZenWorkspaces.allStoredTabs) {
const pinId = tab.getAttribute('zen-pin-id');
if (!pinId) {
continue;
}
if (pinsToCreate.has(pinId)) {
// This is a valid pinned tab that matches a pin
pinnedTabsByUUID.set(pinId, tab);
pinsToCreate.delete(pinId);
if (lazy.zenPinnedTabRestorePinnedTabsToPinnedUrl && init) {
this._resetTabToStoredState(tab);
}
} else {
// This is a pinned tab that no longer has a corresponding pin
gBrowser.removeTab(tab);
}
}
for (const group of gZenWorkspaces.allTabGroups) {
const pinId = group.getAttribute('zen-pin-id');
if (!pinId) {
continue;
}
if (pinsToCreate.has(pinId)) {
// This is a valid pinned group that matches a pin
pinsToCreate.delete(pinId);
}
}
// Second pass: For every existing tab, update its label
// and set 'zen-has-static-label' attribute if it's been edited
for (let pin of pins) {
const tab = pinnedTabsByUUID.get(pin.uuid);
if (!tab) {
continue;
}
tab.removeAttribute('zen-has-static-label'); // So we can set it again
if (pin.title && pin.editedTitle) {
gBrowser._setTabLabel(tab, pin.title, { beforeTabOpen: true });
tab.setAttribute('zen-has-static-label', 'true');
}
}
const groups = new Map();
const pendingTabsInsideGroups = {};
// Third pass: create new tabs for pins that don't have tabs
for (let pin of pins) {
try {
if (!pinsToCreate.has(pin.uuid)) {
continue; // Skip pins that already have tabs
}
if (pin.isGroup) {
const tabs = [];
// If there's already existing tabs, let's use them
for (const [uuid, existingTab] of pinnedTabsByUUID) {
const pinObject = this._pinsCache.find((p) => p.uuid === uuid);
if (pinObject && pinObject.parentUuid === pin.uuid) {
tabs.push(existingTab);
}
}
// We still need to iterate through pending tabs since the database
// query doesn't guarantee the order of insertion
for (const [parentUuid, folderTabs] of Object.entries(pendingTabsInsideGroups)) {
if (parentUuid === pin.uuid) {
tabs.push(...folderTabs);
}
}
const group = gZenFolders.createFolder(tabs, {
label: pin.title,
collapsed: pin.isFolderCollapsed,
initialPinId: pin.uuid,
workspaceId: pin.workspaceUuid,
insertAfter:
groups.get(pin.parentUuid)?.querySelector('.tab-group-container')?.lastChild || null,
});
gZenFolders.setFolderUserIcon(group, pin.folderIcon);
groups.set(pin.uuid, group);
continue;
}
let params = {
skipAnimation: true,
allowInheritPrincipal: false,
skipBackgroundNotify: true,
userContextId: pin.containerTabId || 0,
createLazyBrowser: true,
skipLoad: true,
noInitialLabel: false,
};
// Create and initialize the tab
let newTab = gBrowser.addTrustedTab(pin.url, params);
newTab.setAttribute('zenDefaultUserContextId', true);
// Set initial label/title
if (pin.title) {
gBrowser.setInitialTabTitle(newTab, pin.title);
}
// Set the icon if we have it cached
if (pin.iconUrl) {
gBrowser.setIcon(newTab, pin.iconUrl);
}
newTab.setAttribute('zen-pin-id', pin.uuid);
if (pin.workspaceUuid) {
newTab.setAttribute('zen-workspace-id', pin.workspaceUuid);
}
if (pin.isEssential) {
newTab.setAttribute('zen-essential', 'true');
}
if (pin.editedTitle) {
newTab.setAttribute('zen-has-static-label', 'true');
}
// Initialize browser state if needed
if (!newTab.linkedBrowser._remoteAutoRemoved) {
let state = {
entries: [
{
url: pin.url,
title: pin.title,
triggeringPrincipal_base64: E10SUtils.SERIALIZED_SYSTEMPRINCIPAL,
},
],
userContextId: pin.containerTabId || 0,
image: pin.iconUrl,
};
SessionStore.setTabState(newTab, state);
}
this.log(`Created new pinned tab for pin ${pin.uuid} (isEssential: ${pin.isEssential})`);
gBrowser.pinTab(newTab);
if (pin.parentUuid) {
const parentGroup = groups.get(pin.parentUuid);
if (parentGroup) {
parentGroup.querySelector('.tab-group-container').appendChild(newTab);
} else {
if (pendingTabsInsideGroups[pin.parentUuid]) {
pendingTabsInsideGroups[pin.parentUuid].push(newTab);
} else {
pendingTabsInsideGroups[pin.parentUuid] = [newTab];
}
}
} else {
if (!pin.isEssential) {
const container = gZenWorkspaces.workspaceElement(
pin.workspaceUuid
)?.pinnedTabsContainer;
if (container) {
container.insertBefore(newTab, container.lastChild);
}
} else {
gZenWorkspaces.getEssentialsSection(pin.containerTabId).appendChild(newTab);
}
}
gBrowser.tabContainer._invalidateCachedTabs();
newTab.initialize();
} catch (ex) {
console.error('Failed to initialize pinned tabs:', ex);
}
}
setTimeout(() => {
this.#finishedInitializingPins();
}, 0);
gBrowser._updateTabBarForPinnedTabs();
gZenUIManager.updateTabsToolbar();
}
_onPinnedTabEvent(action, event) { _onPinnedTabEvent(action, event) {
if (!this.enabled) return; if (!this.enabled) return;
const tab = event.target; const tab = event.target;
@@ -413,243 +134,38 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
} }
switch (action) { switch (action) {
case 'TabPinned': case 'TabPinned':
case 'TabAddedToEssentials':
tab._zenClickEventListener = this._zenClickEventListener; tab._zenClickEventListener = this._zenClickEventListener;
tab.addEventListener('click', tab._zenClickEventListener); tab.addEventListener('click', tab._zenClickEventListener);
this._setPinnedAttributes(tab);
break; break;
case 'TabRemovedFromEssentials':
if (tab.pinned) {
this.#onTabMove(tab);
break;
}
// [Fall through] // [Fall through]
case 'TabUnpinned': case 'TabUnpinned':
this._removePinnedAttributes(tab);
if (tab._zenClickEventListener) { if (tab._zenClickEventListener) {
tab.removeEventListener('click', tab._zenClickEventListener); tab.removeEventListener('click', tab._zenClickEventListener);
delete tab._zenClickEventListener; delete tab._zenClickEventListener;
} }
break; break;
case 'TabMove':
this.#onTabMove(tab);
break;
case 'TabGroupCreate':
this.#onTabGroupCreate(event);
break;
case 'TabGroupRemoved':
this.#onTabGroupRemoved(event);
break;
case 'TabGroupMoved':
this.#onTabGroupMoved(event);
break;
case 'ZenFolderRenamed':
case 'ZenFolderIconChanged':
case 'TabGroupCollapse':
case 'TabGroupExpand':
case 'ZenFolderChangedWorkspace':
this.#updateGroupInfo(event.originalTarget, action);
break;
case 'TabGrouped':
this.#onTabGrouped(event);
break;
case 'TabUngrouped':
this.#onTabUngrouped(event);
break;
default: default:
console.warn('ZenPinnedTabManager: Unhandled tab event', action); console.warn('ZenPinnedTabManager: Unhandled tab event', action);
break; break;
} }
} }
async #onTabGroupCreate(event) { async _onTabClick(e) {
const group = event.originalTarget; const tab = e.target?.closest('tab');
if (!group.isZenFolder) { if (e.button === 1 && tab) {
return; await this.onCloseTabShortcut(e, tab, {
closeIfPending: Services.prefs.getBoolPref('zen.pinned-tab-manager.wheel-close-if-pending'),
});
} }
if (group.hasAttribute('zen-pin-id')) {
return; // Group already exists in storage
}
const workspaceId = group.getAttribute('zen-workspace-id');
let id = await ZenPinnedTabsStorage.createGroup(
group.name,
group.iconURL,
group.collapsed,
workspaceId,
group.getAttribute('zen-pin-id'),
group._pPos
);
group.setAttribute('zen-pin-id', id);
for (const tab of group.tabs) {
// Only add it if the tab is directly under the group
if (
tab.pinned &&
tab.hasAttribute('zen-pin-id') &&
tab.group === group &&
this.hasInitializedPins
) {
const tabPinId = tab.getAttribute('zen-pin-id');
await ZenPinnedTabsStorage.addTabToGroup(tabPinId, id, /* position */ tab._pPos);
}
}
await this.refreshPinnedTabs();
}
async #onTabGrouped(event) {
const tab = event.detail;
const group = tab.group;
if (!group.isZenFolder) {
return;
}
const pinId = group.getAttribute('zen-pin-id');
const tabPinId = tab.getAttribute('zen-pin-id');
const tabPin = this._pinsCache?.find((p) => p.uuid === tabPinId);
if (!tabPin || !tabPin.group) {
return;
}
ZenPinnedTabsStorage.addTabToGroup(tabPinId, pinId, /* position */ tab._pPos);
}
async #onTabUngrouped(event) {
const tab = event.detail;
const group = tab.group;
if (!group?.isZenFolder) {
return;
}
const tabPinId = tab.getAttribute('zen-pin-id');
const tabPin = this._pinsCache?.find((p) => p.uuid === tabPinId);
if (!tabPin) {
return;
}
ZenPinnedTabsStorage.removeTabFromGroup(tabPinId, /* position */ tab._pPos);
}
async #updateGroupInfo(group, action) {
if (!group?.isZenFolder) {
return;
}
const pinId = group.getAttribute('zen-pin-id');
const groupPin = this._pinsCache?.find((p) => p.uuid === pinId);
if (groupPin) {
groupPin.title = group.name;
groupPin.folderIcon = group.iconURL;
groupPin.isFolderCollapsed = group.collapsed;
groupPin.position = group._pPos;
groupPin.parentUuid = group.group?.getAttribute('zen-pin-id') || null;
groupPin.workspaceUuid = group.getAttribute('zen-workspace-id') || null;
await this.savePin(groupPin);
switch (action) {
case 'ZenFolderRenamed':
case 'ZenFolderIconChanged':
case 'TabGroupCollapse':
case 'TabGroupExpand':
break;
default:
for (const item of group.allItems) {
if (gBrowser.isTabGroup(item)) {
await this.#updateGroupInfo(item, action);
} else {
await this.#onTabMove(item);
}
}
}
}
}
async #onTabGroupRemoved(event) {
const group = event.originalTarget;
if (!group.isZenFolder) {
return;
}
await ZenPinnedTabsStorage.removePin(group.getAttribute('zen-pin-id'));
group.removeAttribute('zen-pin-id');
}
async #onTabGroupMoved(event) {
const group = event.originalTarget;
if (!group.isZenFolder) {
return;
}
const newIndex = group._pPos;
const pinId = group.getAttribute('zen-pin-id');
if (!pinId) {
return;
}
for (const tab of group.allItemsRecursive) {
if (tab.pinned && tab.getAttribute('zen-pin-id') === pinId) {
const pin = this._pinsCache.find((p) => p.uuid === pinId);
if (pin) {
pin.position = tab._pPos;
pin.parentUuid = tab.group?.getAttribute('zen-pin-id') || null;
pin.workspaceUuid = group.getAttribute('zen-workspace-id');
await this.savePin(pin, false);
}
break;
}
}
const groupPin = this._pinsCache?.find((p) => p.uuid === pinId);
if (groupPin) {
groupPin.position = newIndex;
groupPin.parentUuid = group.group?.getAttribute('zen-pin-id');
groupPin.workspaceUuid = group.getAttribute('zen-workspace-id');
await this.savePin(groupPin);
}
}
async #onTabMove(tab) {
if (!tab.pinned || !this._pinsCache) {
return;
}
const allTabs = [...gBrowser.tabs, ...gBrowser.tabGroups];
for (let i = 0; i < allTabs.length; i++) {
const otherTab = allTabs[i];
if (
otherTab.pinned &&
otherTab.getAttribute('zen-pin-id') !== tab.getAttribute('zen-pin-id')
) {
const actualPin = this._pinsCache.find(
(pin) => pin.uuid === otherTab.getAttribute('zen-pin-id')
);
if (!actualPin) {
continue;
}
actualPin.position = otherTab._pPos;
actualPin.workspaceUuid = otherTab.getAttribute('zen-workspace-id');
actualPin.parentUuid = otherTab.group?.getAttribute('zen-pin-id') || null;
await this.savePin(actualPin, false);
}
}
const actualPin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
if (!actualPin) {
return;
}
actualPin.position = tab._pPos;
actualPin.isEssential = tab.hasAttribute('zen-essential');
actualPin.parentUuid = tab.group?.getAttribute('zen-pin-id') || null;
actualPin.workspaceUuid = tab.getAttribute('zen-workspace-id') || null;
// There was a bug where the title and hasStaticLabel attribute were not being set
// This is a workaround to fix that
if (tab.hasAttribute('zen-has-static-label')) {
actualPin.editedTitle = true;
actualPin.title = tab.label;
}
await this.savePin(actualPin);
tab.dispatchEvent(
new CustomEvent('ZenPinnedTabMoved', {
detail: { tab },
})
);
} }
async _onTabClick(e) { async _onTabClick(e) {
const tab = e.target?.closest('tab'); const tab = e.target?.closest('tab');
if (e.button === 1 && tab) { if (e.button === 1 && tab) {
await this.onCloseTabShortcut(e, tab, { await this.onCloseTabShortcut(e, tab, {
closeIfPending: Services.prefs.getBoolPref('zen.pinned-tab-manager.wheel-close-if-pending'), closeIfPending: Services.prefs.getBoolPref(
'zen.pinned-tab-manager.wheel-close-if-pending'
),
}); });
} }
} }
@@ -672,105 +188,12 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
return; return;
} }
const browser = tab.linkedBrowser;
const pin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
if (!pin) {
return;
}
const userContextId = tab.getAttribute('usercontextid');
pin.title = tab.label || browser.contentTitle;
pin.url = browser.currentURI.spec;
pin.workspaceUuid = tab.getAttribute('zen-workspace-id');
pin.userContextId = userContextId ? parseInt(userContextId, 10) : 0;
await this.savePin(pin);
this.resetPinChangedUrl(tab); this.resetPinChangedUrl(tab);
await this.refreshPinnedTabs();
gZenUIManager.showToast('zen-pinned-tab-replaced'); gZenUIManager.showToast('zen-pinned-tab-replaced');
} }
async _setPinnedAttributes(tab) { _initClosePinnedTabShortcut() {
if ( let cmdClose = document.getElementById('cmd_close');
tab.hasAttribute('zen-pin-id') ||
!this._hasFinishedLoading ||
tab.hasAttribute('zen-empty-tab')
) {
return;
}
this.log(`Setting pinned attributes for tab ${tab.linkedBrowser.currentURI.spec}`);
const browser = tab.linkedBrowser;
const uuid = gZenUIManager.generateUuidv4();
const userContextId = tab.getAttribute('usercontextid');
let entry = null;
if (tab.getAttribute('zen-pinned-entry')) {
entry = JSON.parse(tab.getAttribute('zen-pinned-entry'));
}
await this.savePin({
uuid,
title: entry?.title || tab.label || browser.contentTitle,
url: entry?.url || browser.currentURI.spec,
containerTabId: userContextId ? parseInt(userContextId, 10) : 0,
workspaceUuid: tab.getAttribute('zen-workspace-id'),
isEssential: tab.getAttribute('zen-essential') === 'true',
parentUuid: tab.group?.getAttribute('zen-pin-id') || null,
position: tab._pPos,
});
tab.setAttribute('zen-pin-id', uuid);
tab.dispatchEvent(
new CustomEvent('ZenPinnedTabCreated', {
detail: { tab },
})
);
// This is used while migrating old pins to new system - we don't want to refresh when migrating
if (tab.getAttribute('zen-pinned-entry')) {
tab.removeAttribute('zen-pinned-entry');
return;
}
this.onLocationChange(browser);
await this.refreshPinnedTabs();
}
async _removePinnedAttributes(tab, isClosing = false) {
tab.removeAttribute('zen-has-static-label');
if (!tab.getAttribute('zen-pin-id') || this._temporarilyUnpiningEssential) {
return;
}
if (Services.startup.shuttingDown || window.skipNextCanClose) {
return;
}
this.log(`Removing pinned attributes for tab ${tab.getAttribute('zen-pin-id')}`);
await ZenPinnedTabsStorage.removePin(tab.getAttribute('zen-pin-id'));
this.resetPinChangedUrl(tab);
if (!isClosing) {
tab.removeAttribute('zen-pin-id');
tab.removeAttribute('zen-essential'); // Just in case
if (!tab.hasAttribute('zen-workspace-id') && gZenWorkspaces.workspaceEnabled) {
const workspace = await gZenWorkspaces.getActiveWorkspace();
tab.setAttribute('zen-workspace-id', workspace.uuid);
}
}
await this.refreshPinnedTabs();
tab.dispatchEvent(
new CustomEvent('ZenPinnedTabRemoved', {
detail: { tab },
})
);
}
_initClosePinnedTabShortcut() { _initClosePinnedTabShortcut() {
let cmdClose = document.getElementById('cmd_close'); let cmdClose = document.getElementById('cmd_close');
@@ -780,20 +203,31 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
} }
} }
async savePin(pin, notifyObservers = true) { async onCloseTabShortcut(
if (!this.hasInitializedPins && !gZenUIManager.testingEnabled) { event,
return; selectedTab = gBrowser.selectedTab,
} {
const existingPin = this._pinsCache.find((p) => p.uuid === pin.uuid); behavior = lazy.zenPinnedTabCloseShortcutBehavior,
if (existingPin) { noClose = false,
Object.assign(existingPin, pin); closeIfPending = false,
} else { alwaysUnload = false,
// We shouldn't need it, but just in case there's folderToUnload = null,
// a race condition while making new pinned tabs. } = {}
this._pinsCache.push(pin); ) {
} try {
await ZenPinnedTabsStorage.savePin(pin, notifyObservers); const tabs = Array.isArray(selectedTab) ? selectedTab : [selectedTab];
const pinnedTabs = [
...new Set(
tabs
.flatMap((tab) => {
if (tab.group?.hasAttribute('split-view-group')) {
return tab.group.tabs;
} }
return tab;
})
.filter((tab) => tab?.pinned)
),
];
async onCloseTabShortcut( async onCloseTabShortcut(
event, event,
@@ -841,7 +275,6 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
switch (behavior) { switch (behavior) {
case 'close': { case 'close': {
for (const tab of pinnedTabs) { for (const tab of pinnedTabs) {
this._removePinnedAttributes(tab, true);
gBrowser.removeTab(tab, { animate: true }); gBrowser.removeTab(tab, { animate: true });
} }
break; break;
@@ -1017,12 +450,6 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
tab.removeAttribute('zen-workspace-id'); tab.removeAttribute('zen-workspace-id');
} }
if (tab.pinned && tab.hasAttribute('zen-pin-id')) { if (tab.pinned && tab.hasAttribute('zen-pin-id')) {
const pin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
if (pin) {
pin.isEssential = true;
pin.workspaceUuid = null;
this.savePin(pin);
}
gBrowser.zenHandleTabMove(tab, () => { gBrowser.zenHandleTabMove(tab, () => {
if (tab.ownerGlobal !== window) { if (tab.ownerGlobal !== window) {
tab = gBrowser.adoptTab(tab, { tab = gBrowser.adoptTab(tab, {
@@ -1415,11 +842,8 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
return document.documentElement.getAttribute('zen-sidebar-expanded') === 'true'; return document.documentElement.getAttribute('zen-sidebar-expanded') === 'true';
} }
async updatePinTitle(tab, newTitle, isEdited = true, notifyObservers = true) { async updatePinTitle(tab, newTitle, isEdited = true) {
const uuid = tab.getAttribute('zen-pin-id'); const uuid = tab.getAttribute('zen-pin-id');
await ZenPinnedTabsStorage.updatePinTitle(uuid, newTitle, isEdited, notifyObservers);
await this.refreshPinnedTabs();
const browsers = Services.wm.getEnumerator('navigator:browser'); const browsers = Services.wm.getEnumerator('navigator:browser');
@@ -1564,6 +988,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
} }
async onTabLabelChanged(tab) { async onTabLabelChanged(tab) {
tab.dispatchEvent(new CustomEvent('ZenTabLabelChanged', { detail: { tab } }));
if (!this._pinsCache) { if (!this._pinsCache) {
return; return;
} }

View File

@@ -1,10 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
var ZenPinnedTabsStorage = {
window.ZenPinnedTabsStorage = {
_saveCache: [],
async init() { async init() {
await this._ensureTable(); await this._ensureTable();
}, },
@@ -77,22 +74,6 @@ window.ZenPinnedTabsStorage = {
}, },
async savePin(pin, notifyObservers = true) { async savePin(pin, notifyObservers = true) {
// If we find the exact same pin in the cache, skip saving
const existingIndex = this._saveCache.findIndex((cachedPin) => cachedPin.uuid === pin.uuid);
if (existingIndex !== -1) {
const existingPin = this._saveCache[existingIndex];
const isSame = Object.keys(pin).every((key) => pin[key] === existingPin[key]);
if (isSame) {
return; // No changes, skip saving
} else {
// Update the cached pin
this._saveCache[existingIndex] = pin;
}
} else {
// Add to cache
this._saveCache.push(pin);
}
const changedUUIDs = new Set(); const changedUUIDs = new Set();
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.savePin', async (db) => { await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.savePin', async (db) => {
@@ -408,11 +389,6 @@ window.ZenPinnedTabsStorage = {
}, },
async removePin(uuid, notifyObservers = true) { async removePin(uuid, notifyObservers = true) {
const cachedIndex = this._saveCache.findIndex((cachedPin) => cachedPin.uuid === uuid);
if (cachedIndex !== -1) {
this._saveCache.splice(cachedIndex, 1);
}
const changedUUIDs = [uuid]; const changedUUIDs = [uuid];
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.removePin', async (db) => { await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.removePin', async (db) => {

View File

@@ -935,7 +935,6 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
await this.workspaceBookmarks(); await this.workspaceBookmarks();
await this.initializeTabsStripSections(); await this.initializeTabsStripSections();
this._initializeEmptyTab(); this._initializeEmptyTab();
await gZenPinnedTabManager.refreshPinnedTabs({ init: true });
await this.changeWorkspace(activeWorkspace, { onInit: true }); await this.changeWorkspace(activeWorkspace, { onInit: true });
this.#fixTabPositions(); this.#fixTabPositions();
this.onWindowResize(); this.onWindowResize();
@@ -1474,11 +1473,6 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
!tab.hasAttribute('zen-empty-tab') && !tab.hasAttribute('zen-empty-tab') &&
!tab.hasAttribute('zen-essential') !tab.hasAttribute('zen-essential')
); );
for (const tab of tabs) {
if (tab.pinned) {
await ZenPinnedTabsStorage.removePin(tab.getAttribute('zen-pin-id'));
}
}
gBrowser.removeTabs(tabs, { gBrowser.removeTabs(tabs, {
animate: false, animate: false,
skipSessionStore: true, skipSessionStore: true,

View File

@@ -26,7 +26,6 @@ export default [
'ZEN_KEYSET_ID', 'ZEN_KEYSET_ID',
'gZenPinnedTabManager', 'gZenPinnedTabManager',
'ZenPinnedTabsStorage',
'gZenEmojiPicker', 'gZenEmojiPicker',
'gZenSessionStore', 'gZenSessionStore',