/* 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", ]); }