Home | History | Annotate | Download | only in js
      1 /*
      2  * Copyright (C) 2010 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  * @fileoverview Heap profiler panel implementation.
     33  */
     34 
     35 WebInspector.ProfilesPanel.prototype.addSnapshot = function(snapshot) {
     36     snapshot.title = WebInspector.UIString("Snapshot %d", snapshot.number);
     37     snapshot.typeId = WebInspector.HeapSnapshotProfileType.TypeId;
     38 
     39     var snapshots = WebInspector.HeapSnapshotProfileType.snapshots;
     40     snapshots.push(snapshot);
     41 
     42     snapshot.listIndex = snapshots.length - 1;
     43 
     44     if (WebInspector.CPUProfile)
     45         this.addProfileHeader(WebInspector.HeapSnapshotProfileType.TypeId, snapshot);
     46     else
     47         this.addProfileHeader(snapshot);
     48 
     49     this.dispatchEventToListeners("snapshot added");
     50 }
     51 
     52 
     53 WebInspector.HeapSnapshotView = function(parent, profile)
     54 {
     55     WebInspector.View.call(this);
     56 
     57     this.element.addStyleClass("heap-snapshot-view");
     58 
     59     this.parent = parent;
     60     this.parent.addEventListener("snapshot added", this._updateBaseOptions, this);
     61 
     62     this.showCountAsPercent = false;
     63     this.showSizeAsPercent = false;
     64     this.showCountDeltaAsPercent = false;
     65     this.showSizeDeltaAsPercent = false;
     66 
     67     this.categories = {
     68         code: new WebInspector.ResourceCategory("code", WebInspector.UIString("Code"), "rgb(255,121,0)"),
     69         data: new WebInspector.ResourceCategory("data", WebInspector.UIString("Objects"), "rgb(47,102,236)")
     70     };
     71 
     72     var summaryContainer = document.createElement("div");
     73     summaryContainer.id = "heap-snapshot-summary-container";
     74 
     75     this.countsSummaryBar = new WebInspector.SummaryBar(this.categories);
     76     this.countsSummaryBar.element.className = "heap-snapshot-summary";
     77     this.countsSummaryBar.calculator = new WebInspector.HeapSummaryCountCalculator();
     78     var countsLabel = document.createElement("div");
     79     countsLabel.className = "heap-snapshot-summary-label";
     80     countsLabel.textContent = WebInspector.UIString("Count");
     81     this.countsSummaryBar.element.appendChild(countsLabel);
     82     summaryContainer.appendChild(this.countsSummaryBar.element);
     83 
     84     this.sizesSummaryBar = new WebInspector.SummaryBar(this.categories);
     85     this.sizesSummaryBar.element.className = "heap-snapshot-summary";
     86     this.sizesSummaryBar.calculator = new WebInspector.HeapSummarySizeCalculator();
     87     var sizesLabel = document.createElement("label");
     88     sizesLabel.className = "heap-snapshot-summary-label";
     89     sizesLabel.textContent = WebInspector.UIString("Size");
     90     this.sizesSummaryBar.element.appendChild(sizesLabel);
     91     summaryContainer.appendChild(this.sizesSummaryBar.element);
     92 
     93     this.element.appendChild(summaryContainer);
     94 
     95     var columns = { "cons": { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
     96                     "count": { title: WebInspector.UIString("Count"), width: "54px", sortable: true },
     97                     "size": { title: WebInspector.UIString("Size"), width: "72px", sort: "descending", sortable: true },
     98                     "countDelta": { title: WebInspector.UIString("\xb1 Count"), width: "72px", sortable: true },
     99                     "sizeDelta": { title: WebInspector.UIString("\xb1 Size"), width: "72px", sortable: true } };
    100 
    101     this.dataGrid = new WebInspector.DataGrid(columns);
    102     this.dataGrid.addEventListener("sorting changed", this._sortData, this);
    103     this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
    104     this.element.appendChild(this.dataGrid.element);
    105 
    106     this.profile = profile;
    107 
    108     this.baseSelectElement = document.createElement("select");
    109     this.baseSelectElement.className = "status-bar-item";
    110     this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
    111     this._updateBaseOptions();
    112     if (this.profile.listIndex > 0)
    113         this.baseSelectElement.selectedIndex = this.profile.listIndex - 1;
    114     else
    115         this.baseSelectElement.selectedIndex = this.profile.listIndex;
    116     this._resetDataGridList();
    117 
    118     this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
    119     this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);
    120 
    121     this.refresh();
    122 
    123     this._updatePercentButton();
    124 };
    125 
    126 WebInspector.HeapSnapshotView.prototype = {
    127 
    128     get statusBarItems()
    129     {
    130         return [this.baseSelectElement, this.percentButton.element];
    131     },
    132 
    133     get profile()
    134     {
    135         return this._profile;
    136     },
    137 
    138     set profile(profile)
    139     {
    140         this._profile = profile;
    141     },
    142 
    143     show: function(parentElement)
    144     {
    145         WebInspector.View.prototype.show.call(this, parentElement);
    146         this.dataGrid.updateWidths();
    147     },
    148 
    149     hide: function()
    150     {
    151         WebInspector.View.prototype.hide.call(this);
    152         this._currentSearchResultIndex = -1;
    153     },
    154 
    155     resize: function()
    156     {
    157         if (this.dataGrid)
    158             this.dataGrid.updateWidths();
    159     },
    160 
    161     refresh: function()
    162     {
    163         this.dataGrid.removeChildren();
    164 
    165         var children = this.snapshotDataGridList.children;
    166         var count = children.length;
    167         for (var index = 0; index < count; ++index)
    168             this.dataGrid.appendChild(children[index]);
    169 
    170         this._updateSummaryGraph();
    171     },
    172 
    173     refreshShowAsPercents: function()
    174     {
    175         this._updatePercentButton();
    176         this.refreshVisibleData();
    177     },
    178 
    179     _deleteSearchMatchedFlags: function(node)
    180     {
    181         delete node._searchMatchedConsColumn;
    182         delete node._searchMatchedCountColumn;
    183         delete node._searchMatchedSizeColumn;
    184         delete node._searchMatchedCountDeltaColumn;
    185         delete node._searchMatchedSizeDeltaColumn;
    186     },
    187 
    188     searchCanceled: function()
    189     {
    190         if (this._searchResults) {
    191             for (var i = 0; i < this._searchResults.length; ++i) {
    192                 var profileNode = this._searchResults[i].profileNode;
    193                 this._deleteSearchMatchedFlags(profileNode);
    194                 profileNode.refresh();
    195             }
    196         }
    197 
    198         delete this._searchFinishedCallback;
    199         this._currentSearchResultIndex = -1;
    200         this._searchResults = [];
    201     },
    202 
    203     performSearch: function(query, finishedCallback)
    204     {
    205         // Call searchCanceled since it will reset everything we need before doing a new search.
    206         this.searchCanceled();
    207 
    208         query = query.trimWhitespace();
    209 
    210         if (!query.length)
    211             return;
    212 
    213         this._searchFinishedCallback = finishedCallback;
    214 
    215         var helper = WebInspector.HeapSnapshotView.SearchHelper;
    216 
    217         var operationAndNumber = helper.parseOperationAndNumber(query);
    218         var operation = operationAndNumber[0];
    219         var queryNumber = operationAndNumber[1];
    220 
    221         var percentUnits = helper.percents.test(query);
    222         var megaBytesUnits = helper.megaBytes.test(query);
    223         var kiloBytesUnits = helper.kiloBytes.test(query);
    224         var bytesUnits = helper.bytes.test(query);
    225 
    226         var queryNumberBytes = (megaBytesUnits ? (queryNumber * 1024 * 1024) : (kiloBytesUnits ? (queryNumber * 1024) : queryNumber));
    227 
    228         function matchesQuery(heapSnapshotDataGridNode)
    229         {
    230             WebInspector.HeapSnapshotView.prototype._deleteSearchMatchedFlags(heapSnapshotDataGridNode);
    231 
    232             if (percentUnits) {
    233                 heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.countPercent, queryNumber);
    234                 heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.sizePercent, queryNumber);
    235                 heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDeltaPercent, queryNumber);
    236                 heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDeltaPercent, queryNumber);
    237             } else if (megaBytesUnits || kiloBytesUnits || bytesUnits) {
    238                 heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.size, queryNumberBytes);
    239                 heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDelta, queryNumberBytes);
    240             } else {
    241                 heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.count, queryNumber);
    242                 heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDelta, queryNumber);
    243             }
    244 
    245             if (heapSnapshotDataGridNode.constructorName.hasSubstring(query, true))
    246                 heapSnapshotDataGridNode._searchMatchedConsColumn = true;
    247 
    248             if (heapSnapshotDataGridNode._searchMatchedConsColumn ||
    249                 heapSnapshotDataGridNode._searchMatchedCountColumn ||
    250                 heapSnapshotDataGridNode._searchMatchedSizeColumn ||
    251                 heapSnapshotDataGridNode._searchMatchedCountDeltaColumn ||
    252                 heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn) {
    253                 heapSnapshotDataGridNode.refresh();
    254                 return true;
    255             }
    256 
    257             return false;
    258         }
    259 
    260         var current = this.snapshotDataGridList.children[0];
    261         var depth = 0;
    262         var info = {};
    263 
    264         // The second and subsequent levels of heap snapshot nodes represent retainers,
    265         // so recursive expansion will be infinite, since a graph is being traversed.
    266         // So default to a recursion cap of 2 levels.
    267         var maxDepth = 2;
    268 
    269         while (current) {
    270             if (matchesQuery(current))
    271                 this._searchResults.push({ profileNode: current });
    272             current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
    273             depth += info.depthChange;
    274         }
    275 
    276         finishedCallback(this, this._searchResults.length);
    277     },
    278 
    279     jumpToFirstSearchResult: WebInspector.CPUProfileView.prototype.jumpToFirstSearchResult,
    280     jumpToLastSearchResult: WebInspector.CPUProfileView.prototype.jumpToLastSearchResult,
    281     jumpToNextSearchResult: WebInspector.CPUProfileView.prototype.jumpToNextSearchResult,
    282     jumpToPreviousSearchResult: WebInspector.CPUProfileView.prototype.jumpToPreviousSearchResult,
    283     showingFirstSearchResult: WebInspector.CPUProfileView.prototype.showingFirstSearchResult,
    284     showingLastSearchResult: WebInspector.CPUProfileView.prototype.showingLastSearchResult,
    285     _jumpToSearchResult: WebInspector.CPUProfileView.prototype._jumpToSearchResult,
    286 
    287     refreshVisibleData: function()
    288     {
    289         var child = this.dataGrid.children[0];
    290         while (child) {
    291             child.refresh();
    292             child = child.traverseNextNode(false, null, true);
    293         }
    294         this._updateSummaryGraph();
    295     },
    296 
    297     _changeBase: function() {
    298         if (this.baseSnapshot === WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex])
    299             return;
    300 
    301         this._resetDataGridList();
    302         this.refresh();
    303 
    304         if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
    305             return;
    306 
    307         // The current search needs to be performed again. First negate out previous match
    308         // count by calling the search finished callback with a negative number of matches.
    309         // Then perform the search again with the same query and callback.
    310         this._searchFinishedCallback(this, -this._searchResults.length);
    311         this.performSearch(this.currentQuery, this._searchFinishedCallback);
    312     },
    313 
    314     _createSnapshotDataGridList: function()
    315     {
    316         if (this._snapshotDataGridList)
    317           delete this._snapshotDataGridList;
    318 
    319         this._snapshotDataGridList = new WebInspector.HeapSnapshotDataGridList(this, this.baseSnapshot.entries, this.profile.entries);
    320         return this._snapshotDataGridList;
    321     },
    322 
    323     _mouseDownInDataGrid: function(event)
    324     {
    325         if (event.detail < 2)
    326             return;
    327 
    328         var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
    329         if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("size-column") && !cell.hasStyleClass("countDelta-column") && !cell.hasStyleClass("sizeDelta-column")))
    330             return;
    331 
    332         if (cell.hasStyleClass("count-column"))
    333             this.showCountAsPercent = !this.showCountAsPercent;
    334         else if (cell.hasStyleClass("size-column"))
    335             this.showSizeAsPercent = !this.showSizeAsPercent;
    336         else if (cell.hasStyleClass("countDelta-column"))
    337             this.showCountDeltaAsPercent = !this.showCountDeltaAsPercent;
    338         else if (cell.hasStyleClass("sizeDelta-column"))
    339             this.showSizeDeltaAsPercent = !this.showSizeDeltaAsPercent;
    340 
    341         this.refreshShowAsPercents();
    342 
    343         event.preventDefault();
    344         event.stopPropagation();
    345     },
    346 
    347     get _isShowingAsPercent()
    348     {
    349         return this.showCountAsPercent && this.showSizeAsPercent && this.showCountDeltaAsPercent && this.showSizeDeltaAsPercent;
    350     },
    351 
    352     _percentClicked: function(event)
    353     {
    354         var currentState = this._isShowingAsPercent;
    355         this.showCountAsPercent = !currentState;
    356         this.showSizeAsPercent = !currentState;
    357         this.showCountDeltaAsPercent = !currentState;
    358         this.showSizeDeltaAsPercent = !currentState;
    359         this.refreshShowAsPercents();
    360     },
    361 
    362     _resetDataGridList: function()
    363     {
    364         this.baseSnapshot = WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex];
    365         var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("size", false);
    366         if (this.snapshotDataGridList)
    367             lastComparator = this.snapshotDataGridList.lastComparator;
    368         this.snapshotDataGridList = this._createSnapshotDataGridList();
    369         this.snapshotDataGridList.sort(lastComparator, true);
    370     },
    371 
    372     _sortData: function()
    373     {
    374         var sortAscending = this.dataGrid.sortOrder === "ascending";
    375         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
    376         var sortProperty = {
    377                 "cons": ["constructorName", null],
    378                 "count": ["count", null],
    379                 "size": ["size", "count"],
    380                 "countDelta": this.showCountDeltaAsPercent ? ["countDeltaPercent", null] : ["countDelta", null],
    381                 "sizeDelta": this.showSizeDeltaAsPercent ? ["sizeDeltaPercent", "countDeltaPercent"] : ["sizeDelta", "sizeDeltaPercent"]
    382         }[sortColumnIdentifier];
    383 
    384         this.snapshotDataGridList.sort(WebInspector.HeapSnapshotDataGridList.propertyComparator(sortProperty[0], sortProperty[1], sortAscending));
    385 
    386         this.refresh();
    387     },
    388 
    389     _updateBaseOptions: function()
    390     {
    391         var list = WebInspector.HeapSnapshotProfileType.snapshots;
    392         // We're assuming that snapshots can only be added.
    393         if (this.baseSelectElement.length === list.length)
    394             return;
    395 
    396         for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) {
    397             var baseOption = document.createElement("option");
    398             baseOption.label = WebInspector.UIString("Compared to %s", list[i].title);
    399             this.baseSelectElement.appendChild(baseOption);
    400         }
    401     },
    402 
    403     _updatePercentButton: function()
    404     {
    405         if (this._isShowingAsPercent) {
    406             this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes.");
    407             this.percentButton.toggled = true;
    408         } else {
    409             this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages.");
    410             this.percentButton.toggled = false;
    411         }
    412     },
    413 
    414     _updateSummaryGraph: function()
    415     {
    416         this.countsSummaryBar.calculator.showAsPercent = this._isShowingAsPercent;
    417         this.countsSummaryBar.update(this.profile.lowlevels);
    418 
    419         this.sizesSummaryBar.calculator.showAsPercent = this._isShowingAsPercent;
    420         this.sizesSummaryBar.update(this.profile.lowlevels);
    421     }
    422 };
    423 
    424 WebInspector.HeapSnapshotView.prototype.__proto__ = WebInspector.View.prototype;
    425 
    426 WebInspector.HeapSnapshotView.SearchHelper = {
    427     // In comparators, we assume that a value from a node is passed as the first parameter.
    428     operations: { LESS: function (a, b) { return a !== null && a < b; },
    429                   LESS_OR_EQUAL: function (a, b) { return a !== null && a <= b; },
    430                   EQUAL: function (a, b) { return a !== null && a === b; },
    431                   GREATER_OR_EQUAL: function (a, b) { return a !== null && a >= b; },
    432                   GREATER: function (a, b) { return a !== null && a > b; } },
    433 
    434     operationParsers: { LESS: /^<(\d+)/,
    435                         LESS_OR_EQUAL: /^<=(\d+)/,
    436                         GREATER_OR_EQUAL: /^>=(\d+)/,
    437                         GREATER: /^>(\d+)/ },
    438 
    439     parseOperationAndNumber: function(query)
    440     {
    441         var operations = WebInspector.HeapSnapshotView.SearchHelper.operations;
    442         var parsers = WebInspector.HeapSnapshotView.SearchHelper.operationParsers;
    443         for (var operation in parsers) {
    444             var match = query.match(parsers[operation]);
    445             if (match !== null)
    446                 return [operations[operation], parseFloat(match[1])];
    447         }
    448         return [operations.EQUAL, parseFloat(query)];
    449     },
    450 
    451     percents: /%$/,
    452 
    453     megaBytes: /MB$/i,
    454 
    455     kiloBytes: /KB$/i,
    456 
    457     bytes: /B$/i
    458 }
    459 
    460 WebInspector.HeapSummaryCalculator = function(lowLevelField)
    461 {
    462     this.total = 1;
    463     this.lowLevelField = lowLevelField;
    464 }
    465 
    466 WebInspector.HeapSummaryCalculator.prototype = {
    467     computeSummaryValues: function(lowLevels)
    468     {
    469         var highLevels = {data: 0, code: 0};
    470         this.total = 0;
    471         for (var item in lowLevels) {
    472             var highItem = this._highFromLow(item);
    473             if (highItem) {
    474                 var value = lowLevels[item][this.lowLevelField];
    475                 highLevels[highItem] += value;
    476                 this.total += value;
    477             }
    478         }
    479         var result = {categoryValues: highLevels};
    480         if (!this.showAsPercent)
    481             result.total = this.total;
    482         return result;
    483     },
    484 
    485     formatValue: function(value)
    486     {
    487         if (this.showAsPercent)
    488             return WebInspector.UIString("%.2f%%", value / this.total * 100.0);
    489         else
    490             return this._valueToString(value);
    491     },
    492 
    493     get showAsPercent()
    494     {
    495         return this._showAsPercent;
    496     },
    497 
    498     set showAsPercent(x)
    499     {
    500         this._showAsPercent = x;
    501     }
    502 }
    503 
    504 WebInspector.HeapSummaryCountCalculator = function()
    505 {
    506     WebInspector.HeapSummaryCalculator.call(this, "count");
    507 }
    508 
    509 WebInspector.HeapSummaryCountCalculator.prototype = {
    510     _highFromLow: function(type) {
    511         if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE") return "code";
    512         if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/)) return "data";
    513         return null;
    514     },
    515 
    516     _valueToString: function(value) {
    517         return value.toString();
    518     }
    519 }
    520 
    521 WebInspector.HeapSummaryCountCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;
    522 
    523 WebInspector.HeapSummarySizeCalculator = function()
    524 {
    525     WebInspector.HeapSummaryCalculator.call(this, "size");
    526 }
    527 
    528 WebInspector.HeapSummarySizeCalculator.prototype = {
    529     _highFromLow: function(type) {
    530         if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE") return "code";
    531         if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/) || type.match(/_ARRAY_TYPE$/)) return "data";
    532         return null;
    533     },
    534 
    535     _valueToString: Number.bytesToString
    536 }
    537 
    538 WebInspector.HeapSummarySizeCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;
    539 
    540 WebInspector.HeapSnapshotSidebarTreeElement = function(snapshot)
    541 {
    542     this.profile = snapshot;
    543 
    544     WebInspector.SidebarTreeElement.call(this, "heap-snapshot-sidebar-tree-item", "", "", snapshot, false);
    545 
    546     this.refreshTitles();
    547 };
    548 
    549 WebInspector.HeapSnapshotSidebarTreeElement.prototype = {
    550     get mainTitle()
    551     {
    552         if (this._mainTitle)
    553             return this._mainTitle;
    554         return this.profile.title;
    555     },
    556 
    557     set mainTitle(x)
    558     {
    559         this._mainTitle = x;
    560         this.refreshTitles();
    561     }
    562 };
    563 
    564 WebInspector.HeapSnapshotSidebarTreeElement.prototype.__proto__ = WebInspector.ProfileSidebarTreeElement.prototype;
    565 
    566 WebInspector.HeapSnapshotDataGridNodeWithRetainers = function(owningTree)
    567 {
    568     this.tree = owningTree;
    569 
    570     WebInspector.DataGridNode.call(this, null, this._hasRetainers);
    571 
    572     this.addEventListener("populate", this._populate, this);
    573 };
    574 
    575 WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype = {
    576     isEmptySet: function(set)
    577     {
    578         for (var x in set)
    579             return false;
    580         return true;
    581     },
    582 
    583     get _hasRetainers()
    584     {
    585         return !this.isEmptySet(this.retainers);
    586     },
    587 
    588     get _parent()
    589     {
    590         // For top-level nodes, return owning tree as a parent, not data grid.
    591         return this.parent !== this.dataGrid ? this.parent : this.tree;
    592     },
    593 
    594     _populate: function(event)
    595     {
    596         var self = this;
    597         this.produceDiff(this.baseRetainers, this.retainers, function(baseItem, snapshotItem) {
    598             self.appendChild(new WebInspector.HeapSnapshotDataGridRetainerNode(self.snapshotView, baseItem, snapshotItem, self.tree));
    599         });
    600 
    601         if (this._parent) {
    602             var currentComparator = this._parent.lastComparator;
    603             if (currentComparator)
    604                 this.sort(currentComparator, true);
    605         }
    606 
    607         this.removeEventListener("populate", this._populate, this);
    608     },
    609 
    610     produceDiff: function(baseEntries, currentEntries, callback)
    611     {
    612         for (var item in currentEntries)
    613             callback(baseEntries[item], currentEntries[item]);
    614 
    615         for (item in baseEntries) {
    616             if (!(item in currentEntries))
    617                 callback(baseEntries[item], null);
    618         }
    619     },
    620 
    621     sort: function(comparator, force) {
    622         if (!force && this.lastComparator === comparator)
    623             return;
    624 
    625         this.children.sort(comparator);
    626         var childCount = this.children.length;
    627         for (var childIndex = 0; childIndex < childCount; ++childIndex)
    628             this.children[childIndex]._recalculateSiblings(childIndex);
    629         for (var i = 0; i < this.children.length; ++i) {
    630             var child = this.children[i];
    631             if (!force && (!child.expanded || child.lastComparator === comparator))
    632                 continue;
    633             child.sort(comparator, force);
    634         }
    635         this.lastComparator = comparator;
    636     },
    637 
    638     signForDelta: function(delta) {
    639         if (delta === 0)
    640             return "";
    641         if (delta > 0)
    642             return "+";
    643         else
    644             // Math minus sign, same width as plus.
    645             return "\u2212";
    646     },
    647 
    648     showDeltaAsPercent: function(value) {
    649         if (value === Number.POSITIVE_INFINITY)
    650             return WebInspector.UIString("new");
    651         else if (value === Number.NEGATIVE_INFINITY)
    652             return WebInspector.UIString("deleted");
    653         if (value > 1000.0)
    654             return WebInspector.UIString("%s >1000%%", this.signForDelta(value));
    655         return WebInspector.UIString("%s%.2f%%", this.signForDelta(value), Math.abs(value));
    656     },
    657 
    658     getTotalCount: function() {
    659         if (!this._count) {
    660             this._count = 0;
    661             for (var i = 0, n = this.children.length; i < n; ++i)
    662                 this._count += this.children[i].count;
    663         }
    664         return this._count;
    665     },
    666 
    667     getTotalSize: function() {
    668         if (!this._size) {
    669             this._size = 0;
    670             for (var i = 0, n = this.children.length; i < n; ++i)
    671                 this._size += this.children[i].size;
    672         }
    673         return this._size;
    674     },
    675 
    676     get countPercent()
    677     {
    678         return this.count / this._parent.getTotalCount() * 100.0;
    679     },
    680 
    681     get sizePercent()
    682     {
    683         return this.size / this._parent.getTotalSize() * 100.0;
    684     },
    685 
    686     get countDeltaPercent()
    687     {
    688         if (this.baseCount > 0) {
    689             if (this.count > 0)
    690                 return this.countDelta / this.baseCount * 100.0;
    691             else
    692                 return Number.NEGATIVE_INFINITY;
    693         } else
    694             return Number.POSITIVE_INFINITY;
    695     },
    696 
    697     get sizeDeltaPercent()
    698     {
    699         if (this.baseSize > 0) {
    700             if (this.size > 0)
    701                 return this.sizeDelta / this.baseSize * 100.0;
    702             else
    703                 return Number.NEGATIVE_INFINITY;
    704         } else
    705             return Number.POSITIVE_INFINITY;
    706     },
    707 
    708     get data()
    709     {
    710         var data = {};
    711 
    712         data["cons"] = this.constructorName;
    713 
    714         if (this.snapshotView.showCountAsPercent)
    715             data["count"] = WebInspector.UIString("%.2f%%", this.countPercent);
    716         else
    717             data["count"] = this.count;
    718 
    719         if (this.size !== null) {
    720             if (this.snapshotView.showSizeAsPercent)
    721                 data["size"] = WebInspector.UIString("%.2f%%", this.sizePercent);
    722             else
    723                 data["size"] = Number.bytesToString(this.size);
    724         } else
    725             data["size"] = "";
    726 
    727         if (this.snapshotView.showCountDeltaAsPercent)
    728             data["countDelta"] = this.showDeltaAsPercent(this.countDeltaPercent);
    729         else
    730             data["countDelta"] = WebInspector.UIString("%s%d", this.signForDelta(this.countDelta), Math.abs(this.countDelta));
    731 
    732         if (this.sizeDelta !== null) {
    733             if (this.snapshotView.showSizeDeltaAsPercent)
    734                 data["sizeDelta"] = this.showDeltaAsPercent(this.sizeDeltaPercent);
    735             else
    736                 data["sizeDelta"] = WebInspector.UIString("%s%s", this.signForDelta(this.sizeDelta), Number.bytesToString(Math.abs(this.sizeDelta)));
    737         } else
    738             data["sizeDelta"] = "";
    739 
    740         return data;
    741     },
    742 
    743     createCell: function(columnIdentifier)
    744     {
    745         var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
    746 
    747         if ((columnIdentifier === "cons" && this._searchMatchedConsColumn) ||
    748             (columnIdentifier === "count" && this._searchMatchedCountColumn) ||
    749             (columnIdentifier === "size" && this._searchMatchedSizeColumn) ||
    750             (columnIdentifier === "countDelta" && this._searchMatchedCountDeltaColumn) ||
    751             (columnIdentifier === "sizeDelta" && this._searchMatchedSizeDeltaColumn))
    752             cell.addStyleClass("highlight");
    753 
    754         return cell;
    755     }
    756 };
    757 
    758 WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.__proto__ = WebInspector.DataGridNode.prototype;
    759 
    760 WebInspector.HeapSnapshotDataGridNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
    761 {
    762     this.snapshotView = snapshotView;
    763 
    764     if (!snapshotEntry)
    765         snapshotEntry = { cons: baseEntry.cons, count: 0, size: 0, retainers: {} };
    766     this.constructorName = snapshotEntry.cons;
    767     this.count = snapshotEntry.count;
    768     this.size = snapshotEntry.size;
    769     this.retainers = snapshotEntry.retainers;
    770 
    771     if (!baseEntry)
    772         baseEntry = { count: 0, size: 0, retainers: {} };
    773     this.baseCount = baseEntry.count;
    774     this.countDelta = this.count - this.baseCount;
    775     this.baseSize = baseEntry.size;
    776     this.sizeDelta = this.size - this.baseSize;
    777     this.baseRetainers = baseEntry.retainers;
    778 
    779     WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
    780 };
    781 
    782 WebInspector.HeapSnapshotDataGridNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;
    783 
    784 WebInspector.HeapSnapshotDataGridList = function(snapshotView, baseEntries, snapshotEntries)
    785 {
    786     this.tree = this;
    787     this.snapshotView = snapshotView;
    788     this.children = [];
    789     this.lastComparator = null;
    790     this.populateChildren(baseEntries, snapshotEntries);
    791 };
    792 
    793 WebInspector.HeapSnapshotDataGridList.prototype = {
    794     appendChild: function(child)
    795     {
    796         this.insertChild(child, this.children.length);
    797     },
    798 
    799     insertChild: function(child, index)
    800     {
    801         this.children.splice(index, 0, child);
    802     },
    803 
    804     removeChildren: function()
    805     {
    806         this.children = [];
    807     },
    808 
    809     populateChildren: function(baseEntries, snapshotEntries)
    810     {
    811         var self = this;
    812         this.produceDiff(baseEntries, snapshotEntries, function(baseItem, snapshotItem) {
    813             self.appendChild(new WebInspector.HeapSnapshotDataGridNode(self.snapshotView, baseItem, snapshotItem, self));
    814         });
    815     },
    816 
    817     produceDiff: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.produceDiff,
    818     sort: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.sort,
    819     getTotalCount: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalCount,
    820     getTotalSize: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalSize
    821 };
    822 
    823 WebInspector.HeapSnapshotDataGridList.propertyComparators = [{}, {}];
    824 
    825 WebInspector.HeapSnapshotDataGridList.propertyComparator = function(property, property2, isAscending)
    826 {
    827     var propertyHash = property + "#" + property2;
    828     var comparator = this.propertyComparators[(isAscending ? 1 : 0)][propertyHash];
    829     if (!comparator) {
    830         comparator = function(lhs, rhs) {
    831             var l = lhs[property], r = rhs[property];
    832             if ((l === null || r === null) && property2 !== null)
    833                 l = lhs[property2], r = rhs[property2];
    834             var result = l < r ? -1 : (l > r ? 1 : 0);
    835             return isAscending ? result : -result;
    836         };
    837         this.propertyComparators[(isAscending ? 1 : 0)][propertyHash] = comparator;
    838     }
    839     return comparator;
    840 };
    841 
    842 WebInspector.HeapSnapshotDataGridRetainerNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
    843 {
    844     this.snapshotView = snapshotView;
    845 
    846     if (!snapshotEntry)
    847         snapshotEntry = { cons: baseEntry.cons, count: 0, clusters: {} };
    848     this.constructorName = snapshotEntry.cons;
    849     this.count = snapshotEntry.count;
    850     this.retainers = this._calculateRetainers(this.snapshotView.profile, snapshotEntry.clusters);
    851 
    852     if (!baseEntry)
    853         baseEntry = { count: 0, clusters: {} };
    854     this.baseCount = baseEntry.count;
    855     this.countDelta = this.count - this.baseCount;
    856     this.baseRetainers = this._calculateRetainers(this.snapshotView.baseSnapshot, baseEntry.clusters);
    857 
    858     this.size = null;
    859     this.sizeDelta = null;
    860 
    861     WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
    862 }
    863 
    864 WebInspector.HeapSnapshotDataGridRetainerNode.prototype = {
    865     get sizePercent()
    866     {
    867         return null;
    868     },
    869 
    870     get sizeDeltaPercent()
    871     {
    872         return null;
    873     },
    874 
    875     _calculateRetainers: function(snapshot, clusters) {
    876         var retainers = {};
    877         if (this.isEmptySet(clusters)) {
    878             if (this.constructorName in snapshot.entries)
    879                 return snapshot.entries[this.constructorName].retainers;
    880         } else {
    881             // In case when an entry is retained by clusters, we need to gather up the list
    882             // of retainers by merging retainers of every cluster.
    883             // E.g. having such a tree:
    884             //   A
    885             //     Object:1  10
    886             //       X       3
    887             //       Y       4
    888             //     Object:2  5
    889             //       X       6
    890             //
    891             // will result in a following retainers list: X 9, Y 4.
    892             for (var clusterName in clusters) {
    893                 if (clusterName in snapshot.clusters) {
    894                     var clusterRetainers = snapshot.clusters[clusterName].retainers;
    895                     for (var clusterRetainer in clusterRetainers) {
    896                         var clusterRetainerEntry = clusterRetainers[clusterRetainer];
    897                         if (!(clusterRetainer in retainers))
    898                             retainers[clusterRetainer] = { cons: clusterRetainerEntry.cons, count: 0, clusters: {} };
    899                         retainers[clusterRetainer].count += clusterRetainerEntry.count;
    900                         for (var clusterRetainerCluster in clusterRetainerEntry.clusters)
    901                             retainers[clusterRetainer].clusters[clusterRetainerCluster] = true;
    902                     }
    903                 }
    904             }
    905         }
    906         return retainers;
    907     }
    908 };
    909 
    910 WebInspector.HeapSnapshotDataGridRetainerNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;
    911 
    912 
    913 WebInspector.HeapSnapshotProfileType = function()
    914 {
    915     WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS"));
    916 }
    917 
    918 WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
    919 
    920 WebInspector.HeapSnapshotProfileType.snapshots = [];
    921 
    922 WebInspector.HeapSnapshotProfileType.prototype = {
    923     get buttonTooltip()
    924     {
    925         return WebInspector.UIString("Take heap snapshot.");
    926     },
    927 
    928     get buttonStyle()
    929     {
    930         return "heap-snapshot-status-bar-item status-bar-item";
    931     },
    932 
    933     buttonClicked: function()
    934     {
    935         InspectorBackend.takeHeapSnapshot();
    936     },
    937 
    938     get welcomeMessage()
    939     {
    940         return WebInspector.UIString("Get a heap snapshot by pressing<br>the %s button on the status bar.");
    941     },
    942 
    943     createSidebarTreeElementForProfile: function(profile)
    944     {
    945         var element = new WebInspector.HeapSnapshotSidebarTreeElement(profile);
    946         element.small = false;
    947         return element;
    948     },
    949 
    950     createView: function(profile)
    951     {
    952         return new WebInspector.HeapSnapshotView(WebInspector.panels.profiles, profile);
    953     }
    954 }
    955 
    956 WebInspector.HeapSnapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;
    957 
    958 
    959 (function() {
    960     var originalCreatePanels = WebInspector._createPanels;
    961     WebInspector._createPanels = function() {
    962         originalCreatePanels.apply(this, arguments);
    963         if (WebInspector.panels.profiles)
    964             WebInspector.panels.profiles.registerProfileType(new WebInspector.HeapSnapshotProfileType());
    965     }
    966 })();
    967