app.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  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/. */
  4. const { assert } = require("devtools/shared/DevToolsUtils");
  5. const { appinfo } = require("Services");
  6. const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
  7. const { connect } = require("devtools/client/shared/vendor/react-redux");
  8. const { censusDisplays, labelDisplays, treeMapDisplays, diffingState, viewState } = require("./constants");
  9. const { toggleRecordingAllocationStacks } = require("./actions/allocations");
  10. const { setCensusDisplayAndRefresh } = require("./actions/census-display");
  11. const { setLabelDisplayAndRefresh } = require("./actions/label-display");
  12. const { setTreeMapDisplayAndRefresh } = require("./actions/tree-map-display");
  13. const {
  14. getCustomCensusDisplays,
  15. getCustomLabelDisplays,
  16. getCustomTreeMapDisplays,
  17. } = require("devtools/client/memory/utils");
  18. const {
  19. selectSnapshotForDiffingAndRefresh,
  20. toggleDiffing,
  21. expandDiffingCensusNode,
  22. collapseDiffingCensusNode,
  23. focusDiffingCensusNode,
  24. } = require("./actions/diffing");
  25. const { setFilterStringAndRefresh } = require("./actions/filter");
  26. const { pickFileAndExportSnapshot, pickFileAndImportSnapshotAndCensus } = require("./actions/io");
  27. const {
  28. selectSnapshotAndRefresh,
  29. takeSnapshotAndCensus,
  30. clearSnapshots,
  31. deleteSnapshot,
  32. fetchImmediatelyDominated,
  33. expandCensusNode,
  34. collapseCensusNode,
  35. focusCensusNode,
  36. expandDominatorTreeNode,
  37. collapseDominatorTreeNode,
  38. focusDominatorTreeNode,
  39. fetchIndividuals,
  40. focusIndividual,
  41. } = require("./actions/snapshot");
  42. const { changeViewAndRefresh, popViewAndRefresh } = require("./actions/view");
  43. const { resizeShortestPaths } = require("./actions/sizes");
  44. const Toolbar = createFactory(require("./components/toolbar"));
  45. const List = createFactory(require("./components/list"));
  46. const SnapshotListItem = createFactory(require("./components/snapshot-list-item"));
  47. const Heap = createFactory(require("./components/heap"));
  48. const { app: appModel } = require("./models");
  49. const MemoryApp = createClass({
  50. displayName: "MemoryApp",
  51. propTypes: appModel,
  52. getDefaultProps() {
  53. return {};
  54. },
  55. componentDidMount() {
  56. // Attach the keydown listener directly to the window. When an element that
  57. // has the focus (such as a tree node) is removed from the DOM, the focus
  58. // falls back to the body.
  59. window.addEventListener("keydown", this.onKeyDown);
  60. },
  61. componentWillUnmount() {
  62. window.removeEventListener("keydown", this.onKeyDown);
  63. },
  64. childContextTypes: {
  65. front: PropTypes.any,
  66. heapWorker: PropTypes.any,
  67. toolbox: PropTypes.any,
  68. },
  69. getChildContext() {
  70. return {
  71. front: this.props.front,
  72. heapWorker: this.props.heapWorker,
  73. toolbox: this.props.toolbox,
  74. };
  75. },
  76. onKeyDown(e) {
  77. let { snapshots, dispatch, heapWorker } = this.props;
  78. const selectedSnapshot = snapshots.find(s => s.selected);
  79. const selectedIndex = snapshots.indexOf(selectedSnapshot);
  80. let isOSX = appinfo.OS == "Darwin";
  81. let isAccelKey = (isOSX && e.metaKey) || (!isOSX && e.ctrlKey);
  82. // On ACCEL+UP, select previous snapshot.
  83. if (isAccelKey && e.key === "ArrowUp") {
  84. let previousIndex = Math.max(0, selectedIndex - 1);
  85. let previousSnapshotId = snapshots[previousIndex].id;
  86. dispatch(selectSnapshotAndRefresh(heapWorker, previousSnapshotId));
  87. }
  88. // On ACCEL+DOWN, select next snapshot.
  89. if (isAccelKey && e.key === "ArrowDown") {
  90. let nextIndex = Math.min(snapshots.length - 1, selectedIndex + 1);
  91. let nextSnapshotId = snapshots[nextIndex].id;
  92. dispatch(selectSnapshotAndRefresh(heapWorker, nextSnapshotId));
  93. }
  94. },
  95. _getCensusDisplays() {
  96. const customDisplays = getCustomCensusDisplays();
  97. const custom = Object.keys(customDisplays).reduce((arr, key) => {
  98. arr.push(customDisplays[key]);
  99. return arr;
  100. }, []);
  101. return [
  102. censusDisplays.coarseType,
  103. censusDisplays.allocationStack,
  104. censusDisplays.invertedAllocationStack,
  105. ].concat(custom);
  106. },
  107. _getLabelDisplays() {
  108. const customDisplays = getCustomLabelDisplays();
  109. const custom = Object.keys(customDisplays).reduce((arr, key) => {
  110. arr.push(customDisplays[key]);
  111. return arr;
  112. }, []);
  113. return [
  114. labelDisplays.coarseType,
  115. labelDisplays.allocationStack,
  116. ].concat(custom);
  117. },
  118. _getTreeMapDisplays() {
  119. const customDisplays = getCustomTreeMapDisplays();
  120. const custom = Object.keys(customDisplays).reduce((arr, key) => {
  121. arr.push(customDisplays[key]);
  122. return arr;
  123. }, []);
  124. return [
  125. treeMapDisplays.coarseType
  126. ].concat(custom);
  127. },
  128. render() {
  129. let {
  130. dispatch,
  131. snapshots,
  132. front,
  133. heapWorker,
  134. allocations,
  135. toolbox,
  136. filter,
  137. diffing,
  138. view,
  139. sizes,
  140. censusDisplay,
  141. labelDisplay,
  142. individuals,
  143. } = this.props;
  144. const selectedSnapshot = snapshots.find(s => s.selected);
  145. const onClickSnapshotListItem = diffing && diffing.state === diffingState.SELECTING
  146. ? snapshot => dispatch(selectSnapshotForDiffingAndRefresh(heapWorker, snapshot))
  147. : snapshot => dispatch(selectSnapshotAndRefresh(heapWorker, snapshot.id));
  148. return (
  149. dom.div(
  150. {
  151. id: "memory-tool"
  152. },
  153. Toolbar({
  154. snapshots,
  155. censusDisplays: this._getCensusDisplays(),
  156. censusDisplay,
  157. onCensusDisplayChange: newDisplay =>
  158. dispatch(setCensusDisplayAndRefresh(heapWorker, newDisplay)),
  159. onImportClick: () => dispatch(pickFileAndImportSnapshotAndCensus(heapWorker)),
  160. onClearSnapshotsClick: () => dispatch(clearSnapshots(heapWorker)),
  161. onTakeSnapshotClick: () => dispatch(takeSnapshotAndCensus(front, heapWorker)),
  162. onToggleRecordAllocationStacks: () =>
  163. dispatch(toggleRecordingAllocationStacks(front)),
  164. allocations,
  165. filterString: filter,
  166. setFilterString: filterString =>
  167. dispatch(setFilterStringAndRefresh(filterString, heapWorker)),
  168. diffing,
  169. onToggleDiffing: () => dispatch(toggleDiffing()),
  170. view,
  171. labelDisplays: this._getLabelDisplays(),
  172. labelDisplay,
  173. onLabelDisplayChange: newDisplay =>
  174. dispatch(setLabelDisplayAndRefresh(heapWorker, newDisplay)),
  175. treeMapDisplays: this._getTreeMapDisplays(),
  176. onTreeMapDisplayChange: newDisplay =>
  177. dispatch(setTreeMapDisplayAndRefresh(heapWorker, newDisplay)),
  178. onViewChange: v => dispatch(changeViewAndRefresh(v, heapWorker)),
  179. }),
  180. dom.div(
  181. {
  182. id: "memory-tool-container"
  183. },
  184. List({
  185. itemComponent: SnapshotListItem,
  186. items: snapshots,
  187. onSave: snapshot => dispatch(pickFileAndExportSnapshot(snapshot)),
  188. onDelete: snapshot => dispatch(deleteSnapshot(heapWorker, snapshot)),
  189. onClick: onClickSnapshotListItem,
  190. diffing,
  191. }),
  192. Heap({
  193. snapshot: selectedSnapshot,
  194. diffing,
  195. onViewSourceInDebugger: frame => toolbox.viewSourceInDebugger(frame.source, frame.line),
  196. onSnapshotClick: () =>
  197. dispatch(takeSnapshotAndCensus(front, heapWorker)),
  198. onLoadMoreSiblings: lazyChildren =>
  199. dispatch(fetchImmediatelyDominated(heapWorker,
  200. selectedSnapshot.id,
  201. lazyChildren)),
  202. onPopView: () => dispatch(popViewAndRefresh(heapWorker)),
  203. individuals,
  204. onViewIndividuals: node => {
  205. const snapshotId = diffing
  206. ? diffing.secondSnapshotId
  207. : selectedSnapshot.id;
  208. dispatch(fetchIndividuals(heapWorker,
  209. snapshotId,
  210. censusDisplay.breakdown,
  211. node.reportLeafIndex));
  212. },
  213. onFocusIndividual: node => {
  214. assert(view.state === viewState.INDIVIDUALS,
  215. "Should be in the individuals view");
  216. dispatch(focusIndividual(node));
  217. },
  218. onCensusExpand: (census, node) => {
  219. if (diffing) {
  220. assert(diffing.census === census,
  221. "Should only expand active census");
  222. dispatch(expandDiffingCensusNode(node));
  223. } else {
  224. assert(selectedSnapshot && selectedSnapshot.census === census,
  225. "If not diffing, should be expanding on selected snapshot's census");
  226. dispatch(expandCensusNode(selectedSnapshot.id, node));
  227. }
  228. },
  229. onCensusCollapse: (census, node) => {
  230. if (diffing) {
  231. assert(diffing.census === census,
  232. "Should only collapse active census");
  233. dispatch(collapseDiffingCensusNode(node));
  234. } else {
  235. assert(selectedSnapshot && selectedSnapshot.census === census,
  236. "If not diffing, should be collapsing on selected snapshot's census");
  237. dispatch(collapseCensusNode(selectedSnapshot.id, node));
  238. }
  239. },
  240. onCensusFocus: (census, node) => {
  241. if (diffing) {
  242. assert(diffing.census === census,
  243. "Should only focus nodes in active census");
  244. dispatch(focusDiffingCensusNode(node));
  245. } else {
  246. assert(selectedSnapshot && selectedSnapshot.census === census,
  247. "If not diffing, should be focusing on nodes in selected snapshot's census");
  248. dispatch(focusCensusNode(selectedSnapshot.id, node));
  249. }
  250. },
  251. onDominatorTreeExpand: node => {
  252. assert(view.state === viewState.DOMINATOR_TREE,
  253. "If expanding dominator tree nodes, should be in dominator tree view");
  254. assert(selectedSnapshot, "...and we should have a selected snapshot");
  255. assert(selectedSnapshot.dominatorTree,
  256. "...and that snapshot should have a dominator tree");
  257. dispatch(expandDominatorTreeNode(selectedSnapshot.id, node));
  258. },
  259. onDominatorTreeCollapse: node => {
  260. assert(view.state === viewState.DOMINATOR_TREE,
  261. "If collapsing dominator tree nodes, should be in dominator tree view");
  262. assert(selectedSnapshot, "...and we should have a selected snapshot");
  263. assert(selectedSnapshot.dominatorTree,
  264. "...and that snapshot should have a dominator tree");
  265. dispatch(collapseDominatorTreeNode(selectedSnapshot.id, node));
  266. },
  267. onDominatorTreeFocus: node => {
  268. assert(view.state === viewState.DOMINATOR_TREE,
  269. "If focusing dominator tree nodes, should be in dominator tree view");
  270. assert(selectedSnapshot, "...and we should have a selected snapshot");
  271. assert(selectedSnapshot.dominatorTree,
  272. "...and that snapshot should have a dominator tree");
  273. dispatch(focusDominatorTreeNode(selectedSnapshot.id, node));
  274. },
  275. onShortestPathsResize: newSize => {
  276. dispatch(resizeShortestPaths(newSize));
  277. },
  278. sizes,
  279. view,
  280. })
  281. )
  282. )
  283. );
  284. },
  285. });
  286. /**
  287. * Passed into react-redux's `connect` method that is called on store change
  288. * and passed to components.
  289. */
  290. function mapStateToProps(state) {
  291. return state;
  292. }
  293. module.exports = connect(mapStateToProps)(MemoryApp);