/* * 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.HeapSnapshotGridNode = function(tree, hasChildren) { WebInspector.DataGridNode.call(this, null, hasChildren); this._defaultPopulateCount = tree._defaultPopulateCount; this._provider = null; this.addEventListener("populate", this._populate, this); } WebInspector.HeapSnapshotGridNode.prototype = { createCell: function(columnIdentifier) { var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); if (this._searchMatched) cell.addStyleClass("highlight"); return cell; }, _populate: function(event) { WebInspector.PleaseWaitMessage.prototype.startAction(this.dataGrid.element, doPopulate.bind(this)); function doPopulate() { this.removeEventListener("populate", this._populate, this); function sorted(ignored) { this.populateChildren(); } this._provider.sortAndRewind(this.comparator(), sorted.bind(this)); } }, populateChildren: function(provider, howMany, atIndex, afterPopulate, suppressNotifyAboutCompletion) { if (!howMany && provider) { howMany = provider.instanceCount; provider.instanceCount = 0; } provider = provider || this._provider; if (!("instanceCount" in provider)) provider.instanceCount = 0; howMany = howMany || this._defaultPopulateCount; atIndex = atIndex || this.children.length; var haveSavedChildren = !!this._savedChildren; if (haveSavedChildren) { haveSavedChildren = false; for (var c in this._savedChildren) { haveSavedChildren = true; break; } } function childrenRetrieved(items, hasNext, length) { for (var i = 0, l = items.length; i < l; ++i) { var item = items[i]; if (haveSavedChildren) { var hash = this._childHashForEntity(item); if (hash in this._savedChildren) { this.insertChild(this._savedChildren[hash], atIndex++); continue; } } this.insertChild(this._createChildNode(item, provider), atIndex++); } provider.instanceCount += items.length; if (hasNext) this.insertChild(new WebInspector.ShowMoreDataGridNode(this.populateChildren.bind(this, provider), this._defaultPopulateCount, length), atIndex++); if (afterPopulate) afterPopulate(); if (!suppressNotifyAboutCompletion) { function notify() { this.dispatchEventToListeners("populate complete"); } setTimeout(notify.bind(this), 0); } } provider.getNextItems(howMany, childrenRetrieved.bind(this)); }, _saveChildren: function() { this._savedChildren = {}; for (var i = 0, childrenCount = this.children.length; i < childrenCount; ++i) { var child = this.children[i]; if (child.expanded) this._savedChildren[this._childHashForNode(child)] = child; } }, sort: function() { function doSort() { function afterSort(sorted) { if (!sorted) return; this._saveChildren(); this.removeChildren(); function afterPopulate() { for (var i = 0, l = this.children.length; i < l; ++i) { var child = this.children[i]; if (child.expanded) child.sort(); } this.dataGrid.dispatchEventToListeners("sorting complete"); } this.populateChildren(this._provider, null, null, afterPopulate.bind(this)); } this._provider.sortAndRewind(this.comparator(), afterSort.bind(this)); } this.dataGrid.dispatchEventToListeners("start sorting"); WebInspector.PleaseWaitMessage.prototype.startAction(this.dataGrid.element, doSort.bind(this)); } }; WebInspector.HeapSnapshotGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; WebInspector.HeapSnapshotGenericObjectNode = function(tree, node) { WebInspector.HeapSnapshotGridNode.call(this, tree, false); this._name = node.name; this._type = node.type; this._shallowSize = node.selfSize; this._retainedSize = node.retainedSize; this.snapshotNodeId = node.id; this.snapshotNodeIndex = node.nodeIndex; }; WebInspector.HeapSnapshotGenericObjectNode.prototype = { createCell: function(columnIdentifier) { var cell = columnIdentifier !== "object" ? WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier) : this._createObjectCell(); if (this._searchMatched) cell.addStyleClass("highlight"); return cell; }, _createObjectCell: function() { var cell = document.createElement("td"); cell.className = "object-column"; var div = document.createElement("div"); div.className = "source-code event-properties"; div.style.overflow = "hidden"; var data = this.data["object"]; if (this._prefixObjectCell) this._prefixObjectCell(div, data); var valueSpan = document.createElement("span"); valueSpan.className = "value console-formatted-" + data.valueStyle; valueSpan.textContent = data.value; div.appendChild(valueSpan); cell.appendChild(div); cell.addStyleClass("disclosure"); if (this.depth) cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px"); return cell; }, get _countPercent() { return this._count / this.dataGrid.snapshot.nodeCount * 100.0; }, get data() { var data = this._emptyData(); var value = this._name; var valueStyle = "object"; switch (this._type) { case "string": value = "\"" + value + "\""; valueStyle = "string"; break; case "regexp": value = "/" + value + "/"; valueStyle = "string"; break; case "closure": value = "function " + value + "()"; valueStyle = "function"; break; case "number": valueStyle = "number"; break; case "hidden": valueStyle = "null"; break; case "array": value += "[]"; break; }; data["object"] = { valueStyle: valueStyle, value: value + " @" + this.snapshotNodeId }; var view = this.dataGrid.snapshotView; data["shallowSize"] = view.showShallowSizeAsPercent ? WebInspector.UIString("%.2f%%", this._shallowSizePercent) : Number.bytesToString(this._shallowSize); data["retainedSize"] = view.showRetainedSizeAsPercent ? WebInspector.UIString("%.2f%%", this._retainedSizePercent) : Number.bytesToString(this._retainedSize); return this._enhanceData ? this._enhanceData(data) : data; }, get _retainedSizePercent() { return this._retainedSize / this.dataGrid.snapshot.totalSize * 100.0; }, get _shallowSizePercent() { return this._shallowSize / this.dataGrid.snapshot.totalSize * 100.0; }, _updateHasChildren: function() { function isEmptyCallback(isEmpty) { this.hasChildren = !isEmpty; } this._provider.isEmpty(isEmptyCallback.bind(this)); } } WebInspector.HeapSnapshotGenericObjectNode.prototype.__proto__ = WebInspector.HeapSnapshotGridNode.prototype; WebInspector.HeapSnapshotObjectNode = function(tree, edge) { WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, edge.node); this._referenceName = edge.name; this._referenceType = edge.type; this._provider = this._createProvider(tree.snapshot, edge.nodeIndex); this._updateHasChildren(); } WebInspector.HeapSnapshotObjectNode.prototype = { _createChildNode: function(item) { return new WebInspector.HeapSnapshotObjectNode(this.dataGrid, item); }, _createProvider: function(snapshot, nodeIndex) { var showHiddenData = WebInspector.DetailedHeapshotView.prototype.showHiddenData; return snapshot.createEdgesProvider( nodeIndex, function(edge) { return !edge.isInvisible && (showHiddenData || (!edge.isHidden && !edge.node.isHidden)); }); }, _childHashForEntity: function(edge) { return edge.type + "#" + edge.name; }, _childHashForNode: function(childNode) { return childNode._referenceType + "#" + childNode._referenceName; }, comparator: function() { var sortAscending = this.dataGrid.sortOrder === "ascending"; 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] }[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false]; return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); }, _emptyData: function() { return {count:"", addedCount: "", removedCount: "", countDelta:"", addedSize: "", removedSize: "", sizeDelta: ""}; }, _enhanceData: function(data) { var name = this._referenceName; if (name === "") name = "(empty)"; var nameClass = "name"; switch (this._referenceType) { case "context": nameClass = "console-formatted-number"; break; case "internal": case "hidden": nameClass = "console-formatted-null"; break; } data["object"].nameClass = nameClass; data["object"].name = name; return data; }, _prefixObjectCell: function(div, data) { var nameSpan = document.createElement("span"); nameSpan.className = data.nameClass; nameSpan.textContent = data.name; var separatorSpan = document.createElement("span"); separatorSpan.className = "separator"; separatorSpan.textContent = ": "; div.appendChild(nameSpan); div.appendChild(separatorSpan); } } WebInspector.HeapSnapshotObjectNode.prototype.__proto__ = WebInspector.HeapSnapshotGenericObjectNode.prototype; WebInspector.HeapSnapshotInstanceNode = function(tree, baseSnapshot, snapshot, node) { WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node); this._isDeletedNode = !!baseSnapshot; this._provider = this._createProvider(baseSnapshot || snapshot, node.nodeIndex); this._updateHasChildren(); }; WebInspector.HeapSnapshotInstanceNode.prototype = { _createChildNode: function(item) { return new WebInspector.HeapSnapshotObjectNode(this.dataGrid, item); }, _createProvider: function(snapshot, nodeIndex) { var showHiddenData = WebInspector.DetailedHeapshotView.prototype.showHiddenData; return snapshot.createEdgesProvider( nodeIndex, function(edge) { return !edge.isInvisible && (showHiddenData || (!edge.isHidden && !edge.node.isHidden)); }); }, _childHashForEntity: function(edge) { return edge.type + "#" + edge.name; }, _childHashForNode: function(childNode) { return childNode._referenceType + "#" + childNode._referenceName; }, comparator: function() { var sortAscending = this.dataGrid.sortOrder === "ascending"; var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; var sortFields = { object: ["!edgeName", 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.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); }, _emptyData: function() { return {count:"", countDelta:"", sizeDelta: ""}; }, _enhanceData: function(data) { if (this._isDeletedNode) { data["addedCount"] = ""; data["addedSize"] = ""; data["removedCount"] = "\u2022"; data["removedSize"] = Number.bytesToString(this._shallowSize); } else { data["addedCount"] = "\u2022"; data["addedSize"] = Number.bytesToString(this._shallowSize); data["removedCount"] = ""; data["removedSize"] = ""; } return data; } } WebInspector.HeapSnapshotInstanceNode.prototype.__proto__ = WebInspector.HeapSnapshotGenericObjectNode.prototype; WebInspector.HeapSnapshotConstructorNode = function(tree, className, aggregate) { WebInspector.HeapSnapshotGridNode.call(this, tree, aggregate.count > 0); this._name = className; this._count = aggregate.count; this._shallowSize = aggregate.self; this._retainedSize = aggregate.maxRet; this._provider = this._createNodesProvider(tree.snapshot, aggregate.type, className); } WebInspector.HeapSnapshotConstructorNode.prototype = { _createChildNode: function(item) { return new WebInspector.HeapSnapshotInstanceNode(this.dataGrid, null, this.dataGrid.snapshot, item); }, _createNodesProvider: function(snapshot, nodeType, nodeClassName) { return snapshot.createNodesProvider( function (node) { return node.type === nodeType && (nodeClassName === null || node.className === nodeClassName); }); }, comparator: function() { var sortAscending = this.dataGrid.sortOrder === "ascending"; var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; var sortFields = { object: ["id", sortAscending, "retainedSize", false], count: ["id", true, "retainedSize", false], shallowSize: ["selfSize", sortAscending, "id", true], retainedSize: ["retainedSize", sortAscending, "id", true] }[sortColumnIdentifier]; return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); }, _childHashForEntity: function(node) { return node.id; }, _childHashForNode: function(childNode) { return childNode.snapshotNodeId; }, get data() { var data = {object: this._name, count: this._count}; var view = this.dataGrid.snapshotView; data["count"] = view.showCountAsPercent ? WebInspector.UIString("%.2f%%", this._countPercent) : this._count; data["shallowSize"] = view.showShallowSizeAsPercent ? WebInspector.UIString("%.2f%%", this._shallowSizePercent) : Number.bytesToString(this._shallowSize); data["retainedSize"] = "> " + (view.showRetainedSizeAsPercent ? WebInspector.UIString("%.2f%%", this._retainedSizePercent) : Number.bytesToString(this._retainedSize)); return data; }, get _countPercent() { return this._count / this.dataGrid.snapshot.nodeCount * 100.0; }, get _retainedSizePercent() { return this._retainedSize / this.dataGrid.snapshot.totalSize * 100.0; }, get _shallowSizePercent() { return this._shallowSize / this.dataGrid.snapshot.totalSize * 100.0; } }; WebInspector.HeapSnapshotConstructorNode.prototype.__proto__ = WebInspector.HeapSnapshotGridNode.prototype; WebInspector.HeapSnapshotIteratorsTuple = function(it1, it2) { this._it1 = it1; this._it2 = it2; } WebInspector.HeapSnapshotIteratorsTuple.prototype = { sortAndRewind: function(comparator, callback) { function afterSort(ignored) { this._it2.sortAndRewind(comparator, callback); } this._it1.sortAndRewind(comparator, afterSort.bind(this)); } }; WebInspector.HeapSnapshotDiffNode = function(tree, className, baseAggregate, aggregate) { WebInspector.HeapSnapshotGridNode.call(this, tree, true); this._name = className; this._baseIndexes = baseAggregate ? baseAggregate.idxs : []; this._indexes = aggregate ? aggregate.idxs : []; this._provider = this._createNodesProvider(tree.baseSnapshot, tree.snapshot, aggregate ? aggregate.type : baseAggregate.type, className); } WebInspector.HeapSnapshotDiffNode.prototype = { calculateDiff: function(dataGrid, callback) { var diff = dataGrid.snapshot.createDiff(this._name); function diffCalculated(diffResult) { this._diff = diffResult; this._baseIndexes = null; this._indexes = null; callback(this._diff.addedSize === 0 && this._diff.removedSize === 0); } function baseSelfSizesReceived(baseSelfSizes) { diff.pushBaseSelfSizes(baseSelfSizes); diff.calculate(diffCalculated.bind(this)); } function baseIdsReceived(baseIds) { diff.pushBaseIds(dataGrid.baseSnapshot.uid, baseIds); dataGrid.snapshot.pushBaseIds(dataGrid.baseSnapshot.uid, this._name, baseIds); dataGrid.baseSnapshot.nodeFieldValuesByIndex("selfSize", this._baseIndexes, baseSelfSizesReceived.bind(this)); } function idsReceived(ids) { dataGrid.baseSnapshot.pushBaseIds(dataGrid.snapshot.uid, this._name, ids); } dataGrid.baseSnapshot.nodeFieldValuesByIndex("id", this._baseIndexes, baseIdsReceived.bind(this)); dataGrid.snapshot.nodeFieldValuesByIndex("id", this._indexes, idsReceived.bind(this)); }, _createChildNode: function(item, provider) { if (provider === this._provider._it1) return new WebInspector.HeapSnapshotInstanceNode(this.dataGrid, null, provider.snapshot, item); else return new WebInspector.HeapSnapshotInstanceNode(this.dataGrid, provider.snapshot, null, item); }, _createNodesProvider: function(baseSnapshot, snapshot, nodeType, nodeClassName) { var className = this._name; return new WebInspector.HeapSnapshotIteratorsTuple( createProvider(snapshot, baseSnapshot), createProvider(baseSnapshot, snapshot)); function createProvider(snapshot, otherSnapshot) { var otherSnapshotId = otherSnapshot.uid; var provider = snapshot.createNodesProvider( function (node) { return node.type === nodeType && (nodeClassName === null || node.className === nodeClassName) && !this.baseSnapshotHasNode(otherSnapshotId, className, node.id); }); provider.snapshot = snapshot; return provider; } }, _childHashForEntity: function(node) { return node.id; }, _childHashForNode: function(childNode) { return childNode.snapshotNodeId; }, comparator: function() { var sortAscending = this.dataGrid.sortOrder === "ascending"; var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; var sortFields = { object: ["id", sortAscending, "selfSize", false], addedCount: ["selfSize", sortAscending, "id", true], removedCount: ["selfSize", sortAscending, "id", true], countDelta: ["selfSize", sortAscending, "id", true], addedSize: ["selfSize", sortAscending, "id", true], removedSize: ["selfSize", sortAscending, "id", true], sizeDelta: ["selfSize", sortAscending, "id", true] }[sortColumnIdentifier]; return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); }, populateChildren: function(provider, howMany, atIndex, afterPopulate) { if (!provider && !howMany) { var firstProviderPopulated = function() { WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it2, this._defaultPopulateCount, atIndex, afterPopulate); }; WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it1, this._defaultPopulateCount, atIndex, firstProviderPopulated.bind(this), true); } else if (!howMany) { var firstProviderPopulated = function() { WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it2, null, atIndex, afterPopulate); }; WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it1, null, atIndex, firstProviderPopulated.bind(this), true); } else WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, provider, howMany, atIndex, afterPopulate); }, _signForDelta: function(delta) { if (delta === 0) return ""; if (delta > 0) return "+"; else return "\u2212"; // Math minus sign, same width as plus. }, get data() { var data = {object: this._name}; data["addedCount"] = this._diff.addedCount; data["removedCount"] = this._diff.removedCount; var countDelta = this._diff.countDelta; data["countDelta"] = WebInspector.UIString("%s%d", this._signForDelta(countDelta), Math.abs(countDelta)); data["addedSize"] = Number.bytesToString(this._diff.addedSize); data["removedSize"] = Number.bytesToString(this._diff.removedSize); var sizeDelta = this._diff.sizeDelta; data["sizeDelta"] = WebInspector.UIString("%s%s", this._signForDelta(sizeDelta), Number.bytesToString(Math.abs(sizeDelta))); return data; } }; WebInspector.HeapSnapshotDiffNode.prototype.__proto__ = WebInspector.HeapSnapshotGridNode.prototype; WebInspector.HeapSnapshotDominatorObjectNode = function(tree, node) { WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node); this._provider = this._createProvider(tree.snapshot, node.nodeIndex); this._updateHasChildren(); }; WebInspector.HeapSnapshotDominatorObjectNode.prototype = { _createChildNode: function(item) { return new WebInspector.HeapSnapshotDominatorObjectNode(this.dataGrid, item); }, _createProvider: function(snapshot, nodeIndex) { var showHiddenData = WebInspector.DetailedHeapshotView.prototype.showHiddenData; return snapshot.createNodesProvider( function (node) { var dominatorIndex = node.dominatorIndex; return dominatorIndex === nodeIndex && dominatorIndex !== node.nodeIndex && (showHiddenData || !node.isHidden); }); }, _childHashForEntity: function(node) { return node.id; }, _childHashForNode: function(childNode) { return childNode.snapshotNodeId; }, comparator: function() { var sortAscending = this.dataGrid.sortOrder === "ascending"; var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; var sortFields = { object: ["id", sortAscending, "retainedSize", false], shallowSize: ["selfSize", sortAscending, "id", true], retainedSize: ["retainedSize", sortAscending, "id", true] }[sortColumnIdentifier]; return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); }, _emptyData: function() { return {}; } }; WebInspector.HeapSnapshotDominatorObjectNode.prototype.__proto__ = WebInspector.HeapSnapshotGenericObjectNode.prototype; function MixInSnapshotNodeFunctions(sourcePrototype, targetPrototype) { targetPrototype._childHashForEntity = sourcePrototype._childHashForEntity; targetPrototype._childHashForNode = sourcePrototype._childHashForNode; targetPrototype.comparator = sourcePrototype.comparator; targetPrototype._createChildNode = sourcePrototype._createChildNode; targetPrototype._createProvider = sourcePrototype._createProvider; targetPrototype.populateChildren = sourcePrototype.populateChildren; targetPrototype._saveChildren = sourcePrototype._saveChildren; targetPrototype.sort = sourcePrototype.sort; }