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