UNPKG

occaecatidicta

Version:
1,510 lines (1,342 loc) 59.8 kB
/* * 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.Uint32Array = function(size) { const preallocateSize = 1000; size = size || preallocateSize; this._usedSize = 0; this._array = new Uint32Array(preallocateSize); } WebInspector.Uint32Array.prototype = { push: function(value) { if (this._usedSize + 1 > this._array.length) { var tempArray = new Uint32Array(this._array.length * 2); tempArray.set(this._array); this._array = tempArray; } this._array[this._usedSize++] = value; }, get array() { return this._array.subarray(0, this._usedSize); } } WebInspector.HeapSnapshotLoader = function() { this._json = ""; this._state = "find-snapshot-info"; this._snapshot = {}; } WebInspector.HeapSnapshotLoader.prototype = { _findBalancedCurlyBrackets: function() { var counter = 0; var openingBracket = "{".charCodeAt(0), closingBracket = "}".charCodeAt(0); for (var i = 0, l = this._json.length; i < l; ++i) { var character = this._json.charCodeAt(i); if (character === openingBracket) ++counter; else if (character === closingBracket) { if (--counter === 0) return i + 1; } } return -1; }, finishLoading: function() { if (!this._json) return null; this._parseStringsArray(); this._json = ""; var result = new WebInspector.HeapSnapshot(this._snapshot); this._json = ""; this._snapshot = {}; return result; }, _parseNodes: 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); this._snapshot.nodes = this._nodes.array; 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._nodes.push(nextNumber); } }, _parseStringsArray: function() { 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); }, pushJSONChunk: function(chunk) { this._json += chunk; 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.pushJSONChunk(""); break; } case "parse-snapshot-info": { var closingBracketIndex = this._findBalancedCurlyBrackets(); if (closingBracketIndex === -1) return; this._snapshot.snapshot = JSON.parse(this._json.slice(0, closingBracketIndex)); this._json = this._json.slice(closingBracketIndex); this._state = "find-nodes"; this.pushJSONChunk(""); 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); this._state = "parse-nodes-meta-info"; this.pushJSONChunk(""); break; } case "parse-nodes-meta-info": { var closingBracketIndex = this._findBalancedCurlyBrackets(); if (closingBracketIndex === -1) return; this._nodes = new WebInspector.Uint32Array(); this._nodes.push(0); this._snapshot.metaNode = JSON.parse(this._json.slice(0, closingBracketIndex)); this._json = this._json.slice(closingBracketIndex); this._state = "parse-nodes"; this.pushJSONChunk(""); break; } case "parse-nodes": { if (this._parseNodes()) return; this._state = "find-strings"; this.pushJSONChunk(""); 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": break; } } }; WebInspector.HeapSnapshotArraySlice = function(array, start, end) { this._array = array; this._start = start; this.length = end - start; } WebInspector.HeapSnapshotArraySlice.prototype = { item: function(index) { return this._array[this._start + index]; }, slice: function(start, end) { if (typeof end === "undefined") end = this.length; return this._array.subarray(this._start + start, this._start + end); } } WebInspector.HeapSnapshotEdge = function(snapshot, edges, edgeIndex) { this._snapshot = snapshot; this._edges = edges; this.edgeIndex = edgeIndex || 0; } WebInspector.HeapSnapshotEdge.prototype = { clone: function() { return new WebInspector.HeapSnapshotEdge(this._snapshot, this._edges, this.edgeIndex); }, get hasStringName() { if (!this.isShortcut) return this._hasStringName; return isNaN(parseInt(this._name, 10)); }, get isElement() { return this._type() === this._snapshot._edgeElementType; }, get isHidden() { return this._type() === this._snapshot._edgeHiddenType; }, get isWeak() { return this._type() === this._snapshot._edgeWeakType; }, get isInternal() { return this._type() === this._snapshot._edgeInternalType; }, get isInvisible() { return this._type() === this._snapshot._edgeInvisibleType; }, get isShortcut() { return this._type() === this._snapshot._edgeShortcutType; }, get name() { if (!this.isShortcut) return this._name; var numName = parseInt(this._name, 10); return isNaN(numName) ? this._name : numName; }, get node() { return new WebInspector.HeapSnapshotNode(this._snapshot, this.nodeIndex); }, get nodeIndex() { return this._edges.item(this.edgeIndex + this._snapshot._edgeToNodeOffset); }, get rawEdges() { return this._edges; }, toString: function() { switch (this.type) { case "context": return "->" + this.name; case "element": return "[" + this.name + "]"; case "weak": return "[[" + this.name + "]]"; case "property": return this.name.indexOf(" ") === -1 ? "." + this.name : "[\"" + this.name + "\"]"; case "shortcut": var name = this.name; if (typeof name === "string") return this.name.indexOf(" ") === -1 ? "." + this.name : "[\"" + this.name + "\"]"; else return "[" + this.name + "]"; case "internal": case "hidden": case "invisible": return "{" + this.name + "}"; }; return "?" + this.name + "?"; }, get type() { return this._snapshot._edgeTypes[this._type()]; }, get _hasStringName() { return !this.isElement && !this.isHidden && !this.isWeak; }, get _name() { return this._hasStringName ? this._snapshot._strings[this._nameOrIndex] : this._nameOrIndex; }, get _nameOrIndex() { return this._edges.item(this.edgeIndex + this._snapshot._edgeNameOffset); }, _type: function() { return this._edges.item(this.edgeIndex + this._snapshot._edgeTypeOffset); } }; WebInspector.HeapSnapshotEdgeIterator = function(edge) { this.edge = edge; } WebInspector.HeapSnapshotEdgeIterator.prototype = { first: function() { this.edge.edgeIndex = 0; }, hasNext: function() { return this.edge.edgeIndex < this.edge._edges.length; }, get index() { return this.edge.edgeIndex; }, set index(newIndex) { this.edge.edgeIndex = newIndex; }, get item() { return this.edge; }, next: function() { this.edge.edgeIndex += this.edge._snapshot._edgeFieldsCount; } }; WebInspector.HeapSnapshotRetainerEdge = function(snapshot, retainers, retainerIndex) { this._snapshot = snapshot; this._retainers = retainers; this.retainerIndex = retainerIndex || 0; } WebInspector.HeapSnapshotRetainerEdge.prototype = { clone: function() { return new WebInspector.HeapSnapshotRetainerEdge(this._snapshot, this._retainers, this.retainerIndex); }, get hasStringName() { return this._edge.hasStringName; }, get isElement() { return this._edge.isElement; }, get isHidden() { return this._edge.isHidden; }, get isInternal() { return this._edge.isInternal; }, get isInvisible() { return this._edge.isInvisible; }, get isShortcut() { return this._edge.isShortcut; }, get isWeak() { return this._edge.isWeak; }, get name() { return this._edge.name; }, get node() { return this._node; }, get nodeIndex() { return this._nodeIndex; }, get retainerIndex() { return this._retainerIndex; }, set retainerIndex(newIndex) { if (newIndex !== this._retainerIndex) { this._retainerIndex = newIndex; this.edgeIndex = newIndex; } }, set edgeIndex(edgeIndex) { this._globalEdgeIndex = this._retainers.item(edgeIndex); this._nodeIndex = this._snapshot._findNearestNodeIndex(this._globalEdgeIndex); delete this._edgeInstance; delete this._nodeInstance; }, get _node() { if (!this._nodeInstance) this._nodeInstance = new WebInspector.HeapSnapshotNode(this._snapshot, this._nodeIndex); return this._nodeInstance; }, get _edge() { if (!this._edgeInstance) { var edgeIndex = this._globalEdgeIndex - this._nodeIndex - this._snapshot._firstEdgeOffset; this._edgeInstance = new WebInspector.HeapSnapshotEdge(this._snapshot, this._node.rawEdges, edgeIndex); } return this._edgeInstance; }, toString: function() { return this._edge.toString(); }, get type() { return this._edge.type; } } WebInspector.HeapSnapshotRetainerEdgeIterator = function(retainer) { this.retainer = retainer; } WebInspector.HeapSnapshotRetainerEdgeIterator.prototype = { first: function() { this.retainer.retainerIndex = 0; }, hasNext: function() { return this.retainer.retainerIndex < this.retainer._retainers.length; }, get index() { return this.retainer.retainerIndex; }, set index(newIndex) { this.retainer.retainerIndex = newIndex; }, get item() { return this.retainer; }, next: function() { ++this.retainer.retainerIndex; } }; WebInspector.HeapSnapshotNode = function(snapshot, nodeIndex) { this._snapshot = snapshot; this._firstNodeIndex = nodeIndex; this.nodeIndex = nodeIndex; } WebInspector.HeapSnapshotNode.prototype = { get canBeQueried() { var flags = this._snapshot._flagsOfNode(this); return !!(flags & this._snapshot._nodeFlags.canBeQueried); }, get distanceToWindow() { return this._snapshot._distancesToWindow[this.nodeIndex]; }, get className() { switch (this.type) { case "hidden": return WebInspector.UIString("(system)"); case "object": case "native": return this.name; case "code": return WebInspector.UIString("(compiled code)"); default: return "(" + this.type + ")"; } }, get classIndex() { var type = this._type(); switch (type) { case this._snapshot._nodeObjectType: case this._snapshot._nodeNativeType: return this._name(); default: return -1 - type; } }, get dominatorIndex() { return this._nodes[this.nodeIndex + this._snapshot._dominatorOffset]; }, get edges() { return new WebInspector.HeapSnapshotEdgeIterator(new WebInspector.HeapSnapshotEdge(this._snapshot, this.rawEdges)); }, get edgesCount() { return this._nodes[this.nodeIndex + this._snapshot._edgesCountOffset]; }, get flags() { return this._snapshot._flagsOfNode(this); }, get id() { return this._nodes[this.nodeIndex + this._snapshot._nodeIdOffset]; }, get isHidden() { return this._type() === this._snapshot._nodeHiddenType; }, get isNative() { return this._type() === this._snapshot._nodeNativeType; }, get isSynthetic() { return this._type() === this._snapshot._nodeSyntheticType; }, get isWindow() { const windowRE = /^Window/; return windowRE.test(this.name); }, get isDetachedDOMTreesRoot() { return this.name === "(Detached DOM trees)"; }, get isDetachedDOMTree() { const detachedDOMTreeRE = /^Detached DOM tree/; return detachedDOMTreeRE.test(this.className); }, get isRoot() { return this.nodeIndex === this._snapshot._rootNodeIndex; }, get name() { return this._snapshot._strings[this._name()]; }, get rawEdges() { var firstEdgeIndex = this._firstEdgeIndex(); return new WebInspector.HeapSnapshotArraySlice(this._snapshot._nodes, firstEdgeIndex, firstEdgeIndex + this.edgesCount * this._snapshot._edgeFieldsCount); }, get retainedSize() { return this._nodes[this.nodeIndex + this._snapshot._nodeRetainedSizeOffset]; }, get retainers() { return new WebInspector.HeapSnapshotRetainerEdgeIterator(new WebInspector.HeapSnapshotRetainerEdge(this._snapshot, this._snapshot._retainersForNode(this))); }, get selfSize() { return this._nodes[this.nodeIndex + this._snapshot._nodeSelfSizeOffset]; }, get type() { return this._snapshot._nodeTypes[this._type()]; }, _name: function() { return this._nodes[this.nodeIndex + this._snapshot._nodeNameOffset]; }, get _nodes() { return this._snapshot._nodes; }, _firstEdgeIndex: function() { return this.nodeIndex + this._snapshot._firstEdgeOffset; }, get _nextNodeIndex() { return this._firstEdgeIndex() + this.edgesCount * this._snapshot._edgeFieldsCount; }, _type: function() { return this._nodes[this.nodeIndex + this._snapshot._nodeTypeOffset]; } }; WebInspector.HeapSnapshotNodeIterator = function(node) { this.node = node; } WebInspector.HeapSnapshotNodeIterator.prototype = { first: function() { this.node.nodeIndex = this.node._firstNodeIndex; }, hasNext: function() { return this.node.nodeIndex < this.node._nodes.length; }, get index() { return this.node.nodeIndex; }, set index(newIndex) { this.node.nodeIndex = newIndex; }, get item() { return this.node; }, next: function() { this.node.nodeIndex = this.node._nextNodeIndex; } } WebInspector.HeapSnapshot = function(profile) { this.uid = profile.snapshot.uid; this._nodes = profile.nodes; this._metaNode = profile.metaNode; this._strings = profile.strings; this._init(); } WebInspector.HeapSnapshot.prototype = { _init: function() { this._rootNodeIndex = 1; var meta = this._metaNode; this._nodeTypeOffset = meta.fields.indexOf("type"); this._nodeNameOffset = meta.fields.indexOf("name"); this._nodeIdOffset = meta.fields.indexOf("id"); this._nodeSelfSizeOffset = meta.fields.indexOf("self_size"); this._nodeRetainedSizeOffset = meta.fields.indexOf("retained_size"); this._dominatorOffset = meta.fields.indexOf("dominator"); this._edgesCountOffset = meta.fields.indexOf("children_count"); this._firstEdgeOffset = meta.fields.indexOf("children"); this._nodeFieldCount = this._firstEdgeOffset; this._nodeTypes = meta.types[this._nodeTypeOffset]; this._nodeHiddenType = this._nodeTypes.indexOf("hidden"); this._nodeObjectType = this._nodeTypes.indexOf("object"); this._nodeNativeType = this._nodeTypes.indexOf("native"); this._nodeCodeType = this._nodeTypes.indexOf("code"); this._nodeSyntheticType = this._nodeTypes.indexOf("synthetic"); var edgesMeta = meta.types[this._firstEdgeOffset]; this._edgeFieldsCount = edgesMeta.fields.length; this._edgeTypeOffset = edgesMeta.fields.indexOf("type"); this._edgeNameOffset = edgesMeta.fields.indexOf("name_or_index"); this._edgeToNodeOffset = edgesMeta.fields.indexOf("to_node"); this._edgeTypes = edgesMeta.types[this._edgeTypeOffset]; 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.length; this._edgeTypes.push("invisible"); this._nodeFlags = { // bit flags canBeQueried: 1, detachedDOMTreeNode: 2, }; this._markInvisibleEdges(); this._buildNodeIndex(); this._buildRetainers(); this._buildDominatedNodes(); this._calculateFlags(); this._calculateObjectToWindowDistance(); }, _buildContinuousNodeArray: function() { // Estimate number of nodes. var totalEdgeCount = 0; var totalNodeCount = 0; for (var index = this._rootNodeIndex; index < this._nodes.length; ) { ++totalNodeCount; var edgesCount = this._nodes[index + this._edgesCountOffset]; totalEdgeCount += edgesCount; index += this._firstEdgeOffset + edgesCount * this._edgeFieldsCount; } this._createOnlyNodesArray(totalNodeCount); this._createContainmentEdgesArray(totalEdgeCount); this._createRetainmentEdgesArray(totalNodeCount, totalEdgeCount); this._restoreNodeTypes(); }, _createOnlyNodesArray: function(totalNodeCount) { // Copy nodes to their own array. this._onlyNodes = new Uint32Array(totalNodeCount * this._nodeFieldCount); var dstIndex = 0; var srcIndex = this._rootNodeIndex; while (srcIndex < this._nodes.length) { var srcNodeTypeIndex = srcIndex + this._nodeTypeOffset; var currentDstIndex = dstIndex; var edgesCount = this._nodes[srcIndex + this._edgesCountOffset]; for (var i = 0; i < this._nodeFieldCount; i++) this._onlyNodes[dstIndex++] = this._nodes[srcIndex++]; // Write new node index into the type field. this._nodes[srcNodeTypeIndex] = currentDstIndex; srcIndex += edgesCount * this._edgeFieldsCount; } // Translate dominator indexes. for (var dominatorSlotIndex = this._dominatorOffset; dominatorSlotIndex < this._onlyNodes.length; dominatorSlotIndex += this._nodeFieldCount) { var dominatorIndex = this._onlyNodes[dominatorSlotIndex]; this._onlyNodes[dominatorSlotIndex] = this._nodes[dominatorIndex + this._nodeTypeOffset]; } }, _createContainmentEdgesArray: function(totalEdgeCount) { // Copy edges to their own array. this._containmentEdges = new Uint32Array(totalEdgeCount * this._edgeFieldsCount); var edgeArrayIndex = 0; var srcIndex = this._rootNodeIndex; while (srcIndex < this._nodes.length) { var srcNodeNewIndex = this._nodes[srcIndex + this._nodeTypeOffset]; // Set index of first outgoing egde in the _containmentEdges array. this._onlyNodes[srcNodeNewIndex + this._edgesCountOffset] = edgeArrayIndex; // Now copy all edge information. var edgesCount = this._nodes[srcIndex + this._edgesCountOffset]; srcIndex += this._firstEdgeOffset; var nextNodeIndex = srcIndex + edgesCount * this._edgeFieldsCount; while (srcIndex < nextNodeIndex) { this._containmentEdges[edgeArrayIndex] = this._nodes[srcIndex]; // Translate destination node indexes for the copied edges. if (edgeArrayIndex % this._edgeFieldsCount === this._edgeToNodeOffset) { var toNodeIndex = this._containmentEdges[edgeArrayIndex]; this._containmentEdges[edgeArrayIndex] = this._nodes[toNodeIndex + this._nodeTypeOffset]; } ++edgeArrayIndex; ++srcIndex; } } }, _createRetainmentEdgesArray: function(totalNodeCount, totalEdgeCount) { this._retainingNodes = new Uint32Array(totalEdgeCount); // Index of the first retainer in the _retainingNodes array. Addressed by retained node index. this._firstRetainerIndex = new Uint32Array(totalNodeCount); for (var toNodeIndex = this._edgeToNodeOffset; toNodeIndex < this._containmentEdges.length; toNodeIndex += this._edgeFieldsCount) ++this._firstRetainerIndex[this._containmentEdges[toNodeIndex]]; for (var i = 0, firstUnusedRetainerSlot = 0; i < this._firstRetainerIndex.length; i++) { var retainersCount = this._firstRetainerIndex[i]; this._firstRetainerIndex[i] = firstUnusedRetainerSlot; this._retainingNodes[firstUnusedRetainerSlot] = retainersCount; firstUnusedRetainerSlot += retainersCount; } var srcNodeIndex = 0; var nextNodeFirstEdgeIndex = this._edgesCountOffset; while (srcNodeIndex < this._onlyNodes.length) { var firstEdgeIndex = nextNodeFirstEdgeIndex; var nextNodeIndex = srcNodeIndex + this._nodeFieldCount; var nextNodeFirstEdgeIndex = nextNodeIndex < this._onlyNodes.length ? this._onlyNodes[nextNodeIndex + this._edgesCountOffset] : this._containmentEdges.length; for (var edgeIndex = firstEdgeIndex; edgeIndex < nextNodeFirstEdgeIndex; edgeIndex += this._edgeFieldsCount) { var toNodeIndex = this._containmentEdges[edgeIndex + this._edgeToNodeOffset]; var firstRetainerSlotIndex = this._firstRetainerIndex[toNodeIndex]; var nextUnusedRetainerSlotIndex = firstRetainerSlotIndex + (--this._retainingNodes[firstRetainerSlotIndex]); this._retainingNodes[nextUnusedRetainerSlotIndex] = srcNodeIndex; } srcNodeIndex = nextNodeIndex; } }, _restoreNodeTypes: function() { var srcIndex = this._rootNodeIndex; while (srcIndex < this._nodes.length) { var srcNodeTypeIndex = srcIndex + this._nodeTypeOffset; var newNodeIndex = this._nodes[srcNodeTypeIndex]; this._nodes[srcNodeTypeIndex] = this._onlyNodes[newNodeIndex + this._nodeTypeOffset]; var edgesCount = this._nodes[srcIndex + this._edgesCountOffset]; srcIndex += this._firstEdgeOffset + edgesCount * this._edgeFieldsCount; } }, dispose: function() { delete this._nodes; delete this._strings; delete this._retainers; delete this._retainerIndex; delete this._nodeIndex; if (this._aggregates) { delete this._aggregates; delete this._aggregatesSortedFlags; } delete this._baseNodeIds; delete this._dominatedNodes; delete this._dominatedIndex; delete this._flags; delete this._distancesToWindow; }, get _allNodes() { return new WebInspector.HeapSnapshotNodeIterator(this.rootNode); }, get nodeCount() { return this.nodeIndexes.length - 1; }, nodeFieldValuesByIndex: function(fieldName, indexes) { var node = new WebInspector.HeapSnapshotNode(this); var result = new Array(indexes.length); for (var i = 0, l = indexes.length; i < l; ++i) { node.nodeIndex = indexes[i]; result[i] = node[fieldName]; } return result; }, get rootNode() { return new WebInspector.HeapSnapshotNode(this, this._rootNodeIndex); }, get maxNodeId() { if (typeof this._maxNodeId === "number") return this._maxNodeId; this._maxNodeId = 0; var node = new WebInspector.HeapSnapshotNode(this, this.nodeIndexes[0]); for (var i = 0, l = this.nodeCount; i < l; ++i) { node.nodeIndex = this.nodeIndexes[i]; var id = node.id; if ((id % 2) && id > this._maxNodeId) this._maxNodeId = id; } return this._maxNodeId; }, get rootNodeIndex() { return this._rootNodeIndex; }, get totalSize() { return this.rootNode.retainedSize; }, _retainersForNode: function(node) { var retIndexFrom = this._getRetainerIndex(node.nodeIndex); var retIndexTo = this._getRetainerIndex(node._nextNodeIndex); return new WebInspector.HeapSnapshotArraySlice(this._retainers, retIndexFrom, retIndexTo); }, _dominatedNodesOfNode: function(node) { var dominatedIndexFrom = this._getDominatedIndex(node.nodeIndex); var dominatedIndexTo = this._getDominatedIndex(node._nextNodeIndex); return new WebInspector.HeapSnapshotArraySlice(this._dominatedNodes, dominatedIndexFrom, dominatedIndexTo); }, _flagsOfNode: function(node) { return this._flags[node.nodeIndex]; }, aggregates: function(sortedIndexes, key, filterString) { if (!this._aggregates) { this._aggregates = {}; this._aggregatesSortedFlags = {}; } var aggregates = this._aggregates[key]; if (aggregates) { if (sortedIndexes && !this._aggregatesSortedFlags[key]) { this._sortAggregateIndexes(aggregates); this._aggregatesSortedFlags[key] = sortedIndexes; } return aggregates; } var filter; if (filterString) filter = this._parseFilter(filterString); aggregates = this._buildAggregates(filter); if (sortedIndexes) this._sortAggregateIndexes(aggregates); this._aggregatesSortedFlags[key] = sortedIndexes; this._aggregates[key] = aggregates; return aggregates; }, _buildRetainers: function() { var nodeIndexes = this.nodeIndexes; var nodePositions = this._nodePosition; var nodeCount = this.nodeCount; var nodes = this._nodes; // Builds up two arrays: // - "backRefsArray" is a continuous array, where each node owns an // interval (can be empty) with corresponding back references. // - "indexArray" is an array of indexes in the "backRefsArray" // with the same positions as in the _nodeIndex. var indexArray = this._retainerIndex = new Uint32Array(nodeCount + 1); var edgesCountOffset = this._edgesCountOffset; var firstEdgeOffset = this._firstEdgeOffset; var edgeFieldsCount = this._edgeFieldsCount; var edgeToNodeOffset = this._edgeToNodeOffset; var backRefsCount = 0; // Count the number of retainers for each node for (var i = 0; i < nodeCount; ++i) { var nodeIndex = nodeIndexes[i]; var edgesOffset = nodeIndex + firstEdgeOffset + edgeToNodeOffset; var edgesCount = nodes[nodeIndex + edgesCountOffset]; backRefsCount += edgesCount; for (var j = 0; j < edgesCount; ++j) { var targetNodeIndex = nodes[j * edgeFieldsCount + edgesOffset]; ++indexArray[nodePositions[targetNodeIndex]]; } } var backRefsArray = this._retainers = new Uint32Array(backRefsCount); // Put in the first slot of each backRefsArray slice the count of entries // that will be filled. var backRefsPosition = 0; // The one extra slot in the _retainerIndex array is set to the // end of _retainers array. It is used in the retainers iterator. for (i = 0; i <= nodeCount; ++i) { backRefsCount = backRefsArray[backRefsPosition] = indexArray[i]; indexArray[i] = backRefsPosition; backRefsPosition += backRefsCount; } var retainerIndex = this._retainerIndex; // Fill up the retainers array with indexes of edges. for (var i = 0; i < nodeCount; ++i) { var nodeIndex = nodeIndexes[i]; var edgesOffset = nodeIndex + firstEdgeOffset; var edgesCount = nodes[nodeIndex + edgesCountOffset]; for (var j = 0; j < edgesCount; ++j) { var edgeIndex = j * edgeFieldsCount + edgesOffset; var retNode = nodePositions[nodes[edgeIndex + edgeToNodeOffset]]; var retIndex = indexArray[retNode]; var backRefIndex = retIndex + (--backRefsArray[retIndex]); backRefsArray[backRefIndex] = edgeIndex; } } }, _calculateObjectToWindowDistance: function() { this._distancesToWindow = new Array(this.nodeCount); // bfs for Window roots var list = []; for (var iter = this.rootNode.edges; iter.hasNext(); iter.next()) { var node = iter.edge.node; if (node.isWindow) { list.push(node.nodeIndex); this._distancesToWindow[node.nodeIndex] = 0; } } this._bfs(list); // bfs for root list = []; list.push(this._rootNodeIndex); this._distancesToWindow[this.rootNode.nodeIndex] = 0; this._bfs(list); }, _bfs: function(list) { var index = 0; var nodes = this._nodes; while (index < list.length) { var nodeIndex = list[index++]; // shift generates too much garbage. if (index > 100000) { list = list.slice(index); index = 0; } var distance = this._distancesToWindow[nodeIndex] + 1; var edgesCount = nodes[nodeIndex + this._edgesCountOffset]; var edgeToNodeIndex = nodeIndex + this._firstEdgeOffset + this._edgeToNodeOffset; for (var i = 0; i < edgesCount; ++i) { var childNodeIndex = nodes[edgeToNodeIndex]; edgeToNodeIndex += this._edgeFieldsCount; if (childNodeIndex in this._distancesToWindow) continue; this._distancesToWindow[childNodeIndex] = distance; list.push(childNodeIndex); } } }, _buildAggregates: function(filter) { function shouldSkip(node) { if (filter && !filter(node)) return true; if (!node.isNative && node.selfSize === 0) return true; return false; } var aggregates = {}; var aggregatesByClassName = {}; var node = new WebInspector.HeapSnapshotNode(this, this.nodeIndexes[0]); for (var i = 0, l = this.nodeCount; i < l; ++i) { node.nodeIndex = this.nodeIndexes[i]; if (shouldSkip(node)) continue; var classIndex = node.classIndex; if (!(classIndex in aggregates)) { var nodeType = node.type; var nameMatters = nodeType === "object" || nodeType === "native"; var value = { count: 1, distanceToWindow: node.distanceToWindow, self: node.selfSize, maxRet: 0, type: nodeType, name: nameMatters ? node.name : null, idxs: [node.nodeIndex] }; aggregates[classIndex] = value; aggregatesByClassName[node.className] = value; } else { var clss = aggregates[classIndex]; clss.distanceToWindow = Math.min(clss.distanceToWindow, node.distanceToWindow); ++clss.count; clss.self += node.selfSize; clss.idxs.push(node.nodeIndex); } } // Recursively visit dominators tree and sum up retained sizes // of topmost objects in each class. // This gives us retained sizes for classes. var seenClassNameIndexes = {}; var snapshot = this; function forDominatedNodes(nodeIndex) { var node = new WebInspector.HeapSnapshotNode(snapshot, nodeIndex); var classIndex = node.classIndex; var seen = !!seenClassNameIndexes[classIndex]; if (!seen && classIndex in aggregates && !shouldSkip(node)) { aggregates[classIndex].maxRet += node.retainedSize; seenClassNameIndexes[classIndex] = true; } var dominatedNodes = snapshot._dominatedNodesOfNode(node); for (var i = 0; i < dominatedNodes.length; i++) forDominatedNodes(dominatedNodes.item(i)); seenClassNameIndexes[classIndex] = seen; } forDominatedNodes(this._rootNodeIndex); // Shave off provisionally allocated space. for (var classIndex in aggregates) aggregates[classIndex].idxs = aggregates[classIndex].idxs.slice(0); return aggregatesByClassName; }, _sortAggregateIndexes: function(aggregates) { var nodeA = new WebInspector.HeapSnapshotNode(this); var nodeB = new WebInspector.HeapSnapshotNode(this); 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; }); }, get nodeIndexes() { return this._nodeIndex; }, _buildNodeIndex: function() { var count = 0; for (var i = this._rootNodeIndex, l = this._nodes.length; i < l; ++count) { var edgesCount = this._nodes[i + this._edgesCountOffset]; i += this._firstEdgeOffset + edgesCount * this._edgeFieldsCount; } var nodeIndex = new Uint32Array(count + 1); var nodePosition = {}; count = 0; for (var i = this._rootNodeIndex, l = this._nodes.length; i < l; ++count) { nodeIndex[count] = i; nodePosition[i] = count; var edgesCount = this._nodes[i + this._edgesCountOffset]; i += this._firstEdgeOffset + edgesCount * this._edgeFieldsCount; } nodeIndex[count] = this._nodes.length; nodePosition[this._nodes.length] = count; this._nodeIndex = nodeIndex; this._nodePosition = nodePosition; }, _findNearestNodeIndex: function(index) { var result = binarySearch(index, this._nodeIndex, this._numbersComparator); if (result < 0) { result = -result - 1; nodeIndex = this._nodeIndex[result]; // Binary search can return either maximum lower value, or minimum higher value. if (nodeIndex > index) nodeIndex = this._nodeIndex[result - 1]; } else var nodeIndex = this._nodeIndex[result]; return nodeIndex; }, _getRetainerIndex: function(nodeIndex) { var nodePosition = this._nodePosition[nodeIndex]; return this._retainerIndex[nodePosition]; }, _buildDominatedNodes: function() { var nodeCount = this.nodeCount; // 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._dominatedIndex = new Uint32Array(nodeCount + 1); // Count the number of retainers for each node for (var i = 0; i < nodeCount; ++i) { var nodeIndex = this.nodeIndexes[i]; var dominatorIndex = this._nodes[nodeIndex + this._dominatorOffset]; if (nodeIndex === dominatorIndex) continue; ++indexArray[this._nodePosition[dominatorIndex]]; } var dominatedNodes = this._dominatedNodes = new Uint32Array(nodeCount - 1); // Put in the first slot of each dominatedNodes slice the count of entries // that will be filled. var dominatedPosition = 0; for (i = 0; i <= nodeCount; ++i) { var dominatedCount = dominatedNodes[dominatedPosition] = indexArray[i]; indexArray[i] = dominatedPosition; dominatedPosition += dominatedCount; } // Fill up the dominatedNodes array with indexes of dominated nodes. for (var i = 0; i < nodeCount; ++i) { var nodeIndex = this.nodeIndexes[i]; var dominatorIndex = this._nodes[nodeIndex + this._dominatorOffset]; if (nodeIndex === dominatorIndex) continue; var dominatorPos = this._nodePosition[dominatorIndex]; var dominatedRefIndex = indexArray[dominatorPos]; dominatedRefIndex += (--dominatedNodes[dominatedRefIndex]); dominatedNodes[dominatedRefIndex] = nodeIndex; } }, _getDominatedIndex: function(nodeIndex) { var nodePosition = this._nodePosition[nodeIndex]; return this._dominatedIndex[nodePosition]; }, _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.first(); innerIter.hasNext(); innerIter.next()) { var globalObjEdge = innerIter.edge; if (!globalObjEdge.isShortcut && globalObjEdge.node.isHidden && globalObjEdge._hasStringName && (globalObjEdge._nameOrIndex in propNames)) this._nodes[globalObjEdge._edges._start + globalObjEdge.edgeIndex + this._edgeTypeOffset] = this._edgeInvisibleType; } } }, _numbersComparator: function(a, b) { return a < b ? -1 : (a > b ? 1 : 0); }, _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.isDetachedDOMTreesRoot) { detachedDOMTreesRoot = node; break; } } if (!detachedDOMTreesRoot) return; for (var iter = detachedDOMTreesRoot.edges; iter.hasNext(); iter.next()) { var node = iter.edge.node; if (node.isDetachedDOMTree) { for (var edgesIter = node.edges; edgesIter.hasNext(); edgesIter.next()) this._flags[edgesIter.edge.node.nodeIndex] |= 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 list = []; for (var iter = this.rootNode.edges; iter.hasNext(); iter.next()) { if (iter.edge.node.isWindow) list.push(iter.edge.node.nodeIndex); } var edge = new WebInspector.HeapSnapshotEdge(this); var node = new WebInspector.HeapSnapshotNode(this); while (list.length) { var nodeIndex = list.pop(); if (this._flags[nodeIndex] & flag) continue; node.nodeIndex = nodeIndex; this._flags[nodeIndex] |= flag; var edgesOffset = nodeIndex + this._firstEdgeOffset; var edgesCount = this._nodes[nodeIndex + this._edgesCountOffset]; edge._edges = node.rawEdges; for (var j = 0; j < edgesCount; ++j) { edge.edgeIndex = j * this._edgeFieldsCount; var nodeIndex = this._nodes[edgesOffset + edge.edgeIndex + this._edgeToNodeOffset]; if (this._flags[nodeIndex] & flag) continue; if (edge.isHidden || edge.isInvisible) continue; if (edge.isInternal) continue; var name = edge.name; if (!name) continue; list.push(nodeIndex); } } }, _calculateFlags: function() { this._flags = new Array(this.nodeCount); this._markDetachedDOMTreeNodes(); this._markQueriableHeapObjects(); }, baseSnapshotHasNode: function(baseSnapshotId, className, nodeId) { return this._baseNodeIds[baseSnapshotId][className].binaryIndexOf(nodeId, this._numbersComparator) !== -1; }, pushBaseIds: function(baseSnapshotId, className, nodeIds) { if (!this._baseNodeIds) this._baseNodeIds = []; if (!this._baseNodeIds[baseSnapshotId]) this._baseNodeIds[baseSnapshotId] = {}; this._baseNodeIds[baseSnapshotId][className] = nodeIds; }, createDiff: function(className) { return new WebInspector.HeapSnapshotsDiff(this, className); }, _parseFilter: function(filter) { if (!filter) return null; var parsedFilter = eval("(function(){return " + filter + "})()"); return parsedFilter.bind(this); }, createEdgesProvider: function(nodeIndex, filter) { var node = new WebInspector.HeapSnapshotNode(this, nodeIndex); return new WebInspector.HeapSnapshotEdgesProvider(this, nodeIndex, this._parseFilter(filter), node.edges); }, createRetainingEdgesProvider: function(nodeIndex, filter) { var node = new WebInspector.HeapSnapshotNode(this, nodeIndex); return new WebInspector.HeapSnapshotEdgesProvider(this, nodeIndex, this._parseFilter(filter), node.retainers); }, createNodesProvider: function(filter) { return new WebInspector.HeapSnapshotNodesProvider(this, this._parseFilter(filter)); }, createNodesProviderForClass: function(className, aggregatesKey) { return new WebInspector.HeapSnapshotNodesProvider(this, null, this.aggregates(false, aggregatesKey)[className].idxs); }, createNodesProviderForDominator: function(nodeIndex, filter) { var node = new WebInspector.HeapSnapshotNode(this, nodeIndex); return new WebInspector.HeapSnapshotNodesProvider(this, this._parseFilter(filter), this._dominatedNodesOfNode(node)); }, updateStaticData: function() { return {nodeCount: this.nodeCount, rootNodeIndex: this._rootNodeIndex, totalSize: this.totalSize, uid: this.uid, nodeFlags: this._nodeFlags, maxNodeId: this.maxNodeId}; } }; WebInspector.HeapSnapshotFilteredOrderedIterator = function(iterator, filter, unfilteredIterationOrder) { this._filter = filter; this._iterator = iterator; this._unfilteredIterationOrder = unfilteredIterationOrder; this._iterationOrder = null; this._position = 0; this._currentComparator = null; this._lastComparator = null; } WebInspector.HeapSnapshotFilteredOrderedIterator.prototype = { _createIterationOrder: function() { if (this._iterationOrder) return; if (this._unfilteredIterationOrder && !this._filter) { this._iterationOrder = this._unfilteredIterationOrder.slice(0); this._unfilteredIterationOrder = null; return; } this._iterationOrder = []; var iterator = this._iterator; if (!this._unfilteredIterationOrder && !this._filter) { for (iterator.first(); iterator.hasNext(); iterator.next()) this._iterationOrder.push(iterator.index); } else if (!this._unfilteredIterationOrder) { for (iterator.first(); iterator.hasNext(); iterator.next()) { if (this._filter(iterator.item)) this._iterationOrder.push(iterator.index); } } else { var order = this._unfilteredIterationOrder.constructor === Array ? this._unfilteredIterationOrder : this._unfilteredIterationOrder.slice(0); for (var i = 0, l = order.length; i < l; ++i) { iterator.index = order[i]; if (this._filter(iterator.item)) this._iterationOrder.push(iterator.index); } this._unfilteredIterationOr