mirror of
https://github.com/zen-browser/desktop.git
synced 2026-02-11 22:38:40 +00:00
fix: Make sure to save the state instantly on some occations, b=bug #12152, c=scripts
This commit is contained in:
@@ -262,6 +262,18 @@ export class nsZenSessionManager {
|
||||
}
|
||||
return initialState;
|
||||
}
|
||||
const allowRestoreUnsynced = Services.prefs.getBoolPref(
|
||||
"zen.session-store.restore-unsynced-windows",
|
||||
true
|
||||
);
|
||||
if (initialState?.windows?.length && !allowRestoreUnsynced) {
|
||||
initialState.windows = initialState.windows.filter((win) => {
|
||||
if (win.isZenUnsynced) {
|
||||
this.log("Skipping unsynced window during restore");
|
||||
}
|
||||
return !win.isZenUnsynced;
|
||||
});
|
||||
}
|
||||
// If there are no windows, we create an empty one. By default,
|
||||
// firefox would create simply a new empty window, but we want
|
||||
// to make sure that the sidebar object is properly initialized.
|
||||
@@ -300,27 +312,18 @@ export class nsZenSessionManager {
|
||||
// Restore all windows with the same sidebar object, this will
|
||||
// guarantee that all tabs, groups, folders and split view data
|
||||
// are properly synced across all windows.
|
||||
const allowRestoreUnsynced = Services.prefs.getBoolPref(
|
||||
"zen.session-store.restore-unsynced-windows",
|
||||
true
|
||||
);
|
||||
if (!this._shouldRunMigration) {
|
||||
this.log(`Restoring Zen session data into ${initialState.windows?.length || 0} windows`);
|
||||
for (let i = 0; i < initialState.windows.length; i++) {
|
||||
let winData = initialState.windows[i];
|
||||
if (winData.isZenUnsynced) {
|
||||
if (!allowRestoreUnsynced) {
|
||||
// We don't wan't to restore any unsynced windows with the sidebar data.
|
||||
this.log("Skipping restore of unsynced window");
|
||||
delete initialState.windows[i];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
this.#restoreWindowData(winData);
|
||||
}
|
||||
} else if (initialState) {
|
||||
this.log("Saving windata state after migration");
|
||||
this.saveState(Cu.cloneInto(initialState, {}));
|
||||
this.saveState(Cu.cloneInto(initialState, {}), true);
|
||||
}
|
||||
delete this._shouldRunMigration;
|
||||
}
|
||||
@@ -438,8 +441,11 @@ export class nsZenSessionManager {
|
||||
* Saves the current session state. Collects data and writes to disk.
|
||||
*
|
||||
* @param {object} state The current session state.
|
||||
* @param {boolean} soon Whether to save the file soon or immediately.
|
||||
* If true, the file will be saved asynchronously or when quitting
|
||||
* the app. If false, the file will be saved immediately.
|
||||
*/
|
||||
saveState(state) {
|
||||
saveState(state, soon = false) {
|
||||
let windows = state?.windows || [];
|
||||
windows = windows.filter((win) => this.#isWindowSaveable(win));
|
||||
if (!windows.length) {
|
||||
@@ -448,11 +454,14 @@ export class nsZenSessionManager {
|
||||
return;
|
||||
}
|
||||
this.#collectWindowData(windows);
|
||||
// This would save the data to disk asynchronously or when
|
||||
// quitting the app.
|
||||
// This would save the data to disk asynchronously or when quitting the app.
|
||||
let sidebar = this.#sidebar;
|
||||
this.#file.data = sidebar;
|
||||
this.#file.saveSoon();
|
||||
if (soon) {
|
||||
this.#file.saveSoon();
|
||||
} else {
|
||||
this.#file._save();
|
||||
}
|
||||
this.#debounceRegeneration();
|
||||
this.log(`Saving Zen session data with ${sidebar.tabs?.length || 0} tabs`);
|
||||
}
|
||||
@@ -533,7 +542,7 @@ export class nsZenSessionManager {
|
||||
return;
|
||||
}
|
||||
this.log("Saving closed window session data into Zen session store");
|
||||
this.saveState({ windows: [aWinData] });
|
||||
this.saveState({ windows: [aWinData] }, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,7 @@ XPCOMUtils.defineLazyPreferenceGetter(
|
||||
XPCOMUtils.defineLazyPreferenceGetter(lazy, "gShouldLog", "zen.window-sync.log", true);
|
||||
|
||||
const OBSERVING = ["browser-window-before-show", "sessionstore-windows-restored"];
|
||||
const INSTANT_EVENTS = ["SSWindowClosing"];
|
||||
const INSTANT_EVENTS = ["SSWindowClosing", "TabSelect", "focus"];
|
||||
const UNSYNCED_WINDOW_EVENTS = ["TabOpen"];
|
||||
const EVENTS = [
|
||||
"TabClose",
|
||||
@@ -50,9 +50,6 @@ const EVENTS = [
|
||||
"ZenTabRemovedFromSplit",
|
||||
"ZenSplitViewTabsSplit",
|
||||
|
||||
"TabSelect",
|
||||
|
||||
"focus",
|
||||
...INSTANT_EVENTS,
|
||||
...UNSYNCED_WINDOW_EVENTS,
|
||||
];
|
||||
@@ -81,6 +78,13 @@ class nsZenWindowSync {
|
||||
lastHandlerPromise: Promise.resolve(),
|
||||
};
|
||||
|
||||
/**
|
||||
* Promise that resolves when the current docshell swap operation is finished.
|
||||
* Used to avoid multiple simultaneous swap operations that could interfere with each other.
|
||||
* For example, when focusing a window AND selecting a tab at the same time.
|
||||
*/
|
||||
#docShellSwitchPromise = Promise.resolve();
|
||||
|
||||
/**
|
||||
* Map of sync handlers for different event types.
|
||||
* Each handler is a function that takes the event as an argument.
|
||||
@@ -814,15 +818,17 @@ class nsZenWindowSync {
|
||||
};
|
||||
});
|
||||
|
||||
await promiseToWait;
|
||||
this.#createPseudoImageForBrowser(otherBrowser, mySrc);
|
||||
this.#maybeRemovePseudoImageForBrowser(ourBrowser);
|
||||
ourBrowser.removeAttribute("zen-pseudo-hidden");
|
||||
otherBrowser.setAttribute("zen-pseudo-hidden", "true");
|
||||
let promise = this.#createPseudoImageForBrowser(otherBrowser, mySrc);
|
||||
await Promise.all([promiseToWait, promise]);
|
||||
aOurTab.ownerGlobal.requestAnimationFrame(() => {
|
||||
otherBrowser.setAttribute("zen-pseudo-hidden", "true");
|
||||
ourBrowser.removeAttribute("zen-pseudo-hidden");
|
||||
this.#maybeRemovePseudoImageForBrowser(ourBrowser);
|
||||
});
|
||||
callback();
|
||||
} else {
|
||||
this.#maybeRemovePseudoImageForBrowser(ourBrowser);
|
||||
ourBrowser.removeAttribute("zen-pseudo-hidden");
|
||||
this.#maybeRemovePseudoImageForBrowser(ourBrowser);
|
||||
}
|
||||
|
||||
resolve();
|
||||
@@ -837,10 +843,25 @@ class nsZenWindowSync {
|
||||
*/
|
||||
#createPseudoImageForBrowser(aBrowser, aSrc) {
|
||||
const doc = aBrowser.ownerDocument;
|
||||
const win = aBrowser.ownerGlobal;
|
||||
const img = doc.createElement("img");
|
||||
img.className = "zen-pseudo-browser-image";
|
||||
img.src = aSrc;
|
||||
let promise = new Promise((resolve) => {
|
||||
if (img.complete) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
let finish = () => {
|
||||
win.requestAnimationFrame(() => {
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
img.onload = finish;
|
||||
img.onerror = finish;
|
||||
});
|
||||
aBrowser.after(img);
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -914,14 +935,8 @@ class nsZenWindowSync {
|
||||
*
|
||||
* @param {Window} aWindow - The window that triggered the event.
|
||||
* @param {object} aPreviousTab - The previously selected tab.
|
||||
* @param {boolean} ignoreSameTab - Indicates if the same tab should be ignored.
|
||||
*/
|
||||
async #onTabSwitchOrWindowFocus(aWindow, aPreviousTab = null, ignoreSameTab = false) {
|
||||
// On some occasions, such as when closing a window, this
|
||||
// function might be called multiple times for the same tab.
|
||||
if (aWindow.gBrowser.selectedTab === this.#lastSelectedTab && !ignoreSameTab) {
|
||||
return;
|
||||
}
|
||||
async #onTabSwitchOrWindowFocus(aWindow, aPreviousTab = null) {
|
||||
let activeBrowsers = aWindow.gBrowser.selectedBrowsers;
|
||||
let activeTabs = activeBrowsers.map((browser) => aWindow.gBrowser.getTabForBrowser(browser));
|
||||
// Ignore previous tabs that are still "active". These scenarios could happen for example,
|
||||
@@ -1214,11 +1229,12 @@ class nsZenWindowSync {
|
||||
});
|
||||
}
|
||||
|
||||
on_focus(aEvent) {
|
||||
async on_focus(aEvent) {
|
||||
if (typeof aEvent.target !== "object") {
|
||||
return;
|
||||
}
|
||||
const { ownerGlobal: window } = aEvent.target;
|
||||
await this.#docShellSwitchPromise;
|
||||
const window = Services.focus.activeWindow;
|
||||
if (
|
||||
!window?.gBrowser ||
|
||||
this.#lastFocusedWindow?.deref() === window ||
|
||||
@@ -1229,17 +1245,21 @@ class nsZenWindowSync {
|
||||
}
|
||||
this.#lastFocusedWindow = new WeakRef(window);
|
||||
this.#lastSelectedTab = new WeakRef(window.gBrowser.selectedTab);
|
||||
return this.#onTabSwitchOrWindowFocus(window);
|
||||
return (this.#docShellSwitchPromise = this.#onTabSwitchOrWindowFocus(window));
|
||||
}
|
||||
|
||||
on_TabSelect(aEvent) {
|
||||
async on_TabSelect(aEvent) {
|
||||
await this.#docShellSwitchPromise;
|
||||
const tab = aEvent.target;
|
||||
if (this.#lastSelectedTab?.deref() === tab) {
|
||||
return;
|
||||
}
|
||||
this.#lastSelectedTab = new WeakRef(tab);
|
||||
const previousTab = aEvent.detail.previousTab;
|
||||
return this.#onTabSwitchOrWindowFocus(aEvent.target.ownerGlobal, previousTab);
|
||||
return (this.#docShellSwitchPromise = this.#onTabSwitchOrWindowFocus(
|
||||
aEvent.target.ownerGlobal,
|
||||
previousTab
|
||||
));
|
||||
}
|
||||
|
||||
on_SSWindowClosing(aEvent) {
|
||||
@@ -1270,7 +1290,7 @@ class nsZenWindowSync {
|
||||
|
||||
// If the page has a title, set it. When doing a swap and we still didn't
|
||||
// flush the tab state, the title might not be correct.
|
||||
if (activePageData) {
|
||||
if (activePageData && win.gBrowser) {
|
||||
win.gBrowser.setInitialTabTitle(tab, activePageData.title, {
|
||||
isContentTitle: activePageData.title && activePageData.title != activePageData.url,
|
||||
});
|
||||
@@ -1375,7 +1395,7 @@ class nsZenWindowSync {
|
||||
|
||||
return new Promise((resolve) => {
|
||||
lazy.setTimeout(() => {
|
||||
this.#onTabSwitchOrWindowFocus(window, null, /* ignoreSameTab = */ true).finally(resolve);
|
||||
this.#onTabSwitchOrWindowFocus(window, null).finally(resolve);
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user