summaryrefslogtreecommitdiffstats
path: root/devtools/client/memory/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/memory/app.js')
-rw-r--r--devtools/client/memory/app.js322
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);