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