tor-browser

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

TabManager.sys.mjs (8254B)


      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
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
      9  EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
     10  MobileTabBrowser: "chrome://remote/content/shared/MobileTabBrowser.sys.mjs",
     11  UserContextManager:
     12    "chrome://remote/content/shared/UserContextManager.sys.mjs",
     13  windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
     14 });
     15 
     16 class TabManagerClass {
     17  /**
     18   * Retrieve all the tabs in open browser windows.
     19   *
     20   * @returns {Array<Tab>}
     21   *     All the open browser tabs. Will return an empty list if tab browser
     22   *     is not available or tabs are undefined.
     23   */
     24  get allTabs() {
     25    return lazy.windowManager.windows.flatMap(win =>
     26      this.getTabsForWindow(win)
     27    );
     28  }
     29 
     30  /**
     31   * Get the linked `xul:browser` for the specified tab.
     32   *
     33   * @param {Tab} tab
     34   *     The tab whose browser needs to be returned.
     35   *
     36   * @returns {XULBrowser|null}
     37   *     The linked browser for the tab or `null` if no browser can be found.
     38   */
     39  getBrowserForTab(tab) {
     40    return tab?.linkedBrowser ?? null;
     41  }
     42 
     43  /**
     44   * Retrieve all the browser elements from tabs as contained in open windows.
     45   *
     46   * By default excludes browsers for unloaded tabs.
     47   *
     48   * @param {object=} options
     49   * @param {boolean=} options.unloaded
     50   *     Pass true to also retrieve browsers for unloaded tabs. Defaults to
     51   *     false.
     52   *
     53   * @returns {Array<XULBrowser>}
     54   *     All the found <xul:browser>s. Will return an empty array if
     55   *     no windows and tabs can be found.
     56   */
     57  getBrowsers(options = {}) {
     58    const { unloaded = false } = options;
     59 
     60    return this.allTabs
     61      .map(tab => this.getBrowserForTab(tab))
     62      .filter(browser => {
     63        return (
     64          browser !== null &&
     65          (unloaded ||
     66            this.isValidCanonicalBrowsingContext(browser.browsingContext))
     67        );
     68      });
     69  }
     70 
     71  /**
     72   * Return the tab browser for the specified chrome window.
     73   *
     74   * @param {ChromeWindow} win
     75   *     Window whose <code>tabbrowser</code> needs to be accessed.
     76   *
     77   * @returns {TabBrowser|null}
     78   *     Tab browser or `null` if it's not a browser window.
     79   */
     80  getTabBrowser(win) {
     81    if (!win) {
     82      return null;
     83    }
     84 
     85    if (lazy.AppInfo.isAndroid) {
     86      return new lazy.MobileTabBrowser(win);
     87    } else if (lazy.AppInfo.isFirefox) {
     88      return win.gBrowser;
     89    }
     90 
     91    return null;
     92  }
     93 
     94  /**
     95   * Create a new tab.
     96   *
     97   * @param {object} options
     98   * @param {boolean=} options.focus
     99   *     Set to true if the new tab should be focused (selected). Defaults to
    100   *     false. `false` value is not properly supported on Android, additional
    101   *     focus of previously selected tab is required after initial navigation.
    102   * @param {Tab=} options.referenceTab
    103   *     The reference tab after which the new tab will be added. If no
    104   *     reference tab is provided, the new tab will be added after all the
    105   *     other tabs.
    106   * @param {string=} options.userContextId
    107   *     A user context id from UserContextManager.
    108   * @param {window=} options.window
    109   *     The window where the new tab will open. Defaults to
    110   *     Services.wm.getMostRecentBrowserWindow if no window is provided.
    111   *     Will be ignored if referenceTab is provided.
    112   */
    113  async addTab(options = {}) {
    114    let {
    115      focus = false,
    116      referenceTab = null,
    117      userContextId = null,
    118      window = Services.wm.getMostRecentBrowserWindow(),
    119    } = options;
    120 
    121    let tabIndex;
    122    if (referenceTab != null) {
    123      // If a reference tab was specified, the window should be the window
    124      // owning the reference tab.
    125      window = this.getWindowForTab(referenceTab);
    126    }
    127 
    128    if (referenceTab != null) {
    129      tabIndex = this.getTabsForWindow(window).indexOf(referenceTab) + 1;
    130    }
    131 
    132    const tabBrowser = this.getTabBrowser(window);
    133 
    134    const tab = await tabBrowser.addTab("about:blank", {
    135      tabIndex,
    136      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    137      userContextId: lazy.UserContextManager.getInternalIdById(userContextId),
    138    });
    139 
    140    if (focus) {
    141      await this.selectTab(tab);
    142    }
    143 
    144    return tab;
    145  }
    146 
    147  /**
    148   * Retrieve the count of all the open tabs.
    149   *
    150   * @returns {number} Number of open tabs.
    151   */
    152  getTabCount() {
    153    return lazy.windowManager.windows.reduce((total, win) => {
    154      // For browser windows count the tabs. Otherwise take the window itself.
    155      const tabsLength = this.getTabsForWindow(win).length;
    156      return total + (tabsLength ? tabsLength : 1);
    157    }, 0);
    158  }
    159 
    160  /**
    161   * Retrieve the tab owning a Browsing Context.
    162   *
    163   * @param {BrowsingContext=} browsingContext
    164   *     The browsing context to get the tab from.
    165   *
    166   * @returns {Tab|null}
    167   *     The tab owning the Browsing Context.
    168   */
    169  getTabForBrowsingContext(browsingContext) {
    170    const browser = browsingContext?.top.embedderElement;
    171    if (!browser) {
    172      return null;
    173    }
    174 
    175    const tabBrowser = this.getTabBrowser(browser.ownerGlobal);
    176    return tabBrowser.getTabForBrowser(browser);
    177  }
    178 
    179  /**
    180   * Retrieve the list of tabs for a given window.
    181   *
    182   * @param {ChromeWindow} win
    183   *     Window whose tabs need to be returned.
    184   *
    185   * @returns {Array<Tab>}
    186   *     The list of tabs. Will return an empty list if tab browser is not available
    187   *     or tabs are undefined.
    188   */
    189  getTabsForWindow(win) {
    190    const tabBrowser = this.getTabBrowser(win);
    191 
    192    // For web-platform reftests a faked tabbrowser is used,
    193    // which does not actually have tabs.
    194    if (tabBrowser && tabBrowser.tabs) {
    195      return tabBrowser.tabs;
    196    }
    197 
    198    return [];
    199  }
    200 
    201  getWindowForTab(tab) {
    202    // `.linkedBrowser.ownerGlobal` works both with Firefox Desktop and Mobile.
    203    // Other accessors (eg `.ownerGlobal` or `.browser.ownerGlobal`) fail on one
    204    // of the platforms.
    205    return tab.linkedBrowser.ownerGlobal;
    206  }
    207 
    208  /**
    209   * Check if the given argument is a valid canonical browsing context and was not
    210   * discarded.
    211   *
    212   * @param {BrowsingContext} browsingContext
    213   *     The browsing context to check.
    214   *
    215   * @returns {boolean}
    216   *     True if the browsing context is valid, false otherwise.
    217   */
    218  isValidCanonicalBrowsingContext(browsingContext) {
    219    return (
    220      CanonicalBrowsingContext.isInstance(browsingContext) &&
    221      !browsingContext.isDiscarded
    222    );
    223  }
    224 
    225  /**
    226   * Remove the given tab.
    227   *
    228   * @param {Tab} tab
    229   *     Tab to remove.
    230   * @param {object=} options
    231   * @param {boolean=} options.skipPermitUnload
    232   *     Flag to indicate if a potential beforeunload prompt should be skipped
    233   *     when closing the tab. Defaults to false.
    234   */
    235  async removeTab(tab, options = {}) {
    236    const { skipPermitUnload = false } = options;
    237 
    238    if (!tab) {
    239      return;
    240    }
    241 
    242    const ownerWindow = this.getWindowForTab(tab);
    243    const tabBrowser = this.getTabBrowser(ownerWindow);
    244    await tabBrowser.removeTab(tab, {
    245      skipPermitUnload,
    246    });
    247  }
    248 
    249  /**
    250   * Select the given tab.
    251   *
    252   * @param {Tab} tab
    253   *     Tab to select.
    254   *
    255   * @returns {Promise}
    256   *     Promise that resolves when the given tab has been selected.
    257   */
    258  async selectTab(tab) {
    259    if (!tab) {
    260      return Promise.resolve();
    261    }
    262 
    263    const ownerWindow = this.getWindowForTab(tab);
    264    const tabBrowser = this.getTabBrowser(ownerWindow);
    265 
    266    if (tab === tabBrowser.selectedTab) {
    267      return Promise.resolve();
    268    }
    269 
    270    const selected = new lazy.EventPromise(ownerWindow, "TabSelect");
    271    tabBrowser.selectedTab = tab;
    272 
    273    await selected;
    274 
    275    // Sometimes at that point window is not focused.
    276    if (Services.focus.activeWindow != ownerWindow) {
    277      const activated = new lazy.EventPromise(ownerWindow, "activate");
    278      ownerWindow.focus();
    279      return activated;
    280    }
    281 
    282    return Promise.resolve();
    283  }
    284 
    285  supportsTabs() {
    286    return lazy.AppInfo.isAndroid || lazy.AppInfo.isFirefox;
    287  }
    288 }
    289 
    290 // Expose a shared singleton.
    291 export const TabManager = new TabManagerClass();