зеркало из https://github.com/mozilla/gecko-dev.git
431 строка
14 KiB
JavaScript
431 строка
14 KiB
JavaScript
/* 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);
|