• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
37WebInspector.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
51WebInspector.HeapSnapshotGridNode.Events = {
52    PopulateComplete: "PopulateComplete"
53}
54
55WebInspector.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 */
379WebInspector.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
404WebInspector.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 */
561WebInspector.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
576WebInspector.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 */
684WebInspector.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
692WebInspector.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 */
765WebInspector.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
776WebInspector.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 */
911WebInspector.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
919WebInspector.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 */
994WebInspector.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
1008WebInspector.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 */
1089WebInspector.HeapSnapshotDominatorObjectNode = function(tree, node)
1090{
1091    WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node);
1092    this.updateHasChildren();
1093};
1094
1095WebInspector.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