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 /** 32 * @constructor 33 * @extends {WebInspector.DataGridNode} 34 * @param {!WebInspector.HeapSnapshotSortableDataGrid} tree 35 * @param {boolean} hasChildren 36 */ 37 WebInspector.HeapSnapshotGridNode = function(tree, hasChildren) 38 { 39 WebInspector.DataGridNode.call(this, null, hasChildren); 40 this._dataGrid = tree; 41 this._instanceCount = 0; 42 43 this._savedChildren = null; 44 /** 45 * List of position ranges for all visible nodes: [startPos1, endPos1),...,[startPosN, endPosN) 46 * Position is an item position in the provider. 47 */ 48 this._retrievedChildrenRanges = []; 49 } 50 51 WebInspector.HeapSnapshotGridNode.Events = { 52 PopulateComplete: "PopulateComplete" 53 } 54 55 WebInspector.HeapSnapshotGridNode.prototype = { 56 /** 57 * @return {!WebInspector.HeapSnapshotProviderProxy} 58 */ 59 createProvider: function() 60 { 61 throw new Error("Needs implemented."); 62 }, 63 64 /** 65 * @return {!WebInspector.HeapSnapshotProviderProxy} 66 */ 67 _provider: function() 68 { 69 if (!this._providerObject) 70 this._providerObject = this.createProvider(); 71 return this._providerObject; 72 }, 73 74 createCell: function(columnIdentifier) 75 { 76 var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); 77 if (this._searchMatched) 78 cell.classList.add("highlight"); 79 return cell; 80 }, 81 82 collapse: function() 83 { 84 WebInspector.DataGridNode.prototype.collapse.call(this); 85 this._dataGrid.updateVisibleNodes(); 86 }, 87 88 dispose: function() 89 { 90 if (this._provider()) 91 this._provider().dispose(); 92 for (var node = this.children[0]; node; node = node.traverseNextNode(true, this, true)) 93 if (node.dispose) 94 node.dispose(); 95 }, 96 97 _reachableFromWindow: false, 98 99 queryObjectContent: function(callback) 100 { 101 }, 102 103 /** 104 * @override 105 */ 106 wasDetached: function() 107 { 108 this._dataGrid.nodeWasDetached(this); 109 }, 110 111 _toPercentString: function(num) 112 { 113 return num.toFixed(0) + "\u2009%"; // \u2009 is a thin space. 114 }, 115 116 /** 117 * @param {number} nodePosition 118 */ 119 childForPosition: function(nodePosition) 120 { 121 var indexOfFirsChildInRange = 0; 122 for (var i = 0; i < this._retrievedChildrenRanges.length; i++) { 123 var range = this._retrievedChildrenRanges[i]; 124 if (range.from <= nodePosition && nodePosition < range.to) { 125 var childIndex = indexOfFirsChildInRange + nodePosition - range.from; 126 return this.children[childIndex]; 127 } 128 indexOfFirsChildInRange += range.to - range.from + 1; 129 } 130 return null; 131 }, 132 133 _createValueCell: function(columnIdentifier) 134 { 135 var cell = document.createElement("td"); 136 cell.className = columnIdentifier + "-column"; 137 if (this.dataGrid.snapshot.totalSize !== 0) { 138 var div = document.createElement("div"); 139 var valueSpan = document.createElement("span"); 140 valueSpan.textContent = this.data[columnIdentifier]; 141 div.appendChild(valueSpan); 142 var percentColumn = columnIdentifier + "-percent"; 143 if (percentColumn in this.data) { 144 var percentSpan = document.createElement("span"); 145 percentSpan.className = "percent-column"; 146 percentSpan.textContent = this.data[percentColumn]; 147 div.appendChild(percentSpan); 148 div.classList.add("heap-snapshot-multiple-values"); 149 } 150 cell.appendChild(div); 151 } 152 return cell; 153 }, 154 155 populate: function(event) 156 { 157 if (this._populated) 158 return; 159 this._populated = true; 160 161 /** 162 * @this {WebInspector.HeapSnapshotGridNode} 163 */ 164 function sorted() 165 { 166 this._populateChildren(); 167 } 168 this._provider().sortAndRewind(this.comparator(), sorted.bind(this)); 169 }, 170 171 expandWithoutPopulate: function(callback) 172 { 173 // Make sure default populate won't take action. 174 this._populated = true; 175 this.expand(); 176 this._provider().sortAndRewind(this.comparator(), callback); 177 }, 178 179 /** 180 * @param {?number=} fromPosition 181 * @param {?number=} toPosition 182 * @param {function()=} afterPopulate 183 */ 184 _populateChildren: function(fromPosition, toPosition, afterPopulate) 185 { 186 fromPosition = fromPosition || 0; 187 toPosition = toPosition || fromPosition + this._dataGrid.defaultPopulateCount(); 188 var firstNotSerializedPosition = fromPosition; 189 190 /** 191 * @this {WebInspector.HeapSnapshotGridNode} 192 */ 193 function serializeNextChunk() 194 { 195 if (firstNotSerializedPosition >= toPosition) 196 return; 197 var end = Math.min(firstNotSerializedPosition + this._dataGrid.defaultPopulateCount(), toPosition); 198 this._provider().serializeItemsRange(firstNotSerializedPosition, end, childrenRetrieved.bind(this)); 199 firstNotSerializedPosition = end; 200 } 201 202 /** 203 * @this {WebInspector.HeapSnapshotGridNode} 204 */ 205 function insertRetrievedChild(item, insertionIndex) 206 { 207 if (this._savedChildren) { 208 var hash = this._childHashForEntity(item); 209 if (hash in this._savedChildren) { 210 this.insertChild(this._savedChildren[hash], insertionIndex); 211 return; 212 } 213 } 214 this.insertChild(this._createChildNode(item), insertionIndex); 215 } 216 217 /** 218 * @this {WebInspector.HeapSnapshotGridNode} 219 */ 220 function insertShowMoreButton(from, to, insertionIndex) 221 { 222 var button = new WebInspector.ShowMoreDataGridNode(this._populateChildren.bind(this), from, to, this._dataGrid.defaultPopulateCount()); 223 this.insertChild(button, insertionIndex); 224 } 225 226 /** 227 * @this {WebInspector.HeapSnapshotGridNode} 228 */ 229 function childrenRetrieved(items) 230 { 231 var itemIndex = 0; 232 var itemPosition = items.startPosition; 233 var insertionIndex = 0; 234 235 if (!this._retrievedChildrenRanges.length) { 236 if (items.startPosition > 0) { 237 this._retrievedChildrenRanges.push({from: 0, to: 0}); 238 insertShowMoreButton.call(this, 0, items.startPosition, insertionIndex++); 239 } 240 this._retrievedChildrenRanges.push({from: items.startPosition, to: items.endPosition}); 241 for (var i = 0, l = items.length; i < l; ++i) 242 insertRetrievedChild.call(this, items[i], insertionIndex++); 243 if (items.endPosition < items.totalLength) 244 insertShowMoreButton.call(this, items.endPosition, items.totalLength, insertionIndex++); 245 } else { 246 var rangeIndex = 0; 247 var found = false; 248 var range; 249 while (rangeIndex < this._retrievedChildrenRanges.length) { 250 range = this._retrievedChildrenRanges[rangeIndex]; 251 if (range.to >= itemPosition) { 252 found = true; 253 break; 254 } 255 insertionIndex += range.to - range.from; 256 // Skip the button if there is one. 257 if (range.to < items.totalLength) 258 insertionIndex += 1; 259 ++rangeIndex; 260 } 261 262 if (!found || items.startPosition < range.from) { 263 // Update previous button. 264 this.children[insertionIndex - 1].setEndPosition(items.startPosition); 265 insertShowMoreButton.call(this, items.startPosition, found ? range.from : items.totalLength, insertionIndex); 266 range = {from: items.startPosition, to: items.startPosition}; 267 if (!found) 268 rangeIndex = this._retrievedChildrenRanges.length; 269 this._retrievedChildrenRanges.splice(rangeIndex, 0, range); 270 } else { 271 insertionIndex += itemPosition - range.from; 272 } 273 // At this point insertionIndex is always an index before button or between nodes. 274 // Also it is always true here that range.from <= itemPosition <= range.to 275 276 // Stretch the range right bound to include all new items. 277 while (range.to < items.endPosition) { 278 // Skip already added nodes. 279 var skipCount = range.to - itemPosition; 280 insertionIndex += skipCount; 281 itemIndex += skipCount; 282 itemPosition = range.to; 283 284 // We're at the position before button: ...<?node>x<button> 285 var nextRange = this._retrievedChildrenRanges[rangeIndex + 1]; 286 var newEndOfRange = nextRange ? nextRange.from : items.totalLength; 287 if (newEndOfRange > items.endPosition) 288 newEndOfRange = items.endPosition; 289 while (itemPosition < newEndOfRange) { 290 insertRetrievedChild.call(this, items[itemIndex++], insertionIndex++); 291 ++itemPosition; 292 } 293 // Merge with the next range. 294 if (nextRange && newEndOfRange === nextRange.from) { 295 range.to = nextRange.to; 296 // Remove "show next" button if there is one. 297 this.removeChild(this.children[insertionIndex]); 298 this._retrievedChildrenRanges.splice(rangeIndex + 1, 1); 299 } else { 300 range.to = newEndOfRange; 301 // Remove or update next button. 302 if (newEndOfRange === items.totalLength) 303 this.removeChild(this.children[insertionIndex]); 304 else 305 this.children[insertionIndex].setStartPosition(items.endPosition); 306 } 307 } 308 } 309 310 // TODO: fix this. 311 this._instanceCount += items.length; 312 if (firstNotSerializedPosition < toPosition) { 313 serializeNextChunk.call(this); 314 return; 315 } 316 317 if (afterPopulate) 318 afterPopulate(); 319 this.dispatchEventToListeners(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete); 320 } 321 serializeNextChunk.call(this); 322 }, 323 324 _saveChildren: function() 325 { 326 this._savedChildren = null; 327 for (var i = 0, childrenCount = this.children.length; i < childrenCount; ++i) { 328 var child = this.children[i]; 329 if (!child.expanded) 330 continue; 331 if (!this._savedChildren) 332 this._savedChildren = {}; 333 this._savedChildren[this._childHashForNode(child)] = child; 334 } 335 }, 336 337 sort: function() 338 { 339 this._dataGrid.recursiveSortingEnter(); 340 341 /** 342 * @this {WebInspector.HeapSnapshotGridNode} 343 */ 344 function afterSort() 345 { 346 this._saveChildren(); 347 this.removeChildren(); 348 this._retrievedChildrenRanges = []; 349 350 /** 351 * @this {WebInspector.HeapSnapshotGridNode} 352 */ 353 function afterPopulate() 354 { 355 for (var i = 0, l = this.children.length; i < l; ++i) { 356 var child = this.children[i]; 357 if (child.expanded) 358 child.sort(); 359 } 360 this._dataGrid.recursiveSortingLeave(); 361 } 362 var instanceCount = this._instanceCount; 363 this._instanceCount = 0; 364 this._populateChildren(0, instanceCount, afterPopulate.bind(this)); 365 } 366 367 this._provider().sortAndRewind(this.comparator(), afterSort.bind(this)); 368 }, 369 370 __proto__: WebInspector.DataGridNode.prototype 371 } 372 373 374 /** 375 * @constructor 376 * @extends {WebInspector.HeapSnapshotGridNode} 377 * @param {!WebInspector.HeapSnapshotSortableDataGrid} tree 378 */ 379 WebInspector.HeapSnapshotGenericObjectNode = function(tree, node) 380 { 381 this.snapshotNodeIndex = 0; 382 WebInspector.HeapSnapshotGridNode.call(this, tree, false); 383 // node is null for DataGrid root nodes. 384 if (!node) 385 return; 386 this._name = node.name; 387 this._type = node.type; 388 this._distance = node.distance; 389 this._shallowSize = node.selfSize; 390 this._retainedSize = node.retainedSize; 391 this.snapshotNodeId = node.id; 392 this.snapshotNodeIndex = node.nodeIndex; 393 if (this._type === "string") 394 this._reachableFromWindow = true; 395 else if (this._type === "object" && this._name.startsWith("Window")) { 396 this._name = this.shortenWindowURL(this._name, false); 397 this._reachableFromWindow = true; 398 } else if (node.canBeQueried) 399 this._reachableFromWindow = true; 400 if (node.detachedDOMTreeNode) 401 this.detachedDOMTreeNode = true; 402 }; 403 404 WebInspector.HeapSnapshotGenericObjectNode.prototype = { 405 createCell: function(columnIdentifier) 406 { 407 var cell = columnIdentifier !== "object" ? this._createValueCell(columnIdentifier) : this._createObjectCell(); 408 if (this._searchMatched) 409 cell.classList.add("highlight"); 410 return cell; 411 }, 412 413 _createObjectCell: function() 414 { 415 var cell = document.createElement("td"); 416 cell.className = "object-column"; 417 var div = document.createElement("div"); 418 div.className = "source-code event-properties"; 419 div.style.overflow = "visible"; 420 421 var data = this.data["object"]; 422 if (this._prefixObjectCell) 423 this._prefixObjectCell(div, data); 424 425 var valueSpan = document.createElement("span"); 426 valueSpan.className = "value console-formatted-" + data.valueStyle; 427 valueSpan.textContent = data.value; 428 div.appendChild(valueSpan); 429 430 var idSpan = document.createElement("span"); 431 idSpan.className = "console-formatted-id"; 432 idSpan.textContent = " @" + data["nodeId"]; 433 div.appendChild(idSpan); 434 435 if (this._postfixObjectCell) 436 this._postfixObjectCell(div, data); 437 438 cell.appendChild(div); 439 cell.classList.add("disclosure"); 440 if (this.depth) 441 cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px"); 442 cell.heapSnapshotNode = this; 443 return cell; 444 }, 445 446 get data() 447 { 448 var data = this._emptyData(); 449 450 var value = this._name; 451 var valueStyle = "object"; 452 switch (this._type) { 453 case "concatenated string": 454 case "string": 455 value = "\"" + value + "\""; 456 valueStyle = "string"; 457 break; 458 case "regexp": 459 value = "/" + value + "/"; 460 valueStyle = "string"; 461 break; 462 case "closure": 463 value = "function" + (value ? " " : "") + value + "()"; 464 valueStyle = "function"; 465 break; 466 case "number": 467 valueStyle = "number"; 468 break; 469 case "hidden": 470 valueStyle = "null"; 471 break; 472 case "array": 473 if (!value) 474 value = "[]"; 475 else 476 value += "[]"; 477 break; 478 }; 479 if (this._reachableFromWindow) 480 valueStyle += " highlight"; 481 if (value === "Object") 482 value = ""; 483 if (this.detachedDOMTreeNode) 484 valueStyle += " detached-dom-tree-node"; 485 data["object"] = { valueStyle: valueStyle, value: value, nodeId: this.snapshotNodeId }; 486 487 data["distance"] = this._distance; 488 data["shallowSize"] = Number.withThousandsSeparator(this._shallowSize); 489 data["retainedSize"] = Number.withThousandsSeparator(this._retainedSize); 490 data["shallowSize-percent"] = this._toPercentString(this._shallowSizePercent); 491 data["retainedSize-percent"] = this._toPercentString(this._retainedSizePercent); 492 493 return this._enhanceData ? this._enhanceData(data) : data; 494 }, 495 496 queryObjectContent: function(callback, objectGroupName) 497 { 498 /** 499 * @param {?Protocol.Error} error 500 * @param {!RuntimeAgent.RemoteObject} object 501 */ 502 function formatResult(error, object) 503 { 504 if (!error && object.type) 505 callback(WebInspector.RemoteObject.fromPayload(object), !!error); 506 else 507 callback(WebInspector.RemoteObject.fromPrimitiveValue(WebInspector.UIString("Preview is not available"))); 508 } 509 510 if (this._type === "string") 511 callback(WebInspector.RemoteObject.fromPrimitiveValue(this._name)); 512 else 513 HeapProfilerAgent.getObjectByHeapObjectId(String(this.snapshotNodeId), objectGroupName, formatResult); 514 }, 515 516 get _retainedSizePercent() 517 { 518 return this._retainedSize / this.dataGrid.snapshot.totalSize * 100.0; 519 }, 520 521 get _shallowSizePercent() 522 { 523 return this._shallowSize / this.dataGrid.snapshot.totalSize * 100.0; 524 }, 525 526 updateHasChildren: function() 527 { 528 /** 529 * @this {WebInspector.HeapSnapshotGenericObjectNode} 530 */ 531 function isEmptyCallback(isEmpty) 532 { 533 this.hasChildren = !isEmpty; 534 } 535 this._provider().isEmpty(isEmptyCallback.bind(this)); 536 }, 537 538 shortenWindowURL: function(fullName, hasObjectId) 539 { 540 var startPos = fullName.indexOf("/"); 541 var endPos = hasObjectId ? fullName.indexOf("@") : fullName.length; 542 if (startPos !== -1 && endPos !== -1) { 543 var fullURL = fullName.substring(startPos + 1, endPos).trimLeft(); 544 var url = fullURL.trimURL(); 545 if (url.length > 40) 546 url = url.trimMiddle(40); 547 return fullName.substr(0, startPos + 2) + url + fullName.substr(endPos); 548 } else 549 return fullName; 550 }, 551 552 __proto__: WebInspector.HeapSnapshotGridNode.prototype 553 } 554 555 /** 556 * @constructor 557 * @extends {WebInspector.HeapSnapshotGenericObjectNode} 558 * @param {!WebInspector.HeapSnapshotSortableDataGrid} tree 559 * @param {boolean} isFromBaseSnapshot 560 */ 561 WebInspector.HeapSnapshotObjectNode = function(tree, isFromBaseSnapshot, edge, parentGridNode) 562 { 563 WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, edge.node); 564 this._referenceName = edge.name; 565 this._referenceType = edge.type; 566 this._distance = edge.distance; 567 this.showRetainingEdges = tree.showRetainingEdges; 568 this._isFromBaseSnapshot = isFromBaseSnapshot; 569 570 this._parentGridNode = parentGridNode; 571 this._cycledWithAncestorGridNode = this._findAncestorWithSameSnapshotNodeId(); 572 if (!this._cycledWithAncestorGridNode) 573 this.updateHasChildren(); 574 } 575 576 WebInspector.HeapSnapshotObjectNode.prototype = { 577 /** 578 * @return {!WebInspector.HeapSnapshotProviderProxy} 579 */ 580 createProvider: function() 581 { 582 var tree = this._dataGrid; 583 var showHiddenData = WebInspector.settings.showAdvancedHeapSnapshotProperties.get(); 584 var snapshot = this._isFromBaseSnapshot ? tree.baseSnapshot : tree.snapshot; 585 if (this.showRetainingEdges) 586 return snapshot.createRetainingEdgesProvider(this.snapshotNodeIndex, showHiddenData); 587 else 588 return snapshot.createEdgesProvider(this.snapshotNodeIndex, showHiddenData); 589 }, 590 591 _findAncestorWithSameSnapshotNodeId: function() 592 { 593 var ancestor = this._parentGridNode; 594 while (ancestor) { 595 if (ancestor.snapshotNodeId === this.snapshotNodeId) 596 return ancestor; 597 ancestor = ancestor._parentGridNode; 598 } 599 return null; 600 }, 601 602 _createChildNode: function(item) 603 { 604 return new WebInspector.HeapSnapshotObjectNode(this._dataGrid, this._isFromBaseSnapshot, item, this); 605 }, 606 607 _childHashForEntity: function(edge) 608 { 609 var prefix = this.showRetainingEdges ? edge.node.id + "#" : ""; 610 return prefix + edge.type + "#" + edge.name; 611 }, 612 613 _childHashForNode: function(childNode) 614 { 615 var prefix = this.showRetainingEdges ? childNode.snapshotNodeId + "#" : ""; 616 return prefix + childNode._referenceType + "#" + childNode._referenceName; 617 }, 618 619 comparator: function() 620 { 621 var sortAscending = this._dataGrid.isSortOrderAscending(); 622 var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier(); 623 var sortFields = { 624 object: ["!edgeName", sortAscending, "retainedSize", false], 625 count: ["!edgeName", true, "retainedSize", false], 626 shallowSize: ["selfSize", sortAscending, "!edgeName", true], 627 retainedSize: ["retainedSize", sortAscending, "!edgeName", true], 628 distance: ["distance", sortAscending, "_name", true] 629 }[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false]; 630 return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); 631 }, 632 633 _emptyData: function() 634 { 635 return { count: "", addedCount: "", removedCount: "", countDelta: "", addedSize: "", removedSize: "", sizeDelta: "" }; 636 }, 637 638 _enhanceData: function(data) 639 { 640 var name = this._referenceName; 641 if (name === "") name = "(empty)"; 642 var nameClass = "name"; 643 switch (this._referenceType) { 644 case "context": 645 nameClass = "console-formatted-number"; 646 break; 647 case "internal": 648 case "hidden": 649 nameClass = "console-formatted-null"; 650 break; 651 case "element": 652 name = "[" + name + "]"; 653 break; 654 } 655 data["object"].nameClass = nameClass; 656 data["object"].name = name; 657 data["distance"] = this._distance; 658 return data; 659 }, 660 661 _prefixObjectCell: function(div, data) 662 { 663 if (this._cycledWithAncestorGridNode) 664 div.className += " cycled-ancessor-node"; 665 666 var nameSpan = document.createElement("span"); 667 nameSpan.className = data.nameClass; 668 nameSpan.textContent = data.name; 669 div.appendChild(nameSpan); 670 671 var separatorSpan = document.createElement("span"); 672 separatorSpan.className = "grayed"; 673 separatorSpan.textContent = this.showRetainingEdges ? " in " : " :: "; 674 div.appendChild(separatorSpan); 675 }, 676 677 __proto__: WebInspector.HeapSnapshotGenericObjectNode.prototype 678 } 679 680 /** 681 * @constructor 682 * @extends {WebInspector.HeapSnapshotGenericObjectNode} 683 */ 684 WebInspector.HeapSnapshotInstanceNode = function(tree, baseSnapshot, snapshot, node) 685 { 686 WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node); 687 this._baseSnapshotOrSnapshot = baseSnapshot || snapshot; 688 this._isDeletedNode = !!baseSnapshot; 689 this.updateHasChildren(); 690 }; 691 692 WebInspector.HeapSnapshotInstanceNode.prototype = { 693 createProvider: function() 694 { 695 var showHiddenData = WebInspector.settings.showAdvancedHeapSnapshotProperties.get(); 696 return this._baseSnapshotOrSnapshot.createEdgesProvider( 697 this.snapshotNodeIndex, 698 showHiddenData); 699 }, 700 701 _createChildNode: function(item) 702 { 703 return new WebInspector.HeapSnapshotObjectNode(this._dataGrid, this._isDeletedNode, item, null); 704 }, 705 706 _childHashForEntity: function(edge) 707 { 708 return edge.type + "#" + edge.name; 709 }, 710 711 _childHashForNode: function(childNode) 712 { 713 return childNode._referenceType + "#" + childNode._referenceName; 714 }, 715 716 comparator: function() 717 { 718 var sortAscending = this._dataGrid.isSortOrderAscending(); 719 var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier(); 720 var sortFields = { 721 object: ["!edgeName", sortAscending, "retainedSize", false], 722 distance: ["distance", sortAscending, "retainedSize", false], 723 count: ["!edgeName", true, "retainedSize", false], 724 addedSize: ["selfSize", sortAscending, "!edgeName", true], 725 removedSize: ["selfSize", sortAscending, "!edgeName", true], 726 shallowSize: ["selfSize", sortAscending, "!edgeName", true], 727 retainedSize: ["retainedSize", sortAscending, "!edgeName", true] 728 }[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false]; 729 return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); 730 }, 731 732 _emptyData: function() 733 { 734 return {count: "", countDelta: "", sizeDelta: ""}; 735 }, 736 737 _enhanceData: function(data) 738 { 739 if (this._isDeletedNode) { 740 data["addedCount"] = ""; 741 data["addedSize"] = ""; 742 data["removedCount"] = "\u2022"; 743 data["removedSize"] = Number.withThousandsSeparator(this._shallowSize); 744 } else { 745 data["addedCount"] = "\u2022"; 746 data["addedSize"] = Number.withThousandsSeparator(this._shallowSize); 747 data["removedCount"] = ""; 748 data["removedSize"] = ""; 749 } 750 return data; 751 }, 752 753 get isDeletedNode() 754 { 755 return this._isDeletedNode; 756 }, 757 758 __proto__: WebInspector.HeapSnapshotGenericObjectNode.prototype 759 } 760 761 /** 762 * @constructor 763 * @extends {WebInspector.HeapSnapshotGridNode} 764 */ 765 WebInspector.HeapSnapshotConstructorNode = function(tree, className, aggregate, aggregatesKey) 766 { 767 WebInspector.HeapSnapshotGridNode.call(this, tree, aggregate.count > 0); 768 this._name = className; 769 this._aggregatesKey = aggregatesKey; 770 this._distance = aggregate.distance; 771 this._count = aggregate.count; 772 this._shallowSize = aggregate.self; 773 this._retainedSize = aggregate.maxRet; 774 } 775 776 WebInspector.HeapSnapshotConstructorNode.prototype = { 777 /** 778 * @override 779 * @return {!WebInspector.HeapSnapshotProviderProxy} 780 */ 781 createProvider: function() 782 { 783 return this._dataGrid.snapshot.createNodesProviderForClass(this._name, this._aggregatesKey) 784 }, 785 786 /** 787 * @param {number} snapshotObjectId 788 * @param {function(boolean)} callback 789 */ 790 revealNodeBySnapshotObjectId: function(snapshotObjectId, callback) 791 { 792 /** 793 * @this {WebInspector.HeapSnapshotConstructorNode} 794 */ 795 function didExpand() 796 { 797 this._provider().nodePosition(snapshotObjectId, didGetNodePosition.bind(this)); 798 } 799 800 /** 801 * @this {WebInspector.HeapSnapshotConstructorNode} 802 */ 803 function didGetNodePosition(nodePosition) 804 { 805 if (nodePosition === -1) { 806 this.collapse(); 807 callback(false); 808 } else { 809 this._populateChildren(nodePosition, null, didPopulateChildren.bind(this, nodePosition)); 810 } 811 } 812 813 /** 814 * @this {WebInspector.HeapSnapshotConstructorNode} 815 */ 816 function didPopulateChildren(nodePosition) 817 { 818 var indexOfFirsChildInRange = 0; 819 for (var i = 0; i < this._retrievedChildrenRanges.length; i++) { 820 var range = this._retrievedChildrenRanges[i]; 821 if (range.from <= nodePosition && nodePosition < range.to) { 822 var childIndex = indexOfFirsChildInRange + nodePosition - range.from; 823 var instanceNode = this.children[childIndex]; 824 this._dataGrid.highlightNode(/** @type {!WebInspector.HeapSnapshotGridNode} */ (instanceNode)); 825 callback(true); 826 return; 827 } 828 indexOfFirsChildInRange += range.to - range.from + 1; 829 } 830 callback(false); 831 } 832 833 this.expandWithoutPopulate(didExpand.bind(this)); 834 }, 835 836 createCell: function(columnIdentifier) 837 { 838 var cell = columnIdentifier !== "object" ? this._createValueCell(columnIdentifier) : WebInspector.HeapSnapshotGridNode.prototype.createCell.call(this, columnIdentifier); 839 if (this._searchMatched) 840 cell.classList.add("highlight"); 841 return cell; 842 }, 843 844 _createChildNode: function(item) 845 { 846 return new WebInspector.HeapSnapshotInstanceNode(this._dataGrid, null, this._dataGrid.snapshot, item); 847 }, 848 849 comparator: function() 850 { 851 var sortAscending = this._dataGrid.isSortOrderAscending(); 852 var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier(); 853 var sortFields = { 854 object: ["id", sortAscending, "retainedSize", false], 855 distance: ["distance", sortAscending, "retainedSize", false], 856 count: ["id", true, "retainedSize", false], 857 shallowSize: ["selfSize", sortAscending, "id", true], 858 retainedSize: ["retainedSize", sortAscending, "id", true] 859 }[sortColumnIdentifier]; 860 return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); 861 }, 862 863 _childHashForEntity: function(node) 864 { 865 return node.id; 866 }, 867 868 _childHashForNode: function(childNode) 869 { 870 return childNode.snapshotNodeId; 871 }, 872 873 get data() 874 { 875 var data = { object: this._name }; 876 data["count"] = Number.withThousandsSeparator(this._count); 877 data["distance"] = this._distance; 878 data["shallowSize"] = Number.withThousandsSeparator(this._shallowSize); 879 data["retainedSize"] = Number.withThousandsSeparator(this._retainedSize); 880 data["count-percent"] = this._toPercentString(this._countPercent); 881 data["shallowSize-percent"] = this._toPercentString(this._shallowSizePercent); 882 data["retainedSize-percent"] = this._toPercentString(this._retainedSizePercent); 883 return data; 884 }, 885 886 get _countPercent() 887 { 888 return this._count / this.dataGrid.snapshot.nodeCount * 100.0; 889 }, 890 891 get _retainedSizePercent() 892 { 893 return this._retainedSize / this.dataGrid.snapshot.totalSize * 100.0; 894 }, 895 896 get _shallowSizePercent() 897 { 898 return this._shallowSize / this.dataGrid.snapshot.totalSize * 100.0; 899 }, 900 901 __proto__: WebInspector.HeapSnapshotGridNode.prototype 902 } 903 904 905 /** 906 * @constructor 907 * @extends {WebInspector.HeapSnapshotProviderProxy} 908 * @param {!WebInspector.HeapSnapshotProviderProxy} addedNodesProvider 909 * @param {!WebInspector.HeapSnapshotProviderProxy} deletedNodesProvider 910 */ 911 WebInspector.HeapSnapshotDiffNodesProvider = function(addedNodesProvider, deletedNodesProvider, addedCount, removedCount) 912 { 913 this._addedNodesProvider = addedNodesProvider; 914 this._deletedNodesProvider = deletedNodesProvider; 915 this._addedCount = addedCount; 916 this._removedCount = removedCount; 917 } 918 919 WebInspector.HeapSnapshotDiffNodesProvider.prototype = { 920 dispose: function() 921 { 922 this._addedNodesProvider.dispose(); 923 this._deletedNodesProvider.dispose(); 924 }, 925 926 isEmpty: function(callback) 927 { 928 callback(false); 929 }, 930 931 serializeItemsRange: function(beginPosition, endPosition, callback) 932 { 933 /** 934 * @this {WebInspector.HeapSnapshotDiffNodesProvider} 935 */ 936 function didReceiveAllItems(items) 937 { 938 items.totalLength = this._addedCount + this._removedCount; 939 callback(items); 940 } 941 942 /** 943 * @this {WebInspector.HeapSnapshotDiffNodesProvider} 944 */ 945 function didReceiveDeletedItems(addedItems, items) 946 { 947 if (!addedItems.length) 948 addedItems.startPosition = this._addedCount + items.startPosition; 949 for (var i = 0; i < items.length; i++) { 950 items[i].isAddedNotRemoved = false; 951 addedItems.push(items[i]); 952 } 953 addedItems.endPosition = this._addedCount + items.endPosition; 954 didReceiveAllItems.call(this, addedItems); 955 } 956 957 /** 958 * @this {WebInspector.HeapSnapshotDiffNodesProvider} 959 */ 960 function didReceiveAddedItems(items) 961 { 962 for (var i = 0; i < items.length; i++) 963 items[i].isAddedNotRemoved = true; 964 if (items.endPosition < endPosition) 965 return this._deletedNodesProvider.serializeItemsRange(0, endPosition - items.endPosition, didReceiveDeletedItems.bind(this, items)); 966 967 items.totalLength = this._addedCount + this._removedCount; 968 didReceiveAllItems.call(this, items); 969 } 970 971 if (beginPosition < this._addedCount) 972 this._addedNodesProvider.serializeItemsRange(beginPosition, endPosition, didReceiveAddedItems.bind(this)); 973 else 974 this._deletedNodesProvider.serializeItemsRange(beginPosition - this._addedCount, endPosition - this._addedCount, didReceiveDeletedItems.bind(this, [])); 975 }, 976 977 sortAndRewind: function(comparator, callback) 978 { 979 /** 980 * @this {WebInspector.HeapSnapshotDiffNodesProvider} 981 */ 982 function afterSort() 983 { 984 this._deletedNodesProvider.sortAndRewind(comparator, callback); 985 } 986 this._addedNodesProvider.sortAndRewind(comparator, afterSort.bind(this)); 987 } 988 }; 989 990 /** 991 * @constructor 992 * @extends {WebInspector.HeapSnapshotGridNode} 993 */ 994 WebInspector.HeapSnapshotDiffNode = function(tree, className, diffForClass) 995 { 996 WebInspector.HeapSnapshotGridNode.call(this, tree, true); 997 this._name = className; 998 999 this._addedCount = diffForClass.addedCount; 1000 this._removedCount = diffForClass.removedCount; 1001 this._countDelta = diffForClass.countDelta; 1002 this._addedSize = diffForClass.addedSize; 1003 this._removedSize = diffForClass.removedSize; 1004 this._sizeDelta = diffForClass.sizeDelta; 1005 this._deletedIndexes = diffForClass.deletedIndexes; 1006 } 1007 1008 WebInspector.HeapSnapshotDiffNode.prototype = { 1009 /** 1010 * @override 1011 * @return {!WebInspector.HeapSnapshotDiffNodesProvider} 1012 */ 1013 createProvider: function() 1014 { 1015 var tree = this._dataGrid; 1016 return new WebInspector.HeapSnapshotDiffNodesProvider( 1017 tree.snapshot.createAddedNodesProvider(tree.baseSnapshot.uid, this._name), 1018 tree.baseSnapshot.createDeletedNodesProvider(this._deletedIndexes), 1019 this._addedCount, 1020 this._removedCount); 1021 }, 1022 1023 _createChildNode: function(item) 1024 { 1025 if (item.isAddedNotRemoved) 1026 return new WebInspector.HeapSnapshotInstanceNode(this._dataGrid, null, this._dataGrid.snapshot, item); 1027 else 1028 return new WebInspector.HeapSnapshotInstanceNode(this._dataGrid, this._dataGrid.baseSnapshot, null, item); 1029 }, 1030 1031 _childHashForEntity: function(node) 1032 { 1033 return node.id; 1034 }, 1035 1036 _childHashForNode: function(childNode) 1037 { 1038 return childNode.snapshotNodeId; 1039 }, 1040 1041 comparator: function() 1042 { 1043 var sortAscending = this._dataGrid.isSortOrderAscending(); 1044 var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier(); 1045 var sortFields = { 1046 object: ["id", sortAscending, "selfSize", false], 1047 addedCount: ["selfSize", sortAscending, "id", true], 1048 removedCount: ["selfSize", sortAscending, "id", true], 1049 countDelta: ["selfSize", sortAscending, "id", true], 1050 addedSize: ["selfSize", sortAscending, "id", true], 1051 removedSize: ["selfSize", sortAscending, "id", true], 1052 sizeDelta: ["selfSize", sortAscending, "id", true] 1053 }[sortColumnIdentifier]; 1054 return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); 1055 }, 1056 1057 _signForDelta: function(delta) 1058 { 1059 if (delta === 0) 1060 return ""; 1061 if (delta > 0) 1062 return "+"; 1063 else 1064 return "\u2212"; // Math minus sign, same width as plus. 1065 }, 1066 1067 get data() 1068 { 1069 var data = {object: this._name}; 1070 1071 data["addedCount"] = Number.withThousandsSeparator(this._addedCount); 1072 data["removedCount"] = Number.withThousandsSeparator(this._removedCount); 1073 data["countDelta"] = this._signForDelta(this._countDelta) + Number.withThousandsSeparator(Math.abs(this._countDelta)); 1074 data["addedSize"] = Number.withThousandsSeparator(this._addedSize); 1075 data["removedSize"] = Number.withThousandsSeparator(this._removedSize); 1076 data["sizeDelta"] = this._signForDelta(this._sizeDelta) + Number.withThousandsSeparator(Math.abs(this._sizeDelta)); 1077 1078 return data; 1079 }, 1080 1081 __proto__: WebInspector.HeapSnapshotGridNode.prototype 1082 } 1083 1084 1085 /** 1086 * @constructor 1087 * @extends {WebInspector.HeapSnapshotGenericObjectNode} 1088 */ 1089 WebInspector.HeapSnapshotDominatorObjectNode = function(tree, node) 1090 { 1091 WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node); 1092 this.updateHasChildren(); 1093 }; 1094 1095 WebInspector.HeapSnapshotDominatorObjectNode.prototype = { 1096 /** 1097 * @override 1098 * @return {!WebInspector.HeapSnapshotProviderProxy} 1099 */ 1100 createProvider: function() 1101 { 1102 return this._dataGrid.snapshot.createNodesProviderForDominator(this.snapshotNodeIndex); 1103 }, 1104 1105 /** 1106 * @param {number} snapshotObjectId 1107 * @param {function(?WebInspector.HeapSnapshotDominatorObjectNode)} callback 1108 */ 1109 retrieveChildBySnapshotObjectId: function(snapshotObjectId, callback) 1110 { 1111 /** 1112 * @this {WebInspector.HeapSnapshotDominatorObjectNode} 1113 */ 1114 function didExpand() 1115 { 1116 this._provider().nodePosition(snapshotObjectId, didGetNodePosition.bind(this)); 1117 } 1118 1119 /** 1120 * @this {WebInspector.HeapSnapshotDominatorObjectNode} 1121 */ 1122 function didGetNodePosition(nodePosition) 1123 { 1124 if (nodePosition === -1) { 1125 this.collapse(); 1126 callback(null); 1127 } else 1128 this._populateChildren(nodePosition, null, didPopulateChildren.bind(this, nodePosition)); 1129 } 1130 1131 /** 1132 * @this {WebInspector.HeapSnapshotDominatorObjectNode} 1133 */ 1134 function didPopulateChildren(nodePosition) 1135 { 1136 var child = this.childForPosition(nodePosition); 1137 callback(child); 1138 } 1139 1140 // Make sure hasChildren flag is updated before expanding this node as updateHasChildren response 1141 // may not have been received yet. 1142 this.hasChildren = true; 1143 this.expandWithoutPopulate(didExpand.bind(this)); 1144 }, 1145 1146 _createChildNode: function(item) 1147 { 1148 return new WebInspector.HeapSnapshotDominatorObjectNode(this._dataGrid, item); 1149 }, 1150 1151 _childHashForEntity: function(node) 1152 { 1153 return node.id; 1154 }, 1155 1156 _childHashForNode: function(childNode) 1157 { 1158 return childNode.snapshotNodeId; 1159 }, 1160 1161 comparator: function() 1162 { 1163 var sortAscending = this._dataGrid.isSortOrderAscending(); 1164 var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier(); 1165 var sortFields = { 1166 object: ["id", sortAscending, "retainedSize", false], 1167 shallowSize: ["selfSize", sortAscending, "id", true], 1168 retainedSize: ["retainedSize", sortAscending, "id", true] 1169 }[sortColumnIdentifier]; 1170 return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); 1171 }, 1172 1173 _emptyData: function() 1174 { 1175 return {}; 1176 }, 1177 1178 __proto__: WebInspector.HeapSnapshotGenericObjectNode.prototype 1179 } 1180 1181