blob: 66e845dbf1477407ef86b0a7c08bd573671e4d26 [file] [log] [blame]
/*
* 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;
}
/** @enum {symbol} */
WebInspector.HeapSnapshotGridNode.Events = {
PopulateComplete: Symbol("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
* @return {!Promise<number>}
*/
nodePosition: function(snapshotObjectId) { },
/**
* @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
* @return {!Promise<?>}
*/
sortAndRewind: function(comparator) { }
}
WebInspector.HeapSnapshotGridNode.prototype = {
/**
* @return {!WebInspector.HeapSnapshotSortableDataGrid}
*/
heapSnapshotDataGrid: function()
{
return this._dataGrid;
},
/**
* @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;
},
/**
* @override
* @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);
},
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 = createElement("td");
cell.className = "numeric-column";
if (this.dataGrid.snapshot.totalSize !== 0) {
var div = createElement("div");
var valueSpan = createElement("span");
valueSpan.textContent = this.data[columnIdentifier];
div.appendChild(valueSpan);
var percentColumn = columnIdentifier + "-percent";
if (percentColumn in this.data) {
var percentSpan = 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._provider().sortAndRewind(this.comparator()).then(this._populateChildren.bind(this));
},
/**
* @return {!Promise<?>}
*/
expandWithoutPopulate: function()
{
// Make sure default populate won't take action.
this._populated = true;
this.expand();
return this._provider().sortAndRewind(this.comparator());
},
/**
* @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()).then(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 = {
/**
* @override
* @return {?{snapshot:!WebInspector.HeapSnapshotProxy, snapshotNodeIndex:number}}
*/
retainersDataSource: function()
{
return {snapshot: this._dataGrid.snapshot, snapshotNodeIndex: this.snapshotNodeIndex};
},
/**
* @override
* @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 = value + "()";
valueStyle = "function";
break;
case "number":
valueStyle = "number";
break;
case "hidden":
valueStyle = "null";
break;
case "array":
value = (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 = createElement("td");
cell.className = "object-column";
var div = createElement("div");
div.className = "source-code event-properties";
div.style.overflow = "visible";
this._prefixObjectCell(div);
var valueSpan = createElement("span");
valueSpan.className = "value object-value-" + valueStyle;
valueSpan.textContent = value;
div.appendChild(valueSpan);
var idSpan = createElement("span");
idSpan.className = "object-value-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)
{
},
/**
* @param {!WebInspector.Target} target
* @param {function(!WebInspector.RemoteObject)} callback
* @param {string} objectGroupName
*/
queryObjectContent: function(target, callback, objectGroupName)
{
/**
* @param {?Protocol.Error} error
* @param {!RuntimeAgent.RemoteObject} object
*/
function formatResult(error, object)
{
if (!error && object.type)
callback(target.runtimeModel.createRemoteObject(object));
else
callback(target.runtimeModel.createRemoteObjectFromPrimitiveValue(WebInspector.UIString("Preview is not available")));
}
if (this._type === "string")
callback(target.runtimeModel.createRemoteObjectFromPrimitiveValue(this._name));
else
target.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 = {
/**
* @override
* @return {?{snapshot:!WebInspector.HeapSnapshotProxy, snapshotNodeIndex:number}}
*/
retainersDataSource: function()
{
return {snapshot: this._snapshot, snapshotNodeIndex: this.snapshotNodeIndex};
},
/**
* @override
* @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 || "(empty)";
var nameClass = "name";
switch (this._referenceType) {
case "context":
nameClass = "object-value-number";
break;
case "internal":
case "hidden":
case "weak":
nameClass = "object-value-null";
break;
case "element":
name = "[" + name + "]";
break;
}
if (this._cycledWithAncestorGridNode)
div.className += " cycled-ancessor-node";
var nameSpan = createElement("span");
nameSpan.className = nameClass;
nameSpan.textContent = name;
div.appendChild(nameSpan);
var separatorSpan = 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 = {
/**
* @override
* @return {!WebInspector.HeapSnapshotProviderProxy}
*/
createProvider: function()
{
return this._snapshot.createRetainingEdgesProvider(this.snapshotNodeIndex);
},
/**
* @override
* @param {!WebInspector.HeapSnapshotCommon.Edge} item
* @return {!WebInspector.HeapSnapshotRetainingObjectNode}
*/
_createChildNode: function(item)
{
return new WebInspector.HeapSnapshotRetainingObjectNode(this._dataGrid, this._snapshot, item, this);
},
/**
* @override
* @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 = {
/**
* @override
* @return {?{snapshot:!WebInspector.HeapSnapshotProxy, snapshotNodeIndex:number}}
*/
retainersDataSource: function()
{
return {snapshot: this._baseSnapshotOrSnapshot, snapshotNodeIndex: this.snapshotNodeIndex};
},
/**
* @override
* @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
* @return {!Promise<!Array<!WebInspector.HeapSnapshotGridNode>>}
*/
populateNodeBySnapshotObjectId: function(snapshotObjectId)
{
/**
* @this {WebInspector.HeapSnapshotConstructorNode}
*/
function didExpand()
{
return this._provider().nodePosition(snapshotObjectId).then(didGetNodePosition.bind(this));
}
/**
* @this {WebInspector.HeapSnapshotConstructorNode}
* @param {number} nodePosition
* @return {!Promise<!Array<!WebInspector.HeapSnapshotGridNode>>}
*/
function didGetNodePosition(nodePosition)
{
if (nodePosition === -1) {
this.collapse();
return Promise.resolve([]);
} else {
/**
* @param {function(!Array<!WebInspector.HeapSnapshotGridNode>)} fulfill
* @this {WebInspector.HeapSnapshotConstructorNode}
*/
function action(fulfill)
{
this._populateChildren(nodePosition, null, didPopulateChildren.bind(this, nodePosition, fulfill));
}
return new Promise(action.bind(this));
}
}
/**
* @this {WebInspector.HeapSnapshotConstructorNode}
* @param {number} nodePosition
* @param {function(!Array<!WebInspector.HeapSnapshotGridNode>)} callback
*/
function didPopulateChildren(nodePosition, callback)
{
var node = /** @type {?WebInspector.HeapSnapshotGridNode} */ (this.childForPosition(nodePosition));
callback(node ? [this, node] : []);
}
this._dataGrid.resetNameFilter();
return this.expandWithoutPopulate().then(didExpand.bind(this));
},
/**
* @param {string} filterValue
* @return {boolean}
*/
filteredOut: function(filterValue)
{
return this._name.toLowerCase().indexOf(filterValue) === -1;
},
/**
* @override
* @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: ["name", sortAscending, "id", true],
distance: ["distance", sortAscending, "retainedSize", false],
count: ["name", true, "id", true],
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 = {
/**
* @override
*/
dispose: function()
{
this._addedNodesProvider.dispose();
this._deletedNodesProvider.dispose();
},
/**
* @override
* @param {number} snapshotObjectId
* @return {!Promise<number>}
*/
nodePosition: function(snapshotObjectId)
{
throw new Error("Unreachable");
},
/**
* @override
* @param {function(boolean)} callback
*/
isEmpty: function(callback)
{
callback(false);
},
/**
* @override
* @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));
}
},
/**
* @override
* @param {!WebInspector.HeapSnapshotCommon.ComparatorConfig} comparator
* @return {!Promise<?>}
*/
sortAndRewind: function(comparator)
{
/**
* @this {WebInspector.HeapSnapshotDiffNodesProvider}
* @return {!Promise<?>}
*/
function afterSort()
{
return this._deletedNodesProvider.sortAndRewind(comparator);
}
return this._addedNodesProvider.sortAndRewind(comparator).then(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);
},
/**
* @override
* @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: ["name", sortAscending, "id", true],
addedCount: ["name", true, "id", true],
removedCount: ["name", true, "id", true],
countDelta: ["name", true, "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.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;
var target = this._dataGrid.target();
if (allocationNode.scriptId) {
var linkifier = this._dataGrid._linkifier;
var urlElement = linkifier.linkifyScriptLocation(target, String(allocationNode.scriptId), 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
}