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 /**
     32  * @constructor
     33  * @extends {WebInspector.View}
     34  * @param {!WebInspector.ProfilesPanel} parent
     35  * @param {!WebInspector.HeapProfileHeader} profile
     36  */
     37 WebInspector.HeapSnapshotView = function(parent, profile)
     38 {
     39     WebInspector.View.call(this);
     40 
     41     this.element.addStyleClass("heap-snapshot-view");
     42 
     43     this.parent = parent;
     44     this.parent.addEventListener("profile added", this._onProfileHeaderAdded, this);
     45 
     46     if (profile._profileType.id === WebInspector.TrackingHeapSnapshotProfileType.TypeId) {
     47         this._trackingOverviewGrid = new WebInspector.HeapTrackingOverviewGrid(profile);
     48         this._trackingOverviewGrid.addEventListener(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, this._onIdsRangeChanged.bind(this));
     49         this._trackingOverviewGrid.show(this.element);
     50     }
     51 
     52     this.viewsContainer = document.createElement("div");
     53     this.viewsContainer.addStyleClass("views-container");
     54     this.element.appendChild(this.viewsContainer);
     55 
     56     this.containmentView = new WebInspector.View();
     57     this.containmentView.element.addStyleClass("view");
     58     this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid();
     59     this.containmentDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
     60     this.containmentDataGrid.show(this.containmentView.element);
     61     this.containmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
     62 
     63     this.constructorsView = new WebInspector.View();
     64     this.constructorsView.element.addStyleClass("view");
     65     this.constructorsView.element.appendChild(this._createToolbarWithClassNameFilter());
     66 
     67     this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid();
     68     this.constructorsDataGrid.element.addStyleClass("class-view-grid");
     69     this.constructorsDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
     70     this.constructorsDataGrid.show(this.constructorsView.element);
     71     this.constructorsDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
     72 
     73     this.dataGrid = /** @type {WebInspector.HeapSnapshotSortableDataGrid} */ (this.constructorsDataGrid);
     74     this.currentView = this.constructorsView;
     75     this.currentView.show(this.viewsContainer);
     76 
     77     this.diffView = new WebInspector.View();
     78     this.diffView.element.addStyleClass("view");
     79     this.diffView.element.appendChild(this._createToolbarWithClassNameFilter());
     80 
     81     this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid();
     82     this.diffDataGrid.element.addStyleClass("class-view-grid");
     83     this.diffDataGrid.show(this.diffView.element);
     84     this.diffDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
     85 
     86     this.dominatorView = new WebInspector.View();
     87     this.dominatorView.element.addStyleClass("view");
     88     this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid();
     89     this.dominatorDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
     90     this.dominatorDataGrid.show(this.dominatorView.element);
     91     this.dominatorDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
     92 
     93     this.retainmentViewHeader = document.createElement("div");
     94     this.retainmentViewHeader.addStyleClass("retainers-view-header");
     95     WebInspector.installDragHandle(this.retainmentViewHeader, this._startRetainersHeaderDragging.bind(this), this._retainersHeaderDragging.bind(this), this._endRetainersHeaderDragging.bind(this), "row-resize");
     96     var retainingPathsTitleDiv = document.createElement("div");
     97     retainingPathsTitleDiv.className = "title";
     98     var retainingPathsTitle = document.createElement("span");
     99     retainingPathsTitle.textContent = WebInspector.UIString("Object's retaining tree");
    100     retainingPathsTitleDiv.appendChild(retainingPathsTitle);
    101     this.retainmentViewHeader.appendChild(retainingPathsTitleDiv);
    102     this.element.appendChild(this.retainmentViewHeader);
    103 
    104     this.retainmentView = new WebInspector.View();
    105     this.retainmentView.element.addStyleClass("view");
    106     this.retainmentView.element.addStyleClass("retaining-paths-view");
    107     this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainmentDataGrid();
    108     this.retainmentDataGrid.show(this.retainmentView.element);
    109     this.retainmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._inspectedObjectChanged, this);
    110     this.retainmentView.show(this.element);
    111     this.retainmentDataGrid.reset();
    112 
    113     this.viewSelect = new WebInspector.StatusBarComboBox(this._onSelectedViewChanged.bind(this));
    114 
    115     this.views = [{title: "Summary", view: this.constructorsView, grid: this.constructorsDataGrid},
    116                   {title: "Comparison", view: this.diffView, grid: this.diffDataGrid},
    117                   {title: "Containment", view: this.containmentView, grid: this.containmentDataGrid}];
    118     if (WebInspector.settings.showAdvancedHeapSnapshotProperties.get())
    119         this.views.push({title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid});
    120     this.views.current = 0;
    121     for (var i = 0; i < this.views.length; ++i)
    122         this.viewSelect.createOption(WebInspector.UIString(this.views[i].title));
    123 
    124     this._profileUid = profile.uid;
    125     this._profileTypeId = profile.profileType().id;
    126 
    127     this.baseSelect = new WebInspector.StatusBarComboBox(this._changeBase.bind(this));
    128     this.baseSelect.element.addStyleClass("hidden");
    129     this._updateBaseOptions();
    130 
    131     this.filterSelect = new WebInspector.StatusBarComboBox(this._changeFilter.bind(this));
    132     this._updateFilterOptions();
    133 
    134     this.helpButton = new WebInspector.StatusBarButton("", "heap-snapshot-help-status-bar-item status-bar-item");
    135     this.helpButton.addEventListener("click", this._helpClicked, this);
    136 
    137     this.selectedSizeText = new WebInspector.StatusBarText("");
    138 
    139     this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true);
    140 
    141     this.profile.load(profileCallback.bind(this));
    142 
    143     function profileCallback(heapSnapshotProxy)
    144     {
    145         var list = this._profiles();
    146         var profileIndex;
    147         for (var i = 0; i < list.length; ++i) {
    148             if (list[i].uid === this._profileUid) {
    149                 profileIndex = i;
    150                 break;
    151             }
    152         }
    153 
    154         if (profileIndex > 0)
    155             this.baseSelect.setSelectedIndex(profileIndex - 1);
    156         else
    157             this.baseSelect.setSelectedIndex(profileIndex);
    158         this.dataGrid.setDataSource(heapSnapshotProxy);
    159     }
    160 }
    161 
    162 WebInspector.HeapSnapshotView.prototype = {
    163     _onIdsRangeChanged: function(event)
    164     {
    165         var minId = event.data.minId;
    166         var maxId = event.data.maxId;
    167         this.selectedSizeText.setText(WebInspector.UIString("Selected size: %s", Number.bytesToString(event.data.size)));
    168         if (this.constructorsDataGrid.snapshot)
    169             this.constructorsDataGrid.setSelectionRange(minId, maxId);
    170     },
    171 
    172     dispose: function()
    173     {
    174         this.parent.removeEventListener("profile added", this._onProfileHeaderAdded, this);
    175         this.profile.dispose();
    176         if (this.baseProfile)
    177             this.baseProfile.dispose();
    178         this.containmentDataGrid.dispose();
    179         this.constructorsDataGrid.dispose();
    180         this.diffDataGrid.dispose();
    181         this.dominatorDataGrid.dispose();
    182         this.retainmentDataGrid.dispose();
    183     },
    184 
    185     get statusBarItems()
    186     {
    187         return [this.viewSelect.element, this.baseSelect.element, this.filterSelect.element, this.helpButton.element, this.selectedSizeText.element];
    188     },
    189 
    190     get profile()
    191     {
    192         return this.parent.getProfile(this._profileTypeId, this._profileUid);
    193     },
    194 
    195     get baseProfile()
    196     {
    197         return this.parent.getProfile(this._profileTypeId, this._baseProfileUid);
    198     },
    199 
    200     wasShown: function()
    201     {
    202         // FIXME: load base and current snapshots in parallel
    203         this.profile.load(profileCallback.bind(this));
    204         function profileCallback() {
    205             this.profile._wasShown();
    206             if (this.baseProfile)
    207                 this.baseProfile.load(function() { });
    208         }
    209     },
    210 
    211     willHide: function()
    212     {
    213         this._currentSearchResultIndex = -1;
    214         this._popoverHelper.hidePopover();
    215         if (this.helpPopover && this.helpPopover.isShowing())
    216             this.helpPopover.hide();
    217     },
    218 
    219     onResize: function()
    220     {
    221         var height = this.retainmentView.element.clientHeight;
    222         this._updateRetainmentViewHeight(height);
    223     },
    224 
    225     searchCanceled: function()
    226     {
    227         if (this._searchResults) {
    228             for (var i = 0; i < this._searchResults.length; ++i) {
    229                 var node = this._searchResults[i].node;
    230                 delete node._searchMatched;
    231                 node.refresh();
    232             }
    233         }
    234 
    235         delete this._searchFinishedCallback;
    236         this._currentSearchResultIndex = -1;
    237         this._searchResults = [];
    238     },
    239 
    240     /**
    241      * @param {string} query
    242      * @param {function(!WebInspector.View, number)} finishedCallback
    243      */
    244     performSearch: function(query, finishedCallback)
    245     {
    246         // Call searchCanceled since it will reset everything we need before doing a new search.
    247         this.searchCanceled();
    248 
    249         query = query.trim();
    250 
    251         if (!query)
    252             return;
    253         if (this.currentView !== this.constructorsView && this.currentView !== this.diffView)
    254             return;
    255 
    256         this._searchFinishedCallback = finishedCallback;
    257         var nameRegExp = createPlainTextSearchRegex(query, "i");
    258         var snapshotNodeId = null;
    259 
    260         function matchesByName(gridNode) {
    261             return ("_name" in gridNode) && nameRegExp.test(gridNode._name);
    262         }
    263 
    264         function matchesById(gridNode) {
    265             return ("snapshotNodeId" in gridNode) && gridNode.snapshotNodeId === snapshotNodeId;
    266         }
    267 
    268         var matchPredicate;
    269         if (query.charAt(0) !== "@")
    270             matchPredicate = matchesByName;
    271         else {
    272             snapshotNodeId = parseInt(query.substring(1), 10);
    273             matchPredicate = matchesById;
    274         }
    275 
    276         function matchesQuery(gridNode)
    277         {
    278             delete gridNode._searchMatched;
    279             if (matchPredicate(gridNode)) {
    280                 gridNode._searchMatched = true;
    281                 gridNode.refresh();
    282                 return true;
    283             }
    284             return false;
    285         }
    286 
    287         var current = this.dataGrid.rootNode().children[0];
    288         var depth = 0;
    289         var info = {};
    290 
    291         // Restrict to type nodes and instances.
    292         const maxDepth = 1;
    293 
    294         while (current) {
    295             if (matchesQuery(current))
    296                 this._searchResults.push({ node: current });
    297             current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
    298             depth += info.depthChange;
    299         }
    300 
    301         finishedCallback(this, this._searchResults.length);
    302     },
    303 
    304     jumpToFirstSearchResult: function()
    305     {
    306         if (!this._searchResults || !this._searchResults.length)
    307             return;
    308         this._currentSearchResultIndex = 0;
    309         this._jumpToSearchResult(this._currentSearchResultIndex);
    310     },
    311 
    312     jumpToLastSearchResult: function()
    313     {
    314         if (!this._searchResults || !this._searchResults.length)
    315             return;
    316         this._currentSearchResultIndex = (this._searchResults.length - 1);
    317         this._jumpToSearchResult(this._currentSearchResultIndex);
    318     },
    319 
    320     jumpToNextSearchResult: function()
    321     {
    322         if (!this._searchResults || !this._searchResults.length)
    323             return;
    324         if (++this._currentSearchResultIndex >= this._searchResults.length)
    325             this._currentSearchResultIndex = 0;
    326         this._jumpToSearchResult(this._currentSearchResultIndex);
    327     },
    328 
    329     jumpToPreviousSearchResult: function()
    330     {
    331         if (!this._searchResults || !this._searchResults.length)
    332             return;
    333         if (--this._currentSearchResultIndex < 0)
    334             this._currentSearchResultIndex = (this._searchResults.length - 1);
    335         this._jumpToSearchResult(this._currentSearchResultIndex);
    336     },
    337 
    338     showingFirstSearchResult: function()
    339     {
    340         return (this._currentSearchResultIndex === 0);
    341     },
    342 
    343     showingLastSearchResult: function()
    344     {
    345         return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
    346     },
    347 
    348     _jumpToSearchResult: function(index)
    349     {
    350         var searchResult = this._searchResults[index];
    351         if (!searchResult)
    352             return;
    353 
    354         var node = searchResult.node;
    355         node.revealAndSelect();
    356     },
    357 
    358     refreshVisibleData: function()
    359     {
    360         var child = this.dataGrid.rootNode().children[0];
    361         while (child) {
    362             child.refresh();
    363             child = child.traverseNextNode(false, null, true);
    364         }
    365     },
    366 
    367     _changeBase: function()
    368     {
    369         if (this._baseProfileUid === this._profiles()[this.baseSelect.selectedIndex()].uid)
    370             return;
    371 
    372         this._baseProfileUid = this._profiles()[this.baseSelect.selectedIndex()].uid;
    373         var dataGrid = /** @type {WebInspector.HeapSnapshotDiffDataGrid} */ (this.dataGrid);
    374         // Change set base data source only if main data source is already set.
    375         if (dataGrid.snapshot)
    376             this.baseProfile.load(dataGrid.setBaseDataSource.bind(dataGrid));
    377 
    378         if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
    379             return;
    380 
    381         // The current search needs to be performed again. First negate out previous match
    382         // count by calling the search finished callback with a negative number of matches.
    383         // Then perform the search again with the same query and callback.
    384         this._searchFinishedCallback(this, -this._searchResults.length);
    385         this.performSearch(this.currentQuery, this._searchFinishedCallback);
    386     },
    387 
    388     _changeFilter: function()
    389     {
    390         var profileIndex = this.filterSelect.selectedIndex() - 1;
    391         this.dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex);
    392 
    393         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
    394             action: WebInspector.UserMetrics.UserActionNames.HeapSnapshotFilterChanged,
    395             label: this.filterSelect.selectedOption().label
    396         });
    397 
    398         if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
    399             return;
    400 
    401         // The current search needs to be performed again. First negate out previous match
    402         // count by calling the search finished callback with a negative number of matches.
    403         // Then perform the search again with the same query and callback.
    404         this._searchFinishedCallback(this, -this._searchResults.length);
    405         this.performSearch(this.currentQuery, this._searchFinishedCallback);
    406     },
    407 
    408     _createToolbarWithClassNameFilter: function()
    409     {
    410         var toolbar = document.createElement("div");
    411         toolbar.addStyleClass("class-view-toolbar");
    412         var classNameFilter = document.createElement("input");
    413         classNameFilter.addStyleClass("class-name-filter");
    414         classNameFilter.setAttribute("placeholder", WebInspector.UIString("Class filter"));
    415         classNameFilter.addEventListener("keyup", this._changeNameFilter.bind(this, classNameFilter), false);
    416         toolbar.appendChild(classNameFilter);
    417         return toolbar;
    418     },
    419 
    420     _changeNameFilter: function(classNameInputElement)
    421     {
    422         var filter = classNameInputElement.value;
    423         this.dataGrid.changeNameFilter(filter);
    424     },
    425 
    426     /**
    427      * @return {!Array.<!WebInspector.ProfileHeader>}
    428      */
    429     _profiles: function()
    430     {
    431         return this.parent.getProfileType(this._profileTypeId).getProfiles();
    432     },
    433 
    434     /**
    435      * @param {WebInspector.ContextMenu} contextMenu
    436      * @param {Event} event
    437      */
    438     populateContextMenu: function(contextMenu, event)
    439     {
    440         this.dataGrid.populateContextMenu(this.parent, contextMenu, event);
    441     },
    442 
    443     _selectionChanged: function(event)
    444     {
    445         var selectedNode = event.target.selectedNode;
    446         this._setRetainmentDataGridSource(selectedNode);
    447         this._inspectedObjectChanged(event);
    448     },
    449 
    450     _inspectedObjectChanged: function(event)
    451     {
    452         var selectedNode = event.target.selectedNode;
    453         if (!this.profile.fromFile() && selectedNode instanceof WebInspector.HeapSnapshotGenericObjectNode)
    454             ConsoleAgent.addInspectedHeapObject(selectedNode.snapshotNodeId);
    455     },
    456 
    457     _setRetainmentDataGridSource: function(nodeItem)
    458     {
    459         if (nodeItem && nodeItem.snapshotNodeIndex)
    460             this.retainmentDataGrid.setDataSource(nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex);
    461         else
    462             this.retainmentDataGrid.reset();
    463     },
    464 
    465     _mouseDownInContentsGrid: function(event)
    466     {
    467         if (event.detail < 2)
    468             return;
    469 
    470         var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
    471         if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("shallowSize-column") && !cell.hasStyleClass("retainedSize-column")))
    472             return;
    473 
    474         event.consume(true);
    475     },
    476 
    477     changeView: function(viewTitle, callback)
    478     {
    479         var viewIndex = null;
    480         for (var i = 0; i < this.views.length; ++i) {
    481             if (this.views[i].title === viewTitle) {
    482                 viewIndex = i;
    483                 break;
    484             }
    485         }
    486         if (this.views.current === viewIndex || viewIndex == null) {
    487             setTimeout(callback, 0);
    488             return;
    489         }
    490 
    491         function dataGridContentShown(event)
    492         {
    493             var dataGrid = event.data;
    494             dataGrid.removeEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
    495             if (dataGrid === this.dataGrid)
    496                 callback();
    497         }
    498         this.views[viewIndex].grid.addEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
    499 
    500         this.viewSelect.setSelectedIndex(viewIndex);
    501         this._changeView(viewIndex);
    502     },
    503 
    504     _updateDataSourceAndView: function()
    505     {
    506         var dataGrid = this.dataGrid;
    507         if (dataGrid.snapshot)
    508             return;
    509 
    510         this.profile.load(didLoadSnapshot.bind(this));
    511         function didLoadSnapshot(snapshotProxy)
    512         {
    513             if (this.dataGrid !== dataGrid)
    514                 return;
    515             if (dataGrid.snapshot !== snapshotProxy)
    516                 dataGrid.setDataSource(snapshotProxy);
    517             if (dataGrid === this.diffDataGrid) {
    518                 if (!this._baseProfileUid)
    519                     this._baseProfileUid = this._profiles()[this.baseSelect.selectedIndex()].uid;
    520                 this.baseProfile.load(didLoadBaseSnaphot.bind(this));
    521             }
    522         }
    523 
    524         function didLoadBaseSnaphot(baseSnapshotProxy)
    525         {
    526             if (this.diffDataGrid.baseSnapshot !== baseSnapshotProxy)
    527                 this.diffDataGrid.setBaseDataSource(baseSnapshotProxy);
    528         }
    529     },
    530 
    531     _onSelectedViewChanged: function(event)
    532     {
    533         this._changeView(event.target.selectedIndex);
    534     },
    535 
    536     _updateSelectorsVisibility: function()
    537     {
    538         if (this.currentView === this.diffView)
    539             this.baseSelect.element.removeStyleClass("hidden");
    540         else
    541             this.baseSelect.element.addStyleClass("hidden");
    542 
    543         if (this.currentView === this.constructorsView) {
    544             if (this._trackingOverviewGrid) {
    545                 this._trackingOverviewGrid.element.removeStyleClass("hidden");
    546                 this._trackingOverviewGrid.update();
    547                 this.viewsContainer.addStyleClass("reserve-80px-at-top");
    548             }
    549             this.filterSelect.element.removeStyleClass("hidden");
    550         } else {
    551             this.filterSelect.element.addStyleClass("hidden");
    552             if (this._trackingOverviewGrid) {
    553                 this._trackingOverviewGrid.element.addStyleClass("hidden");
    554                 this.viewsContainer.removeStyleClass("reserve-80px-at-top");
    555             }
    556         }
    557     },
    558 
    559     _changeView: function(selectedIndex)
    560     {
    561         if (selectedIndex === this.views.current)
    562             return;
    563 
    564         this.views.current = selectedIndex;
    565         this.currentView.detach();
    566         var view = this.views[this.views.current];
    567         this.currentView = view.view;
    568         this.dataGrid = view.grid;
    569         this.currentView.show(this.viewsContainer);
    570         this.refreshVisibleData();
    571         this.dataGrid.updateWidths();
    572 
    573         this._updateSelectorsVisibility();
    574 
    575         this._updateDataSourceAndView();
    576 
    577         if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
    578             return;
    579 
    580         // The current search needs to be performed again. First negate out previous match
    581         // count by calling the search finished callback with a negative number of matches.
    582         // Then perform the search again the with same query and callback.
    583         this._searchFinishedCallback(this, -this._searchResults.length);
    584         this.performSearch(this.currentQuery, this._searchFinishedCallback);
    585     },
    586 
    587     _getHoverAnchor: function(target)
    588     {
    589         var span = target.enclosingNodeOrSelfWithNodeName("span");
    590         if (!span)
    591             return;
    592         var row = target.enclosingNodeOrSelfWithNodeName("tr");
    593         if (!row)
    594             return;
    595         span.node = row._dataGridNode;
    596         return span;
    597     },
    598 
    599     _resolveObjectForPopover: function(element, showCallback, objectGroupName)
    600     {
    601         if (this.profile.fromFile())
    602             return;
    603         element.node.queryObjectContent(showCallback, objectGroupName);
    604     },
    605 
    606     _helpClicked: function(event)
    607     {
    608         if (!this._helpPopoverContentElement) {
    609             var refTypes = ["a:", "console-formatted-name", WebInspector.UIString("property"),
    610                             "0:", "console-formatted-name", WebInspector.UIString("element"),
    611                             "a:", "console-formatted-number", WebInspector.UIString("context var"),
    612                             "a:", "console-formatted-null", WebInspector.UIString("system prop")];
    613             var objTypes = [" a ", "console-formatted-object", "Object",
    614                             "\"a\"", "console-formatted-string", "String",
    615                             "/a/", "console-formatted-string", "RegExp",
    616                             "a()", "console-formatted-function", "Function",
    617                             "a[]", "console-formatted-object", "Array",
    618                             "num", "console-formatted-number", "Number",
    619                             " a ", "console-formatted-null", "System"];
    620 
    621             var contentElement = document.createElement("table");
    622             contentElement.className = "heap-snapshot-help";
    623             var headerRow = document.createElement("tr");
    624             var propsHeader = document.createElement("th");
    625             propsHeader.textContent = WebInspector.UIString("Property types:");
    626             headerRow.appendChild(propsHeader);
    627             var objsHeader = document.createElement("th");
    628             objsHeader.textContent = WebInspector.UIString("Object types:");
    629             headerRow.appendChild(objsHeader);
    630             contentElement.appendChild(headerRow);
    631 
    632             function appendHelp(help, index, cell)
    633             {
    634                 var div = document.createElement("div");
    635                 div.className = "source-code event-properties";
    636                 var name = document.createElement("span");
    637                 name.textContent = help[index];
    638                 name.className = help[index + 1];
    639                 div.appendChild(name);
    640                 var desc = document.createElement("span");
    641                 desc.textContent = " " + help[index + 2];
    642                 div.appendChild(desc);
    643                 cell.appendChild(div);
    644             }
    645 
    646             var len = Math.max(refTypes.length, objTypes.length);
    647             for (var i = 0; i < len; i += 3) {
    648                 var row = document.createElement("tr");
    649                 var refCell = document.createElement("td");
    650                 if (refTypes[i])
    651                     appendHelp(refTypes, i, refCell);
    652                 row.appendChild(refCell);
    653                 var objCell = document.createElement("td");
    654                 if (objTypes[i])
    655                     appendHelp(objTypes, i, objCell);
    656                 row.appendChild(objCell);
    657                 contentElement.appendChild(row);
    658             }
    659             this._helpPopoverContentElement = contentElement;
    660             this.helpPopover = new WebInspector.Popover();
    661         }
    662         if (this.helpPopover.isShowing())
    663             this.helpPopover.hide();
    664         else
    665             this.helpPopover.show(this._helpPopoverContentElement, this.helpButton.element);
    666     },
    667 
    668     /**
    669      * @return {boolean}
    670      */
    671     _startRetainersHeaderDragging: function(event)
    672     {
    673         if (!this.isShowing())
    674             return false;
    675 
    676         this._previousDragPosition = event.pageY;
    677         return true;
    678     },
    679 
    680     _retainersHeaderDragging: function(event)
    681     {
    682         var height = this.retainmentView.element.clientHeight;
    683         height += this._previousDragPosition - event.pageY;
    684         this._previousDragPosition = event.pageY;
    685         this._updateRetainmentViewHeight(height);
    686         event.consume(true);
    687     },
    688 
    689     _endRetainersHeaderDragging: function(event)
    690     {
    691         delete this._previousDragPosition;
    692         event.consume();
    693     },
    694 
    695     _updateRetainmentViewHeight: function(height)
    696     {
    697         height = Number.constrain(height, Preferences.minConsoleHeight, this.element.clientHeight - Preferences.minConsoleHeight);
    698         this.viewsContainer.style.bottom = (height + this.retainmentViewHeader.clientHeight) + "px";
    699         if (this._trackingOverviewGrid && this.currentView === this.constructorsView)
    700             this.viewsContainer.addStyleClass("reserve-80px-at-top");
    701         this.retainmentView.element.style.height = height + "px";
    702         this.retainmentViewHeader.style.bottom = height + "px";
    703         this.currentView.doResize();
    704     },
    705 
    706     _updateBaseOptions: function()
    707     {
    708         var list = this._profiles();
    709         // We're assuming that snapshots can only be added.
    710         if (this.baseSelect.size() === list.length)
    711             return;
    712 
    713         for (var i = this.baseSelect.size(), n = list.length; i < n; ++i) {
    714             var title = list[i].title;
    715             if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(title))
    716                 title = WebInspector.UIString("Snapshot %d", WebInspector.ProfilesPanelDescriptor.userInitiatedProfileIndex(title));
    717             this.baseSelect.createOption(title);
    718         }
    719     },
    720 
    721     _updateFilterOptions: function()
    722     {
    723         var list = this._profiles();
    724         // We're assuming that snapshots can only be added.
    725         if (this.filterSelect.size() - 1 === list.length)
    726             return;
    727 
    728         if (!this.filterSelect.size())
    729             this.filterSelect.createOption(WebInspector.UIString("All objects"));
    730 
    731         if (this.profile.fromFile())
    732             return;
    733         for (var i = this.filterSelect.size() - 1, n = list.length; i < n; ++i) {
    734             var profile = list[i];
    735             var title = list[i].title;
    736             if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(title)) {
    737                 var profileIndex = WebInspector.ProfilesPanelDescriptor.userInitiatedProfileIndex(title);
    738                 if (!i)
    739                     title = WebInspector.UIString("Objects allocated before Snapshot %d", profileIndex);
    740                 else
    741                     title = WebInspector.UIString("Objects allocated between Snapshots %d and %d", profileIndex - 1, profileIndex);
    742             }
    743             this.filterSelect.createOption(title);
    744         }
    745     },
    746 
    747     /**
    748      * @param {WebInspector.Event} event
    749      */
    750     _onProfileHeaderAdded: function(event)
    751     {
    752         if (!event.data || event.data.type !== this._profileTypeId)
    753             return;
    754         this._updateBaseOptions();
    755         this._updateFilterOptions();
    756     },
    757 
    758     __proto__: WebInspector.View.prototype
    759 }
    760 
    761 /**
    762  * @constructor
    763  * @implements {HeapProfilerAgent.Dispatcher}
    764  */
    765 WebInspector.HeapProfilerDispatcher = function()
    766 {
    767     this._dispatchers = [];
    768     InspectorBackend.registerHeapProfilerDispatcher(this);
    769 }
    770 
    771 WebInspector.HeapProfilerDispatcher.prototype = {
    772     /**
    773      * @param {HeapProfilerAgent.Dispatcher} dispatcher
    774      */
    775     register: function(dispatcher)
    776     {
    777         this._dispatchers.push(dispatcher);
    778     },
    779 
    780     _genericCaller: function(eventName)
    781     {
    782         var args = Array.prototype.slice.call(arguments.callee.caller.arguments);
    783         for (var i = 0; i < this._dispatchers.length; ++i)
    784             this._dispatchers[i][eventName].apply(this._dispatchers[i], args);
    785     },
    786 
    787     /**
    788      * @override
    789      * @param {Array.<number>} samples
    790      */
    791     heapStatsUpdate: function(samples)
    792     {
    793         this._genericCaller("heapStatsUpdate");
    794     },
    795 
    796     /**
    797      * @override
    798      * @param {number} lastSeenObjectId
    799      * @param {number} timestamp
    800      */
    801     lastSeenObjectId: function(lastSeenObjectId, timestamp)
    802     {
    803         this._genericCaller("lastSeenObjectId");
    804     },
    805 
    806     /**
    807      * @param {HeapProfilerAgent.ProfileHeader} profileHeader
    808      */
    809     addProfileHeader: function(profileHeader)
    810     {
    811         this._genericCaller("addProfileHeader");
    812     },
    813 
    814     /**
    815      * @override
    816      * @param {number} uid
    817      * @param {string} chunk
    818      */
    819     addHeapSnapshotChunk: function(uid, chunk)
    820     {
    821         this._genericCaller("addHeapSnapshotChunk");
    822     },
    823 
    824     /**
    825      * @override
    826      * @param {number} uid
    827      */
    828     finishHeapSnapshot: function(uid)
    829     {
    830         this._genericCaller("finishHeapSnapshot");
    831     },
    832 
    833     /**
    834      * @override
    835      * @param {number} done
    836      * @param {number} total
    837      */
    838     reportHeapSnapshotProgress: function(done, total)
    839     {
    840         this._genericCaller("reportHeapSnapshotProgress");
    841     },
    842 
    843     /**
    844      * @override
    845      */
    846     resetProfiles: function()
    847     {
    848         this._genericCaller("resetProfiles");
    849     }
    850 }
    851 
    852 WebInspector.HeapProfilerDispatcher._dispatcher = new WebInspector.HeapProfilerDispatcher();
    853 
    854 /**
    855  * @constructor
    856  * @extends {WebInspector.ProfileType}
    857  * @implements {HeapProfilerAgent.Dispatcher}
    858  */
    859 WebInspector.HeapSnapshotProfileType = function()
    860 {
    861     WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("Take Heap Snapshot"));
    862     WebInspector.HeapProfilerDispatcher._dispatcher.register(this);
    863 }
    864 
    865 WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
    866 WebInspector.HeapSnapshotProfileType.SnapshotReceived = "SnapshotReceived";
    867 
    868 WebInspector.HeapSnapshotProfileType.prototype = {
    869     /**
    870      * @override
    871      * @return {string}
    872      */
    873     fileExtension: function()
    874     {
    875         return ".heapsnapshot";
    876     },
    877 
    878     get buttonTooltip()
    879     {
    880         return WebInspector.UIString("Take heap snapshot.");
    881     },
    882 
    883     /**
    884      * @override
    885      * @return {boolean}
    886      */
    887     isInstantProfile: function()
    888     {
    889         return true;
    890     },
    891 
    892     /**
    893      * @override
    894      * @return {boolean}
    895      */
    896     buttonClicked: function()
    897     {
    898         this._takeHeapSnapshot(function() {});
    899         WebInspector.userMetrics.ProfilesHeapProfileTaken.record();
    900         return false;
    901     },
    902 
    903     /**
    904      * @override
    905      * @param {Array.<number>} samples
    906      */
    907     heapStatsUpdate: function(samples)
    908     {
    909     },
    910 
    911     /**
    912      * @override
    913      * @param {number} lastSeenObjectId
    914      * @param {number} timestamp
    915      */
    916     lastSeenObjectId: function(lastSeenObjectId, timestamp)
    917     {
    918     },
    919 
    920     get treeItemTitle()
    921     {
    922         return WebInspector.UIString("HEAP SNAPSHOTS");
    923     },
    924 
    925     get description()
    926     {
    927         return WebInspector.UIString("Heap snapshot profiles show memory distribution among your page's JavaScript objects and related DOM nodes.");
    928     },
    929 
    930     /**
    931      * @override
    932      * @param {string=} title
    933      * @return {!WebInspector.ProfileHeader}
    934      */
    935     createTemporaryProfile: function(title)
    936     {
    937         title = title || WebInspector.UIString("Snapshotting\u2026");
    938         return new WebInspector.HeapProfileHeader(this, title);
    939     },
    940 
    941     /**
    942      * @override
    943      * @param {HeapProfilerAgent.ProfileHeader} profile
    944      * @return {!WebInspector.ProfileHeader}
    945      */
    946     createProfile: function(profile)
    947     {
    948         return new WebInspector.HeapProfileHeader(this, profile.title, profile.uid, profile.maxJSObjectId || 0);
    949     },
    950 
    951     _takeHeapSnapshot: function(callback)
    952     {
    953         var temporaryProfile = this.findTemporaryProfile();
    954         if (!temporaryProfile)
    955             this.addProfile(this.createTemporaryProfile());
    956         HeapProfilerAgent.takeHeapSnapshot(true, callback);
    957     },
    958 
    959     /**
    960      * @param {HeapProfilerAgent.ProfileHeader} profileHeader
    961      */
    962     addProfileHeader: function(profileHeader)
    963     {
    964         if (!this.findTemporaryProfile())
    965             return;
    966         var profile = this.createProfile(profileHeader);
    967         profile._profileSamples = this._profileSamples;
    968         this._profileSamples = null;
    969         this.addProfile(profile);
    970     },
    971 
    972     /**
    973      * @override
    974      * @param {number} uid
    975      * @param {string} chunk
    976      */
    977     addHeapSnapshotChunk: function(uid, chunk)
    978     {
    979         var profile = this._profilesIdMap[this._makeKey(uid)];
    980         if (profile)
    981             profile.transferChunk(chunk);
    982     },
    983 
    984     /**
    985      * @override
    986      * @param {number} uid
    987      */
    988     finishHeapSnapshot: function(uid)
    989     {
    990         var profile = this._profilesIdMap[this._makeKey(uid)];
    991         if (profile)
    992             profile.finishHeapSnapshot();
    993     },
    994 
    995     /**
    996      * @override
    997      * @param {number} done
    998      * @param {number} total
    999      */
   1000     reportHeapSnapshotProgress: function(done, total)
   1001     {
   1002         var profile = this.findTemporaryProfile();
   1003         if (profile)
   1004             this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProgressUpdated, {"profile": profile, "done": done, "total": total});
   1005     },
   1006 
   1007     /**
   1008      * @override
   1009      */
   1010     resetProfiles: function()
   1011     {
   1012         this._reset();
   1013     },
   1014 
   1015     /**
   1016      * @override
   1017      * @param {!WebInspector.ProfileHeader} profile
   1018      */
   1019     removeProfile: function(profile)
   1020     {
   1021         WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
   1022         if (!profile.isTemporary)
   1023             HeapProfilerAgent.removeProfile(profile.uid);
   1024     },
   1025 
   1026     /**
   1027      * @override
   1028      * @param {function(this:WebInspector.ProfileType, ?string, Array.<HeapProfilerAgent.ProfileHeader>)} populateCallback
   1029      */
   1030     _requestProfilesFromBackend: function(populateCallback)
   1031     {
   1032         HeapProfilerAgent.getProfileHeaders(populateCallback);
   1033     },
   1034 
   1035     _snapshotReceived: function(profile)
   1036     {
   1037         this.dispatchEventToListeners(WebInspector.HeapSnapshotProfileType.SnapshotReceived, profile);
   1038     },
   1039 
   1040     __proto__: WebInspector.ProfileType.prototype
   1041 }
   1042 
   1043 
   1044 /**
   1045  * @constructor
   1046  * @extends {WebInspector.HeapSnapshotProfileType}
   1047  * @param {WebInspector.ProfilesPanel} profilesPanel
   1048  */
   1049 WebInspector.TrackingHeapSnapshotProfileType = function(profilesPanel)
   1050 {
   1051     WebInspector.ProfileType.call(this, WebInspector.TrackingHeapSnapshotProfileType.TypeId, WebInspector.UIString("Record Heap Allocations"));
   1052     this._profilesPanel = profilesPanel;
   1053     WebInspector.HeapProfilerDispatcher._dispatcher.register(this);
   1054 }
   1055 
   1056 WebInspector.TrackingHeapSnapshotProfileType.TypeId = "HEAP-RECORD";
   1057 
   1058 WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate = "HeapStatsUpdate";
   1059 WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted = "TrackingStarted";
   1060 WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped = "TrackingStopped";
   1061 
   1062 WebInspector.TrackingHeapSnapshotProfileType.prototype = {
   1063 
   1064     /**
   1065      * @override
   1066      * @param {Array.<number>} samples
   1067      */
   1068     heapStatsUpdate: function(samples)
   1069     {
   1070         if (!this._profileSamples)
   1071             return;
   1072         var index;
   1073         for (var i = 0; i < samples.length; i += 3) {
   1074             index = samples[i];
   1075             var count = samples[i+1];
   1076             var size  = samples[i+2];
   1077             this._profileSamples.sizes[index] = size;
   1078             if (!this._profileSamples.max[index] || size > this._profileSamples.max[index])
   1079                 this._profileSamples.max[index] = size;
   1080         }
   1081         this._lastUpdatedIndex = index;
   1082     },
   1083 
   1084     /**
   1085      * @override
   1086      * @param {number} lastSeenObjectId
   1087      * @param {number} timestamp
   1088      */
   1089     lastSeenObjectId: function(lastSeenObjectId, timestamp)
   1090     {
   1091         var profileSamples = this._profileSamples;
   1092         if (!profileSamples)
   1093             return;
   1094         var currentIndex = Math.max(profileSamples.ids.length, profileSamples.max.length - 1);
   1095         profileSamples.ids[currentIndex] = lastSeenObjectId;
   1096         if (!profileSamples.max[currentIndex]) {
   1097             profileSamples.max[currentIndex] = 0;
   1098             profileSamples.sizes[currentIndex] = 0;
   1099         }
   1100         profileSamples.timestamps[currentIndex] = timestamp;
   1101         if (profileSamples.totalTime < timestamp - profileSamples.timestamps[0])
   1102             profileSamples.totalTime *= 2;
   1103         this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._profileSamples);
   1104         var profile = this.findTemporaryProfile();
   1105         profile.sidebarElement.wait = true;
   1106         if (profile.sidebarElement && !profile.sidebarElement.wait)
   1107             profile.sidebarElement.wait = true;
   1108     },
   1109 
   1110     /**
   1111      * @override
   1112      * @return {boolean}
   1113      */
   1114     hasTemporaryView: function()
   1115     {
   1116         return true;
   1117     },
   1118 
   1119     get buttonTooltip()
   1120     {
   1121         return this._recording ? WebInspector.UIString("Stop recording heap profile.") : WebInspector.UIString("Start recording heap profile.");
   1122     },
   1123 
   1124     /**
   1125      * @override
   1126      * @return {boolean}
   1127      */
   1128     isInstantProfile: function()
   1129     {
   1130         return false;
   1131     },
   1132 
   1133     /**
   1134      * @override
   1135      * @return {boolean}
   1136      */
   1137     buttonClicked: function()
   1138     {
   1139         return this._toggleRecording();
   1140     },
   1141 
   1142     _startRecordingProfile: function()
   1143     {
   1144         this._lastSeenIndex = -1;
   1145         this._profileSamples = {
   1146             'sizes': [],
   1147             'ids': [],
   1148             'timestamps': [],
   1149             'max': [],
   1150             'totalTime': 30000
   1151         };
   1152         this._recording = true;
   1153         HeapProfilerAgent.startTrackingHeapObjects();
   1154         this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted);
   1155     },
   1156 
   1157     _stopRecordingProfile: function()
   1158     {
   1159         HeapProfilerAgent.stopTrackingHeapObjects();
   1160         HeapProfilerAgent.takeHeapSnapshot(true);
   1161         this._recording = false;
   1162         this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped);
   1163     },
   1164 
   1165     _toggleRecording: function()
   1166     {
   1167         if (this._recording)
   1168             this._stopRecordingProfile();
   1169         else
   1170             this._startRecordingProfile();
   1171         return this._recording;
   1172     },
   1173 
   1174     get treeItemTitle()
   1175     {
   1176         return WebInspector.UIString("HEAP TIMELINES");
   1177     },
   1178 
   1179     get description()
   1180     {
   1181         return WebInspector.UIString("Record JavaScript object allocations over time. Use this profile type to isolate memory leaks.");
   1182     },
   1183 
   1184     _reset: function()
   1185     {
   1186         WebInspector.HeapSnapshotProfileType.prototype._reset.call(this);
   1187         if (this._recording)
   1188             this._stopRecordingProfile();
   1189         this._profileSamples = null;
   1190         this._lastSeenIndex = -1;
   1191     },
   1192 
   1193     /**
   1194      * @override
   1195      * @param {string=} title
   1196      * @return {!WebInspector.ProfileHeader}
   1197      */
   1198     createTemporaryProfile: function(title)
   1199     {
   1200         title = title || WebInspector.UIString("Recording\u2026");
   1201         return new WebInspector.HeapProfileHeader(this, title);
   1202     },
   1203 
   1204     /**
   1205      * @override
   1206      * @param {function(this:WebInspector.ProfileType, ?string, Array.<HeapProfilerAgent.ProfileHeader>)} populateCallback
   1207      */
   1208     _requestProfilesFromBackend: function(populateCallback)
   1209     {
   1210     },
   1211 
   1212     __proto__: WebInspector.HeapSnapshotProfileType.prototype
   1213 }
   1214 
   1215 /**
   1216  * @constructor
   1217  * @extends {WebInspector.ProfileHeader}
   1218  * @param {!WebInspector.ProfileType} type
   1219  * @param {string} title
   1220  * @param {number=} uid
   1221  * @param {number=} maxJSObjectId
   1222  */
   1223 WebInspector.HeapProfileHeader = function(type, title, uid, maxJSObjectId)
   1224 {
   1225     WebInspector.ProfileHeader.call(this, type, title, uid);
   1226     this.maxJSObjectId = maxJSObjectId;
   1227     /**
   1228      * @type {WebInspector.OutputStream}
   1229      */
   1230     this._receiver = null;
   1231     /**
   1232      * @type {WebInspector.HeapSnapshotProxy}
   1233      */
   1234     this._snapshotProxy = null;
   1235     this._totalNumberOfChunks = 0;
   1236 }
   1237 
   1238 WebInspector.HeapProfileHeader.prototype = {
   1239     /**
   1240      * @override
   1241      */
   1242     createSidebarTreeElement: function()
   1243     {
   1244         return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item");
   1245     },
   1246 
   1247     /**
   1248      * @override
   1249      * @param {!WebInspector.ProfilesPanel} profilesPanel
   1250      */
   1251     createView: function(profilesPanel)
   1252     {
   1253         return new WebInspector.HeapSnapshotView(profilesPanel, this);
   1254     },
   1255 
   1256     /**
   1257      * @override
   1258      * @param {function(WebInspector.HeapSnapshotProxy):void} callback
   1259      */
   1260     load: function(callback)
   1261     {
   1262         if (this.uid === -1)
   1263             return;
   1264         if (this._snapshotProxy) {
   1265             callback(this._snapshotProxy);
   1266             return;
   1267         }
   1268 
   1269         this._numberOfChunks = 0;
   1270         this._savedChunks = 0;
   1271         this._savingToFile = false;
   1272         if (!this._receiver) {
   1273             this._setupWorker();
   1274             this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
   1275             this.sidebarElement.wait = true;
   1276             this.startSnapshotTransfer();
   1277         }
   1278         var loaderProxy = /** @type {WebInspector.HeapSnapshotLoaderProxy} */ (this._receiver);
   1279         loaderProxy.addConsumer(callback);
   1280     },
   1281 
   1282     startSnapshotTransfer: function()
   1283     {
   1284         HeapProfilerAgent.getHeapSnapshot(this.uid);
   1285     },
   1286 
   1287     snapshotConstructorName: function()
   1288     {
   1289         return "JSHeapSnapshot";
   1290     },
   1291 
   1292     snapshotProxyConstructor: function()
   1293     {
   1294         return WebInspector.HeapSnapshotProxy;
   1295     },
   1296 
   1297     _setupWorker: function()
   1298     {
   1299         function setProfileWait(event)
   1300         {
   1301             this.sidebarElement.wait = event.data;
   1302         }
   1303         var worker = new WebInspector.HeapSnapshotWorker();
   1304         worker.addEventListener("wait", setProfileWait, this);
   1305         var loaderProxy = worker.createLoader(this.snapshotConstructorName(), this.snapshotProxyConstructor());
   1306         loaderProxy.addConsumer(this._snapshotReceived.bind(this));
   1307         this._receiver = loaderProxy;
   1308     },
   1309 
   1310     /**
   1311      * @override
   1312      */
   1313     dispose: function()
   1314     {
   1315         if (this._receiver)
   1316             this._receiver.close();
   1317         else if (this._snapshotProxy)
   1318             this._snapshotProxy.dispose();
   1319         if (this._view) {
   1320             var view = this._view;
   1321             this._view = null;
   1322             view.dispose();
   1323         }
   1324     },
   1325 
   1326     /**
   1327      * @param {number} value
   1328      * @param {number} maxValue
   1329      */
   1330     _updateTransferProgress: function(value, maxValue)
   1331     {
   1332         var percentValue = ((maxValue ? (value / maxValue) : 0) * 100).toFixed(0);
   1333         if (this._savingToFile)
   1334             this.sidebarElement.subtitle = WebInspector.UIString("Saving\u2026 %d\%", percentValue);
   1335         else
   1336             this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026 %d\%", percentValue);
   1337     },
   1338 
   1339     _updateSnapshotStatus: function()
   1340     {
   1341         this.sidebarElement.subtitle = Number.bytesToString(this._snapshotProxy.totalSize);
   1342         this.sidebarElement.wait = false;
   1343     },
   1344 
   1345     /**
   1346      * @param {string} chunk
   1347      */
   1348     transferChunk: function(chunk)
   1349     {
   1350         ++this._numberOfChunks;
   1351         this._receiver.write(chunk, callback.bind(this));
   1352         function callback()
   1353         {
   1354             this._updateTransferProgress(++this._savedChunks, this._totalNumberOfChunks);
   1355             if (this._totalNumberOfChunks === this._savedChunks) {
   1356                 if (this._savingToFile)
   1357                     this._updateSnapshotStatus();
   1358                 else
   1359                     this.sidebarElement.subtitle = WebInspector.UIString("Parsing\u2026");
   1360 
   1361                 this._receiver.close();
   1362             }
   1363         }
   1364     },
   1365 
   1366     _snapshotReceived: function(snapshotProxy)
   1367     {
   1368         this._receiver = null;
   1369         if (snapshotProxy)
   1370             this._snapshotProxy = snapshotProxy;
   1371         this._updateSnapshotStatus();
   1372         var worker = /** @type {WebInspector.HeapSnapshotWorker} */ (this._snapshotProxy.worker);
   1373         this.isTemporary = false;
   1374         worker.startCheckingForLongRunningCalls();
   1375         this.notifySnapshotReceived();
   1376     },
   1377 
   1378     notifySnapshotReceived: function()
   1379     {
   1380         this._profileType._snapshotReceived(this);
   1381     },
   1382 
   1383     finishHeapSnapshot: function()
   1384     {
   1385         this._totalNumberOfChunks = this._numberOfChunks;
   1386     },
   1387 
   1388     // Hook point for tests.
   1389     _wasShown: function()
   1390     {
   1391     },
   1392 
   1393     /**
   1394      * @override
   1395      * @return {boolean}
   1396      */
   1397     canSaveToFile: function()
   1398     {
   1399         return !this.fromFile() && !!this._snapshotProxy && !this._receiver;
   1400     },
   1401 
   1402     /**
   1403      * @override
   1404      */
   1405     saveToFile: function()
   1406     {
   1407         this._numberOfChunks = 0;
   1408 
   1409         var fileOutputStream = new WebInspector.FileOutputStream();
   1410         function onOpen()
   1411         {
   1412             this._receiver = fileOutputStream;
   1413             this._savedChunks = 0;
   1414             this._updateTransferProgress(0, this._totalNumberOfChunks);
   1415             HeapProfilerAgent.getHeapSnapshot(this.uid);
   1416         }
   1417         this._savingToFile = true;
   1418         this._fileName = this._fileName || "Heap-" + new Date().toISO8601Compact() + this._profileType.fileExtension();
   1419         fileOutputStream.open(this._fileName, onOpen.bind(this));
   1420     },
   1421 
   1422     /**
   1423      * @override
   1424      * @param {File} file
   1425      */
   1426     loadFromFile: function(file)
   1427     {
   1428         this.title = file.name;
   1429         this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
   1430         this.sidebarElement.wait = true;
   1431         this._setupWorker();
   1432         this._numberOfChunks = 0;
   1433         this._savingToFile = false;
   1434 
   1435         var delegate = new WebInspector.HeapSnapshotLoadFromFileDelegate(this);
   1436         var fileReader = this._createFileReader(file, delegate);
   1437         fileReader.start(this._receiver);
   1438     },
   1439 
   1440     _createFileReader: function(file, delegate)
   1441     {
   1442         return new WebInspector.ChunkedFileReader(file, 10000000, delegate);
   1443     },
   1444 
   1445     __proto__: WebInspector.ProfileHeader.prototype
   1446 }
   1447 
   1448 /**
   1449  * @constructor
   1450  * @implements {WebInspector.OutputStreamDelegate}
   1451  */
   1452 WebInspector.HeapSnapshotLoadFromFileDelegate = function(snapshotHeader)
   1453 {
   1454     this._snapshotHeader = snapshotHeader;
   1455 }
   1456 
   1457 WebInspector.HeapSnapshotLoadFromFileDelegate.prototype = {
   1458     onTransferStarted: function()
   1459     {
   1460     },
   1461 
   1462     /**
   1463      * @param {WebInspector.ChunkedReader} reader
   1464      */
   1465     onChunkTransferred: function(reader)
   1466     {
   1467         this._snapshotHeader._updateTransferProgress(reader.loadedSize(), reader.fileSize());
   1468     },
   1469 
   1470     onTransferFinished: function()
   1471     {
   1472         this._snapshotHeader.finishHeapSnapshot();
   1473     },
   1474 
   1475     /**
   1476      * @param {WebInspector.ChunkedReader} reader
   1477      */
   1478     onError: function (reader, e)
   1479     {
   1480         switch(e.target.error.code) {
   1481         case e.target.error.NOT_FOUND_ERR:
   1482             this._snapshotHeader.sidebarElement.subtitle = WebInspector.UIString("'%s' not found.", reader.fileName());
   1483         break;
   1484         case e.target.error.NOT_READABLE_ERR:
   1485             this._snapshotHeader.sidebarElement.subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName());
   1486         break;
   1487         case e.target.error.ABORT_ERR:
   1488             break;
   1489         default:
   1490             this._snapshotHeader.sidebarElement.subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code);
   1491         }
   1492     }
   1493 }
   1494 
   1495 /**
   1496  * @constructor
   1497  * @extends {WebInspector.View}
   1498  * @param {!WebInspector.HeapProfileHeader} heapProfileHeader
   1499  */
   1500 WebInspector.HeapTrackingOverviewGrid = function(heapProfileHeader)
   1501 {
   1502     WebInspector.View.call(this);
   1503     this.registerRequiredCSS("flameChart.css");
   1504     this.element.id = "heap-recording-view";
   1505 
   1506     this._overviewContainer = this.element.createChild("div", "overview-container");
   1507     this._overviewGrid = new WebInspector.OverviewGrid("heap-recording");
   1508     this._overviewCanvas = this._overviewContainer.createChild("canvas", "heap-recording-overview-canvas");
   1509     this._overviewContainer.appendChild(this._overviewGrid.element);
   1510     this._overviewCalculator = new WebInspector.HeapTrackingOverviewGrid.OverviewCalculator();
   1511     this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
   1512 
   1513     this._profileSamples = heapProfileHeader._profileSamples || heapProfileHeader._profileType._profileSamples;
   1514     if (heapProfileHeader.isTemporary) {
   1515         this._profileType = heapProfileHeader._profileType;
   1516         this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
   1517         this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
   1518     }
   1519     var timestamps = this._profileSamples.timestamps;
   1520     var totalTime = this._profileSamples.totalTime;
   1521     this._windowLeft = 0.0;
   1522     this._windowRight = totalTime && timestamps.length ? (timestamps[timestamps.length - 1] - timestamps[0]) / totalTime : 1.0;
   1523     this._overviewGrid.setWindow(this._windowLeft, this._windowRight);
   1524     this._yScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale();
   1525     this._xScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale();
   1526 }
   1527 
   1528 WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged = "IdsRangeChanged";
   1529 
   1530 WebInspector.HeapTrackingOverviewGrid.prototype = {
   1531     _onStopTracking: function(event)
   1532     {
   1533         this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
   1534         this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
   1535     },
   1536 
   1537     _onHeapStatsUpdate: function(event)
   1538     {
   1539         this._profileSamples = event.data;
   1540         this._scheduleUpdate();
   1541     },
   1542 
   1543      /**
   1544       * @param {number} width
   1545       * @param {number} height
   1546       */
   1547     _drawOverviewCanvas: function(width, height)
   1548     {
   1549         if (!this._profileSamples)
   1550             return;
   1551         var profileSamples = this._profileSamples;
   1552         var sizes = profileSamples.sizes;
   1553         var topSizes = profileSamples.max;
   1554         var timestamps = profileSamples.timestamps;
   1555         var startTime = timestamps[0];
   1556         var endTime = timestamps[timestamps.length - 1];
   1557 
   1558         var scaleFactor = this._xScale.nextScale(width / profileSamples.totalTime);
   1559         var maxSize = 0;
   1560         /**
   1561           * @param {Array.<number>} sizes
   1562           * @param {function(number, number):void} callback
   1563           */
   1564         function aggregateAndCall(sizes, callback)
   1565         {
   1566             var size = 0;
   1567             var currentX = 0;
   1568             for (var i = 1; i < timestamps.length; ++i) {
   1569                 var x = Math.floor((timestamps[i] - startTime) * scaleFactor);
   1570                 if (x !== currentX) {
   1571                     if (size)
   1572                         callback(currentX, size);
   1573                     size = 0;
   1574                     currentX = x;
   1575                 }
   1576                 size += sizes[i];
   1577             }
   1578             callback(currentX, size);
   1579         }
   1580 
   1581         /**
   1582           * @param {number} x
   1583           * @param {number} size
   1584           */
   1585         function maxSizeCallback(x, size)
   1586         {
   1587             maxSize = Math.max(maxSize, size);
   1588         }
   1589 
   1590         aggregateAndCall(sizes, maxSizeCallback);
   1591 
   1592         var yScaleFactor = this._yScale.nextScale(maxSize ? height / (maxSize * 1.1) : 0.0);
   1593 
   1594         this._overviewCanvas.width = width * window.devicePixelRatio;
   1595         this._overviewCanvas.height = height * window.devicePixelRatio;
   1596         this._overviewCanvas.style.width = width + "px";
   1597         this._overviewCanvas.style.height = height + "px";
   1598 
   1599         var context = this._overviewCanvas.getContext("2d");
   1600         context.scale(window.devicePixelRatio, window.devicePixelRatio);
   1601 
   1602         context.beginPath();
   1603         context.lineWidth = 2;
   1604         context.strokeStyle = "rgba(192, 192, 192, 0.6)";
   1605         var currentX = (endTime - startTime) * scaleFactor;
   1606         context.moveTo(currentX, height - 1);
   1607         context.lineTo(currentX, 0);
   1608         context.stroke();
   1609         context.closePath();
   1610 
   1611         var gridY;
   1612         var gridValue;
   1613         var gridLabelHeight = 14;
   1614         if (yScaleFactor) {
   1615             const maxGridValue = (height - gridLabelHeight) / yScaleFactor;
   1616             // The round value calculation is a bit tricky, because
   1617             // it has a form k*10^n*1024^m, where k=[1,5], n=[0..3], m is an integer,
   1618             // e.g. a round value 10KB is 10240 bytes.
   1619             gridValue = Math.pow(1024, Math.floor(Math.log(maxGridValue) / Math.log(1024)));
   1620             gridValue *= Math.pow(10, Math.floor(Math.log(maxGridValue / gridValue) / Math.log(10)));
   1621             if (gridValue * 5 <= maxGridValue)
   1622                 gridValue *= 5;
   1623             gridY = Math.round(height - gridValue * yScaleFactor - 0.5) + 0.5;
   1624             context.beginPath();
   1625             context.lineWidth = 1;
   1626             context.strokeStyle = "rgba(0, 0, 0, 0.2)";
   1627             context.moveTo(0, gridY);
   1628             context.lineTo(width, gridY);
   1629             context.stroke();
   1630             context.closePath();
   1631         }
   1632 
   1633         /**
   1634           * @param {number} x
   1635           * @param {number} size
   1636           */
   1637         function drawBarCallback(x, size)
   1638         {
   1639             context.moveTo(x, height - 1);
   1640             context.lineTo(x, Math.round(height - size * yScaleFactor - 1));
   1641         }
   1642 
   1643         context.beginPath();
   1644         context.lineWidth = 2;
   1645         context.strokeStyle = "rgba(192, 192, 192, 0.6)";
   1646         aggregateAndCall(topSizes, drawBarCallback);
   1647         context.stroke();
   1648         context.closePath();
   1649 
   1650         context.beginPath();
   1651         context.lineWidth = 2;
   1652         context.strokeStyle = "rgba(0, 0, 192, 0.8)";
   1653         aggregateAndCall(sizes, drawBarCallback);
   1654         context.stroke();
   1655         context.closePath();
   1656 
   1657         if (gridValue) {
   1658             var label = Number.bytesToString(gridValue);
   1659             var labelPadding = 4;
   1660             var labelX = 0;
   1661             var labelY = gridY - 0.5;
   1662             var labelWidth = 2 * labelPadding + context.measureText(label).width;
   1663             context.beginPath();
   1664             context.textBaseline = "bottom";
   1665             context.font = "10px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
   1666             context.fillStyle = "rgba(255, 255, 255, 0.75)";
   1667             context.fillRect(labelX, labelY - gridLabelHeight, labelWidth, gridLabelHeight);
   1668             context.fillStyle = "rgb(64, 64, 64)";
   1669             context.fillText(label, labelX + labelPadding, labelY);
   1670             context.fill();
   1671             context.closePath();
   1672         }
   1673     },
   1674 
   1675     onResize: function()
   1676     {
   1677         this._updateOverviewCanvas = true;
   1678         this._scheduleUpdate();
   1679     },
   1680 
   1681     _onWindowChanged: function()
   1682     {
   1683         if (!this._updateGridTimerId)
   1684             this._updateGridTimerId = setTimeout(this._updateGrid.bind(this), 10);
   1685     },
   1686 
   1687     _scheduleUpdate: function()
   1688     {
   1689         if (this._updateTimerId)
   1690             return;
   1691         this._updateTimerId = setTimeout(this.update.bind(this), 10);
   1692     },
   1693 
   1694     _updateBoundaries: function()
   1695     {
   1696         this._windowLeft = this._overviewGrid.windowLeft();
   1697         this._windowRight = this._overviewGrid.windowRight();
   1698         this._windowWidth = this._windowRight - this._windowLeft;
   1699     },
   1700 
   1701     update: function()
   1702     {
   1703         this._updateTimerId = null;
   1704         if (!this.isShowing())
   1705             return;
   1706         this._updateBoundaries();
   1707         this._overviewCalculator._updateBoundaries(this);
   1708         this._overviewGrid.updateDividers(this._overviewCalculator);
   1709         this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20);
   1710     },
   1711 
   1712     _updateGrid: function()
   1713     {
   1714         this._updateGridTimerId = 0;
   1715         this._updateBoundaries();
   1716         var ids = this._profileSamples.ids;
   1717         var timestamps = this._profileSamples.timestamps;
   1718         var sizes = this._profileSamples.sizes;
   1719         var startTime = timestamps[0];
   1720         var totalTime = this._profileSamples.totalTime;
   1721         var timeLeft = startTime + totalTime * this._windowLeft;
   1722         var timeRight = startTime + totalTime * this._windowRight;
   1723         var minId = 0;
   1724         var maxId = ids[ids.length - 1] + 1;
   1725         var size = 0;
   1726         for (var i = 0; i < timestamps.length; ++i) {
   1727             if (!timestamps[i])
   1728                 continue;
   1729             if (timestamps[i] > timeRight)
   1730                 break;
   1731             maxId = ids[i];
   1732             if (timestamps[i] < timeLeft) {
   1733                 minId = ids[i];
   1734                 continue;
   1735             }
   1736             size += sizes[i];
   1737         }
   1738 
   1739         this.dispatchEventToListeners(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, {minId: minId, maxId: maxId, size: size});
   1740     },
   1741 
   1742     __proto__: WebInspector.View.prototype
   1743 }
   1744 
   1745 
   1746 /**
   1747  * @constructor
   1748  */
   1749 WebInspector.HeapTrackingOverviewGrid.SmoothScale = function()
   1750 {
   1751     this._lastUpdate = 0;
   1752     this._currentScale = 0.0;
   1753 }
   1754 
   1755 WebInspector.HeapTrackingOverviewGrid.SmoothScale.prototype = {
   1756     /**
   1757      * @param {number} target
   1758      * @return {number}
   1759      */
   1760     nextScale: function(target) {
   1761         target = target || this._currentScale;
   1762         if (this._currentScale) {
   1763             var now = Date.now();
   1764             var timeDeltaMs = now - this._lastUpdate;
   1765             this._lastUpdate = now;
   1766             var maxChangePerSec = 20;
   1767             var maxChangePerDelta = Math.pow(maxChangePerSec, timeDeltaMs / 1000);
   1768             var scaleChange = target / this._currentScale;
   1769             this._currentScale *= Number.constrain(scaleChange, 1 / maxChangePerDelta, maxChangePerDelta);
   1770         } else
   1771             this._currentScale = target;
   1772         return this._currentScale;
   1773     }
   1774 }
   1775 
   1776 
   1777 /**
   1778  * @constructor
   1779  * @implements {WebInspector.TimelineGrid.Calculator}
   1780  */
   1781 WebInspector.HeapTrackingOverviewGrid.OverviewCalculator = function()
   1782 {
   1783 }
   1784 
   1785 WebInspector.HeapTrackingOverviewGrid.OverviewCalculator.prototype = {
   1786     /**
   1787      * @param {WebInspector.HeapTrackingOverviewGrid} chart
   1788      */
   1789     _updateBoundaries: function(chart)
   1790     {
   1791         this._minimumBoundaries = 0;
   1792         this._maximumBoundaries = chart._profileSamples.totalTime;
   1793         this._xScaleFactor = chart._overviewContainer.clientWidth / this._maximumBoundaries;
   1794     },
   1795 
   1796     /**
   1797      * @param {number} time
   1798      */
   1799     computePosition: function(time)
   1800     {
   1801         return (time - this._minimumBoundaries) * this._xScaleFactor;
   1802     },
   1803 
   1804     formatTime: function(value)
   1805     {
   1806         return Number.secondsToString((value + this._minimumBoundaries) / 1000);
   1807     },
   1808 
   1809     maximumBoundary: function()
   1810     {
   1811         return this._maximumBoundaries;
   1812     },
   1813 
   1814     minimumBoundary: function()
   1815     {
   1816         return this._minimumBoundaries;
   1817     },
   1818 
   1819     zeroTime: function()
   1820     {
   1821         return this._minimumBoundaries;
   1822     },
   1823 
   1824     boundarySpan: function()
   1825     {
   1826         return this._maximumBoundaries - this._minimumBoundaries;
   1827     }
   1828 }
   1829