feat: Start doing out own session restore, b=no-bug, c=folders, tabs

This commit is contained in:
Mr. M
2025-10-29 23:57:20 +01:00
parent 435762c682
commit 34c725aad0
13 changed files with 270 additions and 263 deletions

View File

@@ -0,0 +1,21 @@
diff --git a/browser/components/sessionstore/SessionFile.sys.mjs b/browser/components/sessionstore/SessionFile.sys.mjs
index 157c55ab24a418b56690d2e26320582909b919e4..14755f57dc450583e69eee94eb11f16980d5e5cb 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";
@@ -364,7 +365,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 eb62ff3e733e43fdaa299babddea3ba0125abb06..09567fe1be2af56429b60cbcbb36aa477fa68794 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 = { @@ -195,6 +197,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", () => {
@@ -1904,6 +1907,8 @@ var SessionStoreInternal = {
case "TabPinned": case "TabPinned":
case "TabUnpinned": case "TabUnpinned":
case "SwapDocShells": case "SwapDocShells":
@@ -20,7 +28,18 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
this.saveStateDelayed(win); this.saveStateDelayed(win);
break; break;
case "TabGroupCreate": case "TabGroupCreate":
@@ -2151,7 +2155,6 @@ var SessionStoreInternal = { @@ -2041,6 +2046,10 @@ var SessionStoreInternal = {
// A regular window is not a private window, taskbar tab window, or popup window
let isRegularWindow =
!isPrivateWindow && !isTaskbarTab && aWindow.toolbar.visible;
+ if (!aInitialState && isRegularWindow) {
+ aInitialState = ZenSessionStore.getNewWindowData(this._windows);
+ this.restoreWindows(aWindow, aInitialState, {});
+ }
// perform additional initialization when the first window is loading
if (lazy.RunState.isStopped) {
@@ -2139,7 +2148,6 @@ var SessionStoreInternal = {
if (closedWindowState) { if (closedWindowState) {
let newWindowState; let newWindowState;
if ( if (
@@ -28,7 +47,7 @@ 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 = { @@ -2372,11 +2380,9 @@ var SessionStoreInternal = {
tabbrowser.selectedTab.label; tabbrowser.selectedTab.label;
} }
@@ -40,7 +59,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 = { @@ -3361,7 +3367,7 @@ var SessionStoreInternal = {
if (!isPrivateWindow && tabState.isPrivate) { if (!isPrivateWindow && tabState.isPrivate) {
return; return;
} }
@@ -49,7 +68,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
return; return;
} }
@@ -4089,6 +4090,12 @@ var SessionStoreInternal = { @@ -4073,6 +4079,11 @@ var SessionStoreInternal = {
Math.min(tabState.index, tabState.entries.length) Math.min(tabState.index, tabState.entries.length)
); );
tabState.pinned = false; tabState.pinned = false;
@@ -62,7 +81,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
if (inBackground === false) { if (inBackground === false) {
aWindow.gBrowser.selectedTab = newTab; aWindow.gBrowser.selectedTab = newTab;
@@ -4525,6 +4532,7 @@ var SessionStoreInternal = { @@ -4509,6 +4520,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 +89,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
userContextId: state.userContextId, userContextId: state.userContextId,
skipLoad: true, skipLoad: true,
preferredRemoteType, preferredRemoteType,
@@ -5374,7 +5382,7 @@ var SessionStoreInternal = { @@ -5358,7 +5370,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 +98,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
removableTabs.push(tab); removableTabs.push(tab);
} }
} }
@@ -5434,7 +5442,7 @@ var SessionStoreInternal = { @@ -5418,7 +5430,7 @@ var SessionStoreInternal = {
} }
let workspaceID = aWindow.getWorkspaceID(); let workspaceID = aWindow.getWorkspaceID();
@@ -88,7 +107,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
winData.workspaceID = workspaceID; winData.workspaceID = workspaceID;
} }
}, },
@@ -5625,11 +5633,12 @@ var SessionStoreInternal = { @@ -5609,11 +5621,12 @@ var SessionStoreInternal = {
} }
let tabbrowser = aWindow.gBrowser; let tabbrowser = aWindow.gBrowser;
@@ -102,7 +121,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 = { @@ -5624,6 +5637,7 @@ var SessionStoreInternal = {
tabsData.push(tabData); tabsData.push(tabData);
} }
@@ -110,7 +129,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 = { @@ -5636,7 +5650,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 +138,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 = { @@ -5748,8 +5762,8 @@ var SessionStoreInternal = {
// selectTab represents. // selectTab represents.
let selectTab = 0; let selectTab = 0;
if (overwriteTabs) { if (overwriteTabs) {
@@ -130,7 +149,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
selectTab = Math.min(selectTab, winData.tabs.length); selectTab = Math.min(selectTab, winData.tabs.length);
} }
@@ -5808,6 +5818,8 @@ var SessionStoreInternal = { @@ -5792,6 +5806,8 @@ var SessionStoreInternal = {
winData.tabs, winData.tabs,
winData.groups ?? [] winData.groups ?? []
); );
@@ -139,7 +158,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 = { @@ -6348,6 +6364,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.
@@ -165,7 +184,7 @@ index 1cdbc0f41bf5b55dfbbd850cb618c6d870f7a261..4eac2fed26d779908107ef60f1c2bd0e
if (tabData.pinned) { if (tabData.pinned) {
tabbrowser.pinTab(tab); tabbrowser.pinTab(tab);
@@ -7289,7 +7320,7 @@ var SessionStoreInternal = { @@ -7263,7 +7298,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

@@ -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

@@ -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

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

View File

@@ -0,0 +1,6 @@
# 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

View File

@@ -2,26 +2,39 @@
// 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/.
const FILE_NAME = 'zen-sessions.jsonlz4'; // 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 { export class nsZenSessionFile {
#path; #path = PathUtils.join(PathUtils.profileDir, FILE_NAME);
#sidebar = [];
#windows;
constructor() {
this.#path = PathUtils.join(profileDir, FILE_NAME);
}
async read() { async read() {
try { try {
return await IOUtils.readJSON(this.#path, { compress: true }); const data = await IOUtils.readJSON(this.#path, { compress: SHOULD_COMPRESS_FILE });
} catch (e) { this.#sidebar = data.sidebar || [];
return {}; } catch {
// File doesn't exist yet, that's fine.
} }
} }
async write(data) { get sidebar() {
await IOUtils.writeJSON(this.#path, data, { compress: true }); 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

@@ -12,23 +12,74 @@ import {
const lazy = {}; const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, { ChromeUtils.defineESModuleGetters(lazy, {
ZenSessionFile: 'resource://gre/modules/ZenSessionFile.sys.mjs', nsZenSessionFile: 'resource:///modules/zen/ZenSessionFile.sys.mjs',
PrivateBrowsingUtils: 'resource://gre/modules/PrivateBrowsingUtils.sys.mjs', PrivateBrowsingUtils: 'resource://gre/modules/PrivateBrowsingUtils.sys.mjs',
RunState: 'resource:///modules/sessionstore/RunState.sys.mjs', BrowserWindowTracker: 'resource:///modules/BrowserWindowTracker.sys.mjs',
TabGroupState: 'resource:///modules/sessionstore/TabGroupState.sys.mjs',
SessionStore: 'resource:///modules/sessionstore/SessionStore.sys.mjs',
}); });
const TAB_CUSTOM_VALUES = new WeakMap();
const LAZY_COLLECT_THRESHOLD = 5 * 60 * 1000; // 5 minutes
const OBSERVING = ['sessionstore-state-write-complete', 'browser-window-before-show'];
class nsZenSessionManager { class nsZenSessionManager {
#file; #file;
constructor() { constructor() {
this.#file = null; this.#file = new lazy.nsZenSessionFile();
} }
get file() { // Called from SessionComponents.manifest on app-startup
if (!this.#file) { init() {
this.#file = lazy.ZenSessionFile; this.#initObservers();
} }
return this.#file;
async readFile() {
await this.#file.read();
}
onFileRead(initialState) {
for (const winData of initialState.windows || []) {
this.restoreWindowData(winData);
}
}
#initObservers() {
for (let topic of OBSERVING) {
Services.obs.addObserver(this, topic);
}
}
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
}
get #topMostWindow() {
return lazy.BrowserWindowTracker.getTopWindow();
} }
/** /**
@@ -38,12 +89,82 @@ class nsZenSessionManager {
* Forces us to recollect data for all windows and will bypass and * Forces us to recollect data for all windows and will bypass and
* update the corresponding caches. * update the corresponding caches.
*/ */
saveState(forceUpdateAllWindows = false) { async #saveState(forceUpdateAllWindows = false) {
if (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing) { if (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing) {
// Don't save (or even collect) anything in permanent private // Don't save (or even collect) anything in permanent private
// browsing mode // browsing mode
return Promise.resolve(); return Promise.resolve();
} }
// 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.
* @param zIndex
* The z-index of the window.
*/
#collectWindowData(window, forceUpdate = false, zIndex = 0) {
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;
}
getNewWindowData(aWindows) {
return { windows: [Cu.cloneInto(aWindows[Object.keys(aWindows)[0]], {})] };
} }
} }

View File

@@ -1,35 +0,0 @@
// 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/.
export class ZenSessionWindow {
#id;
#selectedWorkspace;
#selectedTab;
constructor(id) {
this.#id = id;
this.#selectedWorkspace = null;
this.#selectedTab = null;
}
get id() {
return this.#id;
}
get selectedWorkspace() {
return this.#selectedWorkspace;
}
set selectedWorkspace(workspace) {
this.#selectedWorkspace = workspace;
}
get selectedTab() {
return this.#selectedTab;
}
set selectedTab(tab) {
this.#selectedTab = tab;
}
}

View File

@@ -0,0 +1,8 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXTRA_JS_MODULES.zen += [
"ZenSessionFile.sys.mjs",
"ZenSessionManager.sys.mjs",
]

View File

@@ -213,196 +213,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
} }
async #initializePinnedTabs(init = false) { async #initializePinnedTabs(init = false) {
const pins = this._pinsCache;
if (!pins?.length || !init) {
this.#finishedInitializingPins(); 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) {

View File

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