Source code
Revision control
Copy as Markdown
Other Tools
/* 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,
// Current version of the format used by Session Restore.
const FORMAT_VERSION = 1;
const PERSIST_SESSIONS = Services.prefs.getBoolPref(
"browser.sessionstore.persist_closed_tabs_between_sessions"
);
const TAB_CUSTOM_VALUES = new WeakMap();
const TAB_LAZY_STATES = new WeakMap();
const TAB_STATE_NEEDS_RESTORE = 1;
const TAB_STATE_RESTORING = 2;
const TAB_STATE_FOR_BROWSER = new WeakMap();
const WINDOW_RESTORE_IDS = new WeakMap();
const WINDOW_RESTORE_ZINDICES = new WeakMap();
const WINDOW_SHOWING_PROMISES = new Map();
const WINDOW_FLUSHING_PROMISES = new Map();
// A new window has just been restored. At this stage, tabs are generally
// not restored.
const NOTIFY_SINGLE_WINDOW_RESTORED = "sessionstore-single-window-restored";
const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored";
const NOTIFY_LAST_SESSION_CLEARED = "sessionstore-last-session-cleared";
const NOTIFY_RESTORING_ON_STARTUP = "sessionstore-restoring-on-startup";
const NOTIFY_INITIATING_MANUAL_RESTORE =
"sessionstore-initiating-manual-restore";
const NOTIFY_CLOSED_OBJECTS_CHANGED = "sessionstore-closed-objects-changed";
const NOTIFY_TAB_RESTORED = "sessionstore-debug-tab-restored"; // WARNING: debug-only
const NOTIFY_DOMWINDOWCLOSED_HANDLED =
"sessionstore-debug-domwindowclosed-handled"; // WARNING: debug-only
const NOTIFY_BROWSER_SHUTDOWN_FLUSH = "sessionstore-browser-shutdown-flush";
// Maximum number of tabs to restore simultaneously. Previously controlled by
// the browser.sessionstore.max_concurrent_tabs pref.
const MAX_CONCURRENT_TAB_RESTORES = 3;
// Minimum amount (in CSS px) by which we allow window edges to be off-screen
// when restoring a window, before we override the saved position to pull the
// window back within the available screen area.
const MIN_SCREEN_EDGE_SLOP = 8;
// global notifications observed
const OBSERVING = [
"browser-window-before-show",
"domwindowclosed",
"quit-application-granted",
"browser-lastwindow-close-granted",
"quit-application",
"browser:purge-session-history",
"browser:purge-session-history-for-domain",
"idle-daily",
"clear-origin-attributes-data",
"browsing-context-did-set-embedder",
"browsing-context-discarded",
"browser-shutdown-tabstate-updated",
];
// XUL Window properties to (re)store
// Restored in restoreDimensions()
const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
const CHROME_FLAGS_MAP = [
[Ci.nsIWebBrowserChrome.CHROME_TITLEBAR, "titlebar"],
[Ci.nsIWebBrowserChrome.CHROME_WINDOW_CLOSE, "close"],
[Ci.nsIWebBrowserChrome.CHROME_TOOLBAR, "toolbar"],
[Ci.nsIWebBrowserChrome.CHROME_LOCATIONBAR, "location"],
[Ci.nsIWebBrowserChrome.CHROME_PERSONAL_TOOLBAR, "personalbar"],
[Ci.nsIWebBrowserChrome.CHROME_STATUSBAR, "status"],
[Ci.nsIWebBrowserChrome.CHROME_MENUBAR, "menubar"],
[Ci.nsIWebBrowserChrome.CHROME_WINDOW_RESIZE, "resizable"],
[Ci.nsIWebBrowserChrome.CHROME_WINDOW_MINIMIZE, "minimizable"],
[Ci.nsIWebBrowserChrome.CHROME_SCROLLBARS, "", "scrollbars=0"],
[Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW, "private"],
[Ci.nsIWebBrowserChrome.CHROME_NON_PRIVATE_WINDOW, "non-private"],
// Do not inherit remoteness and fissionness from the previous session.
//[Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW, "remote", "non-remote"],
//[Ci.nsIWebBrowserChrome.CHROME_FISSION_WINDOW, "fission", "non-fission"],
// "chrome" and "suppressanimation" are always set.
//[Ci.nsIWebBrowserChrome.CHROME_SUPPRESS_ANIMATION, "suppressanimation"],
[Ci.nsIWebBrowserChrome.CHROME_ALWAYS_ON_TOP, "alwaysontop"],
//[Ci.nsIWebBrowserChrome.CHROME_OPENAS_CHROME, "chrome", "chrome=0"],
[Ci.nsIWebBrowserChrome.CHROME_EXTRA, "extrachrome"],
[Ci.nsIWebBrowserChrome.CHROME_CENTER_SCREEN, "centerscreen"],
[Ci.nsIWebBrowserChrome.CHROME_DEPENDENT, "dependent"],
[Ci.nsIWebBrowserChrome.CHROME_MODAL, "modal"],
[Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG, "dialog", "dialog=0"],
];
// Hideable window features to (re)store
// Restored in restoreWindowFeatures()
const WINDOW_HIDEABLE_FEATURES = [
"menubar",
"toolbar",
"locationbar",
"personalbar",
"statusbar",
"scrollbars",
];
const WINDOW_OPEN_FEATURES_MAP = {
locationbar: "location",
statusbar: "status",
};
// These are tab events that we listen to.
const TAB_EVENTS = [
"TabOpen",
"TabBrowserInserted",
"TabClose",
"TabSelect",
"TabShow",
"TabHide",
"TabPinned",
"TabUnpinned",
"TabGroupCreate",
"TabGroupRemoveRequested",
"TabGroupRemoved",
"TabGrouped",
"TabUngrouped",
"TabGroupCollapse",
"TabGroupExpand",
];
/**
* When calling restoreTabContent, we can supply a reason why
* the content is being restored. These are those reasons.
*/
const RESTORE_TAB_CONTENT_REASON = {
/**
* SET_STATE:
* We're restoring this tab's content because we're setting
* state inside this browser tab, probably because the user
* has asked us to restore a tab (or window, or entire session).
*/
SET_STATE: 0,
/**
* NAVIGATE_AND_RESTORE:
* We're restoring this tab's content because a navigation caused
* us to do a remoteness-flip.
*/
NAVIGATE_AND_RESTORE: 1,
};
// 'browser.startup.page' preference value to resume the previous session.
const BROWSER_STARTUP_RESUME_SESSION = 3;
// Used by SessionHistoryListener.
const kNoIndex = Number.MAX_SAFE_INTEGER;
const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
import { TelemetryTimestamps } from "resource://gre/modules/TelemetryTimestamps.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { GlobalState } from "resource:///modules/sessionstore/GlobalState.sys.mjs";
const lazy = {};
XPCOMUtils.defineLazyServiceGetters(lazy, {
gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"],
});
ChromeUtils.defineESModuleGetters(lazy, {
AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.sys.mjs",
E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
HomePage: "resource:///modules/HomePage.sys.mjs",
sessionStoreLogger: "resource:///modules/sessionstore/SessionLogger.sys.mjs",
RunState: "resource:///modules/sessionstore/RunState.sys.mjs",
SessionCookies: "resource:///modules/sessionstore/SessionCookies.sys.mjs",
SessionFile: "resource:///modules/sessionstore/SessionFile.sys.mjs",
SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.sys.mjs",
SessionSaver: "resource:///modules/sessionstore/SessionSaver.sys.mjs",
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
SessionStoreHelper:
"resource://gre/modules/sessionstore/SessionStoreHelper.sys.mjs",
TabAttributes: "resource:///modules/sessionstore/TabAttributes.sys.mjs",
TabCrashHandler: "resource:///modules/ContentCrashHandlers.sys.mjs",
TabGroupState: "resource:///modules/sessionstore/TabGroupState.sys.mjs",
TabState: "resource:///modules/sessionstore/TabState.sys.mjs",
TabStateCache: "resource:///modules/sessionstore/TabStateCache.sys.mjs",
TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.sys.mjs",
setTimeout: "resource://gre/modules/Timer.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "blankURI", () => {
return Services.io.newURI("about:blank");
});
/**
* |true| if we are in debug mode, |false| otherwise.
* Debug mode is controlled by preference browser.sessionstore.debug
*/
var gDebuggingEnabled = false;
/**
* @namespace SessionStore
*/
export var SessionStore = {
get logger() {
return SessionStoreInternal._log;
},
get promiseInitialized() {
return SessionStoreInternal.promiseInitialized;
},
get promiseAllWindowsRestored() {
return SessionStoreInternal.promiseAllWindowsRestored;
},
get canRestoreLastSession() {
return SessionStoreInternal.canRestoreLastSession;
},
set canRestoreLastSession(val) {
SessionStoreInternal.canRestoreLastSession = val;
},
get lastClosedObjectType() {
return SessionStoreInternal.lastClosedObjectType;
},
get lastClosedActions() {
return [...SessionStoreInternal._lastClosedActions];
},
get LAST_ACTION_CLOSED_TAB() {
return SessionStoreInternal._LAST_ACTION_CLOSED_TAB;
},
get LAST_ACTION_CLOSED_WINDOW() {
return SessionStoreInternal._LAST_ACTION_CLOSED_WINDOW;
},
get savedGroups() {
return SessionStoreInternal._savedGroups;
},
get willAutoRestore() {
return SessionStoreInternal.willAutoRestore;
},
init: function ss_init() {
SessionStoreInternal.init();
},
/**
* Get the collection of all matching windows tracked by SessionStore
* @param {Window|Object} [aWindowOrOptions] Optionally an options object or a window to used to determine if we're filtering for private or non-private windows
* @param {boolean} [aWindowOrOptions.private] Determine if we should filter for private or non-private windows
*/
getWindows(aWindowOrOptions) {
return SessionStoreInternal.getWindows(aWindowOrOptions);
},
/**
* Get window a given closed tab belongs to
* @param {integer} aClosedId The closedId of the tab whose window we want to find
* @param {boolean} [aIncludePrivate] Optionally include private windows when searching for the closed tab
*/
getWindowForTabClosedId(aClosedId, aIncludePrivate) {
return SessionStoreInternal.getWindowForTabClosedId(
aClosedId,
aIncludePrivate
);
},
getBrowserState: function ss_getBrowserState() {
return SessionStoreInternal.getBrowserState();
},
setBrowserState: function ss_setBrowserState(aState) {
SessionStoreInternal.setBrowserState(aState);
},
getWindowState: function ss_getWindowState(aWindow) {
return SessionStoreInternal.getWindowState(aWindow);
},
setWindowState: function ss_setWindowState(aWindow, aState, aOverwrite) {
SessionStoreInternal.setWindowState(aWindow, aState, aOverwrite);
},
getTabState: function ss_getTabState(aTab) {
return SessionStoreInternal.getTabState(aTab);
},
setTabState: function ss_setTabState(aTab, aState) {
SessionStoreInternal.setTabState(aTab, aState);
},
// Return whether a tab is restoring.
isTabRestoring(aTab) {
return TAB_STATE_FOR_BROWSER.has(aTab.linkedBrowser);
},
getInternalObjectState(obj) {
return SessionStoreInternal.getInternalObjectState(obj);
},
duplicateTab: function ss_duplicateTab(
aWindow,
aTab,
aDelta = 0,
aRestoreImmediately = true,
aOptions = {}
) {
return SessionStoreInternal.duplicateTab(
aWindow,
aTab,
aDelta,
aRestoreImmediately,
aOptions
);
},
/**
* How many tabs were last closed. If multiple tabs were selected and closed together,
* we'll return that number. Normally the count is 1, or 0 if no tabs have been
* recently closed in this window.
* @returns the number of tabs that were last closed.
*/
getLastClosedTabCount(aWindow) {
return SessionStoreInternal.getLastClosedTabCount(aWindow);
},
resetLastClosedTabCount(aWindow) {
SessionStoreInternal.resetLastClosedTabCount(aWindow);
},
/**
* Get the number of closed tabs associated with a specific window
* @param {Window} aWindow
*/
getClosedTabCountForWindow: function ss_getClosedTabCountForWindow(aWindow) {
return SessionStoreInternal.getClosedTabCountForWindow(aWindow);
},
/**
* Get the number of closed tabs associated with all matching windows
* @param {Window|Object} [aOptions]
* Either a DOMWindow (see aOptions.sourceWindow) or an object with properties
to identify which closed tabs to include in the count.
* @param {Window} aOptions.sourceWindow
A browser window used to identity privateness.
When closedTabsFromAllWindows is false, we only count closed tabs assocated with this window.
* @param {boolean} [aOptions.private = false]
Explicit indicator to constrain tab count to only private or non-private windows,
* @param {boolean} [aOptions.closedTabsFromAllWindows]
Override the value of the closedTabsFromAllWindows preference.
* @param {boolean} [aOptions.closedTabsFromClosedWindows]
Override the value of the closedTabsFromClosedWindows preference.
*/
getClosedTabCount: function ss_getClosedTabCount(aOptions) {
return SessionStoreInternal.getClosedTabCount(aOptions);
},
/**
* Get the number of closed tabs from recently closed window
*
* This is normally only relevant in a non-private window context, as we don't
* keep data from closed private windows.
*/
getClosedTabCountFromClosedWindows:
function ss_getClosedTabCountFromClosedWindows() {
return SessionStoreInternal.getClosedTabCountFromClosedWindows();
},
/**
* Get the closed tab data associated with this window
* @param {Window} aWindow
*/
getClosedTabDataForWindow: function ss_getClosedTabDataForWindow(aWindow) {
return SessionStoreInternal.getClosedTabDataForWindow(aWindow);
},
/**
* Get the closed tab data associated with all matching windows
* @param {Window|Object} [aOptions]
* Either a DOMWindow (see aOptions.sourceWindow) or an object with properties
to identify which closed tabs to get data from
* @param {Window} aOptions.sourceWindow
A browser window used to identity privateness.
When closedTabsFromAllWindows is false, we only include closed tabs assocated with this window.
* @param {boolean} [aOptions.private = false]
Explicit indicator to constrain tab data to only private or non-private windows,
* @param {boolean} [aOptions.closedTabsFromAllWindows]
Override the value of the closedTabsFromAllWindows preference.
* @param {boolean} [aOptions.closedTabsFromClosedWindows]
Override the value of the closedTabsFromClosedWindows preference.
*/
getClosedTabData: function ss_getClosedTabData(aOptions) {
return SessionStoreInternal.getClosedTabData(aOptions);
},
/**
* Get the closed tab data associated with all closed windows
* @returns an un-sorted array of tabData for closed tabs from closed windows
*/
getClosedTabDataFromClosedWindows:
function ss_getClosedTabDataFromClosedWindows() {
return SessionStoreInternal.getClosedTabDataFromClosedWindows();
},
/**
* Get the closed tab group data associated with all matching windows
* @param {Window|object} aOptions
* Either a DOMWindow (see aOptions.sourceWindow) or an object with properties
to identify the window source of the closed tab groups
* @param {Window} [aOptions.sourceWindow]
A browser window used to identity privateness.
When closedTabsFromAllWindows is false, we only include closed tab groups assocated with this window.
* @param {boolean} [aOptions.private = false]
Explicit indicator to constrain tab group data to only private or non-private windows,
* @param {boolean} [aOptions.closedTabsFromAllWindows]
Override the value of the closedTabsFromAllWindows preference.
* @param {boolean} [aOptions.closedTabsFromClosedWindows]
Override the value of the closedTabsFromClosedWindows preference.
* @returns {ClosedTabGroupStateData[]}
*/
getClosedTabGroups: function ss_getClosedTabGroups(aOptions) {
return SessionStoreInternal.getClosedTabGroups(aOptions);
},
/**
* Get the last closed tab ID associated with a specific window
* @param {Window} aWindow
*/
getLastClosedTabGroupId(window) {
return SessionStoreInternal.getLastClosedTabGroupId(window);
},
/**
* Re-open a closed tab
* @param {Window|Object} aSource
* Either a DOMWindow or an object with properties to resolve to the window
* the tab was previously open in.
* @param {String} aSource.sourceWindowId
A SessionStore window id used to look up the window where the tab was closed
* @param {number} aSource.sourceClosedId
The closedId used to look up the closed window where the tab was closed
* @param {Integer} [aIndex = 0]
* The index of the tab in the closedTabs array (via SessionStore.getClosedTabData), where 0 is most recent.
* @param {Window} [aTargetWindow = aWindow] Optional window to open the tab into, defaults to current (topWindow).
* @returns a reference to the reopened tab.
*/
undoCloseTab: function ss_undoCloseTab(aSource, aIndex, aTargetWindow) {
return SessionStoreInternal.undoCloseTab(aSource, aIndex, aTargetWindow);
},
/**
* Re-open a tab from a closed window, which corresponds to the closedId
* @param {Window|Object} aSource
* Either a DOMWindow or an object with properties to resolve to the window
* the tab was previously open in.
* @param {String} aSource.sourceWindowId
A SessionStore window id used to look up the window where the tab was closed
* @param {number} aSource.sourceClosedId
The closedId used to look up the closed window where the tab was closed
* @param {integer} aClosedId
* The closedId of the tab or window
* @param {Window} [aTargetWindow = aWindow] Optional window to open the tab into, defaults to current (topWindow).
* @returns a reference to the reopened tab.
*/
undoClosedTabFromClosedWindow: function ss_undoClosedTabFromClosedWindow(
aSource,
aClosedId,
aTargetWindow
) {
return SessionStoreInternal.undoClosedTabFromClosedWindow(
aSource,
aClosedId,
aTargetWindow
);
},
/**
* Forget a closed tab associated with a given window
* Removes the record at the given index so it cannot be un-closed or appear
* in a list of recently-closed tabs
*
* @param {Window|Object} aSource
* Either a DOMWindow or an object with properties to resolve to the window
* the tab was previously open in.
* @param {String} aSource.sourceWindowId
A SessionStore window id used to look up the window where the tab was closed
* @param {number} aSource.sourceClosedId
The closedId used to look up the closed window where the tab was closed
* @param {Integer} [aIndex = 0]
* The index into the window's list of closed tabs
* @throws {InvalidArgumentError} if the window is not tracked by SessionStore, or index is out of bounds
*/
forgetClosedTab: function ss_forgetClosedTab(aSource, aIndex) {
return SessionStoreInternal.forgetClosedTab(aSource, aIndex);
},
/**
* Forget a closed tab group associated with a given window
* Removes the record at the given index so it cannot be un-closed or appear
* in a list of recently-closed tabs
*
* @param {Window|Object} aSource
* Either a DOMWindow or an object with properties to resolve to the window
* the tab was previously open in.
* @param {String} aSource.sourceWindowId
A SessionStore window id used to look up the window where the tab group was closed
* @param {number} aSource.sourceClosedId
The closedId used to look up the closed window where the tab group was closed
* @param {string} tabGroupId
* The tab group ID of the closed tab group
* @throws {InvalidArgumentError}
* if the window or tab group is not tracked by SessionStore
*/
forgetClosedTabGroup: function ss_forgetClosedTabGroup(aSource, tabGroupId) {
return SessionStoreInternal.forgetClosedTabGroup(aSource, tabGroupId);
},
/**
* Forget a closed tab that corresponds to the closedId
* Removes the record with this closedId so it cannot be un-closed or appear
* in a list of recently-closed tabs
*
* @param {integer} aClosedId
* The closedId of the tab
* @param {Window|Object} aSourceOptions
* Either a DOMWindow or an object with properties to resolve to the window
* the tab was previously open in.
* @param {boolean} [aSourceOptions.includePrivate = true]
If no other means of resolving a source window is given, this flag is used to
constrain a search across all open window's closed tabs.
* @param {String} aSourceOptions.sourceWindowId
A SessionStore window id used to look up the window where the tab was closed
* @param {number} aSourceOptions.sourceClosedId
The closedId used to look up the closed window where the tab was closed
* @throws {InvalidArgumentError} if the closedId doesnt match a closed tab in any window
*/
forgetClosedTabById: function ss_forgetClosedTabById(
aClosedId,
aSourceOptions
) {
SessionStoreInternal.forgetClosedTabById(aClosedId, aSourceOptions);
},
/**
* Forget a closed window.
* Removes the record with this closedId so it cannot be un-closed or appear
* in a list of recently-closed windows
*
* @param {integer} aClosedId
* The closedId of the window
* @throws {InvalidArgumentError} if the closedId doesnt match a closed window
*/
forgetClosedWindowById: function ss_forgetClosedWindowById(aClosedId) {
SessionStoreInternal.forgetClosedWindowById(aClosedId);
},
/**
* Look up the object type ("tab" or "window") for a given closedId
* @param {integer} aClosedId
*/
getObjectTypeForClosedId(aClosedId) {
return SessionStoreInternal.getObjectTypeForClosedId(aClosedId);
},
/**
* Look up a window tracked by SessionStore by its id
* @param {String} aSessionStoreId
*/
getWindowById: function ss_getWindowById(aSessionStoreId) {
return SessionStoreInternal.getWindowById(aSessionStoreId);
},
getClosedWindowCount: function ss_getClosedWindowCount() {
return SessionStoreInternal.getClosedWindowCount();
},
// this should only be used by one caller (currently restoreLastClosedTabOrWindowOrSession in browser.js)
popLastClosedAction: function ss_popLastClosedAction() {
return SessionStoreInternal._lastClosedActions.pop();
},
// for testing purposes
resetLastClosedActions: function ss_resetLastClosedActions() {
SessionStoreInternal._lastClosedActions = [];
},
getClosedWindowData: function ss_getClosedWindowData() {
return SessionStoreInternal.getClosedWindowData();
},
maybeDontRestoreTabs(aWindow) {
SessionStoreInternal.maybeDontRestoreTabs(aWindow);
},
undoCloseWindow: function ss_undoCloseWindow(aIndex) {
return SessionStoreInternal.undoCloseWindow(aIndex);
},
forgetClosedWindow: function ss_forgetClosedWindow(aIndex) {
return SessionStoreInternal.forgetClosedWindow(aIndex);
},
getCustomWindowValue(aWindow, aKey) {
return SessionStoreInternal.getCustomWindowValue(aWindow, aKey);
},
setCustomWindowValue(aWindow, aKey, aStringValue) {
SessionStoreInternal.setCustomWindowValue(aWindow, aKey, aStringValue);
},
deleteCustomWindowValue(aWindow, aKey) {
SessionStoreInternal.deleteCustomWindowValue(aWindow, aKey);
},
getCustomTabValue(aTab, aKey) {
return SessionStoreInternal.getCustomTabValue(aTab, aKey);
},
setCustomTabValue(aTab, aKey, aStringValue) {
SessionStoreInternal.setCustomTabValue(aTab, aKey, aStringValue);
},
deleteCustomTabValue(aTab, aKey) {
SessionStoreInternal.deleteCustomTabValue(aTab, aKey);
},
getLazyTabValue(aTab, aKey) {
return SessionStoreInternal.getLazyTabValue(aTab, aKey);
},
getCustomGlobalValue(aKey) {
return SessionStoreInternal.getCustomGlobalValue(aKey);
},
setCustomGlobalValue(aKey, aStringValue) {
SessionStoreInternal.setCustomGlobalValue(aKey, aStringValue);
},
deleteCustomGlobalValue(aKey) {
SessionStoreInternal.deleteCustomGlobalValue(aKey);
},
restoreLastSession: function ss_restoreLastSession() {
SessionStoreInternal.restoreLastSession();
},
speculativeConnectOnTabHover(tab) {
SessionStoreInternal.speculativeConnectOnTabHover(tab);
},
getCurrentState(aUpdateAll) {
return SessionStoreInternal.getCurrentState(aUpdateAll);
},
reviveCrashedTab(aTab) {
return SessionStoreInternal.reviveCrashedTab(aTab);
},
reviveAllCrashedTabs() {
return SessionStoreInternal.reviveAllCrashedTabs();
},
updateSessionStoreFromTablistener(
aBrowser,
aBrowsingContext,
aPermanentKey,
aData,
aForStorage
) {
return SessionStoreInternal.updateSessionStoreFromTablistener(
aBrowser,
aBrowsingContext,
aPermanentKey,
aData,
aForStorage
);
},
getSessionHistory(tab, updatedCallback) {
return SessionStoreInternal.getSessionHistory(tab, updatedCallback);
},
/**
* Re-open a tab or window which corresponds to the closedId
*
* @param {integer} aClosedId
* The closedId of the tab or window
* @param {boolean} [aIncludePrivate = true]
* Whether to match the aClosedId to only closed private tabs/windows or non-private
* @param {Window} [aTargetWindow]
* When aClosedId is for a closed tab, which window to re-open the tab into.
* Defaults to current (topWindow).
*
* @returns a tab or window object
*/
undoCloseById(aClosedId, aIncludePrivate, aTargetWindow) {
return SessionStoreInternal.undoCloseById(
aClosedId,
aIncludePrivate,
aTargetWindow
);
},
resetBrowserToLazyState(tab) {
return SessionStoreInternal.resetBrowserToLazyState(tab);
},
maybeExitCrashedState(browser) {
SessionStoreInternal.maybeExitCrashedState(browser);
},
isBrowserInCrashedSet(browser) {
return SessionStoreInternal.isBrowserInCrashedSet(browser);
},
// this is used for testing purposes
resetNextClosedId() {
SessionStoreInternal._nextClosedId = 0;
},
/**
* Ensures that session store has registered and started tracking a given window.
* @param window
* Window reference
*/
ensureInitialized(window) {
if (SessionStoreInternal._sessionInitialized && !window.__SSi) {
/*
We need to check that __SSi is not defined on the window so that if
onLoad function is in the middle of executing we don't enter the function
again and try to redeclare the ContentSessionStore script.
*/
SessionStoreInternal.onLoad(window);
}
},
getCurrentEpoch(browser) {
return SessionStoreInternal.getCurrentEpoch(browser.permanentKey);
},
/**
* Determines whether the passed version number is compatible with
* the current version number of the SessionStore.
*
* @param version The format and version of the file, as an array, e.g.
* ["sessionrestore", 1]
*/
isFormatVersionCompatible(version) {
if (!version) {
return false;
}
if (!Array.isArray(version)) {
// Improper format.
return false;
}
if (version[0] != "sessionrestore") {
// Not a Session Restore file.
return false;
}
let number = Number.parseFloat(version[1]);
if (Number.isNaN(number)) {
return false;
}
return number <= FORMAT_VERSION;
},
/**
* Filters out not worth-saving tabs from a given browser state object.
*
* @param aState (object)
* The browser state for which we remove worth-saving tabs.
* The given object will be modified.
*/
keepOnlyWorthSavingTabs(aState) {
let closedWindowShouldRestore = null;
for (let i = aState.windows.length - 1; i >= 0; i--) {
let win = aState.windows[i];
for (let j = win.tabs.length - 1; j >= 0; j--) {
let tab = win.tabs[j];
if (!SessionStoreInternal._shouldSaveTab(tab)) {
win.tabs.splice(j, 1);
if (win.selected > j) {
win.selected--;
}
}
}
// If it's the last window (and no closedWindow that will restore), keep the window state with no tabs.
if (
!win.tabs.length &&
(aState.windows.length > 1 ||
closedWindowShouldRestore ||
(closedWindowShouldRestore == null &&
(closedWindowShouldRestore = aState._closedWindows.some(
w => w._shouldRestore
))))
) {
aState.windows.splice(i, 1);
if (aState.selectedWindow > i) {
aState.selectedWindow--;
}
}
}
},
/**
* Clear session store data for a given private browsing window.
* @param {ChromeWindow} win - Open private browsing window to clear data for.
*/
purgeDataForPrivateWindow(win) {
return SessionStoreInternal.purgeDataForPrivateWindow(win);
},
/**
* Add a tab group to the session's saved group list.
* @param {MozTabbrowserTabGroup} tabGroup - The group to save
*/
addSavedTabGroup(tabGroup) {
return SessionStoreInternal.addSavedTabGroup(tabGroup);
},
/**
* Retrieve the tab group state of a saved tab group by ID.
*
* @param {string} tabGroupId
* @returns {SavedTabGroupStateData|undefined}
*/
getSavedTabGroup(tabGroupId) {
return SessionStoreInternal.getSavedTabGroup(tabGroupId);
},
/**
* Returns all tab groups that were saved in this session.
* @returns {SavedTabGroupStateData[]}
*/
getSavedTabGroups() {
return SessionStoreInternal.getSavedTabGroups();
},
/**
* Remove a tab group from the session's saved tab group list.
* @param {string} tabGroupId
* The ID of the tab group to remove
*/
forgetSavedTabGroup(tabGroupId) {
return SessionStoreInternal.forgetSavedTabGroup(tabGroupId);
},
/**
* Re-open a closed tab group
* @param {Window|Object} source
* Either a DOMWindow or an object with properties to resolve to the window
* the tab was previously open in.
* @param {string} source.sourceWindowId
A SessionStore window id used to look up the window where the tab was closed.
* @param {number} source.sourceClosedId
The closedId used to look up the closed window where the tab was closed.
* @param {string} tabGroupId
* The unique ID of the group to restore.
* @param {Window} [targetWindow] defaults to the top window if not specified.
* @returns {MozTabbrowserTabGroup}
* a reference to the restored tab group in a browser window.
*/
undoCloseTabGroup(source, tabGroupId, targetWindow) {
return SessionStoreInternal.undoCloseTabGroup(
source,
tabGroupId,
targetWindow
);
},
/**
* Re-open a saved tab group.
* Note that this method does not require passing a window source, as saved
* tab groups are independent of windows.
* Attempting to open a saved tab group in a private window will raise an error.
* @param {string} tabGroupId
* The unique ID of the group to restore.
* @param {Window} [targetWindow] defaults to the top window if not specified.
* @returns {MozTabbrowserTabGroup}
* a reference to the restored tab group in a browser window.
*/
openSavedTabGroup(tabGroupId, targetWindow) {
return SessionStoreInternal.openSavedTabGroup(tabGroupId, targetWindow);
},
/**
* Determine whether a group is saveable, based on whether any of its tabs
* are saveable per ssi_shouldSaveTabState.
* @param {MozTabbrowserTabGroup} group the tab group to check
* @returns {boolean} true if the group can be saved, false if it should
* be discarded.
*/
shouldSaveTabGroup(group) {
return SessionStoreInternal.shouldSaveTabGroup(group);
},
};
// Freeze the SessionStore object. We don't want anyone to modify it.
Object.freeze(SessionStore);
/**
* @namespace SessionStoreInternal
*
* @description Internal implementations and helpers for the public SessionStore methods
*/
var SessionStoreInternal = {
QueryInterface: ChromeUtils.generateQI([
"nsIObserver",
"nsISupportsWeakReference",
]),
_globalState: new GlobalState(),
// A counter to be used to generate a unique ID for each closed tab or window.
_nextClosedId: 0,
// During the initial restore and setBrowserState calls tracks the number of
// windows yet to be restored
_restoreCount: -1,
// For each <browser> element, records the SHistoryListener.
_browserSHistoryListener: new WeakMap(),
// Tracks the various listeners that are used throughout the restore.
_restoreListeners: new WeakMap(),
// Records the promise created in _restoreHistory, which is used to track
// the completion of the first phase of the restore.
_tabStateRestorePromises: new WeakMap(),
// The history data needed to be restored in the parent.
_tabStateToRestore: new WeakMap(),
// For each <browser> element, records the current epoch.
_browserEpochs: new WeakMap(),
// Any browsers that fires the oop-browser-crashed event gets stored in
// here - that way we know which browsers to ignore messages from (until
// they get restored).
_crashedBrowsers: new WeakSet(),
// A map (xul:browser -> FrameLoader) that maps a browser to the last
// associated frameLoader we heard about.
_lastKnownFrameLoader: new WeakMap(),
// A map (xul:browser -> object) that maps a browser associated with a
// recently closed tab to all its necessary state information we need to
// properly handle final update message.
_closingTabMap: new WeakMap(),
// A map (xul:browser -> object) that maps a browser associated with a
// recently closed tab due to a window closure to the tab state information
// that is being stored in _closedWindows for that tab.
_tabClosingByWindowMap: new WeakMap(),
// A set of window data that has the potential to be saved in the _closedWindows
// array for the session. We will remove window data from this set whenever
// forgetClosedWindow is called for the window, or when session history is
// purged, so that we don't accidentally save that data after the flush has
// completed. Closed tabs use a more complicated mechanism for this particular
// problem. When forgetClosedTab is called, the browser is removed from the
// _closingTabMap, so its data is not recorded. In the purge history case,
// the closedTabs array per window is overwritten so that once the flush is
// complete, the tab would only ever add itself to an array that SessionStore
// work more like the window case, which is more explicit, and easier to
// reason about.
_saveableClosedWindowData: new WeakSet(),
// whether a setBrowserState call is in progress
_browserSetState: false,
// time in milliseconds when the session was started (saved across sessions),
// defaults to now if no session was restored or timestamp doesn't exist
_sessionStartTime: Date.now(),
/**
* states for all currently opened windows
* @type {object.<WindowID, WindowStateData>}
*/
_windows: {},
// counter for creating unique window IDs
_nextWindowID: 0,
// states for all recently closed windows
_closedWindows: [],
/** @type {SavedTabGroupStateData[]} states for all saved+closed tab groups */
_savedGroups: [],
// collection of session states yet to be restored
_statesToRestore: {},
// counts the number of crashes since the last clean start
_recentCrashes: 0,
// whether the last window was closed and should be restored
_restoreLastWindow: false,
// number of tabs currently restoring
_tabsRestoringCount: 0,
/**
* @typedef {Object} CloseAction
* @property {string} type
* What the close action acted upon. One of either _LAST_ACTION_CLOSED_TAB or
* _LAST_ACTION_CLOSED_WINDOW
* @property {number} closedId
* The unique ID of the item that closed.
*/
/**
* An in-order stack of close actions for tabs and windows.
* @type {CloseAction[]}
*/
_lastClosedActions: [],
/**
* Removes an object from the _lastClosedActions list
*
* @param closedAction
* Either _LAST_ACTION_CLOSED_TAB or _LAST_ACTION_CLOSED_WINDOW
* @param {integer} closedId
* The closedId of a tab or window
*/
_removeClosedAction(closedAction, closedId) {
let closedActionIndex = this._lastClosedActions.findIndex(
obj => obj.type == closedAction && obj.closedId == closedId
);
if (closedActionIndex > -1) {
this._lastClosedActions.splice(closedActionIndex, 1);
}
},
/**
* Add an object to the _lastClosedActions list and truncates the list if needed
*
* @param closedAction
* Either _LAST_ACTION_CLOSED_TAB or _LAST_ACTION_CLOSED_WINDOW
* @param {integer} closedId
* The closedId of a tab or window
*/
_addClosedAction(closedAction, closedId) {
this._lastClosedActions.push({
type: closedAction,
closedId,
});
let maxLength = this._max_tabs_undo * this._max_windows_undo;
if (this._lastClosedActions.length > maxLength) {
this._lastClosedActions = this._lastClosedActions.slice(-maxLength);
}
},
_LAST_ACTION_CLOSED_TAB: "tab",
_LAST_ACTION_CLOSED_WINDOW: "window",
_log: null,
// When starting Firefox with a single private window, this is the place
// where we keep the session we actually wanted to restore in case the user
// decides to later open a non-private window as well.
_deferredInitialState: null,
// Keeps track of whether a notification needs to be sent that closed objects have changed.
_closedObjectsChanged: false,
// A promise resolved once initialization is complete
_deferredInitialized: (function () {
let deferred = {};
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
})(),
// Whether session has been initialized
_sessionInitialized: false,
// A promise resolved once all windows are restored.
_deferredAllWindowsRestored: (function () {
let deferred = {};
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
})(),
get promiseAllWindowsRestored() {
return this._deferredAllWindowsRestored.promise;
},
// Promise that is resolved when we're ready to initialize
// and restore the session.
_promiseReadyForInitialization: null,
// Keep busy state counters per window.
_windowBusyStates: new WeakMap(),
/**
* A promise fulfilled once initialization is complete.
*/
get promiseInitialized() {
return this._deferredInitialized.promise;
},
get canRestoreLastSession() {
return LastSession.canRestore;
},
set canRestoreLastSession(val) {
// Cheat a bit; only allow false.
if (!val) {
LastSession.clear();
}
},
/**
* Returns a string describing the last closed object, either "tab" or "window".
*
* This was added to support the sessions.restore WebExtensions API.
*/
get lastClosedObjectType() {
if (this._closedWindows.length) {
// Since there are closed windows, we need to check if there's a closed tab
// in one of the currently open windows that was closed after the
// last-closed window.
let tabTimestamps = [];
for (let window of Services.wm.getEnumerator("navigator:browser")) {
let windowState = this._windows[window.__SSi];
if (windowState && windowState._closedTabs[0]) {
tabTimestamps.push(windowState._closedTabs[0].closedAt);
}
}
if (
!tabTimestamps.length ||
tabTimestamps.sort((a, b) => b - a)[0] < this._closedWindows[0].closedAt
) {
return this._LAST_ACTION_CLOSED_WINDOW;
}
}
return this._LAST_ACTION_CLOSED_TAB;
},
/**
* Returns a boolean that determines whether the session will be automatically
* restored upon the _next_ startup or a restart.
*/
get willAutoRestore() {
return (
!PrivateBrowsingUtils.permanentPrivateBrowsing &&
(Services.prefs.getBoolPref("browser.sessionstore.resume_session_once") ||
Services.prefs.getIntPref("browser.startup.page") ==
BROWSER_STARTUP_RESUME_SESSION)
);
},
/**
* Initialize the sessionstore service.
*/
init() {
if (this._initialized) {
throw new Error("SessionStore.init() must only be called once!");
}
TelemetryTimestamps.add("sessionRestoreInitialized");
OBSERVING.forEach(function (aTopic) {
Services.obs.addObserver(this, aTopic, true);
}, this);
this._initPrefs();
this._initialized = true;
this.promiseAllWindowsRestored.finally(() => () => {
this._log.debug("promiseAllWindowsRestored finalized");
});
},
/**
* Initialize the session using the state provided by SessionStartup
*/
initSession() {
TelemetryStopwatch.start("FX_SESSION_RESTORE_STARTUP_INIT_SESSION_MS");
let state;
let ss = lazy.SessionStartup;
let willRestore = ss.willRestore();
if (willRestore || ss.sessionType == ss.DEFER_SESSION) {
state = ss.state;
}
this._log.debug(
`initSession willRestore: ${willRestore}, SessionStartup.sessionType: ${ss.sessionType}`
);
if (state) {
try {
// If we're doing a DEFERRED session, then we want to pull pinned tabs
// out so they can be restored.
if (ss.sessionType == ss.DEFER_SESSION) {
let [iniState, remainingState] =
this._prepDataForDeferredRestore(state);
// If we have a iniState with windows, that means that we have windows
// with pinned tabs to restore.
if (iniState.windows.length) {
state = iniState;
} else {
state = null;
}
this._log.debug(
`initSession deferred restore with ${iniState.windows.length} initial windows, ${remainingState.windows.length} remaining windows`
);
if (remainingState.windows.length) {
LastSession.setState(remainingState);
}
Glean.browserEngagement.sessionrestoreInterstitial.deferred_restore.add(
1
);
} else {
// Get the last deferred session in case the user still wants to
// restore it
LastSession.setState(state.lastSessionState);
let restoreAsCrashed = ss.willRestoreAsCrashed();
if (restoreAsCrashed) {
this._recentCrashes =
((state.session && state.session.recentCrashes) || 0) + 1;
this._log.debug(
`initSession, restoreAsCrashed, crashes: ${this._recentCrashes}`
);
// _needsRestorePage will record sessionrestore_interstitial,
// including the specific reason we decided we needed to show
// about:sessionrestore, if that's what we do.
if (this._needsRestorePage(state, this._recentCrashes)) {
// replace the crashed session with a restore-page-only session
let url = "about:sessionrestore";
let formdata = { id: { sessionData: state }, url };
let entry = {
url,
triggeringPrincipal_base64:
lazy.E10SUtils.SERIALIZED_SYSTEMPRINCIPAL,
};
state = { windows: [{ tabs: [{ entries: [entry], formdata }] }] };
this._log.debug("initSession, will show about:sessionrestore");
} else if (
this._hasSingleTabWithURL(state.windows, "about:welcomeback")
) {
this._log.debug("initSession, will show about:welcomeback");
Glean.browserEngagement.sessionrestoreInterstitial.shown_only_about_welcomeback.add(
1
);
// On a single about:welcomeback URL that crashed, replace about:welcomeback
// with about:sessionrestore, to make clear to the user that we crashed.
state.windows[0].tabs[0].entries[0].url = "about:sessionrestore";
state.windows[0].tabs[0].entries[0].triggeringPrincipal_base64 =
lazy.E10SUtils.SERIALIZED_SYSTEMPRINCIPAL;
} else {
restoreAsCrashed = false;
}
}
// If we didn't use about:sessionrestore, record that:
if (!restoreAsCrashed) {
Glean.browserEngagement.sessionrestoreInterstitial.autorestore.add(
1
);
this._log.debug("initSession, will autorestore");
this._removeExplicitlyClosedTabs(state);
}
// Update the session start time using the restored session state.
this._updateSessionStartTime(state);
// Make sure that at least the first window doesn't have anything hidden.
delete state.windows[0].hidden;
// Since nothing is hidden in the first window, it cannot be a popup.
delete state.windows[0].isPopup;
// We don't want to minimize and then open a window at startup.
if (state.windows[0].sizemode == "minimized") {
state.windows[0].sizemode = "normal";
}
// clear any lastSessionWindowID attributes since those don't matter
// during normal restore
state.windows.forEach(function (aWindow) {
delete aWindow.__lastSessionWindowID;
});
}
// clear _maybeDontRestoreTabs because we have restored (or not)
// windows and so they don't matter
state?.windows?.forEach(win => delete win._maybeDontRestoreTabs);
state?._closedWindows?.forEach(win => delete win._maybeDontRestoreTabs);
this._savedGroups = state?.savedGroups ?? [];
} catch (ex) {
this._log.error("The session file is invalid: ", ex);
}
}
// at this point, we've as good as resumed the session, so we can
// clear the resume_session_once flag, if it's set
if (
!lazy.RunState.isQuitting &&
this._prefBranch.getBoolPref("sessionstore.resume_session_once")
) {
this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
}
TelemetryStopwatch.finish("FX_SESSION_RESTORE_STARTUP_INIT_SESSION_MS");
return state;
},
/**
* When initializing session, if we are restoring the last session at startup,
* close open tabs or close windows marked _maybeDontRestoreTabs (if they were closed
* by closing remaining tabs).
*/
_removeExplicitlyClosedTabs(state) {
// Don't restore tabs that has been explicitly closed
for (let i = 0; i < state.windows.length; ) {
const winData = state.windows[i];
if (winData._maybeDontRestoreTabs) {
if (state.windows.length == 1) {
// it's the last window, we just want to close tabs
let j = 0;
// reset close group (we don't want to append tabs to existing group close).
winData._lastClosedTabGroupCount = -1;
while (winData.tabs.length) {
const tabState = winData.tabs.pop();
// Ensure the index is in bounds.
let activeIndex = (tabState.index || tabState.entries.length) - 1;
activeIndex = Math.min(activeIndex, tabState.entries.length - 1);
activeIndex = Math.max(activeIndex, 0);
let title = "";
if (activeIndex in tabState.entries) {
title =
tabState.entries[activeIndex].title ||
tabState.entries[activeIndex].url;
}
const tabData = {
state: tabState,
title,
image: tabState.image,
pos: j++,
closedAt: Date.now(),
closedInGroup: true,
};
if (this._shouldSaveTabState(tabState)) {
this.saveClosedTabData(winData, winData._closedTabs, tabData);
}
}
} else {
// We can remove the window since it doesn't have any
// tabs that we should restore and it's not the only window
if (winData.tabs.some(this._shouldSaveTabState)) {
winData.closedAt = Date.now();
state._closedWindows.unshift(winData);
}
state.windows.splice(i, 1);
continue; // we don't want to increment the index
}
}
i++;
}
},
_initPrefs() {
this._prefBranch = Services.prefs.getBranch("browser.");
gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
Services.prefs.addObserver("browser.sessionstore.debug", () => {
gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
});
this._log = lazy.sessionStoreLogger;
this._max_tabs_undo = this._prefBranch.getIntPref(
"sessionstore.max_tabs_undo"
);
this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
this._closedTabsFromAllWindowsEnabled = this._prefBranch.getBoolPref(
"sessionstore.closedTabsFromAllWindows"
);
this._prefBranch.addObserver(
"sessionstore.closedTabsFromAllWindows",
this,
true
);
this._closedTabsFromClosedWindowsEnabled = this._prefBranch.getBoolPref(
"sessionstore.closedTabsFromClosedWindows"
);
this._prefBranch.addObserver(
"sessionstore.closedTabsFromClosedWindows",
this,
true
);
this._max_windows_undo = this._prefBranch.getIntPref(
"sessionstore.max_windows_undo"
);
this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true);
this._restore_on_demand = this._prefBranch.getBoolPref(
"sessionstore.restore_on_demand"
);
this._prefBranch.addObserver("sessionstore.restore_on_demand", this, true);
},
/**
* Called on application shutdown, after notifications:
* quit-application-granted, quit-application
*/
_uninit: function ssi_uninit() {
if (!this._initialized) {
throw new Error("SessionStore is not initialized.");
}
// Prepare to close the session file and write the last state.
lazy.RunState.setClosing();
// save all data for session resuming
if (this._sessionInitialized) {
lazy.SessionSaver.run();
}
// clear out priority queue in case it's still holding refs
TabRestoreQueue.reset();
// Make sure to cancel pending saves.
lazy.SessionSaver.cancel();
},
/**
* Handle notifications
*/
observe: function ssi_observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "browser-window-before-show": // catch new windows
this.onBeforeBrowserWindowShown(aSubject);
break;
case "domwindowclosed": // catch closed windows
this.onClose(aSubject).then(() => {
this._notifyOfClosedObjectsChange();
});
if (gDebuggingEnabled) {
Services.obs.notifyObservers(null, NOTIFY_DOMWINDOWCLOSED_HANDLED);
}
break;
case "quit-application-granted": {
let syncShutdown = aData == "syncShutdown";
this.onQuitApplicationGranted(syncShutdown);
break;
}
case "browser-lastwindow-close-granted":
this.onLastWindowCloseGranted();
break;
case "quit-application":
this.onQuitApplication(aData);
break;
case "browser:purge-session-history": // catch sanitization
this.onPurgeSessionHistory();
this._notifyOfClosedObjectsChange();
break;
case "browser:purge-session-history-for-domain":
this.onPurgeDomainData(aData);
this._notifyOfClosedObjectsChange();
break;
case "nsPref:changed": // catch pref changes
this.onPrefChange(aData);
this._notifyOfClosedObjectsChange();
break;
case "idle-daily":
this.onIdleDaily();
this._notifyOfClosedObjectsChange();
break;
case "clear-origin-attributes-data": {
let userContextId = 0;
try {
userContextId = JSON.parse(aData).userContextId;
} catch (e) {}
if (userContextId) {
this._forgetTabsWithUserContextId(userContextId);
}
break;
}
case "browsing-context-did-set-embedder":
if (aSubject === aSubject.top && aSubject.isContent) {
const permanentKey = aSubject.embedderElement?.permanentKey;
if (permanentKey) {
this.maybeRecreateSHistoryListener(permanentKey, aSubject);
}
}
break;
case "browsing-context-discarded": {
let permanentKey = aSubject?.embedderElement?.permanentKey;
if (permanentKey) {
this._browserSHistoryListener.get(permanentKey)?.unregister();
}
break;
}
case "browser-shutdown-tabstate-updated":
this.onFinalTabStateUpdateComplete(aSubject);
this._notifyOfClosedObjectsChange();
break;
}
},
getOrCreateSHistoryListener(permanentKey, browsingContext) {
if (!permanentKey || browsingContext !== browsingContext.top) {
return null;
}
const listener = this._browserSHistoryListener.get(permanentKey);
if (listener) {
return listener;
}
return this.createSHistoryListener(permanentKey, browsingContext, false);
},
maybeRecreateSHistoryListener(permanentKey, browsingContext) {
const listener = this._browserSHistoryListener.get(permanentKey);
if (!listener || listener._browserId != browsingContext.browserId) {
listener?.unregister(permanentKey);
this.createSHistoryListener(permanentKey, browsingContext, true);
}
},
createSHistoryListener(permanentKey, browsingContext, collectImmediately) {
class SHistoryListener {
constructor() {
this.QueryInterface = ChromeUtils.generateQI([
"nsISHistoryListener",
"nsISupportsWeakReference",
]);
this._browserId = browsingContext.browserId;
this._fromIndex = kNoIndex;
}
unregister() {
let bc = BrowsingContext.getCurrentTopByBrowserId(this._browserId);
bc?.sessionHistory?.removeSHistoryListener(this);
SessionStoreInternal._browserSHistoryListener.delete(permanentKey);
}
collect(
permanentKey, // eslint-disable-line no-shadow
browsingContext, // eslint-disable-line no-shadow
{ collectFull = true, writeToCache = false }
) {
// Don't bother doing anything if we haven't seen any navigations.
if (!collectFull && this._fromIndex === kNoIndex) {
return null;
}
TelemetryStopwatch.start(
"FX_SESSION_RESTORE_COLLECT_SESSION_HISTORY_MS"
);
let fromIndex = collectFull ? -1 : this._fromIndex;
this._fromIndex = kNoIndex;
let historychange = lazy.SessionHistory.collectFromParent(
browsingContext.currentURI?.spec,
browsingContext.sessionHistory,
fromIndex
);
if (writeToCache) {
let win =
browsingContext.embedderElement?.ownerGlobal ||
browsingContext.currentWindowGlobal?.browsingContext?.window;
SessionStoreInternal.onTabStateUpdate(permanentKey, win, {
data: { historychange },
});
}
TelemetryStopwatch.finish(
"FX_SESSION_RESTORE_COLLECT_SESSION_HISTORY_MS"
);
return historychange;
}
collectFrom(index) {
if (this._fromIndex <= index) {
// If we already know that we need to update history from index N we
// can ignore any changes that happened with an element with index
// larger than N.
//
// Note: initially we use kNoIndex which is MAX_SAFE_INTEGER which
// means we don't ignore anything here, and in case of navigation in
// the history back and forth cases we use kLastIndex which ignores
// only the subsequent navigations, but not any new elements added.
return;
}
let bc = BrowsingContext.getCurrentTopByBrowserId(this._browserId);
if (bc?.embedderElement?.frameLoader) {
this._fromIndex = index;
// Queue a tab state update on the |browser.sessionstore.interval|
// timer. We'll call this.collect() when we receive the update.
bc.embedderElement.frameLoader.requestSHistoryUpdate();
}
}
OnHistoryNewEntry(newURI, oldIndex) {
// We use oldIndex - 1 to collect the current entry as well. This makes
// sure to collect any changes that were made to the entry while the
// document was active.
this.collectFrom(oldIndex == -1 ? oldIndex : oldIndex - 1);
}
OnHistoryGotoIndex() {
this.collectFrom(kLastIndex);
}
OnHistoryPurge() {
this.collectFrom(-1);
}
OnHistoryReload() {
this.collectFrom(-1);
return true;
}
OnHistoryReplaceEntry() {
this.collectFrom(-1);
}
}
let sessionHistory = browsingContext.sessionHistory;
if (!sessionHistory) {
return null;
}