Bug 1874684 - Part 4: Prefer const references instead of copying Instant values....
[gecko.git] / devtools / client / memory / app.js
blob1549cf23eb48fa47affdc3cdd4839d8f2864870e
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 file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const { assert } = require("resource://devtools/shared/DevToolsUtils.js");
8 const {
9   Component,
10   createFactory,
11 } = require("resource://devtools/client/shared/vendor/react.js");
12 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
13 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
14 const {
15   connect,
16 } = require("resource://devtools/client/shared/vendor/react-redux.js");
17 const {
18   censusDisplays,
19   labelDisplays,
20   treeMapDisplays,
21   diffingState,
22   viewState,
23 } = require("resource://devtools/client/memory/constants.js");
24 const {
25   toggleRecordingAllocationStacks,
26 } = require("resource://devtools/client/memory/actions/allocations.js");
27 const {
28   setCensusDisplayAndRefresh,
29 } = require("resource://devtools/client/memory/actions/census-display.js");
30 const {
31   setLabelDisplayAndRefresh,
32 } = require("resource://devtools/client/memory/actions/label-display.js");
33 const {
34   setTreeMapDisplayAndRefresh,
35 } = require("resource://devtools/client/memory/actions/tree-map-display.js");
37 const {
38   getCustomCensusDisplays,
39   getCustomLabelDisplays,
40   getCustomTreeMapDisplays,
41 } = require("resource://devtools/client/memory/utils.js");
42 const {
43   selectSnapshotForDiffingAndRefresh,
44   toggleDiffing,
45   expandDiffingCensusNode,
46   collapseDiffingCensusNode,
47   focusDiffingCensusNode,
48 } = require("resource://devtools/client/memory/actions/diffing.js");
49 const {
50   setFilterStringAndRefresh,
51 } = require("resource://devtools/client/memory/actions/filter.js");
52 const {
53   pickFileAndExportSnapshot,
54   pickFileAndImportSnapshotAndCensus,
55 } = require("resource://devtools/client/memory/actions/io.js");
56 const {
57   selectSnapshotAndRefresh,
58   takeSnapshotAndCensus,
59   clearSnapshots,
60   deleteSnapshot,
61   fetchImmediatelyDominated,
62   expandCensusNode,
63   collapseCensusNode,
64   focusCensusNode,
65   expandDominatorTreeNode,
66   collapseDominatorTreeNode,
67   focusDominatorTreeNode,
68   fetchIndividuals,
69   focusIndividual,
70 } = require("resource://devtools/client/memory/actions/snapshot.js");
71 const {
72   changeViewAndRefresh,
73   popViewAndRefresh,
74 } = require("resource://devtools/client/memory/actions/view.js");
75 const {
76   resizeShortestPaths,
77 } = require("resource://devtools/client/memory/actions/sizes.js");
78 const Toolbar = createFactory(
79   require("resource://devtools/client/memory/components/Toolbar.js")
81 const List = createFactory(
82   require("resource://devtools/client/memory/components/List.js")
84 const SnapshotListItem = createFactory(
85   require("resource://devtools/client/memory/components/SnapshotListItem.js")
87 const Heap = createFactory(
88   require("resource://devtools/client/memory/components/Heap.js")
90 const {
91   app: appModel,
92 } = require("resource://devtools/client/memory/models.js");
94 class MemoryApp extends Component {
95   static get propTypes() {
96     return {
97       allocations: appModel.allocations,
98       censusDisplay: appModel.censusDisplay,
99       commands: appModel.commands,
100       diffing: appModel.diffing,
101       dispatch: PropTypes.func,
102       filter: appModel.filter,
103       front: appModel.front,
104       heapWorker: appModel.heapWorker,
105       individuals: appModel.individuals,
106       labelDisplay: appModel.labelDisplay,
107       sizes: PropTypes.object,
108       snapshots: appModel.snapshots,
109       toolbox: PropTypes.object,
110       view: appModel.view,
111     };
112   }
114   static get childContextTypes() {
115     return {
116       front: PropTypes.any,
117       heapWorker: PropTypes.any,
118       toolbox: PropTypes.any,
119     };
120   }
122   static get defaultProps() {
123     return {};
124   }
126   constructor(props) {
127     super(props);
128     this.onKeyDown = this.onKeyDown.bind(this);
129     this._getCensusDisplays = this._getCensusDisplays.bind(this);
130     this._getLabelDisplays = this._getLabelDisplays.bind(this);
131     this._getTreeMapDisplays = this._getTreeMapDisplays.bind(this);
132   }
134   getChildContext() {
135     return {
136       front: this.props.front,
137       heapWorker: this.props.heapWorker,
138       toolbox: this.props.toolbox,
139     };
140   }
142   componentDidMount() {
143     // Attach the keydown listener directly to the window. When an element that
144     // has the focus (such as a tree node) is removed from the DOM, the focus
145     // falls back to the body.
146     window.addEventListener("keydown", this.onKeyDown);
147   }
149   componentWillUnmount() {
150     window.removeEventListener("keydown", this.onKeyDown);
151   }
153   onKeyDown(e) {
154     const { snapshots, dispatch, heapWorker } = this.props;
155     const selectedSnapshot = snapshots.find(s => s.selected);
156     const selectedIndex = snapshots.indexOf(selectedSnapshot);
158     const isOSX = Services.appinfo.OS == "Darwin";
159     const isAccelKey = (isOSX && e.metaKey) || (!isOSX && e.ctrlKey);
161     // On ACCEL+UP, select previous snapshot.
162     if (isAccelKey && e.key === "ArrowUp") {
163       const previousIndex = Math.max(0, selectedIndex - 1);
164       const previousSnapshotId = snapshots[previousIndex].id;
165       dispatch(selectSnapshotAndRefresh(heapWorker, previousSnapshotId));
166     }
168     // On ACCEL+DOWN, select next snapshot.
169     if (isAccelKey && e.key === "ArrowDown") {
170       const nextIndex = Math.min(snapshots.length - 1, selectedIndex + 1);
171       const nextSnapshotId = snapshots[nextIndex].id;
172       dispatch(selectSnapshotAndRefresh(heapWorker, nextSnapshotId));
173     }
174   }
176   _getCensusDisplays() {
177     const customDisplays = getCustomCensusDisplays();
178     const custom = Object.keys(customDisplays).reduce((arr, key) => {
179       arr.push(customDisplays[key]);
180       return arr;
181     }, []);
183     return [
184       censusDisplays.coarseType,
185       censusDisplays.allocationStack,
186       censusDisplays.invertedAllocationStack,
187     ].concat(custom);
188   }
190   _getLabelDisplays() {
191     const customDisplays = getCustomLabelDisplays();
192     const custom = Object.keys(customDisplays).reduce((arr, key) => {
193       arr.push(customDisplays[key]);
194       return arr;
195     }, []);
197     return [labelDisplays.coarseType, labelDisplays.allocationStack].concat(
198       custom
199     );
200   }
202   _getTreeMapDisplays() {
203     const customDisplays = getCustomTreeMapDisplays();
204     const custom = Object.keys(customDisplays).reduce((arr, key) => {
205       arr.push(customDisplays[key]);
206       return arr;
207     }, []);
209     return [treeMapDisplays.coarseType].concat(custom);
210   }
212   render() {
213     const {
214       commands,
215       dispatch,
216       snapshots,
217       front,
218       heapWorker,
219       allocations,
220       toolbox,
221       filter,
222       diffing,
223       view,
224       sizes,
225       censusDisplay,
226       labelDisplay,
227       individuals,
228     } = this.props;
230     const selectedSnapshot = snapshots.find(s => s.selected);
232     const onClickSnapshotListItem =
233       diffing && diffing.state === diffingState.SELECTING
234         ? snapshot =>
235             dispatch(selectSnapshotForDiffingAndRefresh(heapWorker, snapshot))
236         : snapshot =>
237             dispatch(selectSnapshotAndRefresh(heapWorker, snapshot.id));
239     return dom.div(
240       {
241         id: "memory-tool",
242       },
244       Toolbar({
245         snapshots,
246         censusDisplays: this._getCensusDisplays(),
247         censusDisplay,
248         onCensusDisplayChange: newDisplay =>
249           dispatch(setCensusDisplayAndRefresh(heapWorker, newDisplay)),
250         onImportClick: () =>
251           dispatch(pickFileAndImportSnapshotAndCensus(heapWorker)),
252         onClearSnapshotsClick: () => dispatch(clearSnapshots(heapWorker)),
253         onTakeSnapshotClick: () =>
254           dispatch(takeSnapshotAndCensus(front, heapWorker)),
255         onToggleRecordAllocationStacks: () =>
256           dispatch(toggleRecordingAllocationStacks(commands)),
257         allocations,
258         filterString: filter,
259         setFilterString: filterString =>
260           dispatch(setFilterStringAndRefresh(filterString, heapWorker)),
261         diffing,
262         onToggleDiffing: () => dispatch(toggleDiffing()),
263         view,
264         labelDisplays: this._getLabelDisplays(),
265         labelDisplay,
266         onLabelDisplayChange: newDisplay =>
267           dispatch(setLabelDisplayAndRefresh(heapWorker, newDisplay)),
268         treeMapDisplays: this._getTreeMapDisplays(),
269         onTreeMapDisplayChange: newDisplay =>
270           dispatch(setTreeMapDisplayAndRefresh(heapWorker, newDisplay)),
271         onViewChange: v => dispatch(changeViewAndRefresh(v, heapWorker)),
272       }),
274       dom.div(
275         {
276           id: "memory-tool-container",
277         },
279         List({
280           itemComponent: SnapshotListItem,
281           items: snapshots,
282           onSave: snapshot => dispatch(pickFileAndExportSnapshot(snapshot)),
283           onDelete: snapshot => dispatch(deleteSnapshot(heapWorker, snapshot)),
284           onClick: onClickSnapshotListItem,
285           diffing,
286         }),
288         Heap({
289           snapshot: selectedSnapshot,
290           diffing,
291           onViewSourceInDebugger: ({ url, line, column }) => {
292             toolbox.viewSourceInDebugger(url, line, column);
293           },
294           onSnapshotClick: () =>
295             dispatch(takeSnapshotAndCensus(front, heapWorker)),
296           onLoadMoreSiblings: lazyChildren =>
297             dispatch(
298               fetchImmediatelyDominated(
299                 heapWorker,
300                 selectedSnapshot.id,
301                 lazyChildren
302               )
303             ),
304           onPopView: () => dispatch(popViewAndRefresh(heapWorker)),
305           individuals,
306           onViewIndividuals: node => {
307             const snapshotId = diffing
308               ? diffing.secondSnapshotId
309               : selectedSnapshot.id;
310             dispatch(
311               fetchIndividuals(
312                 heapWorker,
313                 snapshotId,
314                 censusDisplay.breakdown,
315                 node.reportLeafIndex
316               )
317             );
318           },
319           onFocusIndividual: node => {
320             assert(
321               view.state === viewState.INDIVIDUALS,
322               "Should be in the individuals view"
323             );
324             dispatch(focusIndividual(node));
325           },
326           onCensusExpand: (census, node) => {
327             if (diffing) {
328               assert(
329                 diffing.census === census,
330                 "Should only expand active census"
331               );
332               dispatch(expandDiffingCensusNode(node));
333             } else {
334               assert(
335                 selectedSnapshot && selectedSnapshot.census === census,
336                 "If not diffing, " +
337                   "should be expanding on selected snapshot's census"
338               );
339               dispatch(expandCensusNode(selectedSnapshot.id, node));
340             }
341           },
342           onCensusCollapse: (census, node) => {
343             if (diffing) {
344               assert(
345                 diffing.census === census,
346                 "Should only collapse active census"
347               );
348               dispatch(collapseDiffingCensusNode(node));
349             } else {
350               assert(
351                 selectedSnapshot && selectedSnapshot.census === census,
352                 "If not diffing, " +
353                   "should be collapsing on selected snapshot's census"
354               );
355               dispatch(collapseCensusNode(selectedSnapshot.id, node));
356             }
357           },
358           onCensusFocus: (census, node) => {
359             if (diffing) {
360               assert(
361                 diffing.census === census,
362                 "Should only focus nodes in active census"
363               );
364               dispatch(focusDiffingCensusNode(node));
365             } else {
366               assert(
367                 selectedSnapshot && selectedSnapshot.census === census,
368                 "If not diffing, " +
369                   "should be focusing on nodes in selected snapshot's census"
370               );
371               dispatch(focusCensusNode(selectedSnapshot.id, node));
372             }
373           },
374           onDominatorTreeExpand: node => {
375             assert(
376               view.state === viewState.DOMINATOR_TREE,
377               "If expanding dominator tree nodes, " +
378                 "should be in dominator tree view"
379             );
380             assert(
381               selectedSnapshot,
382               "...and we should have a selected snapshot"
383             );
384             assert(
385               selectedSnapshot.dominatorTree,
386               "...and that snapshot should have a dominator tree"
387             );
388             dispatch(expandDominatorTreeNode(selectedSnapshot.id, node));
389           },
390           onDominatorTreeCollapse: node => {
391             assert(
392               view.state === viewState.DOMINATOR_TREE,
393               "If collapsing dominator tree nodes, " +
394                 "should be in dominator tree view"
395             );
396             assert(
397               selectedSnapshot,
398               "...and we should have a selected snapshot"
399             );
400             assert(
401               selectedSnapshot.dominatorTree,
402               "...and that snapshot should have a dominator tree"
403             );
404             dispatch(collapseDominatorTreeNode(selectedSnapshot.id, node));
405           },
406           onDominatorTreeFocus: node => {
407             assert(
408               view.state === viewState.DOMINATOR_TREE,
409               "If focusing dominator tree nodes, " +
410                 "should be in dominator tree view"
411             );
412             assert(
413               selectedSnapshot,
414               "...and we should have a selected snapshot"
415             );
416             assert(
417               selectedSnapshot.dominatorTree,
418               "...and that snapshot should have a dominator tree"
419             );
420             dispatch(focusDominatorTreeNode(selectedSnapshot.id, node));
421           },
422           onShortestPathsResize: newSize => {
423             dispatch(resizeShortestPaths(newSize));
424           },
425           sizes,
426           view,
427         })
428       )
429     );
430   }
434  * Passed into react-redux's `connect` method that is called on store change
435  * and passed to components.
436  */
437 function mapStateToProps(state) {
438   return state;
441 module.exports = connect(mapStateToProps)(MemoryApp);