SessionWindowUI.sys.mjs (9545B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", 9 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", 10 SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", 11 TabMetrics: "moz-src:///browser/components/tabbrowser/TabMetrics.sys.mjs", 12 }); 13 14 /** 15 * This module handles UI interactions for session restore features, 16 * primarily for interacting with browser windows to restore closed tabs 17 * and sessions. 18 */ 19 export var SessionWindowUI = { 20 /** 21 * Applies only to the cmd|ctrl + shift + T keyboard shortcut 22 * Undo the last action that was taken - either closing the last tab or closing the last window; 23 * If none of those were the last actions, restore the last session if possible. 24 */ 25 restoreLastClosedTabOrWindowOrSession(window) { 26 let lastActionTaken = lazy.SessionStore.popLastClosedAction(); 27 if (lastActionTaken) { 28 switch (lastActionTaken.type) { 29 case lazy.SessionStore.LAST_ACTION_CLOSED_TAB: 30 { 31 const sourceWindow = lazy.SessionStore.getWindowForTabClosedId( 32 lastActionTaken.closedId 33 ); 34 this.undoCloseTab(window, undefined, sourceWindow?.__SSi); 35 } 36 break; 37 case lazy.SessionStore.LAST_ACTION_CLOSED_WINDOW: { 38 this.undoCloseWindow(); 39 break; 40 } 41 } 42 } else { 43 let closedTabCount = lazy.SessionStore.getLastClosedTabCount(window); 44 if (lazy.SessionStore.canRestoreLastSession) { 45 lazy.SessionStore.restoreLastSession(); 46 } else if (closedTabCount) { 47 // we need to support users who have automatic session restore enabled 48 this.undoCloseTab(window); 49 } 50 } 51 }, 52 53 /** 54 * Re-open a closed tab into the current window. 55 * 56 * @param window 57 * Window reference 58 * @param [aIndex] 59 * The index of the tab (via SessionStore.getClosedTabData). 60 * When undefined, the first n closed tabs will be re-opened, where n is provided by getLastClosedTabCount. 61 * @param {string} [sourceWindowSSId] 62 * An optional sessionstore id to identify the source window for the tab. 63 * I.e. the window the tab belonged to when closed. 64 * When undefined we'll use the current window 65 * @returns a reference to the reopened tab. 66 */ 67 undoCloseTab(window, aIndex, sourceWindowSSId) { 68 // the window we'll open the tab into 69 let targetWindow = window; 70 // the window the tab was closed from 71 let sourceWindow; 72 if (sourceWindowSSId) { 73 sourceWindow = lazy.SessionStore.getWindowById(sourceWindowSSId); 74 if (!sourceWindow) { 75 throw new Error( 76 "sourceWindowSSId argument to undoCloseTab didn't resolve to a window" 77 ); 78 } 79 } else { 80 sourceWindow = window; 81 } 82 83 // wallpaper patch to prevent an unnecessary blank tab (bug 343895) 84 let blankTabToRemove = null; 85 if ( 86 targetWindow.gBrowser.visibleTabs.length == 1 && 87 targetWindow.gBrowser.selectedTab.isEmpty 88 ) { 89 blankTabToRemove = targetWindow.gBrowser.selectedTab; 90 } 91 92 let tabsRemoved = false; 93 let tab = null; 94 const lastClosedTabGroupId = 95 lazy.SessionStore.getLastClosedTabGroupId(sourceWindow); 96 if (aIndex === undefined && lastClosedTabGroupId) { 97 let group; 98 if (lazy.SessionStore.getSavedTabGroup(lastClosedTabGroupId)) { 99 group = lazy.SessionStore.openSavedTabGroup( 100 lastClosedTabGroupId, 101 targetWindow, 102 { 103 source: lazy.TabMetrics.METRIC_SOURCE.RECENT_TABS, 104 } 105 ); 106 } else { 107 group = lazy.SessionStore.undoCloseTabGroup( 108 window, 109 lastClosedTabGroupId, 110 targetWindow 111 ); 112 } 113 tabsRemoved = true; 114 tab = group.tabs.at(-1); 115 } else { 116 // We are specifically interested in the lastClosedTabCount for the source window. 117 // When aIndex is undefined, we restore all the lastClosedTabCount tabs. 118 let lastClosedTabCount = 119 lazy.SessionStore.getLastClosedTabCount(sourceWindow); 120 // aIndex is undefined if the function is called without a specific tab to restore. 121 let tabsToRemove = 122 aIndex !== undefined ? [aIndex] : new Array(lastClosedTabCount).fill(0); 123 for (let index of tabsToRemove) { 124 if ( 125 lazy.SessionStore.getClosedTabCountForWindow(sourceWindow) > index 126 ) { 127 tab = lazy.SessionStore.undoCloseTab( 128 sourceWindow, 129 index, 130 targetWindow 131 ); 132 tabsRemoved = true; 133 } 134 } 135 } 136 137 if (tabsRemoved && blankTabToRemove) { 138 targetWindow.gBrowser.removeTab(blankTabToRemove); 139 } 140 141 return tab; 142 }, 143 144 /** 145 * Re-open a closed window. 146 * 147 * @param aIndex 148 * The index of the window (via SessionStore.getClosedWindowData) 149 * @returns a reference to the reopened window. 150 */ 151 undoCloseWindow(aIndex) { 152 let restoredWindow = null; 153 if (lazy.SessionStore.getClosedWindowCount() > (aIndex || 0)) { 154 restoredWindow = lazy.SessionStore.undoCloseWindow(aIndex || 0); 155 } 156 157 return restoredWindow; 158 }, 159 160 /** 161 * Only show the infobar when canRestoreLastSession and the pref value == 1 162 */ 163 async maybeShowRestoreSessionInfoBar() { 164 let win = lazy.BrowserWindowTracker.getTopWindow({ 165 allowFromInactiveWorkspace: true, 166 }); 167 let count = Services.prefs.getIntPref( 168 "browser.startup.couldRestoreSession.count", 169 0 170 ); 171 if (count < 0 || count >= 2) { 172 return; 173 } 174 if (count == 0) { 175 // We don't show the infobar right after the update which establishes this pref 176 // Increment the counter so we can consider it next time 177 Services.prefs.setIntPref( 178 "browser.startup.couldRestoreSession.count", 179 ++count 180 ); 181 return; 182 } 183 184 // We've restarted at least once; we will show the notification if possible. 185 // We can't do that if there's no session to restore, or this is a private window. 186 if ( 187 !lazy.SessionStore.canRestoreLastSession || 188 lazy.PrivateBrowsingUtils.isWindowPrivate(win) 189 ) { 190 return; 191 } 192 193 Services.prefs.setIntPref( 194 "browser.startup.couldRestoreSession.count", 195 ++count 196 ); 197 198 const messageFragment = win.document.createDocumentFragment(); 199 const message = win.document.createElement("span"); 200 const icon = win.document.createElement("img"); 201 icon.src = "chrome://browser/skin/menu.svg"; 202 icon.setAttribute("data-l10n-name", "icon"); 203 icon.className = "inline-icon"; 204 message.appendChild(icon); 205 messageFragment.appendChild(message); 206 win.document.l10n.setAttributes( 207 message, 208 "restore-session-startup-suggestion-message" 209 ); 210 211 const buttons = [ 212 { 213 "l10n-id": "restore-session-startup-suggestion-button", 214 primary: true, 215 callback: () => { 216 win.PanelUI.selectAndMarkItem([ 217 "appMenu-history-button", 218 "appMenu-restoreSession", 219 ]); 220 }, 221 }, 222 ]; 223 224 const notifyBox = win.gBrowser.getNotificationBox(); 225 const notification = await notifyBox.appendNotification( 226 "startup-restore-session-suggestion", 227 { 228 label: messageFragment, 229 priority: notifyBox.PRIORITY_INFO_MEDIUM, 230 }, 231 buttons 232 ); 233 // Don't allow it to be immediately hidden: 234 notification.timeout = Date.now() + 3000; 235 }, 236 }; 237 238 export class RestoreLastSessionObserver { 239 constructor(window) { 240 this._window = window; 241 this._window.addEventListener("unload", this); 242 this._observersAdded = false; 243 } 244 245 init() { 246 if ( 247 lazy.SessionStore.canRestoreLastSession && 248 !lazy.PrivateBrowsingUtils.isWindowPrivate(this._window) 249 ) { 250 Services.obs.addObserver(this, "sessionstore-last-session-cleared", true); 251 Services.obs.addObserver( 252 this, 253 "sessionstore-last-session-re-enable", 254 true 255 ); 256 this._observersAdded = true; 257 this._window.goSetCommandEnabled("Browser:RestoreLastSession", true); 258 } else if (lazy.SessionStore.willAutoRestore) { 259 this._window.document.getElementById( 260 "Browser:RestoreLastSession" 261 ).hidden = true; 262 } 263 } 264 265 uninit() { 266 if (this._window) { 267 if (this._observersAdded) { 268 Services.obs.removeObserver(this, "sessionstore-last-session-cleared"); 269 Services.obs.removeObserver( 270 this, 271 "sessionstore-last-session-re-enable" 272 ); 273 this._observersAdded = false; 274 } 275 276 this._window.removeEventListener("unload", this); 277 this._window = null; 278 } 279 } 280 281 handleEvent(event) { 282 if (event.type === "unload") { 283 this.uninit(); 284 } 285 } 286 287 observe(aSubject, aTopic) { 288 if (!this._window) { 289 return; 290 } 291 292 switch (aTopic) { 293 case "sessionstore-last-session-cleared": 294 this._window.goSetCommandEnabled("Browser:RestoreLastSession", false); 295 break; 296 case "sessionstore-last-session-re-enable": 297 this._window.goSetCommandEnabled("Browser:RestoreLastSession", true); 298 break; 299 } 300 } 301 302 QueryInterface = ChromeUtils.generateQI([ 303 "nsIObserver", 304 "nsISupportsWeakReference", 305 ]); 306 }