Bug 1880488 [wpt PR 44609] - Update wpt metadata, a=testonly
[gecko.git] / devtools / client / inspector / toolsidebar.js
blobcff5eb96fa17296827a1ec6485e1e7ae34fa32af
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    * @param {String} tabPanelId Optional. If provided, this ID will be used
155    * instead of the tabId to retrieve and remove the corresponding <tabpanel>
156    */
157   removeTab(tabId, tabPanelId) {
158     this._tabbar.removeTab(tabId);
160     this.emit("tab-unregistered", tabId);
161   },
163   /**
164    * Show or hide a specific tab.
165    * @param {Boolean} isVisible True to show the tab/tabpanel, False to hide it.
166    * @param {String} id The ID of the tab to be hidden.
167    */
168   toggleTab(isVisible, id) {
169     this._tabbar.toggleTab(id, isVisible);
170   },
172   /**
173    * Select a specific tab.
174    */
175   select(id) {
176     this._tabbar.select(id);
177   },
179   /**
180    * Return the id of the selected tab.
181    */
182   getCurrentTabID() {
183     return this._currentTool;
184   },
186   /**
187    * Returns the requested tab panel based on the id.
188    * @param {String} id
189    * @return {DOMNode}
190    */
191   getTabPanel(id) {
192     // Search with and without the ID prefix as there might have been existing
193     // tabpanels by the time the sidebar got created
194     return this._panelDoc.querySelector(
195       "#" + this.TABPANEL_ID_PREFIX + id + ", #" + id
196     );
197   },
199   /**
200    * Event handler.
201    */
202   handleSelectionChange(id) {
203     if (this._destroyed) {
204       return;
205     }
207     const previousTool = this._currentTool;
208     if (previousTool) {
209       this.emit(previousTool + "-unselected");
210     }
212     this._currentTool = id;
214     this.updateTelemetryOnChange(id, previousTool);
215     this.emit(this._currentTool + "-selected");
216     this.emit("select", this._currentTool);
217   },
219   /**
220    * Log toolClosed and toolOpened events on telemetry.
221    *
222    * @param  {String} currentToolId
223    *         id of the tool being selected.
224    * @param  {String} previousToolId
225    *         id of the previously selected tool.
226    */
227   updateTelemetryOnChange(currentToolId, previousToolId) {
228     if (currentToolId === previousToolId || !this._telemetry) {
229       // Skip telemetry if the tool id did not change or telemetry is unavailable.
230       return;
231     }
233     currentToolId = this.getTelemetryPanelNameOrOther(currentToolId);
235     if (previousToolId) {
236       previousToolId = this.getTelemetryPanelNameOrOther(previousToolId);
237       this._telemetry.toolClosed(previousToolId, this);
239       this._telemetry.recordEvent("sidepanel_changed", "inspector", null, {
240         oldpanel: previousToolId,
241         newpanel: currentToolId,
242         os: this._telemetry.osNameAndVersion,
243       });
244     }
245     this._telemetry.toolOpened(currentToolId, this);
246   },
248   /**
249    * Returns a panel id in the case of built in panels or "other" in the case of
250    * third party panels. This is necessary due to limitations in addon id strings,
251    * the permitted length of event telemetry property values and what we actually
252    * want to see in our telemetry.
253    *
254    * @param {String} id
255    *        The panel id we would like to process.
256    */
257   getTelemetryPanelNameOrOther(id) {
258     if (!this._toolNames) {
259       // Get all built in tool ids. We identify third party tool ids by checking
260       // for a "-", which shows it originates from an addon.
261       const ids = this._tabbar.state.tabs.map(({ id: toolId }) => {
262         return toolId.includes("-") ? "other" : toolId;
263       });
265       this._toolNames = new Set(ids);
266     }
268     if (!this._toolNames.has(id)) {
269       return "other";
270     }
272     return id;
273   },
275   /**
276    * Show the sidebar.
277    *
278    * @param  {String} id
279    *         The sidebar tab id to select.
280    */
281   show(id) {
282     this._tabbox.hidden = false;
284     // If an id is given, select the corresponding sidebar tab.
285     if (id) {
286       this.select(id);
287     }
289     this.emit("show");
290   },
292   /**
293    * Show the sidebar.
294    */
295   hide() {
296     this._tabbox.hidden = true;
298     this.emit("hide");
299   },
301   /**
302    * Clean-up.
303    */
304   destroy() {
305     if (this._destroyed) {
306       return;
307     }
308     this._destroyed = true;
310     this.emit("destroy");
312     if (this._currentTool && this._telemetry) {
313       this._telemetry.toolClosed(this._currentTool, this);
314     }
316     this._toolPanel.emit("sidebar-destroyed", this);
318     this.ReactDOM.unmountComponentAtNode(this._tabbox);
320     this._tabs = null;
321     this._tabbox = null;
322     this._telemetry = null;
323     this._panelDoc = null;
324     this._toolPanel = null;
325   },