1 /* 2 * Copyright (C) 2011 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 WebInspector.HeapSnapshotContainmentDataGrid = function() 32 { 33 var columns = { 34 object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true, sort: "ascending" }, 35 shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true }, 36 retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sortable: true } 37 }; 38 WebInspector.DataGrid.call(this, columns); 39 this.addEventListener("sorting changed", this.sort, this); 40 } 41 42 WebInspector.HeapSnapshotContainmentDataGrid.prototype = { 43 _defaultPopulateCount: 100, 44 45 setDataSource: function(snapshotView, snapshot) 46 { 47 this.snapshotView = snapshotView; 48 this.snapshot = snapshot; 49 this.snapshotNodeIndex = this.snapshot.rootNodeIndex; 50 this._provider = this._createProvider(snapshot, this.snapshotNodeIndex); 51 this.sort(); 52 } 53 }; 54 55 MixInSnapshotNodeFunctions(WebInspector.HeapSnapshotObjectNode.prototype, WebInspector.HeapSnapshotContainmentDataGrid.prototype); 56 WebInspector.HeapSnapshotContainmentDataGrid.prototype.__proto__ = WebInspector.DataGrid.prototype; 57 58 WebInspector.HeapSnapshotSortableDataGrid = function(columns) 59 { 60 WebInspector.DataGrid.call(this, columns); 61 this.addEventListener("sorting changed", this.sortingChanged, this); 62 } 63 64 WebInspector.HeapSnapshotSortableDataGrid.prototype = { 65 sortingChanged: function() 66 { 67 var sortAscending = this.sortOrder === "ascending"; 68 var sortColumnIdentifier = this.sortColumnIdentifier; 69 if (this._lastSortColumnIdentifier === sortColumnIdentifier && this._lastSortAscending === sortAscending) 70 return; 71 this._lastSortColumnIdentifier = sortColumnIdentifier; 72 this._lastSortAscending = sortAscending; 73 var sortFields = this._sortFields(sortColumnIdentifier, sortAscending); 74 75 function SortByTwoFields(nodeA, nodeB) 76 { 77 var field1 = nodeA[sortFields[0]]; 78 var field2 = nodeB[sortFields[0]]; 79 var result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0); 80 if (!sortFields[1]) 81 result = -result; 82 if (result !== 0) 83 return result; 84 field1 = nodeA[sortFields[2]]; 85 field2 = nodeB[sortFields[2]]; 86 result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0); 87 if (!sortFields[3]) 88 result = -result; 89 return result; 90 } 91 92 this._performSorting(SortByTwoFields); 93 }, 94 95 _performSorting: function(sortFunction) 96 { 97 this.dispatchEventToListeners("start sorting"); 98 var children = this.children; 99 this.removeChildren(); 100 children.sort(sortFunction); 101 for (var i = 0, l = children.length; i < l; ++i) { 102 var child = children[i]; 103 this.appendChild(child); 104 if (child.expanded) 105 child.sort(); 106 } 107 this.dispatchEventToListeners("sorting complete"); 108 } 109 }; 110 111 WebInspector.HeapSnapshotSortableDataGrid.prototype.__proto__ = WebInspector.DataGrid.prototype; 112 113 WebInspector.HeapSnapshotConstructorsDataGrid = function() 114 { 115 var columns = { 116 object: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true }, 117 count: { title: WebInspector.UIString("#"), width: "45px", sortable: true }, 118 shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true }, 119 retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sort: "descending", sortable: true } 120 }; 121 WebInspector.HeapSnapshotSortableDataGrid.call(this, columns); 122 } 123 124 WebInspector.HeapSnapshotConstructorsDataGrid.prototype = { 125 _defaultPopulateCount: 100, 126 127 _sortFields: function(sortColumn, sortAscending) 128 { 129 return { 130 object: ["_name", sortAscending, "_count", false], 131 count: ["_count", sortAscending, "_name", true], 132 shallowSize: ["_shallowSize", sortAscending, "_name", true], 133 retainedSize: ["_retainedSize", sortAscending, "_name", true] 134 }[sortColumn]; 135 }, 136 137 setDataSource: function(snapshotView, snapshot) 138 { 139 this.snapshotView = snapshotView; 140 this.snapshot = snapshot; 141 this.populateChildren(); 142 }, 143 144 populateChildren: function() 145 { 146 function aggregatesReceived(aggregates) 147 { 148 for (var constructor in aggregates) 149 this.appendChild(new WebInspector.HeapSnapshotConstructorNode(this, constructor, aggregates[constructor])); 150 this.sortingChanged(); 151 } 152 this.snapshot.aggregates(false, aggregatesReceived.bind(this)); 153 } 154 }; 155 156 WebInspector.HeapSnapshotConstructorsDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype; 157 158 WebInspector.HeapSnapshotDiffDataGrid = function() 159 { 160 var columns = { 161 object: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true }, 162 addedCount: { title: WebInspector.UIString("# New"), width: "72px", sortable: true, sort: "descending" }, 163 removedCount: { title: WebInspector.UIString("# Deleted"), width: "72px", sortable: true }, 164 // \u0394 is a Greek delta letter. 165 countDelta: { title: "\u0394", width: "40px", sortable: true }, 166 addedSize: { title: WebInspector.UIString("Alloc. Size"), width: "72px", sortable: true }, 167 removedSize: { title: WebInspector.UIString("Freed Size"), width: "72px", sortable: true }, 168 sizeDelta: { title: "\u0394", width: "72px", sortable: true } 169 }; 170 WebInspector.HeapSnapshotSortableDataGrid.call(this, columns); 171 } 172 173 WebInspector.HeapSnapshotDiffDataGrid.prototype = { 174 _defaultPopulateCount: 50, 175 176 _sortFields: function(sortColumn, sortAscending) 177 { 178 return { 179 object: ["_name", sortAscending, "_count", false], 180 addedCount: ["_addedCount", sortAscending, "_name", true], 181 removedCount: ["_removedCount", sortAscending, "_name", true], 182 countDelta: ["_countDelta", sortAscending, "_name", true], 183 addedSize: ["_addedSize", sortAscending, "_name", true], 184 removedSize: ["_removedSize", sortAscending, "_name", true], 185 sizeDelta: ["_sizeDelta", sortAscending, "_name", true] 186 }[sortColumn]; 187 }, 188 189 setDataSource: function(snapshotView, snapshot) 190 { 191 this.snapshotView = snapshotView; 192 this.snapshot = snapshot; 193 }, 194 195 setBaseDataSource: function(baseSnapshot) 196 { 197 this.baseSnapshot = baseSnapshot; 198 this.removeChildren(); 199 if (this.baseSnapshot === this.snapshot) 200 return; 201 this.populateChildren(); 202 }, 203 204 populateChildren: function() 205 { 206 function baseAggregatesReceived(baseClasses) 207 { 208 function aggregatesReceived(classes) 209 { 210 var nodeCount = 0; 211 function addNodeIfNonZeroDiff(node, zeroDiff) 212 { 213 if (!zeroDiff) 214 this.appendChild(node); 215 if (!--nodeCount) 216 this.sortingChanged(); 217 } 218 for (var clss in baseClasses) { 219 var node = new WebInspector.HeapSnapshotDiffNode(this, clss, baseClasses[clss], classes[clss]); 220 ++nodeCount; 221 node.calculateDiff(this, addNodeIfNonZeroDiff.bind(this, node)); 222 } 223 for (clss in classes) { 224 if (!(clss in baseClasses)) { 225 var node = new WebInspector.HeapSnapshotDiffNode(this, clss, null, classes[clss]); 226 ++nodeCount; 227 node.calculateDiff(this, addNodeIfNonZeroDiff.bind(this, node)); 228 } 229 } 230 } 231 this.snapshot.aggregates(true, aggregatesReceived.bind(this)); 232 } 233 this.baseSnapshot.aggregates(true, baseAggregatesReceived.bind(this)); 234 } 235 }; 236 237 WebInspector.HeapSnapshotDiffDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype; 238 239 WebInspector.HeapSnapshotDominatorsDataGrid = function() 240 { 241 var columns = { 242 object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true }, 243 shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true }, 244 retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sort: "descending", sortable: true } 245 }; 246 WebInspector.DataGrid.call(this, columns); 247 this.addEventListener("sorting changed", this.sort, this); 248 } 249 250 WebInspector.HeapSnapshotDominatorsDataGrid.prototype = { 251 _defaultPopulateCount: 25, 252 253 setDataSource: function(snapshotView, snapshot) 254 { 255 this.snapshotView = snapshotView; 256 this.snapshot = snapshot; 257 this.snapshotNodeIndex = this.snapshot.rootNodeIndex; 258 this._provider = this._createProvider(snapshot, this.snapshotNodeIndex); 259 this.sort(); 260 } 261 }; 262 263 MixInSnapshotNodeFunctions(WebInspector.HeapSnapshotDominatorObjectNode.prototype, WebInspector.HeapSnapshotDominatorsDataGrid.prototype); 264 WebInspector.HeapSnapshotDominatorsDataGrid.prototype.__proto__ = WebInspector.DataGrid.prototype; 265 266 WebInspector.HeapSnapshotRetainingPathsList = function() 267 { 268 var columns = { 269 path: { title: WebInspector.UIString("Retaining path"), sortable: true }, 270 len: { title: WebInspector.UIString("Length"), width: "90px", sortable: true, sort: "ascending" } 271 }; 272 WebInspector.HeapSnapshotSortableDataGrid.call(this, columns); 273 this._defaultPopulateCount = 100; 274 } 275 276 WebInspector.HeapSnapshotRetainingPathsList.prototype = { 277 _sortFields: function(sortColumn, sortAscending) 278 { 279 return { 280 path: ["path", sortAscending, "len", true], 281 len: ["len", sortAscending, "path", true] 282 }[sortColumn]; 283 }, 284 285 _resetPaths: function() 286 { 287 this._setRootChildrenForFinder(); 288 this.removeChildren(); 289 this._counter = 0; 290 this.showNext(this._defaultPopulateCount); 291 }, 292 293 setDataSource: function(snapshotView, snapshot, nodeIndex, prefix) 294 { 295 this.snapshotView = snapshotView; 296 this._prefix = prefix; 297 298 if (this.pathFinder) 299 this.searchCancelled(); 300 this.pathFinder = snapshot.createPathFinder(nodeIndex); 301 this._resetPaths(); 302 }, 303 304 refresh: function() 305 { 306 delete this._cancel; 307 this._resetPaths(); 308 }, 309 310 showNext: function(pathsCount) 311 { 312 WebInspector.PleaseWaitMessage.prototype.show(this.element, this.searchCancelled.bind(this, pathsCount)); 313 314 function pathFound(result) 315 { 316 if (result === null) { 317 WebInspector.PleaseWaitMessage.prototype.hide(); 318 if (!this.children.length) 319 this.appendChild(new WebInspector.DataGridNode({path:WebInspector.UIString("Can't find any paths."), len:""}, false)); 320 return; 321 } else if (result !== false) { 322 if (this._prefix) 323 result.path = this._prefix + result.path; 324 this.appendChild(new WebInspector.DataGridNode(result, false)); 325 ++this._counter; 326 } 327 setTimeout(startSearching.bind(this), 0); 328 } 329 330 function startSearching() 331 { 332 if (this._cancel === this.pathFinder) 333 return; 334 delete this._cancel; 335 if (this._counter < pathsCount) 336 this.pathFinder.findNext(pathFound.bind(this)); 337 else { 338 this.searchCancelled.call(this, pathsCount); 339 delete this._cancel; 340 } 341 } 342 setTimeout(startSearching.bind(this), 0); 343 }, 344 345 searchCancelled: function(pathsCount) 346 { 347 WebInspector.PleaseWaitMessage.prototype.hide(); 348 this._counter = 0; 349 this._cancel = this.pathFinder; 350 if (pathsCount) { 351 this.appendChild(new WebInspector.ShowMoreDataGridNode(this.showNext.bind(this), pathsCount)); 352 this.sortingChanged(); 353 } 354 }, 355 356 _setRootChildrenForFinder: function() 357 { 358 function FilterDOMWindow(node) 359 { 360 return node.name === "DOMWindow"; 361 } 362 363 if (this.snapshotView.isTracingToWindowObjects) 364 this.pathFinder.updateRoots(FilterDOMWindow); 365 else 366 this.pathFinder.updateRoots(); 367 }, 368 369 _performSorting: function(sortFunction) 370 { 371 function DataExtractorWrapper(nodeA, nodeB) 372 { 373 return sortFunction(nodeA.data, nodeB.data); 374 } 375 376 this.sortNodes(DataExtractorWrapper); 377 } 378 }; 379 380 WebInspector.HeapSnapshotRetainingPathsList.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype; 381 382 WebInspector.DetailedHeapshotView = function(parent, profile) 383 { 384 WebInspector.View.call(this); 385 386 this.element.addStyleClass("detailed-heapshot-view"); 387 388 this.parent = parent; 389 this.parent.addEventListener("profile added", this._updateBaseOptions, this); 390 391 this.showCountAsPercent = false; 392 this.showShallowSizeAsPercent = false; 393 this.showRetainedSizeAsPercent = false; 394 395 this.containmentView = new WebInspector.View(); 396 this.containmentView.element.addStyleClass("view"); 397 this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid(); 398 this.containmentDataGrid.element.addEventListener("click", this._mouseClickInContainmentGrid.bind(this), true); 399 this.containmentView.element.appendChild(this.containmentDataGrid.element); 400 this.element.appendChild(this.containmentView.element); 401 402 this.constructorsView = new WebInspector.View(); 403 this.constructorsView.element.addStyleClass("view"); 404 this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid(); 405 this.constructorsDataGrid.element.addEventListener("click", this._mouseClickInContainmentGrid.bind(this), true); 406 this.constructorsView.element.appendChild(this.constructorsDataGrid.element); 407 this.element.appendChild(this.constructorsView.element); 408 409 this.diffView = new WebInspector.View(); 410 this.diffView.element.addStyleClass("view"); 411 this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid(); 412 this.diffDataGrid.element.addEventListener("click", this._mouseClickInContainmentGrid.bind(this), true); 413 this.diffView.element.appendChild(this.diffDataGrid.element); 414 this.element.appendChild(this.diffView.element); 415 416 this.dominatorView = new WebInspector.View(); 417 this.dominatorView.element.addStyleClass("view"); 418 this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid(); 419 this.dominatorDataGrid.element.addEventListener("click", this._mouseClickInContainmentGrid.bind(this), true); 420 this.dominatorView.element.appendChild(this.dominatorDataGrid.element); 421 this.element.appendChild(this.dominatorView.element); 422 423 var retainmentView = new WebInspector.View(); 424 retainmentView.element.addStyleClass("view"); 425 retainmentView.element.addStyleClass("retaining-paths-view"); 426 var retainingPathsTitleDiv = document.createElement("div"); 427 retainingPathsTitleDiv.className = "title"; 428 var retainingPathsTitle = document.createElement("span"); 429 retainingPathsTitle.textContent = WebInspector.UIString("Paths from the selected object"); 430 this.retainingPathsRoot = document.createElement("select"); 431 this.retainingPathsRoot.className = "status-bar-item"; 432 this.retainingPathsRoot.addEventListener("change", this._changeRetainingPathsRoot.bind(this), false); 433 var toGCRootsTraceOption = document.createElement("option"); 434 toGCRootsTraceOption.label = WebInspector.UIString("to GC roots"); 435 var toWindowObjectsTraceOption = document.createElement("option"); 436 toWindowObjectsTraceOption.label = WebInspector.UIString("to window objects"); 437 this.retainingPathsRoot.appendChild(toGCRootsTraceOption); 438 this.retainingPathsRoot.appendChild(toWindowObjectsTraceOption); 439 retainingPathsTitleDiv.appendChild(retainingPathsTitle); 440 retainingPathsTitleDiv.appendChild(this.retainingPathsRoot); 441 retainmentView.element.appendChild(retainingPathsTitleDiv); 442 this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainingPathsList(); 443 retainmentView.element.appendChild(this.retainmentDataGrid.element); 444 retainmentView.visible = true; 445 this.element.appendChild(retainmentView.element); 446 447 this.dataGrid = this.constructorsDataGrid; 448 this.currentView = this.constructorsView; 449 450 this.viewSelectElement = document.createElement("select"); 451 this.viewSelectElement.className = "status-bar-item"; 452 this.viewSelectElement.addEventListener("change", this._changeView.bind(this), false); 453 454 this.views = [{title: "Summary", view: this.constructorsView, grid: this.constructorsDataGrid}, 455 {title: "Comparison", view: this.diffView, grid: this.diffDataGrid}, 456 {title: "Containment", view: this.containmentView, grid: this.containmentDataGrid}, 457 {title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid}]; 458 this.views.current = 0; 459 for (var i = 0; i < this.views.length; ++i) { 460 var view = this.views[i]; 461 var option = document.createElement("option"); 462 option.label = WebInspector.UIString(view.title); 463 this.viewSelectElement.appendChild(option); 464 } 465 466 this._profileUid = profile.uid; 467 468 this.baseSelectElement = document.createElement("select"); 469 this.baseSelectElement.className = "status-bar-item hidden"; 470 this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false); 471 this._updateBaseOptions(); 472 473 this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item"); 474 this.percentButton.addEventListener("click", this._percentClicked.bind(this), false); 475 this.helpButton = new WebInspector.StatusBarButton("", "heapshot-help-status-bar-item status-bar-item"); 476 this.helpButton.addEventListener("click", this._helpClicked.bind(this), false); 477 478 var popoverHelper = new WebInspector.PopoverHelper(this.element, this._getHoverAnchor.bind(this), this._showStringContentPopup.bind(this)); 479 480 this._loadProfile(this._profileUid, profileCallback.bind(this)); 481 482 function profileCallback() 483 { 484 var list = this._profiles(); 485 var profileIndex; 486 for (var i = 0; i < list.length; ++i) 487 if (list[i].uid === this._profileUid) { 488 profileIndex = i; 489 break; 490 } 491 if (profileIndex > 0) 492 this.baseSelectElement.selectedIndex = profileIndex - 1; 493 else 494 this.baseSelectElement.selectedIndex = profileIndex; 495 this.dataGrid.setDataSource(this, this.profileWrapper); 496 this._updatePercentButton(); 497 } 498 } 499 500 WebInspector.DetailedHeapshotView.prototype = { 501 dispose: function() 502 { 503 if (this._profileWrapper) 504 this._profileWrapper.dispose(); 505 if (this._baseProfileWrapper) 506 this._baseProfileWrapper.dispose(); 507 }, 508 509 get statusBarItems() 510 { 511 return [this.viewSelectElement, this.baseSelectElement, this.percentButton.element, this.helpButton.element]; 512 }, 513 514 get profile() 515 { 516 return this.parent.getProfile(WebInspector.HeapSnapshotProfileType.TypeId, this._profileUid); 517 }, 518 519 get profileWrapper() 520 { 521 if (!this._profileWrapper) 522 this._profileWrapper = this.profile.proxy; 523 return this._profileWrapper; 524 }, 525 526 get baseProfile() 527 { 528 return this.parent.getProfile(WebInspector.HeapSnapshotProfileType.TypeId, this._baseProfileUid); 529 }, 530 531 get baseProfileWrapper() 532 { 533 if (!this._baseProfileWrapper) 534 this._baseProfileWrapper = this.baseProfile.proxy; 535 return this._baseProfileWrapper; 536 }, 537 538 show: function(parentElement) 539 { 540 WebInspector.View.prototype.show.call(this, parentElement); 541 if (!this.profileWrapper.loaded) 542 this._loadProfile(this._profileUid, profileCallback1.bind(this)); 543 else 544 profileCallback1.call(this); 545 546 function profileCallback1() { 547 if (this.baseProfile && !this.baseProfileWrapper.loaded) 548 this._loadProfile(this._baseProfileUid, profileCallback2.bind(this)); 549 else 550 profileCallback2.call(this); 551 } 552 553 function profileCallback2() { 554 this.currentView.show(); 555 this.dataGrid.updateWidths(); 556 } 557 }, 558 559 hide: function() 560 { 561 WebInspector.View.prototype.hide.call(this); 562 this._currentSearchResultIndex = -1; 563 }, 564 565 resize: function() 566 { 567 if (this.dataGrid) 568 this.dataGrid.updateWidths(); 569 }, 570 571 refreshShowAsPercents: function() 572 { 573 this._updatePercentButton(); 574 this.refreshVisibleData(); 575 }, 576 577 searchCanceled: function() 578 { 579 if (this._searchResults) { 580 for (var i = 0; i < this._searchResults.length; ++i) { 581 var node = this._searchResults[i].node; 582 delete node._searchMatched; 583 node.refresh(); 584 } 585 } 586 587 delete this._searchFinishedCallback; 588 this._currentSearchResultIndex = -1; 589 this._searchResults = []; 590 }, 591 592 performSearch: function(query, finishedCallback) 593 { 594 // Call searchCanceled since it will reset everything we need before doing a new search. 595 this.searchCanceled(); 596 597 query = query.trim(); 598 599 if (!query.length) 600 return; 601 if (this.currentView !== this.constructorsView && this.currentView !== this.diffView) 602 return; 603 604 this._searchFinishedCallback = finishedCallback; 605 606 function matchesByName(gridNode) { 607 return ("name" in gridNode) && gridNode.name.hasSubstring(query, true); 608 } 609 610 function matchesById(gridNode) { 611 return ("snapshotNodeId" in gridNode) && gridNode.snapshotNodeId === query; 612 } 613 614 var matchPredicate; 615 if (query.charAt(0) !== "@") 616 matchPredicate = matchesByName; 617 else { 618 query = parseInt(query.substring(1), 10); 619 matchPredicate = matchesById; 620 } 621 622 function matchesQuery(gridNode) 623 { 624 delete gridNode._searchMatched; 625 if (matchPredicate(gridNode)) { 626 gridNode._searchMatched = true; 627 gridNode.refresh(); 628 return true; 629 } 630 return false; 631 } 632 633 var current = this.dataGrid.children[0]; 634 var depth = 0; 635 var info = {}; 636 637 // Restrict to type nodes and instances. 638 const maxDepth = 1; 639 640 while (current) { 641 if (matchesQuery(current)) 642 this._searchResults.push({ node: current }); 643 current = current.traverseNextNode(false, null, (depth >= maxDepth), info); 644 depth += info.depthChange; 645 } 646 647 finishedCallback(this, this._searchResults.length); 648 }, 649 650 jumpToFirstSearchResult: function() 651 { 652 if (!this._searchResults || !this._searchResults.length) 653 return; 654 this._currentSearchResultIndex = 0; 655 this._jumpToSearchResult(this._currentSearchResultIndex); 656 }, 657 658 jumpToLastSearchResult: function() 659 { 660 if (!this._searchResults || !this._searchResults.length) 661 return; 662 this._currentSearchResultIndex = (this._searchResults.length - 1); 663 this._jumpToSearchResult(this._currentSearchResultIndex); 664 }, 665 666 jumpToNextSearchResult: function() 667 { 668 if (!this._searchResults || !this._searchResults.length) 669 return; 670 if (++this._currentSearchResultIndex >= this._searchResults.length) 671 this._currentSearchResultIndex = 0; 672 this._jumpToSearchResult(this._currentSearchResultIndex); 673 }, 674 675 jumpToPreviousSearchResult: function() 676 { 677 if (!this._searchResults || !this._searchResults.length) 678 return; 679 if (--this._currentSearchResultIndex < 0) 680 this._currentSearchResultIndex = (this._searchResults.length - 1); 681 this._jumpToSearchResult(this._currentSearchResultIndex); 682 }, 683 684 showingFirstSearchResult: function() 685 { 686 return (this._currentSearchResultIndex === 0); 687 }, 688 689 showingLastSearchResult: function() 690 { 691 return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); 692 }, 693 694 _jumpToSearchResult: function(index) 695 { 696 var searchResult = this._searchResults[index]; 697 if (!searchResult) 698 return; 699 700 var node = searchResult.node; 701 node.reveal(); 702 node.select(); 703 }, 704 705 refreshVisibleData: function() 706 { 707 var child = this.dataGrid.children[0]; 708 while (child) { 709 child.refresh(); 710 child = child.traverseNextNode(false, null, true); 711 } 712 }, 713 714 _changeBase: function() 715 { 716 if (this._baseProfileUid === this._profiles()[this.baseSelectElement.selectedIndex].uid) 717 return; 718 719 this._baseProfileUid = this._profiles()[this.baseSelectElement.selectedIndex].uid; 720 this._loadProfile(this._baseProfileUid, baseProfileLoaded.bind(this)); 721 722 function baseProfileLoaded() 723 { 724 delete this._baseProfileWrapper; 725 this.baseProfile._lastShown = Date.now(); 726 WebInspector.PleaseWaitMessage.prototype.startAction(this.currentView.element, showDiffData.bind(this)); 727 } 728 729 function showDiffData() 730 { 731 this.diffDataGrid.setBaseDataSource(this.baseProfileWrapper); 732 } 733 734 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) 735 return; 736 737 // The current search needs to be performed again. First negate out previous match 738 // count by calling the search finished callback with a negative number of matches. 739 // Then perform the search again with the same query and callback. 740 this._searchFinishedCallback(this, -this._searchResults.length); 741 this.performSearch(this.currentQuery, this._searchFinishedCallback); 742 }, 743 744 _profiles: function() 745 { 746 return WebInspector.panels.profiles.getProfiles(WebInspector.HeapSnapshotProfileType.TypeId); 747 }, 748 749 _loadProfile: function(profileUid, callback) 750 { 751 WebInspector.panels.profiles.loadHeapSnapshot(profileUid, callback); 752 }, 753 754 isDetailedSnapshot: function(snapshot) 755 { 756 var s = new WebInspector.HeapSnapshot(snapshot); 757 for (var iter = s.rootNode.edges; iter.hasNext(); iter.next()) 758 if (iter.edge.node.name === "(GC roots)") 759 return true; 760 return false; 761 }, 762 763 processLoadedSnapshot: function(profile, snapshot) 764 { 765 profile.nodes = snapshot.nodes; 766 profile.strings = snapshot.strings; 767 var s = new WebInspector.HeapSnapshot(profile); 768 profile.sideBarElement.subtitle = Number.bytesToString(s.totalSize); 769 }, 770 771 _mouseClickInContainmentGrid: function(event) 772 { 773 var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); 774 if (!cell || (!cell.hasStyleClass("object-column") && !cell.hasStyleClass("shallowSize-column") && !cell.hasStyleClass("retainedSize-column"))) 775 return; 776 var row = event.target.enclosingNodeOrSelfWithNodeName("tr"); 777 if (!row) 778 return; 779 var nodeItem = row._dataGridNode; 780 if (!nodeItem || nodeItem.isEventWithinDisclosureTriangle(event) || !nodeItem.snapshotNodeIndex) 781 return; 782 783 this.retainmentDataGrid.setDataSource(this, nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex, nodeItem.isDeletedNode ? this.baseSelectElement.childNodes[this.baseSelectElement.selectedIndex].label + " | " : ""); 784 }, 785 786 _changeView: function(event) 787 { 788 if (!event || !this._profileUid) 789 return; 790 if (event.target.selectedIndex === this.views.current) 791 return; 792 793 this.views.current = event.target.selectedIndex; 794 this.currentView.hide(); 795 var view = this.views[this.views.current]; 796 this.currentView = view.view; 797 this.dataGrid = view.grid; 798 this.currentView.show(); 799 this.refreshVisibleData(); 800 if (this.currentView === this.diffView) { 801 this.baseSelectElement.removeStyleClass("hidden"); 802 if (!this.dataGrid.snapshotView) { 803 this.dataGrid.setDataSource(this, this.profileWrapper); 804 this._changeBase(); 805 } 806 } else { 807 this.baseSelectElement.addStyleClass("hidden"); 808 if (!this.dataGrid.snapshotView) 809 WebInspector.PleaseWaitMessage.prototype.startAction(this.currentView.element, loadData.bind(this)); 810 } 811 812 function loadData() 813 { 814 this.dataGrid.setDataSource(this, this.profileWrapper); 815 } 816 817 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) 818 return; 819 820 // The current search needs to be performed again. First negate out previous match 821 // count by calling the search finished callback with a negative number of matches. 822 // Then perform the search again the with same query and callback. 823 this._searchFinishedCallback(this, -this._searchResults.length); 824 this.performSearch(this.currentQuery, this._searchFinishedCallback); 825 }, 826 827 _changeRetainingPathsRoot: function(event) 828 { 829 if (!event) 830 return; 831 this.retainmentDataGrid.refresh(); 832 }, 833 834 _getHoverAnchor: function(target) 835 { 836 var span = target.enclosingNodeOrSelfWithNodeName("span"); 837 if (!span || !span.hasStyleClass("console-formatted-string")) 838 return; 839 var row = target.enclosingNodeOrSelfWithNodeName("tr"); 840 if (!row) 841 return; 842 var gridNode = row._dataGridNode; 843 if (!gridNode.snapshotNodeIndex) 844 return; 845 span.snapshotNodeIndex = gridNode.snapshotNodeIndex; 846 return span; 847 }, 848 849 get isTracingToWindowObjects() 850 { 851 return this.retainingPathsRoot.selectedIndex === 1; 852 }, 853 854 get _isShowingAsPercent() 855 { 856 return this.showCountAsPercent && this.showShallowSizeAsPercent && this.showRetainedSizeAsPercent; 857 }, 858 859 _percentClicked: function(event) 860 { 861 var currentState = this._isShowingAsPercent; 862 this.showCountAsPercent = !currentState; 863 this.showShallowSizeAsPercent = !currentState; 864 this.showRetainedSizeAsPercent = !currentState; 865 this.refreshShowAsPercents(); 866 }, 867 868 _showStringContentPopup: function(span) 869 { 870 var snapshotNode = new WebInspector.HeapSnapshotNode(this.profileWrapper, span.snapshotNodeIndex); 871 var stringContentElement = document.createElement("span"); 872 stringContentElement.className = "monospace console-formatted-string"; 873 stringContentElement.style.whiteSpace = "pre"; 874 stringContentElement.textContent = "\"" + snapshotNode.name + "\""; 875 var popover = new WebInspector.Popover(stringContentElement); 876 popover.show(span); 877 return popover; 878 }, 879 880 _helpClicked: function(event) 881 { 882 if (!this.helpPopover) { 883 var refTypes = ["a:", "console-formatted-name", WebInspector.UIString("property"), 884 "0:", "console-formatted-name", WebInspector.UIString("element"), 885 "a:", "console-formatted-number", WebInspector.UIString("context var"), 886 "a:", "console-formatted-null", WebInspector.UIString("system prop")]; 887 var objTypes = [" a ", "console-formatted-object", "Object", 888 "\"a\"", "console-formatted-string", "String", 889 "/a/", "console-formatted-string", "RegExp", 890 "a()", "console-formatted-function", "Function", 891 "a[]", "console-formatted-object", "Array", 892 "num", "console-formatted-number", "Number", 893 " a ", "console-formatted-null", "System"]; 894 895 var contentElement = document.createElement("table"); 896 contentElement.className = "heapshot-help"; 897 var headerRow = document.createElement("tr"); 898 var propsHeader = document.createElement("th"); 899 propsHeader.textContent = WebInspector.UIString("Property types:"); 900 headerRow.appendChild(propsHeader); 901 var objsHeader = document.createElement("th"); 902 objsHeader.textContent = WebInspector.UIString("Object types:"); 903 headerRow.appendChild(objsHeader); 904 contentElement.appendChild(headerRow); 905 var len = Math.max(refTypes.length, objTypes.length); 906 for (var i = 0; i < len; i += 3) { 907 var row = document.createElement("tr"); 908 var refCell = document.createElement("td"); 909 if (refTypes[i]) 910 appendHelp(refTypes, i, refCell); 911 row.appendChild(refCell); 912 var objCell = document.createElement("td"); 913 if (objTypes[i]) 914 appendHelp(objTypes, i, objCell); 915 row.appendChild(objCell); 916 contentElement.appendChild(row); 917 } 918 this.helpPopover = new WebInspector.Popover(contentElement); 919 920 function appendHelp(help, index, cell) 921 { 922 var div = document.createElement("div"); 923 div.className = "source-code event-properties"; 924 var name = document.createElement("span"); 925 name.textContent = help[index]; 926 name.className = help[index + 1]; 927 div.appendChild(name); 928 var desc = document.createElement("span"); 929 desc.textContent = " " + help[index + 2]; 930 div.appendChild(desc); 931 cell.appendChild(div); 932 } 933 } 934 if (this.helpPopover.visible) 935 this.helpPopover.hide(); 936 else 937 this.helpPopover.show(this.helpButton.element); 938 }, 939 940 _updateBaseOptions: function() 941 { 942 var list = this._profiles(); 943 // We're assuming that snapshots can only be added. 944 if (this.baseSelectElement.length === list.length) 945 return; 946 947 for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) { 948 var baseOption = document.createElement("option"); 949 var title = list[i].title; 950 if (!title.indexOf(UserInitiatedProfileName)) 951 title = WebInspector.UIString("Snapshot %d", title.substring(UserInitiatedProfileName.length + 1)); 952 baseOption.label = title; 953 this.baseSelectElement.appendChild(baseOption); 954 } 955 }, 956 957 _updatePercentButton: function() 958 { 959 if (this._isShowingAsPercent) { 960 this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes."); 961 this.percentButton.toggled = true; 962 } else { 963 this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages."); 964 this.percentButton.toggled = false; 965 } 966 } 967 }; 968 969 WebInspector.DetailedHeapshotView.prototype.__proto__ = WebInspector.View.prototype; 970 971 WebInspector.DetailedHeapshotView.prototype.showHiddenData = true; 972 973 WebInspector.DetailedHeapshotProfileType = function() 974 { 975 WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS")); 976 } 977 978 WebInspector.DetailedHeapshotProfileType.prototype = { 979 get buttonTooltip() 980 { 981 return WebInspector.UIString("Take heap snapshot."); 982 }, 983 984 get buttonStyle() 985 { 986 return "heap-snapshot-status-bar-item status-bar-item"; 987 }, 988 989 buttonClicked: function() 990 { 991 WebInspector.panels.profiles.takeHeapSnapshot(true); 992 }, 993 994 get welcomeMessage() 995 { 996 return WebInspector.UIString("Get a heap snapshot by pressing the %s button on the status bar."); 997 }, 998 999 createSidebarTreeElementForProfile: function(profile) 1000 { 1001 return new WebInspector.ProfileSidebarTreeElement(profile, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item"); 1002 }, 1003 1004 createView: function(profile) 1005 { 1006 return new WebInspector.DetailedHeapshotView(WebInspector.panels.profiles, profile); 1007 } 1008 } 1009 1010 WebInspector.DetailedHeapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype; 1011