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/. */
7 const { assert } = require("resource://devtools/shared/DevToolsUtils.js");
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");
16 } = require("resource://devtools/client/shared/vendor/react-redux.js");
23 } = require("resource://devtools/client/memory/constants.js");
25 toggleRecordingAllocationStacks,
26 } = require("resource://devtools/client/memory/actions/allocations.js");
28 setCensusDisplayAndRefresh,
29 } = require("resource://devtools/client/memory/actions/census-display.js");
31 setLabelDisplayAndRefresh,
32 } = require("resource://devtools/client/memory/actions/label-display.js");
34 setTreeMapDisplayAndRefresh,
35 } = require("resource://devtools/client/memory/actions/tree-map-display.js");
38 getCustomCensusDisplays,
39 getCustomLabelDisplays,
40 getCustomTreeMapDisplays,
41 } = require("resource://devtools/client/memory/utils.js");
43 selectSnapshotForDiffingAndRefresh,
45 expandDiffingCensusNode,
46 collapseDiffingCensusNode,
47 focusDiffingCensusNode,
48 } = require("resource://devtools/client/memory/actions/diffing.js");
50 setFilterStringAndRefresh,
51 } = require("resource://devtools/client/memory/actions/filter.js");
53 pickFileAndExportSnapshot,
54 pickFileAndImportSnapshotAndCensus,
55 } = require("resource://devtools/client/memory/actions/io.js");
57 selectSnapshotAndRefresh,
58 takeSnapshotAndCensus,
61 fetchImmediatelyDominated,
65 expandDominatorTreeNode,
66 collapseDominatorTreeNode,
67 focusDominatorTreeNode,
70 } = require("resource://devtools/client/memory/actions/snapshot.js");
74 } = require("resource://devtools/client/memory/actions/view.js");
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")
92 } = require("resource://devtools/client/memory/models.js");
94 class MemoryApp extends Component {
95 static get propTypes() {
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,
114 static get childContextTypes() {
116 front: PropTypes.any,
117 heapWorker: PropTypes.any,
118 toolbox: PropTypes.any,
122 static get defaultProps() {
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);
136 front: this.props.front,
137 heapWorker: this.props.heapWorker,
138 toolbox: this.props.toolbox,
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);
149 componentWillUnmount() {
150 window.removeEventListener("keydown", this.onKeyDown);
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));
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));
176 _getCensusDisplays() {
177 const customDisplays = getCustomCensusDisplays();
178 const custom = Object.keys(customDisplays).reduce((arr, key) => {
179 arr.push(customDisplays[key]);
184 censusDisplays.coarseType,
185 censusDisplays.allocationStack,
186 censusDisplays.invertedAllocationStack,
190 _getLabelDisplays() {
191 const customDisplays = getCustomLabelDisplays();
192 const custom = Object.keys(customDisplays).reduce((arr, key) => {
193 arr.push(customDisplays[key]);
197 return [labelDisplays.coarseType, labelDisplays.allocationStack].concat(
202 _getTreeMapDisplays() {
203 const customDisplays = getCustomTreeMapDisplays();
204 const custom = Object.keys(customDisplays).reduce((arr, key) => {
205 arr.push(customDisplays[key]);
209 return [treeMapDisplays.coarseType].concat(custom);
230 const selectedSnapshot = snapshots.find(s => s.selected);
232 const onClickSnapshotListItem =
233 diffing && diffing.state === diffingState.SELECTING
235 dispatch(selectSnapshotForDiffingAndRefresh(heapWorker, snapshot))
237 dispatch(selectSnapshotAndRefresh(heapWorker, snapshot.id));
246 censusDisplays: this._getCensusDisplays(),
248 onCensusDisplayChange: newDisplay =>
249 dispatch(setCensusDisplayAndRefresh(heapWorker, newDisplay)),
251 dispatch(pickFileAndImportSnapshotAndCensus(heapWorker)),
252 onClearSnapshotsClick: () => dispatch(clearSnapshots(heapWorker)),
253 onTakeSnapshotClick: () =>
254 dispatch(takeSnapshotAndCensus(front, heapWorker)),
255 onToggleRecordAllocationStacks: () =>
256 dispatch(toggleRecordingAllocationStacks(commands)),
258 filterString: filter,
259 setFilterString: filterString =>
260 dispatch(setFilterStringAndRefresh(filterString, heapWorker)),
262 onToggleDiffing: () => dispatch(toggleDiffing()),
264 labelDisplays: this._getLabelDisplays(),
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)),
276 id: "memory-tool-container",
280 itemComponent: SnapshotListItem,
282 onSave: snapshot => dispatch(pickFileAndExportSnapshot(snapshot)),
283 onDelete: snapshot => dispatch(deleteSnapshot(heapWorker, snapshot)),
284 onClick: onClickSnapshotListItem,
289 snapshot: selectedSnapshot,
291 onViewSourceInDebugger: ({ url, line, column }) => {
292 toolbox.viewSourceInDebugger(url, line, column);
294 onSnapshotClick: () =>
295 dispatch(takeSnapshotAndCensus(front, heapWorker)),
296 onLoadMoreSiblings: lazyChildren =>
298 fetchImmediatelyDominated(
304 onPopView: () => dispatch(popViewAndRefresh(heapWorker)),
306 onViewIndividuals: node => {
307 const snapshotId = diffing
308 ? diffing.secondSnapshotId
309 : selectedSnapshot.id;
314 censusDisplay.breakdown,
319 onFocusIndividual: node => {
321 view.state === viewState.INDIVIDUALS,
322 "Should be in the individuals view"
324 dispatch(focusIndividual(node));
326 onCensusExpand: (census, node) => {
329 diffing.census === census,
330 "Should only expand active census"
332 dispatch(expandDiffingCensusNode(node));
335 selectedSnapshot && selectedSnapshot.census === census,
337 "should be expanding on selected snapshot's census"
339 dispatch(expandCensusNode(selectedSnapshot.id, node));
342 onCensusCollapse: (census, node) => {
345 diffing.census === census,
346 "Should only collapse active census"
348 dispatch(collapseDiffingCensusNode(node));
351 selectedSnapshot && selectedSnapshot.census === census,
353 "should be collapsing on selected snapshot's census"
355 dispatch(collapseCensusNode(selectedSnapshot.id, node));
358 onCensusFocus: (census, node) => {
361 diffing.census === census,
362 "Should only focus nodes in active census"
364 dispatch(focusDiffingCensusNode(node));
367 selectedSnapshot && selectedSnapshot.census === census,
369 "should be focusing on nodes in selected snapshot's census"
371 dispatch(focusCensusNode(selectedSnapshot.id, node));
374 onDominatorTreeExpand: node => {
376 view.state === viewState.DOMINATOR_TREE,
377 "If expanding dominator tree nodes, " +
378 "should be in dominator tree view"
382 "...and we should have a selected snapshot"
385 selectedSnapshot.dominatorTree,
386 "...and that snapshot should have a dominator tree"
388 dispatch(expandDominatorTreeNode(selectedSnapshot.id, node));
390 onDominatorTreeCollapse: node => {
392 view.state === viewState.DOMINATOR_TREE,
393 "If collapsing dominator tree nodes, " +
394 "should be in dominator tree view"
398 "...and we should have a selected snapshot"
401 selectedSnapshot.dominatorTree,
402 "...and that snapshot should have a dominator tree"
404 dispatch(collapseDominatorTreeNode(selectedSnapshot.id, node));
406 onDominatorTreeFocus: node => {
408 view.state === viewState.DOMINATOR_TREE,
409 "If focusing dominator tree nodes, " +
410 "should be in dominator tree view"
414 "...and we should have a selected snapshot"
417 selectedSnapshot.dominatorTree,
418 "...and that snapshot should have a dominator tree"
420 dispatch(focusDominatorTreeNode(selectedSnapshot.id, node));
422 onShortestPathsResize: newSize => {
423 dispatch(resizeShortestPaths(newSize));
434 * Passed into react-redux's `connect` method that is called on store change
435 * and passed to components.
437 function mapStateToProps(state) {
441 module.exports = connect(mapStateToProps)(MemoryApp);