app.js (14122B)
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 5 "use strict"; 6 7 const { assert } = require("resource://devtools/shared/DevToolsUtils.js"); 8 const { 9 Component, 10 createFactory, 11 } = require("resource://devtools/client/shared/vendor/react.mjs"); 12 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 13 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 14 const { 15 connect, 16 } = require("resource://devtools/client/shared/vendor/react-redux.js"); 17 const { 18 censusDisplays, 19 labelDisplays, 20 treeMapDisplays, 21 diffingState, 22 viewState, 23 } = require("resource://devtools/client/memory/constants.js"); 24 const { 25 toggleRecordingAllocationStacks, 26 } = require("resource://devtools/client/memory/actions/allocations.js"); 27 const { 28 setCensusDisplayAndRefresh, 29 } = require("resource://devtools/client/memory/actions/census-display.js"); 30 const { 31 setLabelDisplayAndRefresh, 32 } = require("resource://devtools/client/memory/actions/label-display.js"); 33 const { 34 setTreeMapDisplayAndRefresh, 35 } = require("resource://devtools/client/memory/actions/tree-map-display.js"); 36 37 const { 38 getCustomCensusDisplays, 39 getCustomLabelDisplays, 40 getCustomTreeMapDisplays, 41 } = require("resource://devtools/client/memory/utils.js"); 42 const { 43 selectSnapshotForDiffingAndRefresh, 44 toggleDiffing, 45 expandDiffingCensusNode, 46 collapseDiffingCensusNode, 47 focusDiffingCensusNode, 48 } = require("resource://devtools/client/memory/actions/diffing.js"); 49 const { 50 setFilterStringAndRefresh, 51 } = require("resource://devtools/client/memory/actions/filter.js"); 52 const { 53 pickFileAndExportSnapshot, 54 pickFileAndImportSnapshotAndCensus, 55 } = require("resource://devtools/client/memory/actions/io.js"); 56 const { 57 selectSnapshotAndRefresh, 58 takeSnapshotAndCensus, 59 clearSnapshots, 60 deleteSnapshot, 61 fetchImmediatelyDominated, 62 expandCensusNode, 63 collapseCensusNode, 64 focusCensusNode, 65 expandDominatorTreeNode, 66 collapseDominatorTreeNode, 67 focusDominatorTreeNode, 68 fetchIndividuals, 69 focusIndividual, 70 } = require("resource://devtools/client/memory/actions/snapshot.js"); 71 const { 72 changeViewAndRefresh, 73 popViewAndRefresh, 74 } = require("resource://devtools/client/memory/actions/view.js"); 75 const { 76 resizeShortestPaths, 77 } = require("resource://devtools/client/memory/actions/sizes.js"); 78 const Toolbar = createFactory( 79 require("resource://devtools/client/memory/components/Toolbar.js") 80 ); 81 const List = createFactory( 82 require("resource://devtools/client/memory/components/List.js") 83 ); 84 const SnapshotListItem = createFactory( 85 require("resource://devtools/client/memory/components/SnapshotListItem.js") 86 ); 87 const Heap = createFactory( 88 require("resource://devtools/client/memory/components/Heap.js") 89 ); 90 const { 91 app: appModel, 92 } = require("resource://devtools/client/memory/models.js"); 93 94 class MemoryApp extends Component { 95 static get propTypes() { 96 return { 97 allocations: appModel.allocations, 98 censusDisplay: appModel.censusDisplay, 99 commands: appModel.commands, 100 diffing: appModel.diffing, 101 dispatch: PropTypes.func, 102 filter: appModel.filter, 103 front: appModel.front, 104 heapWorker: appModel.heapWorker, 105 individuals: appModel.individuals, 106 labelDisplay: appModel.labelDisplay, 107 sizes: PropTypes.object, 108 snapshots: appModel.snapshots, 109 toolbox: PropTypes.object, 110 view: appModel.view, 111 }; 112 } 113 114 static get childContextTypes() { 115 return { 116 front: PropTypes.any, 117 heapWorker: PropTypes.any, 118 toolbox: PropTypes.any, 119 }; 120 } 121 122 static get defaultProps() { 123 return {}; 124 } 125 126 constructor(props) { 127 super(props); 128 this.onKeyDown = this.onKeyDown.bind(this); 129 this._getCensusDisplays = this._getCensusDisplays.bind(this); 130 this._getLabelDisplays = this._getLabelDisplays.bind(this); 131 this._getTreeMapDisplays = this._getTreeMapDisplays.bind(this); 132 } 133 134 getChildContext() { 135 return { 136 front: this.props.front, 137 heapWorker: this.props.heapWorker, 138 toolbox: this.props.toolbox, 139 }; 140 } 141 142 componentDidMount() { 143 // Attach the keydown listener directly to the window. When an element that 144 // has the focus (such as a tree node) is removed from the DOM, the focus 145 // falls back to the body. 146 window.addEventListener("keydown", this.onKeyDown); 147 } 148 149 componentWillUnmount() { 150 window.removeEventListener("keydown", this.onKeyDown); 151 } 152 153 onKeyDown(e) { 154 const { snapshots, dispatch, heapWorker } = this.props; 155 const selectedSnapshot = snapshots.find(s => s.selected); 156 const selectedIndex = snapshots.indexOf(selectedSnapshot); 157 158 const isOSX = Services.appinfo.OS == "Darwin"; 159 const isAccelKey = (isOSX && e.metaKey) || (!isOSX && e.ctrlKey); 160 161 // On ACCEL+UP, select previous snapshot. 162 if (isAccelKey && e.key === "ArrowUp") { 163 const previousIndex = Math.max(0, selectedIndex - 1); 164 const previousSnapshotId = snapshots[previousIndex].id; 165 dispatch(selectSnapshotAndRefresh(heapWorker, previousSnapshotId)); 166 } 167 168 // On ACCEL+DOWN, select next snapshot. 169 if (isAccelKey && e.key === "ArrowDown") { 170 const nextIndex = Math.min(snapshots.length - 1, selectedIndex + 1); 171 const nextSnapshotId = snapshots[nextIndex].id; 172 dispatch(selectSnapshotAndRefresh(heapWorker, nextSnapshotId)); 173 } 174 } 175 176 _getCensusDisplays() { 177 const customDisplays = getCustomCensusDisplays(); 178 const custom = Object.keys(customDisplays).reduce((arr, key) => { 179 arr.push(customDisplays[key]); 180 return arr; 181 }, []); 182 183 return [ 184 censusDisplays.coarseType, 185 censusDisplays.allocationStack, 186 censusDisplays.invertedAllocationStack, 187 ].concat(custom); 188 } 189 190 _getLabelDisplays() { 191 const customDisplays = getCustomLabelDisplays(); 192 const custom = Object.keys(customDisplays).reduce((arr, key) => { 193 arr.push(customDisplays[key]); 194 return arr; 195 }, []); 196 197 return [labelDisplays.coarseType, labelDisplays.allocationStack].concat( 198 custom 199 ); 200 } 201 202 _getTreeMapDisplays() { 203 const customDisplays = getCustomTreeMapDisplays(); 204 const custom = Object.keys(customDisplays).reduce((arr, key) => { 205 arr.push(customDisplays[key]); 206 return arr; 207 }, []); 208 209 return [treeMapDisplays.coarseType].concat(custom); 210 } 211 212 render() { 213 const { 214 commands, 215 dispatch, 216 snapshots, 217 front, 218 heapWorker, 219 allocations, 220 toolbox, 221 filter, 222 diffing, 223 view, 224 sizes, 225 censusDisplay, 226 labelDisplay, 227 individuals, 228 } = this.props; 229 230 const selectedSnapshot = snapshots.find(s => s.selected); 231 232 const onClickSnapshotListItem = 233 diffing && diffing.state === diffingState.SELECTING 234 ? snapshot => 235 dispatch(selectSnapshotForDiffingAndRefresh(heapWorker, snapshot)) 236 : snapshot => 237 dispatch(selectSnapshotAndRefresh(heapWorker, snapshot.id)); 238 239 return dom.div( 240 { 241 id: "memory-tool", 242 }, 243 244 Toolbar({ 245 snapshots, 246 censusDisplays: this._getCensusDisplays(), 247 censusDisplay, 248 onCensusDisplayChange: newDisplay => 249 dispatch(setCensusDisplayAndRefresh(heapWorker, newDisplay)), 250 onImportClick: () => 251 dispatch(pickFileAndImportSnapshotAndCensus(heapWorker)), 252 onClearSnapshotsClick: () => dispatch(clearSnapshots(heapWorker)), 253 onTakeSnapshotClick: () => 254 dispatch(takeSnapshotAndCensus(front, heapWorker)), 255 onToggleRecordAllocationStacks: () => 256 dispatch(toggleRecordingAllocationStacks(commands)), 257 allocations, 258 filterString: filter, 259 setFilterString: filterString => 260 dispatch(setFilterStringAndRefresh(filterString, heapWorker)), 261 diffing, 262 onToggleDiffing: () => dispatch(toggleDiffing()), 263 view, 264 labelDisplays: this._getLabelDisplays(), 265 labelDisplay, 266 onLabelDisplayChange: newDisplay => 267 dispatch(setLabelDisplayAndRefresh(heapWorker, newDisplay)), 268 treeMapDisplays: this._getTreeMapDisplays(), 269 onTreeMapDisplayChange: newDisplay => 270 dispatch(setTreeMapDisplayAndRefresh(heapWorker, newDisplay)), 271 onViewChange: v => dispatch(changeViewAndRefresh(v, heapWorker)), 272 }), 273 274 dom.div( 275 { 276 id: "memory-tool-container", 277 }, 278 279 List({ 280 itemComponent: SnapshotListItem, 281 items: snapshots, 282 onSave: snapshot => dispatch(pickFileAndExportSnapshot(snapshot)), 283 onDelete: snapshot => dispatch(deleteSnapshot(heapWorker, snapshot)), 284 onClick: onClickSnapshotListItem, 285 diffing, 286 }), 287 288 Heap({ 289 snapshot: selectedSnapshot, 290 diffing, 291 onViewSourceInDebugger: ({ url, line, column }) => { 292 toolbox.viewSourceInDebugger(url, line, column); 293 }, 294 onSnapshotClick: () => 295 dispatch(takeSnapshotAndCensus(front, heapWorker)), 296 onLoadMoreSiblings: lazyChildren => 297 dispatch( 298 fetchImmediatelyDominated( 299 heapWorker, 300 selectedSnapshot.id, 301 lazyChildren 302 ) 303 ), 304 onPopView: () => dispatch(popViewAndRefresh(heapWorker)), 305 individuals, 306 onViewIndividuals: node => { 307 const snapshotId = diffing 308 ? diffing.secondSnapshotId 309 : selectedSnapshot.id; 310 dispatch( 311 fetchIndividuals( 312 heapWorker, 313 snapshotId, 314 censusDisplay.breakdown, 315 node.reportLeafIndex 316 ) 317 ); 318 }, 319 onFocusIndividual: node => { 320 assert( 321 view.state === viewState.INDIVIDUALS, 322 "Should be in the individuals view" 323 ); 324 dispatch(focusIndividual(node)); 325 }, 326 onCensusExpand: (census, node) => { 327 if (diffing) { 328 assert( 329 diffing.census === census, 330 "Should only expand active census" 331 ); 332 dispatch(expandDiffingCensusNode(node)); 333 } else { 334 assert( 335 selectedSnapshot && selectedSnapshot.census === census, 336 "If not diffing, " + 337 "should be expanding on selected snapshot's census" 338 ); 339 dispatch(expandCensusNode(selectedSnapshot.id, node)); 340 } 341 }, 342 onCensusCollapse: (census, node) => { 343 if (diffing) { 344 assert( 345 diffing.census === census, 346 "Should only collapse active census" 347 ); 348 dispatch(collapseDiffingCensusNode(node)); 349 } else { 350 assert( 351 selectedSnapshot && selectedSnapshot.census === census, 352 "If not diffing, " + 353 "should be collapsing on selected snapshot's census" 354 ); 355 dispatch(collapseCensusNode(selectedSnapshot.id, node)); 356 } 357 }, 358 onCensusFocus: (census, node) => { 359 if (diffing) { 360 assert( 361 diffing.census === census, 362 "Should only focus nodes in active census" 363 ); 364 dispatch(focusDiffingCensusNode(node)); 365 } else { 366 assert( 367 selectedSnapshot && selectedSnapshot.census === census, 368 "If not diffing, " + 369 "should be focusing on nodes in selected snapshot's census" 370 ); 371 dispatch(focusCensusNode(selectedSnapshot.id, node)); 372 } 373 }, 374 onDominatorTreeExpand: node => { 375 assert( 376 view.state === viewState.DOMINATOR_TREE, 377 "If expanding dominator tree nodes, " + 378 "should be in dominator tree view" 379 ); 380 assert( 381 selectedSnapshot, 382 "...and we should have a selected snapshot" 383 ); 384 assert( 385 selectedSnapshot.dominatorTree, 386 "...and that snapshot should have a dominator tree" 387 ); 388 dispatch(expandDominatorTreeNode(selectedSnapshot.id, node)); 389 }, 390 onDominatorTreeCollapse: node => { 391 assert( 392 view.state === viewState.DOMINATOR_TREE, 393 "If collapsing dominator tree nodes, " + 394 "should be in dominator tree view" 395 ); 396 assert( 397 selectedSnapshot, 398 "...and we should have a selected snapshot" 399 ); 400 assert( 401 selectedSnapshot.dominatorTree, 402 "...and that snapshot should have a dominator tree" 403 ); 404 dispatch(collapseDominatorTreeNode(selectedSnapshot.id, node)); 405 }, 406 onDominatorTreeFocus: node => { 407 assert( 408 view.state === viewState.DOMINATOR_TREE, 409 "If focusing dominator tree nodes, " + 410 "should be in dominator tree view" 411 ); 412 assert( 413 selectedSnapshot, 414 "...and we should have a selected snapshot" 415 ); 416 assert( 417 selectedSnapshot.dominatorTree, 418 "...and that snapshot should have a dominator tree" 419 ); 420 dispatch(focusDominatorTreeNode(selectedSnapshot.id, node)); 421 }, 422 onShortestPathsResize: newSize => { 423 dispatch(resizeShortestPaths(newSize)); 424 }, 425 sizes, 426 view, 427 }) 428 ) 429 ); 430 } 431 } 432 433 /** 434 * Passed into react-redux's `connect` method that is called on store change 435 * and passed to components. 436 */ 437 function mapStateToProps(state) { 438 return state; 439 } 440 441 module.exports = connect(mapStateToProps)(MemoryApp);