mirror of
https://github.com/zen-browser/desktop.git
synced 2025-12-19 12:55:43 +00:00
feat: Start doing out own session restore, b=no-bug, c=folders, tabs
This commit is contained in:
@@ -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;
|
||||||
|
},
|
||||||
|
|
||||||
@@ -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;
|
||||||
@@ -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; ) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 = [];
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ DIRS += [
|
|||||||
"tests",
|
"tests",
|
||||||
"urlbar",
|
"urlbar",
|
||||||
"toolkit",
|
"toolkit",
|
||||||
|
"sessionstore",
|
||||||
]
|
]
|
||||||
|
|||||||
6
src/zen/sessionstore/SessionComponents.manifest
Normal file
6
src/zen/sessionstore/SessionComponents.manifest
Normal 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
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
async readFile() {
|
||||||
|
await this.#file.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileRead(initialState) {
|
||||||
|
for (const winData of initialState.windows || []) {
|
||||||
|
this.restoreWindowData(winData);
|
||||||
}
|
}
|
||||||
return this.#file;
|
}
|
||||||
|
|
||||||
|
#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]], {})] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
8
src/zen/sessionstore/moz.build
Normal file
8
src/zen/sessionstore/moz.build
Normal 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",
|
||||||
|
]
|
||||||
@@ -212,199 +212,10 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
|||||||
this.hasInitializedPins = true;
|
this.hasInitializedPins = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
if (!this.enabled) return;
|
if (!this.enabled) return;
|
||||||
const tab = event.target;
|
const tab = event.target;
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export default [
|
|||||||
'ZEN_KEYSET_ID',
|
'ZEN_KEYSET_ID',
|
||||||
|
|
||||||
'gZenPinnedTabManager',
|
'gZenPinnedTabManager',
|
||||||
'ZenPinnedTabsStorage',
|
|
||||||
|
|
||||||
'gZenEmojiPicker',
|
'gZenEmojiPicker',
|
||||||
'gZenSessionStore',
|
'gZenSessionStore',
|
||||||
|
|||||||
Reference in New Issue
Block a user