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        if (node instanceof WebInspector.HeapSnapshotInstanceNode || node instanceof WebInspector.HeapSnapshotObjectNode) {
111            function revealInDominatorsView()
112            {
113                profilesPanel.showObject(node.snapshotNodeId, "Dominators");
114            }
115            contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInDominatorsView.bind(this));
116        } else if (node instanceof WebInspector.HeapSnapshotDominatorObjectNode) {
117            function revealInSummaryView()
118            {
119                profilesPanel.showObject(node.snapshotNodeId, "Summary");
120            }
121            contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInSummaryView.bind(this));
122        }
123    },
124
125    resetSortingCache: function()
126    {
127        delete this._lastSortColumnIdentifier;
128        delete this._lastSortAscending;
129    },
130
131    topLevelNodes: function()
132    {
133        return this.rootNode().children;
134    },
135
136    /**
137     * @param {HeapProfilerAgent.HeapSnapshotObjectId} heapSnapshotObjectId
138     */
139    highlightObjectByHeapSnapshotId: function(heapSnapshotObjectId)
140    {
141    },
142
143    /**
144     * @param {WebInspector.HeapSnapshotGridNode} node
145     */
146    highlightNode: function(node)
147    {
148        var prevNode = this._highlightedNode;
149        this._clearCurrentHighlight();
150        this._highlightedNode = node;
151        this._highlightedNode.element.addStyleClass("highlighted-row");
152        // If highlighted node hasn't changed reinsert it to make the highlight animation restart.
153        if (node === prevNode) {
154            var element = node.element;
155            var parent = element.parentElement;
156            var nextSibling = element.nextSibling;
157            parent.removeChild(element);
158            parent.insertBefore(element, nextSibling);
159        }
160    },
161
162    nodeWasDetached: function(node)
163    {
164        if (this._highlightedNode === node)
165            this._clearCurrentHighlight();
166    },
167
168    _clearCurrentHighlight: function()
169    {
170        if (!this._highlightedNode)
171            return
172        this._highlightedNode.element.removeStyleClass("highlighted-row");
173        this._highlightedNode = null;
174    },
175
176    changeNameFilter: function(filter)
177    {
178        filter = filter.toLowerCase();
179        var children = this.topLevelNodes();
180        for (var i = 0, l = children.length; i < l; ++i) {
181            var node = children[i];
182            if (node.depth === 0)
183                node.revealed = node._name.toLowerCase().indexOf(filter) !== -1;
184        }
185        this.updateVisibleNodes();
186    },
187
188    sortingChanged: function()
189    {
190        var sortAscending = this.isSortOrderAscending();
191        var sortColumnIdentifier = this.sortColumnIdentifier();
192        if (this._lastSortColumnIdentifier === sortColumnIdentifier && this._lastSortAscending === sortAscending)
193            return;
194        this._lastSortColumnIdentifier = sortColumnIdentifier;
195        this._lastSortAscending = sortAscending;
196        var sortFields = this._sortFields(sortColumnIdentifier, sortAscending);
197
198        function SortByTwoFields(nodeA, nodeB)
199        {
200            var field1 = nodeA[sortFields[0]];
201            var field2 = nodeB[sortFields[0]];
202            var result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
203            if (!sortFields[1])
204                result = -result;
205            if (result !== 0)
206                return result;
207            field1 = nodeA[sortFields[2]];
208            field2 = nodeB[sortFields[2]];
209            result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
210            if (!sortFields[3])
211                result = -result;
212            return result;
213        }
214        this._performSorting(SortByTwoFields);
215    },
216
217    _performSorting: function(sortFunction)
218    {
219        this.recursiveSortingEnter();
220        var children = this._topLevelNodes;
221        this.rootNode().removeChildren();
222        children.sort(sortFunction);
223        for (var i = 0, l = children.length; i < l; ++i) {
224            var child = children[i];
225            this.appendChildAfterSorting(child);
226            if (child.expanded)
227                child.sort();
228        }
229        this.updateVisibleNodes();
230        this.recursiveSortingLeave();
231    },
232
233    appendChildAfterSorting: function(child)
234    {
235        var revealed = child.revealed;
236        this.rootNode().appendChild(child);
237        child.revealed = revealed;
238    },
239
240    updateVisibleNodes: function()
241    {
242    },
243
244    recursiveSortingEnter: function()
245    {
246        ++this._recursiveSortingDepth;
247    },
248
249    recursiveSortingLeave: function()
250    {
251        if (!this._recursiveSortingDepth)
252            return;
253        if (!--this._recursiveSortingDepth)
254            this.dispatchEventToListeners("sorting complete");
255    },
256
257    __proto__: WebInspector.DataGrid.prototype
258}
259
260
261
262/**
263 * @constructor
264 * @extends {WebInspector.HeapSnapshotSortableDataGrid}
265 */
266WebInspector.HeapSnapshotViewportDataGrid = function(columns)
267{
268    WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
269    this.scrollContainer.addEventListener("scroll", this._onScroll.bind(this), true);
270    this._topLevelNodes = [];
271    this._topPadding = new WebInspector.HeapSnapshotPaddingNode();
272    this._bottomPadding = new WebInspector.HeapSnapshotPaddingNode();
273    /**
274     * @type {WebInspector.HeapSnapshotGridNode}
275     */
276    this._nodeToHighlightAfterScroll = null;
277}
278
279WebInspector.HeapSnapshotViewportDataGrid.prototype = {
280    topLevelNodes: function()
281    {
282        return this._topLevelNodes;
283    },
284
285    appendChildAfterSorting: function(child)
286    {
287        // Do nothing here, it will be added in updateVisibleNodes.
288    },
289
290    updateVisibleNodes: function()
291    {
292        var scrollTop = this.scrollContainer.scrollTop;
293
294        var viewPortHeight = this.scrollContainer.offsetHeight;
295
296        this._removePaddingRows();
297
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.rootNode().removeChildren();
313        // The height of the view port + invisible top part.
314        var heightToFill = viewPortHeight + (scrollTop - topPadding);
315        var filledHeight = 0;
316        while (i < children.length && filledHeight < heightToFill) {
317            if (children[i].revealed) {
318                this.rootNode().appendChild(children[i]);
319                filledHeight += children[i].nodeHeight();
320            }
321            ++i;
322        }
323
324        var bottomPadding = 0;
325        while (i < children.length) {
326            bottomPadding += children[i].nodeHeight();
327            ++i;
328        }
329
330        this._addPaddingRows(topPadding, bottomPadding);
331    },
332
333    appendTopLevelNode: function(node)
334    {
335        this._topLevelNodes.push(node);
336    },
337
338    removeTopLevelNodes: function()
339    {
340        this.rootNode().removeChildren();
341        this._topLevelNodes = [];
342    },
343
344    /**
345     * @override
346     * @param {WebInspector.HeapSnapshotGridNode} node
347     */
348    highlightNode: function(node)
349    {
350        if (this._isScrolledIntoView(node.element))
351            WebInspector.HeapSnapshotSortableDataGrid.prototype.highlightNode.call(this, node);
352        else {
353            node.element.scrollIntoViewIfNeeded(true);
354            this._nodeToHighlightAfterScroll = node;
355        }
356    },
357
358    _isScrolledIntoView: function(element)
359    {
360        var viewportTop = this.scrollContainer.scrollTop;
361        var viewportBottom = viewportTop + this.scrollContainer.clientHeight;
362        var elemTop = element.offsetTop
363        var elemBottom = elemTop + element.offsetHeight;
364        return elemBottom <= viewportBottom && elemTop >= viewportTop;
365    },
366
367    _addPaddingRows: function(top, bottom)
368    {
369        if (this._topPadding.element.parentNode !== this.dataTableBody)
370            this.dataTableBody.insertBefore(this._topPadding.element, this.dataTableBody.firstChild);
371        if (this._bottomPadding.element.parentNode !== this.dataTableBody)
372            this.dataTableBody.insertBefore(this._bottomPadding.element, this.dataTableBody.lastChild);
373        this._topPadding.setHeight(top);
374        this._bottomPadding.setHeight(bottom);
375    },
376
377    _removePaddingRows: function()
378    {
379        this._bottomPadding.removeFromTable();
380        this._topPadding.removeFromTable();
381    },
382
383    onResize: function()
384    {
385        WebInspector.HeapSnapshotSortableDataGrid.prototype.onResize.call(this);
386        this.updateVisibleNodes();
387    },
388
389    _onScroll: function(event)
390    {
391        this.updateVisibleNodes();
392
393        if (this._nodeToHighlightAfterScroll) {
394            WebInspector.HeapSnapshotSortableDataGrid.prototype.highlightNode.call(this, this._nodeToHighlightAfterScroll);
395            this._nodeToHighlightAfterScroll = null;
396        }
397    },
398
399    __proto__: WebInspector.HeapSnapshotSortableDataGrid.prototype
400}
401
402/**
403 * @constructor
404 */
405WebInspector.HeapSnapshotPaddingNode = function()
406{
407    this.element = document.createElement("tr");
408    this.element.addStyleClass("revealed");
409}
410
411WebInspector.HeapSnapshotPaddingNode.prototype = {
412   setHeight: function(height)
413   {
414       this.element.style.height = height + "px";
415   },
416   removeFromTable: function()
417   {
418        var parent = this.element.parentNode;
419        if (parent)
420            parent.removeChild(this.element);
421   }
422}
423
424
425/**
426 * @constructor
427 * @extends {WebInspector.HeapSnapshotSortableDataGrid}
428 * @param {Array.<!WebInspector.DataGrid.ColumnDescriptor>=} columns
429 */
430WebInspector.HeapSnapshotContainmentDataGrid = function(columns)
431{
432    columns = columns || [
433        {id: "object", title: WebInspector.UIString("Object"), disclosure: true, sortable: true},
434        {id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true},
435        {id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sortable: true, sort: WebInspector.DataGrid.Order.Descending}
436    ];
437    WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
438}
439
440WebInspector.HeapSnapshotContainmentDataGrid.prototype = {
441    setDataSource: function(snapshot, nodeIndex)
442    {
443        this.snapshot = snapshot;
444        var node = new WebInspector.HeapSnapshotNode(snapshot, nodeIndex || snapshot.rootNodeIndex);
445        var fakeEdge = { node: node };
446        this.setRootNode(new WebInspector.HeapSnapshotObjectNode(this, false, fakeEdge, null));
447        this.rootNode().sort();
448    },
449
450    sortingChanged: function()
451    {
452        this.rootNode().sort();
453    },
454
455    __proto__: WebInspector.HeapSnapshotSortableDataGrid.prototype
456}
457
458
459/**
460 * @constructor
461 * @extends {WebInspector.HeapSnapshotContainmentDataGrid}
462 */
463WebInspector.HeapSnapshotRetainmentDataGrid = function()
464{
465    this.showRetainingEdges = true;
466    var columns = [
467        {id: "object", title: WebInspector.UIString("Object"), disclosure: true, sortable: true},
468        {id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true},
469        {id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sortable: true},
470        {id: "distance", title: WebInspector.UIString("Distance"), width: "80px", sortable: true, sort: WebInspector.DataGrid.Order.Ascending}
471    ];
472    WebInspector.HeapSnapshotContainmentDataGrid.call(this, columns);
473}
474
475WebInspector.HeapSnapshotRetainmentDataGrid.Events = {
476    ExpandRetainersComplete: "ExpandRetainersComplete"
477}
478
479WebInspector.HeapSnapshotRetainmentDataGrid.prototype = {
480    _sortFields: function(sortColumn, sortAscending)
481    {
482        return {
483            object: ["_name", sortAscending, "_count", false],
484            count: ["_count", sortAscending, "_name", true],
485            shallowSize: ["_shallowSize", sortAscending, "_name", true],
486            retainedSize: ["_retainedSize", sortAscending, "_name", true],
487            distance: ["_distance", sortAscending, "_name", true]
488        }[sortColumn];
489    },
490
491    reset: function()
492    {
493        this.rootNode().removeChildren();
494        this.resetSortingCache();
495    },
496
497    /**
498     * @param {!WebInspector.HeapSnapshotProxy} snapshot
499     * @param {number} nodeIndex
500     */
501    setDataSource: function(snapshot, nodeIndex)
502    {
503        WebInspector.HeapSnapshotContainmentDataGrid.prototype.setDataSource.call(this, snapshot, nodeIndex);
504
505        var dataGrid = this;
506        var maxExpandLevels = 20;
507        /**
508         * @this {!WebInspector.HeapSnapshotObjectNode}
509         */
510        function populateComplete()
511        {
512            this.removeEventListener(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete, populateComplete, this);
513            this.expand();
514            if (this.children.length === 1 && --maxExpandLevels > 0) {
515                var child = this.children[0];
516                child.addEventListener(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete, populateComplete, child);
517                child.populate();
518            } else
519                dataGrid.dispatchEventToListeners(WebInspector.HeapSnapshotRetainmentDataGrid.Events.ExpandRetainersComplete);
520        }
521        this.rootNode().addEventListener(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete, populateComplete, this.rootNode());
522    },
523
524    __proto__: WebInspector.HeapSnapshotContainmentDataGrid.prototype
525}
526
527
528/**
529 * @constructor
530 * @extends {WebInspector.HeapSnapshotViewportDataGrid}
531 */
532WebInspector.HeapSnapshotConstructorsDataGrid = function()
533{
534    var columns = [
535        {id: "object", title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true},
536        {id: "distance", title: WebInspector.UIString("Distance"), width: "90px", sortable: true},
537        {id: "count", title: WebInspector.UIString("Objects Count"), width: "90px", sortable: true},
538        {id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true},
539        {id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true}
540    ];
541    WebInspector.HeapSnapshotViewportDataGrid.call(this, columns);
542    this._profileIndex = -1;
543    this._topLevelNodes = [];
544
545    this._objectIdToSelect = null;
546}
547
548WebInspector.HeapSnapshotConstructorsDataGrid.prototype = {
549    _sortFields: function(sortColumn, sortAscending)
550    {
551        return {
552            object: ["_name", sortAscending, "_count", false],
553            distance: ["_distance", sortAscending, "_retainedSize", true],
554            count: ["_count", sortAscending, "_name", true],
555            shallowSize: ["_shallowSize", sortAscending, "_name", true],
556            retainedSize: ["_retainedSize", sortAscending, "_name", true]
557        }[sortColumn];
558    },
559
560    /**
561     * @override
562     * @param {HeapProfilerAgent.HeapSnapshotObjectId} id
563     */
564    highlightObjectByHeapSnapshotId: function(id)
565    {
566        if (!this.snapshot) {
567            this._objectIdToSelect = id;
568            return;
569        }
570
571        function didGetClassName(className)
572        {
573            var constructorNodes = this.topLevelNodes();
574            for (var i = 0; i < constructorNodes.length; i++) {
575                var parent = constructorNodes[i];
576                if (parent._name === className) {
577                    parent.revealNodeBySnapshotObjectId(parseInt(id, 10));
578                    return;
579                }
580            }
581        }
582        this.snapshot.nodeClassName(parseInt(id, 10), didGetClassName.bind(this));
583    },
584
585    setDataSource: function(snapshot)
586    {
587        this.snapshot = snapshot;
588        if (this._profileIndex === -1)
589            this._populateChildren();
590
591        if (this._objectIdToSelect) {
592            this.highlightObjectByHeapSnapshotId(this._objectIdToSelect);
593            this._objectIdToSelect = null;
594        }
595    },
596
597    _aggregatesReceived: function(key, aggregates)
598    {
599        for (var constructor in aggregates)
600            this.appendTopLevelNode(new WebInspector.HeapSnapshotConstructorNode(this, constructor, aggregates[constructor], key));
601        this.sortingChanged();
602    },
603
604    _populateChildren: function()
605    {
606
607        this.dispose();
608        this.removeTopLevelNodes();
609        this.resetSortingCache();
610
611        var key = this._profileIndex === -1 ? "allObjects" : this._minNodeId + ".." + this._maxNodeId;
612        var filter = this._profileIndex === -1 ? null : "function(node) { var id = node.id(); return id > " + this._minNodeId + " && id <= " + this._maxNodeId + "; }";
613
614        this.snapshot.aggregates(false, key, filter, this._aggregatesReceived.bind(this, key));
615    },
616
617    filterSelectIndexChanged: function(profiles, profileIndex)
618    {
619        this._profileIndex = profileIndex;
620
621        delete this._maxNodeId;
622        delete this._minNodeId;
623
624        if (this._profileIndex !== -1) {
625            this._minNodeId = profileIndex > 0 ? profiles[profileIndex - 1].maxJSObjectId : 0;
626            this._maxNodeId = profiles[profileIndex].maxJSObjectId;
627        }
628
629        this._populateChildren();
630    },
631
632    __proto__: WebInspector.HeapSnapshotViewportDataGrid.prototype
633}
634
635
636/**
637 * @constructor
638 * @extends {WebInspector.HeapSnapshotViewportDataGrid}
639 */
640WebInspector.HeapSnapshotDiffDataGrid = function()
641{
642    var columns = [
643        {id: "object", title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true},
644        {id: "addedCount", title: WebInspector.UIString("# New"), width: "72px", sortable: true},
645        {id: "removedCount", title: WebInspector.UIString("# Deleted"), width: "72px", sortable: true},
646        {id: "countDelta", title: "# Delta", width: "64px", sortable: true},
647        {id: "addedSize", title: WebInspector.UIString("Alloc. Size"), width: "72px", sortable: true, sort: WebInspector.DataGrid.Order.Descending},
648        {id: "removedSize", title: WebInspector.UIString("Freed Size"), width: "72px", sortable: true},
649        {id: "sizeDelta", title: "Size Delta", width: "72px", sortable: true}
650    ];
651    WebInspector.HeapSnapshotViewportDataGrid.call(this, columns);
652}
653
654WebInspector.HeapSnapshotDiffDataGrid.prototype = {
655    /**
656     * @override
657     * @return {number}
658     */
659    defaultPopulateCount: function()
660    {
661        return 50;
662    },
663
664    _sortFields: function(sortColumn, sortAscending)
665    {
666        return {
667            object: ["_name", sortAscending, "_count", false],
668            addedCount: ["_addedCount", sortAscending, "_name", true],
669            removedCount: ["_removedCount", sortAscending, "_name", true],
670            countDelta: ["_countDelta", sortAscending, "_name", true],
671            addedSize: ["_addedSize", sortAscending, "_name", true],
672            removedSize: ["_removedSize", sortAscending, "_name", true],
673            sizeDelta: ["_sizeDelta", sortAscending, "_name", true]
674        }[sortColumn];
675    },
676
677    setDataSource: function(snapshot)
678    {
679        this.snapshot = snapshot;
680    },
681
682    /**
683     * @param {WebInspector.HeapSnapshotProxy} baseSnapshot
684     */
685    setBaseDataSource: function(baseSnapshot)
686    {
687        this.baseSnapshot = baseSnapshot;
688        this.dispose();
689        this.removeTopLevelNodes();
690        this.resetSortingCache();
691        if (this.baseSnapshot === this.snapshot) {
692            this.dispatchEventToListeners("sorting complete");
693            return;
694        }
695        this._populateChildren();
696    },
697
698    _populateChildren: function()
699    {
700        function aggregatesForDiffReceived(aggregatesForDiff)
701        {
702            this.snapshot.calculateSnapshotDiff(this.baseSnapshot.uid, aggregatesForDiff, didCalculateSnapshotDiff.bind(this));
703            function didCalculateSnapshotDiff(diffByClassName)
704            {
705                for (var className in diffByClassName) {
706                    var diff = diffByClassName[className];
707                    this.appendTopLevelNode(new WebInspector.HeapSnapshotDiffNode(this, className, diff));
708                }
709                this.sortingChanged();
710            }
711        }
712        // Two snapshots live in different workers isolated from each other. That is why
713        // we first need to collect information about the nodes in the first snapshot and
714        // then pass it to the second snapshot to calclulate the diff.
715        this.baseSnapshot.aggregatesForDiff(aggregatesForDiffReceived.bind(this));
716    },
717
718    __proto__: WebInspector.HeapSnapshotViewportDataGrid.prototype
719}
720
721
722/**
723 * @constructor
724 * @extends {WebInspector.HeapSnapshotSortableDataGrid}
725 */
726WebInspector.HeapSnapshotDominatorsDataGrid = function()
727{
728    var columns = [
729        {id: "object", title: WebInspector.UIString("Object"), disclosure: true, sortable: true},
730        {id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true},
731        {id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true}
732    ];
733    WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
734    this._objectIdToSelect = null;
735}
736
737WebInspector.HeapSnapshotDominatorsDataGrid.prototype = {
738    /**
739     * @override
740     * @return {number}
741     */
742    defaultPopulateCount: function()
743    {
744        return 25;
745    },
746
747    setDataSource: function(snapshot)
748    {
749        this.snapshot = snapshot;
750
751        var fakeNode = { nodeIndex: this.snapshot.rootNodeIndex };
752        this.setRootNode(new WebInspector.HeapSnapshotDominatorObjectNode(this, fakeNode));
753        this.rootNode().sort();
754
755        if (this._objectIdToSelect) {
756            this.highlightObjectByHeapSnapshotId(this._objectIdToSelect);
757            this._objectIdToSelect = null;
758        }
759    },
760
761    sortingChanged: function()
762    {
763        this.rootNode().sort();
764    },
765
766    /**
767     * @override
768     * @param {HeapProfilerAgent.HeapSnapshotObjectId} id
769     */
770    highlightObjectByHeapSnapshotId: function(id)
771    {
772        if (!this.snapshot) {
773            this._objectIdToSelect = id;
774            return;
775        }
776
777        function didGetDominators(dominatorIds)
778        {
779            if (!dominatorIds) {
780                WebInspector.log(WebInspector.UIString("Cannot find corresponding heap snapshot node"));
781                return;
782            }
783            var dominatorNode = this.rootNode();
784            expandNextDominator.call(this, dominatorIds, dominatorNode);
785        }
786
787        function expandNextDominator(dominatorIds, dominatorNode)
788        {
789            if (!dominatorNode) {
790                console.error("Cannot find dominator node");
791                return;
792            }
793            if (!dominatorIds.length) {
794                this.highlightNode(dominatorNode);
795                dominatorNode.element.scrollIntoViewIfNeeded(true);
796                return;
797            }
798            var snapshotObjectId = dominatorIds.pop();
799            dominatorNode.retrieveChildBySnapshotObjectId(snapshotObjectId, expandNextDominator.bind(this, dominatorIds));
800        }
801
802        this.snapshot.dominatorIdsForNode(parseInt(id, 10), didGetDominators.bind(this));
803    },
804
805    __proto__: WebInspector.HeapSnapshotSortableDataGrid.prototype
806}
807
808