1/*
2 * Copyright (C) 2012 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.DataGrid}
34 */
35WebInspector.HeapSnapshotSortableDataGrid = function(columns)
36{
37    WebInspector.DataGrid.call(this, columns);
38
39    /**
40     * @type {number}
41     */
42    this._recursiveSortingDepth = 0;
43    /**
44     * @type {?WebInspector.HeapSnapshotGridNode}
45     */
46    this._highlightedNode = null;
47    /**
48     * @type {boolean}
49     */
50    this._populatedAndSorted = false;
51    this.addEventListener("sorting complete", this._sortingComplete, this);
52    this.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this.sortingChanged, this);
53}
54
55WebInspector.HeapSnapshotSortableDataGrid.Events = {
56    ContentShown: "ContentShown"
57}
58
59WebInspector.HeapSnapshotSortableDataGrid.prototype = {
60    /**
61     * @return {number}
62     */
63    defaultPopulateCount: function()
64    {
65        return 100;
66    },
67
68    dispose: function()
69    {
70        var children = this.topLevelNodes();
71        for (var i = 0, l = children.length; i < l; ++i)
72            children[i].dispose();
73    },
74
75    /**
76     * @override
77     */
78    wasShown: function()
79    {
80        if (this._populatedAndSorted)
81            this.dispatchEventToListeners(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, this);
82    },
83
84    _sortingComplete: function()
85    {
86        this.removeEventListener("sorting complete", this._sortingComplete, this);
87        this._populatedAndSorted = true;
88        this.dispatchEventToListeners(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, this);
89    },
90
91    /**
92     * @override
93     */
94    willHide: function()
95    {
96        this._clearCurrentHighlight();
97    },
98
99    /**
100     * @param {!WebInspector.ProfilesPanel} profilesPanel
101     * @param {!WebInspector.ContextMenu} contextMenu
102     * @param {?Event} event
103     */
104    populateContextMenu: function(profilesPanel, contextMenu, event)
105    {
106        var td = event.target.enclosingNodeOrSelfWithNodeName("td");
107        if (!td)
108            return;
109        var node = td.heapSnapshotNode;
110        function revealInDominatorsView()
111        {
112                profilesPanel.showObject(node.snapshotNodeId, "Dominators");
113        }
114        function revealInSummaryView()
115        {
116                profilesPanel.showObject(node.snapshotNodeId, "Summary");
117        }
118        if(node && node.showRetainingEdges) {
119            contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInSummaryView.bind(this));
120            contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInDominatorsView.bind(this));
121        }
122        else if (node instanceof WebInspector.HeapSnapshotInstanceNode || node instanceof WebInspector.HeapSnapshotObjectNode) {
123            contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInDominatorsView.bind(this));
124        } else if (node instanceof WebInspector.HeapSnapshotDominatorObjectNode) {
125            contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInSummaryView.bind(this));
126        }
127    },
128
129    resetSortingCache: function()
130    {
131        delete this._lastSortColumnIdentifier;
132        delete this._lastSortAscending;
133    },
134
135    topLevelNodes: function()
136    {
137        return this.rootNode().children;
138    },
139
140    /**
141     * @param {!HeapProfilerAgent.HeapSnapshotObjectId} heapSnapshotObjectId
142     * @param {function(boolean)} callback
143     */
144    highlightObjectByHeapSnapshotId: function(heapSnapshotObjectId, callback)
145    {
146    },
147
148    /**
149     * @param {!WebInspector.HeapSnapshotGridNode} node
150     */
151    highlightNode: function(node)
152    {
153        var prevNode = this._highlightedNode;
154        this._clearCurrentHighlight();
155        this._highlightedNode = node;
156        this._highlightedNode.element.classList.add("highlighted-row");
157        // If highlighted node hasn't changed reinsert it to make the highlight animation restart.
158        if (node === prevNode) {
159            var element = node.element;
160            var parent = element.parentElement;
161            var nextSibling = element.nextSibling;
162            parent.removeChild(element);
163            parent.insertBefore(element, nextSibling);
164        }
165    },
166
167    nodeWasDetached: function(node)
168    {
169        if (this._highlightedNode === node)
170            this._clearCurrentHighlight();
171    },
172
173    _clearCurrentHighlight: function()
174    {
175        if (!this._highlightedNode)
176            return
177        this._highlightedNode.element.classList.remove("highlighted-row");
178        this._highlightedNode = null;
179    },
180
181    changeNameFilter: function(filter)
182    {
183        filter = filter.toLowerCase();
184        var children = this.topLevelNodes();
185        for (var i = 0, l = children.length; i < l; ++i) {
186            var node = children[i];
187            if (node.depth === 0)
188                node.revealed = node._name.toLowerCase().indexOf(filter) !== -1;
189        }
190        this.updateVisibleNodes();
191    },
192
193    sortingChanged: function()
194    {
195        var sortAscending = this.isSortOrderAscending();
196        var sortColumnIdentifier = this.sortColumnIdentifier();
197        if (this._lastSortColumnIdentifier === sortColumnIdentifier && this._lastSortAscending === sortAscending)
198            return;
199        this._lastSortColumnIdentifier = sortColumnIdentifier;
200        this._lastSortAscending = sortAscending;
201        var sortFields = this._sortFields(sortColumnIdentifier, sortAscending);
202
203        function SortByTwoFields(nodeA, nodeB)
204        {
205            var field1 = nodeA[sortFields[0]];
206            var field2 = nodeB[sortFields[0]];
207            var result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
208            if (!sortFields[1])
209                result = -result;
210            if (result !== 0)
211                return result;
212            field1 = nodeA[sortFields[2]];
213            field2 = nodeB[sortFields[2]];
214            result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
215            if (!sortFields[3])
216                result = -result;
217            return result;
218        }
219        this._performSorting(SortByTwoFields);
220    },
221
222    _performSorting: function(sortFunction)
223    {
224        this.recursiveSortingEnter();
225        var children = this._topLevelNodes;
226        this.rootNode().removeChildren();
227        children.sort(sortFunction);
228        for (var i = 0, l = children.length; i < l; ++i) {
229            var child = children[i];
230            this.appendChildAfterSorting(child);
231            if (child.expanded)
232                child.sort();
233        }
234        this.updateVisibleNodes();
235        this.recursiveSortingLeave();
236    },
237
238    appendChildAfterSorting: function(child)
239    {
240        var revealed = child.revealed;
241        this.rootNode().appendChild(child);
242        child.revealed = revealed;
243    },
244
245    updateVisibleNodes: function()
246    {
247    },
248
249    recursiveSortingEnter: function()
250    {
251        ++this._recursiveSortingDepth;
252    },
253
254    recursiveSortingLeave: function()
255    {
256        if (!this._recursiveSortingDepth)
257            return;
258        if (!--this._recursiveSortingDepth)
259            this.dispatchEventToListeners("sorting complete");
260    },
261
262    __proto__: WebInspector.DataGrid.prototype
263}
264
265
266
267/**
268 * @constructor
269 * @extends {WebInspector.HeapSnapshotSortableDataGrid}
270 */
271WebInspector.HeapSnapshotViewportDataGrid = function(columns)
272{
273    WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
274    this.scrollContainer.addEventListener("scroll", this._onScroll.bind(this), true);
275    this._topLevelNodes = [];
276    this._topPadding = new WebInspector.HeapSnapshotPaddingNode();
277    this._bottomPadding = new WebInspector.HeapSnapshotPaddingNode();
278    /**
279     * @type {?WebInspector.HeapSnapshotGridNode}
280     */
281    this._nodeToHighlightAfterScroll = null;
282}
283
284WebInspector.HeapSnapshotViewportDataGrid.prototype = {
285    topLevelNodes: function()
286    {
287        return this._topLevelNodes;
288    },
289
290    appendChildAfterSorting: function(child)
291    {
292        // Do nothing here, it will be added in updateVisibleNodes.
293    },
294
295    updateVisibleNodes: function()
296    {
297        var scrollTop = this.scrollContainer.scrollTop;
298        var children = this._topLevelNodes;
299
300        var i = 0;
301        var topPadding = 0;
302        while (i < children.length) {
303            if (children[i].revealed) {
304                var newTop = topPadding + children[i].nodeHeight();
305                if (newTop > scrollTop)
306                    break;
307                topPadding = newTop;
308            }
309            ++i;
310        }
311
312        this._addVisibleNodes(i, scrollTop - topPadding, topPadding);
313    },
314
315    _addVisibleNodes: function(firstVisibleNodeIndex, firstNodeHiddenHeight, topPadding)
316    {
317        var viewPortHeight = this.scrollContainer.offsetHeight;
318        this._removePaddingRows();
319
320        var children = this._topLevelNodes;
321        var selectedNode = this.selectedNode;
322
323        this.rootNode().removeChildren();
324        // The height of the view port + invisible top part.
325        var heightToFill = viewPortHeight + firstNodeHiddenHeight;
326        var filledHeight = 0;
327        var i = firstVisibleNodeIndex;
328        while (i < children.length && filledHeight < heightToFill) {
329            if (children[i].revealed) {
330                this.rootNode().appendChild(children[i]);
331                filledHeight += children[i].nodeHeight();
332            }
333            ++i;
334        }
335
336        var bottomPadding = 0;
337        while (i < children.length) {
338            bottomPadding += children[i].nodeHeight();
339            ++i;
340        }
341
342        this._addPaddingRows(topPadding, bottomPadding);
343
344        if (selectedNode) {
345            if (selectedNode.parent) {
346                selectedNode.select(true);
347            } else {
348                // Keep selection even if the node is not in the current viewport.
349                this.selectedNode = selectedNode;
350            }
351        }
352    },
353
354    _revealTopLevelNode: function(nodeToReveal)
355    {
356        var children = this._topLevelNodes;
357
358        var i = 0;
359        var topPadding = 0;
360        while (i < children.length) {
361            if (children[i] === nodeToReveal)
362                break;
363            if (children[i].revealed) {
364                var newTop = topPadding + children[i].nodeHeight();
365                topPadding = newTop;
366            }
367            ++i;
368        }
369
370        this._addVisibleNodes(i, 0, topPadding);
371    },
372
373    appendTopLevelNode: function(node)
374    {
375        this._topLevelNodes.push(node);
376    },
377
378    removeTopLevelNodes: function()
379    {
380        this.rootNode().removeChildren();
381        this._topLevelNodes = [];
382    },
383
384    /**
385     * @override
386     * @param {!WebInspector.HeapSnapshotGridNode} node
387     */
388    highlightNode: function(node)
389    {
390        if (this._isScrolledIntoView(node.element))
391            WebInspector.HeapSnapshotSortableDataGrid.prototype.highlightNode.call(this, node);
392        else {
393            node.element.scrollIntoViewIfNeeded(true);
394            this._nodeToHighlightAfterScroll = node;
395        }
396    },
397
398    _isScrolledIntoView: function(element)
399    {
400        var viewportTop = this.scrollContainer.scrollTop;
401        var viewportBottom = viewportTop + this.scrollContainer.clientHeight;
402        var elemTop = element.offsetTop
403        var elemBottom = elemTop + element.offsetHeight;
404        return elemBottom <= viewportBottom && elemTop >= viewportTop;
405    },
406
407    _addPaddingRows: function(top, bottom)
408    {
409        if (this._topPadding.element.parentNode !== this.dataTableBody)
410            this.dataTableBody.insertBefore(this._topPadding.element, this.dataTableBody.firstChild);
411        if (this._bottomPadding.element.parentNode !== this.dataTableBody)
412            this.dataTableBody.insertBefore(this._bottomPadding.element, this.dataTableBody.lastChild);
413        this._topPadding.setHeight(top);
414        this._bottomPadding.setHeight(bottom);
415    },
416
417    _removePaddingRows: function()
418    {
419        this._bottomPadding.removeFromTable();
420        this._topPadding.removeFromTable();
421    },
422
423    onResize: function()
424    {
425        WebInspector.HeapSnapshotSortableDataGrid.prototype.onResize.call(this);
426        this.updateVisibleNodes();
427    },
428
429    _onScroll: function(event)
430    {
431        this.updateVisibleNodes();
432
433        if (this._nodeToHighlightAfterScroll) {
434            WebInspector.HeapSnapshotSortableDataGrid.prototype.highlightNode.call(this, this._nodeToHighlightAfterScroll);
435            this._nodeToHighlightAfterScroll = null;
436        }
437    },
438
439    __proto__: WebInspector.HeapSnapshotSortableDataGrid.prototype
440}
441
442/**
443 * @constructor
444 */
445WebInspector.HeapSnapshotPaddingNode = function()
446{
447    this.element = document.createElement("tr");
448    this.element.classList.add("revealed");
449}
450
451WebInspector.HeapSnapshotPaddingNode.prototype = {
452   setHeight: function(height)
453   {
454       this.element.style.height = height + "px";
455   },
456   removeFromTable: function()
457   {
458        var parent = this.element.parentNode;
459        if (parent)
460            parent.removeChild(this.element);
461   }
462}
463
464
465/**
466 * @constructor
467 * @extends {WebInspector.HeapSnapshotSortableDataGrid}
468 * @param {!Array.<!WebInspector.DataGrid.ColumnDescriptor>=} columns
469 */
470WebInspector.HeapSnapshotContainmentDataGrid = function(columns)
471{
472    columns = columns || [
473        {id: "object", title: WebInspector.UIString("Object"), disclosure: true, sortable: true},
474        {id: "distance", title: WebInspector.UIString("Distance"), width: "80px", sortable: true},
475        {id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true},
476        {id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sortable: true, sort: WebInspector.DataGrid.Order.Descending}
477    ];
478    WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
479}
480
481WebInspector.HeapSnapshotContainmentDataGrid.prototype = {
482    setDataSource: function(snapshot, nodeIndex)
483    {
484        this.snapshot = snapshot;
485        var node = new WebInspector.HeapSnapshotNode(snapshot, nodeIndex || snapshot.rootNodeIndex);
486        var fakeEdge = { node: node };
487        this.setRootNode(new WebInspector.HeapSnapshotObjectNode(this, false, fakeEdge, null));
488        this.rootNode().sort();
489    },
490
491    sortingChanged: function()
492    {
493        this.rootNode().sort();
494    },
495
496    __proto__: WebInspector.HeapSnapshotSortableDataGrid.prototype
497}
498
499
500/**
501 * @constructor
502 * @extends {WebInspector.HeapSnapshotContainmentDataGrid}
503 */
504WebInspector.HeapSnapshotRetainmentDataGrid = function()
505{
506    this.showRetainingEdges = true;
507    var columns = [
508        {id: "object", title: WebInspector.UIString("Object"), disclosure: true, sortable: true},
509        {id: "distance", title: WebInspector.UIString("Distance"), width: "80px", sortable: true, sort: WebInspector.DataGrid.Order.Ascending},
510        {id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true},
511        {id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sortable: true}
512    ];
513    WebInspector.HeapSnapshotContainmentDataGrid.call(this, columns);
514}
515
516WebInspector.HeapSnapshotRetainmentDataGrid.Events = {
517    ExpandRetainersComplete: "ExpandRetainersComplete"
518}
519
520WebInspector.HeapSnapshotRetainmentDataGrid.prototype = {
521    _sortFields: function(sortColumn, sortAscending)
522    {
523        return {
524            object: ["_name", sortAscending, "_count", false],
525            count: ["_count", sortAscending, "_name", true],
526            shallowSize: ["_shallowSize", sortAscending, "_name", true],
527            retainedSize: ["_retainedSize", sortAscending, "_name", true],
528            distance: ["_distance", sortAscending, "_name", true]
529        }[sortColumn];
530    },
531
532    reset: function()
533    {
534        this.rootNode().removeChildren();
535        this.resetSortingCache();
536    },
537
538    /**
539     * @param {!WebInspector.HeapSnapshotProxy} snapshot
540     * @param {number} nodeIndex
541     */
542    setDataSource: function(snapshot, nodeIndex)
543    {
544        WebInspector.HeapSnapshotContainmentDataGrid.prototype.setDataSource.call(this, snapshot, nodeIndex);
545
546        var dataGrid = this;
547        var maxExpandLevels = 20;
548        /**
549         * @this {!WebInspector.HeapSnapshotObjectNode}
550         */
551        function populateComplete()
552        {
553            this.removeEventListener(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete, populateComplete, this);
554            this.expand();
555            if (--maxExpandLevels > 0 && this.children.length > 0) {
556                var retainer = this.children[0];
557                if (retainer._distance > 1) {
558                    retainer.addEventListener(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete, populateComplete, retainer);
559                    retainer.populate();
560                    return;
561                }
562            }
563            dataGrid.dispatchEventToListeners(WebInspector.HeapSnapshotRetainmentDataGrid.Events.ExpandRetainersComplete);
564        }
565        this.rootNode().addEventListener(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete, populateComplete, this.rootNode());
566    },
567
568    __proto__: WebInspector.HeapSnapshotContainmentDataGrid.prototype
569}
570
571/**
572 * @constructor
573 * @extends {WebInspector.HeapSnapshotViewportDataGrid}
574 */
575WebInspector.HeapSnapshotConstructorsDataGrid = function()
576{
577    var columns = [
578        {id: "object", title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true},
579        {id: "distance", title: WebInspector.UIString("Distance"), width: "90px", sortable: true},
580        {id: "count", title: WebInspector.UIString("Objects Count"), width: "90px", sortable: true},
581        {id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true},
582        {id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true}
583    ];
584    WebInspector.HeapSnapshotViewportDataGrid.call(this, columns);
585    this._profileIndex = -1;
586    this._topLevelNodes = [];
587
588    this._objectIdToSelect = null;
589}
590
591/**
592 * @constructor
593 * @param {number=} minNodeId
594 * @param {number=} maxNodeId
595 */
596WebInspector.HeapSnapshotConstructorsDataGrid.Request = function(minNodeId, maxNodeId)
597{
598    if (typeof minNodeId === "number") {
599        this.key = minNodeId + ".." + maxNodeId;
600        this.filter = "function(node) { var id = node.id(); return id > " + minNodeId + " && id <= " + maxNodeId + "; }";
601    } else {
602        this.key = "allObjects";
603        this.filter = null;
604    }
605}
606
607WebInspector.HeapSnapshotConstructorsDataGrid.prototype = {
608    _sortFields: function(sortColumn, sortAscending)
609    {
610        return {
611            object: ["_name", sortAscending, "_count", false],
612            distance: ["_distance", sortAscending, "_retainedSize", true],
613            count: ["_count", sortAscending, "_name", true],
614            shallowSize: ["_shallowSize", sortAscending, "_name", true],
615            retainedSize: ["_retainedSize", sortAscending, "_name", true]
616        }[sortColumn];
617    },
618
619    /**
620     * @override
621     * @param {!HeapProfilerAgent.HeapSnapshotObjectId} id
622     * @param {function(boolean)} callback
623     */
624    highlightObjectByHeapSnapshotId: function(id, callback)
625    {
626        if (!this.snapshot) {
627            this._objectIdToSelect = id;
628            return;
629        }
630
631        /**
632         * @param {?string} className
633         * @this {WebInspector.HeapSnapshotConstructorsDataGrid}
634         */
635        function didGetClassName(className)
636        {
637            if (!className) {
638                callback(false);
639                return;
640            }
641            var constructorNodes = this.topLevelNodes();
642            for (var i = 0; i < constructorNodes.length; i++) {
643                var parent = constructorNodes[i];
644                if (parent._name === className) {
645                    if (!parent.dataGrid) {
646                        // Make sure Constructor node is within the view port and added
647                        // to the data grid
648                        this._revealTopLevelNode(parent);
649                    }
650                    parent.revealNodeBySnapshotObjectId(parseInt(id, 10), callback);
651                    return;
652                }
653            }
654        }
655        this.snapshot.nodeClassName(parseInt(id, 10), didGetClassName.bind(this));
656    },
657
658    setDataSource: function(snapshot)
659    {
660        this.snapshot = snapshot;
661        if (this._profileIndex === -1)
662            this._populateChildren();
663
664        if (this._objectIdToSelect) {
665            this.highlightObjectByHeapSnapshotId(this._objectIdToSelect, function(found) {});
666            this._objectIdToSelect = null;
667        }
668    },
669
670    /**
671      * @param {number} minNodeId
672      * @param {number} maxNodeId
673      */
674    setSelectionRange: function(minNodeId, maxNodeId)
675    {
676        this._populateChildren(new WebInspector.HeapSnapshotConstructorsDataGrid.Request(minNodeId, maxNodeId));
677    },
678
679    _aggregatesReceived: function(key, aggregates)
680    {
681        this._requestInProgress = null;
682        if (this._nextRequest) {
683            this.snapshot.aggregates(false, this._nextRequest.key, this._nextRequest.filter, this._aggregatesReceived.bind(this, this._nextRequest.key));
684            this._requestInProgress = this._nextRequest;
685            this._nextRequest = null;
686        }
687        this.dispose();
688        this.removeTopLevelNodes();
689        this.resetSortingCache();
690        for (var constructor in aggregates)
691            this.appendTopLevelNode(new WebInspector.HeapSnapshotConstructorNode(this, constructor, aggregates[constructor], key));
692        this.sortingChanged();
693        this._lastKey = key;
694    },
695
696    /**
697      * @param {?WebInspector.HeapSnapshotConstructorsDataGrid.Request=} request
698      */
699    _populateChildren: function(request)
700    {
701        request = request || new WebInspector.HeapSnapshotConstructorsDataGrid.Request();
702
703        if (this._requestInProgress) {
704            this._nextRequest = this._requestInProgress.key === request.key ? null : request;
705            return;
706        }
707        if (this._lastKey === request.key)
708            return;
709        this._requestInProgress = request;
710        this.snapshot.aggregates(false, request.key, request.filter, this._aggregatesReceived.bind(this, request.key));
711    },
712
713    filterSelectIndexChanged: function(profiles, profileIndex)
714    {
715        this._profileIndex = profileIndex;
716
717        var request = null;
718        if (profileIndex !== -1) {
719            var minNodeId = profileIndex > 0 ? profiles[profileIndex - 1].maxJSObjectId : 0;
720            var maxNodeId = profiles[profileIndex].maxJSObjectId;
721            request = new WebInspector.HeapSnapshotConstructorsDataGrid.Request(minNodeId, maxNodeId)
722        }
723
724        this._populateChildren(request);
725    },
726
727    __proto__: WebInspector.HeapSnapshotViewportDataGrid.prototype
728}
729
730
731/**
732 * @constructor
733 * @extends {WebInspector.HeapSnapshotViewportDataGrid}
734 */
735WebInspector.HeapSnapshotDiffDataGrid = function()
736{
737    var columns = [
738        {id: "object", title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true},
739        {id: "addedCount", title: WebInspector.UIString("# New"), width: "72px", sortable: true},
740        {id: "removedCount", title: WebInspector.UIString("# Deleted"), width: "72px", sortable: true},
741        {id: "countDelta", title: "# Delta", width: "64px", sortable: true},
742        {id: "addedSize", title: WebInspector.UIString("Alloc. Size"), width: "72px", sortable: true, sort: WebInspector.DataGrid.Order.Descending},
743        {id: "removedSize", title: WebInspector.UIString("Freed Size"), width: "72px", sortable: true},
744        {id: "sizeDelta", title: "Size Delta", width: "72px", sortable: true}
745    ];
746    WebInspector.HeapSnapshotViewportDataGrid.call(this, columns);
747}
748
749WebInspector.HeapSnapshotDiffDataGrid.prototype = {
750    /**
751     * @override
752     * @return {number}
753     */
754    defaultPopulateCount: function()
755    {
756        return 50;
757    },
758
759    _sortFields: function(sortColumn, sortAscending)
760    {
761        return {
762            object: ["_name", sortAscending, "_count", false],
763            addedCount: ["_addedCount", sortAscending, "_name", true],
764            removedCount: ["_removedCount", sortAscending, "_name", true],
765            countDelta: ["_countDelta", sortAscending, "_name", true],
766            addedSize: ["_addedSize", sortAscending, "_name", true],
767            removedSize: ["_removedSize", sortAscending, "_name", true],
768            sizeDelta: ["_sizeDelta", sortAscending, "_name", true]
769        }[sortColumn];
770    },
771
772    setDataSource: function(snapshot)
773    {
774        this.snapshot = snapshot;
775    },
776
777    /**
778     * @param {!WebInspector.HeapSnapshotProxy} baseSnapshot
779     */
780    setBaseDataSource: function(baseSnapshot)
781    {
782        this.baseSnapshot = baseSnapshot;
783        this.dispose();
784        this.removeTopLevelNodes();
785        this.resetSortingCache();
786        if (this.baseSnapshot === this.snapshot) {
787            this.dispatchEventToListeners("sorting complete");
788            return;
789        }
790        this._populateChildren();
791    },
792
793    _populateChildren: function()
794    {
795        /**
796         * @this {WebInspector.HeapSnapshotDiffDataGrid}
797         */
798        function aggregatesForDiffReceived(aggregatesForDiff)
799        {
800            this.snapshot.calculateSnapshotDiff(this.baseSnapshot.uid, aggregatesForDiff, didCalculateSnapshotDiff.bind(this));
801
802            /**
803             * @this {WebInspector.HeapSnapshotDiffDataGrid}
804             */
805            function didCalculateSnapshotDiff(diffByClassName)
806            {
807                for (var className in diffByClassName) {
808                    var diff = diffByClassName[className];
809                    this.appendTopLevelNode(new WebInspector.HeapSnapshotDiffNode(this, className, diff));
810                }
811                this.sortingChanged();
812            }
813        }
814        // Two snapshots live in different workers isolated from each other. That is why
815        // we first need to collect information about the nodes in the first snapshot and
816        // then pass it to the second snapshot to calclulate the diff.
817        this.baseSnapshot.aggregatesForDiff(aggregatesForDiffReceived.bind(this));
818    },
819
820    __proto__: WebInspector.HeapSnapshotViewportDataGrid.prototype
821}
822
823
824/**
825 * @constructor
826 * @extends {WebInspector.HeapSnapshotSortableDataGrid}
827 */
828WebInspector.HeapSnapshotDominatorsDataGrid = function()
829{
830    var columns = [
831        {id: "object", title: WebInspector.UIString("Object"), disclosure: true, sortable: true},
832        {id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true},
833        {id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true}
834    ];
835    WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
836    this._objectIdToSelect = null;
837}
838
839WebInspector.HeapSnapshotDominatorsDataGrid.prototype = {
840    /**
841     * @override
842     * @return {number}
843     */
844    defaultPopulateCount: function()
845    {
846        return 25;
847    },
848
849    setDataSource: function(snapshot)
850    {
851        this.snapshot = snapshot;
852
853        var fakeNode = { nodeIndex: this.snapshot.rootNodeIndex };
854        this.setRootNode(new WebInspector.HeapSnapshotDominatorObjectNode(this, fakeNode));
855        this.rootNode().sort();
856
857        if (this._objectIdToSelect) {
858            this.highlightObjectByHeapSnapshotId(this._objectIdToSelect, function(found) {});
859            this._objectIdToSelect = null;
860        }
861    },
862
863    sortingChanged: function()
864    {
865        this.rootNode().sort();
866    },
867
868    /**
869     * @override
870     * @param {!HeapProfilerAgent.HeapSnapshotObjectId} id
871     * @param {function(boolean)} callback
872     */
873    highlightObjectByHeapSnapshotId: function(id, callback)
874    {
875        if (!this.snapshot) {
876            this._objectIdToSelect = id;
877            callback(false);
878            return;
879        }
880
881        /**
882         * @this {WebInspector.HeapSnapshotDominatorsDataGrid}
883         */
884        function didGetDominators(dominatorIds)
885        {
886            if (!dominatorIds) {
887                WebInspector.log(WebInspector.UIString("Cannot find corresponding heap snapshot node"));
888                callback(false);
889                return;
890            }
891            var dominatorNode = this.rootNode();
892            expandNextDominator.call(this, dominatorIds, dominatorNode);
893        }
894
895        /**
896         * @this {WebInspector.HeapSnapshotDominatorsDataGrid}
897         */
898        function expandNextDominator(dominatorIds, dominatorNode)
899        {
900            if (!dominatorNode) {
901                console.error("Cannot find dominator node");
902                callback(false);
903                return;
904            }
905            if (!dominatorIds.length) {
906                this.highlightNode(dominatorNode);
907                dominatorNode.element.scrollIntoViewIfNeeded(true);
908                callback(true);
909                return;
910            }
911            var snapshotObjectId = dominatorIds.pop();
912            dominatorNode.retrieveChildBySnapshotObjectId(snapshotObjectId, expandNextDominator.bind(this, dominatorIds));
913        }
914
915        this.snapshot.dominatorIdsForNode(parseInt(id, 10), didGetDominators.bind(this));
916    },
917
918    __proto__: WebInspector.HeapSnapshotSortableDataGrid.prototype
919}
920
921
922/**
923 * @constructor
924 * @extends {WebInspector.DataGrid}
925 */
926WebInspector.AllocationDataGrid = function()
927{
928    var columns = [
929        {id: "count", title: WebInspector.UIString("Count"), width: "72px", sortable: true},
930        {id: "size", title: WebInspector.UIString("Size"), width: "72px", sortable: true, sort: WebInspector.DataGrid.Order.Descending},
931        {id: "name", title: WebInspector.UIString("Function"), disclosure: true, sortable: true},
932    ];
933    WebInspector.DataGrid.call(this, columns);
934    this._linkifier = new WebInspector.Linkifier();
935}
936
937WebInspector.AllocationDataGrid.prototype = {
938    setDataSource: function(snapshot)
939    {
940        this._snapshot = snapshot;
941        this._snapshot.allocationTracesTops(didReceiveAllocationTracesTops.bind(this));
942
943        /**
944         * @param {!Array.<!WebInspector.DataGrid>} tops
945         * @this {WebInspector.AllocationDataGrid}
946         */
947        function didReceiveAllocationTracesTops(tops)
948        {
949            var root = this.rootNode();
950            for (var i = 0; i < tops.length; i++)
951                root.appendChild(new WebInspector.AllocationGridNode(this, tops[i]));
952        }
953    },
954
955    __proto__: WebInspector.DataGrid.prototype
956}
957
958
959/**
960 * @constructor
961 * @extends {WebInspector.DataGridNode}
962 * @param {!WebInspector.DataGrid} dataGrid
963 */
964WebInspector.AllocationGridNode = function(dataGrid, data)
965{
966    WebInspector.DataGridNode.call(this, data, data.hasChildren);
967    this._dataGrid = dataGrid;
968    this._populated = false;
969}
970
971WebInspector.AllocationGridNode.prototype = {
972    populate: function()
973    {
974        if (this._populated)
975            return;
976        this._populated = true;
977        this._dataGrid._snapshot.allocationNodeCallers(this.data.id, didReceiveCallers.bind(this));
978
979        /**
980         * @this {WebInspector.AllocationGridNode}
981         */
982        function didReceiveCallers(callers)
983        {
984            var callersChain = callers.nodesWithSingleCaller;
985            var parentNode = this;
986            for (var i = 0; i < callersChain.length; i++) {
987                var child = new WebInspector.AllocationGridNode(this._dataGrid, callersChain[i]);
988                parentNode.appendChild(child);
989                parentNode = child;
990                parentNode._populated = true;
991                if (this.expanded)
992                    parentNode.expand();
993            }
994
995            var callersBranch = callers.branchingCallers;
996            for (var i = 0; i < callersBranch.length; i++)
997                parentNode.appendChild(new WebInspector.AllocationGridNode(this._dataGrid, callersBranch[i]));
998        }
999    },
1000
1001    /**
1002     * @override
1003     */
1004    expand: function()
1005    {
1006        WebInspector.DataGridNode.prototype.expand.call(this);
1007        if (this.children.length === 1)
1008            this.children[0].expand();
1009    },
1010
1011    /**
1012     * @override
1013     * @param {string} columnIdentifier
1014     * @return {!Element}
1015     */
1016    createCell: function(columnIdentifier)
1017    {
1018        var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
1019
1020        if (columnIdentifier !== "name")
1021            return cell;
1022
1023        var functionInfo = this.data;
1024        if (functionInfo.scriptName) {
1025            var urlElement = this._dataGrid._linkifier.linkifyLocation(functionInfo.scriptName, functionInfo.line - 1, functionInfo.column - 1, "profile-node-file");
1026            urlElement.style.maxWidth = "75%";
1027            cell.insertBefore(urlElement, cell.firstChild);
1028        }
1029
1030        return cell;
1031    },
1032
1033    __proto__: WebInspector.DataGridNode.prototype
1034}
1035
1036