blob: 8b4873a7a32a9f4a08ac410b4cb381c0eebf26a2 [file] [log] [blame]
/*
* 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.HeapSnapshotProgress} progress
* @extends {WebInspector.HeapSnapshot}
*/
WebInspector.JSHeapSnapshot = function(profile, progress)
{
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);
}
WebInspector.JSHeapSnapshot.prototype = {
createNode: function(nodeIndex)
{
return new WebInspector.JSHeapSnapshotNode(this, nodeIndex);
},
createEdge: function(edges, edgeIndex)
{
return new WebInspector.JSHeapSnapshotEdge(this, edges, edgeIndex);
},
createRetainingEdge: function(retainedNodeIndex, retainerIndex)
{
return new WebInspector.JSHeapSnapshotRetainerEdge(this, retainedNodeIndex, retainerIndex);
},
classNodesFilter: function()
{
function filter(node)
{
return node.isUserObject();
}
return filter;
},
containmentEdgesFilter: function(showHiddenData)
{
function filter(edge) {
if (edge.isInvisible())
return false;
if (showHiddenData)
return true;
return !edge.isHidden() && !edge.node().isHidden();
}
return filter;
},
retainingEdgesFilter: function(showHiddenData)
{
var containmentEdgesFilter = this.containmentEdgesFilter(showHiddenData);
function filter(edge)
{
return containmentEdgesFilter(edge) && !edge.node().isRoot() && !edge.isWeak();
}
return filter;
},
dispose: function()
{
WebInspector.HeapSnapshot.prototype.dispose.call(this);
delete this._flags;
},
_markInvisibleEdges: function()
{
// Mark hidden edges of global objects as invisible.
// FIXME: This is a temporary measure. Normally, we should
// really hide all hidden nodes.
for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
var edge = iter.edge;
if (!edge.isShortcut())
continue;
var node = edge.node();
var propNames = {};
for (var innerIter = node.edges(); innerIter.hasNext(); innerIter.next()) {
var globalObjEdge = innerIter.edge;
if (globalObjEdge.isShortcut())
propNames[globalObjEdge._nameOrIndex()] = true;
}
for (innerIter.rewind(); innerIter.hasNext(); innerIter.next()) {
var globalObjEdge = innerIter.edge;
if (!globalObjEdge.isShortcut()
&& globalObjEdge.node().isHidden()
&& globalObjEdge._hasStringName()
&& (globalObjEdge._nameOrIndex() in propNames))
this._containmentEdges[globalObjEdge._edges._start + globalObjEdge.edgeIndex + this._edgeTypeOffset] = this._edgeInvisibleType;
}
}
},
_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|null}
*/
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;
}
/**
* @param {!WebInspector.HeapSnapshotNode} node
* @param {!string} name
* @return {!WebInspector.HeapSnapshotNode|null}
*/
function getChildNodeByLinkName(node, name)
{
for (var iter = node.edges(); iter.hasNext(); iter.next()) {
var edge = iter.edge;
if (edge.name() === name)
return edge.node();
}
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 (node.isDocumentDOMTreesRoot())
doAction(node);
else if (node.isUserRoot()) {
var nativeContextNode = getChildNodeByLinkName(node, "native_context");
if (nativeContextNode)
doAction(nativeContextNode);
else
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())
}
},
userObjectsMapAndFlag: function()
{
return {
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;
}
}
},
__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 = {
canBeQueried: function()
{
var flags = this._snapshot._flagsOfNode(this);
return !!(flags & this._snapshot._nodeFlags.canBeQueried);
},
isUserObject: function()
{
var flags = this._snapshot._flagsOfNode(this);
return !!(flags & this._snapshot._nodeFlags.pageObject);
},
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;
},
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 + ")";
}
},
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;
},
id: function()
{
var snapshot = this._snapshot;
return snapshot._nodes[this.nodeIndex + snapshot._nodeIdOffset];
},
isHidden: function()
{
return this._type() === this._snapshot._nodeHiddenType;
},
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)";
},
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 {Array.<number>} edges
* @param {number=} edgeIndex
*/
WebInspector.JSHeapSnapshotEdge = function(snapshot, edges, edgeIndex)
{
WebInspector.HeapSnapshotEdge.call(this, snapshot, edges, edgeIndex);
}
WebInspector.JSHeapSnapshotEdge.prototype = {
clone: function()
{
return new WebInspector.JSHeapSnapshotEdge(this._snapshot, this._edges, this.edgeIndex);
},
hasStringName: function()
{
if (!this.isShortcut())
return this._hasStringName();
return isNaN(parseInt(this._name(), 10));
},
isElement: function()
{
return this._type() === this._snapshot._edgeElementType;
},
isHidden: function()
{
return this._type() === this._snapshot._edgeHiddenType;
},
isWeak: function()
{
return this._type() === this._snapshot._edgeWeakType;
},
isInternal: function()
{
return this._type() === this._snapshot._edgeInternalType;
},
isInvisible: function()
{
return this._type() === this._snapshot._edgeInvisibleType;
},
isShortcut: function()
{
return this._type() === this._snapshot._edgeShortcutType;
},
name: function()
{
if (!this.isShortcut())
return this._name();
var numName = parseInt(this._name(), 10);
return isNaN(numName) ? this._name() : numName;
},
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() && !this.isWeak();
},
_name: function()
{
return this._hasStringName() ? this._snapshot._strings[this._nameOrIndex()] : this._nameOrIndex();
},
_nameOrIndex: function()
{
return this._edges.item(this.edgeIndex + this._snapshot._edgeNameOffset);
},
_type: function()
{
return this._edges.item(this.edgeIndex + this._snapshot._edgeTypeOffset);
},
__proto__: WebInspector.HeapSnapshotEdge.prototype
};
/**
* @constructor
* @extends {WebInspector.HeapSnapshotRetainerEdge}
* @param {WebInspector.JSHeapSnapshot} snapshot
*/
WebInspector.JSHeapSnapshotRetainerEdge = function(snapshot, retainedNodeIndex, retainerIndex)
{
WebInspector.HeapSnapshotRetainerEdge.call(this, snapshot, retainedNodeIndex, retainerIndex);
}
WebInspector.JSHeapSnapshotRetainerEdge.prototype = {
clone: function()
{
return new WebInspector.JSHeapSnapshotRetainerEdge(this._snapshot, this._retainedNodeIndex, this.retainerIndex());
},
isHidden: function()
{
return this._edge().isHidden();
},
isInternal: function()
{
return this._edge().isInternal();
},
isInvisible: function()
{
return this._edge().isInvisible();
},
isShortcut: function()
{
return this._edge().isShortcut();
},
isWeak: function()
{
return this._edge().isWeak();
},
__proto__: WebInspector.HeapSnapshotRetainerEdge.prototype
}