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,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
TabMetrics: "moz-src:///browser/components/tabbrowser/TabMetrics.sys.mjs",
});
/**
* This module handles UI interactions for session restore features,
* primarily for interacting with browser windows to restore closed tabs
* and sessions.
*/
export var SessionWindowUI = {
/**
* Applies only to the cmd|ctrl + shift + T keyboard shortcut
* Undo the last action that was taken - either closing the last tab or closing the last window;
* If none of those were the last actions, restore the last session if possible.
*/
restoreLastClosedTabOrWindowOrSession(window) {
let lastActionTaken = lazy.SessionStore.popLastClosedAction();
if (lastActionTaken) {
switch (lastActionTaken.type) {
case lazy.SessionStore.LAST_ACTION_CLOSED_TAB: {
this.undoCloseTab(window);
break;
}
case lazy.SessionStore.LAST_ACTION_CLOSED_WINDOW: {
this.undoCloseWindow();
break;
}
}
} else {
let closedTabCount = lazy.SessionStore.getLastClosedTabCount(window);
if (lazy.SessionStore.canRestoreLastSession) {
lazy.SessionStore.restoreLastSession();
} else if (closedTabCount) {
// we need to support users who have automatic session restore enabled
this.undoCloseTab(window);
}
}
},
/**
* Re-open a closed tab into the current window.
* @param window
* Window reference
* @param [aIndex]
* The index of the tab (via SessionStore.getClosedTabData).
* When undefined, the first n closed tabs will be re-opened, where n is provided by getLastClosedTabCount.
* @param {string} [sourceWindowSSId]
* An optional sessionstore id to identify the source window for the tab.
* I.e. the window the tab belonged to when closed.
* When undefined we'll use the current window
* @returns a reference to the reopened tab.
*/
undoCloseTab(window, aIndex, sourceWindowSSId) {
// the window we'll open the tab into
let targetWindow = window;
// the window the tab was closed from
let sourceWindow;
if (sourceWindowSSId) {
sourceWindow = lazy.SessionStore.getWindowById(sourceWindowSSId);
if (!sourceWindow) {
throw new Error(
"sourceWindowSSId argument to undoCloseTab didn't resolve to a window"
);
}
} else {
sourceWindow = window;
}
// wallpaper patch to prevent an unnecessary blank tab (bug 343895)
let blankTabToRemove = null;
if (
targetWindow.gBrowser.visibleTabs.length == 1 &&
targetWindow.gBrowser.selectedTab.isEmpty
) {
blankTabToRemove = targetWindow.gBrowser.selectedTab;
}
let tabsRemoved = false;
let tab = null;
const lastClosedTabGroupId =
lazy.SessionStore.getLastClosedTabGroupId(sourceWindow);
if (aIndex === undefined && lastClosedTabGroupId) {
let group;
if (lazy.SessionStore.getSavedTabGroup(lastClosedTabGroupId)) {
group = lazy.SessionStore.openSavedTabGroup(
lastClosedTabGroupId,
targetWindow,
{
source: lazy.TabMetrics.METRIC_SOURCE.RECENT_TABS,
}
);
} else {
group = lazy.SessionStore.undoCloseTabGroup(
window,
lastClosedTabGroupId,
targetWindow
);
}
tabsRemoved = true;
tab = group.tabs.at(-1);
} else {
// We are specifically interested in the lastClosedTabCount for the source window.
// When aIndex is undefined, we restore all the lastClosedTabCount tabs.
let lastClosedTabCount =
lazy.SessionStore.getLastClosedTabCount(sourceWindow);
// aIndex is undefined if the function is called without a specific tab to restore.
let tabsToRemove =
aIndex !== undefined ? [aIndex] : new Array(lastClosedTabCount).fill(0);
for (let index of tabsToRemove) {
if (
lazy.SessionStore.getClosedTabCountForWindow(sourceWindow) > index
) {
tab = lazy.SessionStore.undoCloseTab(
sourceWindow,
index,
targetWindow
);
tabsRemoved = true;
}
}
}
if (tabsRemoved && blankTabToRemove) {
targetWindow.gBrowser.removeTab(blankTabToRemove);
}
return tab;
},
/**
* Re-open a closed window.
* @param aIndex
* The index of the window (via SessionStore.getClosedWindowData)
* @returns a reference to the reopened window.
*/
undoCloseWindow(aIndex) {
let restoredWindow = null;
if (lazy.SessionStore.getClosedWindowCount() > (aIndex || 0)) {
restoredWindow = lazy.SessionStore.undoCloseWindow(aIndex || 0);
}
return restoredWindow;
},
/**
* Only show the infobar when canRestoreLastSession and the pref value == 1
*/
async maybeShowRestoreSessionInfoBar() {
let win = lazy.BrowserWindowTracker.getTopWindow();
let count = Services.prefs.getIntPref(
"browser.startup.couldRestoreSession.count",
0
);
if (count < 0 || count >= 2) {
return;
}
if (count == 0) {
// We don't show the infobar right after the update which establishes this pref
// Increment the counter so we can consider it next time
Services.prefs.setIntPref(
"browser.startup.couldRestoreSession.count",
++count
);
return;
}
// We've restarted at least once; we will show the notification if possible.
// We can't do that if there's no session to restore, or this is a private window.
if (
!lazy.SessionStore.canRestoreLastSession ||
lazy.PrivateBrowsingUtils.isWindowPrivate(win)
) {
return;
}
Services.prefs.setIntPref(
"browser.startup.couldRestoreSession.count",
++count
);
const messageFragment = win.document.createDocumentFragment();
const message = win.document.createElement("span");
const icon = win.document.createElement("img");
icon.src = "chrome://browser/skin/menu.svg";
icon.setAttribute("data-l10n-name", "icon");
icon.className = "inline-icon";
message.appendChild(icon);
messageFragment.appendChild(message);
win.document.l10n.setAttributes(
message,
"restore-session-startup-suggestion-message"
);
const buttons = [
{
"l10n-id": "restore-session-startup-suggestion-button",
primary: true,
callback: () => {
win.PanelUI.selectAndMarkItem([
"appMenu-history-button",
"appMenu-restoreSession",
]);
},
},
];
const notifyBox = win.gBrowser.getNotificationBox();
const notification = await notifyBox.appendNotification(
"startup-restore-session-suggestion",
{
label: messageFragment,
priority: notifyBox.PRIORITY_INFO_MEDIUM,
},
buttons
);
// Don't allow it to be immediately hidden:
notification.timeout = Date.now() + 3000;
},
};
export class RestoreLastSessionObserver {
constructor(window) {
this._window = window;
this._window.addEventListener("unload", this);
this._observersAdded = false;
}
init() {
if (
lazy.SessionStore.canRestoreLastSession &&
!lazy.PrivateBrowsingUtils.isWindowPrivate(this._window)
) {
Services.obs.addObserver(this, "sessionstore-last-session-cleared", true);
Services.obs.addObserver(
this,
"sessionstore-last-session-re-enable",
true
);
this._observersAdded = true;
this._window.goSetCommandEnabled("Browser:RestoreLastSession", true);
} else if (lazy.SessionStore.willAutoRestore) {
this._window.document.getElementById(
"Browser:RestoreLastSession"
).hidden = true;
}
}
uninit() {
if (this._window) {
if (this._observersAdded) {
Services.obs.removeObserver(this, "sessionstore-last-session-cleared");
Services.obs.removeObserver(
this,
"sessionstore-last-session-re-enable"
);
this._observersAdded = false;
}
this._window.removeEventListener("unload", this);
this._window = null;
}
}
handleEvent(event) {
if (event.type === "unload") {
this.uninit();
}
}
observe(aSubject, aTopic) {
if (!this._window) {
return;
}
switch (aTopic) {
case "sessionstore-last-session-cleared":
this._window.goSetCommandEnabled("Browser:RestoreLastSession", false);
break;
case "sessionstore-last-session-re-enable":
this._window.goSetCommandEnabled("Browser:RestoreLastSession", true);
break;
}
}
QueryInterface = ChromeUtils.generateQI([
"nsIObserver",
"nsISupportsWeakReference",
]);
}