tor-browser

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

toolsidebar.js (8299B)


      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 class ToolSidebar extends EventEmitter {
     10  constructor(tabbox, panel, options = {}) {
     11    super();
     12 
     13    this.#tabbox = tabbox;
     14    this.#panelDoc = this.#tabbox.ownerDocument;
     15    this.#toolPanel = panel;
     16    this.#options = options;
     17 
     18    if (!options.disableTelemetry) {
     19      this.#telemetry = this.#toolPanel.telemetry;
     20    }
     21 
     22    if (this.#options.hideTabstripe) {
     23      this.#tabbox.setAttribute("hidetabs", "true");
     24    }
     25 
     26    this.render();
     27 
     28    this.#toolPanel.emit("sidebar-created", this);
     29  }
     30 
     31  TABPANEL_ID_PREFIX = "sidebar-panel-";
     32  #currentTool;
     33  #destroyed;
     34  #options;
     35  #panelDoc;
     36  #tabbar;
     37  #tabbox;
     38  #telemetry;
     39  #toolNames;
     40  #toolPanel;
     41 
     42  // React
     43 
     44  get React() {
     45    return this.#toolPanel.React;
     46  }
     47 
     48  get ReactDOM() {
     49    return this.#toolPanel.ReactDOM;
     50  }
     51 
     52  get browserRequire() {
     53    return this.#toolPanel.browserRequire;
     54  }
     55 
     56  get InspectorTabPanel() {
     57    return this.#toolPanel.InspectorTabPanel;
     58  }
     59 
     60  get TabBar() {
     61    return this.#toolPanel.TabBar;
     62  }
     63 
     64  // Rendering
     65 
     66  render() {
     67    const sidebar = this.TabBar({
     68      menuDocument: this.#toolPanel.toolbox.doc,
     69      showAllTabsMenu: true,
     70      allTabsMenuButtonTooltip: this.#options.allTabsMenuButtonTooltip,
     71      sidebarToggleButton: this.#options.sidebarToggleButton,
     72      onSelect: this.handleSelectionChange.bind(this),
     73    });
     74 
     75    this.#tabbar = this.ReactDOM.render(sidebar, this.#tabbox);
     76  }
     77 
     78  /**
     79   * Adds all the queued tabs.
     80   */
     81  addAllQueuedTabs() {
     82    this.#tabbar.addAllQueuedTabs();
     83  }
     84 
     85  /**
     86   * Register a side-panel tab.
     87   *
     88   * @param {string} tab uniq id
     89   * @param {string} title tab title
     90   * @param {React.Component} panel component. See `InspectorPanelTab` as an example.
     91   * @param {boolean} selected true if the panel should be selected
     92   * @param {number} index the position where the tab should be inserted
     93   */
     94  addTab(id, title, panel, selected, index) {
     95    this.#tabbar.addTab(id, title, selected, panel, null, index);
     96    this.emit("new-tab-registered", id);
     97  }
     98 
     99  /**
    100   * Helper API for adding side-panels that use existing DOM nodes
    101   * (defined within inspector.xhtml) as the content.
    102   *
    103   * @param {string} tab uniq id
    104   * @param {string} title tab title
    105   * @param {boolean} selected true if the panel should be selected
    106   * @param {number} index the position where the tab should be inserted
    107   */
    108  addExistingTab(id, title, selected, index) {
    109    const panel = this.InspectorTabPanel({
    110      id,
    111      idPrefix: this.TABPANEL_ID_PREFIX,
    112      key: id,
    113      title,
    114    });
    115 
    116    this.addTab(id, title, panel, selected, index);
    117  }
    118 
    119  /**
    120   * Queues a side-panel tab to be added..
    121   *
    122   * @param {string} tab uniq id
    123   * @param {string} title tab title
    124   * @param {React.Component} panel component. See `InspectorPanelTab` as an example.
    125   * @param {boolean} selected true if the panel should be selected
    126   * @param {number} index the position where the tab should be inserted
    127   */
    128  queueTab(id, title, panel, selected, index) {
    129    this.#tabbar.queueTab(id, title, selected, panel, null, index);
    130    this.emit("new-tab-registered", id);
    131  }
    132 
    133  /**
    134   * Helper API for queuing side-panels that use existing DOM nodes
    135   * (defined within inspector.xhtml) as the content.
    136   *
    137   * @param {string} tab uniq id
    138   * @param {string} title tab title
    139   * @param {boolean} selected true if the panel should be selected
    140   * @param {number} index the position where the tab should be inserted
    141   */
    142  queueExistingTab(id, title, selected, index) {
    143    const panel = this.InspectorTabPanel({
    144      id,
    145      idPrefix: this.TABPANEL_ID_PREFIX,
    146      key: id,
    147      title,
    148    });
    149 
    150    this.queueTab(id, title, panel, selected, index);
    151  }
    152 
    153  /**
    154   * Remove an existing tab.
    155   *
    156   * @param {string} tabId The ID of the tab that was used to register it, or
    157   * the tab id attribute value if the tab existed before the sidebar
    158   * got created.
    159   */
    160  removeTab(tabId) {
    161    this.#tabbar.removeTab(tabId);
    162 
    163    this.emit("tab-unregistered", tabId);
    164  }
    165 
    166  /**
    167   * Show or hide a specific tab.
    168   *
    169   * @param {boolean} isVisible True to show the tab/tabpanel, False to hide it.
    170   * @param {string} id The ID of the tab to be hidden.
    171   */
    172  toggleTab(isVisible, id) {
    173    this.#tabbar.toggleTab(id, isVisible);
    174  }
    175 
    176  /**
    177   * Select a specific tab.
    178   */
    179  select(id) {
    180    this.#tabbar.select(id);
    181  }
    182 
    183  /**
    184   * Return the id of the selected tab.
    185   */
    186  getCurrentTabID() {
    187    return this.#currentTool;
    188  }
    189 
    190  /**
    191   * Returns the requested tab panel based on the id.
    192   *
    193   * @param {string} id
    194   * @return {DOMNode}
    195   */
    196  getTabPanel(id) {
    197    // Search with and without the ID prefix as there might have been existing
    198    // tabpanels by the time the sidebar got created
    199    return this.#panelDoc.querySelector(
    200      "#" + this.TABPANEL_ID_PREFIX + id + ", #" + id
    201    );
    202  }
    203 
    204  /**
    205   * Event handler.
    206   */
    207  handleSelectionChange(id) {
    208    if (this.#destroyed) {
    209      return;
    210    }
    211 
    212    const previousTool = this.#currentTool;
    213    if (previousTool) {
    214      this.emit(previousTool + "-unselected");
    215    }
    216 
    217    this.#currentTool = id;
    218 
    219    this.updateTelemetryOnChange(id, previousTool);
    220    this.emit(this.#currentTool + "-selected");
    221    this.emit("select", this.#currentTool);
    222  }
    223 
    224  /**
    225   * Log toolClosed and toolOpened events on telemetry.
    226   *
    227   * @param  {string} currentToolId
    228   *         id of the tool being selected.
    229   * @param  {string} previousToolId
    230   *         id of the previously selected tool.
    231   */
    232  updateTelemetryOnChange(currentToolId, previousToolId) {
    233    if (currentToolId === previousToolId || !this.#telemetry) {
    234      // Skip telemetry if the tool id did not change or telemetry is unavailable.
    235      return;
    236    }
    237 
    238    currentToolId = this.getTelemetryPanelNameOrOther(currentToolId);
    239 
    240    if (previousToolId) {
    241      previousToolId = this.getTelemetryPanelNameOrOther(previousToolId);
    242      this.#telemetry.toolClosed(previousToolId, this);
    243 
    244      this.#telemetry.recordEvent("sidepanel_changed", "inspector", null, {
    245        oldpanel: previousToolId,
    246        newpanel: currentToolId,
    247        os: this.#telemetry.osNameAndVersion,
    248      });
    249    }
    250    this.#telemetry.toolOpened(currentToolId, this);
    251  }
    252 
    253  /**
    254   * Returns a panel id in the case of built in panels or "other" in the case of
    255   * third party panels. This is necessary due to limitations in addon id strings,
    256   * the permitted length of event telemetry property values and what we actually
    257   * want to see in our telemetry.
    258   *
    259   * @param {string} id
    260   *        The panel id we would like to process.
    261   */
    262  getTelemetryPanelNameOrOther(id) {
    263    if (!this.#toolNames) {
    264      // Get all built in tool ids. We identify third party tool ids by checking
    265      // for a "-", which shows it originates from an addon.
    266      const ids = this.#tabbar.state.tabs.map(({ id: toolId }) => {
    267        return toolId.includes("-") ? "other" : toolId;
    268      });
    269 
    270      this.#toolNames = new Set(ids);
    271    }
    272 
    273    if (!this.#toolNames.has(id)) {
    274      return "other";
    275    }
    276 
    277    return id;
    278  }
    279 
    280  /**
    281   * Show the sidebar.
    282   *
    283   * @param  {string} id
    284   *         The sidebar tab id to select.
    285   */
    286  show(id) {
    287    this.#tabbox.hidden = false;
    288 
    289    // If an id is given, select the corresponding sidebar tab.
    290    if (id) {
    291      this.select(id);
    292    }
    293 
    294    this.emit("show");
    295  }
    296 
    297  /**
    298   * Show the sidebar.
    299   */
    300  hide() {
    301    this.#tabbox.hidden = true;
    302 
    303    this.emit("hide");
    304  }
    305 
    306  /**
    307   * Clean-up.
    308   */
    309  destroy() {
    310    if (this.#destroyed) {
    311      return;
    312    }
    313    this.#destroyed = true;
    314 
    315    this.emit("destroy");
    316 
    317    if (this.#currentTool && this.#telemetry) {
    318      this.#telemetry.toolClosed(this.#currentTool, this);
    319    }
    320 
    321    this.#toolPanel.emit("sidebar-destroyed", this);
    322 
    323    this.ReactDOM.unmountComponentAtNode(this.#tabbox);
    324 
    325    this.#tabbox = null;
    326    this.#telemetry = null;
    327    this.#panelDoc = null;
    328    this.#toolPanel = null;
    329  }
    330 }
    331 
    332 exports.ToolSidebar = ToolSidebar;