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