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