--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/WebKit/chromium/src/js/HeapProfilerPanel.js Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,966 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @fileoverview Heap profiler panel implementation.
+ */
+
+WebInspector.ProfilesPanel.prototype.addSnapshot = function(snapshot) {
+ snapshot.title = WebInspector.UIString("Snapshot %d", snapshot.number);
+ snapshot.typeId = WebInspector.HeapSnapshotProfileType.TypeId;
+
+ var snapshots = WebInspector.HeapSnapshotProfileType.snapshots;
+ snapshots.push(snapshot);
+
+ snapshot.listIndex = snapshots.length - 1;
+
+ if (WebInspector.CPUProfile)
+ this.addProfileHeader(WebInspector.HeapSnapshotProfileType.TypeId, snapshot);
+ else
+ this.addProfileHeader(snapshot);
+
+ this.dispatchEventToListeners("snapshot added");
+}
+
+
+WebInspector.HeapSnapshotView = function(parent, profile)
+{
+ WebInspector.View.call(this);
+
+ this.element.addStyleClass("heap-snapshot-view");
+
+ this.parent = parent;
+ this.parent.addEventListener("snapshot added", this._updateBaseOptions, this);
+
+ this.showCountAsPercent = false;
+ this.showSizeAsPercent = false;
+ this.showCountDeltaAsPercent = false;
+ this.showSizeDeltaAsPercent = false;
+
+ this.categories = {
+ code: new WebInspector.ResourceCategory("code", WebInspector.UIString("Code"), "rgb(255,121,0)"),
+ data: new WebInspector.ResourceCategory("data", WebInspector.UIString("Objects"), "rgb(47,102,236)")
+ };
+
+ var summaryContainer = document.createElement("div");
+ summaryContainer.id = "heap-snapshot-summary-container";
+
+ this.countsSummaryBar = new WebInspector.SummaryBar(this.categories);
+ this.countsSummaryBar.element.className = "heap-snapshot-summary";
+ this.countsSummaryBar.calculator = new WebInspector.HeapSummaryCountCalculator();
+ var countsLabel = document.createElement("div");
+ countsLabel.className = "heap-snapshot-summary-label";
+ countsLabel.textContent = WebInspector.UIString("Count");
+ this.countsSummaryBar.element.appendChild(countsLabel);
+ summaryContainer.appendChild(this.countsSummaryBar.element);
+
+ this.sizesSummaryBar = new WebInspector.SummaryBar(this.categories);
+ this.sizesSummaryBar.element.className = "heap-snapshot-summary";
+ this.sizesSummaryBar.calculator = new WebInspector.HeapSummarySizeCalculator();
+ var sizesLabel = document.createElement("label");
+ sizesLabel.className = "heap-snapshot-summary-label";
+ sizesLabel.textContent = WebInspector.UIString("Size");
+ this.sizesSummaryBar.element.appendChild(sizesLabel);
+ summaryContainer.appendChild(this.sizesSummaryBar.element);
+
+ this.element.appendChild(summaryContainer);
+
+ var columns = { "cons": { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
+ "count": { title: WebInspector.UIString("Count"), width: "54px", sortable: true },
+ "size": { title: WebInspector.UIString("Size"), width: "72px", sort: "descending", sortable: true },
+ "countDelta": { title: WebInspector.UIString("\xb1 Count"), width: "72px", sortable: true },
+ "sizeDelta": { title: WebInspector.UIString("\xb1 Size"), width: "72px", sortable: true } };
+
+ this.dataGrid = new WebInspector.DataGrid(columns);
+ this.dataGrid.addEventListener("sorting changed", this._sortData, this);
+ this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
+ this.element.appendChild(this.dataGrid.element);
+
+ this.profile = profile;
+
+ this.baseSelectElement = document.createElement("select");
+ this.baseSelectElement.className = "status-bar-item";
+ this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
+ this._updateBaseOptions();
+ if (this.profile.listIndex > 0)
+ this.baseSelectElement.selectedIndex = this.profile.listIndex - 1;
+ else
+ this.baseSelectElement.selectedIndex = this.profile.listIndex;
+ this._resetDataGridList();
+
+ this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
+ this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);
+
+ this.refresh();
+
+ this._updatePercentButton();
+};
+
+WebInspector.HeapSnapshotView.prototype = {
+
+ get statusBarItems()
+ {
+ return [this.baseSelectElement, this.percentButton.element];
+ },
+
+ get profile()
+ {
+ return this._profile;
+ },
+
+ set profile(profile)
+ {
+ this._profile = profile;
+ },
+
+ show: function(parentElement)
+ {
+ WebInspector.View.prototype.show.call(this, parentElement);
+ this.dataGrid.updateWidths();
+ },
+
+ hide: function()
+ {
+ WebInspector.View.prototype.hide.call(this);
+ this._currentSearchResultIndex = -1;
+ },
+
+ resize: function()
+ {
+ if (this.dataGrid)
+ this.dataGrid.updateWidths();
+ },
+
+ refresh: function()
+ {
+ this.dataGrid.removeChildren();
+
+ var children = this.snapshotDataGridList.children;
+ var count = children.length;
+ for (var index = 0; index < count; ++index)
+ this.dataGrid.appendChild(children[index]);
+
+ this._updateSummaryGraph();
+ },
+
+ refreshShowAsPercents: function()
+ {
+ this._updatePercentButton();
+ this.refreshVisibleData();
+ },
+
+ _deleteSearchMatchedFlags: function(node)
+ {
+ delete node._searchMatchedConsColumn;
+ delete node._searchMatchedCountColumn;
+ delete node._searchMatchedSizeColumn;
+ delete node._searchMatchedCountDeltaColumn;
+ delete node._searchMatchedSizeDeltaColumn;
+ },
+
+ searchCanceled: function()
+ {
+ if (this._searchResults) {
+ for (var i = 0; i < this._searchResults.length; ++i) {
+ var profileNode = this._searchResults[i].profileNode;
+ this._deleteSearchMatchedFlags(profileNode);
+ profileNode.refresh();
+ }
+ }
+
+ delete this._searchFinishedCallback;
+ this._currentSearchResultIndex = -1;
+ this._searchResults = [];
+ },
+
+ performSearch: function(query, finishedCallback)
+ {
+ // Call searchCanceled since it will reset everything we need before doing a new search.
+ this.searchCanceled();
+
+ query = query.trim();
+
+ if (!query.length)
+ return;
+
+ this._searchFinishedCallback = finishedCallback;
+
+ var helper = WebInspector.HeapSnapshotView.SearchHelper;
+
+ var operationAndNumber = helper.parseOperationAndNumber(query);
+ var operation = operationAndNumber[0];
+ var queryNumber = operationAndNumber[1];
+
+ var percentUnits = helper.percents.test(query);
+ var megaBytesUnits = helper.megaBytes.test(query);
+ var kiloBytesUnits = helper.kiloBytes.test(query);
+ var bytesUnits = helper.bytes.test(query);
+
+ var queryNumberBytes = (megaBytesUnits ? (queryNumber * 1024 * 1024) : (kiloBytesUnits ? (queryNumber * 1024) : queryNumber));
+
+ function matchesQuery(heapSnapshotDataGridNode)
+ {
+ WebInspector.HeapSnapshotView.prototype._deleteSearchMatchedFlags(heapSnapshotDataGridNode);
+
+ if (percentUnits) {
+ heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.countPercent, queryNumber);
+ heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.sizePercent, queryNumber);
+ heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDeltaPercent, queryNumber);
+ heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDeltaPercent, queryNumber);
+ } else if (megaBytesUnits || kiloBytesUnits || bytesUnits) {
+ heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.size, queryNumberBytes);
+ heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDelta, queryNumberBytes);
+ } else {
+ heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.count, queryNumber);
+ heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDelta, queryNumber);
+ }
+
+ if (heapSnapshotDataGridNode.constructorName.hasSubstring(query, true))
+ heapSnapshotDataGridNode._searchMatchedConsColumn = true;
+
+ if (heapSnapshotDataGridNode._searchMatchedConsColumn ||
+ heapSnapshotDataGridNode._searchMatchedCountColumn ||
+ heapSnapshotDataGridNode._searchMatchedSizeColumn ||
+ heapSnapshotDataGridNode._searchMatchedCountDeltaColumn ||
+ heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn) {
+ heapSnapshotDataGridNode.refresh();
+ return true;
+ }
+
+ return false;
+ }
+
+ var current = this.snapshotDataGridList.children[0];
+ var depth = 0;
+ var info = {};
+
+ // The second and subsequent levels of heap snapshot nodes represent retainers,
+ // so recursive expansion will be infinite, since a graph is being traversed.
+ // So default to a recursion cap of 2 levels.
+ var maxDepth = 2;
+
+ while (current) {
+ if (matchesQuery(current))
+ this._searchResults.push({ profileNode: current });
+ current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
+ depth += info.depthChange;
+ }
+
+ finishedCallback(this, this._searchResults.length);
+ },
+
+ jumpToFirstSearchResult: WebInspector.CPUProfileView.prototype.jumpToFirstSearchResult,
+ jumpToLastSearchResult: WebInspector.CPUProfileView.prototype.jumpToLastSearchResult,
+ jumpToNextSearchResult: WebInspector.CPUProfileView.prototype.jumpToNextSearchResult,
+ jumpToPreviousSearchResult: WebInspector.CPUProfileView.prototype.jumpToPreviousSearchResult,
+ showingFirstSearchResult: WebInspector.CPUProfileView.prototype.showingFirstSearchResult,
+ showingLastSearchResult: WebInspector.CPUProfileView.prototype.showingLastSearchResult,
+ _jumpToSearchResult: WebInspector.CPUProfileView.prototype._jumpToSearchResult,
+
+ refreshVisibleData: function()
+ {
+ var child = this.dataGrid.children[0];
+ while (child) {
+ child.refresh();
+ child = child.traverseNextNode(false, null, true);
+ }
+ this._updateSummaryGraph();
+ },
+
+ _changeBase: function() {
+ if (this.baseSnapshot === WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex])
+ return;
+
+ this._resetDataGridList();
+ this.refresh();
+
+ if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
+ return;
+
+ // The current search needs to be performed again. First negate out previous match
+ // count by calling the search finished callback with a negative number of matches.
+ // Then perform the search again with the same query and callback.
+ this._searchFinishedCallback(this, -this._searchResults.length);
+ this.performSearch(this.currentQuery, this._searchFinishedCallback);
+ },
+
+ _createSnapshotDataGridList: function()
+ {
+ if (this._snapshotDataGridList)
+ delete this._snapshotDataGridList;
+
+ this._snapshotDataGridList = new WebInspector.HeapSnapshotDataGridList(this, this.baseSnapshot.entries, this.profile.entries);
+ return this._snapshotDataGridList;
+ },
+
+ _mouseDownInDataGrid: function(event)
+ {
+ if (event.detail < 2)
+ return;
+
+ var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
+ if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("size-column") && !cell.hasStyleClass("countDelta-column") && !cell.hasStyleClass("sizeDelta-column")))
+ return;
+
+ if (cell.hasStyleClass("count-column"))
+ this.showCountAsPercent = !this.showCountAsPercent;
+ else if (cell.hasStyleClass("size-column"))
+ this.showSizeAsPercent = !this.showSizeAsPercent;
+ else if (cell.hasStyleClass("countDelta-column"))
+ this.showCountDeltaAsPercent = !this.showCountDeltaAsPercent;
+ else if (cell.hasStyleClass("sizeDelta-column"))
+ this.showSizeDeltaAsPercent = !this.showSizeDeltaAsPercent;
+
+ this.refreshShowAsPercents();
+
+ event.preventDefault();
+ event.stopPropagation();
+ },
+
+ get _isShowingAsPercent()
+ {
+ return this.showCountAsPercent && this.showSizeAsPercent && this.showCountDeltaAsPercent && this.showSizeDeltaAsPercent;
+ },
+
+ _percentClicked: function(event)
+ {
+ var currentState = this._isShowingAsPercent;
+ this.showCountAsPercent = !currentState;
+ this.showSizeAsPercent = !currentState;
+ this.showCountDeltaAsPercent = !currentState;
+ this.showSizeDeltaAsPercent = !currentState;
+ this.refreshShowAsPercents();
+ },
+
+ _resetDataGridList: function()
+ {
+ this.baseSnapshot = WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex];
+ var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("size", false);
+ if (this.snapshotDataGridList)
+ lastComparator = this.snapshotDataGridList.lastComparator;
+ this.snapshotDataGridList = this._createSnapshotDataGridList();
+ this.snapshotDataGridList.sort(lastComparator, true);
+ },
+
+ _sortData: function()
+ {
+ var sortAscending = this.dataGrid.sortOrder === "ascending";
+ var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
+ var sortProperty = {
+ "cons": ["constructorName", null],
+ "count": ["count", null],
+ "size": ["size", "count"],
+ "countDelta": this.showCountDeltaAsPercent ? ["countDeltaPercent", null] : ["countDelta", null],
+ "sizeDelta": this.showSizeDeltaAsPercent ? ["sizeDeltaPercent", "countDeltaPercent"] : ["sizeDelta", "sizeDeltaPercent"]
+ }[sortColumnIdentifier];
+
+ this.snapshotDataGridList.sort(WebInspector.HeapSnapshotDataGridList.propertyComparator(sortProperty[0], sortProperty[1], sortAscending));
+
+ this.refresh();
+ },
+
+ _updateBaseOptions: function()
+ {
+ var list = WebInspector.HeapSnapshotProfileType.snapshots;
+ // We're assuming that snapshots can only be added.
+ if (this.baseSelectElement.length === list.length)
+ return;
+
+ for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) {
+ var baseOption = document.createElement("option");
+ baseOption.label = WebInspector.UIString("Compared to %s", list[i].title);
+ this.baseSelectElement.appendChild(baseOption);
+ }
+ },
+
+ _updatePercentButton: function()
+ {
+ if (this._isShowingAsPercent) {
+ this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes.");
+ this.percentButton.toggled = true;
+ } else {
+ this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages.");
+ this.percentButton.toggled = false;
+ }
+ },
+
+ _updateSummaryGraph: function()
+ {
+ this.countsSummaryBar.calculator.showAsPercent = this._isShowingAsPercent;
+ this.countsSummaryBar.update(this.profile.lowlevels);
+
+ this.sizesSummaryBar.calculator.showAsPercent = this._isShowingAsPercent;
+ this.sizesSummaryBar.update(this.profile.lowlevels);
+ }
+};
+
+WebInspector.HeapSnapshotView.prototype.__proto__ = WebInspector.View.prototype;
+
+WebInspector.HeapSnapshotView.SearchHelper = {
+ // In comparators, we assume that a value from a node is passed as the first parameter.
+ operations: { LESS: function (a, b) { return a !== null && a < b; },
+ LESS_OR_EQUAL: function (a, b) { return a !== null && a <= b; },
+ EQUAL: function (a, b) { return a !== null && a === b; },
+ GREATER_OR_EQUAL: function (a, b) { return a !== null && a >= b; },
+ GREATER: function (a, b) { return a !== null && a > b; } },
+
+ operationParsers: { LESS: /^<(\d+)/,
+ LESS_OR_EQUAL: /^<=(\d+)/,
+ GREATER_OR_EQUAL: /^>=(\d+)/,
+ GREATER: /^>(\d+)/ },
+
+ parseOperationAndNumber: function(query)
+ {
+ var operations = WebInspector.HeapSnapshotView.SearchHelper.operations;
+ var parsers = WebInspector.HeapSnapshotView.SearchHelper.operationParsers;
+ for (var operation in parsers) {
+ var match = query.match(parsers[operation]);
+ if (match !== null)
+ return [operations[operation], parseFloat(match[1])];
+ }
+ return [operations.EQUAL, parseFloat(query)];
+ },
+
+ percents: /%$/,
+
+ megaBytes: /MB$/i,
+
+ kiloBytes: /KB$/i,
+
+ bytes: /B$/i
+}
+
+WebInspector.HeapSummaryCalculator = function(lowLevelField)
+{
+ this.total = 1;
+ this.lowLevelField = lowLevelField;
+}
+
+WebInspector.HeapSummaryCalculator.prototype = {
+ computeSummaryValues: function(lowLevels)
+ {
+ var highLevels = {data: 0, code: 0};
+ this.total = 0;
+ for (var item in lowLevels) {
+ var highItem = this._highFromLow(item);
+ if (highItem) {
+ var value = lowLevels[item][this.lowLevelField];
+ highLevels[highItem] += value;
+ this.total += value;
+ }
+ }
+ var result = {categoryValues: highLevels};
+ if (!this.showAsPercent)
+ result.total = this.total;
+ return result;
+ },
+
+ formatValue: function(value)
+ {
+ if (this.showAsPercent)
+ return WebInspector.UIString("%.2f%%", value / this.total * 100.0);
+ else
+ return this._valueToString(value);
+ },
+
+ get showAsPercent()
+ {
+ return this._showAsPercent;
+ },
+
+ set showAsPercent(x)
+ {
+ this._showAsPercent = x;
+ }
+}
+
+WebInspector.HeapSummaryCountCalculator = function()
+{
+ WebInspector.HeapSummaryCalculator.call(this, "count");
+}
+
+WebInspector.HeapSummaryCountCalculator.prototype = {
+ _highFromLow: function(type) {
+ if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE") return "code";
+ if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/)) return "data";
+ return null;
+ },
+
+ _valueToString: function(value) {
+ return value.toString();
+ }
+}
+
+WebInspector.HeapSummaryCountCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;
+
+WebInspector.HeapSummarySizeCalculator = function()
+{
+ WebInspector.HeapSummaryCalculator.call(this, "size");
+}
+
+WebInspector.HeapSummarySizeCalculator.prototype = {
+ _highFromLow: function(type) {
+ if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE") return "code";
+ if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/) || type.match(/_ARRAY_TYPE$/)) return "data";
+ return null;
+ },
+
+ _valueToString: Number.bytesToString
+}
+
+WebInspector.HeapSummarySizeCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;
+
+WebInspector.HeapSnapshotSidebarTreeElement = function(snapshot)
+{
+ this.profile = snapshot;
+
+ WebInspector.SidebarTreeElement.call(this, "heap-snapshot-sidebar-tree-item", "", "", snapshot, false);
+
+ this.refreshTitles();
+};
+
+WebInspector.HeapSnapshotSidebarTreeElement.prototype = {
+ get mainTitle()
+ {
+ if (this._mainTitle)
+ return this._mainTitle;
+ return this.profile.title;
+ },
+
+ set mainTitle(x)
+ {
+ this._mainTitle = x;
+ this.refreshTitles();
+ }
+};
+
+WebInspector.HeapSnapshotSidebarTreeElement.prototype.__proto__ = WebInspector.ProfileSidebarTreeElement.prototype;
+
+WebInspector.HeapSnapshotDataGridNodeWithRetainers = function(owningTree)
+{
+ this.tree = owningTree;
+
+ WebInspector.DataGridNode.call(this, null, this._hasRetainers);
+
+ this.addEventListener("populate", this._populate, this);
+};
+
+WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype = {
+ isEmptySet: function(set)
+ {
+ for (var x in set)
+ return false;
+ return true;
+ },
+
+ get _hasRetainers()
+ {
+ return !this.isEmptySet(this.retainers);
+ },
+
+ get _parent()
+ {
+ // For top-level nodes, return owning tree as a parent, not data grid.
+ return this.parent !== this.dataGrid ? this.parent : this.tree;
+ },
+
+ _populate: function(event)
+ {
+ var self = this;
+ this.produceDiff(this.baseRetainers, this.retainers, function(baseItem, snapshotItem) {
+ self.appendChild(new WebInspector.HeapSnapshotDataGridRetainerNode(self.snapshotView, baseItem, snapshotItem, self.tree));
+ });
+
+ if (this._parent) {
+ var currentComparator = this._parent.lastComparator;
+ if (currentComparator)
+ this.sort(currentComparator, true);
+ }
+
+ this.removeEventListener("populate", this._populate, this);
+ },
+
+ produceDiff: function(baseEntries, currentEntries, callback)
+ {
+ for (var item in currentEntries)
+ callback(baseEntries[item], currentEntries[item]);
+
+ for (item in baseEntries) {
+ if (!(item in currentEntries))
+ callback(baseEntries[item], null);
+ }
+ },
+
+ sort: function(comparator, force) {
+ if (!force && this.lastComparator === comparator)
+ return;
+
+ this.children.sort(comparator);
+ var childCount = this.children.length;
+ for (var childIndex = 0; childIndex < childCount; ++childIndex)
+ this.children[childIndex]._recalculateSiblings(childIndex);
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this.children[i];
+ if (!force && (!child.expanded || child.lastComparator === comparator))
+ continue;
+ child.sort(comparator, force);
+ }
+ this.lastComparator = comparator;
+ },
+
+ signForDelta: function(delta) {
+ if (delta === 0)
+ return "";
+ if (delta > 0)
+ return "+";
+ else
+ // Math minus sign, same width as plus.
+ return "\u2212";
+ },
+
+ showDeltaAsPercent: function(value) {
+ if (value === Number.POSITIVE_INFINITY)
+ return WebInspector.UIString("new");
+ else if (value === Number.NEGATIVE_INFINITY)
+ return WebInspector.UIString("deleted");
+ if (value > 1000.0)
+ return WebInspector.UIString("%s >1000%%", this.signForDelta(value));
+ return WebInspector.UIString("%s%.2f%%", this.signForDelta(value), Math.abs(value));
+ },
+
+ getTotalCount: function() {
+ if (!this._count) {
+ this._count = 0;
+ for (var i = 0, n = this.children.length; i < n; ++i)
+ this._count += this.children[i].count;
+ }
+ return this._count;
+ },
+
+ getTotalSize: function() {
+ if (!this._size) {
+ this._size = 0;
+ for (var i = 0, n = this.children.length; i < n; ++i)
+ this._size += this.children[i].size;
+ }
+ return this._size;
+ },
+
+ get countPercent()
+ {
+ return this.count / this._parent.getTotalCount() * 100.0;
+ },
+
+ get sizePercent()
+ {
+ return this.size / this._parent.getTotalSize() * 100.0;
+ },
+
+ get countDeltaPercent()
+ {
+ if (this.baseCount > 0) {
+ if (this.count > 0)
+ return this.countDelta / this.baseCount * 100.0;
+ else
+ return Number.NEGATIVE_INFINITY;
+ } else
+ return Number.POSITIVE_INFINITY;
+ },
+
+ get sizeDeltaPercent()
+ {
+ if (this.baseSize > 0) {
+ if (this.size > 0)
+ return this.sizeDelta / this.baseSize * 100.0;
+ else
+ return Number.NEGATIVE_INFINITY;
+ } else
+ return Number.POSITIVE_INFINITY;
+ },
+
+ get data()
+ {
+ var data = {};
+
+ data["cons"] = this.constructorName;
+
+ if (this.snapshotView.showCountAsPercent)
+ data["count"] = WebInspector.UIString("%.2f%%", this.countPercent);
+ else
+ data["count"] = this.count;
+
+ if (this.size !== null) {
+ if (this.snapshotView.showSizeAsPercent)
+ data["size"] = WebInspector.UIString("%.2f%%", this.sizePercent);
+ else
+ data["size"] = Number.bytesToString(this.size);
+ } else
+ data["size"] = "";
+
+ if (this.snapshotView.showCountDeltaAsPercent)
+ data["countDelta"] = this.showDeltaAsPercent(this.countDeltaPercent);
+ else
+ data["countDelta"] = WebInspector.UIString("%s%d", this.signForDelta(this.countDelta), Math.abs(this.countDelta));
+
+ if (this.sizeDelta !== null) {
+ if (this.snapshotView.showSizeDeltaAsPercent)
+ data["sizeDelta"] = this.showDeltaAsPercent(this.sizeDeltaPercent);
+ else
+ data["sizeDelta"] = WebInspector.UIString("%s%s", this.signForDelta(this.sizeDelta), Number.bytesToString(Math.abs(this.sizeDelta)));
+ } else
+ data["sizeDelta"] = "";
+
+ return data;
+ },
+
+ createCell: function(columnIdentifier)
+ {
+ var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
+
+ if ((columnIdentifier === "cons" && this._searchMatchedConsColumn) ||
+ (columnIdentifier === "count" && this._searchMatchedCountColumn) ||
+ (columnIdentifier === "size" && this._searchMatchedSizeColumn) ||
+ (columnIdentifier === "countDelta" && this._searchMatchedCountDeltaColumn) ||
+ (columnIdentifier === "sizeDelta" && this._searchMatchedSizeDeltaColumn))
+ cell.addStyleClass("highlight");
+
+ return cell;
+ }
+};
+
+WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.__proto__ = WebInspector.DataGridNode.prototype;
+
+WebInspector.HeapSnapshotDataGridNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
+{
+ this.snapshotView = snapshotView;
+
+ if (!snapshotEntry)
+ snapshotEntry = { cons: baseEntry.cons, count: 0, size: 0, retainers: {} };
+ this.constructorName = snapshotEntry.cons;
+ this.count = snapshotEntry.count;
+ this.size = snapshotEntry.size;
+ this.retainers = snapshotEntry.retainers;
+
+ if (!baseEntry)
+ baseEntry = { count: 0, size: 0, retainers: {} };
+ this.baseCount = baseEntry.count;
+ this.countDelta = this.count - this.baseCount;
+ this.baseSize = baseEntry.size;
+ this.sizeDelta = this.size - this.baseSize;
+ this.baseRetainers = baseEntry.retainers;
+
+ WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
+};
+
+WebInspector.HeapSnapshotDataGridNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;
+
+WebInspector.HeapSnapshotDataGridList = function(snapshotView, baseEntries, snapshotEntries)
+{
+ this.tree = this;
+ this.snapshotView = snapshotView;
+ this.children = [];
+ this.lastComparator = null;
+ this.populateChildren(baseEntries, snapshotEntries);
+};
+
+WebInspector.HeapSnapshotDataGridList.prototype = {
+ appendChild: function(child)
+ {
+ this.insertChild(child, this.children.length);
+ },
+
+ insertChild: function(child, index)
+ {
+ this.children.splice(index, 0, child);
+ },
+
+ removeChildren: function()
+ {
+ this.children = [];
+ },
+
+ populateChildren: function(baseEntries, snapshotEntries)
+ {
+ var self = this;
+ this.produceDiff(baseEntries, snapshotEntries, function(baseItem, snapshotItem) {
+ self.appendChild(new WebInspector.HeapSnapshotDataGridNode(self.snapshotView, baseItem, snapshotItem, self));
+ });
+ },
+
+ produceDiff: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.produceDiff,
+ sort: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.sort,
+ getTotalCount: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalCount,
+ getTotalSize: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalSize
+};
+
+WebInspector.HeapSnapshotDataGridList.propertyComparators = [{}, {}];
+
+WebInspector.HeapSnapshotDataGridList.propertyComparator = function(property, property2, isAscending)
+{
+ var propertyHash = property + "#" + property2;
+ var comparator = this.propertyComparators[(isAscending ? 1 : 0)][propertyHash];
+ if (!comparator) {
+ comparator = function(lhs, rhs) {
+ var l = lhs[property], r = rhs[property];
+ if ((l === null || r === null) && property2 !== null)
+ l = lhs[property2], r = rhs[property2];
+ var result = l < r ? -1 : (l > r ? 1 : 0);
+ return isAscending ? result : -result;
+ };
+ this.propertyComparators[(isAscending ? 1 : 0)][propertyHash] = comparator;
+ }
+ return comparator;
+};
+
+WebInspector.HeapSnapshotDataGridRetainerNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
+{
+ this.snapshotView = snapshotView;
+
+ if (!snapshotEntry)
+ snapshotEntry = { cons: baseEntry.cons, count: 0, clusters: {} };
+ this.constructorName = snapshotEntry.cons;
+ this.count = snapshotEntry.count;
+ this.retainers = this._calculateRetainers(this.snapshotView.profile, snapshotEntry.clusters);
+
+ if (!baseEntry)
+ baseEntry = { count: 0, clusters: {} };
+ this.baseCount = baseEntry.count;
+ this.countDelta = this.count - this.baseCount;
+ this.baseRetainers = this._calculateRetainers(this.snapshotView.baseSnapshot, baseEntry.clusters);
+
+ this.size = null;
+ this.sizeDelta = null;
+
+ WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
+}
+
+WebInspector.HeapSnapshotDataGridRetainerNode.prototype = {
+ get sizePercent()
+ {
+ return null;
+ },
+
+ get sizeDeltaPercent()
+ {
+ return null;
+ },
+
+ _calculateRetainers: function(snapshot, clusters) {
+ var retainers = {};
+ if (this.isEmptySet(clusters)) {
+ if (this.constructorName in snapshot.entries)
+ return snapshot.entries[this.constructorName].retainers;
+ } else {
+ // In case when an entry is retained by clusters, we need to gather up the list
+ // of retainers by merging retainers of every cluster.
+ // E.g. having such a tree:
+ // A
+ // Object:1 10
+ // X 3
+ // Y 4
+ // Object:2 5
+ // X 6
+ //
+ // will result in a following retainers list: X 9, Y 4.
+ for (var clusterName in clusters) {
+ if (clusterName in snapshot.clusters) {
+ var clusterRetainers = snapshot.clusters[clusterName].retainers;
+ for (var clusterRetainer in clusterRetainers) {
+ var clusterRetainerEntry = clusterRetainers[clusterRetainer];
+ if (!(clusterRetainer in retainers))
+ retainers[clusterRetainer] = { cons: clusterRetainerEntry.cons, count: 0, clusters: {} };
+ retainers[clusterRetainer].count += clusterRetainerEntry.count;
+ for (var clusterRetainerCluster in clusterRetainerEntry.clusters)
+ retainers[clusterRetainer].clusters[clusterRetainerCluster] = true;
+ }
+ }
+ }
+ }
+ return retainers;
+ }
+};
+
+WebInspector.HeapSnapshotDataGridRetainerNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;
+
+
+WebInspector.HeapSnapshotProfileType = function()
+{
+ WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS"));
+}
+
+WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
+
+WebInspector.HeapSnapshotProfileType.snapshots = [];
+
+WebInspector.HeapSnapshotProfileType.prototype = {
+ get buttonTooltip()
+ {
+ return WebInspector.UIString("Take heap snapshot.");
+ },
+
+ get buttonStyle()
+ {
+ return "heap-snapshot-status-bar-item status-bar-item";
+ },
+
+ buttonClicked: function()
+ {
+ devtools.tools.getProfilerAgent().startProfiling(devtools.ProfilerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT);
+ },
+
+ get welcomeMessage()
+ {
+ return WebInspector.UIString("Get a heap snapshot by pressing the %s button on the status bar.");
+ },
+
+ createSidebarTreeElementForProfile: function(profile)
+ {
+ var element = new WebInspector.HeapSnapshotSidebarTreeElement(profile);
+ element.small = false;
+ return element;
+ },
+
+ createView: function(profile)
+ {
+ return new WebInspector.HeapSnapshotView(WebInspector.panels.profiles, profile);
+ }
+}
+
+WebInspector.HeapSnapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;
+
+
+(function() {
+ var originalCreatePanels = WebInspector._createPanels;
+ WebInspector._createPanels = function() {
+ originalCreatePanels.apply(this, arguments);
+ if (WebInspector.panels.profiles)
+ WebInspector.panels.profiles.registerProfileType(new WebInspector.HeapSnapshotProfileType());
+ }
+})();