tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }