snapshots.js (13922B)
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 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 "use strict"; 5 6 const { 7 immutableUpdate, 8 assert, 9 } = require("resource://devtools/shared/DevToolsUtils.js"); 10 const { 11 actions, 12 snapshotState: states, 13 censusState, 14 treeMapState, 15 dominatorTreeState, 16 viewState, 17 } = require("resource://devtools/client/memory/constants.js"); 18 const DominatorTreeNode = require("resource://devtools/shared/heapsnapshot/DominatorTreeNode.js"); 19 20 const handlers = Object.create(null); 21 22 handlers[actions.SNAPSHOT_ERROR] = function (snapshots, { id, error }) { 23 return snapshots.map(snapshot => { 24 return snapshot.id === id 25 ? immutableUpdate(snapshot, { state: states.ERROR, error }) 26 : snapshot; 27 }); 28 }; 29 30 handlers[actions.TAKE_SNAPSHOT_START] = function (snapshots, { snapshot }) { 31 return [...snapshots, snapshot]; 32 }; 33 34 handlers[actions.TAKE_SNAPSHOT_END] = function (snapshots, { id, path }) { 35 return snapshots.map(snapshot => { 36 return snapshot.id === id 37 ? immutableUpdate(snapshot, { state: states.SAVED, path }) 38 : snapshot; 39 }); 40 }; 41 42 handlers[actions.IMPORT_SNAPSHOT_START] = handlers[actions.TAKE_SNAPSHOT_START]; 43 44 handlers[actions.READ_SNAPSHOT_START] = function (snapshots, { id }) { 45 return snapshots.map(snapshot => { 46 return snapshot.id === id 47 ? immutableUpdate(snapshot, { state: states.READING }) 48 : snapshot; 49 }); 50 }; 51 52 handlers[actions.READ_SNAPSHOT_END] = function ( 53 snapshots, 54 { id, creationTime } 55 ) { 56 return snapshots.map(snapshot => { 57 return snapshot.id === id 58 ? immutableUpdate(snapshot, { state: states.READ, creationTime }) 59 : snapshot; 60 }); 61 }; 62 63 handlers[actions.TAKE_CENSUS_START] = function ( 64 snapshots, 65 { id, display, filter } 66 ) { 67 const census = { 68 report: null, 69 display, 70 filter, 71 state: censusState.SAVING, 72 }; 73 74 return snapshots.map(snapshot => { 75 return snapshot.id === id 76 ? immutableUpdate(snapshot, { census }) 77 : snapshot; 78 }); 79 }; 80 81 handlers[actions.TAKE_CENSUS_END] = function ( 82 snapshots, 83 { id, report, parentMap, display, filter } 84 ) { 85 const census = { 86 report, 87 parentMap, 88 expanded: new Set(), 89 display, 90 filter, 91 state: censusState.SAVED, 92 }; 93 94 return snapshots.map(snapshot => { 95 return snapshot.id === id 96 ? immutableUpdate(snapshot, { census }) 97 : snapshot; 98 }); 99 }; 100 101 handlers[actions.TAKE_CENSUS_ERROR] = function (snapshots, { id, error }) { 102 assert(error, "actions with TAKE_CENSUS_ERROR should have an error"); 103 104 return snapshots.map(snapshot => { 105 if (snapshot.id !== id) { 106 return snapshot; 107 } 108 109 const census = Object.freeze({ 110 state: censusState.ERROR, 111 error, 112 }); 113 114 return immutableUpdate(snapshot, { census }); 115 }); 116 }; 117 118 handlers[actions.TAKE_TREE_MAP_START] = function (snapshots, { id, display }) { 119 const treeMap = { 120 report: null, 121 display, 122 state: treeMapState.SAVING, 123 }; 124 125 return snapshots.map(snapshot => { 126 return snapshot.id === id 127 ? immutableUpdate(snapshot, { treeMap }) 128 : snapshot; 129 }); 130 }; 131 132 handlers[actions.TAKE_TREE_MAP_END] = function (snapshots, action) { 133 const { id, report, display } = action; 134 const treeMap = { 135 report, 136 display, 137 state: treeMapState.SAVED, 138 }; 139 140 return snapshots.map(snapshot => { 141 return snapshot.id === id 142 ? immutableUpdate(snapshot, { treeMap }) 143 : snapshot; 144 }); 145 }; 146 147 handlers[actions.TAKE_TREE_MAP_ERROR] = function (snapshots, { id, error }) { 148 assert(error, "actions with TAKE_TREE_MAP_ERROR should have an error"); 149 150 return snapshots.map(snapshot => { 151 if (snapshot.id !== id) { 152 return snapshot; 153 } 154 155 const treeMap = Object.freeze({ 156 state: treeMapState.ERROR, 157 error, 158 }); 159 160 return immutableUpdate(snapshot, { treeMap }); 161 }); 162 }; 163 164 handlers[actions.EXPAND_CENSUS_NODE] = function (snapshots, { id, node }) { 165 return snapshots.map(snapshot => { 166 if (snapshot.id !== id) { 167 return snapshot; 168 } 169 170 assert(snapshot.census, "Should have a census"); 171 assert(snapshot.census.report, "Should have a census report"); 172 assert(snapshot.census.expanded, "Should have a census's expanded set"); 173 174 const expanded = new Set(snapshot.census.expanded); 175 expanded.add(node.id); 176 const census = immutableUpdate(snapshot.census, { expanded }); 177 return immutableUpdate(snapshot, { census }); 178 }); 179 }; 180 181 handlers[actions.COLLAPSE_CENSUS_NODE] = function (snapshots, { id, node }) { 182 return snapshots.map(snapshot => { 183 if (snapshot.id !== id) { 184 return snapshot; 185 } 186 187 assert(snapshot.census, "Should have a census"); 188 assert(snapshot.census.report, "Should have a census report"); 189 assert(snapshot.census.expanded, "Should have a census's expanded set"); 190 191 const expanded = new Set(snapshot.census.expanded); 192 expanded.delete(node.id); 193 const census = immutableUpdate(snapshot.census, { expanded }); 194 return immutableUpdate(snapshot, { census }); 195 }); 196 }; 197 198 handlers[actions.FOCUS_CENSUS_NODE] = function (snapshots, { id, node }) { 199 return snapshots.map(snapshot => { 200 if (snapshot.id !== id) { 201 return snapshot; 202 } 203 204 assert(snapshot.census, "Should have a census"); 205 const census = immutableUpdate(snapshot.census, { focused: node }); 206 return immutableUpdate(snapshot, { census }); 207 }); 208 }; 209 210 handlers[actions.SELECT_SNAPSHOT] = function (snapshots, { id }) { 211 return snapshots.map(s => immutableUpdate(s, { selected: s.id === id })); 212 }; 213 214 handlers[actions.DELETE_SNAPSHOTS_START] = function (snapshots, { ids }) { 215 return snapshots.filter(s => !ids.includes(s.id)); 216 }; 217 218 handlers[actions.DELETE_SNAPSHOTS_END] = function (snapshots) { 219 return snapshots; 220 }; 221 222 handlers[actions.CHANGE_VIEW] = function (snapshots, { newViewState }) { 223 return newViewState === viewState.DIFFING 224 ? snapshots.map(s => immutableUpdate(s, { selected: false })) 225 : snapshots; 226 }; 227 228 handlers[actions.POP_VIEW] = function (snapshots, { previousView }) { 229 return snapshots.map(s => 230 immutableUpdate(s, { 231 selected: s.id === previousView.selected, 232 }) 233 ); 234 }; 235 236 handlers[actions.COMPUTE_DOMINATOR_TREE_START] = function (snapshots, { id }) { 237 const dominatorTree = Object.freeze({ 238 state: dominatorTreeState.COMPUTING, 239 dominatorTreeId: undefined, 240 root: undefined, 241 }); 242 243 return snapshots.map(snapshot => { 244 if (snapshot.id !== id) { 245 return snapshot; 246 } 247 248 assert(!snapshot.dominatorTree, "Should not have a dominator tree model"); 249 return immutableUpdate(snapshot, { dominatorTree }); 250 }); 251 }; 252 253 handlers[actions.COMPUTE_DOMINATOR_TREE_END] = function ( 254 snapshots, 255 { id, dominatorTreeId } 256 ) { 257 return snapshots.map(snapshot => { 258 if (snapshot.id !== id) { 259 return snapshot; 260 } 261 262 assert(snapshot.dominatorTree, "Should have a dominator tree model"); 263 assert( 264 snapshot.dominatorTree.state == dominatorTreeState.COMPUTING, 265 "Should be in the COMPUTING state" 266 ); 267 268 const dominatorTree = immutableUpdate(snapshot.dominatorTree, { 269 state: dominatorTreeState.COMPUTED, 270 dominatorTreeId, 271 }); 272 return immutableUpdate(snapshot, { dominatorTree }); 273 }); 274 }; 275 276 handlers[actions.FETCH_DOMINATOR_TREE_START] = function ( 277 snapshots, 278 { id, display } 279 ) { 280 return snapshots.map(snapshot => { 281 if (snapshot.id !== id) { 282 return snapshot; 283 } 284 285 assert(snapshot.dominatorTree, "Should have a dominator tree model"); 286 assert( 287 snapshot.dominatorTree.state !== dominatorTreeState.COMPUTING && 288 snapshot.dominatorTree.state !== dominatorTreeState.ERROR, 289 "Should have already computed the dominator tree, found state = " + 290 snapshot.dominatorTree.state 291 ); 292 293 const dominatorTree = immutableUpdate(snapshot.dominatorTree, { 294 state: dominatorTreeState.FETCHING, 295 root: undefined, 296 display, 297 }); 298 return immutableUpdate(snapshot, { dominatorTree }); 299 }); 300 }; 301 302 handlers[actions.FETCH_DOMINATOR_TREE_END] = function ( 303 snapshots, 304 { id, root } 305 ) { 306 return snapshots.map(snapshot => { 307 if (snapshot.id !== id) { 308 return snapshot; 309 } 310 311 assert(snapshot.dominatorTree, "Should have a dominator tree model"); 312 assert( 313 snapshot.dominatorTree.state == dominatorTreeState.FETCHING, 314 "Should be in the FETCHING state" 315 ); 316 317 let focused; 318 if (snapshot.dominatorTree.focused) { 319 focused = (function findFocused(node) { 320 if (node.nodeId === snapshot.dominatorTree.focused.nodeId) { 321 return node; 322 } 323 324 if (node.children) { 325 const length = node.children.length; 326 for (let i = 0; i < length; i++) { 327 const result = findFocused(node.children[i]); 328 if (result) { 329 return result; 330 } 331 } 332 } 333 334 return undefined; 335 })(root); 336 } 337 338 const dominatorTree = immutableUpdate(snapshot.dominatorTree, { 339 state: dominatorTreeState.LOADED, 340 root, 341 expanded: new Set(), 342 focused, 343 }); 344 345 return immutableUpdate(snapshot, { dominatorTree }); 346 }); 347 }; 348 349 handlers[actions.EXPAND_DOMINATOR_TREE_NODE] = function ( 350 snapshots, 351 { id, node } 352 ) { 353 return snapshots.map(snapshot => { 354 if (snapshot.id !== id) { 355 return snapshot; 356 } 357 358 assert(snapshot.dominatorTree, "Should have a dominator tree"); 359 assert( 360 snapshot.dominatorTree.expanded, 361 "Should have the dominator tree's expanded set" 362 ); 363 364 const expanded = new Set(snapshot.dominatorTree.expanded); 365 expanded.add(node.nodeId); 366 const dominatorTree = immutableUpdate(snapshot.dominatorTree, { expanded }); 367 return immutableUpdate(snapshot, { dominatorTree }); 368 }); 369 }; 370 371 handlers[actions.COLLAPSE_DOMINATOR_TREE_NODE] = function ( 372 snapshots, 373 { id, node } 374 ) { 375 return snapshots.map(snapshot => { 376 if (snapshot.id !== id) { 377 return snapshot; 378 } 379 380 assert(snapshot.dominatorTree, "Should have a dominator tree"); 381 assert( 382 snapshot.dominatorTree.expanded, 383 "Should have the dominator tree's expanded set" 384 ); 385 386 const expanded = new Set(snapshot.dominatorTree.expanded); 387 expanded.delete(node.nodeId); 388 const dominatorTree = immutableUpdate(snapshot.dominatorTree, { expanded }); 389 return immutableUpdate(snapshot, { dominatorTree }); 390 }); 391 }; 392 393 handlers[actions.FOCUS_DOMINATOR_TREE_NODE] = function ( 394 snapshots, 395 { id, node } 396 ) { 397 return snapshots.map(snapshot => { 398 if (snapshot.id !== id) { 399 return snapshot; 400 } 401 402 assert(snapshot.dominatorTree, "Should have a dominator tree"); 403 const dominatorTree = immutableUpdate(snapshot.dominatorTree, { 404 focused: node, 405 }); 406 return immutableUpdate(snapshot, { dominatorTree }); 407 }); 408 }; 409 410 handlers[actions.FETCH_IMMEDIATELY_DOMINATED_START] = function ( 411 snapshots, 412 { id } 413 ) { 414 return snapshots.map(snapshot => { 415 if (snapshot.id !== id) { 416 return snapshot; 417 } 418 419 assert(snapshot.dominatorTree, "Should have a dominator tree model"); 420 assert( 421 snapshot.dominatorTree.state == dominatorTreeState.INCREMENTAL_FETCHING || 422 snapshot.dominatorTree.state == dominatorTreeState.LOADED, 423 "The dominator tree should be loaded if we are going to " + 424 "incrementally fetch children." 425 ); 426 427 const activeFetchRequestCount = snapshot.dominatorTree 428 .activeFetchRequestCount 429 ? snapshot.dominatorTree.activeFetchRequestCount + 1 430 : 1; 431 432 const dominatorTree = immutableUpdate(snapshot.dominatorTree, { 433 state: dominatorTreeState.INCREMENTAL_FETCHING, 434 activeFetchRequestCount, 435 }); 436 437 return immutableUpdate(snapshot, { dominatorTree }); 438 }); 439 }; 440 441 handlers[actions.FETCH_IMMEDIATELY_DOMINATED_END] = function ( 442 snapshots, 443 { id, path, nodes, moreChildrenAvailable } 444 ) { 445 return snapshots.map(snapshot => { 446 if (snapshot.id !== id) { 447 return snapshot; 448 } 449 450 assert(snapshot.dominatorTree, "Should have a dominator tree model"); 451 assert( 452 snapshot.dominatorTree.root, 453 "Should have a dominator tree model root" 454 ); 455 assert( 456 snapshot.dominatorTree.state === dominatorTreeState.INCREMENTAL_FETCHING, 457 "The dominator tree state should be INCREMENTAL_FETCHING" 458 ); 459 460 const root = DominatorTreeNode.insert( 461 snapshot.dominatorTree.root, 462 path, 463 nodes, 464 moreChildrenAvailable 465 ); 466 467 const focused = snapshot.dominatorTree.focused 468 ? DominatorTreeNode.getNodeByIdAlongPath( 469 snapshot.dominatorTree.focused.nodeId, 470 root, 471 path 472 ) 473 : undefined; 474 475 const activeFetchRequestCount = 476 snapshot.dominatorTree.activeFetchRequestCount === 1 477 ? undefined 478 : snapshot.dominatorTree.activeFetchRequestCount - 1; 479 480 // If there are still outstanding requests, we need to stay in the 481 // INCREMENTAL_FETCHING state until they complete. 482 const state = activeFetchRequestCount 483 ? dominatorTreeState.INCREMENTAL_FETCHING 484 : dominatorTreeState.LOADED; 485 486 const dominatorTree = immutableUpdate(snapshot.dominatorTree, { 487 state, 488 root, 489 focused, 490 activeFetchRequestCount, 491 }); 492 493 return immutableUpdate(snapshot, { dominatorTree }); 494 }); 495 }; 496 497 handlers[actions.DOMINATOR_TREE_ERROR] = function (snapshots, { id, error }) { 498 assert(error, "actions with DOMINATOR_TREE_ERROR should have an error"); 499 500 return snapshots.map(snapshot => { 501 if (snapshot.id !== id) { 502 return snapshot; 503 } 504 505 const dominatorTree = Object.freeze({ 506 state: dominatorTreeState.ERROR, 507 error, 508 }); 509 510 return immutableUpdate(snapshot, { dominatorTree }); 511 }); 512 }; 513 514 module.exports = function (snapshots = [], action) { 515 const handler = handlers[action.type]; 516 if (handler) { 517 return handler(snapshots, action); 518 } 519 return snapshots; 520 };