diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /devtools/client/memory/app.js | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/memory/app.js')
-rw-r--r-- | devtools/client/memory/app.js | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/devtools/client/memory/app.js b/devtools/client/memory/app.js new file mode 100644 index 0000000000..4b76d94ced --- /dev/null +++ b/devtools/client/memory/app.js @@ -0,0 +1,430 @@ +/* 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/. */ + +"use strict"; + +const { assert } = require("devtools/shared/DevToolsUtils"); +const { appinfo } = require("Services"); +const { + Component, + createFactory, +} = require("devtools/client/shared/vendor/react"); +const dom = require("devtools/client/shared/vendor/react-dom-factories"); +const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); +const { connect } = require("devtools/client/shared/vendor/react-redux"); +const { + censusDisplays, + labelDisplays, + treeMapDisplays, + diffingState, + viewState, +} = require("devtools/client/memory/constants"); +const { + toggleRecordingAllocationStacks, +} = require("devtools/client/memory/actions/allocations"); +const { + setCensusDisplayAndRefresh, +} = require("devtools/client/memory/actions/census-display"); +const { + setLabelDisplayAndRefresh, +} = require("devtools/client/memory/actions/label-display"); +const { + setTreeMapDisplayAndRefresh, +} = require("devtools/client/memory/actions/tree-map-display"); + +const { + getCustomCensusDisplays, + getCustomLabelDisplays, + getCustomTreeMapDisplays, +} = require("devtools/client/memory/utils"); +const { + selectSnapshotForDiffingAndRefresh, + toggleDiffing, + expandDiffingCensusNode, + collapseDiffingCensusNode, + focusDiffingCensusNode, +} = require("devtools/client/memory/actions/diffing"); +const { + setFilterStringAndRefresh, +} = require("devtools/client/memory/actions/filter"); +const { + pickFileAndExportSnapshot, + pickFileAndImportSnapshotAndCensus, +} = require("devtools/client/memory/actions/io"); +const { + selectSnapshotAndRefresh, + takeSnapshotAndCensus, + clearSnapshots, + deleteSnapshot, + fetchImmediatelyDominated, + expandCensusNode, + collapseCensusNode, + focusCensusNode, + expandDominatorTreeNode, + collapseDominatorTreeNode, + focusDominatorTreeNode, + fetchIndividuals, + focusIndividual, +} = require("devtools/client/memory/actions/snapshot"); +const { + changeViewAndRefresh, + popViewAndRefresh, +} = require("devtools/client/memory/actions/view"); +const { resizeShortestPaths } = require("devtools/client/memory/actions/sizes"); +const Toolbar = createFactory( + require("devtools/client/memory/components/Toolbar") +); +const List = createFactory(require("devtools/client/memory/components/List")); +const SnapshotListItem = createFactory( + require("devtools/client/memory/components/SnapshotListItem") +); +const Heap = createFactory(require("devtools/client/memory/components/Heap")); +const { app: appModel } = require("devtools/client/memory/models"); + +class MemoryApp extends Component { + static get propTypes() { + return { + allocations: appModel.allocations, + censusDisplay: appModel.censusDisplay, + diffing: appModel.diffing, + dispatch: PropTypes.func, + filter: appModel.filter, + front: appModel.front, + heapWorker: appModel.heapWorker, + individuals: appModel.individuals, + labelDisplay: appModel.labelDisplay, + sizes: PropTypes.object, + snapshots: appModel.snapshots, + toolbox: PropTypes.object, + view: appModel.view, + }; + } + + static get childContextTypes() { + return { + front: PropTypes.any, + heapWorker: PropTypes.any, + toolbox: PropTypes.any, + }; + } + + static get defaultProps() { + return {}; + } + + constructor(props) { + super(props); + this.onKeyDown = this.onKeyDown.bind(this); + this._getCensusDisplays = this._getCensusDisplays.bind(this); + this._getLabelDisplays = this._getLabelDisplays.bind(this); + this._getTreeMapDisplays = this._getTreeMapDisplays.bind(this); + } + + getChildContext() { + return { + front: this.props.front, + heapWorker: this.props.heapWorker, + toolbox: this.props.toolbox, + }; + } + + 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); + } + + onKeyDown(e) { + const { snapshots, dispatch, heapWorker } = this.props; + const selectedSnapshot = snapshots.find(s => s.selected); + const selectedIndex = snapshots.indexOf(selectedSnapshot); + + const isOSX = appinfo.OS == "Darwin"; + const isAccelKey = (isOSX && e.metaKey) || (!isOSX && e.ctrlKey); + + // On ACCEL+UP, select previous snapshot. + if (isAccelKey && e.key === "ArrowUp") { + const previousIndex = Math.max(0, selectedIndex - 1); + const previousSnapshotId = snapshots[previousIndex].id; + dispatch(selectSnapshotAndRefresh(heapWorker, previousSnapshotId)); + } + + // On ACCEL+DOWN, select next snapshot. + if (isAccelKey && e.key === "ArrowDown") { + const nextIndex = Math.min(snapshots.length - 1, selectedIndex + 1); + const 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() { + const { + 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: ({ url, line, column }) => { + toolbox.viewSourceInDebugger(url, line, column); + }, + 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); |