initial import
[vuplus_webkit] / Source / WebCore / inspector / front-end / DetailedHeapshotView.js
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 WebInspector.HeapSnapshotSortableDataGrid = function(columns)
32 {
33     WebInspector.DataGrid.call(this, columns);
34     this.addEventListener("sorting changed", this.sortingChanged, this);
35 }
36
37 WebInspector.HeapSnapshotSortableDataGrid.prototype = {
38     dispose: function()
39     {
40         for (var i = 0, l = this.children.length; i < l; ++i)
41             this.children[i].dispose();
42     },
43
44     resetSortingCache: function()
45     {
46         delete this._lastSortColumnIdentifier;
47         delete this._lastSortAscending;
48     },
49
50     sortingChanged: function()
51     {
52         var sortAscending = this.sortOrder === "ascending";
53         var sortColumnIdentifier = this.sortColumnIdentifier;
54         if (this._lastSortColumnIdentifier === sortColumnIdentifier && this._lastSortAscending === sortAscending)
55             return;
56         this._lastSortColumnIdentifier = sortColumnIdentifier;
57         this._lastSortAscending = sortAscending;
58         var sortFields = this._sortFields(sortColumnIdentifier, sortAscending);
59
60         function SortByTwoFields(nodeA, nodeB)
61         {
62             var field1 = nodeA[sortFields[0]];
63             var field2 = nodeB[sortFields[0]];
64             var result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
65             if (!sortFields[1])
66                 result = -result;
67             if (result !== 0)
68                 return result;
69             field1 = nodeA[sortFields[2]];
70             field2 = nodeB[sortFields[2]];
71             result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
72             if (!sortFields[3])
73                 result = -result;
74             return result;
75         }
76         this._performSorting(SortByTwoFields);
77     },
78
79     _performSorting: function(sortFunction)
80     {
81         this.recursiveSortingEnter();
82         var children = this.children;
83         this.removeChildren();
84         children.sort(sortFunction);
85         for (var i = 0, l = children.length; i < l; ++i) {
86             var child = children[i];
87             this.appendChild(child);
88             if (child.expanded)
89                 child.sort();
90         }
91         this.recursiveSortingLeave();
92     },
93
94     recursiveSortingEnter: function()
95     {
96         if (!("_recursiveSortingDepth" in this))
97             this._recursiveSortingDepth = 1;
98         else
99             ++this._recursiveSortingDepth;
100     },
101
102     recursiveSortingLeave: function()
103     {
104         if (!("_recursiveSortingDepth" in this))
105             return;
106         if (!--this._recursiveSortingDepth) {
107             delete this._recursiveSortingDepth;
108             this.dispatchEventToListeners("sorting complete");
109         }
110     }
111 };
112
113 WebInspector.HeapSnapshotSortableDataGrid.prototype.__proto__ = WebInspector.DataGrid.prototype;
114
115 WebInspector.HeapSnapshotContainmentDataGrid = function()
116 {
117     var columns = {
118         object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true, sort: "ascending" },
119         shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true },
120         retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sortable: true }
121     };
122     WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
123 }
124
125 WebInspector.HeapSnapshotContainmentDataGrid.prototype = {
126     _defaultPopulateCount: 100,
127
128     expandRoute: function(route)
129     {
130         function nextStep(parent, hopIndex)
131         {
132             if (hopIndex >= route.length) {
133                 parent.element.scrollIntoViewIfNeeded(true);
134                 parent.select();
135                 return;
136             }
137             var nodeIndex = route[hopIndex];
138             for (var i = 0, l = parent.children.length; i < l; ++i) {
139                 var child = parent.children[i];
140                 if (child.snapshotNodeIndex === nodeIndex) {
141                     if (child.expanded)
142                         nextStep(child, hopIndex + 1);
143                     else {
144                         function afterExpand()
145                         {
146                             child.removeEventListener("populate complete", afterExpand, null);
147                             var lastChild = child.children[child.children.length - 1];
148                             if (!lastChild.showAll)
149                                 nextStep(child, hopIndex + 1);
150                             else {
151                                 child.addEventListener("populate complete", afterExpand, null);
152                                 lastChild.showAll.click();
153                             }
154                         }
155                         child.addEventListener("populate complete", afterExpand, null);
156                         child.expand();
157                     }
158                     break;
159                 }
160             }
161         }
162         nextStep(this, 0);
163     },
164
165     setDataSource: function(snapshotView, snapshot)
166     {
167         this.snapshotView = snapshotView;
168         this.snapshot = snapshot;
169         this.snapshotNodeIndex = this.snapshot.rootNodeIndex;
170         this._provider = this._createProvider(snapshot, this.snapshotNodeIndex);
171         this.sort();
172     },
173
174     sortingChanged: function()
175     {
176         this.sort();
177     }
178 };
179
180 MixInSnapshotNodeFunctions(WebInspector.HeapSnapshotObjectNode.prototype, WebInspector.HeapSnapshotContainmentDataGrid.prototype);
181 WebInspector.HeapSnapshotContainmentDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
182
183 WebInspector.HeapSnapshotConstructorsDataGrid = function()
184 {
185     var columns = {
186         object: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
187         count: { title: WebInspector.UIString("#"), width: "45px", sortable: true },
188         shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true },
189         retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sort: "descending", sortable: true }
190     };
191     WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
192 }
193
194 WebInspector.HeapSnapshotConstructorsDataGrid.prototype = {
195     _defaultPopulateCount: 100,
196
197     _sortFields: function(sortColumn, sortAscending)
198     {
199         return {
200             object: ["_name", sortAscending, "_count", false],
201             count: ["_count", sortAscending, "_name", true],
202             shallowSize: ["_shallowSize", sortAscending, "_name", true],
203             retainedSize: ["_retainedSize", sortAscending, "_name", true]
204         }[sortColumn];
205     },
206
207     setDataSource: function(snapshotView, snapshot)
208     {
209         this.snapshotView = snapshotView;
210         this.snapshot = snapshot;
211         this.populateChildren();
212     },
213
214     populateChildren: function()
215     {
216         function aggregatesReceived(aggregates)
217         {
218             for (var constructor in aggregates)
219                 this.appendChild(new WebInspector.HeapSnapshotConstructorNode(this, constructor, aggregates[constructor]));
220             this.sortingChanged();
221         }
222         this.snapshot.aggregates(false, aggregatesReceived.bind(this));
223     }
224 };
225
226 WebInspector.HeapSnapshotConstructorsDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
227
228 WebInspector.HeapSnapshotDiffDataGrid = function()
229 {
230     var columns = {
231         object: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
232         addedCount: { title: WebInspector.UIString("# New"), width: "72px", sortable: true, sort: "descending" },
233         removedCount: { title: WebInspector.UIString("# Deleted"), width: "72px", sortable: true },
234         // \u0394 is a Greek delta letter.
235         countDelta: { title: "\u0394", width: "40px", sortable: true },
236         addedSize: { title: WebInspector.UIString("Alloc. Size"), width: "72px", sortable: true },
237         removedSize: { title: WebInspector.UIString("Freed Size"), width: "72px", sortable: true },
238         sizeDelta: { title: "\u0394", width: "72px", sortable: true }
239     };
240     WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
241 }
242
243 WebInspector.HeapSnapshotDiffDataGrid.prototype = {
244     _defaultPopulateCount: 50,
245
246     _sortFields: function(sortColumn, sortAscending)
247     {
248         return {
249             object: ["_name", sortAscending, "_count", false],
250             addedCount: ["_addedCount", sortAscending, "_name", true],
251             removedCount: ["_removedCount", sortAscending, "_name", true],
252             countDelta: ["_countDelta", sortAscending, "_name", true],
253             addedSize: ["_addedSize", sortAscending, "_name", true],
254             removedSize: ["_removedSize", sortAscending, "_name", true],
255             sizeDelta: ["_sizeDelta", sortAscending, "_name", true]
256         }[sortColumn];
257     },
258
259     setDataSource: function(snapshotView, snapshot)
260     {
261         this.snapshotView = snapshotView;
262         this.snapshot = snapshot;
263     },
264
265     setBaseDataSource: function(baseSnapshot)
266     {
267         this.baseSnapshot = baseSnapshot;
268         this.dispose();
269         this.removeChildren();
270         this.resetSortingCache();
271         if (this.baseSnapshot === this.snapshot) {
272             this.dispatchEventToListeners("sorting complete");
273             return;
274         }
275         this.populateChildren();
276     },
277
278     populateChildren: function()
279     {
280         function baseAggregatesReceived(baseClasses)
281         {
282             function aggregatesReceived(classes)
283             {
284                 var nodeCount = 0;
285                 var nodes = [];
286                 for (var clss in baseClasses)
287                     nodes.push(new WebInspector.HeapSnapshotDiffNode(this, clss, baseClasses[clss], classes[clss]));
288                 for (clss in classes) {
289                     if (!(clss in baseClasses))
290                         nodes.push(new WebInspector.HeapSnapshotDiffNode(this, clss, null, classes[clss]));
291                 }
292                 nodeCount = nodes.length;
293                 function addNodeIfNonZeroDiff(boundNode, zeroDiff)
294                 {
295                     if (!zeroDiff)
296                         this.appendChild(boundNode);
297                     if (!--nodeCount)
298                         this.sortingChanged();
299                 }
300                 for (var i = 0, l = nodes.length; i < l; ++i) {
301                     var node = nodes[i];
302                     node.calculateDiff(this, addNodeIfNonZeroDiff.bind(this, node));
303                 }
304             }
305             this.snapshot.aggregates(true, aggregatesReceived.bind(this));
306         }
307         this.baseSnapshot.aggregates(true, baseAggregatesReceived.bind(this));
308     }
309 };
310
311 WebInspector.HeapSnapshotDiffDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
312
313 WebInspector.HeapSnapshotDominatorsDataGrid = function()
314 {
315     var columns = {
316         object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true },
317         shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true },
318         retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sort: "descending", sortable: true }
319     };
320     WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
321 }
322
323 WebInspector.HeapSnapshotDominatorsDataGrid.prototype = {
324     _defaultPopulateCount: 25,
325
326     setDataSource: function(snapshotView, snapshot)
327     {
328         this.snapshotView = snapshotView;
329         this.snapshot = snapshot;
330         this.snapshotNodeIndex = this.snapshot.rootNodeIndex;
331         this._provider = this._createProvider(snapshot, this.snapshotNodeIndex);
332         this.sort();
333     },
334
335     sortingChanged: function()
336     {
337         this.sort();
338     }
339 };
340
341 MixInSnapshotNodeFunctions(WebInspector.HeapSnapshotDominatorObjectNode.prototype, WebInspector.HeapSnapshotDominatorsDataGrid.prototype);
342 WebInspector.HeapSnapshotDominatorsDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
343
344 WebInspector.HeapSnapshotPathFinderState = function(snapshot, nodeIndex, rootFilter)
345 {
346     this._pathFinder = snapshot.createPathFinder(nodeIndex, !WebInspector.DetailedHeapshotView.prototype.showHiddenData);
347     this._pathFinder.updateRoots(rootFilter);
348     this._foundCount = 0;
349     this._foundCountMax = null;
350     this._totalFoundCount = 0;
351     this._cancelled = false;
352 }
353
354 WebInspector.HeapSnapshotPathFinderState.prototype = {
355     batchDone: function(status)
356     {
357     },
358
359     pathFound: function(path)
360     {
361     },
362
363     cancel: function()
364     {
365         this._cancelled = true;
366         this._pathFinder.dispose();
367     },
368
369     startBatch: function(count)
370     {
371         if (this._cancelled)
372             return;
373         this._foundCount = 0;
374         this._foundCountMax = count;
375         this._pathFinder.findNext(this._pathFound.bind(this));
376     },
377
378     _pathFound: function(result)
379     {
380         if (this._cancelled)
381             return;
382         if (result === null) {
383             if (!this._totalFoundCount)
384                 this.batchDone("no-paths-at-all");
385         } else if (result !== false) {
386             this.pathFound(result);
387             ++this._foundCount;
388             ++this._totalFoundCount;
389             if (this._foundCount < this._foundCountMax)
390                 this._pathFinder.findNext(this._pathFound.bind(this));
391             else
392                 this.batchDone("have-more-paths");
393         } else {
394             this.batchDone("no-more-paths");
395         }
396     }
397 };
398
399 WebInspector.HeapSnapshotRetainingPathsList = function()
400 {
401     var columns = {
402         path: { title: WebInspector.UIString("Retaining path"), sortable: true },
403         len: { title: WebInspector.UIString("Length"), width: "90px", sortable: true, sort: "ascending" }
404     };
405     WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
406     this._defaultPopulateCount = 100;
407     this._nodeIndex = null;
408     this._state = null;
409     this._prefix = null;
410 }
411
412 WebInspector.HeapSnapshotRetainingPathsList.prototype = {
413     dispose: function()
414     {
415         if (this._state)
416             this._state.cancel();
417     },
418
419     _sortFields: function(sortColumn, sortAscending)
420     {
421         return {
422             path: ["path", sortAscending, "len", true],
423             len: ["len", sortAscending, "path", true]
424         }[sortColumn];
425     },
426
427     _resetPaths: function()
428     {
429         var rootFilter = this.snapshotView.isTracingToWindowObjects ?
430             "function (node) { return node.name.substr(0, 9) === \"DOMWindow\"; }" : null;
431         if (this._state)
432             this._state.cancel();
433         this._state = new WebInspector.HeapSnapshotPathFinderState(this._snapshot, this._nodeIndex, rootFilter);
434         this._state.batchDone = this._batchDone.bind(this);
435         this._state.pathFound = this._pathFound.bind(this);
436         this.removeChildren();
437         this.resetSortingCache();
438         this.showNext(this._defaultPopulateCount);
439     },
440
441     setDataSource: function(snapshotView, snapshot, nodeIndex, prefix)
442     {
443         if (this._nodeIndex === nodeIndex)
444             return;
445         this.snapshotView = snapshotView;
446         this._snapshot = snapshot;
447         this._nodeIndex = nodeIndex;
448         this._prefix = prefix;
449         this._resetPaths();
450     },
451
452     refresh: function()
453     {
454         if (this.snapshotView)
455             this._resetPaths();
456     },
457
458     reset: function()
459     {
460         if (this._state)
461             this._state.cancel();
462         this.removeChildren();
463         this.resetSortingCache();
464         this.appendChild(new WebInspector.DataGridNode({path:WebInspector.UIString("Click on an object to show retaining paths"), len:""}, false));
465     },
466
467     _batchDone: function(state)
468     {
469         switch (state) {
470         case "no-paths-at-all":
471             this.appendChild(new WebInspector.DataGridNode({path:WebInspector.UIString("Can't find any paths."), len:""}, false));
472             break;
473         case "have-more-paths":
474             this.appendChild(new WebInspector.ShowMoreDataGridNode(this.showNext.bind(this), this._defaultPopulateCount));
475             this.resetSortingCache();
476             this.sortingChanged();
477             break;
478         case "no-more-paths":
479             // Nothing to do.
480             break;
481         }
482     },
483
484     _pathFound: function(result)
485     {
486         if (WebInspector.HeapSnapshotGenericObjectNode.prototype.isDOMWindow(result.path))
487             result.path = WebInspector.HeapSnapshotGenericObjectNode.prototype.shortenWindowURL(result.path, true);
488         if (this._prefix)
489             result.path = this._prefix + result.path;
490         var node = new WebInspector.DataGridNode(result, false);
491         node.route = result.route;
492         this.appendChild(node);
493     },
494
495     showNext: function(pathsCount)
496     {
497         this._state.startBatch(pathsCount);
498     },
499
500     _performSorting: function(sortFunction)
501     {
502         function DataExtractorWrapper(nodeA, nodeB)
503         {
504             return sortFunction(nodeA.data, nodeB.data);
505         }
506         this.recursiveSortingEnter();
507         this.sortNodes(DataExtractorWrapper);
508         this.recursiveSortingLeave();
509     }
510 };
511
512 WebInspector.HeapSnapshotRetainingPathsList.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
513
514 WebInspector.DetailedHeapshotView = function(parent, profile)
515 {
516     WebInspector.View.call(this);
517
518     this.element.addStyleClass("detailed-heapshot-view");
519
520     this.parent = parent;
521     this.parent.addEventListener("profile added", this._updateBaseOptions, this);
522
523     this.showCountAsPercent = false;
524     this.showShallowSizeAsPercent = false;
525     this.showRetainedSizeAsPercent = false;
526
527     this.viewsContainer = document.createElement("div");
528     this.viewsContainer.addStyleClass("views-container");
529     this.element.appendChild(this.viewsContainer);
530
531     this.containmentView = new WebInspector.View();
532     this.containmentView.element.addStyleClass("view");
533     this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid();
534     this.containmentDataGrid.element.addEventListener("click", this._mouseClickInContentsGrid.bind(this), true);
535     this.containmentDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
536     this.containmentView.element.appendChild(this.containmentDataGrid.element);
537     this.viewsContainer.appendChild(this.containmentView.element);
538
539     this.constructorsView = new WebInspector.View();
540     this.constructorsView.element.addStyleClass("view");
541     this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid();
542     this.constructorsDataGrid.element.addEventListener("click", this._mouseClickInContentsGrid.bind(this), true);
543     this.constructorsDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
544     this.constructorsView.element.appendChild(this.constructorsDataGrid.element);
545     this.viewsContainer.appendChild(this.constructorsView.element);
546
547     this.diffView = new WebInspector.View();
548     this.diffView.element.addStyleClass("view");
549     this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid();
550     this.diffDataGrid.element.addEventListener("click", this._mouseClickInContentsGrid.bind(this), true);
551     this.diffView.element.appendChild(this.diffDataGrid.element);
552     this.viewsContainer.appendChild(this.diffView.element);
553
554     this.dominatorView = new WebInspector.View();
555     this.dominatorView.element.addStyleClass("view");
556     this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid();
557     this.dominatorDataGrid.element.addEventListener("click", this._mouseClickInContentsGrid.bind(this), true);
558     this.dominatorDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
559     this.dominatorView.element.appendChild(this.dominatorDataGrid.element);
560     this.viewsContainer.appendChild(this.dominatorView.element);
561
562     this.retainmentViewHeader = document.createElement("div");
563     this.retainmentViewHeader.addStyleClass("retainers-view-header");
564     this.retainmentViewHeader.addEventListener("mousedown", this._startRetainersHeaderDragging.bind(this), true);
565     var retainingPathsTitleDiv = document.createElement("div");
566     retainingPathsTitleDiv.className = "title";
567     var retainingPathsTitle = document.createElement("span");
568     retainingPathsTitle.textContent = WebInspector.UIString("Paths from the selected object");
569     this.retainingPathsRoot = document.createElement("select");
570     this.retainingPathsRoot.className = "status-bar-item";
571     this.retainingPathsRoot.addEventListener("change", this._changeRetainingPathsRoot.bind(this), false);
572     var toGCRootsTraceOption = document.createElement("option");
573     toGCRootsTraceOption.label = WebInspector.UIString("to GC roots");
574     var toWindowObjectsTraceOption = document.createElement("option");
575     toWindowObjectsTraceOption.label = WebInspector.UIString("to window objects");
576     this.retainingPathsRoot.appendChild(toGCRootsTraceOption);
577     this.retainingPathsRoot.appendChild(toWindowObjectsTraceOption);
578     retainingPathsTitleDiv.appendChild(retainingPathsTitle);
579     retainingPathsTitleDiv.appendChild(this.retainingPathsRoot);
580     this.retainmentViewHeader.appendChild(retainingPathsTitleDiv);
581     this.element.appendChild(this.retainmentViewHeader);
582
583     this.retainmentView = new WebInspector.View();
584     this.retainmentView.element.addStyleClass("view");
585     this.retainmentView.element.addStyleClass("retaining-paths-view");
586     this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainingPathsList();
587     this.retainmentDataGrid.element.addEventListener("click", this._mouseClickInRetainmentGrid.bind(this), true);
588     this.retainmentView.element.appendChild(this.retainmentDataGrid.element);
589     this.retainmentView.visible = true;
590     this.element.appendChild(this.retainmentView.element);
591     this.retainmentDataGrid.reset();
592
593     this.dataGrid = this.constructorsDataGrid;
594     this.currentView = this.constructorsView;
595
596     this.viewSelectElement = document.createElement("select");
597     this.viewSelectElement.className = "status-bar-item";
598     this.viewSelectElement.addEventListener("change", this._changeView.bind(this), false);
599
600     this.views = [{title: "Summary", view: this.constructorsView, grid: this.constructorsDataGrid},
601                   {title: "Comparison", view: this.diffView, grid: this.diffDataGrid},
602                   {title: "Containment", view: this.containmentView, grid: this.containmentDataGrid},
603                   {title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid}];
604     this.views.current = 0;
605     for (var i = 0; i < this.views.length; ++i) {
606         var view = this.views[i];
607         var option = document.createElement("option");
608         option.label = WebInspector.UIString(view.title);
609         this.viewSelectElement.appendChild(option);
610     }
611
612     this._profileUid = profile.uid;
613
614     this.baseSelectElement = document.createElement("select");
615     this.baseSelectElement.className = "status-bar-item hidden";
616     this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
617     this._updateBaseOptions();
618
619     this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
620     this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);
621     this.helpButton = new WebInspector.StatusBarButton("", "heapshot-help-status-bar-item status-bar-item");
622     this.helpButton.addEventListener("click", this._helpClicked.bind(this), false);
623
624     var popoverHelper = new WebInspector.PopoverHelper(this.element, this._getHoverAnchor.bind(this), this._showStringContentPopover.bind(this));
625
626     this._loadProfile(this._profileUid, profileCallback.bind(this));
627
628     function profileCallback()
629     {
630         var list = this._profiles();
631         var profileIndex;
632         for (var i = 0; i < list.length; ++i)
633             if (list[i].uid === this._profileUid) {
634                 profileIndex = i;
635                 break;
636             }
637         if (profileIndex > 0)
638             this.baseSelectElement.selectedIndex = profileIndex - 1;
639         else
640             this.baseSelectElement.selectedIndex = profileIndex;
641         this.dataGrid.setDataSource(this, this.profileWrapper);
642         this._updatePercentButton();
643     }
644 }
645
646 WebInspector.DetailedHeapshotView.prototype = {
647     dispose: function()
648     {
649         this.profileWrapper.dispose();
650         if (this.baseProfile)
651             this.baseProfileWrapper.dispose();
652         this.containmentDataGrid.dispose();
653         this.constructorsDataGrid.dispose();
654         this.diffDataGrid.dispose();
655         this.dominatorDataGrid.dispose();
656         this.retainmentDataGrid.dispose();
657     },
658
659     get statusBarItems()
660     {
661         return [this.viewSelectElement, this.baseSelectElement, this.percentButton.element, this.helpButton.element];
662     },
663
664     get profile()
665     {
666         return this.parent.getProfile(WebInspector.DetailedHeapshotProfileType.TypeId, this._profileUid);
667     },
668
669     get profileWrapper()
670     {
671         return this.profile.proxy;
672     },
673
674     get baseProfile()
675     {
676         return this.parent.getProfile(WebInspector.DetailedHeapshotProfileType.TypeId, this._baseProfileUid);
677     },
678
679     get baseProfileWrapper()
680     {
681         return this.baseProfile.proxy;
682     },
683
684     show: function(parentElement)
685     {
686         WebInspector.View.prototype.show.call(this, parentElement);
687         if (!this.profileWrapper.loaded)
688             this._loadProfile(this._profileUid, profileCallback1.bind(this));
689         else
690             profileCallback1.call(this);
691
692         function profileCallback1() {
693             if (this.baseProfile && !this.baseProfileWrapper.loaded)
694                 this._loadProfile(this._baseProfileUid, profileCallback2.bind(this));
695             else
696                 profileCallback2.call(this);
697         }
698
699         function profileCallback2() {
700             this.currentView.show();
701             this.dataGrid.updateWidths();
702         }
703     },
704
705     hide: function()
706     {
707         WebInspector.View.prototype.hide.call(this);
708         this._currentSearchResultIndex = -1;
709     },
710
711     onResize: function()
712     {
713         if (this.dataGrid)
714             this.dataGrid.updateWidths();
715
716         var height = this.retainmentView.element.clientHeight;
717         this._updateRetainmentViewHeight(height);
718     },
719
720     refreshShowAsPercents: function()
721     {
722         this._updatePercentButton();
723         this.refreshVisibleData();
724     },
725
726     searchCanceled: function()
727     {
728         if (this._searchResults) {
729             for (var i = 0; i < this._searchResults.length; ++i) {
730                 var node = this._searchResults[i].node;
731                 delete node._searchMatched;
732                 node.refresh();
733             }
734         }
735
736         delete this._searchFinishedCallback;
737         this._currentSearchResultIndex = -1;
738         this._searchResults = [];
739     },
740
741     performSearch: function(query, finishedCallback)
742     {
743         // Call searchCanceled since it will reset everything we need before doing a new search.
744         this.searchCanceled();
745
746         query = query.trim();
747
748         if (!query.length)
749             return;
750         if (this.currentView !== this.constructorsView && this.currentView !== this.diffView)
751             return;
752
753         this._searchFinishedCallback = finishedCallback;
754
755         function matchesByName(gridNode) {
756             return ("name" in gridNode) && gridNode.name.hasSubstring(query, true);
757         }
758
759         function matchesById(gridNode) {
760             return ("snapshotNodeId" in gridNode) && gridNode.snapshotNodeId === query;
761         }
762
763         var matchPredicate;
764         if (query.charAt(0) !== "@")
765             matchPredicate = matchesByName;
766         else {
767             query = parseInt(query.substring(1), 10);
768             matchPredicate = matchesById;
769         }
770
771         function matchesQuery(gridNode)
772         {
773             delete gridNode._searchMatched;
774             if (matchPredicate(gridNode)) {
775                 gridNode._searchMatched = true;
776                 gridNode.refresh();
777                 return true;
778             }
779             return false;
780         }
781
782         var current = this.dataGrid.children[0];
783         var depth = 0;
784         var info = {};
785
786         // Restrict to type nodes and instances.
787         const maxDepth = 1;
788
789         while (current) {
790             if (matchesQuery(current))
791                 this._searchResults.push({ node: current });
792             current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
793             depth += info.depthChange;
794         }
795
796         finishedCallback(this, this._searchResults.length);
797     },
798
799     jumpToFirstSearchResult: function()
800     {
801         if (!this._searchResults || !this._searchResults.length)
802             return;
803         this._currentSearchResultIndex = 0;
804         this._jumpToSearchResult(this._currentSearchResultIndex);
805     },
806
807     jumpToLastSearchResult: function()
808     {
809         if (!this._searchResults || !this._searchResults.length)
810             return;
811         this._currentSearchResultIndex = (this._searchResults.length - 1);
812         this._jumpToSearchResult(this._currentSearchResultIndex);
813     },
814
815     jumpToNextSearchResult: function()
816     {
817         if (!this._searchResults || !this._searchResults.length)
818             return;
819         if (++this._currentSearchResultIndex >= this._searchResults.length)
820             this._currentSearchResultIndex = 0;
821         this._jumpToSearchResult(this._currentSearchResultIndex);
822     },
823
824     jumpToPreviousSearchResult: function()
825     {
826         if (!this._searchResults || !this._searchResults.length)
827             return;
828         if (--this._currentSearchResultIndex < 0)
829             this._currentSearchResultIndex = (this._searchResults.length - 1);
830         this._jumpToSearchResult(this._currentSearchResultIndex);
831     },
832
833     showingFirstSearchResult: function()
834     {
835         return (this._currentSearchResultIndex === 0);
836     },
837
838     showingLastSearchResult: function()
839     {
840         return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
841     },
842
843     _jumpToSearchResult: function(index)
844     {
845         var searchResult = this._searchResults[index];
846         if (!searchResult)
847             return;
848
849         var node = searchResult.node;
850         node.revealAndSelect();
851     },
852
853     refreshVisibleData: function()
854     {
855         var child = this.dataGrid.children[0];
856         while (child) {
857             child.refresh();
858             child = child.traverseNextNode(false, null, true);
859         }
860     },
861
862     _changeBase: function()
863     {
864         if (this._baseProfileUid === this._profiles()[this.baseSelectElement.selectedIndex].uid)
865             return;
866
867         this._baseProfileUid = this._profiles()[this.baseSelectElement.selectedIndex].uid;
868         this._loadProfile(this._baseProfileUid, baseProfileLoaded.bind(this));
869
870         function baseProfileLoaded()
871         {
872             delete this._baseProfileWrapper;
873             this.baseProfile._lastShown = Date.now();
874             this.diffDataGrid.setBaseDataSource(this.baseProfileWrapper);
875         }
876
877         if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
878             return;
879
880         // The current search needs to be performed again. First negate out previous match
881         // count by calling the search finished callback with a negative number of matches.
882         // Then perform the search again with the same query and callback.
883         this._searchFinishedCallback(this, -this._searchResults.length);
884         this.performSearch(this.currentQuery, this._searchFinishedCallback);
885     },
886
887     _profiles: function()
888     {
889         return WebInspector.panels.profiles.getProfiles(WebInspector.DetailedHeapshotProfileType.TypeId);
890     },
891
892     _loadProfile: function(profileUid, callback)
893     {
894         WebInspector.panels.profiles.loadHeapSnapshot(profileUid, callback);
895     },
896
897     isDetailedSnapshot: function(snapshot)
898     {
899         var s = new WebInspector.HeapSnapshot(snapshot);
900         for (var iter = s.rootNode.edges; iter.hasNext(); iter.next())
901             if (iter.edge.node.name === "(GC roots)")
902                 return true;
903         return false;
904     },
905
906     processLoadedSnapshot: function(profile, snapshot)
907     {
908         profile.nodes = snapshot.nodes;
909         profile.strings = snapshot.strings;
910         var s = new WebInspector.HeapSnapshot(profile);
911         profile.sidebarElement.subtitle = Number.bytesToString(s.totalSize);
912     },
913
914     _mouseClickInContentsGrid: function(event)
915     {
916         var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
917         if (!cell || (!cell.hasStyleClass("object-column")))
918             return;
919         var row = event.target.enclosingNodeOrSelfWithNodeName("tr");
920         if (!row)
921             return;
922         var nodeItem = row._dataGridNode;
923         if (!nodeItem || nodeItem.isEventWithinDisclosureTriangle(event))
924             return;
925         if (nodeItem.snapshotNodeIndex)
926             this.retainmentDataGrid.setDataSource(this, nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex, nodeItem.isDeletedNode ? this.baseSelectElement.childNodes[this.baseSelectElement.selectedIndex].label + " | " : "");
927         else
928             this.retainmentDataGrid.reset();
929     },
930
931     _mouseDownInContentsGrid: function(event)
932     {
933         if (event.detail < 2)
934             return;
935
936         var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
937         if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("shallowSize-column") && !cell.hasStyleClass("retainedSize-column")))
938             return;
939
940         if (cell.hasStyleClass("count-column"))
941             this.showCountAsPercent = !this.showCountAsPercent;
942         else if (cell.hasStyleClass("shallowSize-column"))
943             this.showShallowSizeAsPercent = !this.showShallowSizeAsPercent;
944         else if (cell.hasStyleClass("retainedSize-column"))
945             this.showRetainedSizeAsPercent = !this.showRetainedSizeAsPercent;
946         this.refreshShowAsPercents();
947
948         event.preventDefault();
949         event.stopPropagation();
950     },
951
952     _mouseClickInRetainmentGrid: function(event)
953     {
954         var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
955         if (!cell || (!cell.hasStyleClass("path-column")))
956             return;
957         var row = event.target.enclosingNodeOrSelfWithNodeName("tr");
958         var nodeItem = row._dataGridNode;
959         if (!nodeItem || !nodeItem.route)
960             return;
961         function expandRoute()
962         {
963             this.dataGrid.expandRoute(nodeItem.route);
964         }
965         this.changeView("Containment", expandRoute.bind(this));
966     },
967
968     changeView: function(viewTitle, callback)
969     {
970         var viewIndex = null;
971         for (var i = 0; i < this.views.length; ++i)
972             if (this.views[i].title === viewTitle) {
973                 viewIndex = i;
974                 break;
975             }
976         if (this.views.current === viewIndex) {
977             setTimeout(callback, 0);
978             return;
979         }
980         var grid = this.views[viewIndex].grid;
981         function sortingComplete()
982         {
983             grid.removeEventListener("sorting complete", sortingComplete, this);
984             setTimeout(callback, 0);
985         }
986         this.views[viewIndex].grid.addEventListener("sorting complete", sortingComplete, this);
987         this.viewSelectElement.selectedIndex = viewIndex;
988         this._changeView({target: {selectedIndex: viewIndex}});
989     },
990
991     _changeView: function(event)
992     {
993         if (!event || !this._profileUid)
994             return;
995         if (event.target.selectedIndex === this.views.current)
996             return;
997
998         this.views.current = event.target.selectedIndex;
999         this.currentView.hide();
1000         var view = this.views[this.views.current];
1001         this.currentView = view.view;
1002         this.dataGrid = view.grid;
1003         this.currentView.show();
1004         this.refreshVisibleData();
1005         this.dataGrid.updateWidths();
1006         if (this.currentView === this.diffView) {
1007             this.baseSelectElement.removeStyleClass("hidden");
1008             if (!this.dataGrid.snapshotView) {
1009                 this.dataGrid.setDataSource(this, this.profileWrapper);
1010                 this._changeBase();
1011             }
1012         } else {
1013             this.baseSelectElement.addStyleClass("hidden");
1014             if (!this.dataGrid.snapshotView)
1015                 this.dataGrid.setDataSource(this, this.profileWrapper);
1016         }
1017
1018         if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
1019             return;
1020
1021         // The current search needs to be performed again. First negate out previous match
1022         // count by calling the search finished callback with a negative number of matches.
1023         // Then perform the search again the with same query and callback.
1024         this._searchFinishedCallback(this, -this._searchResults.length);
1025         this.performSearch(this.currentQuery, this._searchFinishedCallback);
1026     },
1027
1028     _changeRetainingPathsRoot: function(event)
1029     {
1030         if (!event)
1031             return;
1032         this.retainmentDataGrid.refresh();
1033     },
1034
1035     _getHoverAnchor: function(target)
1036     {
1037         var span = target.enclosingNodeOrSelfWithNodeName("span");
1038         if (!span)
1039             return;
1040         var row = target.enclosingNodeOrSelfWithNodeName("tr");
1041         if (!row)
1042             return;
1043         var gridNode = row._dataGridNode;
1044         if (!gridNode.hasHoverMessage)
1045             return;
1046         span.node = gridNode;
1047         return span;
1048     },
1049
1050     get isTracingToWindowObjects()
1051     {
1052         return this.retainingPathsRoot.selectedIndex === 1;
1053     },
1054
1055     get _isShowingAsPercent()
1056     {
1057         return this.showCountAsPercent && this.showShallowSizeAsPercent && this.showRetainedSizeAsPercent;
1058     },
1059
1060     _percentClicked: function(event)
1061     {
1062         var currentState = this._isShowingAsPercent;
1063         this.showCountAsPercent = !currentState;
1064         this.showShallowSizeAsPercent = !currentState;
1065         this.showRetainedSizeAsPercent = !currentState;
1066         this.refreshShowAsPercents();
1067     },
1068
1069     _showStringContentPopover: function(anchor, popover)
1070     {
1071         var stringContentElement = document.createElement("span");
1072         stringContentElement.className = "monospace";
1073         stringContentElement.style.whiteSpace = "pre";
1074
1075         function displayString(name, className)
1076         {
1077             stringContentElement.textContent = name;
1078             stringContentElement.className += " " + className;
1079             popover.show(stringContentElement, anchor);
1080         }
1081         anchor.node.hoverMessage(displayString);
1082     },
1083
1084     _helpClicked: function(event)
1085     {
1086         if (!this.helpPopover) {
1087             var refTypes = ["a:", "console-formatted-name", WebInspector.UIString("property"),
1088                             "0:", "console-formatted-name", WebInspector.UIString("element"),
1089                             "a:", "console-formatted-number", WebInspector.UIString("context var"),
1090                             "a:", "console-formatted-null", WebInspector.UIString("system prop")];
1091             var objTypes = [" a ", "console-formatted-object", "Object",
1092                             "\"a\"", "console-formatted-string", "String",
1093                             "/a/", "console-formatted-string", "RegExp",
1094                             "a()", "console-formatted-function", "Function",
1095                             "a[]", "console-formatted-object", "Array",
1096                             "num", "console-formatted-number", "Number",
1097                             " a ", "console-formatted-null", "System"];
1098
1099             var contentElement = document.createElement("table");
1100             contentElement.className = "heapshot-help";
1101             var headerRow = document.createElement("tr");
1102             var propsHeader = document.createElement("th");
1103             propsHeader.textContent = WebInspector.UIString("Property types:");
1104             headerRow.appendChild(propsHeader);
1105             var objsHeader = document.createElement("th");
1106             objsHeader.textContent = WebInspector.UIString("Object types:");
1107             headerRow.appendChild(objsHeader);
1108             contentElement.appendChild(headerRow);
1109             var len = Math.max(refTypes.length, objTypes.length);
1110             for (var i = 0; i < len; i += 3) {
1111                 var row = document.createElement("tr");
1112                 var refCell = document.createElement("td");
1113                 if (refTypes[i])
1114                     appendHelp(refTypes, i, refCell);
1115                 row.appendChild(refCell);
1116                 var objCell = document.createElement("td");
1117                 if (objTypes[i])
1118                     appendHelp(objTypes, i, objCell);
1119                 row.appendChild(objCell);
1120                 contentElement.appendChild(row);
1121             }
1122             this.helpPopover = new WebInspector.Popover(contentElement);
1123
1124             function appendHelp(help, index, cell)
1125             {
1126                 var div = document.createElement("div");
1127                 div.className = "source-code event-properties";
1128                 var name = document.createElement("span");
1129                 name.textContent = help[index];
1130                 name.className = help[index + 1];
1131                 div.appendChild(name);
1132                 var desc = document.createElement("span");
1133                 desc.textContent = " " + help[index + 2];
1134                 div.appendChild(desc);
1135                 cell.appendChild(div);
1136             }
1137         }
1138         if (this.helpPopover.visible)
1139             this.helpPopover.hide();
1140         else
1141             this.helpPopover.show(this.helpButton.element);
1142     },
1143
1144     _startRetainersHeaderDragging: function(event)
1145     {
1146         if (!this.visible || event.target === this.retainingPathsRoot)
1147             return;
1148
1149         WebInspector.elementDragStart(this.retainmentViewHeader, this._retainersHeaderDragging.bind(this), this._endRetainersHeaderDragging.bind(this), event, "row-resize");
1150         this._previousDragPosition = event.pageY;
1151         event.stopPropagation();
1152     },
1153
1154     _retainersHeaderDragging: function(event)
1155     {
1156         var height = this.retainmentView.element.clientHeight;
1157         height += this._previousDragPosition - event.pageY;
1158         this._previousDragPosition = event.pageY;
1159         this._updateRetainmentViewHeight(height);
1160         event.preventDefault();
1161         event.stopPropagation();
1162     },
1163
1164     _endRetainersHeaderDragging: function(event)
1165     {
1166         WebInspector.elementDragEnd(event);
1167         delete this._previousDragPosition;
1168         event.stopPropagation();
1169     },
1170
1171     _updateRetainmentViewHeight: function(height)
1172     {
1173         height = Number.constrain(height, Preferences.minConsoleHeight, this.element.clientHeight - Preferences.minConsoleHeight);
1174         this.viewsContainer.style.bottom = (height + this.retainmentViewHeader.clientHeight) + "px";
1175         this.retainmentView.element.style.height = height + "px";
1176         this.retainmentViewHeader.style.bottom = height + "px";
1177     },
1178
1179     _updateBaseOptions: function()
1180     {
1181         var list = this._profiles();
1182         // We're assuming that snapshots can only be added.
1183         if (this.baseSelectElement.length === list.length)
1184             return;
1185
1186         for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) {
1187             var baseOption = document.createElement("option");
1188             var title = list[i].title;
1189             if (!title.indexOf(UserInitiatedProfileName))
1190                 title = WebInspector.UIString("Snapshot %d", title.substring(UserInitiatedProfileName.length + 1));
1191             baseOption.label = title;
1192             this.baseSelectElement.appendChild(baseOption);
1193         }
1194     },
1195
1196     _updatePercentButton: function()
1197     {
1198         if (this._isShowingAsPercent) {
1199             this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes.");
1200             this.percentButton.toggled = true;
1201         } else {
1202             this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages.");
1203             this.percentButton.toggled = false;
1204         }
1205     }
1206 };
1207
1208 WebInspector.DetailedHeapshotView.prototype.__proto__ = WebInspector.View.prototype;
1209
1210 WebInspector.DetailedHeapshotView.prototype.showHiddenData = true;
1211
1212 WebInspector.DetailedHeapshotProfileType = function()
1213 {
1214     WebInspector.ProfileType.call(this, WebInspector.DetailedHeapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS"));
1215 }
1216
1217 WebInspector.DetailedHeapshotProfileType.TypeId = "HEAP";
1218
1219 WebInspector.DetailedHeapshotProfileType.prototype = {
1220     get buttonTooltip()
1221     {
1222         return WebInspector.UIString("Take heap snapshot.");
1223     },
1224
1225     get buttonStyle()
1226     {
1227         return "heap-snapshot-status-bar-item status-bar-item";
1228     },
1229
1230     buttonClicked: function()
1231     {
1232         WebInspector.panels.profiles.takeHeapSnapshot();
1233     },
1234
1235     get welcomeMessage()
1236     {
1237         return WebInspector.UIString("Get a heap snapshot by pressing the %s button on the status bar.");
1238     },
1239
1240     createSidebarTreeElementForProfile: function(profile)
1241     {
1242         return new WebInspector.ProfileSidebarTreeElement(profile, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item");
1243     },
1244
1245     createView: function(profile)
1246     {
1247         return new WebInspector.DetailedHeapshotView(WebInspector.panels.profiles, profile);
1248     }
1249 }
1250
1251 WebInspector.DetailedHeapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;