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.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