diff options
Diffstat (limited to 'devtools/client/memory/app.js')
-rw-r--r-- | devtools/client/memory/app.js | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/devtools/client/memory/app.js b/devtools/client/memory/app.js new file mode 100644 index 000000000..b705e0a99 --- /dev/null +++ b/devtools/client/memory/app.js @@ -0,0 +1,322 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { assert } = require("devtools/shared/DevToolsUtils"); +const { appinfo } = require("Services"); +const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react"); +const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { censusDisplays, labelDisplays, treeMapDisplays, diffingState, viewState } = require("./constants"); +const { toggleRecordingAllocationStacks } = require("./actions/allocations"); +const { setCensusDisplayAndRefresh } = require("./actions/census-display"); +const { setLabelDisplayAndRefresh } = require("./actions/label-display"); +const { setTreeMapDisplayAndRefresh } = require("./actions/tree-map-display"); + +const { + getCustomCensusDisplays, + getCustomLabelDisplays, + getCustomTreeMapDisplays, +} = require("devtools/client/memory/utils"); +const { + selectSnapshotForDiffingAndRefresh, + toggleDiffing, + expandDiffingCensusNode, + collapseDiffingCensusNode, + focusDiffingCensusNode, +} = require("./actions/diffing"); +const { setFilterStringAndRefresh } = require("./actions/filter"); +const { pickFileAndExportSnapshot, pickFileAndImportSnapshotAndCensus } = require("./actions/io"); +const { + selectSnapshotAndRefresh, + takeSnapshotAndCensus, + clearSnapshots, + deleteSnapshot, + fetchImmediatelyDominated, + expandCensusNode, + collapseCensusNode, + focusCensusNode, + expandDominatorTreeNode, + collapseDominatorTreeNode, + focusDominatorTreeNode, + fetchIndividuals, + focusIndividual, +} = require("./actions/snapshot"); +const { changeViewAndRefresh, popViewAndRefresh } = require("./actions/view"); +const { resizeShortestPaths } = require("./actions/sizes"); +const Toolbar = createFactory(require("./components/toolbar")); +const List = createFactory(require("./components/list")); +const SnapshotListItem = createFactory(require("./components/snapshot-list-item")); +const Heap = createFactory(require("./components/heap")); +const { app: appModel } = require("./models"); + +const MemoryApp = createClass({ + displayName: "MemoryApp", + + propTypes: appModel, + + getDefaultProps() { + return {}; + }, + + componentDidMount() { + // Attach the keydown listener directly to the window. When an element that + // has the focus (such as a tree node) is removed from the DOM, the focus + // falls back to the body. + window.addEventListener("keydown", this.onKeyDown); + }, + + componentWillUnmount() { + window.removeEventListener("keydown", this.onKeyDown); + }, + + childContextTypes: { + front: PropTypes.any, + heapWorker: PropTypes.any, + toolbox: PropTypes.any, + }, + + getChildContext() { + return { + front: this.props.front, + heapWorker: this.props.heapWorker, + toolbox: this.props.toolbox, + }; + }, + + onKeyDown(e) { + let { snapshots, dispatch, heapWorker } = this.props; + const selectedSnapshot = snapshots.find(s => s.selected); + const selectedIndex = snapshots.indexOf(selectedSnapshot); + + let isOSX = appinfo.OS == "Darwin"; + let isAccelKey = (isOSX && e.metaKey) || (!isOSX && e.ctrlKey); + + // On ACCEL+UP, select previous snapshot. + if (isAccelKey && e.key === "ArrowUp") { + let previousIndex = Math.max(0, selectedIndex - 1); + let previousSnapshotId = snapshots[previousIndex].id; + dispatch(selectSnapshotAndRefresh(heapWorker, previousSnapshotId)); + } + + // On ACCEL+DOWN, select next snapshot. + if (isAccelKey && e.key === "ArrowDown") { + let nextIndex = Math.min(snapshots.length - 1, selectedIndex + 1); + let nextSnapshotId = snapshots[nextIndex].id; + dispatch(selectSnapshotAndRefresh(heapWorker, nextSnapshotId)); + } + }, + + _getCensusDisplays() { + const customDisplays = getCustomCensusDisplays(); + const custom = Object.keys(customDisplays).reduce((arr, key) => { + arr.push(customDisplays[key]); + return arr; + }, []); + + return [ + censusDisplays.coarseType, + censusDisplays.allocationStack, + censusDisplays.invertedAllocationStack, + ].concat(custom); + }, + + _getLabelDisplays() { + const customDisplays = getCustomLabelDisplays(); + const custom = Object.keys(customDisplays).reduce((arr, key) => { + arr.push(customDisplays[key]); + return arr; + }, []); + + return [ + labelDisplays.coarseType, + labelDisplays.allocationStack, + ].concat(custom); + }, + + _getTreeMapDisplays() { + const customDisplays = getCustomTreeMapDisplays(); + const custom = Object.keys(customDisplays).reduce((arr, key) => { + arr.push(customDisplays[key]); + return arr; + }, []); + + return [ + treeMapDisplays.coarseType + ].concat(custom); + }, + + render() { + let { + dispatch, + snapshots, + front, + heapWorker, + allocations, + toolbox, + filter, + diffing, + view, + sizes, + censusDisplay, + labelDisplay, + individuals, + } = this.props; + + const selectedSnapshot = snapshots.find(s => s.selected); + + const onClickSnapshotListItem = diffing && diffing.state === diffingState.SELECTING + ? snapshot => dispatch(selectSnapshotForDiffingAndRefresh(heapWorker, snapshot)) + : snapshot => dispatch(selectSnapshotAndRefresh(heapWorker, snapshot.id)); + + return ( + dom.div( + { + id: "memory-tool" + }, + + Toolbar({ + snapshots, + censusDisplays: this._getCensusDisplays(), + censusDisplay, + onCensusDisplayChange: newDisplay => + dispatch(setCensusDisplayAndRefresh(heapWorker, newDisplay)), + onImportClick: () => dispatch(pickFileAndImportSnapshotAndCensus(heapWorker)), + onClearSnapshotsClick: () => dispatch(clearSnapshots(heapWorker)), + onTakeSnapshotClick: () => dispatch(takeSnapshotAndCensus(front, heapWorker)), + onToggleRecordAllocationStacks: () => + dispatch(toggleRecordingAllocationStacks(front)), + allocations, + filterString: filter, + setFilterString: filterString => + dispatch(setFilterStringAndRefresh(filterString, heapWorker)), + diffing, + onToggleDiffing: () => dispatch(toggleDiffing()), + view, + labelDisplays: this._getLabelDisplays(), + labelDisplay, + onLabelDisplayChange: newDisplay => + dispatch(setLabelDisplayAndRefresh(heapWorker, newDisplay)), + treeMapDisplays: this._getTreeMapDisplays(), + onTreeMapDisplayChange: newDisplay => + dispatch(setTreeMapDisplayAndRefresh(heapWorker, newDisplay)), + onViewChange: v => dispatch(changeViewAndRefresh(v, heapWorker)), + }), + + dom.div( + { + id: "memory-tool-container" + }, + + List({ + itemComponent: SnapshotListItem, + items: snapshots, + onSave: snapshot => dispatch(pickFileAndExportSnapshot(snapshot)), + onDelete: snapshot => dispatch(deleteSnapshot(heapWorker, snapshot)), + onClick: onClickSnapshotListItem, + diffing, + }), + + Heap({ + snapshot: selectedSnapshot, + diffing, + onViewSourceInDebugger: frame => toolbox.viewSourceInDebugger(frame.source, frame.line), + onSnapshotClick: () => + dispatch(takeSnapshotAndCensus(front, heapWorker)), + onLoadMoreSiblings: lazyChildren => + dispatch(fetchImmediatelyDominated(heapWorker, + selectedSnapshot.id, + lazyChildren)), + onPopView: () => dispatch(popViewAndRefresh(heapWorker)), + individuals, + onViewIndividuals: node => { + const snapshotId = diffing + ? diffing.secondSnapshotId + : selectedSnapshot.id; + dispatch(fetchIndividuals(heapWorker, + snapshotId, + censusDisplay.breakdown, + node.reportLeafIndex)); + }, + onFocusIndividual: node => { + assert(view.state === viewState.INDIVIDUALS, + "Should be in the individuals view"); + dispatch(focusIndividual(node)); + }, + onCensusExpand: (census, node) => { + if (diffing) { + assert(diffing.census === census, + "Should only expand active census"); + dispatch(expandDiffingCensusNode(node)); + } else { + assert(selectedSnapshot && selectedSnapshot.census === census, + "If not diffing, should be expanding on selected snapshot's census"); + dispatch(expandCensusNode(selectedSnapshot.id, node)); + } + }, + onCensusCollapse: (census, node) => { + if (diffing) { + assert(diffing.census === census, + "Should only collapse active census"); + dispatch(collapseDiffingCensusNode(node)); + } else { + assert(selectedSnapshot && selectedSnapshot.census === census, + "If not diffing, should be collapsing on selected snapshot's census"); + dispatch(collapseCensusNode(selectedSnapshot.id, node)); + } + }, + onCensusFocus: (census, node) => { + if (diffing) { + assert(diffing.census === census, + "Should only focus nodes in active census"); + dispatch(focusDiffingCensusNode(node)); + } else { + assert(selectedSnapshot && selectedSnapshot.census === census, + "If not diffing, should be focusing on nodes in selected snapshot's census"); + dispatch(focusCensusNode(selectedSnapshot.id, node)); + } + }, + onDominatorTreeExpand: node => { + assert(view.state === viewState.DOMINATOR_TREE, + "If expanding dominator tree nodes, should be in dominator tree view"); + assert(selectedSnapshot, "...and we should have a selected snapshot"); + assert(selectedSnapshot.dominatorTree, + "...and that snapshot should have a dominator tree"); + dispatch(expandDominatorTreeNode(selectedSnapshot.id, node)); + }, + onDominatorTreeCollapse: node => { + assert(view.state === viewState.DOMINATOR_TREE, + "If collapsing dominator tree nodes, should be in dominator tree view"); + assert(selectedSnapshot, "...and we should have a selected snapshot"); + assert(selectedSnapshot.dominatorTree, + "...and that snapshot should have a dominator tree"); + dispatch(collapseDominatorTreeNode(selectedSnapshot.id, node)); + }, + onDominatorTreeFocus: node => { + assert(view.state === viewState.DOMINATOR_TREE, + "If focusing dominator tree nodes, should be in dominator tree view"); + assert(selectedSnapshot, "...and we should have a selected snapshot"); + assert(selectedSnapshot.dominatorTree, + "...and that snapshot should have a dominator tree"); + dispatch(focusDominatorTreeNode(selectedSnapshot.id, node)); + }, + onShortestPathsResize: newSize => { + dispatch(resizeShortestPaths(newSize)); + }, + sizes, + view, + }) + ) + ) + ); + }, +}); + +/** + * Passed into react-redux's `connect` method that is called on store change + * and passed to components. + */ +function mapStateToProps(state) { + return state; +} + +module.exports = connect(mapStateToProps)(MemoryApp); |