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