Home | History | Annotate | Download | only in front_end
      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