UNPKG

occaecatidicta

Version:
1,169 lines (1,009 loc) 45.6 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.HeapSnapshotSortableDataGrid = function(columns) { WebInspector.DataGrid.call(this, columns); this.addEventListener("sorting changed", this.sortingChanged, this); } WebInspector.HeapSnapshotSortableDataGrid.prototype = { dispose: function() { for (var i = 0, l = this.children.length; i < l; ++i) this.children[i].dispose(); }, resetSortingCache: function() { delete this._lastSortColumnIdentifier; delete this._lastSortAscending; }, sortingChanged: function() { var sortAscending = this.sortOrder === "ascending"; var sortColumnIdentifier = this.sortColumnIdentifier; if (this._lastSortColumnIdentifier === sortColumnIdentifier && this._lastSortAscending === sortAscending) return; this._lastSortColumnIdentifier = sortColumnIdentifier; this._lastSortAscending = sortAscending; var sortFields = this._sortFields(sortColumnIdentifier, sortAscending); function SortByTwoFields(nodeA, nodeB) { var field1 = nodeA[sortFields[0]]; var field2 = nodeB[sortFields[0]]; var result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0); if (!sortFields[1]) result = -result; if (result !== 0) return result; field1 = nodeA[sortFields[2]]; field2 = nodeB[sortFields[2]]; result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0); if (!sortFields[3]) result = -result; return result; } this._performSorting(SortByTwoFields); }, _performSorting: function(sortFunction) { this.recursiveSortingEnter(); var children = this.children; this.removeChildren(); children.sort(sortFunction); for (var i = 0, l = children.length; i < l; ++i) { var child = children[i]; var revealed = child.revealed; this.appendChild(child); child.revealed = revealed; if (child.expanded) child.sort(); } this.recursiveSortingLeave(); }, recursiveSortingEnter: function() { if (!("_recursiveSortingDepth" in this)) this._recursiveSortingDepth = 1; else ++this._recursiveSortingDepth; }, recursiveSortingLeave: function() { if (!("_recursiveSortingDepth" in this)) return; if (!--this._recursiveSortingDepth) { delete this._recursiveSortingDepth; this.dispatchEventToListeners("sorting complete"); } } }; WebInspector.HeapSnapshotSortableDataGrid.prototype.__proto__ = WebInspector.DataGrid.prototype; WebInspector.HeapSnapshotContainmentDataGrid = function(columns) { columns = columns || { object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true }, shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true }, retainedSize: { title: WebInspector.UIString("Retained Size"), width: "120px", sortable: true, sort: "descending" } }; WebInspector.HeapSnapshotSortableDataGrid.call(this, columns); } WebInspector.HeapSnapshotContainmentDataGrid.prototype = { _defaultPopulateCount: 100, expandRoute: function(route) { function nextStep(parent, hopIndex) { if (hopIndex >= route.length) { parent.element.scrollIntoViewIfNeeded(true); parent.select(); return; } var nodeIndex = route[hopIndex]; for (var i = 0, l = parent.children.length; i < l; ++i) { var child = parent.children[i]; if (child.snapshotNodeIndex === nodeIndex) { if (child.expanded) nextStep(child, hopIndex + 1); else { function afterExpand() { child.removeEventListener("populate complete", afterExpand, null); var lastChild = child.children[child.children.length - 1]; if (!lastChild.showAll) nextStep(child, hopIndex + 1); else { child.addEventListener("populate complete", afterExpand, null); lastChild.showAll.click(); } } child.addEventListener("populate complete", afterExpand, null); child.expand(); } break; } } } nextStep(this, 0); }, setDataSource: function(snapshotView, snapshot, nodeIndex) { this.snapshotView = snapshotView; this.snapshot = snapshot; this.snapshotNodeIndex = nodeIndex || this.snapshot.rootNodeIndex; this._provider = this._createProvider(snapshot, this.snapshotNodeIndex, this); this.sort(); }, sortingChanged: function() { this.sort(); } }; MixInSnapshotNodeFunctions(WebInspector.HeapSnapshotObjectNode.prototype, WebInspector.HeapSnapshotContainmentDataGrid.prototype); WebInspector.HeapSnapshotContainmentDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype; WebInspector.HeapSnapshotRetainmentDataGrid = function() { this.showRetainingEdges = true; var columns = { object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true }, shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true }, retainedSize: { title: WebInspector.UIString("Retained Size"), width: "120px", sortable: true }, distanceToWindow: { title: WebInspector.UIString("Distance"), width: "80px", sortable: true, sort: "ascending" } }; WebInspector.HeapSnapshotContainmentDataGrid.call(this, columns); } WebInspector.HeapSnapshotRetainmentDataGrid.prototype = { _sortFields: function(sortColumn, sortAscending) { return { object: ["_name", sortAscending, "_count", false], count: ["_count", sortAscending, "_name", true], shallowSize: ["_shallowSize", sortAscending, "_name", true], retainedSize: ["_retainedSize", sortAscending, "_name", true], distanceToWindow: ["_distanceToWindow", sortAscending, "_name", true] }[sortColumn]; }, reset: function() { this.removeChildren(); this.resetSortingCache(); }, } WebInspector.HeapSnapshotRetainmentDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotContainmentDataGrid.prototype; WebInspector.HeapSnapshotConstructorsDataGrid = function() { var columns = { object: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true }, distanceToWindow: { title: WebInspector.UIString("Distance"), width: "90px", sortable: true }, count: { title: WebInspector.UIString("Objects Count"), width: "90px", sortable: true }, shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true }, retainedSize: { title: WebInspector.UIString("Retained Size"), width: "120px", sort: "descending", sortable: true } }; WebInspector.HeapSnapshotSortableDataGrid.call(this, columns); this._filterProfileIndex = -1; } WebInspector.HeapSnapshotConstructorsDataGrid.prototype = { _defaultPopulateCount: 100, _sortFields: function(sortColumn, sortAscending) { return { object: ["_name", sortAscending, "_count", false], distanceToWindow: ["_distanceToWindow", sortAscending, "_retainedSize", true], count: ["_count", sortAscending, "_name", true], shallowSize: ["_shallowSize", sortAscending, "_name", true], retainedSize: ["_retainedSize", sortAscending, "_name", true] }[sortColumn]; }, setDataSource: function(snapshotView, snapshot) { this.snapshotView = snapshotView; this.snapshot = snapshot; if (this._filterProfileIndex === -1) this.populateChildren(); }, populateChildren: function() { function aggregatesReceived(key, aggregates) { for (var constructor in aggregates) this.appendChild(new WebInspector.HeapSnapshotConstructorNode(this, constructor, aggregates[constructor], key)); this.sortingChanged(); } this.dispose(); this.removeChildren(); this.resetSortingCache(); var key = this._filterProfileIndex === -1 ? "allObjects" : this._minNodeId + ".." + this._maxNodeId; var filter = this._filterProfileIndex === -1 ? null : "function(node) { var id = node.id; return id > " + this._minNodeId + " && id <= " + this._maxNodeId + "; }"; this.snapshot.aggregates(false, key, filter, aggregatesReceived.bind(this, key)); }, _filterSelectIndexChanged: function(loader, profileIndex) { this._filterProfileIndex = profileIndex; delete this._maxNodeId; delete this._minNodeId; if (this._filterProfileIndex === -1) { this.populateChildren(); return; } function firstSnapshotLoaded(snapshot) { this._maxNodeId = snapshot.maxNodeId; if (profileIndex > 0) loader(profileIndex - 1, secondSnapshotLoaded.bind(this)); else { this._minNodeId = 0; this.populateChildren(); } } function secondSnapshotLoaded(snapshot) { this._minNodeId = snapshot.maxNodeId; this.populateChildren(); } loader(profileIndex, firstSnapshotLoaded.bind(this)); }, }; WebInspector.HeapSnapshotConstructorsDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype; WebInspector.HeapSnapshotDiffDataGrid = function() { var columns = { object: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true }, addedCount: { title: WebInspector.UIString("# New"), width: "72px", sortable: true }, removedCount: { title: WebInspector.UIString("# Deleted"), width: "72px", sortable: true }, countDelta: { title: "# Delta", width: "64px", sortable: true }, addedSize: { title: WebInspector.UIString("Alloc. Size"), width: "72px", sortable: true, sort: "descending" }, removedSize: { title: WebInspector.UIString("Freed Size"), width: "72px", sortable: true }, sizeDelta: { title: "Size Delta", width: "72px", sortable: true } }; WebInspector.HeapSnapshotSortableDataGrid.call(this, columns); } WebInspector.HeapSnapshotDiffDataGrid.prototype = { _defaultPopulateCount: 50, _sortFields: function(sortColumn, sortAscending) { return { object: ["_name", sortAscending, "_count", false], addedCount: ["_addedCount", sortAscending, "_name", true], removedCount: ["_removedCount", sortAscending, "_name", true], countDelta: ["_countDelta", sortAscending, "_name", true], addedSize: ["_addedSize", sortAscending, "_name", true], removedSize: ["_removedSize", sortAscending, "_name", true], sizeDelta: ["_sizeDelta", sortAscending, "_name", true] }[sortColumn]; }, setDataSource: function(snapshotView, snapshot) { this.snapshotView = snapshotView; this.snapshot = snapshot; }, _baseProfileIndexChanged: function(loader, profileIndex) { loader(profileIndex, this.setBaseDataSource.bind(this)); }, setBaseDataSource: function(baseSnapshot) { this.baseSnapshot = baseSnapshot; this.dispose(); this.removeChildren(); this.resetSortingCache(); if (this.baseSnapshot === this.snapshot) { this.dispatchEventToListeners("sorting complete"); return; } this.populateChildren(); }, populateChildren: function() { function baseAggregatesReceived(baseClasses) { function aggregatesReceived(classes) { var nodeCount = 0; var nodes = []; for (var clss in baseClasses) nodes.push(new WebInspector.HeapSnapshotDiffNode(this, clss, baseClasses[clss], classes[clss])); for (clss in classes) { if (!(clss in baseClasses)) nodes.push(new WebInspector.HeapSnapshotDiffNode(this, clss, null, classes[clss])); } nodeCount = nodes.length; function addNodeIfNonZeroDiff(boundNode, zeroDiff) { if (!zeroDiff) this.appendChild(boundNode); if (!--nodeCount) this.sortingChanged(); } for (var i = 0, l = nodes.length; i < l; ++i) { var node = nodes[i]; node.calculateDiff(this, addNodeIfNonZeroDiff.bind(this, node)); } } this.snapshot.aggregates(true, "allObjects", null, aggregatesReceived.bind(this)); } this.baseSnapshot.aggregates(true, "allObjects", null, baseAggregatesReceived.bind(this)); } }; WebInspector.HeapSnapshotDiffDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype; WebInspector.HeapSnapshotDominatorsDataGrid = function() { var columns = { object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true }, shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true }, retainedSize: { title: WebInspector.UIString("Retained Size"), width: "120px", sort: "descending", sortable: true } }; WebInspector.HeapSnapshotSortableDataGrid.call(this, columns); } WebInspector.HeapSnapshotDominatorsDataGrid.prototype = { _defaultPopulateCount: 25, setDataSource: function(snapshotView, snapshot) { this.snapshotView = snapshotView; this.snapshot = snapshot; this.snapshotNodeIndex = this.snapshot.rootNodeIndex; this._provider = this._createProvider(snapshot, this.snapshotNodeIndex); this.sort(); }, sortingChanged: function() { this.sort(); } }; MixInSnapshotNodeFunctions(WebInspector.HeapSnapshotDominatorObjectNode.prototype, WebInspector.HeapSnapshotDominatorsDataGrid.prototype); WebInspector.HeapSnapshotDominatorsDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype; WebInspector.DetailedHeapshotView = function(parent, profile) { WebInspector.View.call(this); this.element.addStyleClass("detailed-heapshot-view"); this.parent = parent; this.parent.addEventListener("profile added", this._updateBaseOptions, this); this.parent.addEventListener("profile added", this._updateFilterOptions, this); this.viewsContainer = document.createElement("div"); this.viewsContainer.addStyleClass("views-container"); this.element.appendChild(this.viewsContainer); this.containmentView = new WebInspector.View(); this.containmentView.element.addStyleClass("view"); this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid(); this.containmentDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true); this.containmentDataGrid.show(this.containmentView.element); this.containmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); this.constructorsView = new WebInspector.View(); this.constructorsView.element.addStyleClass("view"); this.constructorsView.element.appendChild(this._createToolbarWithClassNameFilter()); this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid(); this.constructorsDataGrid.element.addStyleClass("class-view-grid"); this.constructorsDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true); this.constructorsDataGrid.show(this.constructorsView.element); this.constructorsDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); this.diffView = new WebInspector.View(); this.diffView.element.addStyleClass("view"); this.diffView.element.appendChild(this._createToolbarWithClassNameFilter()); this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid(); this.diffDataGrid.element.addStyleClass("class-view-grid"); this.diffDataGrid.show(this.diffView.element); this.diffDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); this.dominatorView = new WebInspector.View(); this.dominatorView.element.addStyleClass("view"); this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid(); this.dominatorDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true); this.dominatorDataGrid.show(this.dominatorView.element); this.dominatorDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); this.retainmentViewHeader = document.createElement("div"); this.retainmentViewHeader.addStyleClass("retainers-view-header"); this.retainmentViewHeader.addEventListener("mousedown", this._startRetainersHeaderDragging.bind(this), true); var retainingPathsTitleDiv = document.createElement("div"); retainingPathsTitleDiv.className = "title"; var retainingPathsTitle = document.createElement("span"); retainingPathsTitle.textContent = WebInspector.UIString("Object's retaining tree"); retainingPathsTitleDiv.appendChild(retainingPathsTitle); this.retainmentViewHeader.appendChild(retainingPathsTitleDiv); this.element.appendChild(this.retainmentViewHeader); this.retainmentView = new WebInspector.View(); this.retainmentView.element.addStyleClass("view"); this.retainmentView.element.addStyleClass("retaining-paths-view"); this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainmentDataGrid(); this.retainmentDataGrid.element.addEventListener("click", this._mouseClickInRetainmentGrid.bind(this), true); this.retainmentDataGrid.show(this.retainmentView.element); this.retainmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._inspectedObjectChanged, this); this.retainmentView.show(this.element); this.retainmentDataGrid.reset(); this.dataGrid = this.constructorsDataGrid; this.currentView = this.constructorsView; this.viewSelectElement = document.createElement("select"); this.viewSelectElement.className = "status-bar-item"; this.viewSelectElement.addEventListener("change", this._changeView.bind(this), false); this.views = [{title: "Summary", view: this.constructorsView, grid: this.constructorsDataGrid}, {title: "Comparison", view: this.diffView, grid: this.diffDataGrid}, {title: "Containment", view: this.containmentView, grid: this.containmentDataGrid}, {title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid}]; this.views.current = 0; for (var i = 0; i < this.views.length; ++i) { var view = this.views[i]; var option = document.createElement("option"); option.label = WebInspector.UIString(view.title); this.viewSelectElement.appendChild(option); } this._profileUid = profile.uid; this.baseSelectElement = document.createElement("select"); this.baseSelectElement.className = "status-bar-item hidden"; this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false); this._updateBaseOptions(); this.filterSelectElement = document.createElement("select"); this.filterSelectElement.className = "status-bar-item"; this.filterSelectElement.addEventListener("change", this._changeFilter.bind(this), false); this._updateFilterOptions(); this.helpButton = new WebInspector.StatusBarButton("", "heapshot-help-status-bar-item status-bar-item"); this.helpButton.addEventListener("click", this._helpClicked.bind(this), false); this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true); this._loadProfile(this._profileUid, profileCallback.bind(this)); function profileCallback() { var list = this._profiles(); var profileIndex; for (var i = 0; i < list.length; ++i) { if (list[i].uid === this._profileUid) { profileIndex = i; break; } } if (profileIndex > 0) this.baseSelectElement.selectedIndex = profileIndex - 1; else this.baseSelectElement.selectedIndex = profileIndex; this.dataGrid.setDataSource(this, this.profileWrapper); } } WebInspector.DetailedHeapshotView.prototype = { dispose: function() { this.profileWrapper.dispose(); if (this.baseProfile) this.baseProfileWrapper.dispose(); this.containmentDataGrid.dispose(); this.constructorsDataGrid.dispose(); this.diffDataGrid.dispose(); this.dominatorDataGrid.dispose(); this.retainmentDataGrid.dispose(); }, get statusBarItems() { return [this.viewSelectElement, this.baseSelectElement, this.filterSelectElement, this.helpButton.element]; }, get profile() { return this.parent.getProfile(WebInspector.DetailedHeapshotProfileType.TypeId, this._profileUid); }, get profileWrapper() { return this.profile.proxy; }, get baseProfile() { return this.parent.getProfile(WebInspector.DetailedHeapshotProfileType.TypeId, this._baseProfileUid); }, get baseProfileWrapper() { return this.baseProfile.proxy; }, wasShown: function() { if (!this.profileWrapper.loaded) this._loadProfile(this._profileUid, profileCallback1.bind(this)); else profileCallback1.call(this); function profileCallback1() { if (this.baseProfile && !this.baseProfileWrapper.loaded) this._loadProfile(this._baseProfileUid, profileCallback2.bind(this)); else profileCallback2.call(this); } function profileCallback2() { this.currentView.show(this.viewsContainer); } }, willHide: function() { this._currentSearchResultIndex = -1; this._popoverHelper.hidePopover(); if (this.helpPopover && this.helpPopover.visible) this.helpPopover.hide(); }, onResize: function() { var height = this.retainmentView.element.clientHeight; this._updateRetainmentViewHeight(height); }, searchCanceled: function() { if (this._searchResults) { for (var i = 0; i < this._searchResults.length; ++i) { var node = this._searchResults[i].node; delete node._searchMatched; node.refresh(); } } delete this._searchFinishedCallback; this._currentSearchResultIndex = -1; this._searchResults = []; }, performSearch: function(query, finishedCallback) { // Call searchCanceled since it will reset everything we need before doing a new search. this.searchCanceled(); query = query.trim(); if (!query.length) return; if (this.currentView !== this.constructorsView && this.currentView !== this.diffView) return; this._searchFinishedCallback = finishedCallback; function matchesByName(gridNode) { return ("name" in gridNode) && gridNode.name.hasSubstring(query, true); } function matchesById(gridNode) { return ("snapshotNodeId" in gridNode) && gridNode.snapshotNodeId === query; } var matchPredicate; if (query.charAt(0) !== "@") matchPredicate = matchesByName; else { query = parseInt(query.substring(1), 10); matchPredicate = matchesById; } function matchesQuery(gridNode) { delete gridNode._searchMatched; if (matchPredicate(gridNode)) { gridNode._searchMatched = true; gridNode.refresh(); return true; } return false; } var current = this.dataGrid.children[0]; var depth = 0; var info = {}; // Restrict to type nodes and instances. const maxDepth = 1; while (current) { if (matchesQuery(current)) this._searchResults.push({ node: current }); current = current.traverseNextNode(false, null, (depth >= maxDepth), info); depth += info.depthChange; } finishedCallback(this, this._searchResults.length); }, jumpToFirstSearchResult: function() { if (!this._searchResults || !this._searchResults.length) return; this._currentSearchResultIndex = 0; this._jumpToSearchResult(this._currentSearchResultIndex); }, jumpToLastSearchResult: function() { if (!this._searchResults || !this._searchResults.length) return; this._currentSearchResultIndex = (this._searchResults.length - 1); this._jumpToSearchResult(this._currentSearchResultIndex); }, jumpToNextSearchResult: function() { if (!this._searchResults || !this._searchResults.length) return; if (++this._currentSearchResultIndex >= this._searchResults.length) this._currentSearchResultIndex = 0; this._jumpToSearchResult(this._currentSearchResultIndex); }, jumpToPreviousSearchResult: function() { if (!this._searchResults || !this._searchResults.length) return; if (--this._currentSearchResultIndex < 0) this._currentSearchResultIndex = (this._searchResults.length - 1); this._jumpToSearchResult(this._currentSearchResultIndex); }, showingFirstSearchResult: function() { return (this._currentSearchResultIndex === 0); }, showingLastSearchResult: function() { return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); }, _jumpToSearchResult: function(index) { var searchResult = this._searchResults[index]; if (!searchResult) return; var node = searchResult.node; node.revealAndSelect(); }, refreshVisibleData: function() { var child = this.dataGrid.children[0]; while (child) { child.refresh(); child = child.traverseNextNode(false, null, true); } }, _changeBase: function() { if (this._baseProfileUid === this._profiles()[this.baseSelectElement.selectedIndex].uid) return; this._baseProfileUid = this._profiles()[this.baseSelectElement.selectedIndex].uid; this.dataGrid._baseProfileIndexChanged(this._loadProfileByIndex.bind(this), this.baseSelectElement.selectedIndex); if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) return; // The current search needs to be performed again. First negate out previous match // count by calling the search finished callback with a negative number of matches. // Then perform the search again with the same query and callback. this._searchFinishedCallback(this, -this._searchResults.length); this.performSearch(this.currentQuery, this._searchFinishedCallback); }, _changeFilter: function() { var profileIndex = this.filterSelectElement.selectedIndex - 1; this.dataGrid._filterSelectIndexChanged(this._loadProfileByIndex.bind(this), profileIndex); if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) return; // The current search needs to be performed again. First negate out previous match // count by calling the search finished callback with a negative number of matches. // Then perform the search again with the same query and callback. this._searchFinishedCallback(this, -this._searchResults.length); this.performSearch(this.currentQuery, this._searchFinishedCallback); }, _createToolbarWithClassNameFilter: function() { var toolbar = document.createElement("div"); toolbar.addStyleClass("class-view-toolbar"); var classNameFilter = document.createElement("input"); classNameFilter.addStyleClass("class-name-filter"); classNameFilter.setAttribute("placeholder", WebInspector.UIString("Class filter")); classNameFilter.addEventListener("keyup", this._changeNameFilter.bind(this, classNameFilter), false); toolbar.appendChild(classNameFilter); return toolbar; }, _changeNameFilter: function(classNameInputElement) { var filter = classNameInputElement.value.toLowerCase(); var children = this.dataGrid.children; for (var i = 0, l = children.length; i < l; ++i) { var node = children[i]; if (node.depth === 0) node.revealed = node._name.toLowerCase().indexOf(filter) !== -1; } }, _profiles: function() { return WebInspector.panels.profiles.getProfiles(WebInspector.DetailedHeapshotProfileType.TypeId); }, _loadProfile: function(profileUid, callback) { WebInspector.panels.profiles.loadHeapSnapshot(profileUid, callback); }, _loadProfileByIndex: function(profileIndex, callback) { var profileUid = this._profiles()[profileIndex].uid; WebInspector.panels.profiles.loadHeapSnapshot(profileUid, callback); }, isDetailedSnapshot: function(snapshot) { var s = new WebInspector.HeapSnapshot(snapshot); for (var iter = s.rootNode.edges; iter.hasNext(); iter.next()) if (iter.edge.node.name === "(GC roots)") return true; return false; }, processLoadedSnapshot: function(profile, snapshot) { profile.nodes = snapshot.nodes; profile.strings = snapshot.strings; var s = new WebInspector.HeapSnapshot(profile); profile.sidebarElement.subtitle = Number.bytesToString(s.totalSize); }, _selectionChanged: function(event) { var selectedNode = event.target.selectedNode; this._setRetainmentDataGridSource(selectedNode); this._inspectedObjectChanged(event); }, _inspectedObjectChanged: function(event) { var selectedNode = event.target.selectedNode; if (selectedNode instanceof WebInspector.HeapSnapshotGenericObjectNode) ConsoleAgent.addInspectedHeapObject(selectedNode.snapshotNodeId); }, _setRetainmentDataGridSource: function(nodeItem) { if (nodeItem && nodeItem.snapshotNodeIndex) this.retainmentDataGrid.setDataSource(this, nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex, nodeItem.isDeletedNode ? this.baseSelectElement.childNodes[this.baseSelectElement.selectedIndex].label + " | " : ""); else this.retainmentDataGrid.reset(); }, _mouseDownInContentsGrid: function(event) { if (event.detail < 2) return; var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("shallowSize-column") && !cell.hasStyleClass("retainedSize-column"))) return; event.consume(true); }, _mouseClickInRetainmentGrid: function(event) { var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); if (!cell || (!cell.hasStyleClass("path-column"))) return; var row = event.target.enclosingNodeOrSelfWithNodeName("tr"); var nodeItem = row._dataGridNode; if (!nodeItem || !nodeItem.route) return; function expandRoute() { this.dataGrid.expandRoute(nodeItem.route); } this.changeView("Containment", expandRoute.bind(this)); }, changeView: function(viewTitle, callback) { var viewIndex = null; for (var i = 0; i < this.views.length; ++i) if (this.views[i].title === viewTitle) { viewIndex = i; break; } if (this.views.current === viewIndex) { setTimeout(callback, 0); return; } var grid = this.views[viewIndex].grid; function sortingComplete() { grid.removeEventListener("sorting complete", sortingComplete, this); setTimeout(callback, 0); } this.views[viewIndex].grid.addEventListener("sorting complete", sortingComplete, this); this.viewSelectElement.selectedIndex = viewIndex; this._changeView({target: {selectedIndex: viewIndex}}); }, _changeView: function(event) { if (!event || !this._profileUid) return; if (event.target.selectedIndex === this.views.current) return; this.views.current = event.target.selectedIndex; this.currentView.detach(); var view = this.views[this.views.current]; this.currentView = view.view; this.dataGrid = view.grid; this.currentView.show(this.viewsContainer); this.refreshVisibleData(); this.dataGrid.updateWidths(); if (this.currentView === this.diffView) { this.baseSelectElement.removeStyleClass("hidden"); if (!this.dataGrid.snapshotView) { this._changeBase(); this.dataGrid.setDataSource(this, this.profileWrapper); } } else { this.baseSelectElement.addStyleClass("hidden"); if (!this.dataGrid.snapshotView) this.dataGrid.setDataSource(this, this.profileWrapper); } if (this.currentView === this.constructorsView) this.filterSelectElement.removeStyleClass("hidden"); else this.filterSelectElement.addStyleClass("hidden"); if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) return; // The current search needs to be performed again. First negate out previous match // count by calling the search finished callback with a negative number of matches. // Then perform the search again the with same query and callback. this._searchFinishedCallback(this, -this._searchResults.length); this.performSearch(this.currentQuery, this._searchFinishedCallback); }, _getHoverAnchor: function(target) { var span = target.enclosingNodeOrSelfWithNodeName("span"); if (!span) return; var row = target.enclosingNodeOrSelfWithNodeName("tr"); if (!row) return; var gridNode = row._dataGridNode; if (!gridNode.hasHoverMessage) return; span.node = gridNode; return span; }, _resolveObjectForPopover: function(element, showCallback, objectGroupName) { element.node.queryObjectContent(showCallback, objectGroupName); }, _helpClicked: function(event) { if (!this._helpPopoverContentElement) { var refTypes = ["a:", "console-formatted-name", WebInspector.UIString("property"), "0:", "console-formatted-name", WebInspector.UIString("element"), "a:", "console-formatted-number", WebInspector.UIString("context var"), "a:", "console-formatted-null", WebInspector.UIString("system prop")]; var objTypes = [" a ", "console-formatted-object", "Object", "\"a\"", "console-formatted-string", "String", "/a/", "console-formatted-string", "RegExp", "a()", "console-formatted-function", "Function", "a[]", "console-formatted-object", "Array", "num", "console-formatted-number", "Number", " a ", "console-formatted-null", "System"]; var contentElement = document.createElement("table"); contentElement.className = "heapshot-help"; var headerRow = document.createElement("tr"); var propsHeader = document.createElement("th"); propsHeader.textContent = WebInspector.UIString("Property types:"); headerRow.appendChild(propsHeader); var objsHeader = document.createElement("th"); objsHeader.textContent = WebInspector.UIString("Object types:"); headerRow.appendChild(objsHeader); contentElement.appendChild(headerRow); var len = Math.max(refTypes.length, objTypes.length); for (var i = 0; i < len; i += 3) { var row = document.createElement("tr"); var refCell = document.createElement("td"); if (refTypes[i]) appendHelp(refTypes, i, refCell); row.appendChild(refCell); var objCell = document.createElement("td"); if (objTypes[i]) appendHelp(objTypes, i, objCell); row.appendChild(objCell); contentElement.appendChild(row); } this._helpPopoverContentElement = contentElement; this.helpPopover = new WebInspector.Popover(); function appendHelp(help, index, cell) { var div = document.createElement("div"); div.className = "source-code event-properties"; var name = document.createElement("span"); name.textContent = help[index]; name.className = help[index + 1]; div.appendChild(name); var desc = document.createElement("span"); desc.textContent = " " + help[index + 2]; div.appendChild(desc); cell.appendChild(div); } } if (this.helpPopover.visible) this.helpPopover.hide(); else this.helpPopover.show(this._helpPopoverContentElement, this.helpButton.element); }, _startRetainersHeaderDragging: function(event) { if (!this.isShowing()) return; WebInspector.elementDragStart(this.retainmentViewHeader, this._retainersHeaderDragging.bind(this), this._endRetainersHeaderDragging.bind(this), event, "row-resize"); this._previousDragPosition = event.pageY; event.consume(); }, _retainersHeaderDragging: function(event) { var height = this.retainmentView.element.clientHeight; height += this._previousDragPosition - event.pageY; this._previousDragPosition = event.pageY; this._updateRetainmentViewHeight(height); event.consume(true); }, _endRetainersHeaderDragging: function(event) { WebInspector.elementDragEnd(event); delete this._previousDragPosition; event.consume(); }, _updateRetainmentViewHeight: function(height) { height = Number.constrain(height, Preferences.minConsoleHeight, this.element.clientHeight - Preferences.minConsoleHeight); this.viewsContainer.style.bottom = (height + this.retainmentViewHeader.clientHeight) + "px"; this.retainmentView.element.style.height = height + "px"; this.retainmentViewHeader.style.bottom = height + "px"; }, _updateBaseOptions: function() { var list = this._profiles(); // We're assuming that snapshots can only be added. if (this.baseSelectElement.length === list.length) return; for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) { var baseOption = document.createElement("option"); var title = list[i].title; if (!title.indexOf(UserInitiatedProfileName)) title = WebInspector.UIString("Snapshot %d", title.substring(UserInitiatedProfileName.length + 1)); baseOption.label = title; this.baseSelectElement.appendChild(baseOption); } }, _updateFilterOptions: function() { var list = this._profiles(); // We're assuming that snapshots can only be added. if (this.filterSelectElement.length - 1 === list.length) return; if (!this.filterSelectElement.length) { var filterOption = document.createElement("option"); filterOption.label = WebInspector.UIString("All objects"); this.filterSelectElement.appendChild(filterOption); } for (var i = this.filterSelectElement.length - 1, n = list.length; i < n; ++i) { var filterOption = document.createElement("option"); var title = list[i].title; if (!title.indexOf(UserInitiatedProfileName)) { if (!i) title = WebInspector.UIString("Objects allocated before Snapshot %d", title.substring(UserInitiatedProfileName.length + 1)); else title = WebInspector.UIString("Objects allocated between Snapshots %d and %d", title.substring(UserInitiatedProfileName.length + 1) - 1, title.substring(UserInitiatedProfileName.length + 1)); } filterOption.label = title; this.filterSelectElement.appendChild(filterOption); } } }; WebInspector.DetailedHeapshotView.prototype.__proto__ = WebInspector.View.prototype; WebInspector.settings.showHeapSnapshotObjectsHiddenProperties = WebInspector.settings.createSetting("showHeaSnapshotObjectsHiddenProperties", false); WebInspector.DetailedHeapshotProfileType = function() { WebInspector.ProfileType.call(this, WebInspector.DetailedHeapshotProfileType.TypeId, WebInspector.UIString("Take Heap Snapshot")); } WebInspector.DetailedHeapshotProfileType.TypeId = "HEAP"; WebInspector.DetailedHeapshotProfileType.prototype = { get buttonTooltip() { return WebInspector.UIString("Take heap snapshot."); }, buttonClicked: function() { WebInspector.panels.profiles.takeHeapSnapshot(); }, get treeItemTitle() { return WebInspector.UIString("HEAP SNAPSHOTS"); }, get description() { return WebInspector.UIString("Heap snapshot profiles show memory distribution among your page's JavaScript objects and related DOM nodes."); }, createSidebarTreeElementForProfile: function(profile) { return new WebInspector.ProfileSidebarTreeElement(profile, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item"); }, createView: function(profile) { return new WebInspector.DetailedHeapshotView(WebInspector.panels.profiles, profile); } } WebInspector.DetailedHeapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;