• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
35WebInspector.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
53WebInspector.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
126WebInspector.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
424WebInspector.HeapSnapshotView.prototype.__proto__ = WebInspector.View.prototype;
425
426WebInspector.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
460WebInspector.HeapSummaryCalculator = function(lowLevelField)
461{
462    this.total = 1;
463    this.lowLevelField = lowLevelField;
464}
465
466WebInspector.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
504WebInspector.HeapSummaryCountCalculator = function()
505{
506    WebInspector.HeapSummaryCalculator.call(this, "count");
507}
508
509WebInspector.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
521WebInspector.HeapSummaryCountCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;
522
523WebInspector.HeapSummarySizeCalculator = function()
524{
525    WebInspector.HeapSummaryCalculator.call(this, "size");
526}
527
528WebInspector.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
538WebInspector.HeapSummarySizeCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;
539
540WebInspector.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
549WebInspector.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
564WebInspector.HeapSnapshotSidebarTreeElement.prototype.__proto__ = WebInspector.ProfileSidebarTreeElement.prototype;
565
566WebInspector.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
575WebInspector.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
758WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.__proto__ = WebInspector.DataGridNode.prototype;
759
760WebInspector.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
782WebInspector.HeapSnapshotDataGridNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;
783
784WebInspector.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
793WebInspector.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
823WebInspector.HeapSnapshotDataGridList.propertyComparators = [{}, {}];
824
825WebInspector.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
842WebInspector.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
864WebInspector.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
910WebInspector.HeapSnapshotDataGridRetainerNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;
911
912
913WebInspector.HeapSnapshotProfileType = function()
914{
915    WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS"));
916}
917
918WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
919
920WebInspector.HeapSnapshotProfileType.snapshots = [];
921
922WebInspector.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
956WebInspector.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