tor-browser

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

manager.js (8030B)


      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 "use strict";
      6 
      7 const EventEmitter = require("resource://devtools/shared/event-emitter.js");
      8 
      9 loader.lazyRequireGetter(
     10  this,
     11  "ResponsiveUI",
     12  "resource://devtools/client/responsive/ui.js"
     13 );
     14 loader.lazyRequireGetter(
     15  this,
     16  "startup",
     17  "resource://devtools/client/responsive/utils/window.js",
     18  true
     19 );
     20 loader.lazyRequireGetter(
     21  this,
     22  "showNotification",
     23  "resource://devtools/client/responsive/utils/notification.js",
     24  true
     25 );
     26 loader.lazyRequireGetter(
     27  this,
     28  "l10n",
     29  "resource://devtools/client/responsive/utils/l10n.js"
     30 );
     31 loader.lazyRequireGetter(
     32  this,
     33  "PriorityLevels",
     34  "resource://devtools/client/shared/components/NotificationBox.js",
     35  true
     36 );
     37 loader.lazyRequireGetter(
     38  this,
     39  "gDevTools",
     40  "resource://devtools/client/framework/devtools.js",
     41  true
     42 );
     43 loader.lazyRequireGetter(
     44  this,
     45  "gDevToolsBrowser",
     46  "resource://devtools/client/framework/devtools-browser.js",
     47  true
     48 );
     49 loader.lazyRequireGetter(
     50  this,
     51  "Telemetry",
     52  "resource://devtools/client/shared/telemetry.js"
     53 );
     54 
     55 /**
     56 * ResponsiveUIManager is the external API for the browser UI, etc. to use when
     57 * opening and closing the responsive UI.
     58 */
     59 class ResponsiveUIManager {
     60  constructor() {
     61    this.activeTabs = new Map();
     62 
     63    this.handleMenuCheck = this.handleMenuCheck.bind(this);
     64 
     65    EventEmitter.decorate(this);
     66  }
     67 
     68  get telemetry() {
     69    if (!this._telemetry) {
     70      this._telemetry = new Telemetry();
     71    }
     72 
     73    return this._telemetry;
     74  }
     75 
     76  /**
     77   * Toggle the responsive UI for a tab.
     78   *
     79   * @param window
     80   *        The main browser chrome window.
     81   * @param tab
     82   *        The browser tab.
     83   * @param options
     84   *        Other options associated with toggling.  Currently includes:
     85   *        - `trigger`: String denoting the UI entry point, such as:
     86   *          - `toolbox`:  Toolbox Button
     87   *          - `menu`:     Browser Tools menu item
     88   *          - `shortcut`: Keyboard shortcut
     89   * @return Promise
     90   *         Resolved when the toggling has completed.  If the UI has opened,
     91   *         it is resolved to the ResponsiveUI instance for this tab.  If the
     92   *         the UI has closed, there is no resolution value.
     93   */
     94  toggle(window, tab, options = {}) {
     95    const completed = this._toggleForTab(window, tab, options);
     96    completed.catch(console.error);
     97    return completed;
     98  }
     99 
    100  _toggleForTab(window, tab, options) {
    101    if (this.isActiveForTab(tab)) {
    102      return this.closeIfNeeded(window, tab, options);
    103    }
    104 
    105    return this.openIfNeeded(window, tab, options);
    106  }
    107 
    108  /**
    109   * Opens the responsive UI, if not already open.
    110   *
    111   * @param window
    112   *        The main browser chrome window.
    113   * @param tab
    114   *        The browser tab.
    115   * @param options
    116   *        Other options associated with opening.  Currently includes:
    117   *        - `trigger`: String denoting the UI entry point, such as:
    118   *          - `toolbox`:  Toolbox Button
    119   *          - `menu`:     Browser Tools menu item
    120   *          - `shortcut`: Keyboard shortcut
    121   * @return Promise
    122   *         Resolved to the ResponsiveUI instance for this tab when opening is
    123   *         complete.
    124   */
    125  async openIfNeeded(window, tab, options = {}) {
    126    if (!this.isActiveForTab(tab)) {
    127      this.initMenuCheckListenerFor(window);
    128 
    129      const ui = new ResponsiveUI(this, window, tab);
    130      this.activeTabs.set(tab, ui);
    131 
    132      // Explicitly not await on telemetry to avoid delaying RDM opening
    133      this.recordTelemetryOpen(window, tab, options);
    134 
    135      await gDevToolsBrowser.loadBrowserStyleSheet(window);
    136      await this.setMenuCheckFor(tab, window);
    137      await ui.initialize();
    138      this.emit("on", { tab });
    139    }
    140 
    141    return this.getResponsiveUIForTab(tab);
    142  }
    143 
    144  /**
    145   * Record all telemetry probes related to RDM opening.
    146   */
    147  recordTelemetryOpen(window, tab, options) {
    148    // Track whether a toolbox was opened before RDM was opened.
    149    const toolbox = gDevTools.getToolboxForTab(tab);
    150    const hostType = toolbox ? toolbox.hostType : "none";
    151    const hasToolbox = !!toolbox;
    152 
    153    if (hasToolbox) {
    154      Glean.devtoolsResponsive.toolboxOpenedFirst.add(1);
    155    }
    156 
    157    this.telemetry.recordEvent("activate", "responsive_design", null, {
    158      host: hostType,
    159      width: Math.ceil(window.outerWidth / 50) * 50,
    160    });
    161 
    162    // Track opens keyed by the UI entry point used.
    163    let { trigger } = options;
    164    if (!trigger) {
    165      trigger = "unknown";
    166    }
    167    Glean.devtoolsResponsive.openTrigger[trigger].add(1);
    168  }
    169 
    170  /**
    171   * Closes the responsive UI, if not already closed.
    172   *
    173   * @param window
    174   *        The main browser chrome window.
    175   * @param tab
    176   *        The browser tab.
    177   * @param options
    178   *        Other options associated with closing.  Currently includes:
    179   *        - `trigger`: String denoting the UI entry point, such as:
    180   *          - `toolbox`:  Toolbox Button
    181   *          - `menu`:     Browser Tools menu item
    182   *          - `shortcut`: Keyboard shortcut
    183   *        - `reason`: String detailing the specific cause for closing
    184   * @return Promise
    185   *         Resolved (with no value) when closing is complete.
    186   */
    187  async closeIfNeeded(window, tab, options = {}) {
    188    if (this.isActiveForTab(tab)) {
    189      const ui = this.activeTabs.get(tab);
    190      const destroyed = await ui.destroy(options);
    191      if (!destroyed) {
    192        // Already in the process of destroying, abort.
    193        return;
    194      }
    195 
    196      this.activeTabs.delete(tab);
    197 
    198      if (!this.isActiveForWindow(window)) {
    199        this.removeMenuCheckListenerFor(window);
    200      }
    201      this.emit("off", { tab });
    202      await this.setMenuCheckFor(tab, window);
    203 
    204      // Explicitly not await on telemetry to avoid delaying RDM closing
    205      this.recordTelemetryClose(window, tab);
    206    }
    207  }
    208 
    209  recordTelemetryClose(window, tab) {
    210    const toolbox = gDevTools.getToolboxForTab(tab);
    211 
    212    const hostType = toolbox ? toolbox.hostType : "none";
    213 
    214    this.telemetry.recordEvent("deactivate", "responsive_design", null, {
    215      host: hostType,
    216      width: Math.ceil(window.outerWidth / 50) * 50,
    217    });
    218  }
    219 
    220  /**
    221   * Returns true if responsive UI is active for a given tab.
    222   *
    223   * @param tab
    224   *        The browser tab.
    225   * @return boolean
    226   */
    227  isActiveForTab(tab) {
    228    return this.activeTabs.has(tab);
    229  }
    230 
    231  /**
    232   * Returns true if responsive UI is active in any tab in the given window.
    233   *
    234   * @param window
    235   *        The main browser chrome window.
    236   * @return boolean
    237   */
    238  isActiveForWindow(window) {
    239    return [...this.activeTabs.keys()].some(t => t.ownerGlobal === window);
    240  }
    241 
    242  /**
    243   * Return the responsive UI controller for a tab.
    244   *
    245   * @param tab
    246   *        The browser tab.
    247   * @return ResponsiveUI
    248   *         The UI instance for this tab.
    249   */
    250  getResponsiveUIForTab(tab) {
    251    return this.activeTabs.get(tab);
    252  }
    253 
    254  handleMenuCheck({ target }) {
    255    this.setMenuCheckFor(target);
    256  }
    257 
    258  initMenuCheckListenerFor(window) {
    259    const { tabContainer } = window.gBrowser;
    260    tabContainer.addEventListener("TabSelect", this.handleMenuCheck);
    261  }
    262 
    263  removeMenuCheckListenerFor(window) {
    264    if (window?.gBrowser?.tabContainer) {
    265      const { tabContainer } = window.gBrowser;
    266      tabContainer.removeEventListener("TabSelect", this.handleMenuCheck);
    267    }
    268  }
    269 
    270  async setMenuCheckFor(tab, window = tab.ownerGlobal) {
    271    await startup(window);
    272 
    273    const menu = window.document.getElementById("menu_responsiveUI");
    274    if (menu) {
    275      menu.setAttribute("checked", this.isActiveForTab(tab));
    276    }
    277  }
    278 
    279  showRemoteOnlyNotification(window, tab, { trigger } = {}) {
    280    return showNotification(window, tab, {
    281      toolboxButton: trigger == "toolbox",
    282      msg: l10n.getStr("responsive.remoteOnly"),
    283      priority: PriorityLevels.PRIORITY_CRITICAL_MEDIUM,
    284    });
    285  }
    286 }
    287 
    288 module.exports = new ResponsiveUIManager();