diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/third_party/WebKit/Source/devtools/front_end/profiler | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/third_party/WebKit/Source/devtools/front_end/profiler')
21 files changed, 15778 insertions, 0 deletions
diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileBottomUpDataGrid.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileBottomUpDataGrid.js new file mode 100644 index 00000000000..5994476145a --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileBottomUpDataGrid.js @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2009 280 North 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +// Bottom Up Profiling shows the entire callstack backwards: +// The root node is a representation of each individual function called, and each child of that node represents +// a reverse-callstack showing how many of those calls came from it. So, unlike top-down, the statistics in +// each child still represent the root node. We have to be particularly careful of recursion with this mode +// because a root node can represent itself AND an ancestor. + +/** + * @constructor + * @extends {WebInspector.ProfileDataGridNode} + * @param {!ProfilerAgent.CPUProfileNode} profileNode + * @param {!WebInspector.TopDownProfileDataGridTree} owningTree + */ +WebInspector.BottomUpProfileDataGridNode = function(profileNode, owningTree) +{ + WebInspector.ProfileDataGridNode.call(this, profileNode, owningTree, this._willHaveChildren(profileNode)); + + this._remainingNodeInfos = []; +} + +WebInspector.BottomUpProfileDataGridNode.prototype = { + /** + * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode + */ + _takePropertiesFromProfileDataGridNode: function(profileDataGridNode) + { + this._save(); + + this.selfTime = profileDataGridNode.selfTime; + this.totalTime = profileDataGridNode.totalTime; + }, + + /** + * When focusing, we keep just the members of the callstack. + * @param {!WebInspector.ProfileDataGridNode} child + */ + _keepOnlyChild: function(child) + { + this._save(); + + this.removeChildren(); + this.appendChild(child); + }, + + _exclude: function(aCallUID) + { + if (this._remainingNodeInfos) + this.populate(); + + this._save(); + + var children = this.children; + var index = this.children.length; + + while (index--) + children[index]._exclude(aCallUID); + + var child = this.childrenByCallUID[aCallUID]; + + if (child) + this._merge(child, true); + }, + + _restore: function() + { + WebInspector.ProfileDataGridNode.prototype._restore(); + + if (!this.children.length) + this.hasChildren = this._willHaveChildren(this.profileNode); + }, + + /** + * @param {!WebInspector.ProfileDataGridNode} child + * @param {boolean} shouldAbsorb + */ + _merge: function(child, shouldAbsorb) + { + this.selfTime -= child.selfTime; + + WebInspector.ProfileDataGridNode.prototype._merge.call(this, child, shouldAbsorb); + }, + + _sharedPopulate: function() + { + var remainingNodeInfos = this._remainingNodeInfos; + var count = remainingNodeInfos.length; + + for (var index = 0; index < count; ++index) { + var nodeInfo = remainingNodeInfos[index]; + var ancestor = nodeInfo.ancestor; + var focusNode = nodeInfo.focusNode; + var child = this.findChild(ancestor); + + // If we already have this child, then merge the data together. + if (child) { + var totalTimeAccountedFor = nodeInfo.totalTimeAccountedFor; + + child.selfTime += focusNode.selfTime; + + if (!totalTimeAccountedFor) + child.totalTime += focusNode.totalTime; + } else { + // If not, add it as a true ancestor. + // In heavy mode, we take our visual identity from ancestor node... + child = new WebInspector.BottomUpProfileDataGridNode(ancestor, this.tree); + + if (ancestor !== focusNode) { + // but the actual statistics from the "root" node (bottom of the callstack). + child.selfTime = focusNode.selfTime; + child.totalTime = focusNode.totalTime; + } + + this.appendChild(child); + } + + var parent = ancestor.parent; + if (parent && parent.parent) { + nodeInfo.ancestor = parent; + child._remainingNodeInfos.push(nodeInfo); + } + } + + for (var i = 0; i < this.children.length; ++i) + this.children[i].buildData(); + + delete this._remainingNodeInfos; + }, + + _willHaveChildren: function(profileNode) + { + // In bottom up mode, our parents are our children since we display an inverted tree. + // However, we don't want to show the very top parent since it is redundant. + return !!(profileNode.parent && profileNode.parent.parent); + }, + + __proto__: WebInspector.ProfileDataGridNode.prototype +} + +/** + * @constructor + * @extends {WebInspector.ProfileDataGridTree} + * @param {!WebInspector.CPUProfileView} profileView + * @param {!ProfilerAgent.CPUProfileNode} rootProfileNode + */ +WebInspector.BottomUpProfileDataGridTree = function(profileView, rootProfileNode) +{ + WebInspector.ProfileDataGridTree.call(this, profileView, rootProfileNode); + + // Iterate each node in pre-order. + var profileNodeUIDs = 0; + var profileNodeGroups = [[], [rootProfileNode]]; + var visitedProfileNodesForCallUID = {}; + + this._remainingNodeInfos = []; + + for (var profileNodeGroupIndex = 0; profileNodeGroupIndex < profileNodeGroups.length; ++profileNodeGroupIndex) { + var parentProfileNodes = profileNodeGroups[profileNodeGroupIndex]; + var profileNodes = profileNodeGroups[++profileNodeGroupIndex]; + var count = profileNodes.length; + + for (var index = 0; index < count; ++index) { + var profileNode = profileNodes[index]; + + if (!profileNode.UID) + profileNode.UID = ++profileNodeUIDs; + + if (profileNode.parent) { + // The total time of this ancestor is accounted for if we're in any form of recursive cycle. + var visitedNodes = visitedProfileNodesForCallUID[profileNode.callUID]; + var totalTimeAccountedFor = false; + + if (!visitedNodes) { + visitedNodes = {} + visitedProfileNodesForCallUID[profileNode.callUID] = visitedNodes; + } else { + // The total time for this node has already been accounted for iff one of it's parents has already been visited. + // We can do this check in this style because we are traversing the tree in pre-order. + var parentCount = parentProfileNodes.length; + for (var parentIndex = 0; parentIndex < parentCount; ++parentIndex) { + if (visitedNodes[parentProfileNodes[parentIndex].UID]) { + totalTimeAccountedFor = true; + break; + } + } + } + + visitedNodes[profileNode.UID] = true; + + this._remainingNodeInfos.push({ ancestor:profileNode, focusNode:profileNode, totalTimeAccountedFor:totalTimeAccountedFor }); + } + + var children = profileNode.children; + if (children.length) { + profileNodeGroups.push(parentProfileNodes.concat([profileNode])) + profileNodeGroups.push(children); + } + } + } + + // Populate the top level nodes. + var any = /** @type {*} */(this); + var node = /** @type {!WebInspector.ProfileDataGridNode} */(any); + WebInspector.BottomUpProfileDataGridNode.prototype.populate.call(node); + + return this; +} + +WebInspector.BottomUpProfileDataGridTree.prototype = { + /** + * When focusing, we keep the entire callstack up to this ancestor. + * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode + */ + focus: function(profileDataGridNode) + { + if (!profileDataGridNode) + return; + + this._save(); + + var currentNode = profileDataGridNode; + var focusNode = profileDataGridNode; + + while (currentNode.parent && (currentNode instanceof WebInspector.ProfileDataGridNode)) { + currentNode._takePropertiesFromProfileDataGridNode(profileDataGridNode); + + focusNode = currentNode; + currentNode = currentNode.parent; + + if (currentNode instanceof WebInspector.ProfileDataGridNode) + currentNode._keepOnlyChild(focusNode); + } + + this.children = [focusNode]; + this.totalTime = profileDataGridNode.totalTime; + }, + + /** + * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode + */ + exclude: function(profileDataGridNode) + { + if (!profileDataGridNode) + return; + + this._save(); + + var excludedCallUID = profileDataGridNode.callUID; + var excludedTopLevelChild = this.childrenByCallUID[excludedCallUID]; + + // If we have a top level node that is excluded, get rid of it completely (not keeping children), + // since bottom up data relies entirely on the root node. + if (excludedTopLevelChild) + this.children.remove(excludedTopLevelChild); + + var children = this.children; + var count = children.length; + + for (var index = 0; index < count; ++index) + children[index]._exclude(excludedCallUID); + + if (this.lastComparator) + this.sort(this.lastComparator, true); + }, + + buildData: function() + { + }, + + _sharedPopulate: WebInspector.BottomUpProfileDataGridNode.prototype._sharedPopulate, + + __proto__: WebInspector.ProfileDataGridTree.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileDataGrid.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileDataGrid.js new file mode 100644 index 00000000000..9fd11dc9135 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileDataGrid.js @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2009 280 North 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +/** + * @constructor + * @extends {WebInspector.DataGridNode} + * @param {!ProfilerAgent.CPUProfileNode} profileNode + * @param {!WebInspector.TopDownProfileDataGridTree} owningTree + * @param {boolean} hasChildren + */ +WebInspector.ProfileDataGridNode = function(profileNode, owningTree, hasChildren) +{ + this._target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); + this.profileNode = profileNode; + + WebInspector.DataGridNode.call(this, null, hasChildren); + + this.tree = owningTree; + + this.childrenByCallUID = {}; + this.lastComparator = null; + + this.callUID = profileNode.callUID; + this.selfTime = profileNode.selfTime; + this.totalTime = profileNode.totalTime; + this.functionName = profileNode.functionName; + this._deoptReason = (!profileNode.deoptReason || profileNode.deoptReason === "no reason") ? "" : profileNode.deoptReason; + this.url = profileNode.url; +} + +WebInspector.ProfileDataGridNode.prototype = { + /** + * @override + * @param {string} columnIdentifier + * @return {!Element} + */ + createCell: function(columnIdentifier) + { + var cell = this._createValueCell(columnIdentifier) || WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); + + if (columnIdentifier === "self" && this._searchMatchedSelfColumn) + cell.classList.add("highlight"); + else if (columnIdentifier === "total" && this._searchMatchedTotalColumn) + cell.classList.add("highlight"); + + if (columnIdentifier !== "function") + return cell; + + if (this._deoptReason) + cell.classList.add("not-optimized"); + + if (this.profileNode._searchMatchedFunctionColumn) + cell.classList.add("highlight"); + + if (this.profileNode.scriptId !== "0") { + var lineNumber = this.profileNode.lineNumber ? this.profileNode.lineNumber - 1 : 0; + var columnNumber = this.profileNode.columnNumber ? this.profileNode.columnNumber - 1 : 0; + var location = new WebInspector.DebuggerModel.Location(/** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()), this.profileNode.scriptId, lineNumber, columnNumber); + var urlElement = this.tree.profileView._linkifier.linkifyRawLocation(location, "profile-node-file"); + if (!urlElement) + urlElement = this.tree.profileView._linkifier.linkifyLocation(this._target, this.profileNode.url, lineNumber, columnNumber, "profile-node-file"); + urlElement.style.maxWidth = "75%"; + cell.insertBefore(urlElement, cell.firstChild); + } + + return cell; + }, + + /** + * @param {string} columnIdentifier + * @return {?Element} + */ + _createValueCell: function(columnIdentifier) + { + if (columnIdentifier !== "self" && columnIdentifier !== "total") + return null; + + var cell = document.createElement("td"); + cell.className = "numeric-column"; + var div = document.createElement("div"); + var valueSpan = document.createElement("span"); + valueSpan.textContent = this.data[columnIdentifier]; + div.appendChild(valueSpan); + var percentColumn = columnIdentifier + "-percent"; + if (percentColumn in this.data) { + var percentSpan = document.createElement("span"); + percentSpan.className = "percent-column"; + percentSpan.textContent = this.data[percentColumn]; + div.appendChild(percentSpan); + div.classList.add("profile-multiple-values"); + } + cell.appendChild(div); + return cell; + }, + + buildData: function() + { + function formatMilliseconds(time) + { + return WebInspector.UIString("%.1f\u2009ms", time); + } + function formatPercent(value) + { + return WebInspector.UIString("%.2f\u2009%", value); + } + + var functionName; + if (this._deoptReason) { + var content = document.createDocumentFragment(); + var marker = content.createChild("span", "profile-warn-marker"); + marker.title = WebInspector.UIString("Not optimized: %s", this._deoptReason); + content.createTextChild(this.functionName); + functionName = content; + } else { + functionName = this.functionName; + } + + this.data = { + "function": functionName, + "self-percent": formatPercent(this.selfPercent), + "self": formatMilliseconds(this.selfTime), + "total-percent": formatPercent(this.totalPercent), + "total": formatMilliseconds(this.totalTime), + }; + }, + + select: function(supressSelectedEvent) + { + WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent); + this.tree.profileView._dataGridNodeSelected(this); + }, + + deselect: function(supressDeselectedEvent) + { + WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent); + this.tree.profileView._dataGridNodeDeselected(this); + }, + + /** + * @param {function(!T, !T)} comparator + * @param {boolean} force + * @template T + */ + sort: function(comparator, force) + { + var gridNodeGroups = [[this]]; + + for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) { + var gridNodes = gridNodeGroups[gridNodeGroupIndex]; + var count = gridNodes.length; + + for (var index = 0; index < count; ++index) { + var gridNode = gridNodes[index]; + + // If the grid node is collapsed, then don't sort children (save operation for later). + // If the grid node has the same sorting as previously, then there is no point in sorting it again. + if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) { + if (gridNode.children.length) + gridNode.shouldRefreshChildren = true; + continue; + } + + gridNode.lastComparator = comparator; + + var children = gridNode.children; + var childCount = children.length; + + if (childCount) { + children.sort(comparator); + + for (var childIndex = 0; childIndex < childCount; ++childIndex) + children[childIndex]._recalculateSiblings(childIndex); + + gridNodeGroups.push(children); + } + } + } + }, + + /** + * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode + * @param {number} index + */ + insertChild: function(profileDataGridNode, index) + { + WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index); + + this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode; + }, + + /** + * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode + */ + removeChild: function(profileDataGridNode) + { + WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode); + + delete this.childrenByCallUID[profileDataGridNode.callUID]; + }, + + removeChildren: function() + { + WebInspector.DataGridNode.prototype.removeChildren.call(this); + + this.childrenByCallUID = {}; + }, + + /** + * @param {!WebInspector.ProfileDataGridNode} node + * @return {?WebInspector.ProfileDataGridNode} + */ + findChild: function(node) + { + if (!node) + return null; + return this.childrenByCallUID[node.callUID]; + }, + + get selfPercent() + { + return this.selfTime / this.tree.totalTime * 100.0; + }, + + get totalPercent() + { + return this.totalTime / this.tree.totalTime * 100.0; + }, + + get _parent() + { + return this.parent !== this.dataGrid ? this.parent : this.tree; + }, + + populate: function() + { + if (this._populated) + return; + this._populated = true; + + this._sharedPopulate(); + + var currentComparator = this.tree.lastComparator; + + if (currentComparator) + this.sort(currentComparator, true); + }, + + // When focusing and collapsing we modify lots of nodes in the tree. + // This allows us to restore them all to their original state when we revert. + _save: function() + { + if (this._savedChildren) + return; + + this._savedSelfTime = this.selfTime; + this._savedTotalTime = this.totalTime; + + this._savedChildren = this.children.slice(); + }, + + // When focusing and collapsing we modify lots of nodes in the tree. + // This allows us to restore them all to their original state when we revert. + _restore: function() + { + if (!this._savedChildren) + return; + + this.selfTime = this._savedSelfTime; + this.totalTime = this._savedTotalTime; + + this.removeChildren(); + + var children = this._savedChildren; + var count = children.length; + + for (var index = 0; index < count; ++index) { + children[index]._restore(); + this.appendChild(children[index]); + } + }, + + _merge: function(child, shouldAbsorb) + { + this.selfTime += child.selfTime; + + if (!shouldAbsorb) + this.totalTime += child.totalTime; + + var children = this.children.slice(); + + this.removeChildren(); + + var count = children.length; + + for (var index = 0; index < count; ++index) { + if (!shouldAbsorb || children[index] !== child) + this.appendChild(children[index]); + } + + children = child.children.slice(); + count = children.length; + + for (var index = 0; index < count; ++index) { + var orphanedChild = children[index], + existingChild = this.childrenByCallUID[orphanedChild.callUID]; + + if (existingChild) + existingChild._merge(orphanedChild, false); + else + this.appendChild(orphanedChild); + } + }, + + __proto__: WebInspector.DataGridNode.prototype +} + +/** + * @constructor + * @param {!WebInspector.CPUProfileView} profileView + * @param {!ProfilerAgent.CPUProfileNode} rootProfileNode + */ +WebInspector.ProfileDataGridTree = function(profileView, rootProfileNode) +{ + this.tree = this; + this.children = []; + + this.profileView = profileView; + + this.totalTime = rootProfileNode.totalTime; + this.lastComparator = null; + + this.childrenByCallUID = {}; +} + +WebInspector.ProfileDataGridTree.prototype = { + get expanded() + { + return true; + }, + + appendChild: function(child) + { + this.insertChild(child, this.children.length); + }, + + insertChild: function(child, index) + { + this.children.splice(index, 0, child); + this.childrenByCallUID[child.callUID] = child; + }, + + removeChildren: function() + { + this.children = []; + this.childrenByCallUID = {}; + }, + + findChild: WebInspector.ProfileDataGridNode.prototype.findChild, + sort: WebInspector.ProfileDataGridNode.prototype.sort, + + _save: function() + { + if (this._savedChildren) + return; + + this._savedTotalTime = this.totalTime; + this._savedChildren = this.children.slice(); + }, + + restore: function() + { + if (!this._savedChildren) + return; + + this.children = this._savedChildren; + this.totalTime = this._savedTotalTime; + + var children = this.children; + var count = children.length; + + for (var index = 0; index < count; ++index) + children[index]._restore(); + + this._savedChildren = null; + } +} + +WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}]; + +/** + * @param {string} property + * @param {boolean} isAscending + * @return {function(!Object.<string, *>, !Object.<string, *>)} + */ +WebInspector.ProfileDataGridTree.propertyComparator = function(property, isAscending) +{ + var comparator = WebInspector.ProfileDataGridTree.propertyComparators[(isAscending ? 1 : 0)][property]; + + if (!comparator) { + if (isAscending) { + comparator = function(lhs, rhs) + { + if (lhs[property] < rhs[property]) + return -1; + + if (lhs[property] > rhs[property]) + return 1; + + return 0; + } + } else { + comparator = function(lhs, rhs) + { + if (lhs[property] > rhs[property]) + return -1; + + if (lhs[property] < rhs[property]) + return 1; + + return 0; + } + } + + WebInspector.ProfileDataGridTree.propertyComparators[(isAscending ? 1 : 0)][property] = comparator; + } + + return comparator; +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileFlameChart.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileFlameChart.js new file mode 100644 index 00000000000..7ad221b89ae --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileFlameChart.js @@ -0,0 +1,650 @@ +/** + * Copyright (C) 2014 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. + */ + + +/** + * @constructor + * @implements {WebInspector.FlameChartDataProvider} + * @param {!WebInspector.CPUProfileDataModel} cpuProfile + * @param {!WebInspector.Target} target + */ +WebInspector.CPUFlameChartDataProvider = function(cpuProfile, target) +{ + WebInspector.FlameChartDataProvider.call(this); + this._cpuProfile = cpuProfile; + this._target = target; + this._colorGenerator = WebInspector.CPUFlameChartDataProvider.colorGenerator(); +} + +WebInspector.CPUFlameChartDataProvider.prototype = { + /** + * @return {number} + */ + barHeight: function() + { + return 15; + }, + + /** + * @return {number} + */ + textBaseline: function() + { + return 4; + }, + + /** + * @return {number} + */ + textPadding: function() + { + return 2; + }, + + /** + * @param {number} startTime + * @param {number} endTime + * @return {?Array.<number>} + */ + dividerOffsets: function(startTime, endTime) + { + return null; + }, + + /** + * @return {number} + */ + minimumBoundary: function() + { + return this._cpuProfile.profileStartTime; + }, + + /** + * @return {number} + */ + totalTime: function() + { + return this._cpuProfile.profileHead.totalTime; + }, + + /** + * @return {number} + */ + maxStackDepth: function() + { + return this._maxStackDepth; + }, + + /** + * @return {?WebInspector.FlameChart.TimelineData} + */ + timelineData: function() + { + return this._timelineData || this._calculateTimelineData(); + }, + + /** + * @return {?WebInspector.FlameChart.TimelineData} + */ + _calculateTimelineData: function() + { + /** + * @constructor + * @param {number} depth + * @param {number} duration + * @param {number} startTime + * @param {number} selfTime + * @param {!ProfilerAgent.CPUProfileNode} node + */ + function ChartEntry(depth, duration, startTime, selfTime, node) + { + this.depth = depth; + this.duration = duration; + this.startTime = startTime; + this.selfTime = selfTime; + this.node = node; + } + + /** @type {!Array.<?ChartEntry>} */ + var entries = []; + /** @type {!Array.<number>} */ + var stack = []; + var maxDepth = 5; + + function onOpenFrame() + { + stack.push(entries.length); + // Reserve space for the entry, as they have to be ordered by startTime. + // The entry itself will be put there in onCloseFrame. + entries.push(null); + } + function onCloseFrame(depth, node, startTime, totalTime, selfTime) + { + var index = stack.pop(); + entries[index] = new ChartEntry(depth, totalTime, startTime, selfTime, node); + maxDepth = Math.max(maxDepth, depth); + } + this._cpuProfile.forEachFrame(onOpenFrame, onCloseFrame); + + /** @type {!Array.<!ProfilerAgent.CPUProfileNode>} */ + var entryNodes = new Array(entries.length); + var entryLevels = new Uint8Array(entries.length); + var entryTotalTimes = new Float32Array(entries.length); + var entrySelfTimes = new Float32Array(entries.length); + var entryStartTimes = new Float64Array(entries.length); + var minimumBoundary = this.minimumBoundary(); + + for (var i = 0; i < entries.length; ++i) { + var entry = entries[i]; + entryNodes[i] = entry.node; + entryLevels[i] = entry.depth; + entryTotalTimes[i] = entry.duration; + entryStartTimes[i] = entry.startTime; + entrySelfTimes[i] = entry.selfTime; + } + + this._maxStackDepth = maxDepth; + + /** @type {!WebInspector.FlameChart.TimelineData} */ + this._timelineData = { + entryLevels: entryLevels, + entryTotalTimes: entryTotalTimes, + entryStartTimes: entryStartTimes, + }; + + /** @type {!Array.<!ProfilerAgent.CPUProfileNode>} */ + this._entryNodes = entryNodes; + this._entrySelfTimes = entrySelfTimes; + + return this._timelineData; + }, + + /** + * @param {number} ms + * @return {string} + */ + _millisecondsToString: function(ms) + { + if (ms === 0) + return "0"; + if (ms < 1000) + return WebInspector.UIString("%.1f\u2009ms", ms); + return Number.secondsToString(ms / 1000, true); + }, + + /** + * @param {number} entryIndex + * @return {?Array.<!{title: string, text: string}>} + */ + prepareHighlightedEntryInfo: function(entryIndex) + { + var timelineData = this._timelineData; + var node = this._entryNodes[entryIndex]; + if (!node) + return null; + + var entryInfo = []; + function pushEntryInfoRow(title, text) + { + var row = {}; + row.title = title; + row.text = text; + entryInfo.push(row); + } + + pushEntryInfoRow(WebInspector.UIString("Name"), node.functionName); + var selfTime = this._millisecondsToString(this._entrySelfTimes[entryIndex]); + var totalTime = this._millisecondsToString(timelineData.entryTotalTimes[entryIndex]); + pushEntryInfoRow(WebInspector.UIString("Self time"), selfTime); + pushEntryInfoRow(WebInspector.UIString("Total time"), totalTime); + var target = this._target; + var text = WebInspector.Linkifier.liveLocationText(target, node.scriptId, node.lineNumber, node.columnNumber); + pushEntryInfoRow(WebInspector.UIString("URL"), text); + pushEntryInfoRow(WebInspector.UIString("Aggregated self time"), Number.secondsToString(node.selfTime / 1000, true)); + pushEntryInfoRow(WebInspector.UIString("Aggregated total time"), Number.secondsToString(node.totalTime / 1000, true)); + if (node.deoptReason && node.deoptReason !== "no reason") + pushEntryInfoRow(WebInspector.UIString("Not optimized"), node.deoptReason); + + return entryInfo; + }, + + /** + * @param {number} entryIndex + * @return {boolean} + */ + canJumpToEntry: function(entryIndex) + { + return this._entryNodes[entryIndex].scriptId !== "0"; + }, + + /** + * @param {number} entryIndex + * @return {?string} + */ + entryTitle: function(entryIndex) + { + var node = this._entryNodes[entryIndex]; + return node.functionName; + }, + + /** + * @param {number} entryIndex + * @return {?string} + */ + entryFont: function(entryIndex) + { + if (!this._font) { + this._font = (this.barHeight() - 4) + "px " + WebInspector.fontFamily(); + this._boldFont = "bold " + this._font; + } + var node = this._entryNodes[entryIndex]; + var reason = node.deoptReason; + return (reason && reason !== "no reason") ? this._boldFont : this._font; + }, + + /** + * @param {number} entryIndex + * @return {string} + */ + entryColor: function(entryIndex) + { + var node = this._entryNodes[entryIndex]; + return this._colorGenerator.colorForID(node.functionName + ":" + node.url); + }, + + /** + * @param {number} entryIndex + * @param {!CanvasRenderingContext2D} context + * @param {?string} text + * @param {number} barX + * @param {number} barY + * @param {number} barWidth + * @param {number} barHeight + * @param {function(number):number} timeToPosition + * @return {boolean} + */ + decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, timeToPosition) + { + return false; + }, + + /** + * @param {number} entryIndex + * @return {boolean} + */ + forceDecoration: function(entryIndex) + { + return false; + }, + + /** + * @param {number} entryIndex + * @return {!{startTime: number, endTime: number}} + */ + highlightTimeRange: function(entryIndex) + { + var startTime = this._timelineData.entryStartTimes[entryIndex]; + return { + startTime: startTime, + endTime: startTime + this._timelineData.entryTotalTimes[entryIndex] + }; + }, + + /** + * @return {number} + */ + paddingLeft: function() + { + return 15; + }, + + /** + * @param {number} entryIndex + * @return {string} + */ + textColor: function(entryIndex) + { + return "#333"; + } +} + + +/** + * @return {!WebInspector.FlameChart.ColorGenerator} + */ +WebInspector.CPUFlameChartDataProvider.colorGenerator = function() +{ + if (!WebInspector.CPUFlameChartDataProvider._colorGenerator) { + var colorGenerator = new WebInspector.FlameChart.ColorGenerator( + { min: 180, max: 310, count: 7 }, + { min: 50, max: 80, count: 5 }, + { min: 80, max: 90, count: 3 }); + colorGenerator.setColorForID("(idle):", "hsl(0, 0%, 94%)"); + colorGenerator.setColorForID("(program):", "hsl(0, 0%, 80%)"); + colorGenerator.setColorForID("(garbage collector):", "hsl(0, 0%, 80%)"); + WebInspector.CPUFlameChartDataProvider._colorGenerator = colorGenerator; + } + return WebInspector.CPUFlameChartDataProvider._colorGenerator; +} + + +/** + * @constructor + * @extends {WebInspector.VBox} + * @param {!WebInspector.FlameChartDataProvider} dataProvider + */ +WebInspector.CPUProfileFlameChart = function(dataProvider) +{ + WebInspector.VBox.call(this); + this.registerRequiredCSS("flameChart.css"); + this.element.id = "cpu-flame-chart"; + + this._overviewPane = new WebInspector.CPUProfileFlameChart.OverviewPane(dataProvider); + this._overviewPane.show(this.element); + + this._mainPane = new WebInspector.FlameChart(dataProvider, this._overviewPane, true); + this._mainPane.show(this.element); + this._mainPane.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this); + this._overviewPane.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); +} + +WebInspector.CPUProfileFlameChart.prototype = { + /** + * @param {!WebInspector.Event} event + */ + _onWindowChanged: function(event) + { + var windowLeft = event.data.windowTimeLeft; + var windowRight = event.data.windowTimeRight; + this._mainPane.setWindowTimes(windowLeft, windowRight); + }, + + /** + * @param {!number} timeLeft + * @param {!number} timeRight + */ + selectRange: function(timeLeft, timeRight) + { + this._overviewPane._selectRange(timeLeft, timeRight); + }, + + /** + * @param {!WebInspector.Event} event + */ + _onEntrySelected: function(event) + { + this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, event.data); + }, + + update: function() + { + this._overviewPane.update(); + this._mainPane.update(); + }, + + __proto__: WebInspector.VBox.prototype +}; + +/** + * @constructor + * @implements {WebInspector.TimelineGrid.Calculator} + */ +WebInspector.CPUProfileFlameChart.OverviewCalculator = function() +{ +} + +WebInspector.CPUProfileFlameChart.OverviewCalculator.prototype = { + /** + * @return {number} + */ + paddingLeft: function() + { + return 0; + }, + + /** + * @param {!WebInspector.CPUProfileFlameChart.OverviewPane} overviewPane + */ + _updateBoundaries: function(overviewPane) + { + this._minimumBoundaries = overviewPane._dataProvider.minimumBoundary(); + var totalTime = overviewPane._dataProvider.totalTime(); + this._maximumBoundaries = this._minimumBoundaries + totalTime; + this._xScaleFactor = overviewPane._overviewContainer.clientWidth / totalTime; + }, + + /** + * @param {number} time + * @return {number} + */ + computePosition: function(time) + { + return (time - this._minimumBoundaries) * this._xScaleFactor; + }, + + /** + * @param {number} value + * @param {number=} precision + * @return {string} + */ + formatTime: function(value, precision) + { + return Number.secondsToString((value - this._minimumBoundaries) / 1000); + }, + + /** + * @return {number} + */ + maximumBoundary: function() + { + return this._maximumBoundaries; + }, + + /** + * @return {number} + */ + minimumBoundary: function() + { + return this._minimumBoundaries; + }, + + /** + * @return {number} + */ + zeroTime: function() + { + return this._minimumBoundaries; + }, + + /** + * @return {number} + */ + boundarySpan: function() + { + return this._maximumBoundaries - this._minimumBoundaries; + } +} + +/** + * @constructor + * @extends {WebInspector.VBox} + * @implements {WebInspector.FlameChartDelegate} + * @param {!WebInspector.FlameChartDataProvider} dataProvider + */ +WebInspector.CPUProfileFlameChart.OverviewPane = function(dataProvider) +{ + WebInspector.VBox.call(this); + this.element.classList.add("flame-chart-overview-pane"); + this._overviewContainer = this.element.createChild("div", "overview-container"); + this._overviewGrid = new WebInspector.OverviewGrid("flame-chart"); + this._overviewGrid.element.classList.add("fill"); + this._overviewCanvas = this._overviewContainer.createChild("canvas", "flame-chart-overview-canvas"); + this._overviewContainer.appendChild(this._overviewGrid.element); + this._overviewCalculator = new WebInspector.CPUProfileFlameChart.OverviewCalculator(); + this._dataProvider = dataProvider; + this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); +} + +WebInspector.CPUProfileFlameChart.OverviewPane.prototype = { + /** + * @param {number} windowStartTime + * @param {number} windowEndTime + */ + requestWindowTimes: function(windowStartTime, windowEndTime) + { + this._selectRange(windowStartTime, windowEndTime); + }, + + /** + * @param {!number} timeLeft + * @param {!number} timeRight + */ + _selectRange: function(timeLeft, timeRight) + { + var startTime = this._dataProvider.minimumBoundary(); + var totalTime = this._dataProvider.totalTime(); + this._overviewGrid.setWindow((timeLeft - startTime) / totalTime, (timeRight - startTime) / totalTime); + }, + + /** + * @param {!WebInspector.Event} event + */ + _onWindowChanged: function(event) + { + var startTime = this._dataProvider.minimumBoundary(); + var totalTime = this._dataProvider.totalTime(); + var data = { + windowTimeLeft: startTime + this._overviewGrid.windowLeft() * totalTime, + windowTimeRight: startTime + this._overviewGrid.windowRight() * totalTime + }; + this.dispatchEventToListeners(WebInspector.OverviewGrid.Events.WindowChanged, data); + }, + + /** + * @return {?WebInspector.FlameChart.TimelineData} + */ + _timelineData: function() + { + return this._dataProvider.timelineData(); + }, + + onResize: function() + { + this._scheduleUpdate(); + }, + + _scheduleUpdate: function() + { + if (this._updateTimerId) + return; + this._updateTimerId = requestAnimationFrame(this.update.bind(this)); + }, + + update: function() + { + this._updateTimerId = 0; + var timelineData = this._timelineData(); + if (!timelineData) + return; + this._resetCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - WebInspector.FlameChart.DividersBarHeight); + this._overviewCalculator._updateBoundaries(this); + this._overviewGrid.updateDividers(this._overviewCalculator); + this._drawOverviewCanvas(); + }, + + _drawOverviewCanvas: function() + { + var canvasWidth = this._overviewCanvas.width; + var canvasHeight = this._overviewCanvas.height; + var drawData = this._calculateDrawData(canvasWidth); + var context = this._overviewCanvas.getContext("2d"); + var ratio = window.devicePixelRatio; + var offsetFromBottom = ratio; + var lineWidth = 1; + var yScaleFactor = canvasHeight / (this._dataProvider.maxStackDepth() * 1.1); + context.lineWidth = lineWidth; + context.translate(0.5, 0.5); + context.strokeStyle = "rgba(20,0,0,0.4)"; + context.fillStyle = "rgba(214,225,254,0.8)"; + context.moveTo(-lineWidth, canvasHeight + lineWidth); + context.lineTo(-lineWidth, Math.round(canvasHeight - drawData[0] * yScaleFactor - offsetFromBottom)); + var value; + for (var x = 0; x < canvasWidth; ++x) { + value = Math.round(canvasHeight - drawData[x] * yScaleFactor - offsetFromBottom); + context.lineTo(x, value); + } + context.lineTo(canvasWidth + lineWidth, value); + context.lineTo(canvasWidth + lineWidth, canvasHeight + lineWidth); + context.fill(); + context.stroke(); + context.closePath(); + }, + + /** + * @param {number} width + * @return {!Uint8Array} + */ + _calculateDrawData: function(width) + { + var dataProvider = this._dataProvider; + var timelineData = this._timelineData(); + var entryStartTimes = timelineData.entryStartTimes; + var entryTotalTimes = timelineData.entryTotalTimes; + var entryLevels = timelineData.entryLevels; + var length = entryStartTimes.length; + var minimumBoundary = this._dataProvider.minimumBoundary(); + + var drawData = new Uint8Array(width); + var scaleFactor = width / dataProvider.totalTime(); + + for (var entryIndex = 0; entryIndex < length; ++entryIndex) { + var start = Math.floor((entryStartTimes[entryIndex] - minimumBoundary) * scaleFactor); + var finish = Math.floor((entryStartTimes[entryIndex] - minimumBoundary + entryTotalTimes[entryIndex]) * scaleFactor); + for (var x = start; x <= finish; ++x) + drawData[x] = Math.max(drawData[x], entryLevels[entryIndex] + 1); + } + return drawData; + }, + + /** + * @param {!number} width + * @param {!number} height + */ + _resetCanvas: function(width, height) + { + var ratio = window.devicePixelRatio; + this._overviewCanvas.width = width * ratio; + this._overviewCanvas.height = height * ratio; + this._overviewCanvas.style.width = width + "px"; + this._overviewCanvas.style.height = height + "px"; + }, + + __proto__: WebInspector.VBox.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileTopDownDataGrid.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileTopDownDataGrid.js new file mode 100644 index 00000000000..f1f7a7a5fe7 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileTopDownDataGrid.js @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2009 280 North 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +/** + * @constructor + * @extends {WebInspector.ProfileDataGridNode} + * @param {!ProfilerAgent.CPUProfileNode} profileNode + * @param {!WebInspector.TopDownProfileDataGridTree} owningTree + */ +WebInspector.TopDownProfileDataGridNode = function(profileNode, owningTree) +{ + var hasChildren = !!(profileNode.children && profileNode.children.length); + + WebInspector.ProfileDataGridNode.call(this, profileNode, owningTree, hasChildren); + + this._remainingChildren = profileNode.children; + this.buildData(); +} + +WebInspector.TopDownProfileDataGridNode.prototype = { + _sharedPopulate: function() + { + var children = this._remainingChildren; + var childrenLength = children.length; + + for (var i = 0; i < childrenLength; ++i) + this.appendChild(new WebInspector.TopDownProfileDataGridNode(children[i], this.tree)); + + this._remainingChildren = null; + }, + + _exclude: function(aCallUID) + { + if (this._remainingChildren) + this.populate(); + + this._save(); + + var children = this.children; + var index = this.children.length; + + while (index--) + children[index]._exclude(aCallUID); + + var child = this.childrenByCallUID[aCallUID]; + + if (child) + this._merge(child, true); + }, + + __proto__: WebInspector.ProfileDataGridNode.prototype +} + +/** + * @constructor + * @extends {WebInspector.ProfileDataGridTree} + * @param {!WebInspector.CPUProfileView} profileView + * @param {!ProfilerAgent.CPUProfileNode} rootProfileNode + */ +WebInspector.TopDownProfileDataGridTree = function(profileView, rootProfileNode) +{ + WebInspector.ProfileDataGridTree.call(this, profileView, rootProfileNode); + + this._remainingChildren = rootProfileNode.children; + + var any = /** @type {*} */(this); + var node = /** @type {!WebInspector.ProfileDataGridNode} */(any); + WebInspector.TopDownProfileDataGridNode.prototype.populate.call(node); +} + +WebInspector.TopDownProfileDataGridTree.prototype = { + /** + * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode + */ + focus: function(profileDataGridNode) + { + if (!profileDataGridNode) + return; + + this._save(); + profileDataGridNode.savePosition(); + + this.children = [profileDataGridNode]; + this.totalTime = profileDataGridNode.totalTime; + }, + + /** + * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode + */ + exclude: function(profileDataGridNode) + { + if (!profileDataGridNode) + return; + + this._save(); + + var excludedCallUID = profileDataGridNode.callUID; + + var any = /** @type {*} */(this); + var node = /** @type {!WebInspector.TopDownProfileDataGridNode} */(any); + WebInspector.TopDownProfileDataGridNode.prototype._exclude.call(node, excludedCallUID); + + if (this.lastComparator) + this.sort(this.lastComparator, true); + }, + + restore: function() + { + if (!this._savedChildren) + return; + + this.children[0].restorePosition(); + + WebInspector.ProfileDataGridTree.prototype.restore.call(this); + }, + + _merge: WebInspector.TopDownProfileDataGridNode.prototype._merge, + + _sharedPopulate: WebInspector.TopDownProfileDataGridNode.prototype._sharedPopulate, + + __proto__: WebInspector.ProfileDataGridTree.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileView.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileView.js new file mode 100644 index 00000000000..c448cc921b1 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileView.js @@ -0,0 +1,897 @@ +/* + * Copyright (C) 2008 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + + +/** + * @constructor + * @extends {WebInspector.VBox} + * @param {!WebInspector.CPUProfileHeader} profileHeader + */ +WebInspector.CPUProfileView = function(profileHeader) +{ + WebInspector.VBox.call(this); + this.element.classList.add("cpu-profile-view"); + + this._viewType = WebInspector.settings.createSetting("cpuProfilerView", WebInspector.CPUProfileView._TypeHeavy); + + var columns = []; + columns.push({id: "self", title: WebInspector.UIString("Self"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true}); + columns.push({id: "total", title: WebInspector.UIString("Total"), width: "120px", sortable: true}); + columns.push({id: "function", title: WebInspector.UIString("Function"), disclosure: true, sortable: true}); + + this.dataGrid = new WebInspector.DataGrid(columns); + this.dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortProfile, this); + this.dataGrid.show(this.element); + + this.viewSelectComboBox = new WebInspector.StatusBarComboBox(this._changeView.bind(this)); + + var options = {}; + options[WebInspector.CPUProfileView._TypeFlame] = this.viewSelectComboBox.createOption(WebInspector.UIString("Chart"), "", WebInspector.CPUProfileView._TypeFlame); + options[WebInspector.CPUProfileView._TypeHeavy] = this.viewSelectComboBox.createOption(WebInspector.UIString("Heavy (Bottom Up)"), "", WebInspector.CPUProfileView._TypeHeavy); + options[WebInspector.CPUProfileView._TypeTree] = this.viewSelectComboBox.createOption(WebInspector.UIString("Tree (Top Down)"), "", WebInspector.CPUProfileView._TypeTree); + + var optionName = this._viewType.get() || WebInspector.CPUProfileView._TypeFlame; + var option = options[optionName] || options[WebInspector.CPUProfileView._TypeFlame]; + this.viewSelectComboBox.select(option); + + this._statusBarButtonsElement = document.createElement("span"); + + this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item"); + this.focusButton.setEnabled(false); + this.focusButton.addEventListener("click", this._focusClicked, this); + this._statusBarButtonsElement.appendChild(this.focusButton.element); + + this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item"); + this.excludeButton.setEnabled(false); + this.excludeButton.addEventListener("click", this._excludeClicked, this); + this._statusBarButtonsElement.appendChild(this.excludeButton.element); + + this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item"); + this.resetButton.visible = false; + this.resetButton.addEventListener("click", this._resetClicked, this); + this._statusBarButtonsElement.appendChild(this.resetButton.element); + + this._profileHeader = profileHeader; + this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultFormatter(30)); + + this.profile = new WebInspector.CPUProfileDataModel(profileHeader._profile || profileHeader.protocolProfile()); + + this._changeView(); + if (this._flameChart) + this._flameChart.update(); +} + +WebInspector.CPUProfileView._TypeFlame = "Flame"; +WebInspector.CPUProfileView._TypeTree = "Tree"; +WebInspector.CPUProfileView._TypeHeavy = "Heavy"; + +WebInspector.CPUProfileView.prototype = { + /** + * @param {!number} timeLeft + * @param {!number} timeRight + */ + selectRange: function(timeLeft, timeRight) + { + if (!this._flameChart) + return; + this._flameChart.selectRange(timeLeft, timeRight); + }, + + get statusBarItems() + { + return [this.viewSelectComboBox.element, this._statusBarButtonsElement]; + }, + + /** + * @return {!WebInspector.ProfileDataGridTree} + */ + _getBottomUpProfileDataGridTree: function() + { + if (!this._bottomUpProfileDataGridTree) + this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, /** @type {!ProfilerAgent.CPUProfileNode} */ (this.profile.profileHead)); + return this._bottomUpProfileDataGridTree; + }, + + /** + * @return {!WebInspector.ProfileDataGridTree} + */ + _getTopDownProfileDataGridTree: function() + { + if (!this._topDownProfileDataGridTree) + this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, /** @type {!ProfilerAgent.CPUProfileNode} */ (this.profile.profileHead)); + return this._topDownProfileDataGridTree; + }, + + willHide: function() + { + this._currentSearchResultIndex = -1; + }, + + refresh: function() + { + var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null; + + this.dataGrid.rootNode().removeChildren(); + + var children = this.profileDataGridTree.children; + var count = children.length; + + for (var index = 0; index < count; ++index) + this.dataGrid.rootNode().appendChild(children[index]); + + if (selectedProfileNode) + selectedProfileNode.selected = true; + }, + + refreshVisibleData: function() + { + var child = this.dataGrid.rootNode().children[0]; + while (child) { + child.refresh(); + child = child.traverseNextNode(false, null, true); + } + }, + + searchCanceled: function() + { + if (this._searchResults) { + for (var i = 0; i < this._searchResults.length; ++i) { + var profileNode = this._searchResults[i].profileNode; + + delete profileNode._searchMatchedSelfColumn; + delete profileNode._searchMatchedTotalColumn; + delete profileNode._searchMatchedFunctionColumn; + + 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 greaterThan = (query.startsWith(">")); + var lessThan = (query.startsWith("<")); + var equalTo = (query.startsWith("=") || ((greaterThan || lessThan) && query.indexOf("=") === 1)); + var percentUnits = (query.lastIndexOf("%") === (query.length - 1)); + var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2)); + var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1)); + + var queryNumber = parseFloat(query); + if (greaterThan || lessThan || equalTo) { + if (equalTo && (greaterThan || lessThan)) + queryNumber = parseFloat(query.substring(2)); + else + queryNumber = parseFloat(query.substring(1)); + } + + var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber); + + // Make equalTo implicitly true if it wasn't specified there is no other operator. + if (!isNaN(queryNumber) && !(greaterThan || lessThan)) + equalTo = true; + + var matcher = createPlainTextSearchRegex(query, "i"); + + function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode) + { + delete profileDataGridNode._searchMatchedSelfColumn; + delete profileDataGridNode._searchMatchedTotalColumn; + delete profileDataGridNode._searchMatchedFunctionColumn; + + if (percentUnits) { + if (lessThan) { + if (profileDataGridNode.selfPercent < queryNumber) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalPercent < queryNumber) + profileDataGridNode._searchMatchedTotalColumn = true; + } else if (greaterThan) { + if (profileDataGridNode.selfPercent > queryNumber) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalPercent > queryNumber) + profileDataGridNode._searchMatchedTotalColumn = true; + } + + if (equalTo) { + if (profileDataGridNode.selfPercent == queryNumber) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalPercent == queryNumber) + profileDataGridNode._searchMatchedTotalColumn = true; + } + } else if (millisecondsUnits || secondsUnits) { + if (lessThan) { + if (profileDataGridNode.selfTime < queryNumberMilliseconds) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalTime < queryNumberMilliseconds) + profileDataGridNode._searchMatchedTotalColumn = true; + } else if (greaterThan) { + if (profileDataGridNode.selfTime > queryNumberMilliseconds) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalTime > queryNumberMilliseconds) + profileDataGridNode._searchMatchedTotalColumn = true; + } + + if (equalTo) { + if (profileDataGridNode.selfTime == queryNumberMilliseconds) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalTime == queryNumberMilliseconds) + profileDataGridNode._searchMatchedTotalColumn = true; + } + } + + if (profileDataGridNode.functionName.match(matcher) || (profileDataGridNode.url && profileDataGridNode.url.match(matcher))) + profileDataGridNode._searchMatchedFunctionColumn = true; + + if (profileDataGridNode._searchMatchedSelfColumn || + profileDataGridNode._searchMatchedTotalColumn || + profileDataGridNode._searchMatchedFunctionColumn) + { + profileDataGridNode.refresh(); + return true; + } + + return false; + } + + var current = this.profileDataGridTree.children[0]; + + while (current) { + if (matchesQuery(current)) { + this._searchResults.push({ profileNode: current }); + } + + current = current.traverseNextNode(false, null, false); + } + + finishedCallback(this, this._searchResults.length); + }, + + jumpToFirstSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._currentSearchResultIndex = 0; + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToLastSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._currentSearchResultIndex = (this._searchResults.length - 1); + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToNextSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (++this._currentSearchResultIndex >= this._searchResults.length) + this._currentSearchResultIndex = 0; + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToPreviousSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (--this._currentSearchResultIndex < 0) + this._currentSearchResultIndex = (this._searchResults.length - 1); + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + /** + * @return {boolean} + */ + showingFirstSearchResult: function() + { + return (this._currentSearchResultIndex === 0); + }, + + /** + * @return {boolean} + */ + showingLastSearchResult: function() + { + return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); + }, + + /** + * @return {number} + */ + currentSearchResultIndex: function() { + return this._currentSearchResultIndex; + }, + + _jumpToSearchResult: function(index) + { + var searchResult = this._searchResults[index]; + if (!searchResult) + return; + + var profileNode = searchResult.profileNode; + profileNode.revealAndSelect(); + }, + + _ensureFlameChartCreated: function() + { + if (this._flameChart) + return; + this._dataProvider = new WebInspector.CPUFlameChartDataProvider(this.profile, this._profileHeader.target()); + this._flameChart = new WebInspector.CPUProfileFlameChart(this._dataProvider); + this._flameChart.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected.bind(this)); + }, + + /** + * @param {!WebInspector.Event} event + */ + _onEntrySelected: function(event) + { + var entryIndex = event.data; + var node = this._dataProvider._entryNodes[entryIndex]; + if (!node || !node.scriptId) + return; + var script = WebInspector.debuggerModel.scriptForId(node.scriptId) + if (!script) + return; + WebInspector.Revealer.reveal(script.rawLocationToUILocation(node.lineNumber)); + }, + + _changeView: function() + { + if (!this.profile) + return; + + switch (this.viewSelectComboBox.selectedOption().value) { + case WebInspector.CPUProfileView._TypeFlame: + this._ensureFlameChartCreated(); + this.dataGrid.detach(); + this._flameChart.show(this.element); + this._viewType.set(WebInspector.CPUProfileView._TypeFlame); + this._statusBarButtonsElement.classList.toggle("hidden", true); + return; + case WebInspector.CPUProfileView._TypeTree: + this.profileDataGridTree = this._getTopDownProfileDataGridTree(); + this._sortProfile(); + this._viewType.set(WebInspector.CPUProfileView._TypeTree); + break; + case WebInspector.CPUProfileView._TypeHeavy: + this.profileDataGridTree = this._getBottomUpProfileDataGridTree(); + this._sortProfile(); + this._viewType.set(WebInspector.CPUProfileView._TypeHeavy); + break; + } + + this._statusBarButtonsElement.classList.toggle("hidden", false); + + if (this._flameChart) + this._flameChart.detach(); + this.dataGrid.show(this.element); + + 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 the with same query and callback. + this._searchFinishedCallback(this, -this._searchResults.length); + this.performSearch(this.currentQuery, this._searchFinishedCallback); + }, + + _focusClicked: function(event) + { + if (!this.dataGrid.selectedNode) + return; + + this.resetButton.visible = true; + this.profileDataGridTree.focus(this.dataGrid.selectedNode); + this.refresh(); + this.refreshVisibleData(); + }, + + _excludeClicked: function(event) + { + var selectedNode = this.dataGrid.selectedNode + + if (!selectedNode) + return; + + selectedNode.deselect(); + + this.resetButton.visible = true; + this.profileDataGridTree.exclude(selectedNode); + this.refresh(); + this.refreshVisibleData(); + }, + + _resetClicked: function(event) + { + this.resetButton.visible = false; + this.profileDataGridTree.restore(); + this._linkifier.reset(); + this.refresh(); + this.refreshVisibleData(); + }, + + _dataGridNodeSelected: function(node) + { + this.focusButton.setEnabled(true); + this.excludeButton.setEnabled(true); + }, + + _dataGridNodeDeselected: function(node) + { + this.focusButton.setEnabled(false); + this.excludeButton.setEnabled(false); + }, + + _sortProfile: function() + { + var sortAscending = this.dataGrid.isSortOrderAscending(); + var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier(); + var sortProperty = { + "self": "selfTime", + "total": "totalTime", + "function": "functionName" + }[sortColumnIdentifier]; + + this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending)); + + this.refresh(); + }, + + __proto__: WebInspector.VBox.prototype +} + +/** + * @constructor + * @extends {WebInspector.ProfileType} + * @implements {WebInspector.CPUProfilerModel.Delegate} + */ +WebInspector.CPUProfileType = function() +{ + WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Collect JavaScript CPU Profile")); + this._recording = false; + + this._nextAnonymousConsoleProfileNumber = 1; + this._anonymousConsoleProfileIdToTitle = {}; + + WebInspector.CPUProfileType.instance = this; + WebInspector.cpuProfilerModel.setDelegate(this); +} + +WebInspector.CPUProfileType.TypeId = "CPU"; + +WebInspector.CPUProfileType.prototype = { + /** + * @override + * @return {string} + */ + fileExtension: function() + { + return ".cpuprofile"; + }, + + get buttonTooltip() + { + return this._recording ? WebInspector.UIString("Stop CPU profiling.") : WebInspector.UIString("Start CPU profiling."); + }, + + /** + * @override + * @return {boolean} + */ + buttonClicked: function() + { + if (this._recording) { + this.stopRecordingProfile(); + return false; + } else { + this.startRecordingProfile(); + return true; + } + }, + + get treeItemTitle() + { + return WebInspector.UIString("CPU PROFILES"); + }, + + get description() + { + return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions."); + }, + + /** + * @param {string} id + * @param {!WebInspector.DebuggerModel.Location} scriptLocation + * @param {string=} title + */ + consoleProfileStarted: function(id, scriptLocation, title) + { + var resolvedTitle = title; + if (!resolvedTitle) { + resolvedTitle = WebInspector.UIString("Profile %s", this._nextAnonymousConsoleProfileNumber++); + this._anonymousConsoleProfileIdToTitle[id] = resolvedTitle; + } + this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.Profile, scriptLocation, WebInspector.UIString("Profile '%s' started.", resolvedTitle)); + }, + + /** + * @param {string} protocolId + * @param {!WebInspector.DebuggerModel.Location} scriptLocation + * @param {!ProfilerAgent.CPUProfile} cpuProfile + * @param {string=} title + */ + consoleProfileFinished: function(protocolId, scriptLocation, cpuProfile, title) + { + var resolvedTitle = title; + if (typeof title === "undefined") { + resolvedTitle = this._anonymousConsoleProfileIdToTitle[protocolId]; + delete this._anonymousConsoleProfileIdToTitle[protocolId]; + } + + var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); + var profile = new WebInspector.CPUProfileHeader(target, this, resolvedTitle); + profile.setProtocolProfile(cpuProfile); + this.addProfile(profile); + this._addMessageToConsole(WebInspector.ConsoleMessage.MessageType.ProfileEnd, scriptLocation, WebInspector.UIString("Profile '%s' finished.", resolvedTitle)); + }, + + /** + * @param {string} type + * @param {!WebInspector.DebuggerModel.Location} scriptLocation + * @param {string} messageText + */ + _addMessageToConsole: function(type, scriptLocation, messageText) + { + var script = scriptLocation.script(); + var message = new WebInspector.ConsoleMessage( + WebInspector.console.target(), + WebInspector.ConsoleMessage.MessageSource.ConsoleAPI, + WebInspector.ConsoleMessage.MessageLevel.Debug, + messageText, + type, + undefined, + undefined, + undefined, + undefined, + undefined, + [{ + functionName: "", + scriptId: scriptLocation.scriptId, + url: script ? script.contentURL() : "", + lineNumber: scriptLocation.lineNumber, + columnNumber: scriptLocation.columnNumber || 0 + }]); + + WebInspector.console.addMessage(message); + }, + + /** + * @return {boolean} + */ + isRecordingProfile: function() + { + return this._recording; + }, + + startRecordingProfile: function() + { + if (this._profileBeingRecorded) + return; + var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); + var profile = new WebInspector.CPUProfileHeader(target, this); + this.setProfileBeingRecorded(profile); + this.addProfile(profile); + profile.updateStatus(WebInspector.UIString("Recording\u2026")); + this._recording = true; + WebInspector.cpuProfilerModel.setRecording(true); + WebInspector.userMetrics.ProfilesCPUProfileTaken.record(); + ProfilerAgent.start(); + }, + + stopRecordingProfile: function() + { + this._recording = false; + WebInspector.cpuProfilerModel.setRecording(false); + + /** + * @param {?string} error + * @param {?ProfilerAgent.CPUProfile} profile + * @this {WebInspector.CPUProfileType} + */ + function didStopProfiling(error, profile) + { + if (!this._profileBeingRecorded) + return; + this._profileBeingRecorded.setProtocolProfile(profile); + this._profileBeingRecorded.updateStatus(""); + var recordedProfile = this._profileBeingRecorded; + this.setProfileBeingRecorded(null); + this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, recordedProfile); + } + ProfilerAgent.stop(didStopProfiling.bind(this)); + }, + + /** + * @override + * @param {string} title + * @return {!WebInspector.ProfileHeader} + */ + createProfileLoadedFromFile: function(title) + { + var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); + return new WebInspector.CPUProfileHeader(target, this, title); + }, + + /** + * @override + */ + profileBeingRecordedRemoved: function() + { + this.stopRecordingProfile(); + }, + + __proto__: WebInspector.ProfileType.prototype +} + +/** + * @constructor + * @extends {WebInspector.ProfileHeader} + * @implements {WebInspector.OutputStream} + * @implements {WebInspector.OutputStreamDelegate} + * @param {!WebInspector.Target} target + * @param {!WebInspector.CPUProfileType} type + * @param {string=} title + */ +WebInspector.CPUProfileHeader = function(target, type, title) +{ + WebInspector.ProfileHeader.call(this, target, type, title || WebInspector.UIString("Profile %d", type._nextProfileUid)); + this._tempFile = null; +} + +WebInspector.CPUProfileHeader.prototype = { + onTransferStarted: function() + { + this._jsonifiedProfile = ""; + this.updateStatus(WebInspector.UIString("Loading\u2026 %s", Number.bytesToString(this._jsonifiedProfile.length)), true); + }, + + /** + * @param {!WebInspector.ChunkedReader} reader + */ + onChunkTransferred: function(reader) + { + this.updateStatus(WebInspector.UIString("Loading\u2026 %d\%", Number.bytesToString(this._jsonifiedProfile.length))); + }, + + onTransferFinished: function() + { + this.updateStatus(WebInspector.UIString("Parsing\u2026"), true); + this._profile = JSON.parse(this._jsonifiedProfile); + this._jsonifiedProfile = null; + this.updateStatus(WebInspector.UIString("Loaded"), false); + + if (this._profileType.profileBeingRecorded() === this) + this._profileType.setProfileBeingRecorded(null); + }, + + /** + * @param {!WebInspector.ChunkedReader} reader + * @param {?Event} e + */ + onError: function(reader, e) + { + var subtitle; + switch(e.target.error.code) { + case e.target.error.NOT_FOUND_ERR: + subtitle = WebInspector.UIString("'%s' not found.", reader.fileName()); + break; + case e.target.error.NOT_READABLE_ERR: + subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName()); + break; + case e.target.error.ABORT_ERR: + return; + default: + subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code); + } + this.updateStatus(subtitle); + }, + + /** + * @param {string} text + */ + write: function(text) + { + this._jsonifiedProfile += text; + }, + + close: function() { }, + + /** + * @override + */ + dispose: function() + { + this.removeTempFile(); + }, + + /** + * @override + * @param {!WebInspector.ProfilesPanel} panel + * @return {!WebInspector.ProfileSidebarTreeElement} + */ + createSidebarTreeElement: function(panel) + { + return new WebInspector.ProfileSidebarTreeElement(panel, this, "profile-sidebar-tree-item"); + }, + + /** + * @override + * @return {!WebInspector.CPUProfileView} + */ + createView: function() + { + return new WebInspector.CPUProfileView(this); + }, + + /** + * @override + * @return {boolean} + */ + canSaveToFile: function() + { + return !this.fromFile() && this._protocolProfile; + }, + + saveToFile: function() + { + var fileOutputStream = new WebInspector.FileOutputStream(); + + /** + * @param {boolean} accepted + * @this {WebInspector.CPUProfileHeader} + */ + function onOpenForSave(accepted) + { + if (!accepted) + return; + function didRead(data) + { + if (data) + fileOutputStream.write(data, fileOutputStream.close.bind(fileOutputStream)); + else + fileOutputStream.close(); + } + if (this._failedToCreateTempFile) { + WebInspector.messageSink.addErrorMessage("Failed to open temp file with heap snapshot"); + fileOutputStream.close(); + } else if (this._tempFile) { + this._tempFile.read(didRead); + } else { + this._onTempFileReady = onOpenForSave.bind(this, accepted); + } + } + this._fileName = this._fileName || "CPU-" + new Date().toISO8601Compact() + this._profileType.fileExtension(); + fileOutputStream.open(this._fileName, onOpenForSave.bind(this)); + }, + + /** + * @param {!File} file + */ + loadFromFile: function(file) + { + this.updateStatus(WebInspector.UIString("Loading\u2026"), true); + var fileReader = new WebInspector.ChunkedFileReader(file, 10000000, this); + fileReader.start(this); + }, + + + /** + * @return {?ProfilerAgent.CPUProfile} + */ + protocolProfile: function() + { + return this._protocolProfile; + }, + + /** + * @param {!ProfilerAgent.CPUProfile} cpuProfile + */ + setProtocolProfile: function(cpuProfile) + { + this._protocolProfile = cpuProfile; + this._saveProfileDataToTempFile(cpuProfile); + if (this.canSaveToFile()) + this.dispatchEventToListeners(WebInspector.ProfileHeader.Events.ProfileReceived); + }, + + /** + * @param {!ProfilerAgent.CPUProfile} data + */ + _saveProfileDataToTempFile: function(data) + { + var serializedData = JSON.stringify(data); + + /** + * @this {WebInspector.CPUProfileHeader} + */ + function didCreateTempFile(tempFile) + { + this._writeToTempFile(tempFile, serializedData); + } + new WebInspector.TempFile("cpu-profiler", this.uid, didCreateTempFile.bind(this)); + }, + + /** + * @param {?WebInspector.TempFile} tempFile + * @param {string} serializedData + */ + _writeToTempFile: function(tempFile, serializedData) + { + this._tempFile = tempFile; + if (!tempFile) { + this._failedToCreateTempFile = true; + this._notifyTempFileReady(); + return; + } + /** + * @param {boolean} success + * @this {WebInspector.CPUProfileHeader} + */ + function didWriteToTempFile(success) + { + if (!success) + this._failedToCreateTempFile = true; + tempFile.finishWriting(); + this._notifyTempFileReady(); + } + tempFile.write(serializedData, didWriteToTempFile.bind(this)); + }, + + _notifyTempFileReady: function() + { + if (this._onTempFileReady) { + this._onTempFileReady(); + this._onTempFileReady = null; + } + }, + + __proto__: WebInspector.ProfileHeader.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CanvasProfileView.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CanvasProfileView.js new file mode 100644 index 00000000000..edaeedb3cde --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CanvasProfileView.js @@ -0,0 +1,1276 @@ +/* + * Copyright (C) 2013 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. + */ + +/** + * @constructor + * @extends {WebInspector.VBox} + * @param {!WebInspector.CanvasProfileHeader} profile + */ +WebInspector.CanvasProfileView = function(profile) +{ + WebInspector.VBox.call(this); + this.registerRequiredCSS("canvasProfiler.css"); + this.element.classList.add("canvas-profile-view"); + + this._profile = profile; + this._traceLogId = profile.traceLogId(); + this._traceLogPlayer = /** @type {!WebInspector.CanvasTraceLogPlayerProxy} */ (profile.traceLogPlayer()); + this._linkifier = new WebInspector.Linkifier(); + + this._replayInfoSplitView = new WebInspector.SplitView(true, true, "canvasProfileViewReplaySplitViewState", 0.34); + this._replayInfoSplitView.show(this.element); + + this._imageSplitView = new WebInspector.SplitView(false, true, "canvasProfileViewSplitViewState", 300); + this._imageSplitView.show(this._replayInfoSplitView.mainElement()); + + var replayImageContainerView = new WebInspector.VBox(); + replayImageContainerView.setMinimumSize(50, 28); + replayImageContainerView.show(this._imageSplitView.mainElement()); + + // NOTE: The replayImageContainer can NOT be a flex div (e.g. VBox or SplitView elements)! + var replayImageContainer = replayImageContainerView.element.createChild("div"); + replayImageContainer.id = "canvas-replay-image-container"; + this._replayImageElement = replayImageContainer.createChild("img", "canvas-replay-image"); + this._debugInfoElement = replayImageContainer.createChild("div", "canvas-debug-info hidden"); + this._spinnerIcon = replayImageContainer.createChild("div", "spinner-icon small hidden"); + + var replayLogContainerView = new WebInspector.VBox(); + replayLogContainerView.setMinimumSize(22, 22); + replayLogContainerView.show(this._imageSplitView.sidebarElement()); + + var replayLogContainer = replayLogContainerView.element; + var controlsContainer = replayLogContainer.createChild("div", "status-bar"); + var logGridContainer = replayLogContainer.createChild("div", "canvas-replay-log"); + + this._createControlButton(controlsContainer, "canvas-replay-first-step", WebInspector.UIString("First call."), this._onReplayFirstStepClick.bind(this)); + this._createControlButton(controlsContainer, "canvas-replay-prev-step", WebInspector.UIString("Previous call."), this._onReplayStepClick.bind(this, false)); + this._createControlButton(controlsContainer, "canvas-replay-next-step", WebInspector.UIString("Next call."), this._onReplayStepClick.bind(this, true)); + this._createControlButton(controlsContainer, "canvas-replay-prev-draw", WebInspector.UIString("Previous drawing call."), this._onReplayDrawingCallClick.bind(this, false)); + this._createControlButton(controlsContainer, "canvas-replay-next-draw", WebInspector.UIString("Next drawing call."), this._onReplayDrawingCallClick.bind(this, true)); + this._createControlButton(controlsContainer, "canvas-replay-last-step", WebInspector.UIString("Last call."), this._onReplayLastStepClick.bind(this)); + + this._replayContextSelector = new WebInspector.StatusBarComboBox(this._onReplayContextChanged.bind(this)); + this._replayContextSelector.createOption(WebInspector.UIString("<screenshot auto>"), WebInspector.UIString("Show screenshot of the last replayed resource."), ""); + controlsContainer.appendChild(this._replayContextSelector.element); + + this._installReplayInfoSidebarWidgets(controlsContainer); + + this._replayStateView = new WebInspector.CanvasReplayStateView(this._traceLogPlayer); + this._replayStateView.show(this._replayInfoSplitView.sidebarElement()); + + /** @type {!Object.<string, boolean>} */ + this._replayContexts = {}; + + var columns = [ + {title: "#", sortable: false, width: "5%"}, + {title: WebInspector.UIString("Call"), sortable: false, width: "75%", disclosure: true}, + {title: WebInspector.UIString("Location"), sortable: false, width: "20%"} + ]; + + this._logGrid = new WebInspector.DataGrid(columns); + this._logGrid.element.classList.add("fill"); + this._logGrid.show(logGridContainer); + this._logGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._replayTraceLog, this); + + this.element.addEventListener("mousedown", this._onMouseClick.bind(this), true); + + this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._popoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), this._onHidePopover.bind(this), true); + this._popoverHelper.setRemoteObjectFormatter(this._hexNumbersFormatter.bind(this)); + + this._requestTraceLog(0); +} + +/** + * @const + * @type {number} + */ +WebInspector.CanvasProfileView.TraceLogPollingInterval = 500; + +WebInspector.CanvasProfileView.prototype = { + dispose: function() + { + this._linkifier.reset(); + }, + + get statusBarItems() + { + return []; + }, + + get profile() + { + return this._profile; + }, + + /** + * @override + * @return {!Array.<!Element>} + */ + elementsToRestoreScrollPositionsFor: function() + { + return [this._logGrid.scrollContainer]; + }, + + /** + * @param {!Element} controlsContainer + */ + _installReplayInfoSidebarWidgets: function(controlsContainer) + { + this._replayInfoResizeWidgetElement = controlsContainer.createChild("div", "resizer-widget"); + this._replayInfoSplitView.addEventListener(WebInspector.SplitView.Events.ShowModeChanged, this._updateReplayInfoResizeWidget, this); + this._updateReplayInfoResizeWidget(); + this._replayInfoSplitView.installResizer(this._replayInfoResizeWidgetElement); + + this._toggleReplayStateSidebarButton = this._replayInfoSplitView.createShowHideSidebarButton("sidebar", "canvas-sidebar-show-hide-button"); + + controlsContainer.appendChild(this._toggleReplayStateSidebarButton.element); + this._replayInfoSplitView.hideSidebar(); + }, + + _updateReplayInfoResizeWidget: function() + { + this._replayInfoResizeWidgetElement.classList.toggle("hidden", this._replayInfoSplitView.showMode() !== WebInspector.SplitView.ShowMode.Both); + }, + + /** + * @param {?Event} event + */ + _onMouseClick: function(event) + { + var resourceLinkElement = event.target.enclosingNodeOrSelfWithClass("canvas-formatted-resource"); + if (resourceLinkElement) { + this._replayInfoSplitView.showBoth(); + this._replayStateView.selectResource(resourceLinkElement.__resourceId); + event.consume(true); + return; + } + if (event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link")) + event.consume(false); + }, + + /** + * @param {!Element} parent + * @param {string} className + * @param {string} title + * @param {function(this:WebInspector.CanvasProfileView)} clickCallback + */ + _createControlButton: function(parent, className, title, clickCallback) + { + var button = new WebInspector.StatusBarButton(title, className + " canvas-replay-button"); + parent.appendChild(button.element); + + button.makeLongClickEnabled(); + button.addEventListener("click", clickCallback, this); + button.addEventListener("longClickDown", clickCallback, this); + button.addEventListener("longClickPress", clickCallback, this); + }, + + _onReplayContextChanged: function() + { + var selectedContextId = this._replayContextSelector.selectedOption().value; + + /** + * @param {?CanvasAgent.ResourceState} resourceState + * @this {WebInspector.CanvasProfileView} + */ + function didReceiveResourceState(resourceState) + { + this._enableWaitIcon(false); + if (selectedContextId !== this._replayContextSelector.selectedOption().value) + return; + var imageURL = (resourceState && resourceState.imageURL) || ""; + this._replayImageElement.src = imageURL; + this._replayImageElement.style.visibility = imageURL ? "" : "hidden"; + } + + this._enableWaitIcon(true); + this._traceLogPlayer.getResourceState(selectedContextId, didReceiveResourceState.bind(this)); + }, + + /** + * @param {boolean} forward + */ + _onReplayStepClick: function(forward) + { + var selectedNode = this._logGrid.selectedNode; + if (!selectedNode) + return; + var nextNode = selectedNode; + do { + nextNode = forward ? nextNode.traverseNextNode(false) : nextNode.traversePreviousNode(false); + } while (nextNode && typeof nextNode.index !== "number"); + (nextNode || selectedNode).revealAndSelect(); + }, + + /** + * @param {boolean} forward + */ + _onReplayDrawingCallClick: function(forward) + { + var selectedNode = this._logGrid.selectedNode; + if (!selectedNode) + return; + var nextNode = selectedNode; + while (nextNode) { + var sibling = forward ? nextNode.nextSibling : nextNode.previousSibling; + if (sibling) { + nextNode = sibling; + if (nextNode.hasChildren || nextNode.call.isDrawingCall) + break; + } else { + nextNode = nextNode.parent; + if (!forward) + break; + } + } + if (!nextNode && forward) + this._onReplayLastStepClick(); + else + (nextNode || selectedNode).revealAndSelect(); + }, + + _onReplayFirstStepClick: function() + { + var firstNode = this._logGrid.rootNode().children[0]; + if (firstNode) + firstNode.revealAndSelect(); + }, + + _onReplayLastStepClick: function() + { + var lastNode = this._logGrid.rootNode().children.peekLast(); + if (!lastNode) + return; + while (lastNode.expanded) { + var lastChild = lastNode.children.peekLast(); + if (!lastChild) + break; + lastNode = lastChild; + } + lastNode.revealAndSelect(); + }, + + /** + * @param {boolean} enable + */ + _enableWaitIcon: function(enable) + { + this._spinnerIcon.classList.toggle("hidden", !enable); + this._debugInfoElement.classList.toggle("hidden", enable); + }, + + _replayTraceLog: function() + { + if (this._pendingReplayTraceLogEvent) + return; + var index = this._selectedCallIndex(); + if (index === -1 || index === this._lastReplayCallIndex) + return; + this._lastReplayCallIndex = index; + this._pendingReplayTraceLogEvent = true; + + /** + * @param {?CanvasAgent.ResourceState} resourceState + * @param {number} replayTime + * @this {WebInspector.CanvasProfileView} + */ + function didReplayTraceLog(resourceState, replayTime) + { + delete this._pendingReplayTraceLogEvent; + this._enableWaitIcon(false); + + this._debugInfoElement.textContent = WebInspector.UIString("Replay time: %s", Number.secondsToString(replayTime / 1000, true)); + this._onReplayContextChanged(); + + if (index !== this._selectedCallIndex()) + this._replayTraceLog(); + } + this._enableWaitIcon(true); + this._traceLogPlayer.replayTraceLog(index, didReplayTraceLog.bind(this)); + }, + + /** + * @param {number} offset + */ + _requestTraceLog: function(offset) + { + /** + * @param {?CanvasAgent.TraceLog} traceLog + * @this {WebInspector.CanvasProfileView} + */ + function didReceiveTraceLog(traceLog) + { + this._enableWaitIcon(false); + if (!traceLog) + return; + var callNodes = []; + var calls = traceLog.calls; + var index = traceLog.startOffset; + for (var i = 0, n = calls.length; i < n; ++i) + callNodes.push(this._createCallNode(index++, calls[i])); + var contexts = traceLog.contexts; + for (var i = 0, n = contexts.length; i < n; ++i) { + var contextId = contexts[i].resourceId || ""; + var description = contexts[i].description || ""; + if (this._replayContexts[contextId]) + continue; + this._replayContexts[contextId] = true; + this._replayContextSelector.createOption(description, WebInspector.UIString("Show screenshot of this context's canvas."), contextId); + } + this._appendCallNodes(callNodes); + if (traceLog.alive) + setTimeout(this._requestTraceLog.bind(this, index), WebInspector.CanvasProfileView.TraceLogPollingInterval); + else + this._flattenSingleFrameNode(); + this._profile._updateCapturingStatus(traceLog); + this._onReplayLastStepClick(); // Automatically replay the last step. + } + this._enableWaitIcon(true); + this._traceLogPlayer.getTraceLog(offset, undefined, didReceiveTraceLog.bind(this)); + }, + + /** + * @return {number} + */ + _selectedCallIndex: function() + { + var node = this._logGrid.selectedNode; + return node ? this._peekLastRecursively(node).index : -1; + }, + + /** + * @param {!WebInspector.DataGridNode} node + * @return {!WebInspector.DataGridNode} + */ + _peekLastRecursively: function(node) + { + var lastChild; + while ((lastChild = node.children.peekLast())) + node = lastChild; + return node; + }, + + /** + * @param {!Array.<!WebInspector.DataGridNode>} callNodes + */ + _appendCallNodes: function(callNodes) + { + var rootNode = this._logGrid.rootNode(); + var frameNode = rootNode.children.peekLast(); + if (frameNode && this._peekLastRecursively(frameNode).call.isFrameEndCall) + frameNode = null; + for (var i = 0, n = callNodes.length; i < n; ++i) { + if (!frameNode) { + var index = rootNode.children.length; + var data = {}; + data[0] = ""; + data[1] = WebInspector.UIString("Frame #%d", index + 1); + data[2] = ""; + frameNode = new WebInspector.DataGridNode(data); + frameNode.selectable = true; + rootNode.appendChild(frameNode); + } + var nextFrameCallIndex = i + 1; + while (nextFrameCallIndex < n && !callNodes[nextFrameCallIndex - 1].call.isFrameEndCall) + ++nextFrameCallIndex; + this._appendCallNodesToFrameNode(frameNode, callNodes, i, nextFrameCallIndex); + i = nextFrameCallIndex - 1; + frameNode = null; + } + }, + + /** + * @param {!WebInspector.DataGridNode} frameNode + * @param {!Array.<!WebInspector.DataGridNode>} callNodes + * @param {number} fromIndex + * @param {number} toIndex not inclusive + */ + _appendCallNodesToFrameNode: function(frameNode, callNodes, fromIndex, toIndex) + { + var self = this; + function appendDrawCallGroup() + { + var index = self._drawCallGroupsCount || 0; + var data = {}; + data[0] = ""; + data[1] = WebInspector.UIString("Draw call group #%d", index + 1); + data[2] = ""; + var node = new WebInspector.DataGridNode(data); + node.selectable = true; + self._drawCallGroupsCount = index + 1; + frameNode.appendChild(node); + return node; + } + + function splitDrawCallGroup(drawCallGroup) + { + var splitIndex = 0; + var splitNode; + while ((splitNode = drawCallGroup.children[splitIndex])) { + if (splitNode.call.isDrawingCall) + break; + ++splitIndex; + } + var newDrawCallGroup = appendDrawCallGroup(); + var lastNode; + while ((lastNode = drawCallGroup.children[splitIndex + 1])) + newDrawCallGroup.appendChild(lastNode); + return newDrawCallGroup; + } + + var drawCallGroup = frameNode.children.peekLast(); + var groupHasDrawCall = false; + if (drawCallGroup) { + for (var i = 0, n = drawCallGroup.children.length; i < n; ++i) { + if (drawCallGroup.children[i].call.isDrawingCall) { + groupHasDrawCall = true; + break; + } + } + } else + drawCallGroup = appendDrawCallGroup(); + + for (var i = fromIndex; i < toIndex; ++i) { + var node = callNodes[i]; + drawCallGroup.appendChild(node); + if (node.call.isDrawingCall) { + if (groupHasDrawCall) + drawCallGroup = splitDrawCallGroup(drawCallGroup); + else + groupHasDrawCall = true; + } + } + }, + + /** + * @param {number} index + * @param {!CanvasAgent.Call} call + * @return {!WebInspector.DataGridNode} + */ + _createCallNode: function(index, call) + { + var callViewElement = document.createElement("div"); + + var data = {}; + data[0] = index + 1; + data[1] = callViewElement; + data[2] = ""; + if (call.sourceURL) { + // FIXME(62725): stack trace line/column numbers are one-based. + var lineNumber = Math.max(0, call.lineNumber - 1) || 0; + var columnNumber = Math.max(0, call.columnNumber - 1) || 0; + data[2] = this._linkifier.linkifyLocation(this.profile.target(), call.sourceURL, lineNumber, columnNumber); + } + + callViewElement.createChild("span", "canvas-function-name").textContent = call.functionName || "context." + call.property; + + if (call.arguments) { + callViewElement.createTextChild("("); + for (var i = 0, n = call.arguments.length; i < n; ++i) { + var argument = /** @type {!CanvasAgent.CallArgument} */ (call.arguments[i]); + if (i) + callViewElement.createTextChild(", "); + var element = WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(argument); + element.__argumentIndex = i; + callViewElement.appendChild(element); + } + callViewElement.createTextChild(")"); + } else if (call.value) { + callViewElement.createTextChild(" = "); + callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.value)); + } + + if (call.result) { + callViewElement.createTextChild(" => "); + callViewElement.appendChild(WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(call.result)); + } + + var node = new WebInspector.DataGridNode(data); + node.index = index; + node.selectable = true; + node.call = call; + return node; + }, + + _popoverAnchor: function(element, event) + { + var argumentElement = element.enclosingNodeOrSelfWithClass("canvas-call-argument"); + if (!argumentElement || argumentElement.__suppressPopover) + return null; + return argumentElement; + }, + + _resolveObjectForPopover: function(argumentElement, showCallback, objectGroupName) + { + /** + * @param {?Protocol.Error} error + * @param {!RuntimeAgent.RemoteObject=} result + * @param {!CanvasAgent.ResourceState=} resourceState + * @this {WebInspector.CanvasProfileView} + */ + function showObjectPopover(error, result, resourceState) + { + if (error) + return; + + // FIXME: handle resourceState also + if (!result) + return; + + this._popoverAnchorElement = argumentElement.cloneNode(true); + this._popoverAnchorElement.classList.add("canvas-popover-anchor"); + this._popoverAnchorElement.classList.add("source-frame-eval-expression"); + argumentElement.parentElement.appendChild(this._popoverAnchorElement); + + var diffLeft = this._popoverAnchorElement.boxInWindow().x - argumentElement.boxInWindow().x; + this._popoverAnchorElement.style.left = this._popoverAnchorElement.offsetLeft - diffLeft + "px"; + + showCallback(WebInspector.runtimeModel.createRemoteObject(result), false, this._popoverAnchorElement); + } + + var evalResult = argumentElement.__evalResult; + if (evalResult) + showObjectPopover.call(this, null, evalResult); + else { + var dataGridNode = this._logGrid.dataGridNodeFromNode(argumentElement); + if (!dataGridNode || typeof dataGridNode.index !== "number") { + this._popoverHelper.hidePopover(); + return; + } + var callIndex = dataGridNode.index; + var argumentIndex = argumentElement.__argumentIndex; + if (typeof argumentIndex !== "number") + argumentIndex = -1; + CanvasAgent.evaluateTraceLogCallArgument(this._traceLogId, callIndex, argumentIndex, objectGroupName, showObjectPopover.bind(this)); + } + }, + + /** + * @param {!WebInspector.RemoteObject} object + * @return {string} + */ + _hexNumbersFormatter: function(object) + { + if (object.type === "number") { + // Show enum values in hex with min length of 4 (e.g. 0x0012). + var str = "0000" + Number(object.description).toString(16).toUpperCase(); + str = str.replace(/^0+(.{4,})$/, "$1"); + return "0x" + str; + } + return object.description || ""; + }, + + _onHidePopover: function() + { + if (this._popoverAnchorElement) { + this._popoverAnchorElement.remove() + delete this._popoverAnchorElement; + } + }, + + _flattenSingleFrameNode: function() + { + var rootNode = this._logGrid.rootNode(); + if (rootNode.children.length !== 1) + return; + var frameNode = rootNode.children[0]; + while (frameNode.children[0]) + rootNode.appendChild(frameNode.children[0]); + rootNode.removeChild(frameNode); + }, + + __proto__: WebInspector.VBox.prototype +} + +/** + * @constructor + * @extends {WebInspector.ProfileType} + */ +WebInspector.CanvasProfileType = function() +{ + WebInspector.ProfileType.call(this, WebInspector.CanvasProfileType.TypeId, WebInspector.UIString("Capture Canvas Frame")); + this._recording = false; + this._lastProfileHeader = null; + + this._capturingModeSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this)); + this._capturingModeSelector.element.title = WebInspector.UIString("Canvas capture mode."); + this._capturingModeSelector.createOption(WebInspector.UIString("Single Frame"), WebInspector.UIString("Capture a single canvas frame."), ""); + this._capturingModeSelector.createOption(WebInspector.UIString("Consecutive Frames"), WebInspector.UIString("Capture consecutive canvas frames."), "1"); + + /** @type {!Object.<string, !Element>} */ + this._frameOptions = {}; + + /** @type {!Object.<string, boolean>} */ + this._framesWithCanvases = {}; + + this._frameSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this)); + this._frameSelector.element.title = WebInspector.UIString("Frame containing the canvases to capture."); + this._frameSelector.element.classList.add("hidden"); + + this._target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); + this._target.resourceTreeModel.frames().forEach(this._addFrame, this); + this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this); + this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameRemoved, this); + + this._dispatcher = new WebInspector.CanvasDispatcher(this); + this._canvasAgentEnabled = false; + + this._decorationElement = document.createElement("div"); + this._decorationElement.className = "profile-canvas-decoration"; + this._updateDecorationElement(); +} + +WebInspector.CanvasProfileType.TypeId = "CANVAS_PROFILE"; + +WebInspector.CanvasProfileType.prototype = { + get statusBarItems() + { + return [this._capturingModeSelector.element, this._frameSelector.element]; + }, + + get buttonTooltip() + { + if (this._isSingleFrameMode()) + return WebInspector.UIString("Capture next canvas frame."); + else + return this._recording ? WebInspector.UIString("Stop capturing canvas frames.") : WebInspector.UIString("Start capturing canvas frames."); + }, + + /** + * @override + * @return {boolean} + */ + buttonClicked: function() + { + if (!this._canvasAgentEnabled) + return false; + if (this._recording) { + this._recording = false; + this._stopFrameCapturing(); + } else if (this._isSingleFrameMode()) { + this._recording = false; + this._runSingleFrameCapturing(); + } else { + this._recording = true; + this._startFrameCapturing(); + } + return this._recording; + }, + + _runSingleFrameCapturing: function() + { + var frameId = this._selectedFrameId(); + this._target.profilingLock.acquire(); + CanvasAgent.captureFrame(frameId, this._didStartCapturingFrame.bind(this, frameId)); + this._target.profilingLock.release(); + }, + + _startFrameCapturing: function() + { + var frameId = this._selectedFrameId(); + this._target.profilingLock.acquire(); + CanvasAgent.startCapturing(frameId, this._didStartCapturingFrame.bind(this, frameId)); + }, + + _stopFrameCapturing: function() + { + if (!this._lastProfileHeader) { + this._target.profilingLock.release(); + return; + } + var profileHeader = this._lastProfileHeader; + var traceLogId = profileHeader.traceLogId(); + this._lastProfileHeader = null; + function didStopCapturing() + { + profileHeader._updateCapturingStatus(); + } + CanvasAgent.stopCapturing(traceLogId, didStopCapturing); + this._target.profilingLock.release(); + }, + + /** + * @param {string|undefined} frameId + * @param {?Protocol.Error} error + * @param {!CanvasAgent.TraceLogId} traceLogId + */ + _didStartCapturingFrame: function(frameId, error, traceLogId) + { + if (error || this._lastProfileHeader && this._lastProfileHeader.traceLogId() === traceLogId) + return; + var profileHeader = new WebInspector.CanvasProfileHeader(this._target, this, traceLogId, frameId); + this._lastProfileHeader = profileHeader; + this.addProfile(profileHeader); + profileHeader._updateCapturingStatus(); + }, + + get treeItemTitle() + { + return WebInspector.UIString("CANVAS PROFILE"); + }, + + get description() + { + return WebInspector.UIString("Canvas calls instrumentation"); + }, + + /** + * @override + * @return {!Element} + */ + decorationElement: function() + { + return this._decorationElement; + }, + + /** + * @override + * @param {!WebInspector.ProfileHeader} profile + */ + removeProfile: function(profile) + { + WebInspector.ProfileType.prototype.removeProfile.call(this, profile); + if (this._recording && profile === this._lastProfileHeader) + this._recording = false; + }, + + /** + * @param {boolean=} forcePageReload + */ + _updateDecorationElement: function(forcePageReload) + { + this._decorationElement.removeChildren(); + this._decorationElement.createChild("div", "warning-icon-small"); + this._decorationElement.appendChild(document.createTextNode(this._canvasAgentEnabled ? WebInspector.UIString("Canvas Profiler is enabled.") : WebInspector.UIString("Canvas Profiler is disabled."))); + var button = this._decorationElement.createChild("button"); + button.type = "button"; + button.textContent = this._canvasAgentEnabled ? WebInspector.UIString("Disable") : WebInspector.UIString("Enable"); + button.addEventListener("click", this._onProfilerEnableButtonClick.bind(this, !this._canvasAgentEnabled), false); + + var target = this._target; + /** + * @param {?Protocol.Error} error + * @param {boolean} result + */ + function hasUninstrumentedCanvasesCallback(error, result) + { + if (error || result) + target.resourceTreeModel.reloadPage(); + } + + if (forcePageReload) { + if (this._canvasAgentEnabled) { + CanvasAgent.hasUninstrumentedCanvases(hasUninstrumentedCanvasesCallback); + } else { + for (var frameId in this._framesWithCanvases) { + if (this._framesWithCanvases.hasOwnProperty(frameId)) { + target.resourceTreeModel.reloadPage(); + break; + } + } + } + } + }, + + /** + * @param {boolean} enable + */ + _onProfilerEnableButtonClick: function(enable) + { + if (this._canvasAgentEnabled === enable) + return; + + /** + * @param {?Protocol.Error} error + * @this {WebInspector.CanvasProfileType} + */ + function callback(error) + { + if (error) + return; + this._canvasAgentEnabled = enable; + this._updateDecorationElement(true); + this._dispatchViewUpdatedEvent(); + } + if (enable) + CanvasAgent.enable(callback.bind(this)); + else + CanvasAgent.disable(callback.bind(this)); + }, + + /** + * @return {boolean} + */ + _isSingleFrameMode: function() + { + return !this._capturingModeSelector.selectedOption().value; + }, + + /** + * @param {!WebInspector.Event} event + */ + _frameAdded: function(event) + { + var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data); + this._addFrame(frame); + }, + + /** + * @param {!WebInspector.ResourceTreeFrame} frame + */ + _addFrame: function(frame) + { + var frameId = frame.id; + var option = document.createElement("option"); + option.text = frame.displayName(); + option.title = frame.url; + option.value = frameId; + + this._frameOptions[frameId] = option; + + if (this._framesWithCanvases[frameId]) { + this._frameSelector.addOption(option); + this._dispatchViewUpdatedEvent(); + } + }, + + /** + * @param {!WebInspector.Event} event + */ + _frameRemoved: function(event) + { + var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data); + var frameId = frame.id; + var option = this._frameOptions[frameId]; + if (option && this._framesWithCanvases[frameId]) { + this._frameSelector.removeOption(option); + this._dispatchViewUpdatedEvent(); + } + delete this._frameOptions[frameId]; + delete this._framesWithCanvases[frameId]; + }, + + /** + * @param {string} frameId + */ + _contextCreated: function(frameId) + { + if (this._framesWithCanvases[frameId]) + return; + this._framesWithCanvases[frameId] = true; + var option = this._frameOptions[frameId]; + if (option) { + this._frameSelector.addOption(option); + this._dispatchViewUpdatedEvent(); + } + }, + + /** + * @param {!PageAgent.FrameId=} frameId + * @param {!CanvasAgent.TraceLogId=} traceLogId + */ + _traceLogsRemoved: function(frameId, traceLogId) + { + var sidebarElementsToDelete = []; + var sidebarElements = /** @type {!Array.<!WebInspector.ProfileSidebarTreeElement>} */ ((this.treeElement && this.treeElement.children) || []); + for (var i = 0, n = sidebarElements.length; i < n; ++i) { + var header = /** @type {!WebInspector.CanvasProfileHeader} */ (sidebarElements[i].profile); + if (!header) + continue; + if (frameId && frameId !== header.frameId()) + continue; + if (traceLogId && traceLogId !== header.traceLogId()) + continue; + sidebarElementsToDelete.push(sidebarElements[i]); + } + for (var i = 0, n = sidebarElementsToDelete.length; i < n; ++i) + sidebarElementsToDelete[i].ondelete(); + }, + + /** + * @return {string|undefined} + */ + _selectedFrameId: function() + { + var option = this._frameSelector.selectedOption(); + return option ? option.value : undefined; + }, + + _dispatchViewUpdatedEvent: function() + { + this._frameSelector.element.classList.toggle("hidden", this._frameSelector.size() <= 1); + this.dispatchEventToListeners(WebInspector.ProfileType.Events.ViewUpdated); + }, + + /** + * @override + * @return {boolean} + */ + isInstantProfile: function() + { + return this._isSingleFrameMode(); + }, + + /** + * @override + * @return {boolean} + */ + isEnabled: function() + { + return this._canvasAgentEnabled; + }, + + __proto__: WebInspector.ProfileType.prototype +} + +/** + * @constructor + * @implements {CanvasAgent.Dispatcher} + * @param {!WebInspector.CanvasProfileType} profileType + */ +WebInspector.CanvasDispatcher = function(profileType) +{ + this._profileType = profileType; + InspectorBackend.registerCanvasDispatcher(this); +} + +WebInspector.CanvasDispatcher.prototype = { + /** + * @param {string} frameId + */ + contextCreated: function(frameId) + { + this._profileType._contextCreated(frameId); + }, + + /** + * @param {!PageAgent.FrameId=} frameId + * @param {!CanvasAgent.TraceLogId=} traceLogId + */ + traceLogsRemoved: function(frameId, traceLogId) + { + this._profileType._traceLogsRemoved(frameId, traceLogId); + } +} + +/** + * @constructor + * @extends {WebInspector.ProfileHeader} + * @param {!WebInspector.Target} target + * @param {!WebInspector.CanvasProfileType} type + * @param {!CanvasAgent.TraceLogId=} traceLogId + * @param {!PageAgent.FrameId=} frameId + */ +WebInspector.CanvasProfileHeader = function(target, type, traceLogId, frameId) +{ + WebInspector.ProfileHeader.call(this, target, type, WebInspector.UIString("Trace Log %d", type._nextProfileUid)); + /** @type {!CanvasAgent.TraceLogId} */ + this._traceLogId = traceLogId || ""; + this._frameId = frameId; + this._alive = true; + this._traceLogSize = 0; + this._traceLogPlayer = traceLogId ? new WebInspector.CanvasTraceLogPlayerProxy(traceLogId) : null; +} + +WebInspector.CanvasProfileHeader.prototype = { + /** + * @return {!CanvasAgent.TraceLogId} + */ + traceLogId: function() + { + return this._traceLogId; + }, + + /** + * @return {?WebInspector.CanvasTraceLogPlayerProxy} + */ + traceLogPlayer: function() + { + return this._traceLogPlayer; + }, + + /** + * @return {!PageAgent.FrameId|undefined} + */ + frameId: function() + { + return this._frameId; + }, + + /** + * @override + * @param {!WebInspector.ProfilesPanel} panel + * @return {!WebInspector.ProfileSidebarTreeElement} + */ + createSidebarTreeElement: function(panel) + { + return new WebInspector.ProfileSidebarTreeElement(panel, this, "profile-sidebar-tree-item"); + }, + + /** + * @override + * @return {!WebInspector.CanvasProfileView} + */ + createView: function() + { + return new WebInspector.CanvasProfileView(this); + }, + + /** + * @override + */ + dispose: function() + { + if (this._traceLogPlayer) + this._traceLogPlayer.dispose(); + clearTimeout(this._requestStatusTimer); + this._alive = false; + }, + + /** + * @param {!CanvasAgent.TraceLog=} traceLog + */ + _updateCapturingStatus: function(traceLog) + { + if (!this._traceLogId) + return; + + if (traceLog) { + this._alive = traceLog.alive; + this._traceLogSize = traceLog.totalAvailableCalls; + } + + var subtitle = this._alive ? WebInspector.UIString("Capturing\u2026 %d calls", this._traceLogSize) : WebInspector.UIString("Captured %d calls", this._traceLogSize); + this.updateStatus(subtitle, this._alive); + + if (this._alive) { + clearTimeout(this._requestStatusTimer); + this._requestStatusTimer = setTimeout(this._requestCapturingStatus.bind(this), WebInspector.CanvasProfileView.TraceLogPollingInterval); + } + }, + + _requestCapturingStatus: function() + { + /** + * @param {?CanvasAgent.TraceLog} traceLog + * @this {WebInspector.CanvasProfileHeader} + */ + function didReceiveTraceLog(traceLog) + { + if (!traceLog) + return; + this._alive = traceLog.alive; + this._traceLogSize = traceLog.totalAvailableCalls; + this._updateCapturingStatus(); + } + this._traceLogPlayer.getTraceLog(0, 0, didReceiveTraceLog.bind(this)); + }, + + __proto__: WebInspector.ProfileHeader.prototype +} + +WebInspector.CanvasProfileDataGridHelper = { + /** + * @param {!CanvasAgent.CallArgument} callArgument + * @return {!Element} + */ + createCallArgumentElement: function(callArgument) + { + if (callArgument.enumName) + return WebInspector.CanvasProfileDataGridHelper.createEnumValueElement(callArgument.enumName, +callArgument.description); + var element = document.createElement("span"); + element.className = "canvas-call-argument"; + var description = callArgument.description; + if (callArgument.type === "string") { + const maxStringLength = 150; + element.createTextChild("\""); + element.createChild("span", "canvas-formatted-string").textContent = description.trimMiddle(maxStringLength); + element.createTextChild("\""); + element.__suppressPopover = (description.length <= maxStringLength && !/[\r\n]/.test(description)); + if (!element.__suppressPopover) + element.__evalResult = WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(description); + } else { + var type = callArgument.subtype || callArgument.type; + if (type) { + element.classList.add("canvas-formatted-" + type); + if (["null", "undefined", "boolean", "number"].indexOf(type) >= 0) + element.__suppressPopover = true; + } + element.textContent = description; + if (callArgument.remoteObject) + element.__evalResult = WebInspector.runtimeModel.createRemoteObject(callArgument.remoteObject); + } + if (callArgument.resourceId) { + element.classList.add("canvas-formatted-resource"); + element.__resourceId = callArgument.resourceId; + } + return element; + }, + + /** + * @param {string} enumName + * @param {number} enumValue + * @return {!Element} + */ + createEnumValueElement: function(enumName, enumValue) + { + var element = document.createElement("span"); + element.className = "canvas-call-argument canvas-formatted-number"; + element.textContent = enumName; + element.__evalResult = WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(enumValue); + return element; + } +} + +/** + * @extends {WebInspector.Object} + * @constructor + * @param {!CanvasAgent.TraceLogId} traceLogId + */ +WebInspector.CanvasTraceLogPlayerProxy = function(traceLogId) +{ + this._traceLogId = traceLogId; + /** @type {!Object.<string, !CanvasAgent.ResourceState>} */ + this._currentResourceStates = {}; + /** @type {?CanvasAgent.ResourceId} */ + this._defaultResourceId = null; +} + +/** @enum {string} */ +WebInspector.CanvasTraceLogPlayerProxy.Events = { + CanvasTraceLogReceived: "CanvasTraceLogReceived", + CanvasReplayStateChanged: "CanvasReplayStateChanged", + CanvasResourceStateReceived: "CanvasResourceStateReceived", +} + +WebInspector.CanvasTraceLogPlayerProxy.prototype = { + /** + * @param {number|undefined} startOffset + * @param {number|undefined} maxLength + * @param {function(?CanvasAgent.TraceLog):void} userCallback + */ + getTraceLog: function(startOffset, maxLength, userCallback) + { + /** + * @param {?Protocol.Error} error + * @param {!CanvasAgent.TraceLog} traceLog + * @this {WebInspector.CanvasTraceLogPlayerProxy} + */ + function callback(error, traceLog) + { + if (error || !traceLog) { + userCallback(null); + return; + } + userCallback(traceLog); + this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasTraceLogReceived, traceLog); + } + CanvasAgent.getTraceLog(this._traceLogId, startOffset, maxLength, callback.bind(this)); + }, + + dispose: function() + { + this._currentResourceStates = {}; + CanvasAgent.dropTraceLog(this._traceLogId); + this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged); + }, + + /** + * @param {?CanvasAgent.ResourceId} resourceId + * @param {function(?CanvasAgent.ResourceState):void} userCallback + */ + getResourceState: function(resourceId, userCallback) + { + resourceId = resourceId || this._defaultResourceId; + if (!resourceId) { + userCallback(null); // Has not been replayed yet. + return; + } + var effectiveResourceId = /** @type {!CanvasAgent.ResourceId} */ (resourceId); + if (this._currentResourceStates[effectiveResourceId]) { + userCallback(this._currentResourceStates[effectiveResourceId]); + return; + } + + /** + * @param {?Protocol.Error} error + * @param {!CanvasAgent.ResourceState} resourceState + * @this {WebInspector.CanvasTraceLogPlayerProxy} + */ + function callback(error, resourceState) + { + if (error || !resourceState) { + userCallback(null); + return; + } + this._currentResourceStates[effectiveResourceId] = resourceState; + userCallback(resourceState); + this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState); + } + CanvasAgent.getResourceState(this._traceLogId, effectiveResourceId, callback.bind(this)); + }, + + /** + * @param {number} index + * @param {function(?CanvasAgent.ResourceState, number):void} userCallback + */ + replayTraceLog: function(index, userCallback) + { + /** + * @param {?Protocol.Error} error + * @param {!CanvasAgent.ResourceState} resourceState + * @param {number} replayTime + * @this {WebInspector.CanvasTraceLogPlayerProxy} + */ + function callback(error, resourceState, replayTime) + { + this._currentResourceStates = {}; + if (error) { + userCallback(null, replayTime); + } else { + this._defaultResourceId = resourceState.id; + this._currentResourceStates[resourceState.id] = resourceState; + userCallback(resourceState, replayTime); + } + this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged); + if (!error) + this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, resourceState); + } + CanvasAgent.replayTraceLog(this._traceLogId, index, callback.bind(this)); + }, + + clearResourceStates: function() + { + this._currentResourceStates = {}; + this.dispatchEventToListeners(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged); + }, + + __proto__: WebInspector.Object.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CanvasReplayStateView.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CanvasReplayStateView.js new file mode 100644 index 00000000000..dedb14c2b4b --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/CanvasReplayStateView.js @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2013 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. + */ + +/** + * @constructor + * @extends {WebInspector.VBox} + * @param {!WebInspector.CanvasTraceLogPlayerProxy} traceLogPlayer + */ +WebInspector.CanvasReplayStateView = function(traceLogPlayer) +{ + WebInspector.VBox.call(this); + this.registerRequiredCSS("canvasProfiler.css"); + this.element.classList.add("canvas-replay-state-view"); + this._traceLogPlayer = traceLogPlayer; + + var controlsContainer = this.element.createChild("div", "status-bar"); + this._prevButton = this._createControlButton(controlsContainer, "canvas-replay-state-prev", WebInspector.UIString("Previous resource."), this._onResourceNavigationClick.bind(this, false)); + this._nextButton = this._createControlButton(controlsContainer, "canvas-replay-state-next", WebInspector.UIString("Next resource."), this._onResourceNavigationClick.bind(this, true)); + this._createControlButton(controlsContainer, "canvas-replay-state-refresh", WebInspector.UIString("Refresh."), this._onStateRefreshClick.bind(this)); + + this._resourceSelector = new WebInspector.StatusBarComboBox(this._onReplayResourceChanged.bind(this)); + this._currentOption = this._resourceSelector.createOption(WebInspector.UIString("<auto>"), WebInspector.UIString("Show state of the last replayed resource."), ""); + controlsContainer.appendChild(this._resourceSelector.element); + + /** @type {!Object.<string, string>} */ + this._resourceIdToDescription = {}; + + /** @type {!Object.<string, !Object.<string, boolean>>} */ + this._gridNodesExpandedState = {}; + /** @type {!Object.<string, !{scrollTop: number, scrollLeft: number}>} */ + this._gridScrollPositions = {}; + + /** @type {?CanvasAgent.ResourceId} */ + this._currentResourceId = null; + /** @type {!Array.<!Element>} */ + this._prevOptionsStack = []; + /** @type {!Array.<!Element>} */ + this._nextOptionsStack = []; + + /** @type {!Array.<!WebInspector.DataGridNode>} */ + this._highlightedGridNodes = []; + + var columns = [ + {title: WebInspector.UIString("Name"), sortable: false, width: "50%", disclosure: true}, + {title: WebInspector.UIString("Value"), sortable: false, width: "50%"} + ]; + + this._stateGrid = new WebInspector.DataGrid(columns); + this._stateGrid.element.classList.add("fill"); + this._stateGrid.show(this.element); + + this._traceLogPlayer.addEventListener(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasReplayStateChanged, this._onReplayResourceChanged, this); + this._traceLogPlayer.addEventListener(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasTraceLogReceived, this._onCanvasTraceLogReceived, this); + this._traceLogPlayer.addEventListener(WebInspector.CanvasTraceLogPlayerProxy.Events.CanvasResourceStateReceived, this._onCanvasResourceStateReceived, this); + + this._updateButtonsEnabledState(); +} + +WebInspector.CanvasReplayStateView.prototype = { + /** + * @param {string} resourceId + */ + selectResource: function(resourceId) + { + if (resourceId === this._resourceSelector.selectedOption().value) + return; + var option = this._resourceSelector.selectElement().firstChild; + for (var index = 0; option; ++index, option = option.nextSibling) { + if (resourceId === option.value) { + this._resourceSelector.setSelectedIndex(index); + this._onReplayResourceChanged(); + break; + } + } + }, + + /** + * @param {!Element} parent + * @param {string} className + * @param {string} title + * @param {function(this:WebInspector.CanvasProfileView)} clickCallback + * @return {!WebInspector.StatusBarButton} + */ + _createControlButton: function(parent, className, title, clickCallback) + { + var button = new WebInspector.StatusBarButton(title, className + " canvas-replay-button"); + parent.appendChild(button.element); + + button.makeLongClickEnabled(); + button.addEventListener("click", clickCallback, this); + button.addEventListener("longClickDown", clickCallback, this); + button.addEventListener("longClickPress", clickCallback, this); + return button; + }, + + /** + * @param {boolean} forward + */ + _onResourceNavigationClick: function(forward) + { + var newOption = forward ? this._nextOptionsStack.pop() : this._prevOptionsStack.pop(); + if (!newOption) + return; + (forward ? this._prevOptionsStack : this._nextOptionsStack).push(this._currentOption); + this._isNavigationButton = true; + this.selectResource(newOption.value); + delete this._isNavigationButton; + this._updateButtonsEnabledState(); + }, + + _onStateRefreshClick: function() + { + this._traceLogPlayer.clearResourceStates(); + }, + + _updateButtonsEnabledState: function() + { + this._prevButton.setEnabled(this._prevOptionsStack.length > 0); + this._nextButton.setEnabled(this._nextOptionsStack.length > 0); + }, + + _updateCurrentOption: function() + { + const maxStackSize = 256; + var selectedOption = this._resourceSelector.selectedOption(); + if (this._currentOption === selectedOption) + return; + if (!this._isNavigationButton) { + this._prevOptionsStack.push(this._currentOption); + this._nextOptionsStack = []; + if (this._prevOptionsStack.length > maxStackSize) + this._prevOptionsStack.shift(); + this._updateButtonsEnabledState(); + } + this._currentOption = selectedOption; + }, + + /** + * @param {!CanvasAgent.TraceLog} traceLog + */ + _collectResourcesFromTraceLog: function(traceLog) + { + /** @type {!Array.<!CanvasAgent.CallArgument>} */ + var collectedResources = []; + var calls = traceLog.calls; + for (var i = 0, n = calls.length; i < n; ++i) { + var call = calls[i]; + var args = call.arguments || []; + for (var j = 0; j < args.length; ++j) + this._collectResourceFromCallArgument(args[j], collectedResources); + this._collectResourceFromCallArgument(call.result, collectedResources); + this._collectResourceFromCallArgument(call.value, collectedResources); + } + var contexts = traceLog.contexts; + for (var i = 0, n = contexts.length; i < n; ++i) + this._collectResourceFromCallArgument(contexts[i], collectedResources); + this._addCollectedResourcesToSelector(collectedResources); + }, + + /** + * @param {!CanvasAgent.ResourceState} resourceState + */ + _collectResourcesFromResourceState: function(resourceState) + { + /** @type {!Array.<!CanvasAgent.CallArgument>} */ + var collectedResources = []; + this._collectResourceFromResourceStateDescriptors(resourceState.descriptors, collectedResources); + this._addCollectedResourcesToSelector(collectedResources); + }, + + /** + * @param {!Array.<!CanvasAgent.ResourceStateDescriptor>|undefined} descriptors + * @param {!Array.<!CanvasAgent.CallArgument>} output + */ + _collectResourceFromResourceStateDescriptors: function(descriptors, output) + { + if (!descriptors) + return; + for (var i = 0, n = descriptors.length; i < n; ++i) { + var descriptor = descriptors[i]; + this._collectResourceFromCallArgument(descriptor.value, output); + this._collectResourceFromResourceStateDescriptors(descriptor.values, output); + } + }, + + /** + * @param {!CanvasAgent.CallArgument|undefined} argument + * @param {!Array.<!CanvasAgent.CallArgument>} output + */ + _collectResourceFromCallArgument: function(argument, output) + { + if (!argument) + return; + var resourceId = argument.resourceId; + if (!resourceId || this._resourceIdToDescription[resourceId]) + return; + this._resourceIdToDescription[resourceId] = argument.description; + output.push(argument); + }, + + /** + * @param {!Array.<!CanvasAgent.CallArgument>} collectedResources + */ + _addCollectedResourcesToSelector: function(collectedResources) + { + if (!collectedResources.length) + return; + /** + * @param {!CanvasAgent.CallArgument} arg1 + * @param {!CanvasAgent.CallArgument} arg2 + * @return {number} + */ + function comparator(arg1, arg2) + { + var a = arg1.description; + var b = arg2.description; + return String.naturalOrderComparator(a, b); + } + collectedResources.sort(comparator); + + var selectElement = this._resourceSelector.selectElement(); + var currentOption = selectElement.firstChild; + currentOption = currentOption.nextSibling; // Skip the "<auto>" option. + for (var i = 0, n = collectedResources.length; i < n; ++i) { + var argument = collectedResources[i]; + while (currentOption && String.naturalOrderComparator(currentOption.text, argument.description) < 0) + currentOption = currentOption.nextSibling; + var option = this._resourceSelector.createOption(argument.description, WebInspector.UIString("Show state of this resource."), argument.resourceId); + if (currentOption) + selectElement.insertBefore(option, currentOption); + } + }, + + _onReplayResourceChanged: function() + { + this._updateCurrentOption(); + var selectedResourceId = this._resourceSelector.selectedOption().value; + + /** + * @param {?CanvasAgent.ResourceState} resourceState + * @this {WebInspector.CanvasReplayStateView} + */ + function didReceiveResourceState(resourceState) + { + if (selectedResourceId !== this._resourceSelector.selectedOption().value) + return; + this._showResourceState(resourceState); + } + this._traceLogPlayer.getResourceState(selectedResourceId, didReceiveResourceState.bind(this)); + }, + + /** + * @param {!WebInspector.Event} event + */ + _onCanvasTraceLogReceived: function(event) + { + var traceLog = /** @type {!CanvasAgent.TraceLog} */ (event.data); + console.assert(traceLog); + this._collectResourcesFromTraceLog(traceLog); + }, + + /** + * @param {!WebInspector.Event} event + */ + _onCanvasResourceStateReceived: function(event) + { + var resourceState = /** @type {!CanvasAgent.ResourceState} */ (event.data); + console.assert(resourceState); + this._collectResourcesFromResourceState(resourceState); + }, + + /** + * @param {?CanvasAgent.ResourceState} resourceState + */ + _showResourceState: function(resourceState) + { + this._saveExpandedState(); + this._saveScrollState(); + + var rootNode = this._stateGrid.rootNode(); + if (!resourceState) { + this._currentResourceId = null; + this._updateDataGridHighlights([]); + rootNode.removeChildren(); + return; + } + + var nodesToHighlight = []; + var nameToOldGridNodes = {}; + + /** + * @param {!Object} map + * @param {!WebInspector.DataGridNode=} node + */ + function populateNameToNodesMap(map, node) + { + if (!node) + return; + for (var i = 0, child; child = node.children[i]; ++i) { + var item = { + node: child, + children: {} + }; + map[child.name] = item; + populateNameToNodesMap(item.children, child); + } + } + populateNameToNodesMap(nameToOldGridNodes, rootNode); + rootNode.removeChildren(); + + /** + * @param {!CanvasAgent.ResourceStateDescriptor} d1 + * @param {!CanvasAgent.ResourceStateDescriptor} d2 + * @return {number} + */ + function comparator(d1, d2) + { + var hasChildren1 = !!d1.values; + var hasChildren2 = !!d2.values; + if (hasChildren1 !== hasChildren2) + return hasChildren1 ? 1 : -1; + return String.naturalOrderComparator(d1.name, d2.name); + } + /** + * @param {!Array.<!CanvasAgent.ResourceStateDescriptor>|undefined} descriptors + * @param {!WebInspector.DataGridNode} parent + * @param {!Object=} nameToOldChildren + * @this {WebInspector.CanvasReplayStateView} + */ + function appendResourceStateDescriptors(descriptors, parent, nameToOldChildren) + { + descriptors = descriptors || []; + descriptors.sort(comparator); + var oldChildren = nameToOldChildren || {}; + for (var i = 0, n = descriptors.length; i < n; ++i) { + var descriptor = descriptors[i]; + var childNode = this._createDataGridNode(descriptor); + parent.appendChild(childNode); + var oldChildrenItem = oldChildren[childNode.name] || {}; + var oldChildNode = oldChildrenItem.node; + if (!oldChildNode || oldChildNode.element.textContent !== childNode.element.textContent) + nodesToHighlight.push(childNode); + appendResourceStateDescriptors.call(this, descriptor.values, childNode, oldChildrenItem.children); + } + } + appendResourceStateDescriptors.call(this, resourceState.descriptors, rootNode, nameToOldGridNodes); + + var shouldHighlightChanges = (this._resourceKindId(this._currentResourceId) === this._resourceKindId(resourceState.id)); + this._currentResourceId = resourceState.id; + this._restoreExpandedState(); + this._updateDataGridHighlights(shouldHighlightChanges ? nodesToHighlight : []); + this._restoreScrollState(); + }, + + /** + * @param {!Array.<!WebInspector.DataGridNode>} nodes + */ + _updateDataGridHighlights: function(nodes) + { + for (var i = 0, n = this._highlightedGridNodes.length; i < n; ++i) + this._highlightedGridNodes[i].element.classList.remove("canvas-grid-node-highlighted"); + + this._highlightedGridNodes = nodes; + + for (var i = 0, n = this._highlightedGridNodes.length; i < n; ++i) { + var node = this._highlightedGridNodes[i]; + WebInspector.runCSSAnimationOnce(node.element, "canvas-grid-node-highlighted"); + node.reveal(); + } + }, + + /** + * @param {?CanvasAgent.ResourceId} resourceId + * @return {string} + */ + _resourceKindId: function(resourceId) + { + var description = (resourceId && this._resourceIdToDescription[resourceId]) || ""; + return description.replace(/\d+/g, ""); + }, + + /** + * @param {function(!WebInspector.DataGridNode, string):void} callback + */ + _forEachGridNode: function(callback) + { + /** + * @param {!WebInspector.DataGridNode} node + * @param {string} key + */ + function processRecursively(node, key) + { + for (var i = 0, child; child = node.children[i]; ++i) { + var childKey = key + "#" + child.name; + callback(child, childKey); + processRecursively(child, childKey); + } + } + processRecursively(this._stateGrid.rootNode(), ""); + }, + + _saveExpandedState: function() + { + if (!this._currentResourceId) + return; + var expandedState = {}; + var key = this._resourceKindId(this._currentResourceId); + this._gridNodesExpandedState[key] = expandedState; + /** + * @param {!WebInspector.DataGridNode} node + * @param {string} key + */ + function callback(node, key) + { + if (node.expanded) + expandedState[key] = true; + } + this._forEachGridNode(callback); + }, + + _restoreExpandedState: function() + { + if (!this._currentResourceId) + return; + var key = this._resourceKindId(this._currentResourceId); + var expandedState = this._gridNodesExpandedState[key]; + if (!expandedState) + return; + /** + * @param {!WebInspector.DataGridNode} node + * @param {string} key + */ + function callback(node, key) + { + if (expandedState[key]) + node.expand(); + } + this._forEachGridNode(callback); + }, + + _saveScrollState: function() + { + if (!this._currentResourceId) + return; + var key = this._resourceKindId(this._currentResourceId); + this._gridScrollPositions[key] = { + scrollTop: this._stateGrid.scrollContainer.scrollTop, + scrollLeft: this._stateGrid.scrollContainer.scrollLeft + }; + }, + + _restoreScrollState: function() + { + if (!this._currentResourceId) + return; + var key = this._resourceKindId(this._currentResourceId); + var scrollState = this._gridScrollPositions[key]; + if (!scrollState) + return; + this._stateGrid.scrollContainer.scrollTop = scrollState.scrollTop; + this._stateGrid.scrollContainer.scrollLeft = scrollState.scrollLeft; + }, + + /** + * @param {!CanvasAgent.ResourceStateDescriptor} descriptor + * @return {!WebInspector.DataGridNode} + */ + _createDataGridNode: function(descriptor) + { + var name = descriptor.name; + var callArgument = descriptor.value; + + /** @type {!Element|string} */ + var valueElement = callArgument ? WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(callArgument) : ""; + + /** @type {!Element|string} */ + var nameElement = name; + if (typeof descriptor.enumValueForName !== "undefined") + nameElement = WebInspector.CanvasProfileDataGridHelper.createEnumValueElement(name, +descriptor.enumValueForName); + + if (descriptor.isArray && descriptor.values) { + if (typeof nameElement === "string") + nameElement += "[" + descriptor.values.length + "]"; + else { + var element = document.createElement("span"); + element.appendChild(nameElement); + element.createTextChild("[" + descriptor.values.length + "]"); + nameElement = element; + } + } + + var data = {}; + data[0] = nameElement; + data[1] = valueElement; + var node = new WebInspector.DataGridNode(data); + node.selectable = false; + node.name = name; + return node; + }, + + __proto__: WebInspector.VBox.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotCommon.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotCommon.js new file mode 100644 index 00000000000..1effb8aa78b --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotCommon.js @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2014 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. + */ + +WebInspector.HeapSnapshotProgressEvent = { + Update: "ProgressUpdate" +}; + +WebInspector.HeapSnapshotCommon = { +} + +WebInspector.HeapSnapshotCommon.baseSystemDistance = 100000000; + +/** + * @param {!Array.<!WebInspector.HeapSnapshotCommon.SerializedAllocationNode>} nodesWithSingleCaller + * @param {!Array.<!WebInspector.HeapSnapshotCommon.SerializedAllocationNode>} branchingCallers + * @constructor + */ +WebInspector.HeapSnapshotCommon.AllocationNodeCallers = function(nodesWithSingleCaller, branchingCallers) +{ + /** @type {!Array.<!WebInspector.HeapSnapshotCommon.SerializedAllocationNode>} */ + this.nodesWithSingleCaller = nodesWithSingleCaller; + /** @type {!Array.<!WebInspector.HeapSnapshotCommon.SerializedAllocationNode>} */ + this.branchingCallers = branchingCallers; +} + +/** + * @param {number} nodeId + * @param {string} functionName + * @param {string} scriptName + * @param {number} scriptId + * @param {number} line + * @param {number} column + * @param {number} count + * @param {number} size + * @param {number} liveCount + * @param {number} liveSize + * @param {boolean} hasChildren + * @constructor + */ +WebInspector.HeapSnapshotCommon.SerializedAllocationNode = function(nodeId, functionName, scriptName, scriptId, line, column, count, size, liveCount, liveSize, hasChildren) +{ + /** @type {number} */ + this.id = nodeId; + /** @type {string} */ + this.name = functionName; + /** @type {string} */ + this.scriptName = scriptName; + /** @type {number} */ + this.scriptId = scriptId; + /** @type {number} */ + this.line = line; + /** @type {number} */ + this.column = column; + /** @type {number} */ + this.count = count; + /** @type {number} */ + this.size = size; + /** @type {number} */ + this.liveCount = liveCount; + /** @type {number} */ + this.liveSize = liveSize; + /** @type {boolean} */ + this.hasChildren = hasChildren; +} + +/** + * @param {string} functionName + * @param {string} scriptName + * @param {number} scriptId + * @param {number} line + * @param {number} column + * @constructor + */ +WebInspector.HeapSnapshotCommon.AllocationStackFrame = function(functionName, scriptName, scriptId, line, column) +{ + /** @type {string} */ + this.functionName = functionName; + /** @type {string} */ + this.scriptName = scriptName; + /** @type {number} */ + this.scriptId = scriptId; + /** @type {number} */ + this.line = line; + /** @type {number} */ + this.column = column; +} + +/** + * @constructor + * @param {number} id + * @param {string} name + * @param {number} distance + * @param {number} nodeIndex + * @param {number} retainedSize + * @param {number} selfSize + * @param {string} type + */ +WebInspector.HeapSnapshotCommon.Node = function(id, name, distance, nodeIndex, retainedSize, selfSize, type) +{ + this.id = id; + this.name = name; + this.distance = distance; + this.nodeIndex = nodeIndex; + this.retainedSize = retainedSize; + this.selfSize = selfSize; + this.type = type; + + this.canBeQueried = false; + this.detachedDOMTreeNode = false; +} + +/** + * @constructor + * @param {string} name + * @param {!WebInspector.HeapSnapshotCommon.Node} node + * @param {string} type + * @param {number} edgeIndex + */ +WebInspector.HeapSnapshotCommon.Edge = function(name, node, type, edgeIndex) +{ + this.name = name; + this.node = node; + this.type = type; + this.edgeIndex = edgeIndex; +}; + +/** + * @constructor + */ +WebInspector.HeapSnapshotCommon.Aggregate = function() +{ + /** @type {number} */ + this.count; + /** @type {number} */ + this.distance; + /** @type {number} */ + this.self; + /** @type {number} */ + this.maxRet; + /** @type {number} */ + this.type; + /** @type {string} */ + this.name; + /** @type {!Array.<number>} */ + this.idxs; +} + +/** + * @constructor + */ +WebInspector.HeapSnapshotCommon.AggregateForDiff = function() { + /** @type {!Array.<number>} */ + this.indexes = []; + /** @type {!Array.<string>} */ + this.ids = []; + /** @type {!Array.<number>} */ + this.selfSizes = []; +} + +/** + * @constructor + */ +WebInspector.HeapSnapshotCommon.Diff = function() +{ + /** @type {number} */ + this.addedCount = 0; + /** @type {number} */ + this.removedCount = 0; + /** @type {number} */ + this.addedSize = 0; + /** @type {number} */ + this.removedSize = 0; + /** @type {!Array.<number>} */ + this.deletedIndexes = []; + /** @type {!Array.<number>} */ + this.addedIndexes = []; +} + +/** + * @constructor + */ +WebInspector.HeapSnapshotCommon.DiffForClass = function() +{ + /** @type {number} */ + this.addedCount; + /** @type {number} */ + this.removedCount; + /** @type {number} */ + this.addedSize; + /** @type {number} */ + this.removedSize; + /** @type {!Array.<number>} */ + this.deletedIndexes; + /** @type {!Array.<number>} */ + this.addedIndexes; + + /** @type {number} */ + this.countDelta; + /** @type {number} */ + this.sizeDelta; +} + +/** + * @constructor + */ +WebInspector.HeapSnapshotCommon.ComparatorConfig = function() +{ + /** @type {string} */ + this.fieldName1; + /** @type {boolean} */ + this.ascending1; + /** @type {string} */ + this.fieldName2; + /** @type {boolean} */ + this.ascending2; +} + +/** + * @constructor + */ +WebInspector.HeapSnapshotCommon.WorkerCommand = function() +{ + /** @type {number} */ + this.callId; + /** @type {string} */ + this.disposition; + /** @type {number} */ + this.objectId; + /** @type {number} */ + this.newObjectId; + /** @type {string} */ + this.methodName; + /** @type {!Array.<*>} */ + this.methodArguments; + /** @type {string} */ + this.source; +} + +/** + * @constructor + * @param {number} startPosition + * @param {number} endPosition + * @param {number} totalLength + * @param {!Array.<*>} items + */ +WebInspector.HeapSnapshotCommon.ItemsRange = function(startPosition, endPosition, totalLength, items) +{ + /** @type {number} */ + this.startPosition = startPosition; + /** @type {number} */ + this.endPosition = endPosition; + /** @type {number} */ + this.totalLength = totalLength; + /** @type {!Array.<*>} */ + this.items = items; +} + +/** + * @param {number} nodeCount + * @param {number} rootNodeIndex + * @param {number} totalSize + * @param {number} maxJSObjectId + * @constructor + */ +WebInspector.HeapSnapshotCommon.StaticData = function(nodeCount, rootNodeIndex, totalSize, maxJSObjectId) +{ + /** @type {number} */ + this.nodeCount = nodeCount; + /** @type {number} */ + this.rootNodeIndex = rootNodeIndex; + /** @type {number} */ + this.totalSize = totalSize; + /** @type {number} */ + this.maxJSObjectId = maxJSObjectId; +} + +/** + * @constructor + */ +WebInspector.HeapSnapshotCommon.Statistics = function() +{ + /** @type {number} */ + this.total; + /** @type {number} */ + this.v8heap; + /** @type {number} */ + this.native; + /** @type {number} */ + this.code; + /** @type {number} */ + this.jsArrays; + /** @type {number} */ + this.strings; +} + + +/** + * @param {number=} minNodeId + * @param {number=} maxNodeId + * @constructor + */ +WebInspector.HeapSnapshotCommon.NodeFilter = function(minNodeId, maxNodeId) +{ + /** @type {number|undefined} */ + this.minNodeId = minNodeId; + /** @type {number|undefined} */ + this.maxNodeId = maxNodeId; + /** @type {number|undefined} */ + this.allocationNodeId; +} + +WebInspector.HeapSnapshotCommon.NodeFilter.prototype = +{ + /** + * @param {!WebInspector.HeapSnapshotCommon.NodeFilter} o + * @return {boolean} + */ + equals: function(o) + { + return this.minNodeId === o.minNodeId && this.maxNodeId === o.maxNodeId && this.allocationNodeId === o.allocationNodeId; + } +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotDataGrids.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotDataGrids.js new file mode 100644 index 00000000000..6b7ee735104 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotDataGrids.js @@ -0,0 +1,1155 @@ +/* + * Copyright (C) 2012 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. + */ + +/** + * @constructor + * @extends {WebInspector.DataGrid} + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + * @param {!Array.<!WebInspector.DataGrid.ColumnDescriptor>} columns + */ +WebInspector.HeapSnapshotSortableDataGrid = function(dataDisplayDelegate, columns) +{ + WebInspector.DataGrid.call(this, columns); + this._dataDisplayDelegate = dataDisplayDelegate; + + /** + * @type {number} + */ + this._recursiveSortingDepth = 0; + /** + * @type {?WebInspector.HeapSnapshotGridNode} + */ + this._highlightedNode = null; + /** + * @type {boolean} + */ + this._populatedAndSorted = false; + /** + * @type {?WebInspector.StatusBarInput} + */ + this._nameFilter = null; + this.addEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.SortingComplete, this._sortingComplete, this); + this.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this.sortingChanged, this); +} + +WebInspector.HeapSnapshotSortableDataGrid.Events = { + ContentShown: "ContentShown", + SortingComplete: "SortingComplete" +} + +WebInspector.HeapSnapshotSortableDataGrid.prototype = { + /** + * @param {!WebInspector.StatusBarInput} nameFilter + */ + setNameFilter: function(nameFilter) + { + this._nameFilter = nameFilter; + }, + + /** + * @return {number} + */ + defaultPopulateCount: function() + { + return 100; + }, + + _disposeAllNodes: function() + { + var children = this.topLevelNodes(); + for (var i = 0, l = children.length; i < l; ++i) + children[i].dispose(); + }, + + /** + * @override + */ + wasShown: function() + { + if (this._nameFilter) { + this._nameFilter.addEventListener(WebInspector.StatusBarInput.Event.TextChanged, this._onNameFilterChanged, this); + this.updateVisibleNodes(true); + } + if (this._populatedAndSorted) + this.dispatchEventToListeners(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, this); + }, + + _sortingComplete: function() + { + this.removeEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.SortingComplete, this._sortingComplete, this); + this._populatedAndSorted = true; + this.dispatchEventToListeners(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, this); + }, + + /** + * @override + */ + willHide: function() + { + if (this._nameFilter) + this._nameFilter.removeEventListener(WebInspector.StatusBarInput.Event.TextChanged, this._onNameFilterChanged, this); + this._clearCurrentHighlight(); + }, + + /** + * @param {!WebInspector.ContextMenu} contextMenu + * @param {?Event} event + */ + populateContextMenu: function(contextMenu, event) + { + var td = event.target.enclosingNodeOrSelfWithNodeName("td"); + if (!td) + return; + var node = td.heapSnapshotNode; + + /** + * @this {WebInspector.HeapSnapshotSortableDataGrid} + */ + function revealInDominatorsView() + { + this._dataDisplayDelegate.showObject(node.snapshotNodeId, "Dominators"); + } + + /** + * @this {WebInspector.HeapSnapshotSortableDataGrid} + */ + function revealInSummaryView() + { + this._dataDisplayDelegate.showObject(node.snapshotNodeId, "Summary"); + } + + if (node instanceof WebInspector.HeapSnapshotRetainingObjectNode) { + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInSummaryView.bind(this)); + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInDominatorsView.bind(this)); + } else if (node instanceof WebInspector.HeapSnapshotInstanceNode || node instanceof WebInspector.HeapSnapshotObjectNode) { + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInDominatorsView.bind(this)); + } else if (node instanceof WebInspector.HeapSnapshotDominatorObjectNode) { + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInSummaryView.bind(this)); + } + }, + + resetSortingCache: function() + { + delete this._lastSortColumnIdentifier; + delete this._lastSortAscending; + }, + + /** + * @return {!Array.<!WebInspector.HeapSnapshotGridNode>} + */ + topLevelNodes: function() + { + return this.rootNode().children; + }, + + /** + * @param {!HeapProfilerAgent.HeapSnapshotObjectId} heapSnapshotObjectId + * @param {function(boolean)} callback + */ + highlightObjectByHeapSnapshotId: function(heapSnapshotObjectId, callback) + { + }, + + /** + * @param {!WebInspector.HeapSnapshotGridNode} node + */ + highlightNode: function(node) + { + var prevNode = this._highlightedNode; + this._clearCurrentHighlight(); + this._highlightedNode = node; + WebInspector.runCSSAnimationOnce(this._highlightedNode.element, "highlighted-row"); + }, + + nodeWasDetached: function(node) + { + if (this._highlightedNode === node) + this._clearCurrentHighlight(); + }, + + _clearCurrentHighlight: function() + { + if (!this._highlightedNode) + return + this._highlightedNode.element.classList.remove("highlighted-row"); + this._highlightedNode = null; + }, + + resetNameFilter: function() + { + this._nameFilter.setValue(""); + this._onNameFilterChanged(); + }, + + _onNameFilterChanged: function() + { + this.updateVisibleNodes(true); + }, + + sortingChanged: function() + { + var sortAscending = this.isSortOrderAscending(); + var sortColumnIdentifier = this.sortColumnIdentifier(); + if (this._lastSortColumnIdentifier === sortColumnIdentifier && this._lastSortAscending === sortAscending) + return; + this._lastSortColumnIdentifier = sortColumnIdentifier; + this._lastSortAscending = sortAscending; + var sortFields = this._sortFields(sortColumnIdentifier, sortAscending); + + function SortByTwoFields(nodeA, nodeB) + { + var field1 = nodeA[sortFields[0]]; + var field2 = nodeB[sortFields[0]]; + var result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0); + if (!sortFields[1]) + result = -result; + if (result !== 0) + return result; + field1 = nodeA[sortFields[2]]; + field2 = nodeB[sortFields[2]]; + result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0); + if (!sortFields[3]) + result = -result; + return result; + } + this._performSorting(SortByTwoFields); + }, + + _performSorting: function(sortFunction) + { + this.recursiveSortingEnter(); + var children = this.allChildren(this.rootNode()); + this.rootNode().removeChildren(); + children.sort(sortFunction); + for (var i = 0, l = children.length; i < l; ++i) { + var child = children[i]; + this.appendChildAfterSorting(child); + if (child.expanded) + child.sort(); + } + this.recursiveSortingLeave(); + }, + + appendChildAfterSorting: function(child) + { + var revealed = child.revealed; + this.rootNode().appendChild(child); + child.revealed = revealed; + }, + + recursiveSortingEnter: function() + { + ++this._recursiveSortingDepth; + }, + + recursiveSortingLeave: function() + { + if (!this._recursiveSortingDepth) + return; + if (--this._recursiveSortingDepth) + return; + this.updateVisibleNodes(true); + this.dispatchEventToListeners(WebInspector.HeapSnapshotSortableDataGrid.Events.SortingComplete); + }, + + /** + * @param {boolean} force + */ + updateVisibleNodes: function(force) + { + }, + + /** + * @param {!WebInspector.DataGridNode} parent + * @return {!Array.<!WebInspector.HeapSnapshotGridNode>} + */ + allChildren: function(parent) + { + return parent.children; + }, + + /** + * @param {!WebInspector.DataGridNode} parent + * @param {!WebInspector.DataGridNode} node + * @param {number} index + */ + insertChild: function(parent, node, index) + { + parent.insertChild(node, index); + }, + + /** + * @param {!WebInspector.HeapSnapshotGridNode} parent + * @param {number} index + */ + removeChildByIndex: function(parent, index) + { + parent.removeChild(parent.children[index]); + }, + + /** + * @param {!WebInspector.HeapSnapshotGridNode} parent + */ + removeAllChildren: function(parent) + { + parent.removeChildren(); + }, + + __proto__: WebInspector.DataGrid.prototype +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotSortableDataGrid} + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + * @param {!Array.<!WebInspector.DataGrid.ColumnDescriptor>} columns + */ +WebInspector.HeapSnapshotViewportDataGrid = function(dataDisplayDelegate, columns) +{ + WebInspector.HeapSnapshotSortableDataGrid.call(this, dataDisplayDelegate, columns); + this.scrollContainer.addEventListener("scroll", this._onScroll.bind(this), true); + /** + * @type {?WebInspector.HeapSnapshotGridNode} + */ + this._nodeToHighlightAfterScroll = null; + this._topPadding = new WebInspector.HeapSnapshotPaddingNode(); + this._topPaddingHeight = 0; + this.dataTableBody.insertBefore(this._topPadding.element, this.dataTableBody.firstChild); + this._bottomPadding = new WebInspector.HeapSnapshotPaddingNode(); + this._bottomPaddingHeight = 0; + this.dataTableBody.insertBefore(this._bottomPadding.element, this.dataTableBody.lastChild); +} + +WebInspector.HeapSnapshotViewportDataGrid.prototype = { + /** + * @return {!Array.<!WebInspector.HeapSnapshotGridNode>} + */ + topLevelNodes: function() + { + return this.allChildren(this.rootNode()); + }, + + appendChildAfterSorting: function(child) + { + // Do nothing here, it will be added in updateVisibleNodes. + }, + + /** + * @override + * @param {boolean} force + * @param {!Array.<!WebInspector.HeapSnapshotGridNode>=} pathToReveal + */ + updateVisibleNodes: function(force, pathToReveal) + { + // Guard zone is used to ensure there are always some extra items + // above and below the viewport to support keyboard navigation. + var guardZoneHeight = 40; + var scrollHeight = this.scrollContainer.scrollHeight; + var scrollTop = this.scrollContainer.scrollTop; + var scrollBottom = scrollHeight - scrollTop - this.scrollContainer.offsetHeight; + scrollTop = Math.max(0, scrollTop - guardZoneHeight); + scrollBottom = Math.max(0, scrollBottom - guardZoneHeight); + var viewPortHeight = scrollHeight - scrollTop - scrollBottom; + if (!pathToReveal) { + // Do nothing if populated nodes still fit the viewport. + if (!force && scrollTop >= this._topPaddingHeight && scrollBottom >= this._bottomPaddingHeight) + return; + var hysteresisHeight = 500; + scrollTop -= hysteresisHeight; + viewPortHeight += 2 * hysteresisHeight; + } + var selectedNode = this.selectedNode; + this.rootNode().removeChildren(); + + this._topPaddingHeight = 0; + this._bottomPaddingHeight = 0; + + this._addVisibleNodes(this.rootNode(), scrollTop, scrollTop + viewPortHeight, pathToReveal || null); + + this._topPadding.setHeight(this._topPaddingHeight); + this._bottomPadding.setHeight(this._bottomPaddingHeight); + + if (selectedNode) { + // Keep selection even if the node is not in the current viewport. + if (selectedNode.parent) + selectedNode.select(true); + else + this.selectedNode = selectedNode; + } + }, + + /** + * @param {!WebInspector.DataGridNode} parentNode + * @param {number} topBound + * @param {number} bottomBound + * @param {?Array.<!WebInspector.HeapSnapshotGridNode>} pathToReveal + * @return {number} + */ + _addVisibleNodes: function(parentNode, topBound, bottomBound, pathToReveal) + { + if (!parentNode.expanded) + return 0; + + var nodeToReveal = pathToReveal ? pathToReveal[0] : null; + var restPathToReveal = pathToReveal && pathToReveal.length > 1 ? pathToReveal.slice(1) : null; + var children = this.allChildren(parentNode); + var topPadding = 0; + var nameFilterValue = this._nameFilter ? this._nameFilter.value().toLowerCase() : ""; + // Iterate over invisible nodes beyond the upper bound of viewport. + // Do not insert them into the grid, but count their total height. + for (var i = 0; i < children.length; ++i) { + var child = children[i]; + if (nameFilterValue && child.filteredOut && child.filteredOut(nameFilterValue)) + continue; + var newTop = topPadding + this._nodeHeight(child); + if (nodeToReveal === child || (!nodeToReveal && newTop > topBound)) + break; + topPadding = newTop; + } + + // Put visible nodes into the data grid. + var position = topPadding; + for (; i < children.length && (nodeToReveal || position < bottomBound); ++i) { + var child = children[i]; + if (nameFilterValue && child.filteredOut && child.filteredOut(nameFilterValue)) + continue; + var hasChildren = child.hasChildren; + child.removeChildren(); + child.hasChildren = hasChildren; + child.revealed = true; + parentNode.appendChild(child); + position += child.nodeSelfHeight(); + position += this._addVisibleNodes(child, topBound - position, bottomBound - position, restPathToReveal); + if (nodeToReveal === child) + break; + } + + // Count the invisible nodes beyond the bottom bound of the viewport. + var bottomPadding = 0; + for (; i < children.length; ++i) { + var child = children[i]; + if (nameFilterValue && child.filteredOut && child.filteredOut(nameFilterValue)) + continue; + bottomPadding += this._nodeHeight(child); + } + + this._topPaddingHeight += topPadding; + this._bottomPaddingHeight += bottomPadding; + return position + bottomPadding; + }, + + /** + * @param {!WebInspector.HeapSnapshotGridNode} node + * @return {number} + */ + _nodeHeight: function(node) + { + if (!node.revealed) + return 0; + var result = node.nodeSelfHeight(); + if (!node.expanded) + return result; + var children = this.allChildren(node); + for (var i = 0; i < children.length; i++) + result += this._nodeHeight(children[i]); + return result; + }, + + /** + * @override + * @return {?Element} + */ + defaultAttachLocation: function() + { + return this._bottomPadding.element; + }, + + /** + * @param {!Array.<!WebInspector.HeapSnapshotGridNode>} pathToReveal + */ + revealTreeNode: function(pathToReveal) + { + this.updateVisibleNodes(true, pathToReveal); + }, + + /** + * @param {!WebInspector.DataGridNode} parent + * @return {!Array.<!WebInspector.HeapSnapshotGridNode>} + */ + allChildren: function(parent) + { + return parent._allChildren || (parent._allChildren = []); + }, + + /** + * @param {!WebInspector.DataGridNode} parent + * @param {!WebInspector.DataGridNode} node + */ + appendNode: function(parent, node) + { + this.allChildren(parent).push(node); + }, + + /** + * @param {!WebInspector.DataGridNode} parent + * @param {!WebInspector.DataGridNode} node + * @param {number} index + */ + insertChild: function(parent, node, index) + { + this.allChildren(parent).splice(index, 0, node); + }, + + removeChildByIndex: function(parent, index) + { + this.allChildren(parent).splice(index, 1); + }, + + removeAllChildren: function(parent) + { + parent._allChildren = []; + }, + + removeTopLevelNodes: function() + { + this._disposeAllNodes(); + this.rootNode().removeChildren(); + this.rootNode()._allChildren = []; + }, + + /** + * @override + * @param {!WebInspector.HeapSnapshotGridNode} node + */ + highlightNode: function(node) + { + if (this._isScrolledIntoView(node.element)) { + this.updateVisibleNodes(true); + WebInspector.HeapSnapshotSortableDataGrid.prototype.highlightNode.call(this, node); + } else { + node.element.scrollIntoViewIfNeeded(true); + this._nodeToHighlightAfterScroll = node; + } + }, + + /** + * @param {!Element} element + * @return {boolean} + */ + _isScrolledIntoView: function(element) + { + var viewportTop = this.scrollContainer.scrollTop; + var viewportBottom = viewportTop + this.scrollContainer.clientHeight; + var elemTop = element.offsetTop + var elemBottom = elemTop + element.offsetHeight; + return elemBottom <= viewportBottom && elemTop >= viewportTop; + }, + + onResize: function() + { + WebInspector.HeapSnapshotSortableDataGrid.prototype.onResize.call(this); + this.updateVisibleNodes(false); + }, + + _onScroll: function(event) + { + this.updateVisibleNodes(false); + + if (this._nodeToHighlightAfterScroll) { + WebInspector.HeapSnapshotSortableDataGrid.prototype.highlightNode.call(this, this._nodeToHighlightAfterScroll); + this._nodeToHighlightAfterScroll = null; + } + }, + + __proto__: WebInspector.HeapSnapshotSortableDataGrid.prototype +} + +/** + * @constructor + */ +WebInspector.HeapSnapshotPaddingNode = function() +{ + this.element = document.createElement("tr"); + this.element.classList.add("revealed"); + this.setHeight(0); +} + +WebInspector.HeapSnapshotPaddingNode.prototype = { + setHeight: function(height) + { + this.element.style.height = height + "px"; + }, + removeFromTable: function() + { + var parent = this.element.parentNode; + if (parent) + parent.removeChild(this.element); + } +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotSortableDataGrid} + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + * @param {!Array.<!WebInspector.DataGrid.ColumnDescriptor>=} columns + */ +WebInspector.HeapSnapshotContainmentDataGrid = function(dataDisplayDelegate, columns) +{ + columns = columns || [ + {id: "object", title: WebInspector.UIString("Object"), disclosure: true, sortable: true}, + {id: "distance", title: WebInspector.UIString("Distance"), width: "80px", sortable: true}, + {id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true}, + {id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sortable: true, sort: WebInspector.DataGrid.Order.Descending} + ]; + WebInspector.HeapSnapshotSortableDataGrid.call(this, dataDisplayDelegate, columns); +} + +WebInspector.HeapSnapshotContainmentDataGrid.prototype = { + /** + * @param {!WebInspector.HeapSnapshotProxy} snapshot + * @param {number} nodeIndex + */ + setDataSource: function(snapshot, nodeIndex) + { + this.snapshot = snapshot; + var node = { nodeIndex: nodeIndex || snapshot.rootNodeIndex }; + var fakeEdge = { node: node }; + this.setRootNode(this._createRootNode(snapshot, fakeEdge)); + this.rootNode().sort(); + }, + + _createRootNode: function(snapshot, fakeEdge) + { + return new WebInspector.HeapSnapshotObjectNode(this, snapshot, fakeEdge, null); + }, + + sortingChanged: function() + { + var rootNode = this.rootNode(); + if (rootNode.hasChildren) + rootNode.sort(); + }, + + __proto__: WebInspector.HeapSnapshotSortableDataGrid.prototype +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotContainmentDataGrid} + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + */ +WebInspector.HeapSnapshotRetainmentDataGrid = function(dataDisplayDelegate) +{ + var columns = [ + {id: "object", title: WebInspector.UIString("Object"), disclosure: true, sortable: true}, + {id: "distance", title: WebInspector.UIString("Distance"), width: "80px", sortable: true, sort: WebInspector.DataGrid.Order.Ascending}, + {id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true}, + {id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sortable: true} + ]; + WebInspector.HeapSnapshotContainmentDataGrid.call(this, dataDisplayDelegate, columns); +} + +WebInspector.HeapSnapshotRetainmentDataGrid.Events = { + ExpandRetainersComplete: "ExpandRetainersComplete" +} + +WebInspector.HeapSnapshotRetainmentDataGrid.prototype = { + _createRootNode: function(snapshot, fakeEdge) + { + return new WebInspector.HeapSnapshotRetainingObjectNode(this, snapshot, fakeEdge, null); + }, + + _sortFields: function(sortColumn, sortAscending) + { + return { + object: ["_name", sortAscending, "_count", false], + count: ["_count", sortAscending, "_name", true], + shallowSize: ["_shallowSize", sortAscending, "_name", true], + retainedSize: ["_retainedSize", sortAscending, "_name", true], + distance: ["_distance", sortAscending, "_name", true] + }[sortColumn]; + }, + + reset: function() + { + this.rootNode().removeChildren(); + this.resetSortingCache(); + }, + + /** + * @param {!WebInspector.HeapSnapshotProxy} snapshot + * @param {number} nodeIndex + */ + setDataSource: function(snapshot, nodeIndex) + { + WebInspector.HeapSnapshotContainmentDataGrid.prototype.setDataSource.call(this, snapshot, nodeIndex); + this.rootNode().expand(); + }, + + __proto__: WebInspector.HeapSnapshotContainmentDataGrid.prototype +} + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotViewportDataGrid} + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + */ +WebInspector.HeapSnapshotConstructorsDataGrid = function(dataDisplayDelegate) +{ + var columns = [ + {id: "object", title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true}, + {id: "distance", title: WebInspector.UIString("Distance"), width: "90px", sortable: true}, + {id: "count", title: WebInspector.UIString("Objects Count"), width: "90px", sortable: true}, + {id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true}, + {id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true} + ]; + WebInspector.HeapSnapshotViewportDataGrid.call(this, dataDisplayDelegate, columns); + this._profileIndex = -1; + + this._objectIdToSelect = null; +} + +WebInspector.HeapSnapshotConstructorsDataGrid.prototype = { + _sortFields: function(sortColumn, sortAscending) + { + return { + object: ["_name", sortAscending, "_count", false], + distance: ["_distance", sortAscending, "_retainedSize", true], + count: ["_count", sortAscending, "_name", true], + shallowSize: ["_shallowSize", sortAscending, "_name", true], + retainedSize: ["_retainedSize", sortAscending, "_name", true] + }[sortColumn]; + }, + + /** + * @override + * @param {!HeapProfilerAgent.HeapSnapshotObjectId} id + * @param {function(boolean)} callback + */ + highlightObjectByHeapSnapshotId: function(id, callback) + { + if (!this.snapshot) { + this._objectIdToSelect = id; + return; + } + + /** + * @param {?string} className + * @this {WebInspector.HeapSnapshotConstructorsDataGrid} + */ + function didGetClassName(className) + { + if (!className) { + callback(false); + return; + } + var constructorNodes = this.topLevelNodes(); + for (var i = 0; i < constructorNodes.length; i++) { + var parent = constructorNodes[i]; + if (parent._name === className) { + parent.revealNodeBySnapshotObjectId(parseInt(id, 10), callback); + return; + } + } + } + this.snapshot.nodeClassName(parseInt(id, 10), didGetClassName.bind(this)); + }, + + clear: function() + { + this._nextRequestedFilter = null; + this._lastFilter = null; + this.removeTopLevelNodes(); + }, + + setDataSource: function(snapshot) + { + this.snapshot = snapshot; + if (this._profileIndex === -1) + this._populateChildren(); + + if (this._objectIdToSelect) { + this.highlightObjectByHeapSnapshotId(this._objectIdToSelect, function(found) {}); + this._objectIdToSelect = null; + } + }, + + /** + * @param {number} minNodeId + * @param {number} maxNodeId + */ + setSelectionRange: function(minNodeId, maxNodeId) + { + this._populateChildren(new WebInspector.HeapSnapshotCommon.NodeFilter(minNodeId, maxNodeId)); + }, + + /** + * @param {number} allocationNodeId + */ + setAllocationNodeId: function(allocationNodeId) + { + var filter = new WebInspector.HeapSnapshotCommon.NodeFilter(); + filter.allocationNodeId = allocationNodeId; + this._populateChildren(filter); + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.NodeFilter} nodeFilter + * @param {!Object.<string, !WebInspector.HeapSnapshotCommon.Aggregate>} aggregates + */ + _aggregatesReceived: function(nodeFilter, aggregates) + { + this._filterInProgress = null; + if (this._nextRequestedFilter) { + this.snapshot.aggregatesWithFilter(this._nextRequestedFilter, this._aggregatesReceived.bind(this, this._nextRequestedFilter)); + this._filterInProgress = this._nextRequestedFilter; + this._nextRequestedFilter = null; + } + this.removeTopLevelNodes(); + this.resetSortingCache(); + for (var constructor in aggregates) + this.appendNode(this.rootNode(), new WebInspector.HeapSnapshotConstructorNode(this, constructor, aggregates[constructor], nodeFilter)); + this.sortingChanged(); + this._lastFilter = nodeFilter; + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.NodeFilter=} nodeFilter + */ + _populateChildren: function(nodeFilter) + { + nodeFilter = nodeFilter || new WebInspector.HeapSnapshotCommon.NodeFilter(); + + if (this._filterInProgress) { + this._nextRequestedFilter = this._filterInProgress.equals(nodeFilter) ? null : nodeFilter; + return; + } + if (this._lastFilter && this._lastFilter.equals(nodeFilter)) + return; + this._filterInProgress = nodeFilter; + this.snapshot.aggregatesWithFilter(nodeFilter, this._aggregatesReceived.bind(this, nodeFilter)); + }, + + filterSelectIndexChanged: function(profiles, profileIndex) + { + this._profileIndex = profileIndex; + + var nodeFilter; + if (profileIndex !== -1) { + var minNodeId = profileIndex > 0 ? profiles[profileIndex - 1].maxJSObjectId : 0; + var maxNodeId = profiles[profileIndex].maxJSObjectId; + nodeFilter = new WebInspector.HeapSnapshotCommon.NodeFilter(minNodeId, maxNodeId) + } + + this._populateChildren(nodeFilter); + }, + + __proto__: WebInspector.HeapSnapshotViewportDataGrid.prototype +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotViewportDataGrid} + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + */ +WebInspector.HeapSnapshotDiffDataGrid = function(dataDisplayDelegate) +{ + var columns = [ + {id: "object", title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true}, + {id: "addedCount", title: WebInspector.UIString("# New"), width: "72px", sortable: true}, + {id: "removedCount", title: WebInspector.UIString("# Deleted"), width: "72px", sortable: true}, + {id: "countDelta", title: WebInspector.UIString("# Delta"), width: "64px", sortable: true}, + {id: "addedSize", title: WebInspector.UIString("Alloc. Size"), width: "72px", sortable: true, sort: WebInspector.DataGrid.Order.Descending}, + {id: "removedSize", title: WebInspector.UIString("Freed Size"), width: "72px", sortable: true}, + {id: "sizeDelta", title: WebInspector.UIString("Size Delta"), width: "72px", sortable: true} + ]; + WebInspector.HeapSnapshotViewportDataGrid.call(this, dataDisplayDelegate, columns); +} + +WebInspector.HeapSnapshotDiffDataGrid.prototype = { + /** + * @override + * @return {number} + */ + defaultPopulateCount: function() + { + return 50; + }, + + _sortFields: function(sortColumn, sortAscending) + { + return { + object: ["_name", sortAscending, "_count", false], + addedCount: ["_addedCount", sortAscending, "_name", true], + removedCount: ["_removedCount", sortAscending, "_name", true], + countDelta: ["_countDelta", sortAscending, "_name", true], + addedSize: ["_addedSize", sortAscending, "_name", true], + removedSize: ["_removedSize", sortAscending, "_name", true], + sizeDelta: ["_sizeDelta", sortAscending, "_name", true] + }[sortColumn]; + }, + + setDataSource: function(snapshot) + { + this.snapshot = snapshot; + }, + + /** + * @param {!WebInspector.HeapSnapshotProxy} baseSnapshot + */ + setBaseDataSource: function(baseSnapshot) + { + this.baseSnapshot = baseSnapshot; + this.removeTopLevelNodes(); + this.resetSortingCache(); + if (this.baseSnapshot === this.snapshot) { + this.dispatchEventToListeners(WebInspector.HeapSnapshotSortableDataGrid.Events.SortingComplete); + return; + } + this._populateChildren(); + }, + + _populateChildren: function() + { + /** + * @this {WebInspector.HeapSnapshotDiffDataGrid} + */ + function aggregatesForDiffReceived(aggregatesForDiff) + { + this.snapshot.calculateSnapshotDiff(this.baseSnapshot.uid, aggregatesForDiff, didCalculateSnapshotDiff.bind(this)); + + /** + * @this {WebInspector.HeapSnapshotDiffDataGrid} + */ + function didCalculateSnapshotDiff(diffByClassName) + { + for (var className in diffByClassName) { + var diff = diffByClassName[className]; + this.appendNode(this.rootNode(), new WebInspector.HeapSnapshotDiffNode(this, className, diff)); + } + this.sortingChanged(); + } + } + // Two snapshots live in different workers isolated from each other. That is why + // we first need to collect information about the nodes in the first snapshot and + // then pass it to the second snapshot to calclulate the diff. + this.baseSnapshot.aggregatesForDiff(aggregatesForDiffReceived.bind(this)); + }, + + __proto__: WebInspector.HeapSnapshotViewportDataGrid.prototype +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotSortableDataGrid} + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + */ +WebInspector.HeapSnapshotDominatorsDataGrid = function(dataDisplayDelegate) +{ + var columns = [ + {id: "object", title: WebInspector.UIString("Object"), disclosure: true, sortable: true}, + {id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true}, + {id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true} + ]; + WebInspector.HeapSnapshotSortableDataGrid.call(this, dataDisplayDelegate, columns); + this._objectIdToSelect = null; +} + +WebInspector.HeapSnapshotDominatorsDataGrid.prototype = { + /** + * @override + * @return {number} + */ + defaultPopulateCount: function() + { + return 25; + }, + + setDataSource: function(snapshot) + { + this.snapshot = snapshot; + + var fakeNode = new WebInspector.HeapSnapshotCommon.Node(-1, "", 0, this.snapshot.rootNodeIndex, 0, 0, ""); + this.setRootNode(new WebInspector.HeapSnapshotDominatorObjectNode(this, fakeNode)); + this.rootNode().sort(); + + if (this._objectIdToSelect) { + this.highlightObjectByHeapSnapshotId(this._objectIdToSelect, function(found) {}); + this._objectIdToSelect = null; + } + }, + + sortingChanged: function() + { + this.rootNode().sort(); + }, + + /** + * @override + * @param {!HeapProfilerAgent.HeapSnapshotObjectId} id + * @param {function(boolean)} callback + */ + highlightObjectByHeapSnapshotId: function(id, callback) + { + if (!this.snapshot) { + this._objectIdToSelect = id; + callback(false); + return; + } + + /** + * @this {WebInspector.HeapSnapshotDominatorsDataGrid} + */ + function didGetDominators(dominatorIds) + { + if (!dominatorIds) { + console.error("Cannot find corresponding heap snapshot node"); + callback(false); + return; + } + var dominatorNode = this.rootNode(); + expandNextDominator.call(this, dominatorIds, dominatorNode); + } + + /** + * @this {WebInspector.HeapSnapshotDominatorsDataGrid} + */ + function expandNextDominator(dominatorIds, dominatorNode) + { + if (!dominatorNode) { + console.error("Cannot find dominator node"); + callback(false); + return; + } + if (!dominatorIds.length) { + this.highlightNode(dominatorNode); + dominatorNode.element.scrollIntoViewIfNeeded(true); + callback(true); + return; + } + var snapshotObjectId = dominatorIds.pop(); + dominatorNode.retrieveChildBySnapshotObjectId(snapshotObjectId, expandNextDominator.bind(this, dominatorIds)); + } + + this.snapshot.dominatorIdsForNode(parseInt(id, 10), didGetDominators.bind(this)); + }, + + __proto__: WebInspector.HeapSnapshotSortableDataGrid.prototype +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotViewportDataGrid} + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + */ +WebInspector.AllocationDataGrid = function(dataDisplayDelegate) +{ + var columns = [ + {id: "liveCount", title: WebInspector.UIString("Live Count"), width: "72px", sortable: true}, + {id: "count", title: WebInspector.UIString("Count"), width: "72px", sortable: true}, + {id: "liveSize", title: WebInspector.UIString("Live Size"), width: "72px", sortable: true}, + {id: "size", title: WebInspector.UIString("Size"), width: "72px", sortable: true, sort: WebInspector.DataGrid.Order.Descending}, + {id: "name", title: WebInspector.UIString("Function"), disclosure: true, sortable: true}, + ]; + WebInspector.HeapSnapshotViewportDataGrid.call(this, dataDisplayDelegate, columns); + this._linkifier = new WebInspector.Linkifier(); +} + +WebInspector.AllocationDataGrid.prototype = { + dispose: function() + { + this._linkifier.reset(); + }, + + setDataSource: function(snapshot) + { + this.snapshot = snapshot; + this.snapshot.allocationTracesTops(didReceiveAllocationTracesTops.bind(this)); + + /** + * @param {!Array.<!WebInspector.HeapSnapshotCommon.SerializedAllocationNode>} tops + * @this {WebInspector.AllocationDataGrid} + */ + function didReceiveAllocationTracesTops(tops) + { + this._topNodes = tops; + this._populateChildren(); + } + }, + + _populateChildren: function() + { + this.removeTopLevelNodes(); + var root = this.rootNode(); + var tops = this._topNodes; + for (var i = 0; i < tops.length; i++) + this.appendNode(root, new WebInspector.AllocationGridNode(this, tops[i])); + this.updateVisibleNodes(true); + }, + + sortingChanged: function() + { + this._topNodes.sort(this._createComparator()); + this.rootNode().removeChildren(); + this._populateChildren(); + }, + + + /** + * @return {function(!Object, !Object):number} + */ + _createComparator: function() + { + var fieldName = this.sortColumnIdentifier(); + var compareResult = (this.sortOrder() === WebInspector.DataGrid.Order.Ascending) ? +1 : -1; + /** + * @param {!Object} a + * @param {!Object} b + * @return {number} + */ + function compare(a, b) + { + if (a[fieldName] > b[fieldName]) + return compareResult; + if (a[fieldName] < b[fieldName]) + return -compareResult; + return 0; + } + return compare; + }, + + __proto__: WebInspector.HeapSnapshotViewportDataGrid.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotGridNodes.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotGridNodes.js new file mode 100644 index 00000000000..3a2223f1fbd --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotGridNodes.js @@ -0,0 +1,1616 @@ +/* + * Copyright (C) 2011 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. + */ + +/** + * @constructor + * @extends {WebInspector.DataGridNode} + * @param {!WebInspector.HeapSnapshotSortableDataGrid} tree + * @param {boolean} hasChildren + */ +WebInspector.HeapSnapshotGridNode = function(tree, hasChildren) +{ + WebInspector.DataGridNode.call(this, null, hasChildren); + this._dataGrid = tree; + this._instanceCount = 0; + + this._savedChildren = null; + /** + * List of position ranges for all visible nodes: [startPos1, endPos1),...,[startPosN, endPosN) + * Position is an item position in the provider. + */ + this._retrievedChildrenRanges = []; + + /** + * @type {?WebInspector.HeapSnapshotGridNode.ChildrenProvider} + */ + this._providerObject = null; +} + +WebInspector.HeapSnapshotGridNode.Events = { + PopulateComplete: "PopulateComplete" +} + +/** + * @param {!Array.<string>} fieldNames + * @return {!WebInspector.HeapSnapshotCommon.ComparatorConfig} + */ +WebInspector.HeapSnapshotGridNode.createComparator = function(fieldNames) +{ + return /** @type {!WebInspector.HeapSnapshotCommon.ComparatorConfig} */ ({fieldName1: fieldNames[0], ascending1: fieldNames[1], fieldName2: fieldNames[2], ascending2: fieldNames[3]}); +} + + +/** + * @interface + */ +WebInspector.HeapSnapshotGridNode.ChildrenProvider = function() { } + +WebInspector.HeapSnapshotGridNode.ChildrenProvider.prototype = { + dispose: function() { }, + + /** + * @param {number} snapshotObjectId + * @param {function(number)} callback + */ + nodePosition: function(snapshotObjectId, callback) { }, + + /** + * @param {function(boolean)} callback + */ + isEmpty: function(callback) { }, + + /** + * @param {number} startPosition + * @param {number} endPosition + * @param {function(!WebInspector.HeapSnapshotCommon.ItemsRange)} callback + */ + serializeItemsRange: function(startPosition, endPosition, callback) { }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.ComparatorConfig} comparator + * @param {function()} callback + */ + sortAndRewind: function(comparator, callback) { } +} + + +WebInspector.HeapSnapshotGridNode.prototype = { + /** + * @return {!WebInspector.HeapSnapshotGridNode.ChildrenProvider} + */ + createProvider: function() + { + throw new Error("Not implemented."); + }, + + /** + * @return {?{snapshot:!WebInspector.HeapSnapshotProxy, snapshotNodeIndex:number}} + */ + retainersDataSource: function() + { + return null; + }, + + /** + * @return {!WebInspector.HeapSnapshotGridNode.ChildrenProvider} + */ + _provider: function() + { + if (!this._providerObject) + this._providerObject = this.createProvider(); + return this._providerObject; + }, + + /** + * @param {string} columnIdentifier + * @return {!Element} + */ + createCell: function(columnIdentifier) + { + var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); + if (this._searchMatched) + cell.classList.add("highlight"); + return cell; + }, + + /** + * @override + */ + collapse: function() + { + WebInspector.DataGridNode.prototype.collapse.call(this); + this._dataGrid.updateVisibleNodes(true); + }, + + /** + * @override + */ + expand: function() + { + WebInspector.DataGridNode.prototype.expand.call(this); + this._dataGrid.updateVisibleNodes(true); + }, + + /** + * @override + */ + dispose: function() + { + if (this._providerObject) + this._providerObject.dispose(); + for (var node = this.children[0]; node; node = node.traverseNextNode(true, this, true)) + if (node.dispose) + node.dispose(); + }, + + _reachableFromWindow: false, + + queryObjectContent: function(callback) + { + }, + + /** + * @override + */ + wasDetached: function() + { + this._dataGrid.nodeWasDetached(this); + }, + + /** + * @param {number} num + * @return {string} + */ + _toPercentString: function(num) + { + return num.toFixed(0) + "\u2009%"; // \u2009 is a thin space. + }, + + /** + * @param {number} distance + * @return {string} + */ + _toUIDistance: function(distance) + { + var baseSystemDistance = WebInspector.HeapSnapshotCommon.baseSystemDistance; + return distance >= 0 && distance < baseSystemDistance ? WebInspector.UIString("%d", distance) : WebInspector.UIString("\u2212"); + }, + + /** + * @return {!Array.<!WebInspector.DataGridNode>} + */ + allChildren: function() + { + return this._dataGrid.allChildren(this); + }, + + /** + * @param {number} index + */ + removeChildByIndex: function(index) + { + this._dataGrid.removeChildByIndex(this, index); + }, + + /** + * @param {number} nodePosition + * @return {?WebInspector.DataGridNode} + */ + childForPosition: function(nodePosition) + { + var indexOfFirstChildInRange = 0; + for (var i = 0; i < this._retrievedChildrenRanges.length; i++) { + var range = this._retrievedChildrenRanges[i]; + if (range.from <= nodePosition && nodePosition < range.to) { + var childIndex = indexOfFirstChildInRange + nodePosition - range.from; + return this.allChildren()[childIndex]; + } + indexOfFirstChildInRange += range.to - range.from + 1; + } + return null; + }, + + /** + * @param {string} columnIdentifier + * @return {!Element} + */ + _createValueCell: function(columnIdentifier) + { + var cell = document.createElement("td"); + cell.className = "numeric-column"; + if (this.dataGrid.snapshot.totalSize !== 0) { + var div = document.createElement("div"); + var valueSpan = document.createElement("span"); + valueSpan.textContent = this.data[columnIdentifier]; + div.appendChild(valueSpan); + var percentColumn = columnIdentifier + "-percent"; + if (percentColumn in this.data) { + var percentSpan = document.createElement("span"); + percentSpan.className = "percent-column"; + percentSpan.textContent = this.data[percentColumn]; + div.appendChild(percentSpan); + div.classList.add("profile-multiple-values"); + } + cell.appendChild(div); + } + return cell; + }, + + populate: function(event) + { + if (this._populated) + return; + this._populated = true; + + /** + * @this {WebInspector.HeapSnapshotGridNode} + */ + function sorted() + { + this._populateChildren(); + } + this._provider().sortAndRewind(this.comparator(), sorted.bind(this)); + }, + + expandWithoutPopulate: function(callback) + { + // Make sure default populate won't take action. + this._populated = true; + this.expand(); + this._provider().sortAndRewind(this.comparator(), callback); + }, + + /** + * @param {?number=} fromPosition + * @param {?number=} toPosition + * @param {function()=} afterPopulate + */ + _populateChildren: function(fromPosition, toPosition, afterPopulate) + { + fromPosition = fromPosition || 0; + toPosition = toPosition || fromPosition + this._dataGrid.defaultPopulateCount(); + var firstNotSerializedPosition = fromPosition; + + /** + * @this {WebInspector.HeapSnapshotGridNode} + */ + function serializeNextChunk() + { + if (firstNotSerializedPosition >= toPosition) + return; + var end = Math.min(firstNotSerializedPosition + this._dataGrid.defaultPopulateCount(), toPosition); + this._provider().serializeItemsRange(firstNotSerializedPosition, end, childrenRetrieved.bind(this)); + firstNotSerializedPosition = end; + } + + /** + * @this {WebInspector.HeapSnapshotGridNode} + */ + function insertRetrievedChild(item, insertionIndex) + { + if (this._savedChildren) { + var hash = this._childHashForEntity(item); + if (hash in this._savedChildren) { + this._dataGrid.insertChild(this, this._savedChildren[hash], insertionIndex); + return; + } + } + this._dataGrid.insertChild(this, this._createChildNode(item), insertionIndex); + } + + /** + * @this {WebInspector.HeapSnapshotGridNode} + */ + function insertShowMoreButton(from, to, insertionIndex) + { + var button = new WebInspector.ShowMoreDataGridNode(this._populateChildren.bind(this), from, to, this._dataGrid.defaultPopulateCount()); + this._dataGrid.insertChild(this, button, insertionIndex); + } + + /** + * @param {!WebInspector.HeapSnapshotCommon.ItemsRange} itemsRange + * @this {WebInspector.HeapSnapshotGridNode} + */ + function childrenRetrieved(itemsRange) + { + var itemIndex = 0; + var itemPosition = itemsRange.startPosition; + var items = itemsRange.items; + var insertionIndex = 0; + + if (!this._retrievedChildrenRanges.length) { + if (itemsRange.startPosition > 0) { + this._retrievedChildrenRanges.push({from: 0, to: 0}); + insertShowMoreButton.call(this, 0, itemsRange.startPosition, insertionIndex++); + } + this._retrievedChildrenRanges.push({from: itemsRange.startPosition, to: itemsRange.endPosition}); + for (var i = 0, l = items.length; i < l; ++i) + insertRetrievedChild.call(this, items[i], insertionIndex++); + if (itemsRange.endPosition < itemsRange.totalLength) + insertShowMoreButton.call(this, itemsRange.endPosition, itemsRange.totalLength, insertionIndex++); + } else { + var rangeIndex = 0; + var found = false; + var range; + while (rangeIndex < this._retrievedChildrenRanges.length) { + range = this._retrievedChildrenRanges[rangeIndex]; + if (range.to >= itemPosition) { + found = true; + break; + } + insertionIndex += range.to - range.from; + // Skip the button if there is one. + if (range.to < itemsRange.totalLength) + insertionIndex += 1; + ++rangeIndex; + } + + if (!found || itemsRange.startPosition < range.from) { + // Update previous button. + this.allChildren()[insertionIndex - 1].setEndPosition(itemsRange.startPosition); + insertShowMoreButton.call(this, itemsRange.startPosition, found ? range.from : itemsRange.totalLength, insertionIndex); + range = {from: itemsRange.startPosition, to: itemsRange.startPosition}; + if (!found) + rangeIndex = this._retrievedChildrenRanges.length; + this._retrievedChildrenRanges.splice(rangeIndex, 0, range); + } else { + insertionIndex += itemPosition - range.from; + } + // At this point insertionIndex is always an index before button or between nodes. + // Also it is always true here that range.from <= itemPosition <= range.to + + // Stretch the range right bound to include all new items. + while (range.to < itemsRange.endPosition) { + // Skip already added nodes. + var skipCount = range.to - itemPosition; + insertionIndex += skipCount; + itemIndex += skipCount; + itemPosition = range.to; + + // We're at the position before button: ...<?node>x<button> + var nextRange = this._retrievedChildrenRanges[rangeIndex + 1]; + var newEndOfRange = nextRange ? nextRange.from : itemsRange.totalLength; + if (newEndOfRange > itemsRange.endPosition) + newEndOfRange = itemsRange.endPosition; + while (itemPosition < newEndOfRange) { + insertRetrievedChild.call(this, items[itemIndex++], insertionIndex++); + ++itemPosition; + } + // Merge with the next range. + if (nextRange && newEndOfRange === nextRange.from) { + range.to = nextRange.to; + // Remove "show next" button if there is one. + this.removeChildByIndex(insertionIndex); + this._retrievedChildrenRanges.splice(rangeIndex + 1, 1); + } else { + range.to = newEndOfRange; + // Remove or update next button. + if (newEndOfRange === itemsRange.totalLength) + this.removeChildByIndex(insertionIndex); + else + this.allChildren()[insertionIndex].setStartPosition(itemsRange.endPosition); + } + } + } + + // TODO: fix this. + this._instanceCount += items.length; + if (firstNotSerializedPosition < toPosition) { + serializeNextChunk.call(this); + return; + } + + if (this.expanded) + this._dataGrid.updateVisibleNodes(true); + if (afterPopulate) + afterPopulate(); + this.dispatchEventToListeners(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete); + } + serializeNextChunk.call(this); + }, + + _saveChildren: function() + { + this._savedChildren = null; + var children = this.allChildren(); + for (var i = 0, l = children.length; i < l; ++i) { + var child = children[i]; + if (!child.expanded) + continue; + if (!this._savedChildren) + this._savedChildren = {}; + this._savedChildren[this._childHashForNode(child)] = child; + } + }, + + sort: function() + { + this._dataGrid.recursiveSortingEnter(); + + /** + * @this {WebInspector.HeapSnapshotGridNode} + */ + function afterSort() + { + this._saveChildren(); + this._dataGrid.removeAllChildren(this); + this._retrievedChildrenRanges = []; + + /** + * @this {WebInspector.HeapSnapshotGridNode} + */ + function afterPopulate() + { + var children = this.allChildren(); + for (var i = 0, l = children.length; i < l; ++i) { + var child = children[i]; + if (child.expanded) + child.sort(); + } + this._dataGrid.recursiveSortingLeave(); + } + var instanceCount = this._instanceCount; + this._instanceCount = 0; + this._populateChildren(0, instanceCount, afterPopulate.bind(this)); + } + + this._provider().sortAndRewind(this.comparator(), afterSort.bind(this)); + }, + + __proto__: WebInspector.DataGridNode.prototype +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotGridNode} + * @param {!WebInspector.HeapSnapshotSortableDataGrid} dataGrid + * @param {!WebInspector.HeapSnapshotCommon.Node} node + */ +WebInspector.HeapSnapshotGenericObjectNode = function(dataGrid, node) +{ + WebInspector.HeapSnapshotGridNode.call(this, dataGrid, false); + // node is null for DataGrid root nodes. + if (!node) + return; + this._name = node.name; + this._type = node.type; + this._distance = node.distance; + this._shallowSize = node.selfSize; + this._retainedSize = node.retainedSize; + this.snapshotNodeId = node.id; + this.snapshotNodeIndex = node.nodeIndex; + if (this._type === "string") + this._reachableFromWindow = true; + else if (this._type === "object" && this._name.startsWith("Window")) { + this._name = this.shortenWindowURL(this._name, false); + this._reachableFromWindow = true; + } else if (node.canBeQueried) + this._reachableFromWindow = true; + if (node.detachedDOMTreeNode) + this.detachedDOMTreeNode = true; + + var snapshot = dataGrid.snapshot; + var shallowSizePercent = this._shallowSize / snapshot.totalSize * 100.0; + var retainedSizePercent = this._retainedSize / snapshot.totalSize * 100.0; + this.data = { + "distance": this._toUIDistance(this._distance), + "shallowSize": Number.withThousandsSeparator(this._shallowSize), + "retainedSize": Number.withThousandsSeparator(this._retainedSize), + "shallowSize-percent": this._toPercentString(shallowSizePercent), + "retainedSize-percent": this._toPercentString(retainedSizePercent) + }; +}; + +WebInspector.HeapSnapshotGenericObjectNode.prototype = { + /** + * @return {?{snapshot:!WebInspector.HeapSnapshotProxy, snapshotNodeIndex:number}} + */ + retainersDataSource: function() + { + return {snapshot: this._dataGrid.snapshot, snapshotNodeIndex: this.snapshotNodeIndex}; + }, + + /** + * @param {string} columnIdentifier + * @return {!Element} + */ + createCell: function(columnIdentifier) + { + var cell = columnIdentifier !== "object" ? this._createValueCell(columnIdentifier) : this._createObjectCell(); + if (this._searchMatched) + cell.classList.add("highlight"); + return cell; + }, + + /** + * @return {!Element} + */ + _createObjectCell: function() + { + var value = this._name; + var valueStyle = "object"; + switch (this._type) { + case "concatenated string": + case "string": + value = "\"" + value + "\""; + valueStyle = "string"; + break; + case "regexp": + value = "/" + value + "/"; + valueStyle = "string"; + break; + case "closure": + value = "function" + (value ? " " : "") + value + "()"; + valueStyle = "function"; + break; + case "number": + valueStyle = "number"; + break; + case "hidden": + valueStyle = "null"; + break; + case "array": + if (!value) + value = "[]"; + else + value += "[]"; + break; + }; + if (this._reachableFromWindow) + valueStyle += " highlight"; + if (value === "Object") + value = ""; + if (this.detachedDOMTreeNode) + valueStyle += " detached-dom-tree-node"; + return this._createObjectCellWithValue(valueStyle, value); + }, + + _createObjectCellWithValue: function(valueStyle, value) + { + var cell = document.createElement("td"); + cell.className = "object-column"; + var div = document.createElement("div"); + div.className = "source-code event-properties"; + div.style.overflow = "visible"; + + this._prefixObjectCell(div); + + var valueSpan = document.createElement("span"); + valueSpan.className = "value console-formatted-" + valueStyle; + valueSpan.textContent = value; + div.appendChild(valueSpan); + + var idSpan = document.createElement("span"); + idSpan.className = "console-formatted-id"; + idSpan.textContent = " @" + this.snapshotNodeId; + div.appendChild(idSpan); + + cell.appendChild(div); + cell.classList.add("disclosure"); + if (this.depth) + cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px"); + cell.heapSnapshotNode = this; + return cell; + }, + + _prefixObjectCell: function(div) + { + }, + + queryObjectContent: function(callback, objectGroupName) + { + /** + * @param {?Protocol.Error} error + * @param {!RuntimeAgent.RemoteObject} object + */ + function formatResult(error, object) + { + if (!error && object.type) + callback(WebInspector.runtimeModel.createRemoteObject(object), !!error); + else + callback(WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(WebInspector.UIString("Preview is not available"))); + } + + if (this._type === "string") + callback(WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(this._name)); + else + HeapProfilerAgent.getObjectByHeapObjectId(String(this.snapshotNodeId), objectGroupName, formatResult); + }, + + updateHasChildren: function() + { + /** + * @this {WebInspector.HeapSnapshotGenericObjectNode} + */ + function isEmptyCallback(isEmpty) + { + this.hasChildren = !isEmpty; + } + this._provider().isEmpty(isEmptyCallback.bind(this)); + }, + + /** + * @param {string} fullName + * @param {boolean} hasObjectId + * @return {string} + */ + shortenWindowURL: function(fullName, hasObjectId) + { + var startPos = fullName.indexOf("/"); + var endPos = hasObjectId ? fullName.indexOf("@") : fullName.length; + if (startPos !== -1 && endPos !== -1) { + var fullURL = fullName.substring(startPos + 1, endPos).trimLeft(); + var url = fullURL.trimURL(); + if (url.length > 40) + url = url.trimMiddle(40); + return fullName.substr(0, startPos + 2) + url + fullName.substr(endPos); + } else + return fullName; + }, + + __proto__: WebInspector.HeapSnapshotGridNode.prototype +} + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotGenericObjectNode} + * @param {!WebInspector.HeapSnapshotSortableDataGrid} dataGrid + * @param {!WebInspector.HeapSnapshotProxy} snapshot + * @param {!WebInspector.HeapSnapshotCommon.Edge} edge + * @param {?WebInspector.HeapSnapshotObjectNode} parentObjectNode + */ +WebInspector.HeapSnapshotObjectNode = function(dataGrid, snapshot, edge, parentObjectNode) +{ + WebInspector.HeapSnapshotGenericObjectNode.call(this, dataGrid, edge.node); + this._referenceName = edge.name; + this._referenceType = edge.type; + this._edgeIndex = edge.edgeIndex; + this._snapshot = snapshot; + + this._parentObjectNode = parentObjectNode; + this._cycledWithAncestorGridNode = this._findAncestorWithSameSnapshotNodeId(); + if (!this._cycledWithAncestorGridNode) + this.updateHasChildren(); + + var data = this.data; + data["count"] = ""; + data["addedCount"] = ""; + data["removedCount"] = ""; + data["countDelta"] = ""; + data["addedSize"] = ""; + data["removedSize"] = ""; + data["sizeDelta"] = ""; +} + +WebInspector.HeapSnapshotObjectNode.prototype = { + /** + * @return {?{snapshot:!WebInspector.HeapSnapshotProxy, snapshotNodeIndex:number}} + */ + retainersDataSource: function() + { + return {snapshot: this._snapshot, snapshotNodeIndex: this.snapshotNodeIndex}; + }, + + /** + * @return {!WebInspector.HeapSnapshotProviderProxy} + */ + createProvider: function() + { + return this._snapshot.createEdgesProvider(this.snapshotNodeIndex); + }, + + _findAncestorWithSameSnapshotNodeId: function() + { + var ancestor = this._parentObjectNode; + while (ancestor) { + if (ancestor.snapshotNodeId === this.snapshotNodeId) + return ancestor; + ancestor = ancestor._parentObjectNode; + } + return null; + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.Edge} item + * @return {!WebInspector.HeapSnapshotObjectNode} + */ + _createChildNode: function(item) + { + return new WebInspector.HeapSnapshotObjectNode(this._dataGrid, this._snapshot, item, this); + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.Edge} edge + * @return {number} + */ + _childHashForEntity: function(edge) + { + return edge.edgeIndex; + }, + + /** + * @param {!WebInspector.HeapSnapshotObjectNode} childNode + * @return {number} + */ + _childHashForNode: function(childNode) + { + return childNode._edgeIndex; + }, + + /** + * @return {!WebInspector.HeapSnapshotCommon.ComparatorConfig} + */ + comparator: function() + { + var sortAscending = this._dataGrid.isSortOrderAscending(); + var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier(); + var sortFields = { + object: ["!edgeName", sortAscending, "retainedSize", false], + count: ["!edgeName", true, "retainedSize", false], + shallowSize: ["selfSize", sortAscending, "!edgeName", true], + retainedSize: ["retainedSize", sortAscending, "!edgeName", true], + distance: ["distance", sortAscending, "_name", true] + }[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false]; + return WebInspector.HeapSnapshotGridNode.createComparator(sortFields); + }, + + _prefixObjectCell: function(div) + { + var name = this._referenceName; + if (name === "") name = "(empty)"; + var nameClass = "name"; + switch (this._referenceType) { + case "context": + nameClass = "console-formatted-number"; + break; + case "internal": + case "hidden": + case "weak": + nameClass = "console-formatted-null"; + break; + case "element": + name = "[" + name + "]"; + break; + } + + if (this._cycledWithAncestorGridNode) + div.className += " cycled-ancessor-node"; + + var nameSpan = document.createElement("span"); + nameSpan.className = nameClass; + nameSpan.textContent = name; + div.appendChild(nameSpan); + + var separatorSpan = document.createElement("span"); + separatorSpan.className = "grayed"; + separatorSpan.textContent = this._edgeNodeSeparator(); + div.appendChild(separatorSpan); + }, + + /** + * @return {string} + */ + _edgeNodeSeparator: function() + { + return " :: "; + }, + + __proto__: WebInspector.HeapSnapshotGenericObjectNode.prototype +} + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotObjectNode} + * @param {!WebInspector.HeapSnapshotSortableDataGrid} dataGrid + * @param {!WebInspector.HeapSnapshotProxy} snapshot + * @param {!WebInspector.HeapSnapshotCommon.Edge} edge + * @param {?WebInspector.HeapSnapshotRetainingObjectNode} parentRetainingObjectNode + */ +WebInspector.HeapSnapshotRetainingObjectNode = function(dataGrid, snapshot, edge, parentRetainingObjectNode) +{ + WebInspector.HeapSnapshotObjectNode.call(this, dataGrid, snapshot, edge, parentRetainingObjectNode); +} + +WebInspector.HeapSnapshotRetainingObjectNode.prototype = { + /** + * @return {!WebInspector.HeapSnapshotProviderProxy} + */ + createProvider: function() + { + return this._snapshot.createRetainingEdgesProvider(this.snapshotNodeIndex); + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.Edge} item + * @return {!WebInspector.HeapSnapshotRetainingObjectNode} + */ + _createChildNode: function(item) + { + return new WebInspector.HeapSnapshotRetainingObjectNode(this._dataGrid, this._snapshot, item, this); + }, + + /** + * @return {string} + */ + _edgeNodeSeparator: function() + { + return " in "; + }, + + expand: function() + { + this._expandRetainersChain(20); + }, + + /** + * @param {number} maxExpandLevels + */ + _expandRetainersChain: function(maxExpandLevels) + { + /** + * @this {!WebInspector.HeapSnapshotRetainingObjectNode} + */ + function populateComplete() + { + this.removeEventListener(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete, populateComplete, this); + this._expandRetainersChain(maxExpandLevels); + } + + if (!this._populated) { + this.addEventListener(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete, populateComplete, this); + this.populate(); + return; + } + WebInspector.HeapSnapshotGenericObjectNode.prototype.expand.call(this); + if (--maxExpandLevels > 0 && this.children.length > 0) { + var retainer = this.children[0]; + if (retainer._distance > 1) { + retainer._expandRetainersChain(maxExpandLevels); + return; + } + } + this._dataGrid.dispatchEventToListeners(WebInspector.HeapSnapshotRetainmentDataGrid.Events.ExpandRetainersComplete); + }, + + __proto__: WebInspector.HeapSnapshotObjectNode.prototype +} + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotGenericObjectNode} + * @param {!WebInspector.HeapSnapshotSortableDataGrid} dataGrid + * @param {!WebInspector.HeapSnapshotProxy} snapshot + * @param {!WebInspector.HeapSnapshotCommon.Node} node + * @param {boolean} isDeletedNode + */ +WebInspector.HeapSnapshotInstanceNode = function(dataGrid, snapshot, node, isDeletedNode) +{ + WebInspector.HeapSnapshotGenericObjectNode.call(this, dataGrid, node); + this._baseSnapshotOrSnapshot = snapshot; + this._isDeletedNode = isDeletedNode; + this.updateHasChildren(); + + var data = this.data; + data["count"] = ""; + data["countDelta"] = ""; + data["sizeDelta"] = ""; + if (this._isDeletedNode) { + data["addedCount"] = ""; + data["addedSize"] = ""; + data["removedCount"] = "\u2022"; + data["removedSize"] = Number.withThousandsSeparator(this._shallowSize); + } else { + data["addedCount"] = "\u2022"; + data["addedSize"] = Number.withThousandsSeparator(this._shallowSize); + data["removedCount"] = ""; + data["removedSize"] = ""; + } +}; + +WebInspector.HeapSnapshotInstanceNode.prototype = { + /** + * @return {?{snapshot:!WebInspector.HeapSnapshotProxy, snapshotNodeIndex:number}} + */ + retainersDataSource: function() + { + return {snapshot: this._baseSnapshotOrSnapshot, snapshotNodeIndex: this.snapshotNodeIndex}; + }, + + /** + * @return {!WebInspector.HeapSnapshotProviderProxy} + */ + createProvider: function() + { + return this._baseSnapshotOrSnapshot.createEdgesProvider(this.snapshotNodeIndex); + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.Edge} item + * @return {!WebInspector.HeapSnapshotObjectNode} + */ + _createChildNode: function(item) + { + return new WebInspector.HeapSnapshotObjectNode(this._dataGrid, this._baseSnapshotOrSnapshot, item, null); + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.Edge} edge + * @return {number} + */ + _childHashForEntity: function(edge) + { + return edge.edgeIndex; + }, + + /** + * @param {!WebInspector.HeapSnapshotObjectNode} childNode + * @return {number} + */ + _childHashForNode: function(childNode) + { + return childNode._edgeIndex; + }, + + /** + * @return {!WebInspector.HeapSnapshotCommon.ComparatorConfig} + */ + comparator: function() + { + var sortAscending = this._dataGrid.isSortOrderAscending(); + var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier(); + var sortFields = { + object: ["!edgeName", sortAscending, "retainedSize", false], + distance: ["distance", sortAscending, "retainedSize", false], + count: ["!edgeName", true, "retainedSize", false], + addedSize: ["selfSize", sortAscending, "!edgeName", true], + removedSize: ["selfSize", sortAscending, "!edgeName", true], + shallowSize: ["selfSize", sortAscending, "!edgeName", true], + retainedSize: ["retainedSize", sortAscending, "!edgeName", true] + }[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false]; + return WebInspector.HeapSnapshotGridNode.createComparator(sortFields); + }, + + __proto__: WebInspector.HeapSnapshotGenericObjectNode.prototype +} + +/** + * @constructor + * @param {!WebInspector.HeapSnapshotConstructorsDataGrid} dataGrid + * @param {string} className + * @param {!WebInspector.HeapSnapshotCommon.Aggregate} aggregate + * @param {!WebInspector.HeapSnapshotCommon.NodeFilter} nodeFilter + * @extends {WebInspector.HeapSnapshotGridNode} + */ +WebInspector.HeapSnapshotConstructorNode = function(dataGrid, className, aggregate, nodeFilter) +{ + WebInspector.HeapSnapshotGridNode.call(this, dataGrid, aggregate.count > 0); + this._name = className; + this._nodeFilter = nodeFilter; + this._distance = aggregate.distance; + this._count = aggregate.count; + this._shallowSize = aggregate.self; + this._retainedSize = aggregate.maxRet; + + var snapshot = dataGrid.snapshot; + var countPercent = this._count / snapshot.nodeCount * 100.0; + var retainedSizePercent = this._retainedSize / snapshot.totalSize * 100.0; + var shallowSizePercent = this._shallowSize / snapshot.totalSize * 100.0; + + this.data = { + "object": className, + "count": Number.withThousandsSeparator(this._count), + "distance": this._toUIDistance(this._distance), + "shallowSize": Number.withThousandsSeparator(this._shallowSize), + "retainedSize": Number.withThousandsSeparator(this._retainedSize), + "count-percent": this._toPercentString(countPercent), + "shallowSize-percent": this._toPercentString(shallowSizePercent), + "retainedSize-percent": this._toPercentString(retainedSizePercent) + }; +} + +WebInspector.HeapSnapshotConstructorNode.prototype = { + /** + * @override + * @return {!WebInspector.HeapSnapshotProviderProxy} + */ + createProvider: function() + { + return this._dataGrid.snapshot.createNodesProviderForClass(this._name, this._nodeFilter) + }, + + /** + * @param {number} snapshotObjectId + * @param {function(boolean)} callback + */ + revealNodeBySnapshotObjectId: function(snapshotObjectId, callback) + { + /** + * @this {WebInspector.HeapSnapshotConstructorNode} + */ + function didExpand() + { + this._provider().nodePosition(snapshotObjectId, didGetNodePosition.bind(this)); + } + + /** + * @this {WebInspector.HeapSnapshotConstructorNode} + * @param {number} nodePosition + */ + function didGetNodePosition(nodePosition) + { + if (nodePosition === -1) { + this.collapse(); + callback(false); + } else { + this._populateChildren(nodePosition, null, didPopulateChildren.bind(this, nodePosition)); + } + } + + /** + * @this {WebInspector.HeapSnapshotConstructorNode} + * @param {number} nodePosition + */ + function didPopulateChildren(nodePosition) + { + var child = this.childForPosition(nodePosition); + if (child) { + this._dataGrid.revealTreeNode([this, child]); + this._dataGrid.highlightNode(/** @type {!WebInspector.HeapSnapshotGridNode} */ (child)); + } + callback(!!child); + } + + this._dataGrid.resetNameFilter(); + this.expandWithoutPopulate(didExpand.bind(this)); + }, + + /** + * @param {string} filterValue + * @return {boolean} + */ + filteredOut: function(filterValue) + { + return this._name.toLowerCase().indexOf(filterValue) === -1; + }, + + /** + * @param {string} columnIdentifier + * @return {!Element} + */ + createCell: function(columnIdentifier) + { + var cell = columnIdentifier !== "object" ? this._createValueCell(columnIdentifier) : WebInspector.HeapSnapshotGridNode.prototype.createCell.call(this, columnIdentifier); + if (this._searchMatched) + cell.classList.add("highlight"); + return cell; + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.Node} item + * @return {!WebInspector.HeapSnapshotInstanceNode} + */ + _createChildNode: function(item) + { + return new WebInspector.HeapSnapshotInstanceNode(this._dataGrid, this._dataGrid.snapshot, item, false); + }, + + /** + * @return {!WebInspector.HeapSnapshotCommon.ComparatorConfig} + */ + comparator: function() + { + var sortAscending = this._dataGrid.isSortOrderAscending(); + var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier(); + var sortFields = { + object: ["id", sortAscending, "retainedSize", false], + distance: ["distance", sortAscending, "retainedSize", false], + count: ["id", true, "retainedSize", false], + shallowSize: ["selfSize", sortAscending, "id", true], + retainedSize: ["retainedSize", sortAscending, "id", true] + }[sortColumnIdentifier]; + return WebInspector.HeapSnapshotGridNode.createComparator(sortFields); + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.Node} node + * @return {number} + */ + _childHashForEntity: function(node) + { + return node.id; + }, + + /** + * @param {!WebInspector.HeapSnapshotInstanceNode} childNode + * @return {number} + */ + _childHashForNode: function(childNode) + { + return childNode.snapshotNodeId; + }, + + __proto__: WebInspector.HeapSnapshotGridNode.prototype +} + + +/** + * @constructor + * @implements {WebInspector.HeapSnapshotGridNode.ChildrenProvider} + * @param {!WebInspector.HeapSnapshotProviderProxy} addedNodesProvider + * @param {!WebInspector.HeapSnapshotProviderProxy} deletedNodesProvider + * @param {number} addedCount + * @param {number} removedCount + */ +WebInspector.HeapSnapshotDiffNodesProvider = function(addedNodesProvider, deletedNodesProvider, addedCount, removedCount) +{ + this._addedNodesProvider = addedNodesProvider; + this._deletedNodesProvider = deletedNodesProvider; + this._addedCount = addedCount; + this._removedCount = removedCount; +} + +WebInspector.HeapSnapshotDiffNodesProvider.prototype = { + dispose: function() + { + this._addedNodesProvider.dispose(); + this._deletedNodesProvider.dispose(); + }, + + /** + * @override + * @param {number} snapshotObjectId + * @param {function(number)} callback + */ + nodePosition: function(snapshotObjectId, callback) + { + throw new Error("Unreachable"); + }, + + /** + * @param {function(boolean)} callback + */ + isEmpty: function(callback) + { + callback(false); + }, + + /** + * @param {number} beginPosition + * @param {number} endPosition + * @param {!function(!WebInspector.HeapSnapshotCommon.ItemsRange)} callback + */ + serializeItemsRange: function(beginPosition, endPosition, callback) + { + /** + * @param {!WebInspector.HeapSnapshotCommon.ItemsRange} items + * @this {WebInspector.HeapSnapshotDiffNodesProvider} + */ + function didReceiveAllItems(items) + { + items.totalLength = this._addedCount + this._removedCount; + callback(items); + } + + /** + * @param {!WebInspector.HeapSnapshotCommon.ItemsRange} addedItems + * @param {!WebInspector.HeapSnapshotCommon.ItemsRange} itemsRange + * @this {WebInspector.HeapSnapshotDiffNodesProvider} + */ + function didReceiveDeletedItems(addedItems, itemsRange) + { + var items = itemsRange.items; + if (!addedItems.items.length) + addedItems.startPosition = this._addedCount + itemsRange.startPosition; + for (var i = 0; i < items.length; i++) { + items[i].isAddedNotRemoved = false; + addedItems.items.push(items[i]); + } + addedItems.endPosition = this._addedCount + itemsRange.endPosition; + didReceiveAllItems.call(this, addedItems); + } + + /** + * @param {!WebInspector.HeapSnapshotCommon.ItemsRange} itemsRange + * @this {WebInspector.HeapSnapshotDiffNodesProvider} + */ + function didReceiveAddedItems(itemsRange) + { + var items = itemsRange.items; + for (var i = 0; i < items.length; i++) + items[i].isAddedNotRemoved = true; + if (itemsRange.endPosition < endPosition) + return this._deletedNodesProvider.serializeItemsRange(0, endPosition - itemsRange.endPosition, didReceiveDeletedItems.bind(this, itemsRange)); + + itemsRange.totalLength = this._addedCount + this._removedCount; + didReceiveAllItems.call(this, itemsRange); + } + + if (beginPosition < this._addedCount) { + this._addedNodesProvider.serializeItemsRange(beginPosition, endPosition, didReceiveAddedItems.bind(this)); + } else { + var emptyRange = new WebInspector.HeapSnapshotCommon.ItemsRange(0, 0, 0, []); + this._deletedNodesProvider.serializeItemsRange(beginPosition - this._addedCount, endPosition - this._addedCount, didReceiveDeletedItems.bind(this, emptyRange)); + } + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.ComparatorConfig} comparator + * @param {function()} callback + */ + sortAndRewind: function(comparator, callback) + { + /** + * @this {WebInspector.HeapSnapshotDiffNodesProvider} + */ + function afterSort() + { + this._deletedNodesProvider.sortAndRewind(comparator, callback); + } + this._addedNodesProvider.sortAndRewind(comparator, afterSort.bind(this)); + } +}; + +/** + * @constructor + * @param {!WebInspector.HeapSnapshotDiffDataGrid} dataGrid + * @param {string} className + * @param {!WebInspector.HeapSnapshotCommon.DiffForClass} diffForClass + * @extends {WebInspector.HeapSnapshotGridNode} + */ +WebInspector.HeapSnapshotDiffNode = function(dataGrid, className, diffForClass) +{ + WebInspector.HeapSnapshotGridNode.call(this, dataGrid, true); + this._name = className; + this._addedCount = diffForClass.addedCount; + this._removedCount = diffForClass.removedCount; + this._countDelta = diffForClass.countDelta; + this._addedSize = diffForClass.addedSize; + this._removedSize = diffForClass.removedSize; + this._sizeDelta = diffForClass.sizeDelta; + this._deletedIndexes = diffForClass.deletedIndexes; + this.data = { + "object": className, + "addedCount": Number.withThousandsSeparator(this._addedCount), + "removedCount": Number.withThousandsSeparator(this._removedCount), + "countDelta": this._signForDelta(this._countDelta) + Number.withThousandsSeparator(Math.abs(this._countDelta)), + "addedSize": Number.withThousandsSeparator(this._addedSize), + "removedSize": Number.withThousandsSeparator(this._removedSize), + "sizeDelta": this._signForDelta(this._sizeDelta) + Number.withThousandsSeparator(Math.abs(this._sizeDelta)) + }; +} + +WebInspector.HeapSnapshotDiffNode.prototype = { + /** + * @override + * @return {!WebInspector.HeapSnapshotDiffNodesProvider} + */ + createProvider: function() + { + var tree = this._dataGrid; + return new WebInspector.HeapSnapshotDiffNodesProvider( + tree.snapshot.createAddedNodesProvider(tree.baseSnapshot.uid, this._name), + tree.baseSnapshot.createDeletedNodesProvider(this._deletedIndexes), + this._addedCount, + this._removedCount); + }, + + /** + * @param {string} columnIdentifier + * @return {!Element} + */ + createCell: function(columnIdentifier) + { + var cell = WebInspector.HeapSnapshotGridNode.prototype.createCell.call(this, columnIdentifier); + if (columnIdentifier !== "object") + cell.classList.add("numeric-column"); + return cell; + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.Node} item + * @return {!WebInspector.HeapSnapshotInstanceNode} + */ + _createChildNode: function(item) + { + if (item.isAddedNotRemoved) + return new WebInspector.HeapSnapshotInstanceNode(this._dataGrid, this._dataGrid.snapshot, item, false); + else + return new WebInspector.HeapSnapshotInstanceNode(this._dataGrid, this._dataGrid.baseSnapshot, item, true); + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.Node} node + * @return {number} + */ + _childHashForEntity: function(node) + { + return node.id; + }, + + /** + * @param {!WebInspector.HeapSnapshotInstanceNode} childNode + * @return {number} + */ + _childHashForNode: function(childNode) + { + return childNode.snapshotNodeId; + }, + + /** + * @return {!WebInspector.HeapSnapshotCommon.ComparatorConfig} + */ + comparator: function() + { + var sortAscending = this._dataGrid.isSortOrderAscending(); + var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier(); + var sortFields = { + object: ["id", sortAscending, "selfSize", false], + addedCount: ["selfSize", sortAscending, "id", true], + removedCount: ["selfSize", sortAscending, "id", true], + countDelta: ["selfSize", sortAscending, "id", true], + addedSize: ["selfSize", sortAscending, "id", true], + removedSize: ["selfSize", sortAscending, "id", true], + sizeDelta: ["selfSize", sortAscending, "id", true] + }[sortColumnIdentifier]; + return WebInspector.HeapSnapshotGridNode.createComparator(sortFields); + }, + + /** + * @param {string} filterValue + * @return {boolean} + */ + filteredOut: function(filterValue) + { + return this._name.toLowerCase().indexOf(filterValue) === -1; + }, + + _signForDelta: function(delta) + { + if (delta === 0) + return ""; + if (delta > 0) + return "+"; + else + return "\u2212"; // Math minus sign, same width as plus. + }, + + __proto__: WebInspector.HeapSnapshotGridNode.prototype +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotGenericObjectNode} + * @param {!WebInspector.HeapSnapshotSortableDataGrid} dataGrid + * @param {!WebInspector.HeapSnapshotCommon.Node} node + */ +WebInspector.HeapSnapshotDominatorObjectNode = function(dataGrid, node) +{ + WebInspector.HeapSnapshotGenericObjectNode.call(this, dataGrid, node); + this.updateHasChildren(); +}; + +WebInspector.HeapSnapshotDominatorObjectNode.prototype = { + /** + * @override + * @return {!WebInspector.HeapSnapshotProviderProxy} + */ + createProvider: function() + { + return this._dataGrid.snapshot.createNodesProviderForDominator(this.snapshotNodeIndex); + }, + + /** + * @param {number} snapshotObjectId + * @param {function(?WebInspector.DataGridNode)} callback + */ + retrieveChildBySnapshotObjectId: function(snapshotObjectId, callback) + { + /** + * @this {WebInspector.HeapSnapshotDominatorObjectNode} + */ + function didExpand() + { + this._provider().nodePosition(snapshotObjectId, didGetNodePosition.bind(this)); + } + + /** + * @this {WebInspector.HeapSnapshotDominatorObjectNode} + */ + function didGetNodePosition(nodePosition) + { + if (nodePosition === -1) { + this.collapse(); + callback(null); + } else + this._populateChildren(nodePosition, null, didPopulateChildren.bind(this, nodePosition)); + } + + /** + * @this {WebInspector.HeapSnapshotDominatorObjectNode} + */ + function didPopulateChildren(nodePosition) + { + var child = this.childForPosition(nodePosition); + callback(child); + } + + // Make sure hasChildren flag is updated before expanding this node as updateHasChildren response + // may not have been received yet. + this.hasChildren = true; + this.expandWithoutPopulate(didExpand.bind(this)); + }, + + _createChildNode: function(item) + { + return new WebInspector.HeapSnapshotDominatorObjectNode(this._dataGrid, item); + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.Node} node + * @return {number} + */ + _childHashForEntity: function(node) + { + return node.id; + }, + + /** + * @param {!WebInspector.HeapSnapshotDominatorObjectNode} childNode + * @return {number} + */ + _childHashForNode: function(childNode) + { + return childNode.snapshotNodeId; + }, + + /** + * @return {!WebInspector.HeapSnapshotCommon.ComparatorConfig} + */ + comparator: function() + { + var sortAscending = this._dataGrid.isSortOrderAscending(); + var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier(); + var sortFields = { + object: ["id", sortAscending, "retainedSize", false], + shallowSize: ["selfSize", sortAscending, "id", true], + retainedSize: ["retainedSize", sortAscending, "id", true] + }[sortColumnIdentifier]; + return WebInspector.HeapSnapshotGridNode.createComparator(sortFields); + }, + + __proto__: WebInspector.HeapSnapshotGenericObjectNode.prototype +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotGridNode} + * @param {!WebInspector.AllocationDataGrid} dataGrid + * @param {!WebInspector.HeapSnapshotCommon.SerializedAllocationNode} data + */ +WebInspector.AllocationGridNode = function(dataGrid, data) +{ + WebInspector.HeapSnapshotGridNode.call(this, dataGrid, data.hasChildren); + this._populated = false; + this._allocationNode = data; + this.data = { + "liveCount": Number.withThousandsSeparator(data.liveCount), + "count": Number.withThousandsSeparator(data.count), + "liveSize": Number.withThousandsSeparator(data.liveSize), + "size": Number.withThousandsSeparator(data.size), + "name": data.name + }; +} + +WebInspector.AllocationGridNode.prototype = { + populate: function() + { + if (this._populated) + return; + this._populated = true; + this._dataGrid.snapshot.allocationNodeCallers(this._allocationNode.id, didReceiveCallers.bind(this)); + + /** + * @param {!WebInspector.HeapSnapshotCommon.AllocationNodeCallers} callers + * @this {WebInspector.AllocationGridNode} + */ + function didReceiveCallers(callers) + { + var callersChain = callers.nodesWithSingleCaller; + var parentNode = this; + var dataGrid = /** @type {!WebInspector.AllocationDataGrid} */ (this._dataGrid); + for (var i = 0; i < callersChain.length; i++) { + var child = new WebInspector.AllocationGridNode(dataGrid, callersChain[i]); + dataGrid.appendNode(parentNode, child); + parentNode = child; + parentNode._populated = true; + if (this.expanded) + parentNode.expand(); + } + + var callersBranch = callers.branchingCallers; + callersBranch.sort(this._dataGrid._createComparator()); + for (var i = 0; i < callersBranch.length; i++) + dataGrid.appendNode(parentNode, new WebInspector.AllocationGridNode(dataGrid, callersBranch[i])); + dataGrid.updateVisibleNodes(true); + } + }, + + /** + * @override + */ + expand: function() + { + WebInspector.HeapSnapshotGridNode.prototype.expand.call(this); + if (this.children.length === 1) + this.children[0].expand(); + }, + + /** + * @override + * @param {string} columnIdentifier + * @return {!Element} + */ + createCell: function(columnIdentifier) + { + if (columnIdentifier !== "name") + return this._createValueCell(columnIdentifier); + + var cell = WebInspector.HeapSnapshotGridNode.prototype.createCell.call(this, columnIdentifier); + var allocationNode = this._allocationNode; + if (allocationNode.scriptId) { + var urlElement; + var linkifier = this._dataGrid._linkifier; + var script = WebInspector.debuggerModel.scriptForId(String(allocationNode.scriptId)); + if (script) { + var rawLocation = WebInspector.debuggerModel.createRawLocation(script, allocationNode.line - 1, allocationNode.column - 1); + urlElement = linkifier.linkifyRawLocation(rawLocation, "profile-node-file"); + } else { + var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); + urlElement = linkifier.linkifyLocation(target, allocationNode.scriptName, allocationNode.line - 1, allocationNode.column - 1, "profile-node-file"); + } + urlElement.style.maxWidth = "75%"; + cell.insertBefore(urlElement, cell.firstChild); + } + return cell; + }, + + /** + * @return {number} + */ + allocationNodeId: function() + { + return this._allocationNode.id; + }, + + __proto__: WebInspector.HeapSnapshotGridNode.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotProxy.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotProxy.js new file mode 100644 index 00000000000..109398a15a0 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotProxy.js @@ -0,0 +1,550 @@ +/* + * Copyright (C) 2011 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 copyrightdd + * 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. + */ + +/** + * @constructor + * @param {function(string, *)} eventHandler + * @extends {WebInspector.Object} + */ +WebInspector.HeapSnapshotWorkerProxy = function(eventHandler) +{ + this._eventHandler = eventHandler; + this._nextObjectId = 1; + this._nextCallId = 1; + this._callbacks = []; + this._previousCallbacks = []; + this._worker = new Worker("profiler/heap_snapshot_worker/HeapSnapshotWorker.js"); + this._worker.onmessage = this._messageReceived.bind(this); +} + +WebInspector.HeapSnapshotWorkerProxy.prototype = { + /** + * @param {number} profileUid + * @param {function(!WebInspector.HeapSnapshotProxy)} snapshotReceivedCallback + * @return {!WebInspector.HeapSnapshotLoaderProxy} + */ + createLoader: function(profileUid, snapshotReceivedCallback) + { + var objectId = this._nextObjectId++; + var proxy = new WebInspector.HeapSnapshotLoaderProxy(this, objectId, profileUid, snapshotReceivedCallback); + this._postMessage({callId: this._nextCallId++, disposition: "create", objectId: objectId, methodName: "WebInspector.HeapSnapshotLoader"}); + return proxy; + }, + + dispose: function() + { + this._worker.terminate(); + if (this._interval) + clearInterval(this._interval); + }, + + disposeObject: function(objectId) + { + this._postMessage({callId: this._nextCallId++, disposition: "dispose", objectId: objectId}); + }, + + evaluateForTest: function(script, callback) + { + var callId = this._nextCallId++; + this._callbacks[callId] = callback; + this._postMessage({callId: callId, disposition: "evaluateForTest", source: script}); + }, + + /** + * @param {?function(...[?])} callback + * @param {string} objectId + * @param {string} methodName + * @param {function(new:T, ...[?])} proxyConstructor + * @return {?Object} + * @template T + */ + callFactoryMethod: function(callback, objectId, methodName, proxyConstructor) + { + var callId = this._nextCallId++; + var methodArguments = Array.prototype.slice.call(arguments, 4); + var newObjectId = this._nextObjectId++; + + /** + * @this {WebInspector.HeapSnapshotWorkerProxy} + */ + function wrapCallback(remoteResult) + { + callback(remoteResult ? new proxyConstructor(this, newObjectId) : null); + } + + if (callback) { + this._callbacks[callId] = wrapCallback.bind(this); + this._postMessage({callId: callId, disposition: "factory", objectId: objectId, methodName: methodName, methodArguments: methodArguments, newObjectId: newObjectId}); + return null; + } else { + this._postMessage({callId: callId, disposition: "factory", objectId: objectId, methodName: methodName, methodArguments: methodArguments, newObjectId: newObjectId}); + return new proxyConstructor(this, newObjectId); + } + }, + + /** + * @param {function(*)} callback + * @param {string} objectId + * @param {string} methodName + */ + callMethod: function(callback, objectId, methodName) + { + var callId = this._nextCallId++; + var methodArguments = Array.prototype.slice.call(arguments, 3); + if (callback) + this._callbacks[callId] = callback; + this._postMessage({callId: callId, disposition: "method", objectId: objectId, methodName: methodName, methodArguments: methodArguments}); + }, + + startCheckingForLongRunningCalls: function() + { + if (this._interval) + return; + this._checkLongRunningCalls(); + this._interval = setInterval(this._checkLongRunningCalls.bind(this), 300); + }, + + _checkLongRunningCalls: function() + { + for (var callId in this._previousCallbacks) + if (!(callId in this._callbacks)) + delete this._previousCallbacks[callId]; + var hasLongRunningCalls = false; + for (callId in this._previousCallbacks) { + hasLongRunningCalls = true; + break; + } + this.dispatchEventToListeners("wait", hasLongRunningCalls); + for (callId in this._callbacks) + this._previousCallbacks[callId] = true; + }, + + /** + * @param {!MessageEvent} event + */ + _messageReceived: function(event) + { + var data = event.data; + if (data.eventName) { + if (this._eventHandler) + this._eventHandler(data.eventName, data.data); + return; + } + if (data.error) { + if (data.errorMethodName) + WebInspector.messageSink.addMessage(WebInspector.UIString("An error occurred when a call to method '%s' was requested", data.errorMethodName)); + WebInspector.messageSink.addMessage(data["errorCallStack"]); + delete this._callbacks[data.callId]; + return; + } + if (!this._callbacks[data.callId]) + return; + var callback = this._callbacks[data.callId]; + delete this._callbacks[data.callId]; + callback(data.result); + }, + + _postMessage: function(message) + { + this._worker.postMessage(message); + }, + + __proto__: WebInspector.Object.prototype +} + + +/** + * @constructor + * @param {!WebInspector.HeapSnapshotWorkerProxy} worker + * @param {number} objectId + */ +WebInspector.HeapSnapshotProxyObject = function(worker, objectId) +{ + this._worker = worker; + this._objectId = objectId; +} + +WebInspector.HeapSnapshotProxyObject.prototype = { + /** + * @param {string} workerMethodName + * @param {!Array.<*>} args + */ + _callWorker: function(workerMethodName, args) + { + args.splice(1, 0, this._objectId); + return this._worker[workerMethodName].apply(this._worker, args); + }, + + dispose: function() + { + this._worker.disposeObject(this._objectId); + }, + + disposeWorker: function() + { + this._worker.dispose(); + }, + + /** + * @param {?function(...[?])} callback + * @param {string} methodName + * @param {function (new:T, ...[?])} proxyConstructor + * @param {...*} var_args + * @return {!T} + * @template T + */ + callFactoryMethod: function(callback, methodName, proxyConstructor, var_args) + { + return this._callWorker("callFactoryMethod", Array.prototype.slice.call(arguments, 0)); + }, + + /** + * @param {function(T)|undefined} callback + * @param {string} methodName + * @param {...*} var_args + * @return {*} + * @template T + */ + callMethod: function(callback, methodName, var_args) + { + return this._callWorker("callMethod", Array.prototype.slice.call(arguments, 0)); + } +}; + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotProxyObject} + * @implements {WebInspector.OutputStream} + * @param {!WebInspector.HeapSnapshotWorkerProxy} worker + * @param {number} objectId + * @param {number} profileUid + * @param {function(!WebInspector.HeapSnapshotProxy)} snapshotReceivedCallback + */ +WebInspector.HeapSnapshotLoaderProxy = function(worker, objectId, profileUid, snapshotReceivedCallback) +{ + WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId); + this._profileUid = profileUid; + this._snapshotReceivedCallback = snapshotReceivedCallback; +} + +WebInspector.HeapSnapshotLoaderProxy.prototype = { + /** + * @param {string} chunk + * @param {function(!WebInspector.OutputStream)=} callback + */ + write: function(chunk, callback) + { + this.callMethod(callback, "write", chunk); + }, + + /** + * @param {function()=} callback + */ + close: function(callback) + { + /** + * @this {WebInspector.HeapSnapshotLoaderProxy} + */ + function buildSnapshot() + { + if (callback) + callback(); + var showHiddenData = WebInspector.settings.showAdvancedHeapSnapshotProperties.get(); + this.callFactoryMethod(updateStaticData.bind(this), "buildSnapshot", WebInspector.HeapSnapshotProxy, showHiddenData); + } + + /** + * @param {!WebInspector.HeapSnapshotProxy} snapshotProxy + * @this {WebInspector.HeapSnapshotLoaderProxy} + */ + function updateStaticData(snapshotProxy) + { + this.dispose(); + snapshotProxy.setProfileUid(this._profileUid); + snapshotProxy.updateStaticData(this._snapshotReceivedCallback.bind(this)); + } + + this.callMethod(buildSnapshot.bind(this), "close"); + }, + + __proto__: WebInspector.HeapSnapshotProxyObject.prototype +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotProxyObject} + * @param {!WebInspector.HeapSnapshotWorkerProxy} worker + * @param {number} objectId + */ +WebInspector.HeapSnapshotProxy = function(worker, objectId) +{ + WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId); + /** @type {?WebInspector.HeapSnapshotCommon.StaticData} */ + this._staticData = null; +} + +WebInspector.HeapSnapshotProxy.prototype = { + /** + * @param {!WebInspector.HeapSnapshotCommon.NodeFilter} filter + * @param {function(!Object.<string, !WebInspector.HeapSnapshotCommon.Aggregate>)} callback + */ + aggregatesWithFilter: function(filter, callback) + { + this.callMethod(callback, "aggregatesWithFilter", filter); + }, + + aggregatesForDiff: function(callback) + { + this.callMethod(callback, "aggregatesForDiff"); + }, + + calculateSnapshotDiff: function(baseSnapshotId, baseSnapshotAggregates, callback) + { + this.callMethod(callback, "calculateSnapshotDiff", baseSnapshotId, baseSnapshotAggregates); + }, + + nodeClassName: function(snapshotObjectId, callback) + { + this.callMethod(callback, "nodeClassName", snapshotObjectId); + }, + + dominatorIdsForNode: function(nodeIndex, callback) + { + this.callMethod(callback, "dominatorIdsForNode", nodeIndex); + }, + + /** + * @param {number} nodeIndex + * @return {!WebInspector.HeapSnapshotProviderProxy} + */ + createEdgesProvider: function(nodeIndex) + { + return this.callFactoryMethod(null, "createEdgesProvider", WebInspector.HeapSnapshotProviderProxy, nodeIndex); + }, + + /** + * @param {number} nodeIndex + * @return {!WebInspector.HeapSnapshotProviderProxy} + */ + createRetainingEdgesProvider: function(nodeIndex) + { + return this.callFactoryMethod(null, "createRetainingEdgesProvider", WebInspector.HeapSnapshotProviderProxy, nodeIndex); + }, + + /** + * @param {string} baseSnapshotId + * @param {string} className + * @return {?WebInspector.HeapSnapshotProviderProxy} + */ + createAddedNodesProvider: function(baseSnapshotId, className) + { + return this.callFactoryMethod(null, "createAddedNodesProvider", WebInspector.HeapSnapshotProviderProxy, baseSnapshotId, className); + }, + + /** + * @param {!Array.<number>} nodeIndexes + * @return {?WebInspector.HeapSnapshotProviderProxy} + */ + createDeletedNodesProvider: function(nodeIndexes) + { + return this.callFactoryMethod(null, "createDeletedNodesProvider", WebInspector.HeapSnapshotProviderProxy, nodeIndexes); + }, + + /** + * @param {function(*):boolean} filter + * @return {?WebInspector.HeapSnapshotProviderProxy} + */ + createNodesProvider: function(filter) + { + return this.callFactoryMethod(null, "createNodesProvider", WebInspector.HeapSnapshotProviderProxy, filter); + }, + + /** + * @param {string} className + * @param {!WebInspector.HeapSnapshotCommon.NodeFilter} nodeFilter + * @return {?WebInspector.HeapSnapshotProviderProxy} + */ + createNodesProviderForClass: function(className, nodeFilter) + { + return this.callFactoryMethod(null, "createNodesProviderForClass", WebInspector.HeapSnapshotProviderProxy, className, nodeFilter); + }, + + /** + * @param {number} nodeIndex + * @return {?WebInspector.HeapSnapshotProviderProxy} + */ + createNodesProviderForDominator: function(nodeIndex) + { + return this.callFactoryMethod(null, "createNodesProviderForDominator", WebInspector.HeapSnapshotProviderProxy, nodeIndex); + }, + + allocationTracesTops: function(callback) + { + this.callMethod(callback, "allocationTracesTops"); + }, + + /** + * @param {number} nodeId + * @param {function(!WebInspector.HeapSnapshotCommon.AllocationNodeCallers)} callback + */ + allocationNodeCallers: function(nodeId, callback) + { + this.callMethod(callback, "allocationNodeCallers", nodeId); + }, + + /** + * @param {number} nodeIndex + * @param {function(?Array.<!WebInspector.HeapSnapshotCommon.AllocationStackFrame>)} callback + */ + allocationStack: function(nodeIndex, callback) + { + this.callMethod(callback, "allocationStack", nodeIndex); + }, + + dispose: function() + { + throw new Error("Should never be called"); + }, + + get nodeCount() + { + return this._staticData.nodeCount; + }, + + get rootNodeIndex() + { + return this._staticData.rootNodeIndex; + }, + + updateStaticData: function(callback) + { + /** + * @param {!WebInspector.HeapSnapshotCommon.StaticData} staticData + * @this {WebInspector.HeapSnapshotProxy} + */ + function dataReceived(staticData) + { + this._staticData = staticData; + callback(this); + } + this.callMethod(dataReceived.bind(this), "updateStaticData"); + }, + + /** + * @param {!function(!WebInspector.HeapSnapshotCommon.Statistics):void} callback + */ + getStatistics: function(callback) + { + this.callMethod(callback, "getStatistics"); + }, + + get totalSize() + { + return this._staticData.totalSize; + }, + + get uid() + { + return this._profileUid; + }, + + setProfileUid: function(profileUid) + { + this._profileUid = profileUid; + }, + + /** + * @return {number} + */ + maxJSObjectId: function() + { + return this._staticData.maxJSObjectId; + }, + + __proto__: WebInspector.HeapSnapshotProxyObject.prototype +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotProxyObject} + * @implements {WebInspector.HeapSnapshotGridNode.ChildrenProvider} + * @param {!WebInspector.HeapSnapshotWorkerProxy} worker + * @param {number} objectId + */ +WebInspector.HeapSnapshotProviderProxy = function(worker, objectId) +{ + WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId); +} + +WebInspector.HeapSnapshotProviderProxy.prototype = { + /** + * @override + * @param {number} snapshotObjectId + * @param {function(number)} callback + */ + nodePosition: function(snapshotObjectId, callback) + { + this.callMethod(callback, "nodePosition", snapshotObjectId); + }, + + /** + * @override + * @param {function(boolean)} callback + */ + isEmpty: function(callback) + { + this.callMethod(callback, "isEmpty"); + }, + + /** + * @override + * @param {number} startPosition + * @param {number} endPosition + * @param {function(!WebInspector.HeapSnapshotCommon.ItemsRange)} callback + */ + serializeItemsRange: function(startPosition, endPosition, callback) + { + this.callMethod(callback, "serializeItemsRange", startPosition, endPosition); + }, + + /** + * @override + * @param {!WebInspector.HeapSnapshotCommon.ComparatorConfig} comparator + * @param {function()} callback + */ + sortAndRewind: function(comparator, callback) + { + this.callMethod(callback, "sortAndRewind", comparator); + }, + + __proto__: WebInspector.HeapSnapshotProxyObject.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotView.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotView.js new file mode 100644 index 00000000000..33148fcd4b1 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/HeapSnapshotView.js @@ -0,0 +1,2285 @@ +/* + * Copyright (C) 2011 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. + */ + +/** + * FIXME: ES5 strict mode check is suppressed due to multiple uses of arguments.callee. + * @fileoverview + * @suppress {es5Strict} + */ + +/** + * @constructor + * @extends {WebInspector.VBox} + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + * @param {!WebInspector.HeapProfileHeader} profile + */ +WebInspector.HeapSnapshotView = function(dataDisplayDelegate, profile) +{ + WebInspector.VBox.call(this); + + this.element.classList.add("heap-snapshot-view"); + + profile.profileType().addEventListener(WebInspector.HeapSnapshotProfileType.SnapshotReceived, this._onReceiveSnapshot, this); + profile.profileType().addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, this._onProfileHeaderRemoved, this); + + if (profile._profileType.id === WebInspector.TrackingHeapSnapshotProfileType.TypeId) { + this._trackingOverviewGrid = new WebInspector.HeapTrackingOverviewGrid(profile); + this._trackingOverviewGrid.addEventListener(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, this._onIdsRangeChanged.bind(this)); + } + + this._splitView = new WebInspector.SplitView(false, true, "heapSnapshotSplitViewState", 200, 200); + this._splitView.show(this.element); + + this._containmentView = new WebInspector.VBox(); + this._containmentView.setMinimumSize(50, 25); + this._containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid(dataDisplayDelegate); + this._containmentDataGrid.show(this._containmentView.element); + this._containmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); + + this._statisticsView = new WebInspector.HeapSnapshotStatisticsView(); + + this._constructorsView = new WebInspector.VBox(); + this._constructorsView.setMinimumSize(50, 25); + + this._constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid(dataDisplayDelegate); + this._constructorsDataGrid.show(this._constructorsView.element); + this._constructorsDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); + + this._diffView = new WebInspector.VBox(); + this._diffView.setMinimumSize(50, 25); + + this._diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid(dataDisplayDelegate); + this._diffDataGrid.show(this._diffView.element); + this._diffDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); + + this._dominatorView = new WebInspector.VBox(); + this._dominatorView.setMinimumSize(50, 25); + this._dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid(dataDisplayDelegate); + this._dominatorDataGrid.show(this._dominatorView.element); + this._dominatorDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); + + if (WebInspector.experimentsSettings.heapAllocationProfiler.isEnabled() && profile.profileType() === WebInspector.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType) { + this._allocationView = new WebInspector.VBox(); + this._allocationView.setMinimumSize(50, 25); + this._allocationDataGrid = new WebInspector.AllocationDataGrid(dataDisplayDelegate); + this._allocationDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._onSelectAllocationNode, this); + this._allocationDataGrid.show(this._allocationView.element); + + this._allocationStackView = new WebInspector.HeapAllocationStackView(); + this._allocationStackView.setMinimumSize(50, 25); + + this._tabbedPane = new WebInspector.TabbedPane(); + this._tabbedPane.closeableTabs = false; + this._tabbedPane.headerElement().classList.add("heap-object-details-header"); + } + + this._retainmentView = new WebInspector.VBox(); + this._retainmentView.setMinimumSize(50, 21); + this._retainmentView.element.classList.add("retaining-paths-view"); + + var splitViewResizer; + if (this._allocationStackView) { + this._tabbedPane = new WebInspector.TabbedPane(); + this._tabbedPane.closeableTabs = false; + this._tabbedPane.headerElement().classList.add("heap-object-details-header"); + + this._tabbedPane.appendTab("retainers", WebInspector.UIString("Retainers"), this._retainmentView); + this._tabbedPane.appendTab("allocation-stack", WebInspector.UIString("Allocation stack"), this._allocationStackView); + + splitViewResizer = this._tabbedPane.headerElement(); + this._objectDetailsView = this._tabbedPane; + } else { + var retainmentViewHeader = document.createElementWithClass("div", "heap-snapshot-view-resizer"); + var retainingPathsTitleDiv = retainmentViewHeader.createChild("div", "title"); + var retainingPathsTitle = retainingPathsTitleDiv.createChild("span"); + retainingPathsTitle.textContent = WebInspector.UIString("Retainers"); + this._retainmentView.element.appendChild(retainmentViewHeader); + + splitViewResizer = retainmentViewHeader; + this._objectDetailsView = this._retainmentView; + } + this._splitView.hideDefaultResizer(); + this._splitView.installResizer(splitViewResizer); + + this._retainmentDataGrid = new WebInspector.HeapSnapshotRetainmentDataGrid(dataDisplayDelegate); + this._retainmentDataGrid.show(this._retainmentView.element); + this._retainmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._inspectedObjectChanged, this); + this._retainmentDataGrid.reset(); + + this._perspectives = []; + this._perspectives.push(new WebInspector.HeapSnapshotView.SummaryPerspective()); + if (profile.profileType() !== WebInspector.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType) + this._perspectives.push(new WebInspector.HeapSnapshotView.ComparisonPerspective()); + this._perspectives.push(new WebInspector.HeapSnapshotView.ContainmentPerspective()); + if (WebInspector.settings.showAdvancedHeapSnapshotProperties.get()) + this._perspectives.push(new WebInspector.HeapSnapshotView.DominatorPerspective()); + if (this._allocationView) + this._perspectives.push(new WebInspector.HeapSnapshotView.AllocationPerspective()); + if (WebInspector.experimentsSettings.heapSnapshotStatistics.isEnabled()) + this._perspectives.push(new WebInspector.HeapSnapshotView.StatisticsPerspective()); + + this._perspectiveSelect = new WebInspector.StatusBarComboBox(this._onSelectedPerspectiveChanged.bind(this)); + for (var i = 0; i < this._perspectives.length; ++i) + this._perspectiveSelect.createOption(this._perspectives[i].title()); + + this._profile = profile; + + this._baseSelect = new WebInspector.StatusBarComboBox(this._changeBase.bind(this)); + this._baseSelect.visible = false; + this._updateBaseOptions(); + + this._filterSelect = new WebInspector.StatusBarComboBox(this._changeFilter.bind(this)); + this._filterSelect.visible = false; + this._updateFilterOptions(); + + this._classNameFilter = new WebInspector.StatusBarInput("Class filter"); + this._classNameFilter.visible = false; + this._constructorsDataGrid.setNameFilter(this._classNameFilter); + this._diffDataGrid.setNameFilter(this._classNameFilter); + + this._selectedSizeText = new WebInspector.StatusBarText(""); + + this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true); + + this._currentPerspectiveIndex = 0; + this._currentPerspective = this._perspectives[0]; + this._currentPerspective.activate(this); + this._dataGrid = this._currentPerspective.masterGrid(this); + + this._refreshView(); +} + +/** + * @constructor + * @param {string} title + */ +WebInspector.HeapSnapshotView.Perspective = function(title) +{ + this._title = title; +} + +WebInspector.HeapSnapshotView.Perspective.prototype = { + /** + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + */ + activate: function(heapSnapshotView) { }, + + /** + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + */ + deactivate: function(heapSnapshotView) + { + heapSnapshotView._baseSelect.visible = false; + heapSnapshotView._filterSelect.visible = false; + heapSnapshotView._classNameFilter.visible = false; + if (heapSnapshotView._trackingOverviewGrid) + heapSnapshotView._trackingOverviewGrid.detach(); + if (heapSnapshotView._allocationView) + heapSnapshotView._allocationView.detach(); + if (heapSnapshotView._statisticsView) + heapSnapshotView._statisticsView.detach(); + + heapSnapshotView._splitView.detach(); + heapSnapshotView._splitView.detachChildViews(); + }, + + /** + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + * @return {?WebInspector.DataGrid} + */ + masterGrid: function(heapSnapshotView) + { + return null; + }, + + /** + * @return {string} + */ + title: function() + { + return this._title; + }, + + /** + * @return {boolean} + */ + supportsSearch: function() + { + return false; + } +} + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotView.Perspective} + */ +WebInspector.HeapSnapshotView.SummaryPerspective = function() +{ + WebInspector.HeapSnapshotView.Perspective.call(this, WebInspector.UIString("Summary")); +} + +WebInspector.HeapSnapshotView.SummaryPerspective.prototype = { + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + */ + activate: function(heapSnapshotView) + { + heapSnapshotView._constructorsView.show(heapSnapshotView._splitView.mainElement()); + heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement()); + heapSnapshotView._splitView.show(heapSnapshotView.element); + heapSnapshotView._filterSelect.visible = true; + heapSnapshotView._classNameFilter.visible = true; + if (heapSnapshotView._trackingOverviewGrid) { + heapSnapshotView._trackingOverviewGrid.show(heapSnapshotView.element, heapSnapshotView._splitView.element); + heapSnapshotView._trackingOverviewGrid.update(); + heapSnapshotView._trackingOverviewGrid._updateGrid(); + } + }, + + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + * @return {?WebInspector.DataGrid} + */ + masterGrid: function(heapSnapshotView) + { + return heapSnapshotView._constructorsDataGrid; + }, + + /** + * @override + * @return {boolean} + */ + supportsSearch: function() + { + return true; + }, + + __proto__: WebInspector.HeapSnapshotView.Perspective.prototype +} + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotView.Perspective} + */ +WebInspector.HeapSnapshotView.ComparisonPerspective = function() +{ + WebInspector.HeapSnapshotView.Perspective.call(this, WebInspector.UIString("Comparison")); +} + +WebInspector.HeapSnapshotView.ComparisonPerspective.prototype = { + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + */ + activate: function(heapSnapshotView) + { + heapSnapshotView._diffView.show(heapSnapshotView._splitView.mainElement()); + heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement()); + heapSnapshotView._splitView.show(heapSnapshotView.element); + heapSnapshotView._baseSelect.visible = true; + heapSnapshotView._classNameFilter.visible = true; + }, + + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + * @return {?WebInspector.DataGrid} + */ + masterGrid: function(heapSnapshotView) + { + return heapSnapshotView._diffDataGrid; + }, + + /** + * @override + * @return {boolean} + */ + supportsSearch: function() + { + return true; + }, + + __proto__: WebInspector.HeapSnapshotView.Perspective.prototype +} + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotView.Perspective} + */ +WebInspector.HeapSnapshotView.ContainmentPerspective = function() +{ + WebInspector.HeapSnapshotView.Perspective.call(this, WebInspector.UIString("Containment")); +} + +WebInspector.HeapSnapshotView.ContainmentPerspective.prototype = { + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + */ + activate: function(heapSnapshotView) + { + heapSnapshotView._containmentView.show(heapSnapshotView._splitView.mainElement()); + heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement()); + heapSnapshotView._splitView.show(heapSnapshotView.element); + }, + + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + * @return {?WebInspector.DataGrid} + */ + masterGrid: function(heapSnapshotView) + { + return heapSnapshotView._containmentDataGrid; + }, + __proto__: WebInspector.HeapSnapshotView.Perspective.prototype +} + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotView.Perspective} + */ +WebInspector.HeapSnapshotView.DominatorPerspective = function() +{ + WebInspector.HeapSnapshotView.Perspective.call(this, WebInspector.UIString("Dominators")); +} + +WebInspector.HeapSnapshotView.DominatorPerspective.prototype = { + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + */ + activate: function(heapSnapshotView) + { + heapSnapshotView._dominatorView.show(heapSnapshotView._splitView.mainElement()); + heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement()); + heapSnapshotView._splitView.show(heapSnapshotView.element); + }, + + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + * @return {?WebInspector.DataGrid} + */ + masterGrid: function(heapSnapshotView) + { + return heapSnapshotView._dominatorDataGrid; + }, + + __proto__: WebInspector.HeapSnapshotView.Perspective.prototype +} + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotView.Perspective} + */ +WebInspector.HeapSnapshotView.AllocationPerspective = function() +{ + WebInspector.HeapSnapshotView.Perspective.call(this, WebInspector.UIString("Allocation")); + this._allocationSplitView = new WebInspector.SplitView(false, true, "heapSnapshotAllocationSplitViewState", 200, 200); + + var resizer = document.createElementWithClass("div", "heap-snapshot-view-resizer"); + var title = resizer.createChild("div", "title").createChild("span"); + title.textContent = WebInspector.UIString("Live objects"); + this._allocationSplitView.hideDefaultResizer(); + this._allocationSplitView.installResizer(resizer); + + this._allocationSplitView.sidebarElement().appendChild(resizer); +} + +WebInspector.HeapSnapshotView.AllocationPerspective.prototype = { + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + */ + activate: function(heapSnapshotView) + { + heapSnapshotView._allocationView.show(this._allocationSplitView.mainElement()); + heapSnapshotView._constructorsView.show(heapSnapshotView._splitView.mainElement()); + heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement()); + heapSnapshotView._splitView.show(this._allocationSplitView.sidebarElement()); + this._allocationSplitView.show(heapSnapshotView.element); + + heapSnapshotView._constructorsDataGrid.clear(); + var selectedNode = heapSnapshotView._allocationDataGrid.selectedNode; + if (selectedNode) + heapSnapshotView._constructorsDataGrid.setAllocationNodeId(selectedNode.allocationNodeId()); + }, + + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + */ + deactivate: function(heapSnapshotView) + { + this._allocationSplitView.detach(); + WebInspector.HeapSnapshotView.Perspective.prototype.deactivate.call(this, heapSnapshotView); + }, + + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + * @return {?WebInspector.DataGrid} + */ + masterGrid: function(heapSnapshotView) + { + return heapSnapshotView._allocationDataGrid; + }, + + __proto__: WebInspector.HeapSnapshotView.Perspective.prototype +} + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotView.Perspective} + */ +WebInspector.HeapSnapshotView.StatisticsPerspective = function() +{ + WebInspector.HeapSnapshotView.Perspective.call(this, WebInspector.UIString("Statistics")); +} + +WebInspector.HeapSnapshotView.StatisticsPerspective.prototype = { + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + */ + activate: function(heapSnapshotView) + { + heapSnapshotView._statisticsView.show(heapSnapshotView.element); + }, + + /** + * @override + * @param {!WebInspector.HeapSnapshotView} heapSnapshotView + * @return {?WebInspector.DataGrid} + */ + masterGrid: function(heapSnapshotView) + { + return null; + }, + + __proto__: WebInspector.HeapSnapshotView.Perspective.prototype +} + + +WebInspector.HeapSnapshotView.prototype = { + _refreshView: function() + { + this._profile.load(profileCallback.bind(this)); + + /** + * @param {!WebInspector.HeapSnapshotProxy} heapSnapshotProxy + * @this {WebInspector.HeapSnapshotView} + */ + function profileCallback(heapSnapshotProxy) + { + heapSnapshotProxy.getStatistics(this._gotStatistics.bind(this)); + var list = this._profiles(); + var profileIndex = list.indexOf(this._profile); + this._baseSelect.setSelectedIndex(Math.max(0, profileIndex - 1)); + this._dataGrid.setDataSource(heapSnapshotProxy); + if (this._trackingOverviewGrid) + this._trackingOverviewGrid._updateGrid(); + } + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.Statistics} statistics + */ + _gotStatistics: function(statistics) { + this._statisticsView.setTotal(statistics.total); + this._statisticsView.addRecord(statistics.code, WebInspector.UIString("Code"), "#f77"); + this._statisticsView.addRecord(statistics.strings, WebInspector.UIString("Strings"), "#5e5"); + this._statisticsView.addRecord(statistics.jsArrays, WebInspector.UIString("JS Arrays"), "#7af"); + this._statisticsView.addRecord(statistics.native, WebInspector.UIString("Typed Arrays"), "#fc5"); + this._statisticsView.addRecord(statistics.total, WebInspector.UIString("Total")); + }, + + _onIdsRangeChanged: function(event) + { + var minId = event.data.minId; + var maxId = event.data.maxId; + this._selectedSizeText.setText(WebInspector.UIString("Selected size: %s", Number.bytesToString(event.data.size))); + if (this._constructorsDataGrid.snapshot) + this._constructorsDataGrid.setSelectionRange(minId, maxId); + }, + + get statusBarItems() + { + var result = [this._perspectiveSelect.element, this._classNameFilter.element]; + if (this._profile.profileType() !== WebInspector.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType) + result.push(this._baseSelect.element, this._filterSelect.element); + result.push(this._selectedSizeText.element); + return result; + }, + + wasShown: function() + { + // FIXME: load base and current snapshots in parallel + this._profile.load(profileCallback.bind(this)); + + /** + * @this {WebInspector.HeapSnapshotView} + */ + function profileCallback() { + this._profile._wasShown(); + if (this._baseProfile) + this._baseProfile.load(function() { }); + } + }, + + willHide: function() + { + this._currentSearchResultIndex = -1; + this._popoverHelper.hidePopover(); + if (this.helpPopover && this.helpPopover.isShowing()) + this.helpPopover.hide(); + }, + + searchCanceled: function() + { + if (this._searchResults) { + for (var i = 0; i < this._searchResults.length; ++i) { + var node = this._searchResults[i].node; + delete node._searchMatched; + node.refresh(); + } + } + + delete this._searchFinishedCallback; + this._currentSearchResultIndex = -1; + this._searchResults = []; + }, + + /** + * @param {string} query + * @param {function(!WebInspector.View, number)} finishedCallback + */ + 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) + return; + if (!this._currentPerspective.supportsSearch()) + return; + + /** + * @param {boolean} found + * @this {WebInspector.HeapSnapshotView} + */ + function didHighlight(found) + { + finishedCallback(this, found ? 1 : 0); + } + + if (query.charAt(0) === "@") { + var snapshotNodeId = parseInt(query.substring(1), 10); + if (!isNaN(snapshotNodeId)) + this._dataGrid.highlightObjectByHeapSnapshotId(String(snapshotNodeId), didHighlight.bind(this)); + else + finishedCallback(this, 0); + return; + } + + this._searchFinishedCallback = finishedCallback; + var nameRegExp = createPlainTextSearchRegex(query, "i"); + + function matchesByName(gridNode) { + return ("_name" in gridNode) && nameRegExp.test(gridNode._name); + } + + function matchesQuery(gridNode) + { + delete gridNode._searchMatched; + if (matchesByName(gridNode)) { + gridNode._searchMatched = true; + gridNode.refresh(); + return true; + } + return false; + } + + var current = this._dataGrid.rootNode().children[0]; + var depth = 0; + var info = {}; + + // Restrict to type nodes and instances. + const maxDepth = 1; + + while (current) { + if (matchesQuery(current)) + this._searchResults.push({ node: current }); + current = current.traverseNextNode(false, null, (depth >= maxDepth), info); + depth += info.depthChange; + } + + finishedCallback(this, this._searchResults.length); + }, + + jumpToFirstSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._currentSearchResultIndex = 0; + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToLastSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._currentSearchResultIndex = (this._searchResults.length - 1); + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToNextSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (++this._currentSearchResultIndex >= this._searchResults.length) + this._currentSearchResultIndex = 0; + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToPreviousSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (--this._currentSearchResultIndex < 0) + this._currentSearchResultIndex = (this._searchResults.length - 1); + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + /** + * @return {boolean} + */ + showingFirstSearchResult: function() + { + return (this._currentSearchResultIndex === 0); + }, + + /** + * @return {boolean} + */ + showingLastSearchResult: function() + { + return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); + }, + + /** + * @return {number} + */ + currentSearchResultIndex: function() { + return this._currentSearchResultIndex; + }, + + _jumpToSearchResult: function(index) + { + var searchResult = this._searchResults[index]; + if (!searchResult) + return; + + var node = searchResult.node; + node.revealAndSelect(); + }, + + refreshVisibleData: function() + { + if (!this._dataGrid) + return; + var child = this._dataGrid.rootNode().children[0]; + while (child) { + child.refresh(); + child = child.traverseNextNode(false, null, true); + } + }, + + _changeBase: function() + { + if (this._baseProfile === this._profiles()[this._baseSelect.selectedIndex()]) + return; + + this._baseProfile = this._profiles()[this._baseSelect.selectedIndex()]; + var dataGrid = /** @type {!WebInspector.HeapSnapshotDiffDataGrid} */ (this._dataGrid); + // Change set base data source only if main data source is already set. + if (dataGrid.snapshot) + this._baseProfile.load(dataGrid.setBaseDataSource.bind(dataGrid)); + + 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); + }, + + _changeFilter: function() + { + var profileIndex = this._filterSelect.selectedIndex() - 1; + this._dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex); + + WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { + action: WebInspector.UserMetrics.UserActionNames.HeapSnapshotFilterChanged, + label: this._filterSelect.selectedOption().label + }); + + 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); + }, + + /** + * @return {!Array.<!WebInspector.ProfileHeader>} + */ + _profiles: function() + { + return this._profile.profileType().getProfiles(); + }, + + /** + * @param {!WebInspector.ContextMenu} contextMenu + * @param {?Event} event + */ + populateContextMenu: function(contextMenu, event) + { + if (this._dataGrid) + this._dataGrid.populateContextMenu(contextMenu, event); + }, + + _selectionChanged: function(event) + { + var selectedNode = event.target.selectedNode; + this._setSelectedNodeForDetailsView(selectedNode); + this._inspectedObjectChanged(event); + }, + + _onSelectAllocationNode: function(event) + { + var selectedNode = event.target.selectedNode; + this._constructorsDataGrid.setAllocationNodeId(selectedNode.allocationNodeId()); + this._setSelectedNodeForDetailsView(null); + }, + + _inspectedObjectChanged: function(event) + { + var selectedNode = event.target.selectedNode; + if (!this._profile.fromFile() && selectedNode instanceof WebInspector.HeapSnapshotGenericObjectNode) + ConsoleAgent.addInspectedHeapObject(selectedNode.snapshotNodeId); + }, + + /** + * @param {?WebInspector.HeapSnapshotGridNode} nodeItem + */ + _setSelectedNodeForDetailsView: function(nodeItem) + { + var dataSource = nodeItem && nodeItem.retainersDataSource(); + if (dataSource) { + this._retainmentDataGrid.setDataSource(dataSource.snapshot, dataSource.snapshotNodeIndex); + if (this._allocationStackView) + this._allocationStackView.setAllocatedObject(dataSource.snapshot, dataSource.snapshotNodeIndex) + } else { + if (this._allocationStackView) + this._allocationStackView.clear(); + this._retainmentDataGrid.reset(); + } + }, + + /** + * @param {string} perspectiveTitle + * @param {function()} callback + */ + _changePerspectiveAndWait: function(perspectiveTitle, callback) + { + var perspectiveIndex = null; + for (var i = 0; i < this._perspectives.length; ++i) { + if (this._perspectives[i].title() === perspectiveTitle) { + perspectiveIndex = i; + break; + } + } + if (this._currentPerspectiveIndex === perspectiveIndex || perspectiveIndex === null) { + setTimeout(callback, 0); + return; + } + + /** + * @this {WebInspector.HeapSnapshotView} + */ + function dataGridContentShown(event) + { + var dataGrid = event.data; + dataGrid.removeEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this); + if (dataGrid === this._dataGrid) + callback(); + } + this._perspectives[perspectiveIndex].masterGrid(this).addEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this); + + this._perspectiveSelect.setSelectedIndex(perspectiveIndex); + this._changePerspective(perspectiveIndex); + }, + + _updateDataSourceAndView: function() + { + var dataGrid = this._dataGrid; + if (!dataGrid || dataGrid.snapshot) + return; + + this._profile.load(didLoadSnapshot.bind(this)); + + /** + * @this {WebInspector.HeapSnapshotView} + */ + function didLoadSnapshot(snapshotProxy) + { + if (this._dataGrid !== dataGrid) + return; + if (dataGrid.snapshot !== snapshotProxy) + dataGrid.setDataSource(snapshotProxy); + if (dataGrid === this._diffDataGrid) { + if (!this._baseProfile) + this._baseProfile = this._profiles()[this._baseSelect.selectedIndex()]; + this._baseProfile.load(didLoadBaseSnaphot.bind(this)); + } + } + + /** + * @this {WebInspector.HeapSnapshotView} + */ + function didLoadBaseSnaphot(baseSnapshotProxy) + { + if (this._diffDataGrid.baseSnapshot !== baseSnapshotProxy) + this._diffDataGrid.setBaseDataSource(baseSnapshotProxy); + } + }, + + _onSelectedPerspectiveChanged: function(event) + { + this._changePerspective(event.target.selectedIndex); + // FIXME: This is needed by CodeSchool extension. + this._onSelectedViewChanged(event); + }, + + _onSelectedViewChanged: function(event) + { + }, + + _changePerspective: function(selectedIndex) + { + if (selectedIndex === this._currentPerspectiveIndex) + return; + + this._currentPerspectiveIndex = selectedIndex; + + this._currentPerspective.deactivate(this); + var perspective = this._perspectives[selectedIndex]; + this._currentPerspective = perspective; + this._dataGrid = perspective.masterGrid(this); + perspective.activate(this); + + this.refreshVisibleData(); + if (this._dataGrid) + this._dataGrid.updateWidths(); + + this._updateDataSourceAndView(); + + 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 the with same query and callback. + this._searchFinishedCallback(this, -this._searchResults.length); + this.performSearch(this.currentQuery, this._searchFinishedCallback); + }, + + /** + * @param {string} perspectiveName + * @param {number} snapshotObjectId + */ + highlightLiveObject: function(perspectiveName, snapshotObjectId) + { + this._changePerspectiveAndWait(perspectiveName, didChangePerspective.bind(this)); + + /** + * @this {WebInspector.HeapSnapshotView} + */ + function didChangePerspective() + { + this._dataGrid.highlightObjectByHeapSnapshotId(snapshotObjectId, didHighlightObject); + } + + function didHighlightObject(found) + { + if (!found) + WebInspector.messageSink.addErrorMessage("Cannot find corresponding heap snapshot node", true); + } + }, + + _getHoverAnchor: function(target) + { + var span = target.enclosingNodeOrSelfWithNodeName("span"); + if (!span) + return; + var row = target.enclosingNodeOrSelfWithNodeName("tr"); + if (!row) + return; + span.node = row._dataGridNode; + return span; + }, + + _resolveObjectForPopover: function(element, showCallback, objectGroupName) + { + if (this._profile.fromFile()) + return; + element.node.queryObjectContent(showCallback, objectGroupName); + }, + + _updateBaseOptions: function() + { + var list = this._profiles(); + // We're assuming that snapshots can only be added. + if (this._baseSelect.size() === list.length) + return; + + for (var i = this._baseSelect.size(), n = list.length; i < n; ++i) { + var title = list[i].title; + this._baseSelect.createOption(title); + } + }, + + _updateFilterOptions: function() + { + var list = this._profiles(); + // We're assuming that snapshots can only be added. + if (this._filterSelect.size() - 1 === list.length) + return; + + if (!this._filterSelect.size()) + this._filterSelect.createOption(WebInspector.UIString("All objects")); + + for (var i = this._filterSelect.size() - 1, n = list.length; i < n; ++i) { + var title = list[i].title; + if (!i) + title = WebInspector.UIString("Objects allocated before %s", title); + else + title = WebInspector.UIString("Objects allocated between %s and %s", list[i - 1].title, title); + this._filterSelect.createOption(title); + } + }, + + _updateControls: function() + { + this._updateBaseOptions(); + this._updateFilterOptions(); + }, + + /** + * @param {!WebInspector.Event} event + */ + _onReceiveSnapshot: function(event) + { + this._updateControls(); + }, + + /** + * @param {!WebInspector.Event} event + */ + _onProfileHeaderRemoved: function(event) + { + var profile = event.data; + if (this._profile === profile) { + this.detach(); + this._profile.profileType().removeEventListener(WebInspector.HeapSnapshotProfileType.SnapshotReceived, this._onReceiveSnapshot, this); + this._profile.profileType().removeEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, this._onProfileHeaderRemoved, this); + this.dispose(); + } else { + this._updateControls(); + } + }, + + dispose: function() + { + if (this._allocationStackView) { + this._allocationStackView.clear(); + this._allocationDataGrid.dispose(); + } + }, + + __proto__: WebInspector.VBox.prototype +} + +/** + * @constructor + * @implements {HeapProfilerAgent.Dispatcher} + */ +WebInspector.HeapProfilerDispatcher = function() +{ + this._dispatchers = []; + InspectorBackend.registerHeapProfilerDispatcher(this); +} + +WebInspector.HeapProfilerDispatcher.prototype = { + /** + * @param {!HeapProfilerAgent.Dispatcher} dispatcher + */ + register: function(dispatcher) + { + this._dispatchers.push(dispatcher); + }, + + _genericCaller: function(eventName) + { + var args = Array.prototype.slice.call(arguments.callee.caller.arguments); + for (var i = 0; i < this._dispatchers.length; ++i) + this._dispatchers[i][eventName].apply(this._dispatchers[i], args); + }, + + /** + * @override + * @param {!Array.<number>} samples + */ + heapStatsUpdate: function(samples) + { + this._genericCaller("heapStatsUpdate"); + }, + + /** + * @override + * @param {number} lastSeenObjectId + * @param {number} timestamp + */ + lastSeenObjectId: function(lastSeenObjectId, timestamp) + { + this._genericCaller("lastSeenObjectId"); + }, + + /** + * @override + * @param {string} chunk + */ + addHeapSnapshotChunk: function(chunk) + { + this._genericCaller("addHeapSnapshotChunk"); + }, + + /** + * @override + * @param {number} done + * @param {number} total + * @param {boolean=} finished + */ + reportHeapSnapshotProgress: function(done, total, finished) + { + this._genericCaller("reportHeapSnapshotProgress"); + }, + + /** + * @override + */ + resetProfiles: function() + { + this._genericCaller("resetProfiles"); + } +} + +WebInspector.HeapProfilerDispatcher._dispatcher = new WebInspector.HeapProfilerDispatcher(); + +/** + * @constructor + * @extends {WebInspector.ProfileType} + * @implements {HeapProfilerAgent.Dispatcher} + * @param {string=} id + * @param {string=} title + */ +WebInspector.HeapSnapshotProfileType = function(id, title) +{ + WebInspector.ProfileType.call(this, id || WebInspector.HeapSnapshotProfileType.TypeId, title || WebInspector.UIString("Take Heap Snapshot")); + WebInspector.HeapProfilerDispatcher._dispatcher.register(this); +} + +WebInspector.HeapSnapshotProfileType.TypeId = "HEAP"; +WebInspector.HeapSnapshotProfileType.SnapshotReceived = "SnapshotReceived"; + +WebInspector.HeapSnapshotProfileType.prototype = { + /** + * @override + * @return {string} + */ + fileExtension: function() + { + return ".heapsnapshot"; + }, + + get buttonTooltip() + { + return WebInspector.UIString("Take heap snapshot."); + }, + + /** + * @override + * @return {boolean} + */ + isInstantProfile: function() + { + return true; + }, + + /** + * @override + * @return {boolean} + */ + buttonClicked: function() + { + this._takeHeapSnapshot(function() {}); + WebInspector.userMetrics.ProfilesHeapProfileTaken.record(); + return false; + }, + + /** + * @override + * @param {!Array.<number>} samples + */ + heapStatsUpdate: function(samples) + { + }, + + /** + * @override + * @param {number} lastSeenObjectId + * @param {number} timestamp + */ + lastSeenObjectId: function(lastSeenObjectId, timestamp) + { + }, + + get treeItemTitle() + { + return WebInspector.UIString("HEAP SNAPSHOTS"); + }, + + get description() + { + return WebInspector.UIString("Heap snapshot profiles show memory distribution among your page's JavaScript objects and related DOM nodes."); + }, + + /** + * @override + * @param {string} title + * @return {!WebInspector.ProfileHeader} + */ + createProfileLoadedFromFile: function(title) + { + var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); + return new WebInspector.HeapProfileHeader(target, this, title); + }, + + _takeHeapSnapshot: function(callback) + { + if (this.profileBeingRecorded()) + return; + var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); + var profile = new WebInspector.HeapProfileHeader(target, this); + this.setProfileBeingRecorded(profile); + this.addProfile(profile); + profile.updateStatus(WebInspector.UIString("Snapshotting\u2026")); + + /** + * @param {?string} error + * @this {WebInspector.HeapSnapshotProfileType} + */ + function didTakeHeapSnapshot(error) + { + var profile = this._profileBeingRecorded; + profile.title = WebInspector.UIString("Snapshot %d", profile.uid); + profile._finishLoad(); + this.setProfileBeingRecorded(null); + this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, profile); + callback(); + } + HeapProfilerAgent.takeHeapSnapshot(true, didTakeHeapSnapshot.bind(this)); + }, + + /** + * @override + * @param {string} chunk + */ + addHeapSnapshotChunk: function(chunk) + { + if (!this.profileBeingRecorded()) + return; + this.profileBeingRecorded().transferChunk(chunk); + }, + + /** + * @override + * @param {number} done + * @param {number} total + * @param {boolean=} finished + */ + reportHeapSnapshotProgress: function(done, total, finished) + { + var profile = this.profileBeingRecorded(); + if (!profile) + return; + profile.updateStatus(WebInspector.UIString("%.0f%", (done / total) * 100), true); + if (finished) + profile._prepareToLoad(); + }, + + /** + * @override + */ + resetProfiles: function() + { + this._reset(); + }, + + _snapshotReceived: function(profile) + { + if (this._profileBeingRecorded === profile) + this.setProfileBeingRecorded(null); + this.dispatchEventToListeners(WebInspector.HeapSnapshotProfileType.SnapshotReceived, profile); + }, + + __proto__: WebInspector.ProfileType.prototype +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotProfileType} + */ +WebInspector.TrackingHeapSnapshotProfileType = function() +{ + WebInspector.HeapSnapshotProfileType.call(this, WebInspector.TrackingHeapSnapshotProfileType.TypeId, WebInspector.UIString("Record Heap Allocations")); +} + +WebInspector.TrackingHeapSnapshotProfileType.TypeId = "HEAP-RECORD"; + +WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate = "HeapStatsUpdate"; +WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted = "TrackingStarted"; +WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped = "TrackingStopped"; + +WebInspector.TrackingHeapSnapshotProfileType.prototype = { + + /** + * @override + * @param {!Array.<number>} samples + */ + heapStatsUpdate: function(samples) + { + if (!this._profileSamples) + return; + var index; + for (var i = 0; i < samples.length; i += 3) { + index = samples[i]; + var count = samples[i+1]; + var size = samples[i+2]; + this._profileSamples.sizes[index] = size; + if (!this._profileSamples.max[index]) + this._profileSamples.max[index] = size; + } + }, + + /** + * @override + * @param {number} lastSeenObjectId + * @param {number} timestamp + */ + lastSeenObjectId: function(lastSeenObjectId, timestamp) + { + var profileSamples = this._profileSamples; + if (!profileSamples) + return; + var currentIndex = Math.max(profileSamples.ids.length, profileSamples.max.length - 1); + profileSamples.ids[currentIndex] = lastSeenObjectId; + if (!profileSamples.max[currentIndex]) { + profileSamples.max[currentIndex] = 0; + profileSamples.sizes[currentIndex] = 0; + } + profileSamples.timestamps[currentIndex] = timestamp; + if (profileSamples.totalTime < timestamp - profileSamples.timestamps[0]) + profileSamples.totalTime *= 2; + this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._profileSamples); + this._profileBeingRecorded.updateStatus(null, true); + }, + + /** + * @override + * @return {boolean} + */ + hasTemporaryView: function() + { + return true; + }, + + get buttonTooltip() + { + return this._recording ? WebInspector.UIString("Stop recording heap profile.") : WebInspector.UIString("Start recording heap profile."); + }, + + /** + * @override + * @return {boolean} + */ + isInstantProfile: function() + { + return false; + }, + + /** + * @override + * @return {boolean} + */ + buttonClicked: function() + { + return this._toggleRecording(); + }, + + _startRecordingProfile: function() + { + if (this.profileBeingRecorded()) + return; + this._addNewProfile(); + HeapProfilerAgent.startTrackingHeapObjects(WebInspector.experimentsSettings.heapAllocationProfiler.isEnabled()); + }, + + _addNewProfile: function() + { + var target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); + this.setProfileBeingRecorded(new WebInspector.HeapProfileHeader(target, this)); + this._lastSeenIndex = -1; + this._profileSamples = { + 'sizes': [], + 'ids': [], + 'timestamps': [], + 'max': [], + 'totalTime': 30000 + }; + this._profileBeingRecorded._profileSamples = this._profileSamples; + this._recording = true; + this.addProfile(this._profileBeingRecorded); + this._profileBeingRecorded.updateStatus(WebInspector.UIString("Recording\u2026")); + this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted); + }, + + _stopRecordingProfile: function() + { + this._profileBeingRecorded.updateStatus(WebInspector.UIString("Snapshotting\u2026")); + /** + * @param {?string} error + * @this {WebInspector.HeapSnapshotProfileType} + */ + function didTakeHeapSnapshot(error) + { + var profile = this._profileBeingRecorded; + if (!profile) + return; + profile._finishLoad(); + this._profileSamples = null; + this.setProfileBeingRecorded(null); + this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, profile); + } + + HeapProfilerAgent.stopTrackingHeapObjects(true, didTakeHeapSnapshot.bind(this)); + this._recording = false; + this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped); + }, + + _toggleRecording: function() + { + if (this._recording) + this._stopRecordingProfile(); + else + this._startRecordingProfile(); + return this._recording; + }, + + get treeItemTitle() + { + return WebInspector.UIString("HEAP TIMELINES"); + }, + + get description() + { + return WebInspector.UIString("Record JavaScript object allocations over time. Use this profile type to isolate memory leaks."); + }, + + /** + * @override + */ + resetProfiles: function() + { + var wasRecording = this._recording; + // Clear current profile to avoid stopping backend. + this.setProfileBeingRecorded(null); + WebInspector.HeapSnapshotProfileType.prototype.resetProfiles.call(this); + this._profileSamples = null; + this._lastSeenIndex = -1; + if (wasRecording) + this._addNewProfile(); + }, + + /** + * @override + */ + profileBeingRecordedRemoved: function() + { + this._stopRecordingProfile(); + this._profileSamples = null; + }, + + __proto__: WebInspector.HeapSnapshotProfileType.prototype +} + +/** + * @constructor + * @extends {WebInspector.ProfileHeader} + * @param {!WebInspector.Target} target + * @param {!WebInspector.HeapSnapshotProfileType} type + * @param {string=} title + */ +WebInspector.HeapProfileHeader = function(target, type, title) +{ + WebInspector.ProfileHeader.call(this, target, type, title || WebInspector.UIString("Snapshot %d", type._nextProfileUid)); + this.maxJSObjectId = -1; + /** + * @type {?WebInspector.HeapSnapshotWorkerProxy} + */ + this._workerProxy = null; + /** + * @type {?WebInspector.OutputStream} + */ + this._receiver = null; + /** + * @type {?WebInspector.HeapSnapshotProxy} + */ + this._snapshotProxy = null; + /** + * @type {?Array.<function(!WebInspector.HeapSnapshotProxy)>} + */ + this._loadCallbacks = []; + this._totalNumberOfChunks = 0; + this._bufferedWriter = null; +} + +WebInspector.HeapProfileHeader.prototype = { + /** + * @override + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + * @return {!WebInspector.ProfileSidebarTreeElement} + */ + createSidebarTreeElement: function(dataDisplayDelegate) + { + return new WebInspector.ProfileSidebarTreeElement(dataDisplayDelegate, this, "heap-snapshot-sidebar-tree-item"); + }, + + /** + * @override + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + * @return {!WebInspector.HeapSnapshotView} + */ + createView: function(dataDisplayDelegate) + { + return new WebInspector.HeapSnapshotView(dataDisplayDelegate, this); + }, + + /** + * @override + * @param {function(!WebInspector.HeapSnapshotProxy):void} callback + */ + load: function(callback) + { + if (this.uid === -1) + return; + if (this._snapshotProxy) { + callback(this._snapshotProxy); + return; + } + this._loadCallbacks.push(callback); + }, + + _prepareToLoad: function() + { + console.assert(!this._receiver, "Already loading"); + this._setupWorker(); + this.updateStatus(WebInspector.UIString("Loading\u2026"), true); + }, + + _finishLoad: function() + { + if (!this._wasDisposed) + this._receiver.close(function() {}); + if (this._bufferedWriter) { + this._bufferedWriter.close(this._didWriteToTempFile.bind(this)); + this._bufferedWriter = null; + } + }, + + _didWriteToTempFile: function(tempFile) + { + if (this._wasDisposed) { + if (tempFile) + tempFile.remove(); + return; + } + this._tempFile = tempFile; + if (!tempFile) + this._failedToCreateTempFile = true; + if (this._onTempFileReady) { + this._onTempFileReady(); + this._onTempFileReady = null; + } + }, + + _setupWorker: function() + { + /** + * @this {WebInspector.HeapProfileHeader} + */ + function setProfileWait(event) + { + this.updateStatus(null, event.data); + } + console.assert(!this._workerProxy, "HeapSnapshotWorkerProxy already exists"); + this._workerProxy = new WebInspector.HeapSnapshotWorkerProxy(this._handleWorkerEvent.bind(this)); + this._workerProxy.addEventListener("wait", setProfileWait, this); + this._receiver = this._workerProxy.createLoader(this.uid, this._snapshotReceived.bind(this)); + }, + + /** + * @param {string} eventName + * @param {*} data + */ + _handleWorkerEvent: function(eventName, data) + { + if (WebInspector.HeapSnapshotProgressEvent.Update !== eventName) + return; + var subtitle = /** @type {string} */ (data); + this.updateStatus(subtitle); + }, + + /** + * @override + */ + dispose: function() + { + if (this._workerProxy) + this._workerProxy.dispose(); + this.removeTempFile(); + this._wasDisposed = true; + }, + + _didCompleteSnapshotTransfer: function() + { + if (!this._snapshotProxy) + return; + this.updateStatus(Number.bytesToString(this._snapshotProxy.totalSize), false); + }, + + /** + * @param {string} chunk + */ + transferChunk: function(chunk) + { + if (!this._bufferedWriter) + this._bufferedWriter = new WebInspector.BufferedTempFileWriter("heap-profiler", this.uid); + this._bufferedWriter.write(chunk); + + ++this._totalNumberOfChunks; + this._receiver.write(chunk, function() {}); + }, + + _snapshotReceived: function(snapshotProxy) + { + if (this._wasDisposed) + return; + this._receiver = null; + this._snapshotProxy = snapshotProxy; + this.maxJSObjectId = snapshotProxy.maxJSObjectId(); + this._didCompleteSnapshotTransfer(); + this._workerProxy.startCheckingForLongRunningCalls(); + this.notifySnapshotReceived(); + }, + + notifySnapshotReceived: function() + { + for (var i = 0; i < this._loadCallbacks.length; i++) + this._loadCallbacks[i](this._snapshotProxy); + this._loadCallbacks = null; + this._profileType._snapshotReceived(this); + if (this.canSaveToFile()) + this.dispatchEventToListeners(WebInspector.ProfileHeader.Events.ProfileReceived); + }, + + // Hook point for tests. + _wasShown: function() + { + }, + + /** + * @override + * @return {boolean} + */ + canSaveToFile: function() + { + return !this.fromFile() && this._snapshotProxy; + }, + + /** + * @override + */ + saveToFile: function() + { + var fileOutputStream = new WebInspector.FileOutputStream(); + + /** + * @param {boolean} accepted + * @this {WebInspector.HeapProfileHeader} + */ + function onOpen(accepted) + { + if (!accepted) + return; + if (this._failedToCreateTempFile) { + WebInspector.messageSink.addErrorMessage("Failed to open temp file with heap snapshot"); + fileOutputStream.close(); + } else if (this._tempFile) { + var delegate = new WebInspector.SaveSnapshotOutputStreamDelegate(this); + this._tempFile.writeToOutputSteam(fileOutputStream, delegate); + } else { + this._onTempFileReady = onOpen.bind(this, accepted); + this._updateSaveProgress(0, 1); + } + } + this._fileName = this._fileName || "Heap-" + new Date().toISO8601Compact() + this._profileType.fileExtension(); + fileOutputStream.open(this._fileName, onOpen.bind(this)); + }, + + _updateSaveProgress: function(value, total) + { + var percentValue = ((total ? (value / total) : 0) * 100).toFixed(0); + this.updateStatus(WebInspector.UIString("Saving\u2026 %d\%", percentValue)); + }, + + /** + * @override + * @param {!File} file + */ + loadFromFile: function(file) + { + this.updateStatus(WebInspector.UIString("Loading\u2026"), true); + this._setupWorker(); + var delegate = new WebInspector.HeapSnapshotLoadFromFileDelegate(this); + var fileReader = this._createFileReader(file, delegate); + fileReader.start(this._receiver); + }, + + _createFileReader: function(file, delegate) + { + return new WebInspector.ChunkedFileReader(file, 10000000, delegate); + }, + + __proto__: WebInspector.ProfileHeader.prototype +} + +/** + * @constructor + * @implements {WebInspector.OutputStreamDelegate} + */ +WebInspector.HeapSnapshotLoadFromFileDelegate = function(snapshotHeader) +{ + this._snapshotHeader = snapshotHeader; +} + +WebInspector.HeapSnapshotLoadFromFileDelegate.prototype = { + onTransferStarted: function() + { + }, + + /** + * @param {!WebInspector.ChunkedReader} reader + */ + onChunkTransferred: function(reader) + { + }, + + onTransferFinished: function() + { + }, + + /** + * @param {!WebInspector.ChunkedReader} reader + * @param {?Event} e + */ + onError: function (reader, e) + { + var subtitle; + switch(e.target.error.code) { + case e.target.error.NOT_FOUND_ERR: + subtitle = WebInspector.UIString("'%s' not found.", reader.fileName()); + break; + case e.target.error.NOT_READABLE_ERR: + subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName()); + break; + case e.target.error.ABORT_ERR: + return; + default: + subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code); + } + this._snapshotHeader.updateStatus(subtitle); + } +} + +/** + * @constructor + * @implements {WebInspector.OutputStreamDelegate} + * @param {!WebInspector.HeapProfileHeader} profileHeader + */ +WebInspector.SaveSnapshotOutputStreamDelegate = function(profileHeader) +{ + this._profileHeader = profileHeader; +} + +WebInspector.SaveSnapshotOutputStreamDelegate.prototype = { + onTransferStarted: function() + { + this._profileHeader._updateSaveProgress(0, 1); + }, + + onTransferFinished: function() + { + this._profileHeader._didCompleteSnapshotTransfer(); + }, + + /** + * @param {!WebInspector.ChunkedReader} reader + */ + onChunkTransferred: function(reader) + { + this._profileHeader._updateSaveProgress(reader.loadedSize(), reader.fileSize()); + }, + + /** + * @param {!WebInspector.ChunkedReader} reader + * @param {?Event} event + */ + onError: function(reader, event) + { + WebInspector.messageSink.addErrorMessage("Failed to read heap snapshot from temp file: " + /** @type {!ErrorEvent} */ (event).message); + this.onTransferFinished(); + } +} + +/** + * @constructor + * @extends {WebInspector.VBox} + * @param {!WebInspector.HeapProfileHeader} heapProfileHeader + */ +WebInspector.HeapTrackingOverviewGrid = function(heapProfileHeader) +{ + WebInspector.VBox.call(this); + this.registerRequiredCSS("flameChart.css"); + this.element.id = "heap-recording-view"; + this.element.classList.add("heap-tracking-overview"); + + this._overviewContainer = this.element.createChild("div", "overview-container"); + this._overviewGrid = new WebInspector.OverviewGrid("heap-recording"); + this._overviewGrid.element.classList.add("fill"); + + this._overviewCanvas = this._overviewContainer.createChild("canvas", "heap-recording-overview-canvas"); + this._overviewContainer.appendChild(this._overviewGrid.element); + this._overviewCalculator = new WebInspector.HeapTrackingOverviewGrid.OverviewCalculator(); + this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); + + this._profileSamples = heapProfileHeader._profileSamples; + if (heapProfileHeader.profileType().profileBeingRecorded() === heapProfileHeader) { + this._profileType = heapProfileHeader._profileType; + this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this); + this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this); + } + var timestamps = this._profileSamples.timestamps; + var totalTime = this._profileSamples.totalTime; + this._windowLeft = 0.0; + this._windowRight = totalTime && timestamps.length ? (timestamps[timestamps.length - 1] - timestamps[0]) / totalTime : 1.0; + this._overviewGrid.setWindow(this._windowLeft, this._windowRight); + this._yScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale(); + this._xScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale(); +} + +WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged = "IdsRangeChanged"; + +WebInspector.HeapTrackingOverviewGrid.prototype = { + _onStopTracking: function(event) + { + this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this); + this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this); + }, + + _onHeapStatsUpdate: function(event) + { + this._profileSamples = event.data; + this._scheduleUpdate(); + }, + + /** + * @param {number} width + * @param {number} height + */ + _drawOverviewCanvas: function(width, height) + { + if (!this._profileSamples) + return; + var profileSamples = this._profileSamples; + var sizes = profileSamples.sizes; + var topSizes = profileSamples.max; + var timestamps = profileSamples.timestamps; + var startTime = timestamps[0]; + var endTime = timestamps[timestamps.length - 1]; + + var scaleFactor = this._xScale.nextScale(width / profileSamples.totalTime); + var maxSize = 0; + /** + * @param {!Array.<number>} sizes + * @param {function(number, number):void} callback + */ + function aggregateAndCall(sizes, callback) + { + var size = 0; + var currentX = 0; + for (var i = 1; i < timestamps.length; ++i) { + var x = Math.floor((timestamps[i] - startTime) * scaleFactor); + if (x !== currentX) { + if (size) + callback(currentX, size); + size = 0; + currentX = x; + } + size += sizes[i]; + } + callback(currentX, size); + } + + /** + * @param {number} x + * @param {number} size + */ + function maxSizeCallback(x, size) + { + maxSize = Math.max(maxSize, size); + } + + aggregateAndCall(sizes, maxSizeCallback); + + var yScaleFactor = this._yScale.nextScale(maxSize ? height / (maxSize * 1.1) : 0.0); + + this._overviewCanvas.width = width * window.devicePixelRatio; + this._overviewCanvas.height = height * window.devicePixelRatio; + this._overviewCanvas.style.width = width + "px"; + this._overviewCanvas.style.height = height + "px"; + + var context = this._overviewCanvas.getContext("2d"); + context.scale(window.devicePixelRatio, window.devicePixelRatio); + + context.beginPath(); + context.lineWidth = 2; + context.strokeStyle = "rgba(192, 192, 192, 0.6)"; + var currentX = (endTime - startTime) * scaleFactor; + context.moveTo(currentX, height - 1); + context.lineTo(currentX, 0); + context.stroke(); + context.closePath(); + + var gridY; + var gridValue; + var gridLabelHeight = 14; + if (yScaleFactor) { + const maxGridValue = (height - gridLabelHeight) / yScaleFactor; + // The round value calculation is a bit tricky, because + // it has a form k*10^n*1024^m, where k=[1,5], n=[0..3], m is an integer, + // e.g. a round value 10KB is 10240 bytes. + gridValue = Math.pow(1024, Math.floor(Math.log(maxGridValue) / Math.log(1024))); + gridValue *= Math.pow(10, Math.floor(Math.log(maxGridValue / gridValue) / Math.LN10)); + if (gridValue * 5 <= maxGridValue) + gridValue *= 5; + gridY = Math.round(height - gridValue * yScaleFactor - 0.5) + 0.5; + context.beginPath(); + context.lineWidth = 1; + context.strokeStyle = "rgba(0, 0, 0, 0.2)"; + context.moveTo(0, gridY); + context.lineTo(width, gridY); + context.stroke(); + context.closePath(); + } + + /** + * @param {number} x + * @param {number} size + */ + function drawBarCallback(x, size) + { + context.moveTo(x, height - 1); + context.lineTo(x, Math.round(height - size * yScaleFactor - 1)); + } + + context.beginPath(); + context.lineWidth = 2; + context.strokeStyle = "rgba(192, 192, 192, 0.6)"; + aggregateAndCall(topSizes, drawBarCallback); + context.stroke(); + context.closePath(); + + context.beginPath(); + context.lineWidth = 2; + context.strokeStyle = "rgba(0, 0, 192, 0.8)"; + aggregateAndCall(sizes, drawBarCallback); + context.stroke(); + context.closePath(); + + if (gridValue) { + var label = Number.bytesToString(gridValue); + var labelPadding = 4; + var labelX = 0; + var labelY = gridY - 0.5; + var labelWidth = 2 * labelPadding + context.measureText(label).width; + context.beginPath(); + context.textBaseline = "bottom"; + context.font = "10px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family"); + context.fillStyle = "rgba(255, 255, 255, 0.75)"; + context.fillRect(labelX, labelY - gridLabelHeight, labelWidth, gridLabelHeight); + context.fillStyle = "rgb(64, 64, 64)"; + context.fillText(label, labelX + labelPadding, labelY); + context.fill(); + context.closePath(); + } + }, + + onResize: function() + { + this._updateOverviewCanvas = true; + this._scheduleUpdate(); + }, + + _onWindowChanged: function() + { + if (!this._updateGridTimerId) + this._updateGridTimerId = setTimeout(this._updateGrid.bind(this), 10); + }, + + _scheduleUpdate: function() + { + if (this._updateTimerId) + return; + this._updateTimerId = setTimeout(this.update.bind(this), 10); + }, + + _updateBoundaries: function() + { + this._windowLeft = this._overviewGrid.windowLeft(); + this._windowRight = this._overviewGrid.windowRight(); + this._windowWidth = this._windowRight - this._windowLeft; + }, + + update: function() + { + this._updateTimerId = null; + if (!this.isShowing()) + return; + this._updateBoundaries(); + this._overviewCalculator._updateBoundaries(this); + this._overviewGrid.updateDividers(this._overviewCalculator); + this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20); + }, + + _updateGrid: function() + { + this._updateGridTimerId = 0; + this._updateBoundaries(); + var ids = this._profileSamples.ids; + var timestamps = this._profileSamples.timestamps; + var sizes = this._profileSamples.sizes; + var startTime = timestamps[0]; + var totalTime = this._profileSamples.totalTime; + var timeLeft = startTime + totalTime * this._windowLeft; + var timeRight = startTime + totalTime * this._windowRight; + var minId = 0; + var maxId = ids[ids.length - 1] + 1; + var size = 0; + for (var i = 0; i < timestamps.length; ++i) { + if (!timestamps[i]) + continue; + if (timestamps[i] > timeRight) + break; + maxId = ids[i]; + if (timestamps[i] < timeLeft) { + minId = ids[i]; + continue; + } + size += sizes[i]; + } + + this.dispatchEventToListeners(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, {minId: minId, maxId: maxId, size: size}); + }, + + __proto__: WebInspector.VBox.prototype +} + + +/** + * @constructor + */ +WebInspector.HeapTrackingOverviewGrid.SmoothScale = function() +{ + this._lastUpdate = 0; + this._currentScale = 0.0; +} + +WebInspector.HeapTrackingOverviewGrid.SmoothScale.prototype = { + /** + * @param {number} target + * @return {number} + */ + nextScale: function(target) { + target = target || this._currentScale; + if (this._currentScale) { + var now = Date.now(); + var timeDeltaMs = now - this._lastUpdate; + this._lastUpdate = now; + var maxChangePerSec = 20; + var maxChangePerDelta = Math.pow(maxChangePerSec, timeDeltaMs / 1000); + var scaleChange = target / this._currentScale; + this._currentScale *= Number.constrain(scaleChange, 1 / maxChangePerDelta, maxChangePerDelta); + } else + this._currentScale = target; + return this._currentScale; + } +} + + +/** + * @constructor + * @implements {WebInspector.TimelineGrid.Calculator} + */ +WebInspector.HeapTrackingOverviewGrid.OverviewCalculator = function() +{ +} + +WebInspector.HeapTrackingOverviewGrid.OverviewCalculator.prototype = { + /** + * @return {number} + */ + paddingLeft: function() + { + return 0; + }, + + /** + * @param {!WebInspector.HeapTrackingOverviewGrid} chart + */ + _updateBoundaries: function(chart) + { + this._minimumBoundaries = 0; + this._maximumBoundaries = chart._profileSamples.totalTime; + this._xScaleFactor = chart._overviewContainer.clientWidth / this._maximumBoundaries; + }, + + /** + * @param {number} time + * @return {number} + */ + computePosition: function(time) + { + return (time - this._minimumBoundaries) * this._xScaleFactor; + }, + + /** + * @param {number} value + * @param {number=} precision + * @return {string} + */ + formatTime: function(value, precision) + { + return Number.secondsToString(value / 1000, !!precision); + }, + + /** + * @return {number} + */ + maximumBoundary: function() + { + return this._maximumBoundaries; + }, + + /** + * @return {number} + */ + minimumBoundary: function() + { + return this._minimumBoundaries; + }, + + /** + * @return {number} + */ + zeroTime: function() + { + return this._minimumBoundaries; + }, + + /** + * @return {number} + */ + boundarySpan: function() + { + return this._maximumBoundaries - this._minimumBoundaries; + } +} + + +/** + * @constructor + * @extends {WebInspector.VBox} + */ +WebInspector.HeapSnapshotStatisticsView = function() +{ + WebInspector.VBox.call(this); + this.setMinimumSize(50, 25); + this._pieChart = new WebInspector.PieChart(); + this._pieChart.setSize(150); + this.element.appendChild(this._pieChart.element); + this._labels = this.element.createChild("div", "heap-snapshot-stats-legend"); +} + +WebInspector.HeapSnapshotStatisticsView.prototype = { + /** + * @param {number} value + */ + setTotal: function(value) + { + this._pieChart.setTotal(value); + }, + + /** + * @param {number} value + * @param {string} name + * @param {string=} color + */ + addRecord: function(value, name, color) + { + if (color) + this._pieChart.addSlice(value, color); + + var node = this._labels.createChild("div"); + var swatchDiv = node.createChild("div", "heap-snapshot-stats-swatch"); + var nameDiv = node.createChild("div", "heap-snapshot-stats-name"); + var sizeDiv = node.createChild("div", "heap-snapshot-stats-size"); + if (color) + swatchDiv.style.backgroundColor = color; + else + swatchDiv.classList.add("heap-snapshot-stats-empty-swatch"); + nameDiv.textContent = name; + sizeDiv.textContent = WebInspector.UIString("%s KB", Number.withThousandsSeparator(Math.round(value / 1024))); + }, + + __proto__: WebInspector.VBox.prototype +} + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.HeapAllocationStackView = function() +{ + WebInspector.View.call(this); + this._target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); + this._linkifier = new WebInspector.Linkifier(); +} + +WebInspector.HeapAllocationStackView.prototype = { + /** + * @param {!WebInspector.HeapSnapshotProxy} snapshot + * @param {number} snapshotNodeIndex + */ + setAllocatedObject: function(snapshot, snapshotNodeIndex) + { + this.clear(); + snapshot.allocationStack(snapshotNodeIndex, this._didReceiveAllocationStack.bind(this)); + }, + + clear: function() + { + this.element.removeChildren(); + this._linkifier.reset(); + }, + + /** + * @param {?Array.<!WebInspector.HeapSnapshotCommon.AllocationStackFrame>} frames + */ + _didReceiveAllocationStack: function(frames) + { + if (!frames) { + var stackDiv = this.element.createChild("div", "no-heap-allocation-stack"); + stackDiv.createTextChild(WebInspector.UIString("Stack was not recorded for this object because it had been allocated before this profile recording started.")); + return; + } + + var stackDiv = this.element.createChild("div", "heap-allocation-stack"); + for (var i = 0; i < frames.length; i++) { + var frame = frames[i]; + var frameDiv = stackDiv.createChild("div", "stack-frame"); + var name = frameDiv.createChild("div"); + name.textContent = frame.functionName; + if (frame.scriptId) { + var urlElement; + var rawLocation = new WebInspector.DebuggerModel.Location(this._target, String(frame.scriptId), frame.line - 1, frame.column - 1); + if (rawLocation) + urlElement = this._linkifier.linkifyRawLocation(rawLocation); + if (!urlElement) + urlElement = this._linkifier.linkifyLocation(this._target, frame.scriptName, frame.line - 1, frame.column - 1); + frameDiv.appendChild(urlElement); + } + } + }, + + __proto__: WebInspector.View.prototype +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/ProfileLauncherView.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/ProfileLauncherView.js new file mode 100644 index 00000000000..a36592873db --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/ProfileLauncherView.js @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2011 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. + */ + +/** + * @constructor + * @extends {WebInspector.VBox} + * @param {!WebInspector.ProfilesPanel} profilesPanel + */ +WebInspector.ProfileLauncherView = function(profilesPanel) +{ + WebInspector.VBox.call(this); + + this._panel = profilesPanel; + + this.element.classList.add("profile-launcher-view"); + this.element.classList.add("panel-enabler-view"); + + this._contentElement = this.element.createChild("div", "profile-launcher-view-content"); + this._innerContentElement = this._contentElement.createChild("div"); + + this._controlButton = this._contentElement.createChild("button", "control-profiling"); + this._controlButton.addEventListener("click", this._controlButtonClicked.bind(this), false); + this._recordButtonEnabled = true; + + this._loadButton = this._contentElement.createChild("button", "load-profile"); + this._loadButton.textContent = WebInspector.UIString("Load"); + this._loadButton.addEventListener("click", this._loadButtonClicked.bind(this), false); +} + +WebInspector.ProfileLauncherView.prototype = { + /** + * @param {!WebInspector.ProfileType} profileType + */ + addProfileType: function(profileType) + { + var descriptionElement = this._innerContentElement.createChild("h1"); + descriptionElement.textContent = profileType.description; + var decorationElement = profileType.decorationElement(); + if (decorationElement) + this._innerContentElement.appendChild(decorationElement); + this._isInstantProfile = profileType.isInstantProfile(); + this._isEnabled = profileType.isEnabled(); + this._profileTypeId = profileType.id; + }, + + _controlButtonClicked: function() + { + this._panel.toggleRecordButton(); + }, + + _loadButtonClicked: function() + { + this._panel.showLoadFromFileDialog(); + }, + + _updateControls: function() + { + if (this._isEnabled && this._recordButtonEnabled) + this._controlButton.removeAttribute("disabled"); + else + this._controlButton.setAttribute("disabled", ""); + this._controlButton.title = this._recordButtonEnabled ? "" : WebInspector.UIString("Another profiler is already active"); + if (this._isInstantProfile) { + this._controlButton.classList.remove("running"); + this._controlButton.textContent = WebInspector.UIString("Take Snapshot"); + } else if (this._isProfiling) { + this._controlButton.classList.add("running"); + this._controlButton.textContent = WebInspector.UIString("Stop"); + } else { + this._controlButton.classList.remove("running"); + this._controlButton.textContent = WebInspector.UIString("Start"); + } + }, + + profileStarted: function() + { + this._isProfiling = true; + this._updateControls(); + }, + + profileFinished: function() + { + this._isProfiling = false; + this._updateControls(); + }, + + /** + * @param {!WebInspector.ProfileType} profileType + * @param {boolean} recordButtonEnabled + */ + updateProfileType: function(profileType, recordButtonEnabled) + { + this._isInstantProfile = profileType.isInstantProfile(); + this._recordButtonEnabled = recordButtonEnabled; + this._isEnabled = profileType.isEnabled(); + this._profileTypeId = profileType.id; + this._updateControls(); + }, + + __proto__: WebInspector.VBox.prototype +} + + +/** + * @constructor + * @extends {WebInspector.ProfileLauncherView} + * @param {!WebInspector.ProfilesPanel} profilesPanel + */ +WebInspector.MultiProfileLauncherView = function(profilesPanel) +{ + WebInspector.ProfileLauncherView.call(this, profilesPanel); + + WebInspector.settings.selectedProfileType = WebInspector.settings.createSetting("selectedProfileType", "CPU"); + + var header = this._innerContentElement.createChild("h1"); + header.textContent = WebInspector.UIString("Select profiling type"); + + this._profileTypeSelectorForm = this._innerContentElement.createChild("form"); + + this._innerContentElement.createChild("div", "flexible-space"); + + this._typeIdToOptionElement = {}; +} + +WebInspector.MultiProfileLauncherView.EventTypes = { + ProfileTypeSelected: "profile-type-selected" +} + +WebInspector.MultiProfileLauncherView.prototype = { + /** + * @override + * @param {!WebInspector.ProfileType} profileType + */ + addProfileType: function(profileType) + { + var labelElement = this._profileTypeSelectorForm.createChild("label"); + labelElement.textContent = profileType.name; + var optionElement = document.createElement("input"); + labelElement.insertBefore(optionElement, labelElement.firstChild); + this._typeIdToOptionElement[profileType.id] = optionElement; + optionElement._profileType = profileType; + optionElement.type = "radio"; + optionElement.name = "profile-type"; + optionElement.style.hidden = true; + optionElement.addEventListener("change", this._profileTypeChanged.bind(this, profileType), false); + var descriptionElement = labelElement.createChild("p"); + descriptionElement.textContent = profileType.description; + var decorationElement = profileType.decorationElement(); + if (decorationElement) + labelElement.appendChild(decorationElement); + }, + + restoreSelectedProfileType: function() + { + var typeId = WebInspector.settings.selectedProfileType.get(); + if (!(typeId in this._typeIdToOptionElement)) + typeId = Object.keys(this._typeIdToOptionElement)[0]; + this._typeIdToOptionElement[typeId].checked = true; + var type = this._typeIdToOptionElement[typeId]._profileType; + this.dispatchEventToListeners(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, type); + }, + + _controlButtonClicked: function() + { + this._panel.toggleRecordButton(); + }, + + _updateControls: function() + { + WebInspector.ProfileLauncherView.prototype._updateControls.call(this); + var items = this._profileTypeSelectorForm.elements; + for (var i = 0; i < items.length; ++i) { + if (items[i].type === "radio") + items[i].disabled = this._isProfiling; + } + }, + + /** + * @param {!WebInspector.ProfileType} profileType + */ + _profileTypeChanged: function(profileType) + { + this.dispatchEventToListeners(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, profileType); + this._isInstantProfile = profileType.isInstantProfile(); + this._isEnabled = profileType.isEnabled(); + this._profileTypeId = profileType.id; + this._updateControls(); + WebInspector.settings.selectedProfileType.set(profileType.id); + }, + + profileStarted: function() + { + this._isProfiling = true; + this._updateControls(); + }, + + profileFinished: function() + { + this._isProfiling = false; + this._updateControls(); + }, + + __proto__: WebInspector.ProfileLauncherView.prototype +} + diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/ProfilesPanel.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/ProfilesPanel.js new file mode 100644 index 00000000000..4e00a2ca50b --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/ProfilesPanel.js @@ -0,0 +1,1350 @@ +/* + * Copyright (C) 2008 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + * @param {string} id + * @param {string} name + */ +WebInspector.ProfileType = function(id, name) +{ + WebInspector.Object.call(this); + this._id = id; + this._name = name; + /** @type {!Array.<!WebInspector.ProfileHeader>} */ + this._profiles = []; + /** @type {?WebInspector.ProfileHeader} */ + this._profileBeingRecorded = null; + this._nextProfileUid = 1; + + window.addEventListener("unload", this._clearTempStorage.bind(this), false); +} + +/** + * @enum {string} + */ +WebInspector.ProfileType.Events = { + AddProfileHeader: "add-profile-header", + ProfileComplete: "profile-complete", + RemoveProfileHeader: "remove-profile-header", + ViewUpdated: "view-updated" +} + +WebInspector.ProfileType.prototype = { + /** + * @return {boolean} + */ + hasTemporaryView: function() + { + return false; + }, + + /** + * @return {?string} + */ + fileExtension: function() + { + return null; + }, + + get statusBarItems() + { + return []; + }, + + get buttonTooltip() + { + return ""; + }, + + get id() + { + return this._id; + }, + + get treeItemTitle() + { + return this._name; + }, + + get name() + { + return this._name; + }, + + /** + * @return {boolean} + */ + buttonClicked: function() + { + return false; + }, + + get description() + { + return ""; + }, + + /** + * @return {boolean} + */ + isInstantProfile: function() + { + return false; + }, + + /** + * @return {boolean} + */ + isEnabled: function() + { + return true; + }, + + /** + * @return {!Array.<!WebInspector.ProfileHeader>} + */ + getProfiles: function() + { + /** + * @param {!WebInspector.ProfileHeader} profile + * @return {boolean} + * @this {WebInspector.ProfileType} + */ + function isFinished(profile) + { + return this._profileBeingRecorded !== profile; + } + return this._profiles.filter(isFinished.bind(this)); + }, + + /** + * @return {?Element} + */ + decorationElement: function() + { + return null; + }, + + /** + * @nosideeffects + * @param {number} uid + * @return {?WebInspector.ProfileHeader} + */ + getProfile: function(uid) + { + + for (var i = 0; i < this._profiles.length; ++i) { + if (this._profiles[i].uid === uid) + return this._profiles[i]; + } + return null; + }, + + /** + * @param {!File} file + */ + loadFromFile: function(file) + { + var name = file.name; + if (name.endsWith(this.fileExtension())) + name = name.substr(0, name.length - this.fileExtension().length); + var profile = this.createProfileLoadedFromFile(name); + profile.setFromFile(); + this.setProfileBeingRecorded(profile); + this.addProfile(profile); + profile.loadFromFile(file); + }, + + /** + * @param {string} title + * @return {!WebInspector.ProfileHeader} + */ + createProfileLoadedFromFile: function(title) + { + throw new Error("Needs implemented."); + }, + + /** + * @param {!WebInspector.ProfileHeader} profile + */ + addProfile: function(profile) + { + this._profiles.push(profile); + this.dispatchEventToListeners(WebInspector.ProfileType.Events.AddProfileHeader, profile); + }, + + /** + * @param {!WebInspector.ProfileHeader} profile + */ + removeProfile: function(profile) + { + var index = this._profiles.indexOf(profile); + if (index === -1) + return; + this._profiles.splice(index, 1); + this._disposeProfile(profile); + }, + + _clearTempStorage: function() + { + for (var i = 0; i < this._profiles.length; ++i) + this._profiles[i].removeTempFile(); + }, + + /** + * @nosideeffects + * @return {?WebInspector.ProfileHeader} + */ + profileBeingRecorded: function() + { + return this._profileBeingRecorded; + }, + + /** + * @param {?WebInspector.ProfileHeader} profile + */ + setProfileBeingRecorded: function(profile) + { + if (this._profileBeingRecorded) + this._profileBeingRecorded.target().profilingLock.release(); + if (profile) + profile.target().profilingLock.acquire(); + this._profileBeingRecorded = profile; + }, + + profileBeingRecordedRemoved: function() + { + }, + + _reset: function() + { + var profiles = this._profiles.slice(0); + for (var i = 0; i < profiles.length; ++i) + this._disposeProfile(profiles[i]); + this._profiles = []; + + this._nextProfileUid = 1; + }, + + /** + * @param {!WebInspector.ProfileHeader} profile + */ + _disposeProfile: function(profile) + { + this.dispatchEventToListeners(WebInspector.ProfileType.Events.RemoveProfileHeader, profile); + profile.dispose(); + if (this._profileBeingRecorded === profile) { + this.profileBeingRecordedRemoved(); + this.setProfileBeingRecorded(null); + } + }, + + __proto__: WebInspector.Object.prototype +} + +/** + * @interface + */ +WebInspector.ProfileType.DataDisplayDelegate = function() +{ +} + +WebInspector.ProfileType.DataDisplayDelegate.prototype = { + /** + * @param {?WebInspector.ProfileHeader} profile + * @return {?WebInspector.View} + */ + showProfile: function(profile) { }, + + /** + * @param {!HeapProfilerAgent.HeapSnapshotObjectId} snapshotObjectId + * @param {string} perspectiveName + */ + showObject: function(snapshotObjectId, perspectiveName) { } +} + +/** + * @constructor + * @extends {WebInspector.TargetAwareObject} + * @param {!WebInspector.Target} target + * @param {!WebInspector.ProfileType} profileType + * @param {string} title + */ +WebInspector.ProfileHeader = function(target, profileType, title) +{ + WebInspector.TargetAwareObject.call(this, target); + this._profileType = profileType; + this.title = title; + this.uid = profileType._nextProfileUid++; + this._fromFile = false; +} + +/** + * @constructor + * @param {?string} subtitle + * @param {boolean|undefined} wait + */ +WebInspector.ProfileHeader.StatusUpdate = function(subtitle, wait) +{ + /** @type {?string} */ + this.subtitle = subtitle; + /** @type {boolean|undefined} */ + this.wait = wait; +} + +WebInspector.ProfileHeader.Events = { + UpdateStatus: "UpdateStatus", + ProfileReceived: "ProfileReceived" +} + +WebInspector.ProfileHeader.prototype = { + /** + * @return {!WebInspector.ProfileType} + */ + profileType: function() + { + return this._profileType; + }, + + /** + * @param {?string} subtitle + * @param {boolean=} wait + */ + updateStatus: function(subtitle, wait) + { + this.dispatchEventToListeners(WebInspector.ProfileHeader.Events.UpdateStatus, new WebInspector.ProfileHeader.StatusUpdate(subtitle, wait)); + }, + + /** + * Must be implemented by subclasses. + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + * @return {!WebInspector.ProfileSidebarTreeElement} + */ + createSidebarTreeElement: function(dataDisplayDelegate) + { + throw new Error("Needs implemented."); + }, + + /** + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + * @return {!WebInspector.View} + */ + createView: function(dataDisplayDelegate) + { + throw new Error("Not implemented."); + }, + + removeTempFile: function() + { + if (this._tempFile) + this._tempFile.remove(); + }, + + dispose: function() + { + }, + + /** + * @param {!Function} callback + */ + load: function(callback) + { + }, + + /** + * @return {boolean} + */ + canSaveToFile: function() + { + return false; + }, + + saveToFile: function() + { + throw new Error("Needs implemented"); + }, + + /** + * @param {!File} file + */ + loadFromFile: function(file) + { + throw new Error("Needs implemented"); + }, + + /** + * @return {boolean} + */ + fromFile: function() + { + return this._fromFile; + }, + + setFromFile: function() + { + this._fromFile = true; + }, + + __proto__: WebInspector.TargetAwareObject.prototype +} + +/** + * @constructor + * @implements {WebInspector.Searchable} + * @implements {WebInspector.ProfileType.DataDisplayDelegate} + * @extends {WebInspector.PanelWithSidebarTree} + */ +WebInspector.ProfilesPanel = function() +{ + WebInspector.PanelWithSidebarTree.call(this, "profiles"); + this.registerRequiredCSS("panelEnablerView.css"); + this.registerRequiredCSS("heapProfiler.css"); + this.registerRequiredCSS("profilesPanel.css"); + + this._target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()); + this._target.profilingLock.addEventListener(WebInspector.Lock.Events.StateChanged, this._onProfilingStateChanged, this); + + this._searchableView = new WebInspector.SearchableView(this); + + var mainView = new WebInspector.VBox(); + this._searchableView.show(mainView.element); + mainView.show(this.mainElement()); + + this.profilesItemTreeElement = new WebInspector.ProfilesSidebarTreeElement(this); + this.sidebarTree.appendChild(this.profilesItemTreeElement); + + this.profileViews = document.createElement("div"); + this.profileViews.id = "profile-views"; + this.profileViews.classList.add("vbox"); + this._searchableView.element.appendChild(this.profileViews); + + var statusBarContainer = document.createElementWithClass("div", "profiles-status-bar"); + mainView.element.insertBefore(statusBarContainer, mainView.element.firstChild); + this._statusBarElement = statusBarContainer.createChild("div", "status-bar"); + + this.sidebarElement().classList.add("profiles-sidebar-tree-box"); + var statusBarContainerLeft = document.createElementWithClass("div", "profiles-status-bar"); + this.sidebarElement().insertBefore(statusBarContainerLeft, this.sidebarElement().firstChild); + this._statusBarButtons = statusBarContainerLeft.createChild("div", "status-bar"); + + this.recordButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item"); + this.recordButton.addEventListener("click", this.toggleRecordButton, this); + this._statusBarButtons.appendChild(this.recordButton.element); + + this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear all profiles."), "clear-status-bar-item"); + this.clearResultsButton.addEventListener("click", this._reset, this); + this._statusBarButtons.appendChild(this.clearResultsButton.element); + + this._profileTypeStatusBarItemsContainer = this._statusBarElement.createChild("div"); + this._profileViewStatusBarItemsContainer = this._statusBarElement.createChild("div"); + + this._profileGroups = {}; + this._launcherView = new WebInspector.MultiProfileLauncherView(this); + this._launcherView.addEventListener(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, this._onProfileTypeSelected, this); + + this._profileToView = []; + this._typeIdToSidebarSection = {}; + var types = WebInspector.ProfileTypeRegistry.instance.profileTypes(); + for (var i = 0; i < types.length; i++) + this._registerProfileType(types[i]); + this._launcherView.restoreSelectedProfileType(); + this.profilesItemTreeElement.select(); + this._showLauncherView(); + + this._createFileSelectorElement(); + this.element.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); + this._registerShortcuts(); + + this._configureCpuProfilerSamplingInterval(); + WebInspector.settings.highResolutionCpuProfiling.addChangeListener(this._configureCpuProfilerSamplingInterval, this); +} + + +/** + * @constructor + */ +WebInspector.ProfileTypeRegistry = function() { + this._profileTypes = []; + + this.cpuProfileType = new WebInspector.CPUProfileType(); + this._addProfileType(this.cpuProfileType); + this.heapSnapshotProfileType = new WebInspector.HeapSnapshotProfileType(); + this._addProfileType(this.heapSnapshotProfileType); + this.trackingHeapSnapshotProfileType = new WebInspector.TrackingHeapSnapshotProfileType(); + this._addProfileType(this.trackingHeapSnapshotProfileType); + HeapProfilerAgent.enable(); + + if (Capabilities.isMainFrontend && WebInspector.experimentsSettings.canvasInspection.isEnabled()) { + this.canvasProfileType = new WebInspector.CanvasProfileType(); + this._addProfileType(this.canvasProfileType); + } +} + +WebInspector.ProfileTypeRegistry.prototype = { + /** + * @param {!WebInspector.ProfileType} profileType + */ + _addProfileType: function(profileType) + { + this._profileTypes.push(profileType); + }, + + /** + * @return {!Array.<!WebInspector.ProfileType>} + */ + profileTypes: function() + { + return this._profileTypes; + } +} + + + +WebInspector.ProfilesPanel.prototype = { + /** + * @return {!WebInspector.SearchableView} + */ + searchableView: function() + { + return this._searchableView; + }, + + _createFileSelectorElement: function() + { + if (this._fileSelectorElement) + this.element.removeChild(this._fileSelectorElement); + this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this)); + this.element.appendChild(this._fileSelectorElement); + }, + + _findProfileTypeByExtension: function(fileName) + { + var types = WebInspector.ProfileTypeRegistry.instance.profileTypes(); + for (var i = 0; i < types.length; i++) { + var type = types[i]; + var extension = type.fileExtension(); + if (!extension) + continue; + if (fileName.endsWith(type.fileExtension())) + return type; + } + return null; + }, + + _registerShortcuts: function() + { + this.registerShortcuts(WebInspector.ShortcutsScreen.ProfilesPanelShortcuts.StartStopRecording, this.toggleRecordButton.bind(this)); + }, + + _configureCpuProfilerSamplingInterval: function() + { + var intervalUs = WebInspector.settings.highResolutionCpuProfiling.get() ? 100 : 1000; + ProfilerAgent.setSamplingInterval(intervalUs, didChangeInterval); + function didChangeInterval(error) + { + if (error) + WebInspector.messageSink.addErrorMessage(error, true); + } + }, + + /** + * @param {!File} file + */ + _loadFromFile: function(file) + { + this._createFileSelectorElement(); + + var profileType = this._findProfileTypeByExtension(file.name); + if (!profileType) { + var extensions = []; + var types = WebInspector.ProfileTypeRegistry.instance.profileTypes(); + for (var i = 0; i < types.length; i++) { + var extension = types[i].fileExtension(); + if (!extension || extensions.indexOf(extension) !== -1) + continue; + extensions.push(extension); + } + WebInspector.messageSink.addMessage(WebInspector.UIString("Can't load file. Only files with extensions '%s' can be loaded.", extensions.join("', '"))); + return; + } + + if (!!profileType.profileBeingRecorded()) { + WebInspector.messageSink.addMessage(WebInspector.UIString("Can't load profile while another profile is recording.")); + return; + } + + profileType.loadFromFile(file); + }, + + /** + * @return {boolean} + */ + toggleRecordButton: function() + { + if (!this.recordButton.enabled()) + return true; + var type = this._selectedProfileType; + var isProfiling = type.buttonClicked(); + this._updateRecordButton(isProfiling); + if (isProfiling) { + this._launcherView.profileStarted(); + if (type.hasTemporaryView()) + this.showProfile(type.profileBeingRecorded()); + } else { + this._launcherView.profileFinished(); + } + return true; + }, + + _onProfilingStateChanged: function() + { + this._updateRecordButton(this.recordButton.toggled); + }, + + /** + * @param {boolean} toggled + */ + _updateRecordButton: function(toggled) + { + var enable = toggled || !this._target.profilingLock.isAcquired(); + this.recordButton.setEnabled(enable); + this.recordButton.toggled = toggled; + if (enable) + this.recordButton.title = this._selectedProfileType ? this._selectedProfileType.buttonTooltip : ""; + else + this.recordButton.title = WebInspector.UIString("Another profiler is already active"); + if (this._selectedProfileType) + this._launcherView.updateProfileType(this._selectedProfileType, enable); + }, + + _profileBeingRecordedRemoved: function() + { + this._updateRecordButton(false); + this._launcherView.profileFinished(); + }, + + /** + * @param {!WebInspector.Event} event + */ + _onProfileTypeSelected: function(event) + { + this._selectedProfileType = /** @type {!WebInspector.ProfileType} */ (event.data); + this._updateProfileTypeSpecificUI(); + }, + + _updateProfileTypeSpecificUI: function() + { + this._updateRecordButton(this.recordButton.toggled); + this._profileTypeStatusBarItemsContainer.removeChildren(); + var statusBarItems = this._selectedProfileType.statusBarItems; + if (statusBarItems) { + for (var i = 0; i < statusBarItems.length; ++i) + this._profileTypeStatusBarItemsContainer.appendChild(statusBarItems[i]); + } + }, + + _reset: function() + { + WebInspector.Panel.prototype.reset.call(this); + + var types = WebInspector.ProfileTypeRegistry.instance.profileTypes(); + for (var i = 0; i < types.length; i++) + types[i]._reset(); + + delete this.visibleView; + delete this.currentQuery; + this.searchCanceled(); + + this._profileGroups = {}; + this._updateRecordButton(false); + this._launcherView.profileFinished(); + + this.sidebarTree.element.classList.remove("some-expandable"); + + this._launcherView.detach(); + this.profileViews.removeChildren(); + this._profileViewStatusBarItemsContainer.removeChildren(); + + this.removeAllListeners(); + + this.recordButton.visible = true; + this._profileViewStatusBarItemsContainer.classList.remove("hidden"); + this.clearResultsButton.element.classList.remove("hidden"); + this.profilesItemTreeElement.select(); + this._showLauncherView(); + }, + + _showLauncherView: function() + { + this.closeVisibleView(); + this._profileViewStatusBarItemsContainer.removeChildren(); + this._launcherView.show(this.profileViews); + this.visibleView = this._launcherView; + }, + + _garbageCollectButtonClicked: function() + { + HeapProfilerAgent.collectGarbage(); + }, + + /** + * @param {!WebInspector.ProfileType} profileType + */ + _registerProfileType: function(profileType) + { + this._launcherView.addProfileType(profileType); + var profileTypeSection = new WebInspector.ProfileTypeSidebarSection(this, profileType); + this._typeIdToSidebarSection[profileType.id] = profileTypeSection + this.sidebarTree.appendChild(profileTypeSection); + profileTypeSection.childrenListElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); + + /** + * @param {!WebInspector.Event} event + * @this {WebInspector.ProfilesPanel} + */ + function onAddProfileHeader(event) + { + this._addProfileHeader(/** @type {!WebInspector.ProfileHeader} */ (event.data)); + } + + /** + * @param {!WebInspector.Event} event + * @this {WebInspector.ProfilesPanel} + */ + function onRemoveProfileHeader(event) + { + this._removeProfileHeader(/** @type {!WebInspector.ProfileHeader} */ (event.data)); + } + + /** + * @param {!WebInspector.Event} event + * @this {WebInspector.ProfilesPanel} + */ + function profileComplete(event) + { + this.showProfile(/** @type {!WebInspector.ProfileHeader} */ (event.data)); + } + + profileType.addEventListener(WebInspector.ProfileType.Events.ViewUpdated, this._updateProfileTypeSpecificUI, this); + profileType.addEventListener(WebInspector.ProfileType.Events.AddProfileHeader, onAddProfileHeader, this); + profileType.addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, onRemoveProfileHeader, this); + profileType.addEventListener(WebInspector.ProfileType.Events.ProfileComplete, profileComplete, this); + + var profiles = profileType.getProfiles(); + for (var i = 0; i < profiles.length; i++) + this._addProfileHeader(profiles[i]); + }, + + /** + * @param {?Event} event + */ + _handleContextMenuEvent: function(event) + { + var element = event.srcElement; + while (element && !element.treeElement && element !== this.element) + element = element.parentElement; + if (!element) + return; + if (element.treeElement && element.treeElement.handleContextMenuEvent) { + element.treeElement.handleContextMenuEvent(event, this); + return; + } + + var contextMenu = new WebInspector.ContextMenu(event); + if (this.visibleView instanceof WebInspector.HeapSnapshotView) { + this.visibleView.populateContextMenu(contextMenu, event); + } + if (element !== this.element || event.srcElement === this.sidebarElement()) { + contextMenu.appendItem(WebInspector.UIString("Load\u2026"), this._fileSelectorElement.click.bind(this._fileSelectorElement)); + } + contextMenu.show(); + }, + + showLoadFromFileDialog: function() + { + this._fileSelectorElement.click(); + }, + + /** + * @param {!WebInspector.ProfileHeader} profile + */ + _addProfileHeader: function(profile) + { + var profileType = profile.profileType(); + var typeId = profileType.id; + this._typeIdToSidebarSection[typeId].addProfileHeader(profile); + if (!this.visibleView || this.visibleView === this._launcherView) + this.showProfile(profile); + }, + + /** + * @param {!WebInspector.ProfileHeader} profile + */ + _removeProfileHeader: function(profile) + { + if (profile.profileType()._profileBeingRecorded === profile) + this._profileBeingRecordedRemoved(); + + var i = this._indexOfViewForProfile(profile); + if (i !== -1) + this._profileToView.splice(i, 1); + + var profileType = profile.profileType(); + var typeId = profileType.id; + var sectionIsEmpty = this._typeIdToSidebarSection[typeId].removeProfileHeader(profile); + + // No other item will be selected if there aren't any other profiles, so + // make sure that view gets cleared when the last profile is removed. + if (sectionIsEmpty) { + this.profilesItemTreeElement.select(); + this._showLauncherView(); + } + }, + + /** + * @param {?WebInspector.ProfileHeader} profile + * @return {?WebInspector.View} + */ + showProfile: function(profile) + { + if (!profile || (profile.profileType().profileBeingRecorded() === profile) && !profile.profileType().hasTemporaryView()) + return null; + + var view = this._viewForProfile(profile); + if (view === this.visibleView) + return view; + + this.closeVisibleView(); + + view.show(this.profileViews); + + this.visibleView = view; + + var profileTypeSection = this._typeIdToSidebarSection[profile.profileType().id]; + var sidebarElement = profileTypeSection.sidebarElementForProfile(profile); + sidebarElement.revealAndSelect(); + + this._profileViewStatusBarItemsContainer.removeChildren(); + + var statusBarItems = view.statusBarItems; + if (statusBarItems) + for (var i = 0; i < statusBarItems.length; ++i) + this._profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]); + + return view; + }, + + /** + * @param {!HeapProfilerAgent.HeapSnapshotObjectId} snapshotObjectId + * @param {string} perspectiveName + */ + showObject: function(snapshotObjectId, perspectiveName) + { + var heapProfiles = WebInspector.ProfileTypeRegistry.instance.heapSnapshotProfileType.getProfiles(); + for (var i = 0; i < heapProfiles.length; i++) { + var profile = heapProfiles[i]; + // FIXME: allow to choose snapshot if there are several options. + if (profile.maxJSObjectId >= snapshotObjectId) { + this.showProfile(profile); + var view = this._viewForProfile(profile); + view.highlightLiveObject(perspectiveName, snapshotObjectId); + break; + } + } + }, + + /** + * @param {!WebInspector.ProfileHeader} profile + * @return {!WebInspector.View} + */ + _viewForProfile: function(profile) + { + var index = this._indexOfViewForProfile(profile); + if (index !== -1) + return this._profileToView[index].view; + var view = profile.createView(this); + view.element.classList.add("profile-view"); + this._profileToView.push({ profile: profile, view: view}); + return view; + }, + + /** + * @param {!WebInspector.ProfileHeader} profile + * @return {number} + */ + _indexOfViewForProfile: function(profile) + { + for (var i = 0; i < this._profileToView.length; i++) { + if (this._profileToView[i].profile === profile) + return i; + } + return -1; + }, + + closeVisibleView: function() + { + if (this.visibleView) + this.visibleView.detach(); + delete this.visibleView; + }, + + /** + * @param {string} query + * @param {boolean} shouldJump + * @param {boolean=} jumpBackwards + */ + performSearch: function(query, shouldJump, jumpBackwards) + { + this.searchCanceled(); + + var visibleView = this.visibleView; + if (!visibleView) + return; + + /** + * @this {WebInspector.ProfilesPanel} + */ + function finishedCallback(view, searchMatches) + { + if (!searchMatches) + return; + this._searchableView.updateSearchMatchesCount(searchMatches); + this._searchResultsView = view; + if (shouldJump) { + if (jumpBackwards) + view.jumpToLastSearchResult(); + else + view.jumpToFirstSearchResult(); + this._searchableView.updateCurrentMatchIndex(view.currentSearchResultIndex()); + } + } + + visibleView.currentQuery = query; + visibleView.performSearch(query, finishedCallback.bind(this)); + }, + + jumpToNextSearchResult: function() + { + if (!this._searchResultsView) + return; + if (this._searchResultsView !== this.visibleView) + return; + this._searchResultsView.jumpToNextSearchResult(); + this._searchableView.updateCurrentMatchIndex(this._searchResultsView.currentSearchResultIndex()); + }, + + jumpToPreviousSearchResult: function() + { + if (!this._searchResultsView) + return; + if (this._searchResultsView !== this.visibleView) + return; + this._searchResultsView.jumpToPreviousSearchResult(); + this._searchableView.updateCurrentMatchIndex(this._searchResultsView.currentSearchResultIndex()); + }, + + searchCanceled: function() + { + if (this._searchResultsView) { + if (this._searchResultsView.searchCanceled) + this._searchResultsView.searchCanceled(); + this._searchResultsView.currentQuery = null; + this._searchResultsView = null; + } + this._searchableView.updateSearchMatchesCount(0); + }, + + /** + * @param {!Event} event + * @param {!WebInspector.ContextMenu} contextMenu + * @param {!Object} target + */ + appendApplicableItems: function(event, contextMenu, target) + { + if (!(target instanceof WebInspector.RemoteObject)) + return; + + if (WebInspector.inspectorView.currentPanel() !== this) + return; + + var object = /** @type {!WebInspector.RemoteObject} */ (target); + var objectId = object.objectId; + if (!objectId) + return; + + var heapProfiles = WebInspector.ProfileTypeRegistry.instance.heapSnapshotProfileType.getProfiles(); + if (!heapProfiles.length) + return; + + /** + * @this {WebInspector.ProfilesPanel} + */ + function revealInView(viewName) + { + HeapProfilerAgent.getHeapObjectId(objectId, didReceiveHeapObjectId.bind(this, viewName)); + } + + /** + * @this {WebInspector.ProfilesPanel} + */ + function didReceiveHeapObjectId(viewName, error, result) + { + if (WebInspector.inspectorView.currentPanel() !== this) + return; + if (!error) + this.showObject(result, viewName); + } + + if (WebInspector.settings.showAdvancedHeapSnapshotProperties.get()) + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInView.bind(this, "Dominators")); + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInView.bind(this, "Summary")); + }, + + __proto__: WebInspector.PanelWithSidebarTree.prototype +} + + +/** + * @constructor + * @extends {WebInspector.SidebarSectionTreeElement} + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + * @param {!WebInspector.ProfileType} profileType + */ +WebInspector.ProfileTypeSidebarSection = function(dataDisplayDelegate, profileType) +{ + WebInspector.SidebarSectionTreeElement.call(this, profileType.treeItemTitle, null, true); + this._dataDisplayDelegate = dataDisplayDelegate; + this._profileTreeElements = []; + this._profileGroups = {}; + this.hidden = true; +} + +/** + * @constructor + */ +WebInspector.ProfileTypeSidebarSection.ProfileGroup = function() +{ + this.profileSidebarTreeElements = []; + this.sidebarTreeElement = null; +} + +WebInspector.ProfileTypeSidebarSection.prototype = { + /** + * @param {!WebInspector.ProfileHeader} profile + */ + addProfileHeader: function(profile) + { + this.hidden = false; + var profileType = profile.profileType(); + var sidebarParent = this; + var profileTreeElement = profile.createSidebarTreeElement(this._dataDisplayDelegate); + this._profileTreeElements.push(profileTreeElement); + + if (!profile.fromFile() && profileType.profileBeingRecorded() !== profile) { + var profileTitle = profile.title; + var group = this._profileGroups[profileTitle]; + if (!group) { + group = new WebInspector.ProfileTypeSidebarSection.ProfileGroup(); + this._profileGroups[profileTitle] = group; + } + group.profileSidebarTreeElements.push(profileTreeElement); + + var groupSize = group.profileSidebarTreeElements.length; + if (groupSize === 2) { + // Make a group TreeElement now that there are 2 profiles. + group.sidebarTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(this._dataDisplayDelegate, profile.title); + + var firstProfileTreeElement = group.profileSidebarTreeElements[0]; + // Insert at the same index for the first profile of the group. + var index = this.children.indexOf(firstProfileTreeElement); + this.insertChild(group.sidebarTreeElement, index); + + // Move the first profile to the group. + var selected = firstProfileTreeElement.selected; + this.removeChild(firstProfileTreeElement); + group.sidebarTreeElement.appendChild(firstProfileTreeElement); + if (selected) + firstProfileTreeElement.revealAndSelect(); + + firstProfileTreeElement.small = true; + firstProfileTreeElement.mainTitle = WebInspector.UIString("Run %d", 1); + + this.treeOutline.element.classList.add("some-expandable"); + } + + if (groupSize >= 2) { + sidebarParent = group.sidebarTreeElement; + profileTreeElement.small = true; + profileTreeElement.mainTitle = WebInspector.UIString("Run %d", groupSize); + } + } + + sidebarParent.appendChild(profileTreeElement); + }, + + /** + * @param {!WebInspector.ProfileHeader} profile + * @return {boolean} + */ + removeProfileHeader: function(profile) + { + var index = this._sidebarElementIndex(profile); + if (index === -1) + return false; + var profileTreeElement = this._profileTreeElements[index]; + this._profileTreeElements.splice(index, 1); + + var sidebarParent = this; + var group = this._profileGroups[profile.title]; + if (group) { + var groupElements = group.profileSidebarTreeElements; + groupElements.splice(groupElements.indexOf(profileTreeElement), 1); + if (groupElements.length === 1) { + // Move the last profile out of its group and remove the group. + var pos = sidebarParent.children.indexOf(group.sidebarTreeElement); + this.insertChild(groupElements[0], pos); + groupElements[0].small = false; + groupElements[0].mainTitle = group.sidebarTreeElement.title; + this.removeChild(group.sidebarTreeElement); + } + if (groupElements.length !== 0) + sidebarParent = group.sidebarTreeElement; + } + sidebarParent.removeChild(profileTreeElement); + profileTreeElement.dispose(); + + if (this.children.length) + return false; + this.hidden = true; + return true; + }, + + /** + * @param {!WebInspector.ProfileHeader} profile + * @return {?WebInspector.ProfileSidebarTreeElement} + */ + sidebarElementForProfile: function(profile) + { + var index = this._sidebarElementIndex(profile); + return index === -1 ? null : this._profileTreeElements[index]; + }, + + /** + * @param {!WebInspector.ProfileHeader} profile + * @return {number} + */ + _sidebarElementIndex: function(profile) + { + var elements = this._profileTreeElements; + for (var i = 0; i < elements.length; i++) { + if (elements[i].profile === profile) + return i; + } + return -1; + }, + + __proto__: WebInspector.SidebarSectionTreeElement.prototype +} + + +/** + * @constructor + * @implements {WebInspector.ContextMenu.Provider} + */ +WebInspector.ProfilesPanel.ContextMenuProvider = function() +{ +} + +WebInspector.ProfilesPanel.ContextMenuProvider.prototype = { + /** + * @param {!Event} event + * @param {!WebInspector.ContextMenu} contextMenu + * @param {!Object} target + */ + appendApplicableItems: function(event, contextMenu, target) + { + WebInspector.inspectorView.panel("profiles").appendApplicableItems(event, contextMenu, target); + } +} + +/** + * @constructor + * @extends {WebInspector.SidebarTreeElement} + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + * @param {!WebInspector.ProfileHeader} profile + * @param {string} className + */ +WebInspector.ProfileSidebarTreeElement = function(dataDisplayDelegate, profile, className) +{ + this._dataDisplayDelegate = dataDisplayDelegate; + this.profile = profile; + WebInspector.SidebarTreeElement.call(this, className, profile.title, "", profile, false); + this.refreshTitles(); + profile.addEventListener(WebInspector.ProfileHeader.Events.UpdateStatus, this._updateStatus, this); + if (profile.canSaveToFile()) + this._createSaveLink(); + else + profile.addEventListener(WebInspector.ProfileHeader.Events.ProfileReceived, this._onProfileReceived, this); +} + +WebInspector.ProfileSidebarTreeElement.prototype = { + _createSaveLink: function() + { + this._saveLinkElement = this.titleContainer.createChild("span", "save-link"); + this._saveLinkElement.textContent = WebInspector.UIString("Save"); + this._saveLinkElement.addEventListener("click", this._saveProfile.bind(this), false); + }, + + _onProfileReceived: function(event) + { + this._createSaveLink(); + }, + + /** + * @param {!WebInspector.Event} event + */ + _updateStatus: function(event) + { + var statusUpdate = event.data; + if (statusUpdate.subtitle !== null) + this.subtitle = statusUpdate.subtitle; + if (typeof statusUpdate.wait === "boolean") + this.wait = statusUpdate.wait; + this.refreshTitles(); + }, + + dispose: function() + { + this.profile.removeEventListener(WebInspector.ProfileHeader.Events.UpdateStatus, this._updateStatus, this); + this.profile.removeEventListener(WebInspector.ProfileHeader.Events.ProfileReceived, this._onProfileReceived, this); + }, + + onselect: function() + { + this._dataDisplayDelegate.showProfile(this.profile); + }, + + /** + * @return {boolean} + */ + ondelete: function() + { + this.profile.profileType().removeProfile(this.profile); + return true; + }, + + /** + * @param {!Event} event + * @param {!WebInspector.ProfilesPanel} panel + */ + handleContextMenuEvent: function(event, panel) + { + var profile = this.profile; + var contextMenu = new WebInspector.ContextMenu(event); + // FIXME: use context menu provider + contextMenu.appendItem(WebInspector.UIString("Load\u2026"), panel._fileSelectorElement.click.bind(panel._fileSelectorElement)); + if (profile.canSaveToFile()) + contextMenu.appendItem(WebInspector.UIString("Save\u2026"), profile.saveToFile.bind(profile)); + contextMenu.appendItem(WebInspector.UIString("Delete"), this.ondelete.bind(this)); + contextMenu.show(); + }, + + _saveProfile: function(event) + { + this.profile.saveToFile(); + }, + + __proto__: WebInspector.SidebarTreeElement.prototype +} + +/** + * @constructor + * @extends {WebInspector.SidebarTreeElement} + * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate + * @param {string} title + * @param {string=} subtitle + */ +WebInspector.ProfileGroupSidebarTreeElement = function(dataDisplayDelegate, title, subtitle) +{ + WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true); + this._dataDisplayDelegate = dataDisplayDelegate; +} + +WebInspector.ProfileGroupSidebarTreeElement.prototype = { + onselect: function() + { + if (this.children.length > 0) + this._dataDisplayDelegate.showProfile(this.children[this.children.length - 1].profile); + }, + + __proto__: WebInspector.SidebarTreeElement.prototype +} + +/** + * @constructor + * @extends {WebInspector.SidebarTreeElement} + * @param {!WebInspector.ProfilesPanel} panel + */ +WebInspector.ProfilesSidebarTreeElement = function(panel) +{ + this._panel = panel; + this.small = false; + + WebInspector.SidebarTreeElement.call(this, "profile-launcher-view-tree-item", WebInspector.UIString("Profiles"), "", null, false); +} + +WebInspector.ProfilesSidebarTreeElement.prototype = { + onselect: function() + { + this._panel._showLauncherView(); + }, + + get selectable() + { + return true; + }, + + __proto__: WebInspector.SidebarTreeElement.prototype +} + + +importScript("../sdk/CPUProfileModel.js"); +importScript("CPUProfileDataGrid.js"); +importScript("CPUProfileBottomUpDataGrid.js"); +importScript("CPUProfileTopDownDataGrid.js"); +importScript("CPUProfileFlameChart.js"); +importScript("CPUProfileView.js"); +importScript("HeapSnapshotCommon.js"); +importScript("HeapSnapshotProxy.js"); +importScript("HeapSnapshotDataGrids.js"); +importScript("HeapSnapshotGridNodes.js"); +importScript("HeapSnapshotView.js"); +importScript("ProfileLauncherView.js"); +importScript("CanvasProfileView.js"); +importScript("CanvasReplayStateView.js"); + +WebInspector.ProfileTypeRegistry.instance = new WebInspector.ProfileTypeRegistry(); diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/AllocationProfile.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/AllocationProfile.js new file mode 100644 index 00000000000..c6eb7c7ac4a --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/AllocationProfile.js @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2013 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. + */ + +/** + * @constructor + */ +WebInspector.AllocationProfile = function(profile, liveObjectStats) +{ + this._strings = profile.strings; + this._liveObjectStats = liveObjectStats; + + this._nextNodeId = 1; + this._functionInfos = [] + this._idToNode = {}; + this._idToTopDownNode = {}; + this._collapsedTopNodeIdToFunctionInfo = {}; + + this._traceTops = null; + + this._buildFunctionAllocationInfos(profile); + this._traceTree = this._buildAllocationTree(profile, liveObjectStats); +} + +WebInspector.AllocationProfile.prototype = { + _buildFunctionAllocationInfos: function(profile) + { + var strings = this._strings; + + var functionInfoFields = profile.snapshot.meta.trace_function_info_fields; + var functionIdOffset = functionInfoFields.indexOf("function_id"); + var functionNameOffset = functionInfoFields.indexOf("name"); + var scriptNameOffset = functionInfoFields.indexOf("script_name"); + var scriptIdOffset = functionInfoFields.indexOf("script_id"); + var lineOffset = functionInfoFields.indexOf("line"); + var columnOffset = functionInfoFields.indexOf("column"); + var functionInfoFieldCount = functionInfoFields.length; + + var rawInfos = profile.trace_function_infos; + var infoLength = rawInfos.length; + var functionInfos = this._functionInfos = new Array(infoLength / functionInfoFieldCount); + var index = 0; + for (var i = 0; i < infoLength; i += functionInfoFieldCount) { + functionInfos[index++] = new WebInspector.FunctionAllocationInfo( + strings[rawInfos[i + functionNameOffset]], + strings[rawInfos[i + scriptNameOffset]], + rawInfos[i + scriptIdOffset], + rawInfos[i + lineOffset], + rawInfos[i + columnOffset]); + } + }, + + _buildAllocationTree: function(profile, liveObjectStats) + { + var traceTreeRaw = profile.trace_tree; + var functionInfos = this._functionInfos; + var idToTopDownNode = this._idToTopDownNode; + + var traceNodeFields = profile.snapshot.meta.trace_node_fields; + var nodeIdOffset = traceNodeFields.indexOf("id"); + var functionInfoIndexOffset = traceNodeFields.indexOf("function_info_index"); + var allocationCountOffset = traceNodeFields.indexOf("count"); + var allocationSizeOffset = traceNodeFields.indexOf("size"); + var childrenOffset = traceNodeFields.indexOf("children"); + var nodeFieldCount = traceNodeFields.length; + + function traverseNode(rawNodeArray, nodeOffset, parent) + { + var functionInfo = functionInfos[rawNodeArray[nodeOffset + functionInfoIndexOffset]]; + var id = rawNodeArray[nodeOffset + nodeIdOffset]; + var stats = liveObjectStats[id]; + var liveCount = stats ? stats.count : 0; + var liveSize = stats ? stats.size : 0; + var result = new WebInspector.TopDownAllocationNode( + id, + functionInfo, + rawNodeArray[nodeOffset + allocationCountOffset], + rawNodeArray[nodeOffset + allocationSizeOffset], + liveCount, + liveSize, + parent); + idToTopDownNode[id] = result; + functionInfo.addTraceTopNode(result); + + var rawChildren = rawNodeArray[nodeOffset + childrenOffset]; + for (var i = 0; i < rawChildren.length; i += nodeFieldCount) { + result.children.push(traverseNode(rawChildren, i, result)); + } + return result; + } + + return traverseNode(traceTreeRaw, 0, null); + }, + + /** + * @return {!Array.<!WebInspector.HeapSnapshotCommon.SerializedAllocationNode>} + */ + serializeTraceTops: function() + { + if (this._traceTops) + return this._traceTops; + var result = this._traceTops = []; + var functionInfos = this._functionInfos; + for (var i = 0; i < functionInfos.length; i++) { + var info = functionInfos[i]; + if (info.totalCount === 0) + continue; + var nodeId = this._nextNodeId++; + var isRoot = i == 0; + result.push(this._serializeNode( + nodeId, + info, + info.totalCount, + info.totalSize, + info.totalLiveCount, + info.totalLiveSize, + !isRoot)); + this._collapsedTopNodeIdToFunctionInfo[nodeId] = info; + } + result.sort(function(a, b) { + return b.size - a.size; + }); + return result; + }, + + /** + * @param {number} nodeId + * @return {!WebInspector.HeapSnapshotCommon.AllocationNodeCallers} + */ + serializeCallers: function(nodeId) + { + var node = this._ensureBottomUpNode(nodeId); + var nodesWithSingleCaller = []; + while (node.callers().length === 1) { + node = node.callers()[0]; + nodesWithSingleCaller.push(this._serializeCaller(node)); + } + + var branchingCallers = []; + var callers = node.callers(); + for (var i = 0; i < callers.length; i++) { + branchingCallers.push(this._serializeCaller(callers[i])); + } + return new WebInspector.HeapSnapshotCommon.AllocationNodeCallers(nodesWithSingleCaller, branchingCallers); + }, + + /** + * @param {number} traceNodeId + * @return {!Array.<!WebInspector.HeapSnapshotCommon.AllocationStackFrame>} + */ + serializeAllocationStack: function(traceNodeId) + { + var node = this._idToTopDownNode[traceNodeId]; + var result = []; + while (node) { + var functionInfo = node.functionInfo; + result.push(new WebInspector.HeapSnapshotCommon.AllocationStackFrame( + functionInfo.functionName, + functionInfo.scriptName, + functionInfo.scriptId, + functionInfo.line, + functionInfo.column + )); + node = node.parent; + } + return result; + }, + + /** + * @param {number} allocationNodeId + * @return {!Array.<number>} + */ + traceIds: function(allocationNodeId) + { + return this._ensureBottomUpNode(allocationNodeId).traceTopIds; + }, + + /** + * @param {number} nodeId + * @return {!WebInspector.BottomUpAllocationNode} + */ + _ensureBottomUpNode: function(nodeId) + { + var node = this._idToNode[nodeId]; + if (!node) { + var functionInfo = this._collapsedTopNodeIdToFunctionInfo[nodeId]; + node = functionInfo.bottomUpRoot(); + delete this._collapsedTopNodeIdToFunctionInfo[nodeId]; + this._idToNode[nodeId] = node; + } + return node; + }, + + /** + * @param {!WebInspector.BottomUpAllocationNode} node + * @return {!WebInspector.HeapSnapshotCommon.SerializedAllocationNode} + */ + _serializeCaller: function(node) + { + var callerId = this._nextNodeId++; + this._idToNode[callerId] = node; + return this._serializeNode( + callerId, + node.functionInfo, + node.allocationCount, + node.allocationSize, + node.liveCount, + node.liveSize, + node.hasCallers()); + }, + + /** + * @param {number} nodeId + * @param {!WebInspector.FunctionAllocationInfo} functionInfo + * @param {number} count + * @param {number} size + * @param {number} liveCount + * @param {number} liveSize + * @param {boolean} hasChildren + * @return {!WebInspector.HeapSnapshotCommon.SerializedAllocationNode} + */ + _serializeNode: function(nodeId, functionInfo, count, size, liveCount, liveSize, hasChildren) + { + return new WebInspector.HeapSnapshotCommon.SerializedAllocationNode( + nodeId, + functionInfo.functionName, + functionInfo.scriptName, + functionInfo.scriptId, + functionInfo.line, + functionInfo.column, + count, + size, + liveCount, + liveSize, + hasChildren + ); + } +} + + +/** + * @constructor + * @param {number} id + * @param {!WebInspector.FunctionAllocationInfo} functionInfo + * @param {number} count + * @param {number} size + * @param {number} liveCount + * @param {number} liveSize + * @param {?WebInspector.TopDownAllocationNode} parent + */ +WebInspector.TopDownAllocationNode = function(id, functionInfo, count, size, liveCount, liveSize, parent) +{ + this.id = id; + this.functionInfo = functionInfo; + this.allocationCount = count; + this.allocationSize = size; + this.liveCount = liveCount; + this.liveSize = liveSize; + this.parent = parent; + this.children = []; +} + + +/** + * @constructor + * @param {!WebInspector.FunctionAllocationInfo} functionInfo + */ +WebInspector.BottomUpAllocationNode = function(functionInfo) +{ + this.functionInfo = functionInfo; + this.allocationCount = 0; + this.allocationSize = 0; + this.liveCount = 0; + this.liveSize = 0; + this.traceTopIds = []; + this._callers = []; +} + + +WebInspector.BottomUpAllocationNode.prototype = { + /** + * @param {!WebInspector.TopDownAllocationNode} traceNode + * @return {!WebInspector.BottomUpAllocationNode} + */ + addCaller: function(traceNode) + { + var functionInfo = traceNode.functionInfo; + var result; + for (var i = 0; i < this._callers.length; i++) { + var caller = this._callers[i]; + if (caller.functionInfo === functionInfo) { + result = caller; + break; + } + } + if (!result) { + result = new WebInspector.BottomUpAllocationNode(functionInfo); + this._callers.push(result); + } + return result; + }, + + /** + * @return {!Array.<!WebInspector.BottomUpAllocationNode>} + */ + callers: function() + { + return this._callers; + }, + + /** + * @return {boolean} + */ + hasCallers: function() + { + return this._callers.length > 0; + } +} + + +/** + * @constructor + * @param {string} functionName + * @param {string} scriptName + * @param {number} scriptId + * @param {number} line + * @param {number} column + */ +WebInspector.FunctionAllocationInfo = function(functionName, scriptName, scriptId, line, column) +{ + this.functionName = functionName; + this.scriptName = scriptName; + this.scriptId = scriptId; + this.line = line; + this.column = column; + this.totalCount = 0; + this.totalSize = 0; + this.totalLiveCount = 0; + this.totalLiveSize = 0; + this._traceTops = []; +} + +WebInspector.FunctionAllocationInfo.prototype = { + /** + * @param {!WebInspector.TopDownAllocationNode} node + */ + addTraceTopNode: function(node) + { + if (node.allocationCount === 0) + return; + this._traceTops.push(node); + this.totalCount += node.allocationCount; + this.totalSize += node.allocationSize; + this.totalLiveCount += node.liveCount; + this.totalLiveSize += node.liveSize; + }, + + /** + * @return {?WebInspector.BottomUpAllocationNode} + */ + bottomUpRoot: function() + { + if (!this._traceTops.length) + return null; + if (!this._bottomUpTree) + this._buildAllocationTraceTree(); + return this._bottomUpTree; + }, + + _buildAllocationTraceTree: function() + { + this._bottomUpTree = new WebInspector.BottomUpAllocationNode(this); + + for (var i = 0; i < this._traceTops.length; i++) { + var node = this._traceTops[i]; + var bottomUpNode = this._bottomUpTree; + var count = node.allocationCount; + var size = node.allocationSize; + var liveCount = node.liveCount; + var liveSize = node.liveSize; + var traceId = node.id; + while (true) { + bottomUpNode.allocationCount += count; + bottomUpNode.allocationSize += size; + bottomUpNode.liveCount += liveCount; + bottomUpNode.liveSize += liveSize; + bottomUpNode.traceTopIds.push(traceId); + node = node.parent; + if (node === null) { + break; + } + bottomUpNode = bottomUpNode.addCaller(node); + } + } + } +} diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/HeapSnapshot.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/HeapSnapshot.js new file mode 100644 index 00000000000..910b5a33f34 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/HeapSnapshot.js @@ -0,0 +1,2293 @@ +/* + * Copyright (C) 2011 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. + */ + +/** + * @interface + */ +WebInspector.HeapSnapshotItem = function() { } + +WebInspector.HeapSnapshotItem.prototype = { + /** + * @return {number} + */ + itemIndex: function() { }, + + /** + * @return {!Object} + */ + serialize: function() { } +}; + +/** + * @constructor + * @implements {WebInspector.HeapSnapshotItem} + * @param {!WebInspector.HeapSnapshot} snapshot + * @param {number=} edgeIndex + */ +WebInspector.HeapSnapshotEdge = function(snapshot, edgeIndex) +{ + this._snapshot = snapshot; + this._edges = snapshot._containmentEdges; + this.edgeIndex = edgeIndex || 0; +} + +WebInspector.HeapSnapshotEdge.prototype = { + /** + * @return {!WebInspector.HeapSnapshotEdge} + */ + clone: function() + { + return new WebInspector.HeapSnapshotEdge(this._snapshot, this.edgeIndex); + }, + + /** + * @return {boolean} + */ + hasStringName: function() + { + throw new Error("Not implemented"); + }, + + /** + * @return {string} + */ + name: function() + { + throw new Error("Not implemented"); + }, + + /** + * @return {!WebInspector.HeapSnapshotNode} + */ + node: function() + { + return this._snapshot.createNode(this.nodeIndex()); + }, + + /** + * @return {number} + */ + nodeIndex: function() + { + return this._edges[this.edgeIndex + this._snapshot._edgeToNodeOffset]; + }, + + /** + * @return {string} + */ + toString: function() + { + return "HeapSnapshotEdge: " + this.name(); + }, + + /** + * @return {string} + */ + type: function() + { + return this._snapshot._edgeTypes[this._type()]; + }, + + /** + * @override + * @return {number} + */ + itemIndex: function() + { + return this.edgeIndex; + }, + + /** + * @override + * @return {!WebInspector.HeapSnapshotCommon.Edge} + */ + serialize: function() + { + return new WebInspector.HeapSnapshotCommon.Edge(this.name(), this.node().serialize(), this.type(), this.edgeIndex); + }, + + _type: function() + { + return this._edges[this.edgeIndex + this._snapshot._edgeTypeOffset]; + } +}; + + +/** + * @interface + */ +WebInspector.HeapSnapshotItemIterator = function() { } + +WebInspector.HeapSnapshotItemIterator.prototype = { + /** + * @return {boolean} + */ + hasNext: function() { }, + + /** + * @return {!WebInspector.HeapSnapshotItem} + */ + item: function() { }, + + next: function() { } +}; + + +/** + * @interface + */ +WebInspector.HeapSnapshotItemIndexProvider = function() { } + +WebInspector.HeapSnapshotItemIndexProvider.prototype = { + /** + * @param {number} newIndex + * @return {!WebInspector.HeapSnapshotItem} + */ + itemForIndex: function(newIndex) { }, +}; + +/** + * @constructor + * @implements {WebInspector.HeapSnapshotItemIndexProvider} + * @param {!WebInspector.HeapSnapshot} snapshot + */ +WebInspector.HeapSnapshotNodeIndexProvider = function(snapshot) +{ + this._node = snapshot.createNode(); +} + +WebInspector.HeapSnapshotNodeIndexProvider.prototype = { + /** + * @param {number} index + * @return {!WebInspector.HeapSnapshotNode} + */ + itemForIndex: function(index) + { + this._node.nodeIndex = index; + return this._node; + } +}; + + +/** + * @constructor + * @implements {WebInspector.HeapSnapshotItemIndexProvider} + * @param {!WebInspector.HeapSnapshot} snapshot + */ +WebInspector.HeapSnapshotEdgeIndexProvider = function(snapshot) +{ + this._edge = snapshot.createEdge(0); +} + +WebInspector.HeapSnapshotEdgeIndexProvider.prototype = { + /** + * @param {number} index + * @return {!WebInspector.HeapSnapshotEdge} + */ + itemForIndex: function(index) + { + this._edge.edgeIndex = index; + return this._edge; + } +}; + + +/** + * @constructor + * @implements {WebInspector.HeapSnapshotItemIndexProvider} + * @param {!WebInspector.HeapSnapshot} snapshot + */ +WebInspector.HeapSnapshotRetainerEdgeIndexProvider = function(snapshot) +{ + this._retainerEdge = snapshot.createRetainingEdge(0); +} + +WebInspector.HeapSnapshotRetainerEdgeIndexProvider.prototype = { + /** + * @param {number} index + * @return {!WebInspector.HeapSnapshotRetainerEdge} + */ + itemForIndex: function(index) + { + this._retainerEdge.setRetainerIndex(index); + return this._retainerEdge; + } +}; + + +/** + * @constructor + * @implements {WebInspector.HeapSnapshotItemIterator} + * @param {!WebInspector.HeapSnapshotNode} node + */ +WebInspector.HeapSnapshotEdgeIterator = function(node) +{ + this._sourceNode = node; + this.edge = node._snapshot.createEdge(node._edgeIndexesStart()); +} + +WebInspector.HeapSnapshotEdgeIterator.prototype = { + /** + * @return {boolean} + */ + hasNext: function() + { + return this.edge.edgeIndex < this._sourceNode._edgeIndexesEnd(); + }, + + /** + * @return {!WebInspector.HeapSnapshotEdge} + */ + item: function() + { + return this.edge; + }, + + next: function() + { + this.edge.edgeIndex += this.edge._snapshot._edgeFieldsCount; + } +}; + +/** + * @constructor + * @implements {WebInspector.HeapSnapshotItem} + * @param {!WebInspector.HeapSnapshot} snapshot + * @param {number} retainerIndex + */ +WebInspector.HeapSnapshotRetainerEdge = function(snapshot, retainerIndex) +{ + this._snapshot = snapshot; + this.setRetainerIndex(retainerIndex); +} + +WebInspector.HeapSnapshotRetainerEdge.prototype = { + /** + * @return {!WebInspector.HeapSnapshotRetainerEdge} + */ + clone: function() + { + return new WebInspector.HeapSnapshotRetainerEdge(this._snapshot, this.retainerIndex()); + }, + + /** + * @return {boolean} + */ + hasStringName: function() + { + return this._edge().hasStringName(); + }, + + /** + * @return {string} + */ + name: function() + { + return this._edge().name(); + }, + + /** + * @return {!WebInspector.HeapSnapshotNode} + */ + node: function() + { + return this._node(); + }, + + /** + * @return {number} + */ + nodeIndex: function() + { + return this._retainingNodeIndex; + }, + + /** + * @return {number} + */ + retainerIndex: function() + { + return this._retainerIndex; + }, + + /** + * @param {number} retainerIndex + */ + setRetainerIndex: function(retainerIndex) + { + if (retainerIndex === this._retainerIndex) + return; + this._retainerIndex = retainerIndex; + this._globalEdgeIndex = this._snapshot._retainingEdges[retainerIndex]; + this._retainingNodeIndex = this._snapshot._retainingNodes[retainerIndex]; + this._edgeInstance = null; + this._nodeInstance = null; + }, + + /** + * @param {number} edgeIndex + */ + set edgeIndex(edgeIndex) + { + this.setRetainerIndex(edgeIndex); + }, + + _node: function() + { + if (!this._nodeInstance) + this._nodeInstance = this._snapshot.createNode(this._retainingNodeIndex); + return this._nodeInstance; + }, + + _edge: function() + { + if (!this._edgeInstance) + this._edgeInstance = this._snapshot.createEdge(this._globalEdgeIndex); + return this._edgeInstance; + }, + + /** + * @return {string} + */ + toString: function() + { + return this._edge().toString(); + }, + + /** + * @override + * @return {number} + */ + itemIndex: function() + { + return this._retainerIndex; + }, + + /** + * @override + * @return {!WebInspector.HeapSnapshotCommon.Edge} + */ + serialize: function() + { + return new WebInspector.HeapSnapshotCommon.Edge(this.name(), this.node().serialize(), this.type(), this._globalEdgeIndex); + }, + + /** + * @return {string} + */ + type: function() + { + return this._edge().type(); + } +} + +/** + * @constructor + * @implements {WebInspector.HeapSnapshotItemIterator} + * @param {!WebInspector.HeapSnapshotNode} retainedNode + */ +WebInspector.HeapSnapshotRetainerEdgeIterator = function(retainedNode) +{ + var snapshot = retainedNode._snapshot; + var retainedNodeOrdinal = retainedNode._ordinal(); + var retainerIndex = snapshot._firstRetainerIndex[retainedNodeOrdinal]; + this._retainersEnd = snapshot._firstRetainerIndex[retainedNodeOrdinal + 1]; + this.retainer = snapshot.createRetainingEdge(retainerIndex); +} + +WebInspector.HeapSnapshotRetainerEdgeIterator.prototype = { + /** + * @return {boolean} + */ + hasNext: function() + { + return this.retainer.retainerIndex() < this._retainersEnd; + }, + + /** + * @return {!WebInspector.HeapSnapshotRetainerEdge} + */ + item: function() + { + return this.retainer; + }, + + next: function() + { + this.retainer.setRetainerIndex(this.retainer.retainerIndex() + 1); + } +}; + +/** + * @constructor + * @implements {WebInspector.HeapSnapshotItem} + * @param {!WebInspector.HeapSnapshot} snapshot + * @param {number=} nodeIndex + */ +WebInspector.HeapSnapshotNode = function(snapshot, nodeIndex) +{ + this._snapshot = snapshot; + this.nodeIndex = nodeIndex || 0; +} + +WebInspector.HeapSnapshotNode.prototype = { + /** + * @return {number} + */ + distance: function() + { + return this._snapshot._nodeDistances[this.nodeIndex / this._snapshot._nodeFieldCount]; + }, + + /** + * @return {string} + */ + className: function() + { + throw new Error("Not implemented"); + }, + + /** + * @return {number} + */ + classIndex: function() + { + throw new Error("Not implemented"); + }, + + /** + * @return {number} + */ + dominatorIndex: function() + { + var nodeFieldCount = this._snapshot._nodeFieldCount; + return this._snapshot._dominatorsTree[this.nodeIndex / this._snapshot._nodeFieldCount] * nodeFieldCount; + }, + + /** + * @return {!WebInspector.HeapSnapshotEdgeIterator} + */ + edges: function() + { + return new WebInspector.HeapSnapshotEdgeIterator(this); + }, + + /** + * @return {number} + */ + edgesCount: function() + { + return (this._edgeIndexesEnd() - this._edgeIndexesStart()) / this._snapshot._edgeFieldsCount; + }, + + /** + * @return {number} + */ + id: function() + { + throw new Error("Not implemented"); + }, + + /** + * @return {boolean} + */ + isRoot: function() + { + return this.nodeIndex === this._snapshot._rootNodeIndex; + }, + + /** + * @return {string} + */ + name: function() + { + return this._snapshot._strings[this._name()]; + }, + + /** + * @return {number} + */ + retainedSize: function() + { + return this._snapshot._retainedSizes[this._ordinal()]; + }, + + /** + * @return {!WebInspector.HeapSnapshotRetainerEdgeIterator} + */ + retainers: function() + { + return new WebInspector.HeapSnapshotRetainerEdgeIterator(this); + }, + + /** + * @return {number} + */ + retainersCount: function() + { + var snapshot = this._snapshot; + var ordinal = this._ordinal(); + return snapshot._firstRetainerIndex[ordinal + 1] - snapshot._firstRetainerIndex[ordinal]; + }, + + /** + * @return {number} + */ + selfSize: function() + { + var snapshot = this._snapshot; + return snapshot._nodes[this.nodeIndex + snapshot._nodeSelfSizeOffset]; + }, + + /** + * @return {string} + */ + type: function() + { + return this._snapshot._nodeTypes[this._type()]; + }, + + /** + * @return {number} + */ + traceNodeId: function() + { + var snapshot = this._snapshot; + return snapshot._nodes[this.nodeIndex + snapshot._nodeTraceNodeIdOffset]; + }, + + /** + * @override + * @return {number} + */ + itemIndex: function() + { + return this.nodeIndex; + }, + + /** + * @override + * @return {!WebInspector.HeapSnapshotCommon.Node} + */ + serialize: function() + { + return new WebInspector.HeapSnapshotCommon.Node(this.id(), this.name(), this.distance(), this.nodeIndex, this.retainedSize(), this.selfSize(), this.type()); + }, + + /** + * @return {number} + */ + _name: function() + { + var snapshot = this._snapshot; + return snapshot._nodes[this.nodeIndex + snapshot._nodeNameOffset]; + }, + + /** + * @return {number} + */ + _edgeIndexesStart: function() + { + return this._snapshot._firstEdgeIndexes[this._ordinal()]; + }, + + /** + * @return {number} + */ + _edgeIndexesEnd: function() + { + return this._snapshot._firstEdgeIndexes[this._ordinal() + 1]; + }, + + /** + * @return {number} + */ + _ordinal: function() + { + return this.nodeIndex / this._snapshot._nodeFieldCount; + }, + + /** + * @return {number} + */ + _nextNodeIndex: function() + { + return this.nodeIndex + this._snapshot._nodeFieldCount; + }, + + /** + * @return {number} + */ + _type: function() + { + var snapshot = this._snapshot; + return snapshot._nodes[this.nodeIndex + snapshot._nodeTypeOffset]; + } +}; + +/** + * @constructor + * @implements {WebInspector.HeapSnapshotItemIterator} + * @param {!WebInspector.HeapSnapshotNode} node + */ +WebInspector.HeapSnapshotNodeIterator = function(node) +{ + this.node = node; + this._nodesLength = node._snapshot._nodes.length; +} + +WebInspector.HeapSnapshotNodeIterator.prototype = { + /** + * @return {boolean} + */ + hasNext: function() + { + return this.node.nodeIndex < this._nodesLength; + }, + + /** + * @return {!WebInspector.HeapSnapshotNode} + */ + item: function() + { + return this.node; + }, + + next: function() + { + this.node.nodeIndex = this.node._nextNodeIndex(); + } +} + + +/** + * @constructor + * @implements {WebInspector.HeapSnapshotItemIterator} + * @param {!WebInspector.HeapSnapshotItemIndexProvider} itemProvider + * @param {!Array.<number>|!Uint32Array} indexes + */ +WebInspector.HeapSnapshotIndexRangeIterator = function(itemProvider, indexes) +{ + this._itemProvider = itemProvider; + this._indexes = indexes; + this._position = 0; +} + +WebInspector.HeapSnapshotIndexRangeIterator.prototype = { + /** + * @return {boolean} + */ + hasNext: function() + { + return this._position < this._indexes.length + }, + + /** + * @return {!WebInspector.HeapSnapshotItem} + */ + item: function() + { + var index = this._indexes[this._position]; + return this._itemProvider.itemForIndex(index); + }, + + next: function() + { + ++this._position; + } +} + + +/** + * @constructor + * @implements {WebInspector.HeapSnapshotItemIterator} + * @param {!WebInspector.HeapSnapshotItemIterator} iterator + * @param {function(!WebInspector.HeapSnapshotItem):boolean=} filter + */ +WebInspector.HeapSnapshotFilteredIterator = function(iterator, filter) +{ + this._iterator = iterator; + this._filter = filter; + this._skipFilteredItems(); +} + +WebInspector.HeapSnapshotFilteredIterator.prototype = { + /** + * @return {boolean} + */ + hasNext: function() + { + return this._iterator.hasNext(); + }, + + /** + * @return {!WebInspector.HeapSnapshotItem} + */ + item: function() + { + return this._iterator.item(); + }, + + next: function() + { + this._iterator.next(); + this._skipFilteredItems(); + }, + + _skipFilteredItems: function() + { + while (this._iterator.hasNext() && !this._filter(this._iterator.item())) { + this._iterator.next(); + } + } +} + + +/** + * @param {!WebInspector.HeapSnapshotWorkerDispatcher=} dispatcher + * @constructor + */ +WebInspector.HeapSnapshotProgress = function(dispatcher) +{ + this._dispatcher = dispatcher; +} + +WebInspector.HeapSnapshotProgress.prototype = { + /** + * @param {string} status + */ + updateStatus: function(status) + { + this._sendUpdateEvent(WebInspector.UIString(status)); + }, + + /** + * @param {string} title + * @param {number} value + * @param {number} total + */ + updateProgress: function(title, value, total) + { + var percentValue = ((total ? (value / total) : 0) * 100).toFixed(0); + this._sendUpdateEvent(WebInspector.UIString(title, percentValue)); + }, + + /** + * @param {string} text + */ + _sendUpdateEvent: function(text) + { + // May be undefined in tests. + if (this._dispatcher) + this._dispatcher.sendEvent(WebInspector.HeapSnapshotProgressEvent.Update, text); + } +} + + +/** + * @param {!Object} profile + * @param {!WebInspector.HeapSnapshotProgress} progress + * @param {boolean} showHiddenData + * @constructor + */ +WebInspector.HeapSnapshot = function(profile, progress, showHiddenData) +{ + this._nodes = profile.nodes; + this._containmentEdges = profile.edges; + /** @type {!HeapSnapshotMetainfo} */ + this._metaNode = profile.snapshot.meta; + this._strings = profile.strings; + this._progress = progress; + + this._noDistance = -5; + this._rootNodeIndex = 0; + if (profile.snapshot.root_index) + this._rootNodeIndex = profile.snapshot.root_index; + + this._snapshotDiffs = {}; + this._aggregatesForDiff = null; + this._aggregates = {}; + this._aggregatesSortedFlags = {}; + this._showHiddenData = showHiddenData; + + this._init(); + + if (profile.snapshot.trace_function_count) { + this._progress.updateStatus("Buiding allocation statistics\u2026"); + var nodes = this._nodes; + var nodesLength = nodes.length; + var nodeFieldCount = this._nodeFieldCount; + var node = this.rootNode(); + var liveObjects = {}; + for (var nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { + node.nodeIndex = nodeIndex; + var traceNodeId = node.traceNodeId(); + var stats = liveObjects[traceNodeId]; + if (!stats) { + liveObjects[traceNodeId] = stats = { count: 0, size: 0, ids: []}; + } + stats.count++; + stats.size += node.selfSize(); + stats.ids.push(node.id()); + } + this._allocationProfile = new WebInspector.AllocationProfile(profile, liveObjects); + this._progress.updateStatus("Done"); + } +} + +/** + * @constructor + */ +function HeapSnapshotMetainfo() +{ + // New format. + this.node_fields = []; + this.node_types = []; + this.edge_fields = []; + this.edge_types = []; + this.trace_function_info_fields = []; + this.trace_node_fields = []; + this.type_strings = {}; +} + +/** + * @constructor + */ +function HeapSnapshotHeader() +{ + // New format. + this.title = ""; + this.meta = new HeapSnapshotMetainfo(); + this.node_count = 0; + this.edge_count = 0; +} + +WebInspector.HeapSnapshot.prototype = { + _init: function() + { + var meta = this._metaNode; + + this._nodeTypeOffset = meta.node_fields.indexOf("type"); + this._nodeNameOffset = meta.node_fields.indexOf("name"); + this._nodeIdOffset = meta.node_fields.indexOf("id"); + this._nodeSelfSizeOffset = meta.node_fields.indexOf("self_size"); + this._nodeEdgeCountOffset = meta.node_fields.indexOf("edge_count"); + this._nodeTraceNodeIdOffset = meta.node_fields.indexOf("trace_node_id"); + this._nodeFieldCount = meta.node_fields.length; + + this._nodeTypes = meta.node_types[this._nodeTypeOffset]; + this._nodeHiddenType = this._nodeTypes.indexOf("hidden"); + this._nodeObjectType = this._nodeTypes.indexOf("object"); + this._nodeNativeType = this._nodeTypes.indexOf("native"); + this._nodeConsStringType = this._nodeTypes.indexOf("concatenated string"); + this._nodeSlicedStringType = this._nodeTypes.indexOf("sliced string"); + this._nodeCodeType = this._nodeTypes.indexOf("code"); + this._nodeSyntheticType = this._nodeTypes.indexOf("synthetic"); + + this._edgeFieldsCount = meta.edge_fields.length; + this._edgeTypeOffset = meta.edge_fields.indexOf("type"); + this._edgeNameOffset = meta.edge_fields.indexOf("name_or_index"); + this._edgeToNodeOffset = meta.edge_fields.indexOf("to_node"); + + this._edgeTypes = meta.edge_types[this._edgeTypeOffset]; + this._edgeTypes.push("invisible"); + this._edgeElementType = this._edgeTypes.indexOf("element"); + this._edgeHiddenType = this._edgeTypes.indexOf("hidden"); + this._edgeInternalType = this._edgeTypes.indexOf("internal"); + this._edgeShortcutType = this._edgeTypes.indexOf("shortcut"); + this._edgeWeakType = this._edgeTypes.indexOf("weak"); + this._edgeInvisibleType = this._edgeTypes.indexOf("invisible"); + + this.nodeCount = this._nodes.length / this._nodeFieldCount; + this._edgeCount = this._containmentEdges.length / this._edgeFieldsCount; + + this._progress.updateStatus("Building edge indexes\u2026"); + this._buildEdgeIndexes(); + this._progress.updateStatus("Building retainers\u2026"); + this._buildRetainers(); + this._progress.updateStatus("Calculating node flags\u2026"); + this._calculateFlags(); + this._progress.updateStatus("Calculating distances\u2026"); + this._calculateDistances(); + this._progress.updateStatus("Building postorder index\u2026"); + var result = this._buildPostOrderIndex(); + // Actually it is array that maps node ordinal number to dominator node ordinal number. + this._progress.updateStatus("Building dominator tree\u2026"); + this._dominatorsTree = this._buildDominatorTree(result.postOrderIndex2NodeOrdinal, result.nodeOrdinal2PostOrderIndex); + this._progress.updateStatus("Calculating retained sizes\u2026"); + this._calculateRetainedSizes(result.postOrderIndex2NodeOrdinal); + this._progress.updateStatus("Buiding dominated nodes\u2026"); + this._buildDominatedNodes(); + this._progress.updateStatus("Calculating statistics\u2026"); + this._calculateStatistics(); + this._progress.updateStatus("Finished processing."); + }, + + _buildEdgeIndexes: function() + { + var nodes = this._nodes; + var nodeCount = this.nodeCount; + var firstEdgeIndexes = this._firstEdgeIndexes = new Uint32Array(nodeCount + 1); + var nodeFieldCount = this._nodeFieldCount; + var edgeFieldsCount = this._edgeFieldsCount; + var nodeEdgeCountOffset = this._nodeEdgeCountOffset; + firstEdgeIndexes[nodeCount] = this._containmentEdges.length; + for (var nodeOrdinal = 0, edgeIndex = 0; nodeOrdinal < nodeCount; ++nodeOrdinal) { + firstEdgeIndexes[nodeOrdinal] = edgeIndex; + edgeIndex += nodes[nodeOrdinal * nodeFieldCount + nodeEdgeCountOffset] * edgeFieldsCount; + } + }, + + _buildRetainers: function() + { + var retainingNodes = this._retainingNodes = new Uint32Array(this._edgeCount); + var retainingEdges = this._retainingEdges = new Uint32Array(this._edgeCount); + // Index of the first retainer in the _retainingNodes and _retainingEdges + // arrays. Addressed by retained node index. + var firstRetainerIndex = this._firstRetainerIndex = new Uint32Array(this.nodeCount + 1); + + var containmentEdges = this._containmentEdges; + var edgeFieldsCount = this._edgeFieldsCount; + var nodeFieldCount = this._nodeFieldCount; + var edgeToNodeOffset = this._edgeToNodeOffset; + var firstEdgeIndexes = this._firstEdgeIndexes; + var nodeCount = this.nodeCount; + + for (var toNodeFieldIndex = edgeToNodeOffset, l = containmentEdges.length; toNodeFieldIndex < l; toNodeFieldIndex += edgeFieldsCount) { + var toNodeIndex = containmentEdges[toNodeFieldIndex]; + if (toNodeIndex % nodeFieldCount) + throw new Error("Invalid toNodeIndex " + toNodeIndex); + ++firstRetainerIndex[toNodeIndex / nodeFieldCount]; + } + for (var i = 0, firstUnusedRetainerSlot = 0; i < nodeCount; i++) { + var retainersCount = firstRetainerIndex[i]; + firstRetainerIndex[i] = firstUnusedRetainerSlot; + retainingNodes[firstUnusedRetainerSlot] = retainersCount; + firstUnusedRetainerSlot += retainersCount; + } + firstRetainerIndex[nodeCount] = retainingNodes.length; + + var nextNodeFirstEdgeIndex = firstEdgeIndexes[0]; + for (var srcNodeOrdinal = 0; srcNodeOrdinal < nodeCount; ++srcNodeOrdinal) { + var firstEdgeIndex = nextNodeFirstEdgeIndex; + nextNodeFirstEdgeIndex = firstEdgeIndexes[srcNodeOrdinal + 1]; + var srcNodeIndex = srcNodeOrdinal * nodeFieldCount; + for (var edgeIndex = firstEdgeIndex; edgeIndex < nextNodeFirstEdgeIndex; edgeIndex += edgeFieldsCount) { + var toNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; + if (toNodeIndex % nodeFieldCount) + throw new Error("Invalid toNodeIndex " + toNodeIndex); + var firstRetainerSlotIndex = firstRetainerIndex[toNodeIndex / nodeFieldCount]; + var nextUnusedRetainerSlotIndex = firstRetainerSlotIndex + (--retainingNodes[firstRetainerSlotIndex]); + retainingNodes[nextUnusedRetainerSlotIndex] = srcNodeIndex; + retainingEdges[nextUnusedRetainerSlotIndex] = edgeIndex; + } + } + }, + + /** + * @param {number=} nodeIndex + */ + createNode: function(nodeIndex) + { + throw new Error("Not implemented"); + }, + + /** + * @param {number} edgeIndex + * @return {!WebInspector.JSHeapSnapshotEdge} + */ + createEdge: function(edgeIndex) + { + throw new Error("Not implemented"); + }, + + /** + * @param {number} retainerIndex + * @return {!WebInspector.JSHeapSnapshotRetainerEdge} + */ + createRetainingEdge: function(retainerIndex) + { + throw new Error("Not implemented"); + }, + + dispose: function() + { + delete this._nodes; + delete this._strings; + delete this._retainingEdges; + delete this._retainingNodes; + delete this._firstRetainerIndex; + delete this._aggregates; + delete this._aggregatesSortedFlags; + delete this._dominatedNodes; + delete this._firstDominatedNodeIndex; + delete this._nodeDistances; + delete this._dominatorsTree; + }, + + _allNodes: function() + { + return new WebInspector.HeapSnapshotNodeIterator(this.rootNode()); + }, + + /** + * @return {!WebInspector.HeapSnapshotNode} + */ + rootNode: function() + { + return this.createNode(this._rootNodeIndex); + }, + + get rootNodeIndex() + { + return this._rootNodeIndex; + }, + + get totalSize() + { + return this.rootNode().retainedSize(); + }, + + _getDominatedIndex: function(nodeIndex) + { + if (nodeIndex % this._nodeFieldCount) + throw new Error("Invalid nodeIndex: " + nodeIndex); + return this._firstDominatedNodeIndex[nodeIndex / this._nodeFieldCount]; + }, + + /** + * @param {!WebInspector.HeapSnapshotNode} node + * @return {!Uint32Array} + */ + _dominatedNodesOfNode: function(node) + { + var dominatedIndexFrom = this._getDominatedIndex(node.nodeIndex); + var dominatedIndexTo = this._getDominatedIndex(node._nextNodeIndex()); + return this._dominatedNodes.subarray(dominatedIndexFrom, dominatedIndexTo); + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.NodeFilter} nodeFilter + * @return {!Object.<string, !WebInspector.HeapSnapshotCommon.Aggregate>} + */ + aggregatesWithFilter: function(nodeFilter) + { + var minNodeId = nodeFilter.minNodeId; + var maxNodeId = nodeFilter.maxNodeId; + var allocationNodeId = nodeFilter.allocationNodeId; + var key; + var filter; + if (typeof allocationNodeId === "number") { + filter = this._createAllocationStackFilter(allocationNodeId); + } else if (typeof minNodeId === "number" && typeof maxNodeId === "number") { + key = minNodeId + ".." + maxNodeId; + filter = this._createNodeIdFilter(minNodeId, maxNodeId); + } else { + key = "allObjects"; + } + return this.aggregates(false, key, filter); + }, + + /** + * @param {number} minNodeId + * @param {number} maxNodeId + * @return {function(!WebInspector.HeapSnapshotNode):boolean} + */ + _createNodeIdFilter: function(minNodeId, maxNodeId) + { + /** + * @param {!WebInspector.HeapSnapshotNode} node + * @return {boolean} + */ + function nodeIdFilter(node) + { + var id = node.id(); + return id > minNodeId && id <= maxNodeId; + } + return nodeIdFilter; + }, + + /** + * @param {number} bottomUpAllocationNodeId + * @return {function(!WebInspector.HeapSnapshotNode):boolean|undefined} + */ + _createAllocationStackFilter: function(bottomUpAllocationNodeId) + { + var traceIds = this._allocationProfile.traceIds(bottomUpAllocationNodeId); + if (!traceIds.length) + return undefined; + var set = {}; + for (var i = 0; i < traceIds.length; i++) + set[traceIds[i]] = true; + /** + * @param {!WebInspector.HeapSnapshotNode} node + * @return {boolean} + */ + function traceIdFilter(node) + { + return !!set[node.traceNodeId()]; + }; + return traceIdFilter; + }, + + /** + * @param {boolean} sortedIndexes + * @param {string=} key + * @param {function(!WebInspector.HeapSnapshotNode):boolean=} filter + * @return {!Object.<string, !WebInspector.HeapSnapshotCommon.Aggregate>} + */ + aggregates: function(sortedIndexes, key, filter) + { + var aggregatesByClassName = key && this._aggregates[key]; + if (!aggregatesByClassName) { + var aggregates = this._buildAggregates(filter); + this._calculateClassesRetainedSize(aggregates.aggregatesByClassIndex, filter); + aggregatesByClassName = aggregates.aggregatesByClassName; + if (key) + this._aggregates[key] = aggregatesByClassName; + } + + if (sortedIndexes && (!key || !this._aggregatesSortedFlags[key])) { + this._sortAggregateIndexes(aggregatesByClassName); + if (key) + this._aggregatesSortedFlags[key] = sortedIndexes; + } + return aggregatesByClassName; + }, + + /** + * @return {!Array.<!WebInspector.HeapSnapshotCommon.SerializedAllocationNode>} + */ + allocationTracesTops: function() + { + return this._allocationProfile.serializeTraceTops(); + }, + + /** + * @param {number} nodeId + * @return {!WebInspector.HeapSnapshotCommon.AllocationNodeCallers} + */ + allocationNodeCallers: function(nodeId) + { + return this._allocationProfile.serializeCallers(nodeId); + }, + + /** + * @param {number} nodeIndex + * @return {?Array.<!WebInspector.HeapSnapshotCommon.AllocationStackFrame>} + */ + allocationStack: function(nodeIndex) + { + var node = this.createNode(nodeIndex); + var allocationNodeId = node.traceNodeId(); + if (!allocationNodeId) + return null; + return this._allocationProfile.serializeAllocationStack(allocationNodeId); + }, + + /** + * @return {!Object.<string, !WebInspector.HeapSnapshotCommon.AggregateForDiff>} + */ + aggregatesForDiff: function() + { + if (this._aggregatesForDiff) + return this._aggregatesForDiff; + + var aggregatesByClassName = this.aggregates(true, "allObjects"); + this._aggregatesForDiff = {}; + + var node = this.createNode(); + for (var className in aggregatesByClassName) { + var aggregate = aggregatesByClassName[className]; + var indexes = aggregate.idxs; + var ids = new Array(indexes.length); + var selfSizes = new Array(indexes.length); + for (var i = 0; i < indexes.length; i++) { + node.nodeIndex = indexes[i]; + ids[i] = node.id(); + selfSizes[i] = node.selfSize(); + } + + this._aggregatesForDiff[className] = { + indexes: indexes, + ids: ids, + selfSizes: selfSizes + }; + } + return this._aggregatesForDiff; + }, + + /** + * @param {!WebInspector.HeapSnapshotNode} node + * @return {!boolean} + */ + _isUserRoot: function(node) + { + return true; + }, + + /** + * @param {function(!WebInspector.HeapSnapshotNode)} action + * @param {boolean=} userRootsOnly + */ + forEachRoot: function(action, userRootsOnly) + { + for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) { + var node = iter.edge.node(); + if (!userRootsOnly || this._isUserRoot(node)) + action(node); + } + }, + + _calculateDistances: function() + { + var nodeFieldCount = this._nodeFieldCount; + var nodeCount = this.nodeCount; + var distances = this._nodeDistances = new Int32Array(nodeCount); + var noDistance = this._noDistance; + for (var i = 0; i < nodeCount; ++i) + distances[i] = noDistance; + + var nodesToVisit = new Uint32Array(this.nodeCount); + var nodesToVisitLength = 0; + + /** + * @param {number} distance + * @param {!WebInspector.HeapSnapshotNode} node + */ + function enqueueNode(distance, node) + { + var ordinal = node._ordinal(); + if (distances[ordinal] !== noDistance) + return; + distances[ordinal] = distance; + nodesToVisit[nodesToVisitLength++] = node.nodeIndex; + } + + this.forEachRoot(enqueueNode.bind(null, 1), true); + this._bfs(nodesToVisit, nodesToVisitLength, distances); + + // bfs for the rest of objects + nodesToVisitLength = 0; + this.forEachRoot(enqueueNode.bind(null, WebInspector.HeapSnapshotCommon.baseSystemDistance), false); + this._bfs(nodesToVisit, nodesToVisitLength, distances); + }, + + /** + * @param {!Uint32Array} nodesToVisit + * @param {!number} nodesToVisitLength + * @param {!Int32Array} distances + */ + _bfs: function(nodesToVisit, nodesToVisitLength, distances) + { + // Preload fields into local variables for better performance. + var edgeFieldsCount = this._edgeFieldsCount; + var nodeFieldCount = this._nodeFieldCount; + var containmentEdges = this._containmentEdges; + var firstEdgeIndexes = this._firstEdgeIndexes; + var edgeToNodeOffset = this._edgeToNodeOffset; + var edgeTypeOffset = this._edgeTypeOffset; + var nodeCount = this.nodeCount; + var containmentEdgesLength = containmentEdges.length; + var edgeWeakType = this._edgeWeakType; + var noDistance = this._noDistance; + + var index = 0; + while (index < nodesToVisitLength) { + var nodeIndex = nodesToVisit[index++]; // shift generates too much garbage. + var nodeOrdinal = nodeIndex / nodeFieldCount; + var distance = distances[nodeOrdinal] + 1; + var firstEdgeIndex = firstEdgeIndexes[nodeOrdinal]; + var edgesEnd = firstEdgeIndexes[nodeOrdinal + 1]; + for (var edgeIndex = firstEdgeIndex; edgeIndex < edgesEnd; edgeIndex += edgeFieldsCount) { + var edgeType = containmentEdges[edgeIndex + edgeTypeOffset]; + if (edgeType == edgeWeakType) + continue; + var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; + var childNodeOrdinal = childNodeIndex / nodeFieldCount; + if (distances[childNodeOrdinal] !== noDistance) + continue; + distances[childNodeOrdinal] = distance; + nodesToVisit[nodesToVisitLength++] = childNodeIndex; + } + } + if (nodesToVisitLength > nodeCount) + throw new Error("BFS failed. Nodes to visit (" + nodesToVisitLength + ") is more than nodes count (" + nodeCount + ")"); + }, + + _buildAggregates: function(filter) + { + var aggregates = {}; + var aggregatesByClassName = {}; + var classIndexes = []; + var nodes = this._nodes; + var mapAndFlag = this.userObjectsMapAndFlag(); + var flags = mapAndFlag ? mapAndFlag.map : null; + var flag = mapAndFlag ? mapAndFlag.flag : 0; + var nodesLength = nodes.length; + var nodeNativeType = this._nodeNativeType; + var nodeFieldCount = this._nodeFieldCount; + var selfSizeOffset = this._nodeSelfSizeOffset; + var nodeTypeOffset = this._nodeTypeOffset; + var node = this.rootNode(); + var nodeDistances = this._nodeDistances; + + for (var nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { + var nodeOrdinal = nodeIndex / nodeFieldCount; + if (flags && !(flags[nodeOrdinal] & flag)) + continue; + node.nodeIndex = nodeIndex; + if (filter && !filter(node)) + continue; + var selfSize = nodes[nodeIndex + selfSizeOffset]; + if (!selfSize && nodes[nodeIndex + nodeTypeOffset] !== nodeNativeType) + continue; + var classIndex = node.classIndex(); + if (!(classIndex in aggregates)) { + var nodeType = node.type(); + var nameMatters = nodeType === "object" || nodeType === "native"; + var value = { + count: 1, + distance: nodeDistances[nodeOrdinal], + self: selfSize, + maxRet: 0, + type: nodeType, + name: nameMatters ? node.name() : null, + idxs: [nodeIndex] + }; + aggregates[classIndex] = value; + classIndexes.push(classIndex); + aggregatesByClassName[node.className()] = value; + } else { + var clss = aggregates[classIndex]; + clss.distance = Math.min(clss.distance, nodeDistances[nodeOrdinal]); + ++clss.count; + clss.self += selfSize; + clss.idxs.push(nodeIndex); + } + } + + // Shave off provisionally allocated space. + for (var i = 0, l = classIndexes.length; i < l; ++i) { + var classIndex = classIndexes[i]; + aggregates[classIndex].idxs = aggregates[classIndex].idxs.slice(); + } + return {aggregatesByClassName: aggregatesByClassName, aggregatesByClassIndex: aggregates}; + }, + + _calculateClassesRetainedSize: function(aggregates, filter) + { + var rootNodeIndex = this._rootNodeIndex; + var node = this.createNode(rootNodeIndex); + var list = [rootNodeIndex]; + var sizes = [-1]; + var classes = []; + var seenClassNameIndexes = {}; + var nodeFieldCount = this._nodeFieldCount; + var nodeTypeOffset = this._nodeTypeOffset; + var nodeNativeType = this._nodeNativeType; + var dominatedNodes = this._dominatedNodes; + var nodes = this._nodes; + var mapAndFlag = this.userObjectsMapAndFlag(); + var flags = mapAndFlag ? mapAndFlag.map : null; + var flag = mapAndFlag ? mapAndFlag.flag : 0; + var firstDominatedNodeIndex = this._firstDominatedNodeIndex; + + while (list.length) { + var nodeIndex = list.pop(); + node.nodeIndex = nodeIndex; + var classIndex = node.classIndex(); + var seen = !!seenClassNameIndexes[classIndex]; + var nodeOrdinal = nodeIndex / nodeFieldCount; + var dominatedIndexFrom = firstDominatedNodeIndex[nodeOrdinal]; + var dominatedIndexTo = firstDominatedNodeIndex[nodeOrdinal + 1]; + + if (!seen && + (!flags || (flags[nodeOrdinal] & flag)) && + (!filter || filter(node)) && + (node.selfSize() || nodes[nodeIndex + nodeTypeOffset] === nodeNativeType) + ) { + aggregates[classIndex].maxRet += node.retainedSize(); + if (dominatedIndexFrom !== dominatedIndexTo) { + seenClassNameIndexes[classIndex] = true; + sizes.push(list.length); + classes.push(classIndex); + } + } + for (var i = dominatedIndexFrom; i < dominatedIndexTo; i++) + list.push(dominatedNodes[i]); + + var l = list.length; + while (sizes[sizes.length - 1] === l) { + sizes.pop(); + classIndex = classes.pop(); + seenClassNameIndexes[classIndex] = false; + } + } + }, + + _sortAggregateIndexes: function(aggregates) + { + var nodeA = this.createNode(); + var nodeB = this.createNode(); + for (var clss in aggregates) + aggregates[clss].idxs.sort( + function(idxA, idxB) { + nodeA.nodeIndex = idxA; + nodeB.nodeIndex = idxB; + return nodeA.id() < nodeB.id() ? -1 : 1; + }); + }, + + _buildPostOrderIndex: function() + { + var nodeFieldCount = this._nodeFieldCount; + var nodes = this._nodes; + var nodeCount = this.nodeCount; + var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount; + + var edgeFieldsCount = this._edgeFieldsCount; + var edgeTypeOffset = this._edgeTypeOffset; + var edgeToNodeOffset = this._edgeToNodeOffset; + var edgeShortcutType = this._edgeShortcutType; + var firstEdgeIndexes = this._firstEdgeIndexes; + var containmentEdges = this._containmentEdges; + + var mapAndFlag = this.userObjectsMapAndFlag(); + var flags = mapAndFlag ? mapAndFlag.map : null; + var flag = mapAndFlag ? mapAndFlag.flag : 0; + + var stackNodes = new Uint32Array(nodeCount); + var stackCurrentEdge = new Uint32Array(nodeCount); + var postOrderIndex2NodeOrdinal = new Uint32Array(nodeCount); + var nodeOrdinal2PostOrderIndex = new Uint32Array(nodeCount); + var visited = new Uint8Array(nodeCount); + var postOrderIndex = 0; + + var stackTop = 0; + stackNodes[0] = rootNodeOrdinal; + stackCurrentEdge[0] = firstEdgeIndexes[rootNodeOrdinal]; + visited[rootNodeOrdinal] = 1; + + while (stackTop >= 0) { + var nodeOrdinal = stackNodes[stackTop]; + var edgeIndex = stackCurrentEdge[stackTop]; + var edgesEnd = firstEdgeIndexes[nodeOrdinal + 1]; + + if (edgeIndex < edgesEnd) { + stackCurrentEdge[stackTop] += edgeFieldsCount; + if (nodeOrdinal !== rootNodeOrdinal && containmentEdges[edgeIndex + edgeTypeOffset] === edgeShortcutType) + continue; + var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; + var childNodeOrdinal = childNodeIndex / nodeFieldCount; + if (visited[childNodeOrdinal]) + continue; + var nodeFlag = !flags || (flags[nodeOrdinal] & flag); + var childNodeFlag = !flags || (flags[childNodeOrdinal] & flag); + // We are skipping the edges from non-page-owned nodes to page-owned nodes. + // Otherwise the dominators for the objects that also were retained by debugger would be affected. + if (nodeOrdinal !== rootNodeOrdinal && childNodeFlag && !nodeFlag) + continue; + ++stackTop; + stackNodes[stackTop] = childNodeOrdinal; + stackCurrentEdge[stackTop] = firstEdgeIndexes[childNodeOrdinal]; + visited[childNodeOrdinal] = 1; + } else { + // Done with all the node children + nodeOrdinal2PostOrderIndex[nodeOrdinal] = postOrderIndex; + postOrderIndex2NodeOrdinal[postOrderIndex++] = nodeOrdinal; + --stackTop; + } + } + + if (postOrderIndex !== nodeCount) { + console.log("Error: Corrupted snapshot. " + (nodeCount - postOrderIndex) + " nodes are unreachable from the root:"); + var dumpNode = this.rootNode(); + for (var i = 0; i < nodeCount; ++i) { + if (!visited[i]) { + // Fix it by giving the node a postorder index anyway. + nodeOrdinal2PostOrderIndex[i] = postOrderIndex; + postOrderIndex2NodeOrdinal[postOrderIndex++] = i; + dumpNode.nodeIndex = i * nodeFieldCount; + console.log(JSON.stringify(dumpNode.serialize())); + for (var retainers = dumpNode.retainers(); retainers.hasNext(); retainers = retainers.item().node().retainers()) + console.log(" edgeName: " + retainers.item().name() + " nodeClassName: " + retainers.item().node().className()); + } + } + } + + return {postOrderIndex2NodeOrdinal: postOrderIndex2NodeOrdinal, nodeOrdinal2PostOrderIndex: nodeOrdinal2PostOrderIndex}; + }, + + // The algorithm is based on the article: + // K. Cooper, T. Harvey and K. Kennedy "A Simple, Fast Dominance Algorithm" + // Softw. Pract. Exper. 4 (2001), pp. 1-10. + /** + * @param {!Array.<number>} postOrderIndex2NodeOrdinal + * @param {!Array.<number>} nodeOrdinal2PostOrderIndex + */ + _buildDominatorTree: function(postOrderIndex2NodeOrdinal, nodeOrdinal2PostOrderIndex) + { + var nodeFieldCount = this._nodeFieldCount; + var nodes = this._nodes; + var firstRetainerIndex = this._firstRetainerIndex; + var retainingNodes = this._retainingNodes; + var retainingEdges = this._retainingEdges; + var edgeFieldsCount = this._edgeFieldsCount; + var edgeTypeOffset = this._edgeTypeOffset; + var edgeToNodeOffset = this._edgeToNodeOffset; + var edgeShortcutType = this._edgeShortcutType; + var firstEdgeIndexes = this._firstEdgeIndexes; + var containmentEdges = this._containmentEdges; + var containmentEdgesLength = this._containmentEdges.length; + var rootNodeIndex = this._rootNodeIndex; + + var mapAndFlag = this.userObjectsMapAndFlag(); + var flags = mapAndFlag ? mapAndFlag.map : null; + var flag = mapAndFlag ? mapAndFlag.flag : 0; + + var nodesCount = postOrderIndex2NodeOrdinal.length; + var rootPostOrderedIndex = nodesCount - 1; + var noEntry = nodesCount; + var dominators = new Uint32Array(nodesCount); + for (var i = 0; i < rootPostOrderedIndex; ++i) + dominators[i] = noEntry; + dominators[rootPostOrderedIndex] = rootPostOrderedIndex; + + // The affected array is used to mark entries which dominators + // have to be racalculated because of changes in their retainers. + var affected = new Uint8Array(nodesCount); + var nodeOrdinal; + + { // Mark the root direct children as affected. + nodeOrdinal = this._rootNodeIndex / nodeFieldCount; + var beginEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal] + edgeToNodeOffset; + var endEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal + 1]; + for (var toNodeFieldIndex = beginEdgeToNodeFieldIndex; + toNodeFieldIndex < endEdgeToNodeFieldIndex; + toNodeFieldIndex += edgeFieldsCount) { + var childNodeOrdinal = containmentEdges[toNodeFieldIndex] / nodeFieldCount; + affected[nodeOrdinal2PostOrderIndex[childNodeOrdinal]] = 1; + } + } + + var changed = true; + while (changed) { + changed = false; + for (var postOrderIndex = rootPostOrderedIndex - 1; postOrderIndex >= 0; --postOrderIndex) { + if (affected[postOrderIndex] === 0) + continue; + affected[postOrderIndex] = 0; + // If dominator of the entry has already been set to root, + // then it can't propagate any further. + if (dominators[postOrderIndex] === rootPostOrderedIndex) + continue; + nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex]; + var nodeFlag = !flags || (flags[nodeOrdinal] & flag); + var newDominatorIndex = noEntry; + var beginRetainerIndex = firstRetainerIndex[nodeOrdinal]; + var endRetainerIndex = firstRetainerIndex[nodeOrdinal + 1]; + for (var retainerIndex = beginRetainerIndex; retainerIndex < endRetainerIndex; ++retainerIndex) { + var retainerEdgeIndex = retainingEdges[retainerIndex]; + var retainerEdgeType = containmentEdges[retainerEdgeIndex + edgeTypeOffset]; + var retainerNodeIndex = retainingNodes[retainerIndex]; + if (retainerNodeIndex !== rootNodeIndex && retainerEdgeType === edgeShortcutType) + continue; + var retainerNodeOrdinal = retainerNodeIndex / nodeFieldCount; + var retainerNodeFlag = !flags || (flags[retainerNodeOrdinal] & flag); + // We are skipping the edges from non-page-owned nodes to page-owned nodes. + // Otherwise the dominators for the objects that also were retained by debugger would be affected. + if (retainerNodeIndex !== rootNodeIndex && nodeFlag && !retainerNodeFlag) + continue; + var retanerPostOrderIndex = nodeOrdinal2PostOrderIndex[retainerNodeOrdinal]; + if (dominators[retanerPostOrderIndex] !== noEntry) { + if (newDominatorIndex === noEntry) + newDominatorIndex = retanerPostOrderIndex; + else { + while (retanerPostOrderIndex !== newDominatorIndex) { + while (retanerPostOrderIndex < newDominatorIndex) + retanerPostOrderIndex = dominators[retanerPostOrderIndex]; + while (newDominatorIndex < retanerPostOrderIndex) + newDominatorIndex = dominators[newDominatorIndex]; + } + } + // If idom has already reached the root, it doesn't make sense + // to check other retainers. + if (newDominatorIndex === rootPostOrderedIndex) + break; + } + } + if (newDominatorIndex !== noEntry && dominators[postOrderIndex] !== newDominatorIndex) { + dominators[postOrderIndex] = newDominatorIndex; + changed = true; + nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex]; + beginEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal] + edgeToNodeOffset; + endEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal + 1]; + for (var toNodeFieldIndex = beginEdgeToNodeFieldIndex; + toNodeFieldIndex < endEdgeToNodeFieldIndex; + toNodeFieldIndex += edgeFieldsCount) { + var childNodeOrdinal = containmentEdges[toNodeFieldIndex] / nodeFieldCount; + affected[nodeOrdinal2PostOrderIndex[childNodeOrdinal]] = 1; + } + } + } + } + + var dominatorsTree = new Uint32Array(nodesCount); + for (var postOrderIndex = 0, l = dominators.length; postOrderIndex < l; ++postOrderIndex) { + nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex]; + dominatorsTree[nodeOrdinal] = postOrderIndex2NodeOrdinal[dominators[postOrderIndex]]; + } + return dominatorsTree; + }, + + _calculateRetainedSizes: function(postOrderIndex2NodeOrdinal) + { + var nodeCount = this.nodeCount; + var nodes = this._nodes; + var nodeSelfSizeOffset = this._nodeSelfSizeOffset; + var nodeFieldCount = this._nodeFieldCount; + var dominatorsTree = this._dominatorsTree; + var retainedSizes = this._retainedSizes = new Float64Array(nodeCount); + + for (var nodeOrdinal = 0; nodeOrdinal < nodeCount; ++nodeOrdinal) + retainedSizes[nodeOrdinal] = nodes[nodeOrdinal * nodeFieldCount + nodeSelfSizeOffset]; + + // Propagate retained sizes for each node excluding root. + for (var postOrderIndex = 0; postOrderIndex < nodeCount - 1; ++postOrderIndex) { + var nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex]; + var dominatorOrdinal = dominatorsTree[nodeOrdinal]; + retainedSizes[dominatorOrdinal] += retainedSizes[nodeOrdinal]; + } + }, + + _buildDominatedNodes: function() + { + // Builds up two arrays: + // - "dominatedNodes" is a continuous array, where each node owns an + // interval (can be empty) with corresponding dominated nodes. + // - "indexArray" is an array of indexes in the "dominatedNodes" + // with the same positions as in the _nodeIndex. + var indexArray = this._firstDominatedNodeIndex = new Uint32Array(this.nodeCount + 1); + // All nodes except the root have dominators. + var dominatedNodes = this._dominatedNodes = new Uint32Array(this.nodeCount - 1); + + // Count the number of dominated nodes for each node. Skip the root (node at + // index 0) as it is the only node that dominates itself. + var nodeFieldCount = this._nodeFieldCount; + var dominatorsTree = this._dominatorsTree; + + var fromNodeOrdinal = 0; + var toNodeOrdinal = this.nodeCount; + var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount; + if (rootNodeOrdinal === fromNodeOrdinal) + fromNodeOrdinal = 1; + else if (rootNodeOrdinal === toNodeOrdinal - 1) + toNodeOrdinal = toNodeOrdinal - 1; + else + throw new Error("Root node is expected to be either first or last"); + for (var nodeOrdinal = fromNodeOrdinal; nodeOrdinal < toNodeOrdinal; ++nodeOrdinal) + ++indexArray[dominatorsTree[nodeOrdinal]]; + // Put in the first slot of each dominatedNodes slice the count of entries + // that will be filled. + var firstDominatedNodeIndex = 0; + for (var i = 0, l = this.nodeCount; i < l; ++i) { + var dominatedCount = dominatedNodes[firstDominatedNodeIndex] = indexArray[i]; + indexArray[i] = firstDominatedNodeIndex; + firstDominatedNodeIndex += dominatedCount; + } + indexArray[this.nodeCount] = dominatedNodes.length; + // Fill up the dominatedNodes array with indexes of dominated nodes. Skip the root (node at + // index 0) as it is the only node that dominates itself. + for (var nodeOrdinal = fromNodeOrdinal; nodeOrdinal < toNodeOrdinal; ++nodeOrdinal) { + var dominatorOrdinal = dominatorsTree[nodeOrdinal]; + var dominatedRefIndex = indexArray[dominatorOrdinal]; + dominatedRefIndex += (--dominatedNodes[dominatedRefIndex]); + dominatedNodes[dominatedRefIndex] = nodeOrdinal * nodeFieldCount; + } + }, + + _calculateFlags: function() + { + throw new Error("Not implemented"); + }, + + _calculateStatistics: function() + { + throw new Error("Not implemented"); + }, + + userObjectsMapAndFlag: function() + { + throw new Error("Not implemented"); + }, + + /** + * @param {string} baseSnapshotId + * @param {!Object.<string, !WebInspector.HeapSnapshotCommon.AggregateForDiff>} baseSnapshotAggregates + * @return {!Object.<string, !WebInspector.HeapSnapshotCommon.Diff>} + */ + calculateSnapshotDiff: function(baseSnapshotId, baseSnapshotAggregates) + { + var snapshotDiff = this._snapshotDiffs[baseSnapshotId]; + if (snapshotDiff) + return snapshotDiff; + snapshotDiff = {}; + + var aggregates = this.aggregates(true, "allObjects"); + for (var className in baseSnapshotAggregates) { + var baseAggregate = baseSnapshotAggregates[className]; + var diff = this._calculateDiffForClass(baseAggregate, aggregates[className]); + if (diff) + snapshotDiff[className] = diff; + } + var emptyBaseAggregate = new WebInspector.HeapSnapshotCommon.AggregateForDiff(); + for (var className in aggregates) { + if (className in baseSnapshotAggregates) + continue; + snapshotDiff[className] = this._calculateDiffForClass(emptyBaseAggregate, aggregates[className]); + } + + this._snapshotDiffs[baseSnapshotId] = snapshotDiff; + return snapshotDiff; + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.AggregateForDiff} baseAggregate + * @param {!WebInspector.HeapSnapshotCommon.Aggregate} aggregate + * @return {?WebInspector.HeapSnapshotCommon.Diff} + */ + _calculateDiffForClass: function(baseAggregate, aggregate) + { + var baseIds = baseAggregate.ids; + var baseIndexes = baseAggregate.indexes; + var baseSelfSizes = baseAggregate.selfSizes; + + var indexes = aggregate ? aggregate.idxs : []; + + var i = 0, l = baseIds.length; + var j = 0, m = indexes.length; + var diff = new WebInspector.HeapSnapshotCommon.Diff(); + + var nodeB = this.createNode(indexes[j]); + while (i < l && j < m) { + var nodeAId = baseIds[i]; + if (nodeAId < nodeB.id()) { + diff.deletedIndexes.push(baseIndexes[i]); + diff.removedCount++; + diff.removedSize += baseSelfSizes[i]; + ++i; + } else if (nodeAId > nodeB.id()) { // Native nodes(e.g. dom groups) may have ids less than max JS object id in the base snapshot + diff.addedIndexes.push(indexes[j]); + diff.addedCount++; + diff.addedSize += nodeB.selfSize(); + nodeB.nodeIndex = indexes[++j]; + } else { // nodeAId === nodeB.id() + ++i; + nodeB.nodeIndex = indexes[++j]; + } + } + while (i < l) { + diff.deletedIndexes.push(baseIndexes[i]); + diff.removedCount++; + diff.removedSize += baseSelfSizes[i]; + ++i; + } + while (j < m) { + diff.addedIndexes.push(indexes[j]); + diff.addedCount++; + diff.addedSize += nodeB.selfSize(); + nodeB.nodeIndex = indexes[++j]; + } + diff.countDelta = diff.addedCount - diff.removedCount; + diff.sizeDelta = diff.addedSize - diff.removedSize; + if (!diff.addedCount && !diff.removedCount) + return null; + return diff; + }, + + _nodeForSnapshotObjectId: function(snapshotObjectId) + { + for (var it = this._allNodes(); it.hasNext(); it.next()) { + if (it.node.id() === snapshotObjectId) + return it.node; + } + return null; + }, + + /** + * @param {string} snapshotObjectId + * @return {?string} + */ + nodeClassName: function(snapshotObjectId) + { + var node = this._nodeForSnapshotObjectId(snapshotObjectId); + if (node) + return node.className(); + return null; + }, + + /** + * @param {string} name + * @return {!Array.<number>} + */ + idsOfObjectsWithName: function(name) + { + var ids = []; + for (var it = this._allNodes(); it.hasNext(); it.next()) { + if (it.item().name() === name) + ids.push(it.item().id()); + } + return ids; + }, + + /** + * @param {string} snapshotObjectId + * @return {?Array.<string>} + */ + dominatorIdsForNode: function(snapshotObjectId) + { + var node = this._nodeForSnapshotObjectId(snapshotObjectId); + if (!node) + return null; + var result = []; + while (!node.isRoot()) { + result.push(node.id()); + node.nodeIndex = node.dominatorIndex(); + } + return result; + }, + + /** + * @param {number} nodeIndex + * @return {!WebInspector.HeapSnapshotEdgesProvider} + */ + createEdgesProvider: function(nodeIndex) + { + var node = this.createNode(nodeIndex); + var filter = this.containmentEdgesFilter(); + var indexProvider = new WebInspector.HeapSnapshotEdgeIndexProvider(this); + return new WebInspector.HeapSnapshotEdgesProvider(this, filter, node.edges(), indexProvider); + }, + + /** + * @param {number} nodeIndex + * @param {?function(!WebInspector.HeapSnapshotEdge):boolean} filter + * @return {!WebInspector.HeapSnapshotEdgesProvider} + */ + createEdgesProviderForTest: function(nodeIndex, filter) + { + var node = this.createNode(nodeIndex); + var indexProvider = new WebInspector.HeapSnapshotEdgeIndexProvider(this); + return new WebInspector.HeapSnapshotEdgesProvider(this, filter, node.edges(), indexProvider); + }, + + /** + * @return {?function(!WebInspector.HeapSnapshotEdge):boolean} + */ + retainingEdgesFilter: function() + { + return null; + }, + + /** + * @return {?function(!WebInspector.HeapSnapshotEdge):boolean} + */ + containmentEdgesFilter: function() + { + return null; + }, + + /** + * @param {number} nodeIndex + * @return {!WebInspector.HeapSnapshotEdgesProvider} + */ + createRetainingEdgesProvider: function(nodeIndex) + { + var node = this.createNode(nodeIndex); + var filter = this.retainingEdgesFilter(); + var indexProvider = new WebInspector.HeapSnapshotRetainerEdgeIndexProvider(this); + return new WebInspector.HeapSnapshotEdgesProvider(this, filter, node.retainers(), indexProvider); + }, + + /** + * @param {string} baseSnapshotId + * @param {string} className + * @return {!WebInspector.HeapSnapshotNodesProvider} + */ + createAddedNodesProvider: function(baseSnapshotId, className) + { + var snapshotDiff = this._snapshotDiffs[baseSnapshotId]; + var diffForClass = snapshotDiff[className]; + return new WebInspector.HeapSnapshotNodesProvider(this, null, diffForClass.addedIndexes); + }, + + /** + * @param {!Array.<number>} nodeIndexes + * @return {!WebInspector.HeapSnapshotNodesProvider} + */ + createDeletedNodesProvider: function(nodeIndexes) + { + return new WebInspector.HeapSnapshotNodesProvider(this, null, nodeIndexes); + }, + + /** + * @return {?function(!WebInspector.HeapSnapshotNode):boolean} + */ + classNodesFilter: function() + { + return null; + }, + + /** + * @param {string} className + * @param {!WebInspector.HeapSnapshotCommon.NodeFilter} nodeFilter + * @return {!WebInspector.HeapSnapshotNodesProvider} + */ + createNodesProviderForClass: function(className, nodeFilter) + { + return new WebInspector.HeapSnapshotNodesProvider(this, this.classNodesFilter(), this.aggregatesWithFilter(nodeFilter)[className].idxs); + }, + + /** + * @param {number} nodeIndex + * @return {!WebInspector.HeapSnapshotNodesProvider} + */ + createNodesProviderForDominator: function(nodeIndex) + { + var node = this.createNode(nodeIndex); + return new WebInspector.HeapSnapshotNodesProvider(this, null, this._dominatedNodesOfNode(node)); + }, + + /** + * @return {number} + */ + _maxJsNodeId: function() + { + var nodeFieldCount = this._nodeFieldCount; + var nodes = this._nodes; + var nodesLength = nodes.length; + var id = 0; + for (var nodeIndex = this._nodeIdOffset; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { + var nextId = nodes[nodeIndex]; + // JS objects have odd ids, skip native objects. + if (nextId % 2 === 0) + continue; + if (id < nextId) + id = nextId; + } + return id; + }, + + /** + * @return {!WebInspector.HeapSnapshotCommon.StaticData} + */ + updateStaticData: function() + { + return new WebInspector.HeapSnapshotCommon.StaticData(this.nodeCount, this._rootNodeIndex, this.totalSize, this._maxJsNodeId()); + } +}; + +/** + * @constructor + * @param {!WebInspector.HeapSnapshotItemIterator} iterator + * @param {!WebInspector.HeapSnapshotItemIndexProvider} indexProvider + */ +WebInspector.HeapSnapshotItemProvider = function(iterator, indexProvider) +{ + this._iterator = iterator; + this._indexProvider = indexProvider; + this._isEmpty = !iterator.hasNext(); + /** @type {?Array.<number>} */ + this._iterationOrder = null; + this._currentComparator = null; + this._sortedPrefixLength = 0; + this._sortedSuffixLength = 0; +} + +WebInspector.HeapSnapshotItemProvider.prototype = { + _createIterationOrder: function() + { + if (this._iterationOrder) + return; + this._iterationOrder = []; + for (var iterator = this._iterator; iterator.hasNext(); iterator.next()) + this._iterationOrder.push(iterator.item().itemIndex()); + }, + + /** + * @return {boolean} + */ + isEmpty: function() + { + return this._isEmpty; + }, + + /** + * @param {number} begin + * @param {number} end + * @return {!WebInspector.HeapSnapshotCommon.ItemsRange} + */ + serializeItemsRange: function(begin, end) + { + this._createIterationOrder(); + if (begin > end) + throw new Error("Start position > end position: " + begin + " > " + end); + if (end > this._iterationOrder.length) + end = this._iterationOrder.length; + if (this._sortedPrefixLength < end && begin < this._iterationOrder.length - this._sortedSuffixLength) { + this.sort(this._currentComparator, this._sortedPrefixLength, this._iterationOrder.length - 1 - this._sortedSuffixLength, begin, end - 1); + if (begin <= this._sortedPrefixLength) + this._sortedPrefixLength = end; + if (end >= this._iterationOrder.length - this._sortedSuffixLength) + this._sortedSuffixLength = this._iterationOrder.length - begin; + } + var position = begin; + var count = end - begin; + var result = new Array(count); + var iterator = this._iterator; + for (var i = 0 ; i < count; ++i) { + var itemIndex = this._iterationOrder[position++]; + var item = this._indexProvider.itemForIndex(itemIndex); + result[i] = item.serialize(); + } + return new WebInspector.HeapSnapshotCommon.ItemsRange(begin, end, this._iterationOrder.length, result); + }, + + sortAndRewind: function(comparator) + { + this._currentComparator = comparator; + this._sortedPrefixLength = 0; + this._sortedSuffixLength = 0; + } +} + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotItemProvider} + * @param {!WebInspector.HeapSnapshot} snapshot + * @param {?function(!WebInspector.HeapSnapshotEdge):boolean} filter + * @param {!WebInspector.HeapSnapshotEdgeIterator} edgesIter + * @param {!WebInspector.HeapSnapshotItemIndexProvider} indexProvider + */ +WebInspector.HeapSnapshotEdgesProvider = function(snapshot, filter, edgesIter, indexProvider) +{ + this.snapshot = snapshot; + var iter = filter ? new WebInspector.HeapSnapshotFilteredIterator(edgesIter, /** @type {function(!WebInspector.HeapSnapshotItem):boolean} */ (filter)) : edgesIter; + WebInspector.HeapSnapshotItemProvider.call(this, iter, indexProvider); +} + +WebInspector.HeapSnapshotEdgesProvider.prototype = { + /** + * @param {!WebInspector.HeapSnapshotCommon.ComparatorConfig} comparator + * @param {number} leftBound + * @param {number} rightBound + * @param {number} windowLeft + * @param {number} windowRight + */ + sort: function(comparator, leftBound, rightBound, windowLeft, windowRight) + { + var fieldName1 = comparator.fieldName1; + var fieldName2 = comparator.fieldName2; + var ascending1 = comparator.ascending1; + var ascending2 = comparator.ascending2; + + var edgeA = this._iterator.item().clone(); + var edgeB = edgeA.clone(); + var nodeA = this.snapshot.createNode(); + var nodeB = this.snapshot.createNode(); + + function compareEdgeFieldName(ascending, indexA, indexB) + { + edgeA.edgeIndex = indexA; + edgeB.edgeIndex = indexB; + if (edgeB.name() === "__proto__") return -1; + if (edgeA.name() === "__proto__") return 1; + var result = + edgeA.hasStringName() === edgeB.hasStringName() ? + (edgeA.name() < edgeB.name() ? -1 : (edgeA.name() > edgeB.name() ? 1 : 0)) : + (edgeA.hasStringName() ? -1 : 1); + return ascending ? result : -result; + } + + function compareNodeField(fieldName, ascending, indexA, indexB) + { + edgeA.edgeIndex = indexA; + nodeA.nodeIndex = edgeA.nodeIndex(); + var valueA = nodeA[fieldName](); + + edgeB.edgeIndex = indexB; + nodeB.nodeIndex = edgeB.nodeIndex(); + var valueB = nodeB[fieldName](); + + var result = valueA < valueB ? -1 : (valueA > valueB ? 1 : 0); + return ascending ? result : -result; + } + + function compareEdgeAndNode(indexA, indexB) { + var result = compareEdgeFieldName(ascending1, indexA, indexB); + if (result === 0) + result = compareNodeField(fieldName2, ascending2, indexA, indexB); + if (result === 0) + return indexA - indexB; + return result; + } + + function compareNodeAndEdge(indexA, indexB) { + var result = compareNodeField(fieldName1, ascending1, indexA, indexB); + if (result === 0) + result = compareEdgeFieldName(ascending2, indexA, indexB); + if (result === 0) + return indexA - indexB; + return result; + } + + function compareNodeAndNode(indexA, indexB) { + var result = compareNodeField(fieldName1, ascending1, indexA, indexB); + if (result === 0) + result = compareNodeField(fieldName2, ascending2, indexA, indexB); + if (result === 0) + return indexA - indexB; + return result; + } + + if (fieldName1 === "!edgeName") + this._iterationOrder.sortRange(compareEdgeAndNode, leftBound, rightBound, windowLeft, windowRight); + else if (fieldName2 === "!edgeName") + this._iterationOrder.sortRange(compareNodeAndEdge, leftBound, rightBound, windowLeft, windowRight); + else + this._iterationOrder.sortRange(compareNodeAndNode, leftBound, rightBound, windowLeft, windowRight); + }, + + __proto__: WebInspector.HeapSnapshotItemProvider.prototype +} + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotItemProvider} + * @param {!WebInspector.HeapSnapshot} snapshot + * @param {?function(!WebInspector.HeapSnapshotNode):boolean} filter + * @param {(!Array.<number>|!Uint32Array)} nodeIndexes + */ +WebInspector.HeapSnapshotNodesProvider = function(snapshot, filter, nodeIndexes) +{ + this.snapshot = snapshot; + var indexProvider = new WebInspector.HeapSnapshotNodeIndexProvider(snapshot); + var it = new WebInspector.HeapSnapshotIndexRangeIterator(indexProvider, nodeIndexes); + + if (filter) + it = new WebInspector.HeapSnapshotFilteredIterator(it, /** @type {function(!WebInspector.HeapSnapshotItem):boolean} */ (filter)); + WebInspector.HeapSnapshotItemProvider.call(this, it, indexProvider); +} + +WebInspector.HeapSnapshotNodesProvider.prototype = { + /** + * @param {string} snapshotObjectId + * @return {number} + */ + nodePosition: function(snapshotObjectId) + { + this._createIterationOrder(); + var node = this.snapshot.createNode(); + for (var i = 0; i < this._iterationOrder.length; i++) { + node.nodeIndex = this._iterationOrder[i]; + if (node.id() === snapshotObjectId) + break; + } + if (i === this._iterationOrder.length) + return -1; + var targetNodeIndex = this._iterationOrder[i]; + var smallerCount = 0; + var compare = this._buildCompareFunction(this._currentComparator); + for (var i = 0; i < this._iterationOrder.length; i++) { + if (compare(this._iterationOrder[i], targetNodeIndex) < 0) + ++smallerCount; + } + return smallerCount; + }, + + /** + * @return {function(number,number):number} + */ + _buildCompareFunction: function(comparator) + { + var nodeA = this.snapshot.createNode(); + var nodeB = this.snapshot.createNode(); + var fieldAccessor1 = nodeA[comparator.fieldName1]; + var fieldAccessor2 = nodeA[comparator.fieldName2]; + var ascending1 = comparator.ascending1 ? 1 : -1; + var ascending2 = comparator.ascending2 ? 1 : -1; + + /** + * @param {function():*} fieldAccessor + * @param {number} ascending + * @return {number} + */ + function sortByNodeField(fieldAccessor, ascending) + { + var valueA = fieldAccessor.call(nodeA); + var valueB = fieldAccessor.call(nodeB); + return valueA < valueB ? -ascending : (valueA > valueB ? ascending : 0); + } + + /** + * @param {number} indexA + * @param {number} indexB + * @return {number} + */ + function sortByComparator(indexA, indexB) + { + nodeA.nodeIndex = indexA; + nodeB.nodeIndex = indexB; + var result = sortByNodeField(fieldAccessor1, ascending1); + if (result === 0) + result = sortByNodeField(fieldAccessor2, ascending2); + return result || indexA - indexB; + } + + return sortByComparator; + }, + + /** + * @param {!WebInspector.HeapSnapshotCommon.ComparatorConfig} comparator + * @param {number} leftBound + * @param {number} rightBound + * @param {number} windowLeft + * @param {number} windowRight + */ + sort: function(comparator, leftBound, rightBound, windowLeft, windowRight) + { + this._iterationOrder.sortRange(this._buildCompareFunction(comparator), leftBound, rightBound, windowLeft, windowRight); + }, + + __proto__: WebInspector.HeapSnapshotItemProvider.prototype +} + diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/HeapSnapshotLoader.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/HeapSnapshotLoader.js new file mode 100644 index 00000000000..8285fa8ccf5 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/HeapSnapshotLoader.js @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2012 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. + */ + +/** + * @constructor + * @param {!WebInspector.HeapSnapshotWorkerDispatcher} dispatcher + */ +WebInspector.HeapSnapshotLoader = function(dispatcher) +{ + this._reset(); + this._progress = new WebInspector.HeapSnapshotProgress(dispatcher); +} + +WebInspector.HeapSnapshotLoader.prototype = { + dispose: function() + { + this._reset(); + }, + + _reset: function() + { + this._json = ""; + this._state = "find-snapshot-info"; + this._snapshot = {}; + }, + + close: function() + { + if (this._json) + this._parseStringsArray(); + }, + + /** + * @param {boolean} showHiddenData + * @return {!WebInspector.JSHeapSnapshot} + */ + buildSnapshot: function(showHiddenData) + { + this._progress.updateStatus("Processing snapshot\u2026"); + var result = new WebInspector.JSHeapSnapshot(this._snapshot, this._progress, showHiddenData); + this._reset(); + return result; + }, + + _parseUintArray: function() + { + var index = 0; + var char0 = "0".charCodeAt(0), char9 = "9".charCodeAt(0), closingBracket = "]".charCodeAt(0); + var length = this._json.length; + while (true) { + while (index < length) { + var code = this._json.charCodeAt(index); + if (char0 <= code && code <= char9) + break; + else if (code === closingBracket) { + this._json = this._json.slice(index + 1); + return false; + } + ++index; + } + if (index === length) { + this._json = ""; + return true; + } + var nextNumber = 0; + var startIndex = index; + while (index < length) { + var code = this._json.charCodeAt(index); + if (char0 > code || code > char9) + break; + nextNumber *= 10; + nextNumber += (code - char0); + ++index; + } + if (index === length) { + this._json = this._json.slice(startIndex); + return true; + } + this._array[this._arrayIndex++] = nextNumber; + } + }, + + _parseStringsArray: function() + { + this._progress.updateStatus("Parsing strings\u2026"); + var closingBracketIndex = this._json.lastIndexOf("]"); + if (closingBracketIndex === -1) + throw new Error("Incomplete JSON"); + this._json = this._json.slice(0, closingBracketIndex + 1); + this._snapshot.strings = JSON.parse(this._json); + }, + + /** + * @param {string} chunk + */ + write: function(chunk) + { + this._json += chunk; + while (true) { + switch (this._state) { + case "find-snapshot-info": { + var snapshotToken = "\"snapshot\""; + var snapshotTokenIndex = this._json.indexOf(snapshotToken); + if (snapshotTokenIndex === -1) + throw new Error("Snapshot token not found"); + this._json = this._json.slice(snapshotTokenIndex + snapshotToken.length + 1); + this._state = "parse-snapshot-info"; + this._progress.updateStatus("Loading snapshot info\u2026"); + break; + } + case "parse-snapshot-info": { + var closingBracketIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(this._json); + if (closingBracketIndex === -1) + return; + this._snapshot.snapshot = /** @type {!HeapSnapshotHeader} */ (JSON.parse(this._json.slice(0, closingBracketIndex))); + this._json = this._json.slice(closingBracketIndex); + this._state = "find-nodes"; + break; + } + case "find-nodes": { + var nodesToken = "\"nodes\""; + var nodesTokenIndex = this._json.indexOf(nodesToken); + if (nodesTokenIndex === -1) + return; + var bracketIndex = this._json.indexOf("[", nodesTokenIndex); + if (bracketIndex === -1) + return; + this._json = this._json.slice(bracketIndex + 1); + var node_fields_count = this._snapshot.snapshot.meta.node_fields.length; + var nodes_length = this._snapshot.snapshot.node_count * node_fields_count; + this._array = new Uint32Array(nodes_length); + this._arrayIndex = 0; + this._state = "parse-nodes"; + break; + } + case "parse-nodes": { + var hasMoreData = this._parseUintArray(); + this._progress.updateProgress("Loading nodes\u2026 %d\%", this._arrayIndex, this._array.length); + if (hasMoreData) + return; + this._snapshot.nodes = this._array; + this._state = "find-edges"; + this._array = null; + break; + } + case "find-edges": { + var edgesToken = "\"edges\""; + var edgesTokenIndex = this._json.indexOf(edgesToken); + if (edgesTokenIndex === -1) + return; + var bracketIndex = this._json.indexOf("[", edgesTokenIndex); + if (bracketIndex === -1) + return; + this._json = this._json.slice(bracketIndex + 1); + var edge_fields_count = this._snapshot.snapshot.meta.edge_fields.length; + var edges_length = this._snapshot.snapshot.edge_count * edge_fields_count; + this._array = new Uint32Array(edges_length); + this._arrayIndex = 0; + this._state = "parse-edges"; + break; + } + case "parse-edges": { + var hasMoreData = this._parseUintArray(); + this._progress.updateProgress("Loading edges\u2026 %d\%", this._arrayIndex, this._array.length); + if (hasMoreData) + return; + this._snapshot.edges = this._array; + this._array = null; + // If there is allocation info parse it, otherwise jump straight to strings. + if (this._snapshot.snapshot.trace_function_count) + this._state = "find-trace-function-infos"; + else + this._state = "find-strings"; + break; + } + case "find-trace-function-infos": { + var tracesToken = "\"trace_function_infos\""; + var tracesTokenIndex = this._json.indexOf(tracesToken); + if (tracesTokenIndex === -1) + return; + var bracketIndex = this._json.indexOf("[", tracesTokenIndex); + if (bracketIndex === -1) + return; + this._json = this._json.slice(bracketIndex + 1); + + var trace_function_info_field_count = this._snapshot.snapshot.meta.trace_function_info_fields.length; + var trace_function_info_length = this._snapshot.snapshot.trace_function_count * trace_function_info_field_count; + this._array = new Uint32Array(trace_function_info_length); + this._arrayIndex = 0; + this._state = "parse-trace-function-infos"; + break; + } + case "parse-trace-function-infos": { + if (this._parseUintArray()) + return; + this._snapshot.trace_function_infos = this._array; + this._array = null; + this._state = "find-trace-tree"; + break; + } + case "find-trace-tree": { + var tracesToken = "\"trace_tree\""; + var tracesTokenIndex = this._json.indexOf(tracesToken); + if (tracesTokenIndex === -1) + return; + var bracketIndex = this._json.indexOf("[", tracesTokenIndex); + if (bracketIndex === -1) + return; + this._json = this._json.slice(bracketIndex); + this._state = "parse-trace-tree"; + break; + } + case "parse-trace-tree": { + var stringsToken = "\"strings\""; + var stringsTokenIndex = this._json.indexOf(stringsToken); + if (stringsTokenIndex === -1) + return; + var bracketIndex = this._json.lastIndexOf("]", stringsTokenIndex); + this._snapshot.trace_tree = JSON.parse(this._json.substring(0, bracketIndex + 1)); + this._json = this._json.slice(bracketIndex); + this._state = "find-strings"; + this._progress.updateStatus("Loading strings\u2026"); + break; + } + case "find-strings": { + var stringsToken = "\"strings\""; + var stringsTokenIndex = this._json.indexOf(stringsToken); + if (stringsTokenIndex === -1) + return; + var bracketIndex = this._json.indexOf("[", stringsTokenIndex); + if (bracketIndex === -1) + return; + this._json = this._json.slice(bracketIndex); + this._state = "accumulate-strings"; + break; + } + case "accumulate-strings": + return; + } + } + } +}; diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/HeapSnapshotWorker.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/HeapSnapshotWorker.js new file mode 100644 index 00000000000..d187e2dfac9 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/HeapSnapshotWorker.js @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 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. + */ + +WebInspector = {}; + +importScripts("../../common/UIString.js"); +importScripts("../../common/utilities.js"); +importScripts("../../ui/TextUtils.js"); +importScripts("../HeapSnapshotCommon.js"); +importScripts("AllocationProfile.js"); +importScripts("HeapSnapshot.js"); +importScripts("HeapSnapshotLoader.js"); +importScripts("HeapSnapshotWorkerDispatcher.js"); +importScripts("JSHeapSnapshot.js"); + +function postMessageWrapper(message) +{ + postMessage(message); +} + +var dispatcher = new WebInspector.HeapSnapshotWorkerDispatcher(this, postMessageWrapper); +addEventListener("message", dispatcher.dispatchMessage.bind(dispatcher), false); diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.js new file mode 100644 index 00000000000..4f3daa763ff --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.js @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2011 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. + */ + +/** + * @constructor + */ +WebInspector.HeapSnapshotWorkerDispatcher = function(globalObject, postMessage) +{ + this._objects = []; + this._global = globalObject; + this._postMessage = postMessage; +} + +WebInspector.HeapSnapshotWorkerDispatcher.prototype = { + _findFunction: function(name) + { + var path = name.split("."); + var result = this._global; + for (var i = 0; i < path.length; ++i) + result = result[path[i]]; + return result; + }, + + /** + * @param {string} name + * @param {*} data + */ + sendEvent: function(name, data) + { + this._postMessage({eventName: name, data: data}); + }, + + dispatchMessage: function(event) + { + var data = /** @type {!WebInspector.HeapSnapshotCommon.WorkerCommand } */(event.data); + var response = {callId: data.callId}; + try { + switch (data.disposition) { + case "create": { + var constructorFunction = this._findFunction(data.methodName); + this._objects[data.objectId] = new constructorFunction(this); + break; + } + case "dispose": { + delete this._objects[data.objectId]; + break; + } + case "getter": { + var object = this._objects[data.objectId]; + var result = object[data.methodName]; + response.result = result; + break; + } + case "factory": { + var object = this._objects[data.objectId]; + var result = object[data.methodName].apply(object, data.methodArguments); + if (result) + this._objects[data.newObjectId] = result; + response.result = !!result; + break; + } + case "method": { + var object = this._objects[data.objectId]; + response.result = object[data.methodName].apply(object, data.methodArguments); + break; + } + case "evaluateForTest": { + try { + response.result = eval(data.source) + } catch (e) { + response.result = e.toString(); + } + break; + } + } + } catch (e) { + response.error = e.toString(); + response.errorCallStack = e.stack; + if (data.methodName) + response.errorMethodName = data.methodName; + } + this._postMessage(response); + } +}; diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/JSHeapSnapshot.js b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/JSHeapSnapshot.js new file mode 100644 index 00000000000..f1c592ab7e6 --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/heap_snapshot_worker/JSHeapSnapshot.js @@ -0,0 +1,824 @@ +/* + * Copyright (C) 2012 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. + */ + +/** + * @constructor + * @extends {WebInspector.HeapSnapshot} + * @param {!Object} profile + * @param {!WebInspector.HeapSnapshotProgress} progress + * @param {boolean} showHiddenData + */ +WebInspector.JSHeapSnapshot = function(profile, progress, showHiddenData) +{ + this._nodeFlags = { // bit flags + canBeQueried: 1, + detachedDOMTreeNode: 2, + pageObject: 4, // The idea is to track separately the objects owned by the page and the objects owned by debugger. + + visitedMarkerMask: 0x0ffff, // bits: 0,1111,1111,1111,1111 + visitedMarker: 0x10000 // bits: 1,0000,0000,0000,0000 + }; + this._lazyStringCache = { }; + WebInspector.HeapSnapshot.call(this, profile, progress, showHiddenData); +} + +WebInspector.JSHeapSnapshot.prototype = { + /** + * @param {number} nodeIndex + * @return {!WebInspector.JSHeapSnapshotNode} + */ + createNode: function(nodeIndex) + { + return new WebInspector.JSHeapSnapshotNode(this, nodeIndex); + }, + + /** + * @override + * @param {number} edgeIndex + * @return {!WebInspector.JSHeapSnapshotEdge} + */ + createEdge: function(edgeIndex) + { + return new WebInspector.JSHeapSnapshotEdge(this, edgeIndex); + }, + + /** + * @override + * @param {number} retainerIndex + * @return {!WebInspector.JSHeapSnapshotRetainerEdge} + */ + createRetainingEdge: function(retainerIndex) + { + return new WebInspector.JSHeapSnapshotRetainerEdge(this, retainerIndex); + }, + + /** + * @override + * @return {?function(!WebInspector.JSHeapSnapshotNode):boolean} + */ + classNodesFilter: function() + { + /** + * @param {!WebInspector.JSHeapSnapshotNode} node + * @return {boolean} + */ + function filter(node) + { + return node.isUserObject(); + } + return this._showHiddenData ? null : filter; + }, + + /** + * @return {function(!WebInspector.HeapSnapshotEdge):boolean} + */ + containmentEdgesFilter: function() + { + var showHiddenData = this._showHiddenData; + function filter(edge) { + if (edge.isInvisible()) + return false; + if (showHiddenData) + return true; + return !edge.isHidden() && !edge.node().isHidden(); + } + return filter; + }, + + /** + * @return {function(!WebInspector.HeapSnapshotEdge):boolean} + */ + retainingEdgesFilter: function() + { + var containmentEdgesFilter = this.containmentEdgesFilter(); + function filter(edge) + { + return containmentEdgesFilter(edge) && !edge.node().isRoot() && !edge.isWeak(); + } + return filter; + }, + + dispose: function() + { + WebInspector.HeapSnapshot.prototype.dispose.call(this); + delete this._flags; + }, + + _calculateFlags: function() + { + this._flags = new Uint32Array(this.nodeCount); + this._markDetachedDOMTreeNodes(); + this._markQueriableHeapObjects(); + this._markPageOwnedNodes(); + }, + + /** + * @param {!WebInspector.HeapSnapshotNode} node + * @return {!boolean} + */ + _isUserRoot: function(node) + { + return node.isUserRoot() || node.isDocumentDOMTreesRoot(); + }, + + /** + * @param {function(!WebInspector.HeapSnapshotNode)} action + * @param {boolean=} userRootsOnly + */ + forEachRoot: function(action, userRootsOnly) + { + /** + * @param {!WebInspector.HeapSnapshotNode} node + * @param {string} name + * @return {?WebInspector.HeapSnapshotNode} + */ + function getChildNodeByName(node, name) + { + for (var iter = node.edges(); iter.hasNext(); iter.next()) { + var child = iter.edge.node(); + if (child.name() === name) + return child; + } + return null; + } + + var visitedNodes = {}; + /** + * @param {!WebInspector.HeapSnapshotNode} node + */ + function doAction(node) + { + var ordinal = node._ordinal(); + if (!visitedNodes[ordinal]) { + action(node); + visitedNodes[ordinal] = true; + } + } + + var gcRoots = getChildNodeByName(this.rootNode(), "(GC roots)"); + if (!gcRoots) + return; + + if (userRootsOnly) { + for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) { + var node = iter.edge.node(); + if (this._isUserRoot(node)) + doAction(node); + } + } else { + for (var iter = gcRoots.edges(); iter.hasNext(); iter.next()) { + var subRoot = iter.edge.node(); + for (var iter2 = subRoot.edges(); iter2.hasNext(); iter2.next()) + doAction(iter2.edge.node()); + doAction(subRoot); + } + for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) + doAction(iter.edge.node()) + } + }, + + /** + * @return {?{map: !Uint32Array, flag: number}} + */ + userObjectsMapAndFlag: function() + { + return this._showHiddenData ? null : { + map: this._flags, + flag: this._nodeFlags.pageObject + }; + }, + + _flagsOfNode: function(node) + { + return this._flags[node.nodeIndex / this._nodeFieldCount]; + }, + + _markDetachedDOMTreeNodes: function() + { + var flag = this._nodeFlags.detachedDOMTreeNode; + var detachedDOMTreesRoot; + for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) { + var node = iter.edge.node(); + if (node.name() === "(Detached DOM trees)") { + detachedDOMTreesRoot = node; + break; + } + } + + if (!detachedDOMTreesRoot) + return; + + var detachedDOMTreeRE = /^Detached DOM tree/; + for (var iter = detachedDOMTreesRoot.edges(); iter.hasNext(); iter.next()) { + var node = iter.edge.node(); + if (detachedDOMTreeRE.test(node.className())) { + for (var edgesIter = node.edges(); edgesIter.hasNext(); edgesIter.next()) + this._flags[edgesIter.edge.node().nodeIndex / this._nodeFieldCount] |= flag; + } + } + }, + + _markQueriableHeapObjects: function() + { + // Allow runtime properties query for objects accessible from Window objects + // via regular properties, and for DOM wrappers. Trying to access random objects + // can cause a crash due to insonsistent state of internal properties of wrappers. + var flag = this._nodeFlags.canBeQueried; + var hiddenEdgeType = this._edgeHiddenType; + var internalEdgeType = this._edgeInternalType; + var invisibleEdgeType = this._edgeInvisibleType; + var weakEdgeType = this._edgeWeakType; + var edgeToNodeOffset = this._edgeToNodeOffset; + var edgeTypeOffset = this._edgeTypeOffset; + var edgeFieldsCount = this._edgeFieldsCount; + var containmentEdges = this._containmentEdges; + var nodes = this._nodes; + var nodeCount = this.nodeCount; + var nodeFieldCount = this._nodeFieldCount; + var firstEdgeIndexes = this._firstEdgeIndexes; + + var flags = this._flags; + var list = []; + + for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) { + if (iter.edge.node().isUserRoot()) + list.push(iter.edge.node().nodeIndex / nodeFieldCount); + } + + while (list.length) { + var nodeOrdinal = list.pop(); + if (flags[nodeOrdinal] & flag) + continue; + flags[nodeOrdinal] |= flag; + var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal]; + var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1]; + for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) { + var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; + var childNodeOrdinal = childNodeIndex / nodeFieldCount; + if (flags[childNodeOrdinal] & flag) + continue; + var type = containmentEdges[edgeIndex + edgeTypeOffset]; + if (type === hiddenEdgeType || type === invisibleEdgeType || type === internalEdgeType || type === weakEdgeType) + continue; + list.push(childNodeOrdinal); + } + } + }, + + _markPageOwnedNodes: function() + { + var edgeShortcutType = this._edgeShortcutType; + var edgeElementType = this._edgeElementType; + var edgeToNodeOffset = this._edgeToNodeOffset; + var edgeTypeOffset = this._edgeTypeOffset; + var edgeFieldsCount = this._edgeFieldsCount; + var edgeWeakType = this._edgeWeakType; + var firstEdgeIndexes = this._firstEdgeIndexes; + var containmentEdges = this._containmentEdges; + var containmentEdgesLength = containmentEdges.length; + var nodes = this._nodes; + var nodeFieldCount = this._nodeFieldCount; + var nodesCount = this.nodeCount; + + var flags = this._flags; + var flag = this._nodeFlags.pageObject; + var visitedMarker = this._nodeFlags.visitedMarker; + var visitedMarkerMask = this._nodeFlags.visitedMarkerMask; + var markerAndFlag = visitedMarker | flag; + + var nodesToVisit = new Uint32Array(nodesCount); + var nodesToVisitLength = 0; + + var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount; + var node = this.rootNode(); + for (var edgeIndex = firstEdgeIndexes[rootNodeOrdinal], endEdgeIndex = firstEdgeIndexes[rootNodeOrdinal + 1]; + edgeIndex < endEdgeIndex; + edgeIndex += edgeFieldsCount) { + var edgeType = containmentEdges[edgeIndex + edgeTypeOffset]; + var nodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; + if (edgeType === edgeElementType) { + node.nodeIndex = nodeIndex; + if (!node.isDocumentDOMTreesRoot()) + continue; + } else if (edgeType !== edgeShortcutType) + continue; + var nodeOrdinal = nodeIndex / nodeFieldCount; + nodesToVisit[nodesToVisitLength++] = nodeOrdinal; + flags[nodeOrdinal] |= visitedMarker; + } + + while (nodesToVisitLength) { + var nodeOrdinal = nodesToVisit[--nodesToVisitLength]; + flags[nodeOrdinal] |= flag; + flags[nodeOrdinal] &= visitedMarkerMask; + var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal]; + var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1]; + for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) { + var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; + var childNodeOrdinal = childNodeIndex / nodeFieldCount; + if (flags[childNodeOrdinal] & markerAndFlag) + continue; + var type = containmentEdges[edgeIndex + edgeTypeOffset]; + if (type === edgeWeakType) + continue; + nodesToVisit[nodesToVisitLength++] = childNodeOrdinal; + flags[childNodeOrdinal] |= visitedMarker; + } + } + }, + + _calculateStatistics: function() + { + var nodeFieldCount = this._nodeFieldCount; + var nodes = this._nodes; + var nodesLength = nodes.length; + var nodeTypeOffset = this._nodeTypeOffset; + var nodeSizeOffset = this._nodeSelfSizeOffset;; + var nodeNativeType = this._nodeNativeType; + var nodeCodeType = this._nodeCodeType; + var nodeConsStringType = this._nodeConsStringType; + var nodeSlicedStringType = this._nodeSlicedStringType; + var sizeNative = 0; + var sizeCode = 0; + var sizeStrings = 0; + var sizeJSArrays = 0; + var node = this.rootNode(); + for (var nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) { + node.nodeIndex = nodeIndex; + var nodeType = nodes[nodeIndex + nodeTypeOffset]; + var nodeSize = nodes[nodeIndex + nodeSizeOffset]; + if (nodeType === nodeNativeType) + sizeNative += nodeSize; + else if (nodeType === nodeCodeType) + sizeCode += nodeSize; + else if (nodeType === nodeConsStringType || nodeType === nodeSlicedStringType || node.type() === "string") + sizeStrings += nodeSize; + else if (node.name() === "Array") + sizeJSArrays += this._calculateArraySize(node); + } + this._statistics = new WebInspector.HeapSnapshotCommon.Statistics(); + this._statistics.total = this.totalSize; + this._statistics.v8heap = this.totalSize - sizeNative; + this._statistics.native = sizeNative; + this._statistics.code = sizeCode; + this._statistics.jsArrays = sizeJSArrays; + this._statistics.strings = sizeStrings; + }, + + /** + * @param {!WebInspector.HeapSnapshotNode} node + * @return {number} + */ + _calculateArraySize: function(node) + { + var size = node.selfSize(); + var beginEdgeIndex = node._edgeIndexesStart(); + var endEdgeIndex = node._edgeIndexesEnd(); + var containmentEdges = this._containmentEdges; + var strings = this._strings; + var edgeToNodeOffset = this._edgeToNodeOffset; + var edgeTypeOffset = this._edgeTypeOffset; + var edgeNameOffset = this._edgeNameOffset; + var edgeFieldsCount = this._edgeFieldsCount; + var edgeInternalType = this._edgeInternalType; + for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) { + var edgeType = containmentEdges[edgeIndex + edgeTypeOffset]; + if (edgeType !== edgeInternalType) + continue; + var edgeName = strings[containmentEdges[edgeIndex + edgeNameOffset]]; + if (edgeName !== "elements") + continue; + var elementsNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset]; + node.nodeIndex = elementsNodeIndex; + if (node.retainersCount() === 1) + size += node.selfSize(); + break; + } + return size; + }, + + /** + * @return {!WebInspector.HeapSnapshotCommon.Statistics} + */ + getStatistics: function() + { + return this._statistics; + }, + + __proto__: WebInspector.HeapSnapshot.prototype +}; + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotNode} + * @param {!WebInspector.JSHeapSnapshot} snapshot + * @param {number=} nodeIndex + */ +WebInspector.JSHeapSnapshotNode = function(snapshot, nodeIndex) +{ + WebInspector.HeapSnapshotNode.call(this, snapshot, nodeIndex) +} + +WebInspector.JSHeapSnapshotNode.prototype = { + /** + * @return {boolean} + */ + canBeQueried: function() + { + var flags = this._snapshot._flagsOfNode(this); + return !!(flags & this._snapshot._nodeFlags.canBeQueried); + }, + + /** + * @return {boolean} + */ + isUserObject: function() + { + var flags = this._snapshot._flagsOfNode(this); + return !!(flags & this._snapshot._nodeFlags.pageObject); + }, + + + /** + * @return {string} + */ + name: function() { + var snapshot = this._snapshot; + if (this._type() === snapshot._nodeConsStringType) { + var string = snapshot._lazyStringCache[this.nodeIndex]; + if (typeof string === "undefined") { + string = this._consStringName(); + snapshot._lazyStringCache[this.nodeIndex] = string; + } + return string; + } + return WebInspector.HeapSnapshotNode.prototype.name.call(this); + }, + + _consStringName: function() + { + var snapshot = this._snapshot; + var consStringType = snapshot._nodeConsStringType; + var edgeInternalType = snapshot._edgeInternalType; + var edgeFieldsCount = snapshot._edgeFieldsCount; + var edgeToNodeOffset = snapshot._edgeToNodeOffset; + var edgeTypeOffset = snapshot._edgeTypeOffset; + var edgeNameOffset = snapshot._edgeNameOffset; + var strings = snapshot._strings; + var edges = snapshot._containmentEdges; + var firstEdgeIndexes = snapshot._firstEdgeIndexes; + var nodeFieldCount = snapshot._nodeFieldCount; + var nodeTypeOffset = snapshot._nodeTypeOffset; + var nodeNameOffset = snapshot._nodeNameOffset; + var nodes = snapshot._nodes; + var nodesStack = []; + nodesStack.push(this.nodeIndex); + var name = ""; + + while (nodesStack.length && name.length < 1024) { + var nodeIndex = nodesStack.pop(); + if (nodes[nodeIndex + nodeTypeOffset] !== consStringType) { + name += strings[nodes[nodeIndex + nodeNameOffset]]; + continue; + } + var nodeOrdinal = nodeIndex / nodeFieldCount; + var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal]; + var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1]; + var firstNodeIndex = 0; + var secondNodeIndex = 0; + for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex && (!firstNodeIndex || !secondNodeIndex); edgeIndex += edgeFieldsCount) { + var edgeType = edges[edgeIndex + edgeTypeOffset]; + if (edgeType === edgeInternalType) { + var edgeName = strings[edges[edgeIndex + edgeNameOffset]]; + if (edgeName === "first") + firstNodeIndex = edges[edgeIndex + edgeToNodeOffset]; + else if (edgeName === "second") + secondNodeIndex = edges[edgeIndex + edgeToNodeOffset]; + } + } + nodesStack.push(secondNodeIndex); + nodesStack.push(firstNodeIndex); + } + return name; + }, + + /** + * @return {string} + */ + className: function() + { + var type = this.type(); + switch (type) { + case "hidden": + return "(system)"; + case "object": + case "native": + return this.name(); + case "code": + return "(compiled code)"; + default: + return "(" + type + ")"; + } + }, + + /** + * @return {number} + */ + classIndex: function() + { + var snapshot = this._snapshot; + var nodes = snapshot._nodes; + var type = nodes[this.nodeIndex + snapshot._nodeTypeOffset];; + if (type === snapshot._nodeObjectType || type === snapshot._nodeNativeType) + return nodes[this.nodeIndex + snapshot._nodeNameOffset]; + return -1 - type; + }, + + /** + * @return {number} + */ + id: function() + { + var snapshot = this._snapshot; + return snapshot._nodes[this.nodeIndex + snapshot._nodeIdOffset]; + }, + + /** + * @return {boolean} + */ + isHidden: function() + { + return this._type() === this._snapshot._nodeHiddenType; + }, + + /** + * @return {boolean} + */ + isSynthetic: function() + { + return this._type() === this._snapshot._nodeSyntheticType; + }, + + /** + * @return {!boolean} + */ + isUserRoot: function() + { + return !this.isSynthetic(); + }, + + /** + * @return {!boolean} + */ + isDocumentDOMTreesRoot: function() + { + return this.isSynthetic() && this.name() === "(Document DOM trees)"; + }, + + /** + * @return {!WebInspector.HeapSnapshotCommon.Node} + */ + serialize: function() + { + var result = WebInspector.HeapSnapshotNode.prototype.serialize.call(this); + var flags = this._snapshot._flagsOfNode(this); + if (flags & this._snapshot._nodeFlags.canBeQueried) + result.canBeQueried = true; + if (flags & this._snapshot._nodeFlags.detachedDOMTreeNode) + result.detachedDOMTreeNode = true; + return result; + }, + + __proto__: WebInspector.HeapSnapshotNode.prototype +}; + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotEdge} + * @param {!WebInspector.JSHeapSnapshot} snapshot + * @param {number=} edgeIndex + */ +WebInspector.JSHeapSnapshotEdge = function(snapshot, edgeIndex) +{ + WebInspector.HeapSnapshotEdge.call(this, snapshot, edgeIndex); +} + +WebInspector.JSHeapSnapshotEdge.prototype = { + /** + * @return {!WebInspector.JSHeapSnapshotEdge} + */ + clone: function() + { + var snapshot = /** @type {!WebInspector.JSHeapSnapshot} */ (this._snapshot); + return new WebInspector.JSHeapSnapshotEdge(snapshot, this.edgeIndex); + }, + + /** + * @return {boolean} + */ + hasStringName: function() + { + if (!this.isShortcut()) + return this._hasStringName(); + return isNaN(parseInt(this._name(), 10)); + }, + + /** + * @return {boolean} + */ + isElement: function() + { + return this._type() === this._snapshot._edgeElementType; + }, + + /** + * @return {boolean} + */ + isHidden: function() + { + return this._type() === this._snapshot._edgeHiddenType; + }, + + /** + * @return {boolean} + */ + isWeak: function() + { + return this._type() === this._snapshot._edgeWeakType; + }, + + /** + * @return {boolean} + */ + isInternal: function() + { + return this._type() === this._snapshot._edgeInternalType; + }, + + /** + * @return {boolean} + */ + isInvisible: function() + { + return this._type() === this._snapshot._edgeInvisibleType; + }, + + /** + * @return {boolean} + */ + isShortcut: function() + { + return this._type() === this._snapshot._edgeShortcutType; + }, + + /** + * @return {string} + */ + name: function() + { + if (!this.isShortcut()) + return this._name(); + var numName = parseInt(this._name(), 10); + return isNaN(numName) ? this._name() : numName; + }, + + /** + * @return {string} + */ + toString: function() + { + var name = this.name(); + switch (this.type()) { + case "context": return "->" + name; + case "element": return "[" + name + "]"; + case "weak": return "[[" + name + "]]"; + case "property": + return name.indexOf(" ") === -1 ? "." + name : "[\"" + name + "\"]"; + case "shortcut": + if (typeof name === "string") + return name.indexOf(" ") === -1 ? "." + name : "[\"" + name + "\"]"; + else + return "[" + name + "]"; + case "internal": + case "hidden": + case "invisible": + return "{" + name + "}"; + }; + return "?" + name + "?"; + }, + + _hasStringName: function() + { + return !this.isElement() && !this.isHidden(); + }, + + _name: function() + { + return this._hasStringName() ? this._snapshot._strings[this._nameOrIndex()] : this._nameOrIndex(); + }, + + _nameOrIndex: function() + { + return this._edges[this.edgeIndex + this._snapshot._edgeNameOffset]; + }, + + _type: function() + { + return this._edges[this.edgeIndex + this._snapshot._edgeTypeOffset]; + }, + + __proto__: WebInspector.HeapSnapshotEdge.prototype +}; + + +/** + * @constructor + * @extends {WebInspector.HeapSnapshotRetainerEdge} + * @param {!WebInspector.JSHeapSnapshot} snapshot + * @param {number} retainerIndex + */ +WebInspector.JSHeapSnapshotRetainerEdge = function(snapshot, retainerIndex) +{ + WebInspector.HeapSnapshotRetainerEdge.call(this, snapshot, retainerIndex); +} + +WebInspector.JSHeapSnapshotRetainerEdge.prototype = { + /** + * @return {!WebInspector.JSHeapSnapshotRetainerEdge} + */ + clone: function() + { + var snapshot = /** @type {!WebInspector.JSHeapSnapshot} */ (this._snapshot); + return new WebInspector.JSHeapSnapshotRetainerEdge(snapshot, this.retainerIndex()); + }, + + /** + * @return {boolean} + */ + isHidden: function() + { + return this._edge().isHidden(); + }, + + /** + * @return {boolean} + */ + isInternal: function() + { + return this._edge().isInternal(); + }, + + /** + * @return {boolean} + */ + isInvisible: function() + { + return this._edge().isInvisible(); + }, + + /** + * @return {boolean} + */ + isShortcut: function() + { + return this._edge().isShortcut(); + }, + + /** + * @return {boolean} + */ + isWeak: function() + { + return this._edge().isWeak(); + }, + + __proto__: WebInspector.HeapSnapshotRetainerEdge.prototype +} + diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/profiler/module.json b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/module.json new file mode 100644 index 00000000000..37a56799c4d --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/profiler/module.json @@ -0,0 +1,31 @@ +{ + "extensions": [ + { + "type": "@WebInspector.Panel", + "name": "profiles", + "title": "Profiles", + "order": 4, + "className": "WebInspector.ProfilesPanel" + }, + { + "type": "@WebInspector.ContextMenu.Provider", + "contextTypes": ["WebInspector.RemoteObject"], + "className": "WebInspector.ProfilesPanel.ContextMenuProvider" + }, + { + "type": "ui-setting", + "section": "Profiler", + "title": "Show advanced heap snapshot properties", + "settingName": "showAdvancedHeapSnapshotProperties", + "settingType": "checkbox" + }, + { + "type": "ui-setting", + "section": "Profiler", + "title": "High resolution CPU profiling", + "settingName": "highResolutionCpuProfiling", + "settingType": "checkbox" + } + ], + "scripts": [ "ProfilesPanel.js" ] +} |